aboutsummaryrefslogtreecommitdiff
path: root/sys/fio/doc/fio.hlp
blob: 1f87049f65bd6a776333be40f9bf5d1f10e34dba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912

.help fio Jan83 "File i/o Design Rev.5"
.tp 30
.sh
STRUCTURE OF THE BASIC FILE I/O PROCEDURES

     The high level FIO input procedures are GETC, GETLINE, and READ.
These procedures read directly out of the "current buffer".  When the
buffer is exhausted, FILBUF is called to refill the buffer.  The action
taken by FILBUF depends on whether the file contains text or binary data,
but does not depend on the characteristics of the device on which the
file is resident.  The output procedures are similar to the input
procedures, except that FLSBUF is called to flush the buffer when it fills.




.ks
.nf
	              getc     getline     read



		               filbuf


              text files                     binary files

	             zgettx    fmkbfs     ffault



	          Structure of the Input Procedures
.fi
.ke





.ks
.nf
		      putc     putline     write




		               flsbuf


	        text files                 binary files


	             zputtx    fmkbfs     ffault



	          Structure of the Output Procedures
.fi
.ke


The "file fault" procedure (FFAULT) is called by both FILBUF and FLSBUF
for binary files, when the referenced data lies outside the range of
the current buffer.



.ks
.nf
 	                        ffault



	                      
	             ffilbf     frelnk      fflsbf




                          fwatio      fbseek



	       aread      await      aseek/anote     awrite



	       zaread     zawait     zseek/znote     zawrite



	        FIO Structure for Accessing Binary Files
.fi
.ke


In the above structure chart, the "z" routines at the lowest level
are system and device dependent, and are actually part of the system
interface, rather than FIO.  A separate set of z-routines is required
for each device serviced by FIO (regular binary files, the CL interface,
pipes, magtapes, memory, etc.).

All of the system and device dependence of FIO is concentrated
into the z-routines.  Only the routines AREAD, AWRITE, and AWAIT know
that more than one type of binary device is serviced by FIO.  Furthermore,
FIO maintains a device table containing the entry point addresses of
the z-routines for each device.  This provides a clean interface to the
device dependent routines, and makes it possible to add new devices
without editing the source for FIO.  In fact, it is possible to interface
new devices to FIO dynamically, at run time.


.tp 10
.sh
SEMICODE FOR THE BASIC FILE I/O PROCEDURES

     The procedures GETC and PUTC read and write character data, a single
character at a time.  Since these procedures may be called once for each
character in a file, they must be as efficient (ergo, simple) as feasible.
These machine code for these routines should be hand optimized if much
text processing (i.e. compilations) is anticipated.



.nf
.tp 5
int procedure getc (fd, ch)			# get character

begin
	if (iop < bufptr || iop >= itop)	# buffer exhausted?
	    switch (filbuf(fd)) {
	    case EOF:
		return (EOF)
	    case ERR:
		take error action
	    }

	ch = Mem[iop]
	iop = iop + 1

	return (ch)
end



.tp 5
procedure putc (fd, ch)				# put character

begin
	if (iop < bufptr || iop >= otop) {	# buffer full?
	    if (flsbuf (fd) == ERR)
		take error action
	}

	Mem[iop] = ch
	iop = iop + 1

	if (ch == newline) {			# end of line?
	    if (flush on newline is enabled for this file)
		if (flsbuf (fd) == ERR)
		    take error action
	}
end
.fi


Characters and strings (and even binary data) may be "pushed back" into
the input stream.  UNGETC pushes a single character.  Subsequent calls
to GETC, GETLINE, READ, etc. will read out the characters in the order
in which they were pushed (first in, first out).  When all of the
pushback data has been read, reading resumes at the preceeding file
position, which may either be in one of the primary buffers, or an
earlier state in the pushback buffer.

UNGETS differs from UNGETC in that it pushes back whole strings,
in a last in, first out fashion.  UNGETS is used to implement recursive
macro expansions.  The amount of recursion permitted may be specified
after the file is opened, and before any data is pushed back.  Recursion
is limited by the size of the input pointer stack, and pushback capacity
by the size of the pushback buffer.


.tp 5
.nf
procedure ungetc (fd, ch)		# push back a character

begin	
	if (iop < bufptr || iop >= otop) {
	    if (no pushback buffer)
		create pushback buffer
	    else
		error: "pushback buffer overflow"

	    stack old iop, itop

	    set iop to point at beginning of the pushback buffer,
	    set itop to iop, otop to top of pushback buffer.
	}

	Mem[iop] = ch
	iop = iop + 1
	itop = itop + 1
end



.tp 5
procedure ungets (fd, str)		# recursively push back a string

begin
	if (iop < bufptr || iop >= otop) {
	    if (no pushback buffer) {
		create pushback buffer
		setup iop, buftop for pushback buffer
	    } else
		error: "pushback buffer overflow"
	}

	stack old iop, itop
	copy string to Mem[iop], advance iop
	itop = iop
end
.fi



Calls to GETLINE may be intermixed with calls to GETC, READ, and so on.
If, however, only GETLINE is used to access a file, and the associated
file is a text file, a file buffer will never need to be created (the
data will be placed directly in the user buffer instead).
If a buffer has been created and is not yet empty, GETLINE will read the
remainder of the current line from that buffer, before again calling FILBUF.

The newline character is returned as part of the line.  The maximum size
of a line (size of a line buffer) is set at compile time by the system
wide constant SZ_LINE.  The constant SZ_LINE includes space for the newline
character, but not for the EOS marker (character array dimensions never
include space for the EOS, because the preprocessor automatically allows an
extra character for the EOS when dimensioning the array for Fortran).
.nf


.tp 5
int procedure getline (fd, linebuf)		# get a line from file

begin
	op = 1
	if (buffer is empty and file type is TEXT_FILE) {
	    # call ZGETTX to copy line directly into user linebuf
	    zgettx (channel(fd), linebuf, status)

	} else {
	    while (op <= SZ_LINE) {
		if (iop < bufptr || iop >= itop) {
		    status = filbuf (fd)
		    if (status == ERR || status == EOF)
			break
		}
		
		linebuf[op] = Mem[iop]
		iop = iop + 1
		op = op + 1

		if (the character was newline)
		    break
	    }
	    linebuf[op] = EOS
	}

	if (status == ERR)
	    take error action
	else if (op == 1)
	    return (EOF)
	else
	    return (op - 1)			# number of chars
