Page Numbers: Yes First Page: 12 X: 527 Y: 10.5"
Margins: Binding: 13
Odd Heading: Not-on-first-page
Alto/Mesa Disk Streams Package
Even Heading:
Alto/Mesa Disk Streams Package
Alto/Mesa Disk Streams Package
October 1980
A disk stream (see StreamDefs) is an array-like representation of a disk file. Parts of the file may reside in memory from time to time at the convenience of the stream. Like most arrays, a stream has a length; unlike array variables, the length of a stream may be changed by appending to it, and the maximum length is very large. Disk streams are created by the procedures:
NewByteStream, NewWordStream: PROCEDURE [
name: STRING, access: AccessOptions]
RETURNS [DiskHandle];
A FileHandle for the file name is created with the given access and DefaultVersion. It is locked and opened, and a byte or word stream is attached to it. If access is Append only, the stream is positioned at the end of the file, otherwise at the beginning. If access is DefaultAccess, Read is assumed. If name is an invalid file name or the file does not exist and append access is not specified, then the following signal will be raised:
FileNameError: SIGNAL [name: STRING];
If a valid FileHandle already exists, a stream may be attached to it by calling the procedures:
CreateByteStream, CreateWordStream: PROCEDURE [
file: FileHandle, access: AccessOptions]
RETURNS [DiskHandle];
The stream’s FileHandle and access may be read directly from the StreamObject (after discrimination) using the field names file and read, write, append.
Disk streams are chained together using the link field in a StreamObject. The head of the list is returned by the procedure:
GetDiskStreamList: PROCEDURE RETURNS [DiskHandle];
The operations allowed on the stream’s length are determined by its access options; these options are negotiated with the underlying file system (see the section on Files). The options supported by the stream package are:
Read:the length is a constant.
Write:the length may decrease.
Append:the length may increase.
The access options may be combined using ReadWrite, WriteAppend, and ReadWriteAppend.
A disk stream has as part of its state a current index into the array representation of the file. The first data item is at index zero, the last at length−1. An invariant of a disk stream is index <= length. The current index and length are used in defining the semantics of the standard operations on a disk streams. See the section on Streams for more information on these generic operations. For a disk stream, s (where i is an item of the appropriate type) they are:
reset[s]
Effect: sets the index to zero.
get[s]
If: read AND index < length.
Effect: t ← s[index]; index ← index+1; RETURN[t].
putback[s, i]
Effect: StreamOperation error.
put[s, i]
If: (write AND index < length) OR
(append AND index = length).
Effect: s[index] ← i; index ← index+1; length ← MAX[index, length].
endof[s]
Effect: RETURN[index = length].
destroy[s]
Effect: IF ~read AND index # 0 THEN length ← index (i.e. truncate the file if it is not positioned at the beginning). Release the FileHandle if there are no segments attached to it.
Since output to the disk is buffered, there may be items in the stream that have not yet been written out. The destroy operation causes these items to be written out before releasing the StreamObject.
Actually, there is a little more to it. Disk streams deliver either byte or word items; in either case, the index is always computed in bytes. So the description above is a simplification of what really happens. Rather than clutter it up, suffice it to say that when accessing files in word mode, index values are always rounded up to word boundaries.
If it is necessary to truncate a file in the cases not covered by destroy (i.e. read OR index = 0), call
TruncateDiskStream: PROCEDURE [stream: StreamHandle];
Effect: length ← index; stream.destroy[stream].
If it is necessary to assure that all the data in the stream has been written to the disk without releasing the StreamObject, call
CleanupDiskStream: PROCEDURE [stream: StreamHandle];
In addition to the standard operations, the following disk dependent functions are provided to efficiently copy large blocks of words to or from the stream:
ReadBlock: PROCEDURE [stream: StreamHandle, address: POINTER, words: CARDINAL]
RETURNS [count: CARDINAL];
If: read.
Effect: count ← MIN[words,length-index];
FOR index IN [index..index+count) DO
address↑ ← stream[index];
address ← address+1;
ENDLOOP.
Fine Point:
If a file with an (odd) integral number of pages is read with one page ReadBlocks, the ReadBlock call after the last page is read will write into the caller’s buffer (probably the null page) and then return 0 indicating nothing was read.
WriteBlock: PROCEDURE [stream: StreamHandle, address: POINTER, words: CARDINAL]
RETURNS [count: CARDINAL];
If: (write AND index < length) OR
(append AND index = length).
Effect: count ← IF append
THEN words
ELSE MIN[words,length-index];
FOR index IN [index..index+count) DO
stream[index] ← address↑;
address ← address+1;
ENDLOOP.
length ← MAX[index, length].
When using ReadBlock and WriteBlock, the initial index must be on a word boundary (otherwise the StreamPosition error results). Note that the returned value may be less than words if the stream’s access does not allow reading or writing of the whole block (the StreamAccess error is never raised by either of these procedures).
The stream index alluded to above is actually a structure:
StreamIndex: TYPE = RECORD [
page: PageNumber,
byte: CARDINAL];
The first data byte of a stream is at StreamIndex[0, 0]. The current stream index can be determined by calling
GetIndex: PROCEDURE [stream: StreamHandle] RETURNS [StreamIndex];
It is quite acceptable to do double precision arithmetic on a StreamIndex (and even single precision operations on the individual fields, if you are careful about borrows, carries, and overflows). Note, however that a StreamIndex is not compatible with the double precision arithmetic of LONG INTEGERs. The paged structure of the index can be restored by invoking
NormalizeIndex: PROCEDURE [index: StreamIndex] RETURNS [StreamIndex];
It returns an index whose byte field is in the range [0..CharsPerPage). An integer value may be added to an index (and the result normalized) by calling
ModifyIndex: PROCEDURE [index: StreamIndex, change: INTEGER]
RETURNS [StreamIndex];
The current index may be set by calling
SetIndex: PROCEDURE [stream: StreamHandle, index: StreamIndex];
Note that this may actually extend the file (with unspecified data) if Append access is allowed. To determine if this will happen, you might first want to call
FileLength: PROCEDURE [stream: StreamHandle] RETURNS [StreamIndex];
Note that FileLength positions the stream to the end-of-file and returns the length as seen through the stream; this may differ from the physical length of the disk file (if, for example, items have been appended to the stream but not yet written to the disk).
You may test for the greater-than relation between two stream indexes by calling the procedures
GrEqualIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN];
GrIndex: PROCEDURE [i1, i2: StreamIndex] RETURNS [BOOLEAN];
For applications that have to do arithmetic on stream indices, there is the notion of a stream position:
StreamPosition: TYPE = LONG CARDINAL;
The first data byte of a stream is at StreamPosition[0]. The current stream position can be determined by calling
GetPosition: PROCEDURE [stream: StreamHandle] RETURNS [StreamPosition];
It is quite acceptable to do double precision arithmetic on a StreamPosition. Care must be taken not to underflow when subtracting a number from a StreamPosition. The same effect of ModifyIndex may be realized by calling
ModifyPosition: PROCEDURE [pos: StreamPosition, change: INTEGER]
RETURNS [StreamPosition];
The current position may be set by calling
SetPosition: PROCEDURE [stream: StreamHandle, pos: StreamPosition];
Note that this may actually extend the file (with unspecified data) if Append access is allowed.
StreamPositions and StreamIndexs may be converted to one another by calling
IndexToPosition: PROCEDURE [index: StreamIndex] RETURNS [StreamPosition];
PositionToIndex: PROCEDURE [pos: StreamPosition] RETURNS [index: StreamIndex];
If a physical disk location is required along with the stream index, a file address (FA) will prove useful (see AltoFileDefs); it is similar to a StreamIndex with a disk address (DA) tacked on the front, except that the page field is one origin (in the Alto file system, page zero is the leader page).
FA: TYPE = MACHINE DEPENDENT RECORD [
da: DA,
page: PageNumber,
byte: CARDINAL];
You may record the current stream index and re-establish it later, in a fashion similar to GetIndex and SetIndex, by calling the procedures
GetFA: PROCEDURE [stream: StreamHandle, fa: POINTER TO FA];
JumpToFA: PROCEDURE [stream: StreamHandle, fa: POINTER TO FA];
The special thing about JumpToFA is that the disk address in the fa is taken as a hint; if it doesn’t work out (the page number or file serial number doesn’t match the stream’s version of them), JumpToFA will attempt to find the requested page via the shortest route and correct the fa accordingly. This may involve starting over at the beginning of the file. If that fails,
InvalidFP: SIGNAL [fp: POINTER TO FP];
will result, probably indicating that the file has been moved (or worse, deleted!) since the stream was attached to it. A call on some directory searching procedure may prove useful in this situation, to determine if retrying the operation (with a new fp) is appropriate.
StreamScan
This interface allows allows overlapped disk I/O when reading from a stream. It is a transliteration of the same code from the Alto Operating System (version 17 or newer). The following are defined in StreamScan:
Descriptor: TYPE = RECORD [
da: AltoFileDefs.vDA,
pageNumber: CARDINAL,
numChars: CARDINAL,
-- private fields];
Handle: TYPE = POINTER TO READONLY Descriptor;
Init: PROCEDURE [
stream: StreamDefs.StreamHandle, bufTable: POINTER, nBufs: CARDINAL]
RETURNS [Handle];
GetBuffer: PUBLIC PROCEDURE [ssd: Handle] RETURNS [POINTER];
Finish: PROCEDURE [ssd: Handle];
Init sets up a scan stream from a disk stream. In addition to the stream, the client supplies a vector of pointers to 256 word blocks useable as disk buffers (bufTable). The number of buffers supplied is nBufs. In other words, you should think of the type of bufTable as
bufTable: POINTER TO ARRAY [0..nBufs) OF Buffer,
where
Buffer: TYPE = ARRAY [0..256) OF WORD;
At least one buffer must be supplied (the normal stream buffer is also used). Each call to GetBuffer will return a pointer to the next sequential page of the file and returns the previous buffer page to the buffer pool (first call returns data page 0, file page 1). The public fields of the Handle are correct for the page returned by the most recent call to GetBuffer. GetBuffer returns NIL when there are no more pages to be read. A call to Finish terminates the scan. No other stream operations should be performed between Init and Finish.