end




.tp 5
procedure putline (fd, linebuf)			# put a line to file

begin
	for (i=1;  linebuf[i] != EOS;  i=i+1) {
	    if (iop < bufptr || iop >= otop)
		if (flsbuf (fd) == ERR)
		    take error action
	    }

	    Mem[iop] = linebuf[i]
	    iop = iop + 1

	    if (the character is newline) {
		if (flush on newline is enabled)
		    if (flsbuf (fd) == ERR)
			take error action
	    }
	}
end



.fi
The READ procedure reads a maximum of MAXCHARS characters from the file
FD into the user supplied buffer BUFFER.  In the case of block structured
devices, READ will continue to read blocks from the file until the output
buffer has filled.  In the case of record structured devices (i.e., terminals,
text files, pipes) READ will read at most one record, after exhausting the
contents of the file buffer.



.tp 5
.nf
int procedure read (fd, buffer, maxchars)

begin
	check that fd is a valid file opened for reading
	nchars = 0

	while (nchars <= maxchars) {
	    if (iop < bufptr || iop >= itop) {
		switch (filbuf(fd)) {
		case EOF:
		    break
		case ERR:
		    take error action
		default:
		    # don't loop if record structured device or EOF
		    if (nchars read != buffer size)
			maxchars = min (maxchars, nchars + nchars read)
		}
	    }
	    chunk = min (maxchars - nchar, itop - iop)
	    if (chunk <= 0)
		break
	    else {
		amovc (Memc[iop], buffer[nchars+1], chunk)
		iop = iop + chunk
		nchars = nchars + chunk
	    }
	}

	if (nchars == 0)
	    return (EOF)
	else
	    return (nchars)
end




.tp 5
procedure write (fd, buffer, maxchars)

begin
	check that fd is a valid file opened for writing
	nchars = 0

	while (nchars <= maxchars) {
	    if (iop < bufptr || iop >= otop) {
		if (flsbuf (fd) == ERR)
		    take error action
	    }
	    chunk = min (maxchars - nchar, otop - iop)
	    if (chunk <= 0)
		break
	    else {
		amovc (buffer[nchars+1], Mem[iop], chunk)
		iop = iop + chunk
		nchars = nchars + chunk
	    }
	}
end




.tp 5
int procedure filbuf (fd)

begin
	verify fd: file open with read permission

	if (iop points into pushback buffer) {
	    pop state off pushback stack
	    return (itop - bufptr)
	    # eventually end up back in a real file buffer
	} else if (no buffers) {
	    call fmkbfs to allocate buffer space for the file
	    # fmkbfs must adjust iop to reflect current file position
	}

	if (TEXT_FILE)
	    zgettx (fd, file_buffer, nchars)
	else
	    nchars = ffault (fd, logical_offset_in_file)

	iop = bufptr
	itop = max (bufptr, bufptr + nchars)
	otop = bufptr

	return (nchars)
end




.tp 5
int procedure flsbuf (fd)

begin
	verify fd: file open with write permission
	if (no buffers)
	    call fmkbfs to allocate buffer space

	if (otop = bufptr) {
	    set otop to top of buffer
	    status = OK
	} else if (TEXT_FILE) {
	    zputtx (channel[fd], file_buffer, status)
	    reset iop to start of buffer
	} else {
	    status = ffault (fd, logical_offset)
	}

	return (status)
end
.fi


.sh
Buffer Management for Binary Files

     FIO maintains a "current buffer" for each file.  A "file pointer"
is also maintained for each file.  The file pointer is the character offset
within the file at which the next i/o transfer will occur.  When the file
pointer no longer points into the current buffer, a "file fault" occurs.
The file pointer is modified when, and only when, an i/o transfer or seek
occurs.

All i/o to binary files is routed through FFAULT.  FILBUF and FLSBUF handle
i/o to text files directly.

FFAULT makes a binary file appear to be a contiguous array (stream) of
characters, regardless of the device on which the file is resident, and
regardless of the block size.  Image i/o and structure i/o depend on the
buffer management capabilities of FFAULT for efficient i/o.

FFAULT must be able to deal with variable block size devices.  The block
size is a run time variable, which is device dependent.
Magtapes and Mem files, for example, have a block size of one char,
whereas most disks have 256 char blocks (assuming two machine bytes per char).

Image i/o requires that the number and size of the buffers for a file
be variable, and that asynchronous i/o be possible.  The size of a
buffer, and the size of the data segment to be read in (normally one
row in the case of two dimensional imagefiles) need not be the same.

Structure or virtual i/o is based on a global pool of buffers, shared
amongst all the files currently mapped for virtual i/o.  Each buffer
in the pool is always linked into the list for the global pool, and is
also linked into the local list for a file, when containing data from
that file.  New buffers are allocated from the tail of the global list.

The virtual i/o primitives interface to file i/o via READ and WRITE
requests on a mapped file.  FFAULT is required to manage the global pool
properly when faulting on a mapped file.  The number and size of the
buffers in the global pool are run time variables.

FFAULT calculates the file offset of the new buffer implied by the offset
argument (note that offset may be BOF or EOF as well as a real offset).
No actual i/o takes place if the data is already buffered.



.tp 5
.nf
int procedure ffault (fd, char_offset)

fd:		file descriptor number
char_offset:	desired char offset in file

begin
	calculate buffer_offset (modulus block size)
	if (i/o in progress on file fd)
	    wait for completion (awatio)

	if (buffer is already in local pool)
	    relink buffer at head of list (frelnk)
	else {
	    if (buffer has been written into)
		flush to file (fflsbf)
	    relink next buffer at head of lists (frelnk)
	    set buffer offset for new buffer
	    fill buffer from file (ffilbf)
	}

	if (file is being accessed sequentially)
	    initiate write behind or read ahead

	set iop corresponding to desired char_offset
	return (status: OK, ERR, or EOF)
end
.fi


.sh
Verification of the File Fault Procedure

     The database managed by FFAULT consists of the local and global
buffer lists, and the file descriptor structure.  The major types of
file access are (1) sequential read, (2) write at EOF, (3) random
access, and (4) sequential write not at EOF.  A mode change may occur
at any time.  In what follows, we follow the logic of FFAULT through
for these four modes of access, to verify that FFAULT works properly
in each case.

.tp 4
.ls 4 Case 1: Sequential Read

FFAULT will detect the sequential nature of the read requests, and will
begin reading ahead asychronously.  No writing occurs, since the buffer
is never written into.  If a buffer were to be written into, the subsequent
write i/o operation would cause read ahead to be interrupted for a time
(random mode would be asserted temporarily).

.ks
.nf
    normally, read ahead will be in progress
    wait for i/o
    buffer is now in pool
    relink buffer at head of lists
    initiate i/o on next available buffer

    when EOF is detected, buffer is zeroed, EOF is returned
.fi
.ke
.le

.tp 4
.ls Case 2: Sequential Write at EOF

When writing at EOF, FFAULT will detect the fact that the writes are
occurring sequentially, and will start flushing the newly filled buffers
asynchronously.  Read ahead does not occur, since the file is positioned
at EOF.

.ks
.nf
    normally, write behind will be in progress
    wait for i/o
    get next buffer (will not need to be flushed, due to
	automatic write behind)
    relink buffer at head of lists
    fill buffer (no actual file access when at EOF)
    initiate write behind of most recent buffer
.fi
.ke
.le

.tp 4
.ls Case 3: Random Access

Old buffer is left in pool.  No i/o is done on the old buffer, regardless
of whether the old buffer has been written into or not (unless there is only
one buffer in the pool).  The buffer pool serves as a cache, with the buffers
linked in order of most recent access.  Read ahead and write behind do not
occur as long as the pattern of access remains random.

.ks
.nf
    no i/o in progress
    buffer not in pool
    take buffer from tail of list
    relink buffer at head of lists
    if (buffer needs to be flushed)
	flush it, wait for completion
    fill buffer
.fi
.ke
.le

.tp 4
.ls Case 4: Sequential Write not at EOF

This mode differs from write at EOF in that read and write i/o operations
are interspersed.  Since only one i/o operation can be in effect on a
given file at one time, we cannot both read ahead and write behind.
Write behind will occur, but reading will not be asynchrounous.

.ks
.nf
    wait for i/o
    buffer not in pool
    take buffer from tail of list
    relink buffer at head of lists
    buffer will not need to be flushed, due to write behind
    fill buffer, wait for completion
    initiate write behind of most recent buffer
.fi
.ke
.le




.fi
In certain circumstances, such as when IMIO overwrites a line of an
image, where each line is known to be aligned on a block boundary,
the "fill buffer" operation can be omitted (since it is guaranteed
that the entire contents of the buffer will be overwritten before the
buffer is flushed).  The fill buffer operation is disabled via an FSET
option.  Both access modes 3 and 4 are affected, yielding a factor
of two reduction in the number of i/o transfers.




.tp 5
.nf
procedure ffilbf (fd, bufdes)

fd:	file descriptor number
bufdes:	buffer descriptor

begin
	if (at EOF)
	    return
	else {
	    if (io in progress on file fd)
		call fwatio to wait for completion of transfer
	    fbseek (fd, bufdes)
	    aread (fd, Memc[bufptr], buffer_size)

	    set i/o mode word in buffer descriptor
	    set pointer to active buffer in file descriptor
	}
end



.fi
The FFLSBF routine is called by FFAULT to actually flush a buffer to
the file.  Note that if the buffer is at the end of the file, and the
buffer is only partially full, a partially full block will be written.
If partial file blocks are not permitted by the underlying system,
the z-routine must compensate.



.tp 6
.nf
procedure fflsbf (fd, bufdes)

fd:	file descriptor number
bufdes:	buffer descriptor

begin
	if (no write permission on file)
	    take error action
	if (io in progress on file fd)
	    call fwatio to wait for completion of transfer

	nchars = max (iop, itop) - bufptr
	fbseek (fd, bufdes)
	awrite (fd, Memc[bufptr], nchars)

	set i/o mode word in buffer descriptor
	set pointer to active buffer in file descriptor
end




.tp 5
procedure fwatio (fd)

begin
	if (i/o mode == NULL)
	    return
	nchars = await (fd)

	if (nchars == ERR)
	    set ERROR bit in status word
	else {
	    # set i/o pointers in buffer descriptor
	    if (i/o mode == READ_IN_PROGRESS)
		itop = bufptr + nchars
	    else
		# don't change itop, data still valid
	    otop = bufptr
	    clear i/o mode word in buffer descriptor
	    clear pointer to active buffer in file descriptor
	}
end




.tp 5
procedure fbseek (fd, bufdes)

begin
	if (current_offset != buffer_offset)
	    aseek (fd, buffer_offset)
end




.fi
SEEK is used to move the file pointer (offset in a file at which the
next data transfer will occur).  With text files, one can only seek
to the start of a line, the position of which must have been determined
by a prior call to NOTE.  For binary files, SEEK merely sets the logical
offset within the file.  This will usually cause a file fault when the
next i/o transfer occurs.  An actual physical seek does not occur until
the fault occurs.

The logical offset is the character offset in the file at which the next
i/o transfer will occur.  In general, there is no simple relationship
between the logical offset and the actual physical offset in the file.
The physical offset is the file offset at which the next AREAD or AWRITE
transfer will occur, and is maintained by those routines and by the system.
The logical offset may be set to any character in a file.  The physical
offset is always a multiple of the device block size.

The logical offset is defined at all times by the offset of the current
buffer (buf_offset), and by the offset within the buffer (iop-bufptr).
The logical offset may take on the special values BOF and EOF.
Since the offset of the first character in a file is one (1),
and BOF and EOF are zero or negative, the special offsets are unambiguous.

.rj (logical offset)
	new iop = offset - buf_offset + bufptr

A logical seek on a binary file is effected merely by setting the in-buffer
pointer IOP according to the relation shown above.  A macro LSEEK (fd, offset)
is defined to perform a logical seek with inline code.
.nf



.tp 5
procedure seek (fd, offset)

begin
	verify that fd is a legal file descriptor of an open file
	clear any pushback

	# make newly written data readable
	itop = max (itop, iop)

	if (TEXT_FILE) {
	    if (buffer has been written into)
		call zputtx to flush buffer to file
	    reset iop to beginning of buffer
	    if (offset is not equal to offset of buffer)
		call zsektx routine to seek on text file
	} else
	    lseek (fd, offset)
end




.tp 5
long procedure note (fd)	# note file position for later seek

begin
	verify that fd is a legal file descriptor of an open file

	if (TEXT_FILE) {
	    call znottx to get offset into text file
	    if (a buffer is in use)
		save offset of buffer in buffer descriptor
	    return (offset)
	} else
	    return (logical offset)
end




.tp 5
procedure flush (fd)

begin
	verify fd: file open with write permission

	if (TEXT_FILE)
	    if (buffer has been written into) {
		call zputtx to write out buffer
		reset buffer pointers
	    }
	else
	    for (each buffer in local pool)
		if (buffer has been written into)
		    call fflsbf to flush buffer
end




.fi
The asynchronous i/o primitives ZAREAD and ZAWRIT must enforce device block
boundaries.  Thus, if maxchars is not an integral multiple of the block size,
the file pointer will nonetheless be advanced to the next block boundary.
Some files (such as Mem files and magtapes) may have a block size of one char.

Note that memory may be accessed as a "file".  This facility is most often
used by the formatted i/o routines, to decode and encode character data in
strings.  On a virtual memory machine, an entire binary file could be mapped
into memory, then opened with MEMOPEN as a memory resident file (this would
in effect replaces the FFAULT file faults by hardware page faults).

The calling program is required to call AWAIT after an AREAD or AWRITE call to
a file, before issuing the next i/o request to that file.  Failure to do so
causes an error action to be taken.  This is done to ensure that the success
or failure of the i/o transfer (the status returned by AWAIT) is checked by
the calling program.

The z-routines ZCALL2 and ZCALL3 are machine dependent routines which
call the procedure whose entry point address is given as the first argument.
The numeric suffix N means that the procedure given as the first argument is
to be called with N arguments, the values of which make up the remaining
arguments to ZCALL.  The additional machine dependence of this routine
is thought to be more than justified by the clean, flexible interface
which it provides between FIO and the various supported devices.
.nf



.tp 5
procedure aread (fd, buffer, maxchars)

begin
	check that fd is a valid file opened for reading
	if (i/o is already in progress on file fd)
	    error: "i/o already in progress"
	set read_in_progress word in file descriptor

	zcall3 (zaread[fd], channel[fd], buffer, maxchars)
end



.fi
Note that FIO, when it seeks to the end of a file for a buffered binary
write, actually seeks to the nearest block boundary preceeding the physical
EOF which is an integral multiple of the file buffer size.  When the file
buffer fills, it is flushed out, OVERWRITING THE EOF.  This may pose problems
for the implementor of the ZAWRITE routine on some systems.



.tp 5
.nf
procedure awrite (fd, buffer, maxchars)

begin
	check that fd is a valid file opened for writing
	if (i/o is already in progress on file fd)
	    error: "i/o already in progress"
	set write_in_progress in i/o mode word in file descriptor

	zcall3 (zawrite[fd], channel[fd], buffer, maxchars)
end




.tp 5
int procedure await (fd)

begin
	verify thaf fd is a legal file descriptor of an open file

	if (bad error code in file descriptor)
	    set status to ERR
	else if (no io in progress on file fd)
	    return (0)
	else
	    zcall2 (zawait[fd], channel[fd], status)

	switch (status) {
	case ERR:
	    set error code in file descriptor
	case EOF:
	    set EOF flag
	default:
	    increment file position counter by N file blocks
	    set nchars_last_transfer in file descriptor
	}

	clear io_in_progress word in file descriptor
	return (status)
end




.tp 5
procedure aseek (fd, offset)

begin
	switch (offset) {
	case BOF:
	    char_offset = 1
	    clear at EOF flag
	case EOF:
	    if (already at EOF)
		return
	    else {
		zcall2 (zaseek[fd], channel[fd], EOF)
		current_offset = anote (fd)
		char_offset = current_offset
		set at EOF flag
	    }
	default:
	    char_offset = offset
	    clear at EOF flag
	}

	# can seek only to the beginning of a device block
	block_offset = char_offset - mod (char_offset-1, block_size)

	zcall2 (zaseek[fd], channel[fd], block_offset)
	if (anote(fd) != block_offset)
	    take error action
end



.tp 5
long procedure anote (fd)

begin
	zcall2 (zanote[fd], channel[fd], current_offset)
	return (current_offset)
end
.fi



.sh
Z-ROUTINES REQUIRED TO INTERFACE TO A BINARY DEVICE

     The interface between FIO and a binary device is defined by a set of
six so called z-routines.  These routines may be as device and system
dependent as necessary, provided the standard calling sequences and semantics
are implemented.

The following z-routines are required for each device serviced by FIO.
Since only the entry point addresses are given to FIO, the actual names
are arbitrary, but must be distinct to avoid collisions.  The names shown
are reserved.

.ks
.nf
	zaread	(channel, buffer, maxchars)
	zawrit	(channel, buffer, maxchars)
	zawait	(channel, nchars/EOF/ERR)
	zaseek	(channel, char_offset/BOF/EOF)
	zanote	(channel, char_offset)
	zblksz	(channel, device_block_size_in_chars)
.fi
.ke

The exact specifications of these routines will be detailed in the system
interface documentation.


The following binary devices are fully supported by the program interface:


.ks
.nf
		device type			initialization

	regular random access binary files	OPEN
	the CL interface (STDIN,STDOUT,...)	task startup
	pipes					CONNECT
	memory					MEMOPEN
	magnetic tapes				MTOPEN
	graphics devices			GOPEN
.fi
.ke


A new device may be interfaced to FIO at run time with the procedure FIODEV.
Repetitive calls to FIODEV for the same device are harmless and are
ignored.  The maximum number of devices that may be interfaced to FIO is set
when FIO is compiled.  An error action will occur if this number is exceedd.

	fiodev	(zaread, zawrit, zawait, zaseek, zanote, zblksz)

The purpose of FIODEV is to make the entry points of the z-routines for the
new device known to FIO.  The device table is indexed by the entry point
address of the ZAREAD procedure, which must therefore be distinct for each
device.

A default device is associated with a file when the file is opened.
To specify a device other than the default device requires a call to FSET,
passing the entry point address of the ZAREAD procedure for the device.
The device must have been installed with the FIODEV call by the time FSET
is called to associate the device with a particular file, or an error action
will result.


.sh
SEMICODE FOR THE FIO INITIALIZATION AND CONTROL PROCEDURES

     Before any i/o can be done on a file, the file must be opened.  The
standard OPEN procedure may be used to access ordinary files containing either
text or binary data.  To access a file on one of the special devices, a special
open procedure must be used (MEMOPEN, MTOPEN, ..).

All file open procedures are alike in that they call the FIO routine
FGETFD to allocate and initialize (with defaults) a file descriptor.
Assorted calls to FSET and possibly FIODEV may optionally follow,
if the default file parameters are not applicable to the device in question.




.ks
.nf
		     open                       close




                          fgetfd     frtnfd     flush




       zmapfn  zopen      malloc     mfree                  zclose



	       Structure of the Initialization Procedures
.fi
.ke




.tp 5
.nf
int procedure open (file, mode, type)

file:	file name (EOS terminated character string)
mode:	type of access permission desired
type:	file type (text or binary)

begin
	# allocate and initialize file descriptor
	fd = fgetfd (file, mode, type)
	if (fd == ERR) {
	    set error code in file descriptor
	    return (ERR)
	}

	# map virtual file name to OS file name
	zmapfn (file, osfname, SZ_OSFNAME)

	switch (type) {			# open file
	case TEXT_FILE:
	    zopntx (osfname, mode, channel[fd])
	case BINARY_FILE:
	    zopenb (osfname, mode, channel[fd])
	default:
	    set error code in file descriptor
	    channel[fd] = ERR
	}

	if (channel[fd] == ERR) {
	    frtnfd (fd)			# return file descriptor
	    return (ERR)
	} else
	    return (fd)
end



.fi
To conserve resources (file descriptors, buffer space) a file should be
closed when no longer needed.  Any file buffers that may have been
created and written into will be flushed before being deallocated.

CLOSE ignores any attempts to close STDIN or CLIN.  Attempts to close
STDOUT, STDERR, or CLOUT cause the respective output byte stream to be
flushed, but are otherwise ignored.  An error action results if one
attempts to close a file which is not open, or if one attempts to close
a file which was not opened with OPEN.
.nf



.tp 5
procedure close (fd)			# close an opened file

begin
	if (fd == STDIN || fd = CLIN) {
	    return
	} else if (fd == STDOUT || fd == STDERR || fd == CLOUT) {
	    flush (fd)
	    return
	} else if (fd is not a valid file descriptor of an open file) {
	    take error action
	} else if (file device is not a standard one)
	    take error action

	flush (fd)
	zclose (channel[fd])
	frtnfd (fd)
end





.tp 5
int procedure fgetfd (file, mode, type)		# get file descriptor

file:	file name (EOS terminated character string)
mode:	type of access permission desired
type:	file type (text or binary)

begin
	# find an unused file descriptor slot
	for (fd=FIRST_FD;  fd <= LAST_FD;  fd=fd+1)
	    if (fdes[fd] == NULL)
		break
	if (fd > LAST_FD)
	    return (ERR)

	# allocate memory for file descriptor proper
	fdes[fd] = malloc (sizeof_struct_fiodes, TY_CHAR)
	if (fdes[fd] == NULL)
	    return (ERR)

	initialize fields of file descriptor to default values
	return (fd)
end




.tp 5
procedure frtnfd (fd)		# return file descriptor and buffers

begin
	if (fdes[fd] == NULL)
	    return
	
	# deallocate file buffers, if any

	if (file takes its buffers from the global pool) {
	    if (any buffers were actually ever allocated)
		decrement reference count of files using global pool
	    for (each buffer in the local list) {
		unlink buffer from the local list
		if (global pool reference count is zero) {
		    unlink buffer from the global list
		    return buffer space to the system
		} else
		    link at tail of the global list
	    }
	} else
	    for (each buffer in the local list) {
		unlink buffer from the local list
		return buffer space to the system
	    }

	if (push back buffer exists)
	    return push-back buffer
	
	mfree (fdes[fd], TY_CHAR)
	fdes[fd] = NULL
end
.fi


.sh
SETTING AND INSPECTING THE FIO CONTROL PARAMETERS

     Any file may be accessed after specifying only the file name, access
mode, and file type parameters in the OPEN call.
Occasionally, however, it is desirable to change the default file control
parameters, to optimize i/o to the file.  The IMIO and VSIO interfaces,
for example, control the size, number, and ownership of the FIO file buffers.


.ks
.nf
		fset (fd, parameter, value)
	value = fget (fd, parameter)
.fi
.ke


The FSET procedure is used to set the FIO parameters for a particular file,
while FGET is used to inspect the values of these parameters.  The special
value DEFAULT will restore the default value of the indicated parameter.
The following parameters are defined:

.ls 4
.ls 15 ADVICE
This parameter is used to advise FIO on the type of access expected for
the file.  The legal values are SEQUENTIAL and RANDOM.  Given such advice,
FIO will set up the buffers for the file using system dependent defaults
for the buffer types, sizes, and numbers.  ADVICE is more system independent
than explicit calls to NBUFFERS, BUF_SIZE, and so on.
.le
.ls ASYNC_IO
If enabled (value = YES), and there are two or more buffers in the pool,
FIO will employ read ahead and early write behind when a sequential pattern
of i/o is detected.  Specifying NO for this parameter guarantees that
buffered data will be retained until reuse of a buffer is forced by a fault.
Note that even if ASYNC_IO is enabled, read ahead and early write behind
are ONLY used while the pattern of i/o remains sequential.
.le
.ls BUF_SIZE
The size of a file buffer, in chars.  The actual size of the buffer
created and used by FIO depends on the device block size and may be larger
than BUF_SIZE, but will not be any smaller.
.le
.ls BUF_TYPE
This parameter may have one of two values, LOCAL or GLOBAL, specifying whether
a local pool of buffers is to be created, or whether buffers are to be drawn
from the global pool.
.le
.ls FIO_DEVICE
The value given must be the entry point address of the ZAREAD procedure
for the desired device.  The device must have been installed in the FIO
device table by a prior call to FIODEV.
.le
.ls FLUSH_NL
If enabled, the output buffer will be flushed after every line of output text,
rather than when the buffer fills or when a flush is otherwise forced.
Useful when the output file is an interactive terminal.
.le
.ls GBUF_SIZE
The size of a buffer in the global pool, in chars.
The FD parameter is ignored.
.le
.ls GNBUFFERS
The number of file buffers in the global pool.
The FD parameter is ignored.
.le
.ls NBUFFERS
The number of file buffers in the local pool.
.le
.ls PBB_SIZE
The size of the combined push back buffer and push back control stack area,
in chars.
.le
.le


The parameters controlling the size and number of the various buffers
(ADVICE, NBUFFERS, BUF_SIZE, BUF_TYPE, PBB_SIZE, GNBUFFERS, GBUF_SIZE) must
be set before i/o causes the affected buffers to be created using the default
number and size parameters.  Thereafter, FSET calls to change these parameters
will be ignored.  The values of the other parameters may be changed at any
time, with the new values taking effect immediately.

.sh
Example 1:  File access is expected to be highly random.

     The most system independent approach is to call FSET to set the
ADVICE parameter to RANDOM.


.nf
	include <fio.h>
	    ...

	fd = open (file, READ_WRITE, BINARY_FILE)
	if (fd == ERR)
	    ...

	call fset (fd, ADVICE, RANDOM)
.fi

.sh
Example 2:  High speed sequential access is desired

     In this case, the best approach would again be to call FSET to set ADVICE
to SEQUENTIAL.  To demonstrate use of some of the other parameters, we have
taken a different approach here.


.nf
	fd = open (file, READ_ONLY, BINARY_FILE)
	if (fd == ERR)
	    ...

	call fset (fd, NBUFFERS, 2)
	call fset (fd, BUF_SIZE, SZ_BLOCK * 16)
	call fset (fd, ASYNC_IO, YES)
.fi


In practice it will rarely be necessary for the user to call FSET, because
the facilities provided by VSIO and IMIO (which do call FSET in the manner
shown) will probably provide the desired i/o capability, without need to
resort to the comparatively low level facilities provided by FIO.
Another reason for NOT calling FSET is that the system provided defaults
may indeed be best for the system on which the software is being run.

The default values selected for the FIO parameters may be tuned to the
particular system.  At one extreme, for example, we might provide a global
pool containing only two buffers, each the size of a single disk block.
By default, all files would share these buffers, and asynchronous i/o
would be disabled.  This would be the minimum memory configuration.
At the other extreme, we might allocate two large buffers to each file,
with asynchronous i/o enabled.


.sh
DETAILS OF THE FIO DATA STRUCTURES

     By this point we have sufficiently detailed information about the
functioning of FIO to be able to fill in the details of the data
structures.  The FIO database consists of the MAXFD file descriptors,
the global buffer pool, the descriptor for the global pool, and the
device table.  Each file descriptor controls a local list of buffers,
and possibly a buffer for pushed back data.   A buffer descriptor
structure is associated with each file buffer.



.ks
.nf
# Static part of file descriptor structures

common fiocom {
	int	gnbufs			# size of global pool
	int	gbufsize		# size of global buffer
	int	gnref			# number of files using gpool
	struct	bufdes *ghead		# head of the global list
	struct	bufdes *gtail		# tail of the local list
	int	ndev			# number of devices
	int	zdev[SZ_DEVTBL]		# device table
	char	*iop[MAXFD]		# i/o pointer
	char	*itop[MAXFD]		# itop for current buffer
	char	*otop[MAXFD]		# otop for current buffer
	char	*bufptr[MAXFD]		# pointer to current buffer
	long	offset[MAXFD]		# offset of the current buffer
	struct	fiodes *fdes[MAXFD]	# pointer to rest of fd
	char	osfname[SZ_OSFNAME]	# buffer for OS file names
}
.fi
.ke


.ks
.nf
# Template for dynamically allocated part of file descriptor

struct fiodes {
	char	fname[SZ_FNAME]		# file name string
	int	fmode			# mode of access
	int	ftype			# type of file
	int	fchan			# OS file number (channel)
	int	fdev			# index into device table
	int	bufsize			# size of a file buffer
	int	pbbsize			# size of pushback buffer
	int	nbufs			# number of local buffers
	int	fflags			# flag bits
	int	nchars			# size of last transfer
	int	iomode			# set if i/o in progress
	int	errcode			# error code
	long    fpos			# actual file position
	char	*pbbp			# pointer to pushback buffer
	char	*pbsp			# pushback stack pointer
	char	*pbsp0			# pointer to stack elem 0
	struct	bufdes *iobuf		# buffer i/o being done on
	struct	bufdes *lhead		# head of local list
	struct	bufdes *ltail		# tail of local list
}
.fi
.ke


.nf
# flags (saved in fdes[fd].fflags)

	F_ASYNC				# enable async_io
	F_EOF				# true if at EOF
	F_ERR				# set when error occurs
	F_FLUSHNL			# flush after newline
	F_GLOBAL			# local or global buffers
	F_RANDOM			# optimize for rand. access
	F_READ				# read perm on file
	F_SEQUENTIAL			# optimize for seq. access
	F_WRITE				# write perm on file
.fi



.ks
.nf
# Buffer descriptor structure.

struct bufdes {		
	int	b_fd			# fd to which buffer belongs
	int	b_iomode		# set when i/o in progress
	int	b_bufsize		# size of buffer, chars
	long	b_offset		# offset of buffer in file
	char	*b_itop			# saved itop
	char	*b_otop			# saved otop
	char	*b_bufptr		# pointer to start of buffer
	struct	bufdes *luplnk		# next buffer up, local list
	struct	bufdes *ldnlnk		# next buffer down, local list
	struct	bufdes *guplnk		# next buffer up, global list
	struct	bufdes *gdnlnk		# next buffer down, global list
}
.fi
.ke


.sh
SEMICODE FOR THE FIO DATABASE ACCESS PROCEDURES

     Routines are required to allocate and deallocate buffers,
and to link and unlink buffers from the buffer lists.  Now that the
data structures have been more clearly defined, we shall go into a
little more detail in the semicode.


.ks
.nf
	                        fmkbfs



                                fmklst



		   flnkhd       fmkbuf       flnktl



		                malloc



	      Structure of the Buffer Allocation Procedures
.fi
.ke



The main buffer creation procedure, FMKBFS, is called by either
FILBUF or FLSBUF when i/o is first done on a file.  FMKLST allocates
a set of buffers and links them into a doubly linked list.  FLNKHD
links a buffer at the head of a list, while FLNKTL links a buffer at
the tail of a list.  FMKBUF calls MALLOC to allocate memory for a file
buffer, and initializes the descriptor for the buffer.




.tp 5
.nf
procedure fmkbfs (fd)

fd:	file descriptor number
fp:	pointer to file descriptor
bp:	pointer to buffer descriptor

begin
	if (use global pool) {
	    if (no buffers in global pool yet) {
		gnbufs = fmklst (NULL, gnbufs, gbufsize, GLOBAL)
		if (gnbufs <= 0)		# can't make buffers
		    take error action
	    }
	    gnref = gnref + 1

	} else {				# create local buffers
	    adjust bufsize to be an integral number of device blocks
	    fp = fdes[fd]
	    fp.nbufs = fmklst (fd, fp.nbufs, bufsize, LOCAL)

	    if (fp.nbufs == 0)			# must be at least one
		take error action
	}
end



.fi
Unlink a buffer from whatever lists it is on, relink it at head of the
local list, and also at head of global list if a mapped file.  Called
by FFAULT.
.nf


.tp 5
procedure frelnk (fd, bp) 

fd:	file descriptor number
bp:	pointer to buffer descriptor

begin
	# relink buffer at head of the local list for file fd
	call funlnk (bp, LOCAL)
	call flnkhd (fd, bp, LOCAL)

	# relink at head of global list, if buffer in global pool
	if (buffer is linked into the global pool) {
	    call funlnk (bp, GLOBAL)
	    call flnkhd (fd, bp, GLOBAL)
	}
end




.tp 5
int procedure fmklst (fd, nbufs, bufsize, list)		# make list

list:	either global or local
bufdes:	pointer to buffer descriptor

begin
	for (nb=0;  nb <= nbufs;  nb=nb+1) {
	    bufdes = fmkbuf (fd, bufsize)
	    if (bufdes == NULL)
		break
	    else if (nb == 1)
		flnkhd (fd, bufdes, list)
	    flnktl (fd, bufdes, list)
	}
	return (nb)
end




.tp 5
int procedure fmkbuf (fd, bufsize)		# make a buffer

begin
	assert (bufsize > 0 && mod (bufsize, block_size) == 0)

	sizeof_buffer = sizeof (struct bufdes) + bufsize
	bufdes_pointer = malloc (sizeof_buffer, TY_CHAR)
	if (bufdes_pointer == NULL)
	    return (NULL)
	else {
	    initialize buffer descriptor
	    return (bufdes_pointer)
	}
end




.tp 5
procedure flnkhd (fd, bp, list)		# link buf at head of list

fd:	file descriptor number
bp:	pointer to buffer descriptor
list:	global or local
fp:	pointer to file descriptor

begin
	assert (bp != NULL)
	assert (list == LOCAL || list == GLOBAL)

	switch (list) {
	case GLOBAL:
	    if (buffer not already linked at head of list) {
		bp.gdnlnk = ghead
		ghead.guplnk = bp
		ghead = bp
	    }
	case LOCAL:
	    fp = fdes[fd]
	    if (buffer not already linked at head of list) {
		bp.fd = fd
		bp.ldnlnk = fp.lhead
		if (fp.lhead != NULL)
		    fp.lhead.luplnk = bp
		fp.lhead = bp
	    }
	}
end




.tp 5
procedure flnktl (fd, bp, list)		# link buf at tail of list

fd:	file descriptor number
bp:	pointer to buffer descriptor
list:	global or local
fp:	pointer to file descriptor

begin
	assert (bp != NULL)
	assert (list == LOCAL || list == GLOBAL)

	switch (list) {
	case GLOBAL:
	    if (buffer not already linked at tail of list) {
		bp.guplnk = gtail
		gtail.gdnlnk = bp
		gtail = bp
	    }
	case LOCAL:
	    fp = fdes[fd]
	    if (buffer not already linked at tail of list) {
		bp.fd = fd
		bp.luplnk = fp.ltail
		if (fp.ltail != NULL)
		    fp.ltail.ldnlnk = bp
		fp.ltail = bp
	    }
	}
end




.tp 5
procedure flnkto (fd, bp, to)		# link buf bp after to

bp:	pointer to descriptor of buffer to be linked
to:	pointer to descriptor of buffer to be linked to

begin
	bp.ldnlnk = to.ldnlnk
	bp.luplnk = to
	to.ldnlnk = bp
	if (bp.ldnlnk == NULL)
	    fdes[fd].ltail = bp			# new tail of list
	else
	    bp.ldnlnk.luplnk = bp
end




.tp 5
procedure funlnk (bp, list)			# unlink from list

bp:	pointer to buffer descriptor
list:	global or local
fp:	pointer to file descriptor

begin
	switch (list) {

	case GLOBAL:
	    if (buffer is at head of the global list)
		ghead = bp.gdnlnk
	    if (buffer is at tail of the global list)
		gtail = bp.guplnk
	    if (bp.guplnk != NULL)
		bp.guplnk.gdnlnk = bp.gdnlnk
	    if (bp.gdnlnk != NULL)
		bp.gdnlnk.guplnk = bp.guplnk

	case LOCAL:
	    fp = fdes[bp.fd]
	    if (buffer is at head of the local list)
		fp.lhead = bp.ldnlnk
	    if (buffer is at tail of the local list)
		fp.ltail = bp.luplnk
	    if (bp.luplnk != NULL)
		bp.luplnk.ldnlnk = bp.ldnlnk
	    if (bp.ldnlnk != NULL)
		bp.ldnlnk.luplnk = bp.luplnk
	}
end
.fi


.sh
SEMICODE FOR FFAULT, AGAIN

     The file fault procedure lies at the heart of FIO.  Now that the
data structures, initialization procedures, and linked list operators are
clearer, it is time to go back and fill in some of the details in FFAULT.



.tp 5
.nf
int procedure ffault (fd, char_offset)

fd:		file descriptor number
char_offset:	desired char offset in file
bp:		pointer to a buffer descriptor
fp:		pointer to the file descriptor

begin
	# calculate buffer_offset (modulus file buffer size)
	buffer_offset = char_offset - mod(char_offset, buffer_size) + 1

	# compute pointers to fd structure, current buffer
	fp = fdes[fd]
	bp = fp.lhead

	# update i/o pointers in the buffer descriptor
	# note writes may have pushed iop beyond original itop
	itop[fd] = max(itop[fd], iop[fd])
	if (bp != NULL) {
	    bp.b_itop = itop[fd]
	    bp.b_otop = otop[fd]
	}
	    
	# if buffer is found in local pool, relink at head of list.
	if (ffndbf (fd, buffer_offset, bp) == YES) {
	    frelnk (fd, bp)
	    itop[fd] = bp.b_itop
	    otop[fd] = bp.b_otop

	# this next section of code is invoked whenever a fault
	# occurs which requires an actual i/o transfer.

	} else {
	    if (bp.otop != bp.b_bufptr)		# buffer dirty?
		fflsbf (fd, bp)			# flush buffer

	    frelnk (fd, bp)			# relink at head
	    bp.b_offset = buffer_offset

	    if (F_READ flag is set) {
		ffilbf (fd, bp)			# fill buffer
		fwatio (fd)
	    } else {
		bp.b_itop = bp.b_bufptr
		bp.b_otop = bp.b_bufptr
	    }

	    # if asynchronous i/o is enabled (only if two or more
	    # buffers) initiate write behind or read ahead, if 
	    # fwatio has detected a sequential pattern of i/o.

	    if (ASYNC_IO enabled)
		switch (io_pattern) {
		case WSEQ:			# write behind
		    bufp = bp.ldnlnk
		    if (bufp != NULL)
			if (bufp.b_otop != bufp.b_bufptr)
			    fflsbf (fd, bufp)
		case RSEQ:			# read ahead
		    new_buffer_offset = buffer_offset + buffer_size
		    if (ffndbf (fd, new_buffer_offset, bufp) == YES)
			# skip read ahead, buffer already in pool
		    else if (bufp.b_otop == bufp.b_bufptr) {
			if (bufp.luplnk != fp.lhead) {
			    funlnk (bufp, LOCAL)
			    flnkto (bp, bufp, fp.lhead)
			}
			if (buffer in global pool) {
			    funlnk (bufp, GLOBAL)
			    flnkhd (bufp, GLOBAL)
			}
			bufp.b_offset = new_buffer_offset
			ffilbf (fd, bufp)
		    }
		}
	}

	bufptr[fd] = bp.b_bufptr		# set i/o pointers
	offset[fd] = buffer_offset
	lseek (fd, char_offset)

	if (fp.status == ERR)			# check for ERR,EOF
	    return (ERR)
	else if (iop[fd] == itop[fd])
	    return (EOF)
	else
	    return (itop[fd] - iop[fd])		# return nchars
end




# Search for a file buffer.  If found, return buffer pointer in BP,
# otherwise allocate a buffer from the tail of either the global or
# local list.


.tp 5
int procedure ffndbf (fd, buffer_offset, bp)

begin
	# desired buffer may be on the way; wait and see
	if (read in progress on file fd)
	    fwatio (fd)

	# search local pool for the buffer
	for (bp = fp.lhead;  bp != NULL;  bp = bp.ldnlnk)
	    if (bp.b_offset == buffer_offset)
		break

	# if buffer already in pool, return buffer pointer,
	# otherwise use oldest buffer in appropriate list.

	if (bp != NULL)			# buffer found in pool
	    return (YES)
	else {				# use buffer at tail of list
	    if (this file uses global pool) {
		bp = gtail
		if (io in progress on this buffer)
		    fwatio (bp.fd)
	    } else
		bp = fp.ltail
	    return (NO)
	}

end
.fi


.sh
SUMMARY OF THE FIO/OS INTERFACE (MACHINE DEPENDENT PRIMITIVES)

     FIO depends on a number of machine dependent primitives.  Many of these
have been introduced in the semicode.  Other primitives are not involved in
i/o, and hence have not appeared thus far in the discussion.  Primitives are
required to map virtual file names into OS file names.

The goal in designing the FIO/OS interface was to make the primitives as
"primitive" as feasible, rather than to minimize the number of primitives.
These primitives should be easy to implement on almost any modern minicomputer.
The ideal target OS will provide asynchronous, random access i/o,
logical name facilities, multiple directories per task, multitasking and
intertask communication facilities, and dynamic memory allocation/deallocation
facilities.


.nf
Text Files

	zopntx	(osfn, access_mode; chan)

	zgettx	(chan, line_buf, maxchars; nchars)
	zputtx	(chan, line_buf, nchars; nchars)
	zflstx	(chan)
	zfsttx	(chan, what; status_info)
	zclstx	(chan)
	zsektx	(chan, znotln_offset; status)
	znottx	(chan; file_offset)


Binary File Initialization (one set per device)

	zopnbf	(osfn, access_mode; chan)
	zfaloc	(osfn, nchars; chan)


Binary File I/O primitives (one set per device)

	zaread	(chan, buffer, maxchars, file_offset)
	zawrit	(chan, buffer, maxchars, file_offset)
	zawait	(chan; status)
	zfsttb	(chan, what; status_info)
	zclsbf	(chan; status)

	standard devices: regular files, inter-task pipes (CL,GIO),
			  memory, magnetic tapes.


Virtual File Name Mapping

	zmapfn	(vfn, osfn, maxch)
	zabsfn	(vfn, osfn, maxch)


File Manipulation, Status, File Protection, Temporary Files

	zacces	(osfn, mode, type; status)
	zfdele	(osfn; status)
	zrenam	(from_osfn, to_osfn; status)
	zfprot	(osfn)
	zmktmp	(root, temp_file_osfn)


Other Dependencies (also used outside of FIO)

	zcallN	(entry_point, arg1, ..., argN)
	pntr =	malloc	(nelements, data_type)
	pntr =	calloc	(nelements, data_type)
		mfree	(pntr, data_type)
	int =	and	(int, int)
	int =	or	(int, int)
	int =	loc	(reference)
.fi


The STATUS returned by the Z-routines may be ERR or a meaningful number,
such as the channel number or number of characters read or written.
EOF is signified at this level by a return value of zero for the number
of characters read (only ZGETTX and ZAREAD read from a file).  There is
no provision for special error codes or messages at the Z-routine level.