UXIOImpl.mesa
Copyright Ó 1988, 1991 by Xerox Corporation. All rights reserved.
Carl Hauser, February 16, 1989 2:13:50 pm PST
Doug Wyatt, May 24, 1988 11:06:24 am PDT
JKF August 1, 1988 11:43:07 am PDT
Eduardo Pelegri-Llopart February 8, 1989 4:22:29 pm PST
Michael Plass, July 19, 1989 9:54:09 am PDT
Chauser, March 13, 1990 1:25 pm PST
Willie-s, August 13, 1991 1:05 pm PDT
DIRECTORY
UXIO,
Atom USING [PutPropOnList],
Basics USING [UnsafeBlock],
BasicTime USING [GMT],
HostTime USING [ExtendedGMTFromHostTime],
IO USING [STREAM, CreateStream, CreateStreamProcs, EndOfStream, StreamProcs],
List USING [Assoc, AList],
ProcessProps USING [GetPropList],
RefText USING [AppendChar, AppendRope, ObtainScratch, ReleaseScratch],
Rope USING [Concat, Equal, Fetch, Flatten, IsEmpty, ROPE, Length],
UnixEnviron USING [GetEnv],
UnixErrno USING [GetErrno, Errno],
UnixSysCalls USING [Close, FStat, FSync, LSeek, Open, Read, Unlink, Write],
UnixTypes USING [CHARPtr, FD, FileFlags, Mode, RES, Stat];
UXIOImpl: CEDAR PROGRAM
IMPORTS Atom, HostTime, IO, List, ProcessProps, Rope, RefText, UnixEnviron, UnixErrno, UnixSysCalls
EXPORTS UXIO
SHARES IO
~ BEGIN
ROPE: TYPE = Rope.ROPE;
GMT: TYPE = BasicTime.GMT;
OpenFile: TYPE = UXIO.OpenFile;
FD: TYPE = UnixTypes.FD;
RES: TYPE = UnixTypes.RES;
CHARPtr: TYPE = UnixTypes.CHARPtr;
Error: PUBLIC ERROR [error: UXIO.ErrorDesc] = CODE;
StreamData: TYPE ~ RECORD [
fd: FD,
fileName: ROPE,
isReset: BOOL ¬ FALSE -- used only as hack in standard input stream case
];
stdInputProcs: REF IO.StreamProcs ~ IO.CreateStreamProcs[
variety: $input, class: $UXStdIO,
getChar: GetChar, endOf: EndOfInput,
reset: ResetInput, charsAvail: InputCharsAvail
];
stdOutputProcs: REF IO.StreamProcs ~ IO.CreateStreamProcs[
variety: $output, class: $UXStdIO,
putChar: PutChar,
unsafePutBlock: UnsafePutBlock
];
fileInputProcs: REF IO.StreamProcs ~ IO.CreateStreamProcs[
variety: $input, class: $UXFileIO,
getChar: GetChar, unsafeGetBlock: UnsafeGetBlock, endOf: EndOf,
getIndex: GetIndex, setIndex: SetIndex, getLength: GetLength, close: Close
];
fileOutputProcs: REF IO.StreamProcs ~ IO.CreateStreamProcs[
variety: $output, class: $UXFileIO,
putChar: PutChar, unsafePutBlock: UnsafePutBlock,
getIndex: GetIndex, getLength: GetLength, setIndex: SetIndex, flush: Flush, close: Close
];
CreateStandardStream: PUBLIC PROC [kind: UXIO.Kind] RETURNS [stream: IO.STREAM] ~ {
SELECT kind FROM
input => stream ¬ IO.CreateStream[streamProcs: stdInputProcs,
streamData: NEW[StreamData ¬ [stdin, NIL]]];
output => stream ¬ IO.CreateStream[streamProcs: stdOutputProcs,
streamData: NEW[StreamData ¬ [stdout, NIL]]];
trace => stream ¬ IO.CreateStream[streamProcs: stdOutputProcs,
streamData: NEW[StreamData ¬ [stdtrc, NIL]]];
ENDCASE;
};
magicFlags: ARRAY UXIO.Access OF UnixTypes.FileFlags ~ [
read: [access: RDONLY], -- read only
append: [creat: true, append: true, access: WRONLY], -- create, append, write only
write: [trunc: true, creat: true, access: WRONLY] -- truncate, create, write only
];
CreateFileStream: PUBLIC PROC [name: Rope.ROPE, access: UXIO.Access, mode: UnixTypes.Mode ¬ UXIO.defaultMode] RETURNS [stream: IO.STREAM] ~ {
fd: FD ~ RopeOpen[name, magicFlags[access], mode];
stream ¬ IO.CreateStream[
streamProcs: IF access=read THEN fileInputProcs ELSE fileOutputProcs,
streamData: NEW[StreamData ¬ [fd, name]]
];
stream.propList ¬ Atom.PutPropOnList[stream.propList, $FileName, Rope.Flatten[name]];
stream.propList ¬ Atom.PutPropOnList[stream.propList, $FD, NEW[FD ¬ fd]];
};
charsPerWord: CARD ~ BITS[WORD]/BITS[CHAR];
WordOfPackedChars: TYPE ~ PACKED ARRAY [0..charsPerWord) OF CHAR;
use this to obtain a (byte) pointer to a CHAR; { char: CHAR; ... @char ... } is wrong!
GetChar: PROC [self: IO.STREAM] RETURNS [CHAR] ~ {
data: REF StreamData ~ NARROW[self.streamData];
charsRead: INT;
chars: WordOfPackedChars; -- @chars points to chars[0]
TRUSTED {charsRead ¬ UnixSysCalls.Read[data.fd, LOOPHOLE[@chars], 1]};
IF charsRead = 0 THEN {
ERROR IO.EndOfStream[self];
};
IF charsRead < 0 THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "read failed to return any bytes in GetChar"]];
};
RETURN[chars[0]];
};
EndOfInput: PROC [self: IO.STREAM] RETURNS [BOOL] = {
backing: IO.STREAM ¬ self.backingStream;
IF backing # NIL THEN RETURN[backing.streamProcs.endOf[backing]]
ELSE
RETURN[FALSE]; -- not a concept for stdin
};
PutChar: PROC [self: IO.STREAM, char: CHAR] ~ {
data: REF StreamData ~ NARROW[self.streamData];
charsWritten: INT;
chars: WordOfPackedChars; -- @chars points to chars[0]
chars[0] ¬ char;
TRUSTED {charsWritten ¬ UnixSysCalls.Write[data.fd, LOOPHOLE[@chars], 1]};
};
UnsafeGetBlock: UNSAFE PROC [self: IO.STREAM, block: Basics.UnsafeBlock] RETURNS [nBytesRead: INT] ~ UNCHECKED {
data: REF StreamData ~ NARROW[self.streamData];
pointer: CHARPtr ~ LOOPHOLE[LOOPHOLE[block.base, POINTER]+CARD[block.startIndex]]; -- byte address!
n: INT ~ UnixSysCalls.Read[data.fd, pointer, CARD[block.count]];
IF n<0 THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "read failed to return any bytes in UnsafeGetBlock"]];
};
RETURN[n];
};
UnsafePutBlock: PROC [self: IO.STREAM, block: Basics.UnsafeBlock] ~ {
data: REF StreamData ~ NARROW[self.streamData];
start: INT ¬ CARD[block.startIndex];
stop: INT ~ start+CARD[block.count];
WHILE start<stop DO
pointer: CHARPtr ~ LOOPHOLE[LOOPHOLE[block.base, POINTER]+start]; -- byte address!
n: INT;
TRUSTED { n ¬ UnixSysCalls.Write[data.fd, pointer, stop-start] };
IF n<0 THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "write failed to write any bytes in UnsafePutBlock"]];
};
start ¬ start+n;
ENDLOOP;
};
GetIndex: PROC [self: IO.STREAM] RETURNS [INT] ~ {
data: REF StreamData ~ NARROW[self.streamData];
index: INT ~ UnixSysCalls.LSeek[data.fd, 0, $incr];
IF index<0 THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "lseek failed in GetIndex"]];
};
RETURN[index];
};
SetIndex: PROC [self: IO.STREAM, index: INT] ~ {
data: REF StreamData ~ NARROW[self.streamData];
newIndex: INT ~ UnixSysCalls.LSeek[data.fd, index, $set];
IF newIndex<0 THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "lseek failed in SetIndex"]];
};
};
GetStat: PROC [fd: FD] RETURNS [stat: UnixTypes.Stat] ~ TRUSTED {
result: RES ~ UnixSysCalls.FStat[fd, @stat];
IF result=failure THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "fstat failed in GetStat"]];
};
};
GetLength: PROC [self: IO.STREAM] RETURNS [INT] ~ {
data: REF StreamData ~ NARROW[self.streamData];
RETURN[GetStat[data.fd].size];
};
EndOf: PROC [self: IO.STREAM] RETURNS [BOOL] ~ {
Is there any better way to do this?!
data: REF StreamData ~ NARROW[self.streamData];
size: INT ~ GetStat[data.fd].size;
index: INT ~ UnixSysCalls.LSeek[data.fd, 0, $incr];
IF index<0 THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "lseek failed in EndOf"]];
};
RETURN[NOT index<size];
};
Flush: PROC [self: IO.STREAM] ~ {
data: REF StreamData ~ NARROW[self.streamData];
result: RES ~ UnixSysCalls.FSync[data.fd];
IF result=failure THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "fsync failed in Flush"]];
};
};
Close: PROC [self: IO.STREAM, abort: BOOL] ~ {
data: REF StreamData ~ NARROW[self.streamData];
result: RES ~ UnixSysCalls.Close[data.fd];
IF result=failure THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "close failed"]];
};
};
ResetInput: PROC [self: IO.STREAM] ~ {
data: REF StreamData ~ NARROW[self.streamData];
data.isReset ¬ TRUE;
};
InputCharsAvail: PROC [self: IO.STREAM, wait: BOOL] RETURNS[INT] ~ {
a grand hack: helps out the command tool when running with the standard input stream. Nobody else uses it, I hope.
data: REF StreamData ~ NARROW[self.streamData];
IF data.isReset THEN {
data.isReset ¬ FALSE;
RETURN[0];
}
ELSE RETURN[ LAST[INT] ];
};
Delete: PUBLIC PROC [name: Rope.ROPE] = {
RopeDelete[name];
};
OpenFileFromStream: PUBLIC PROC [self: IO.STREAM] RETURNS [OpenFile] = {
data: REF StreamData ~ NARROW[self.streamData];
RETURN[[NEW[StreamData ¬ [data.fd, data.fileName]]]]};
GetCreateTime: PUBLIC PROC [file: OpenFile] RETURNS [created: BasicTime.GMT] = {
data: REF StreamData ~ NARROW[file];
created ¬ HostTime.ExtendedGMTFromHostTime[[GetStat[data.fd].mtime, 0, 0, 0]].gmt;
};
GetName: PUBLIC PROC [file: OpenFile] RETURNS [fullFName: Rope.ROPE] = {
data: REF StreamData ~ NARROW[file];
RETURN[data.fileName];
};
DirPointer: TYPE ~ POINTER TO READONLY DirectStruct;
DirProc: TYPE ~ PROC [d: DirPointer] RETURNS [quit: BOOLFALSE];
DirEnumerate: PROC [fd: FD, proc: DirProc] ~ TRUSTED {
nbytes: INT ~ GetStat[fd].blksize;
scratch: REF TEXT ~ RefText.ObtainScratch[nbytes];
buf: POINTER ~ LOOPHOLE[scratch, POINTER]+SIZE[TEXT[0]];
base: INT;
DO
cc: INT ~ Xgetdirentries[fd, buf, nbytes, @base];
index: INT ← 0;
IF cc<0 THEN ERROR Error;
IF cc=0 THEN GOTO Quit;
WHILE index<cc DO
d: DirPointer ~ LOOPHOLE[buf+index];
IF proc[d] THEN GOTO Quit;
index ← index+d.reclen;
ENDLOOP;
REPEAT Quit => NULL;
ENDLOOP;
RefText.ReleaseScratch[scratch];
};
EnumerateForNames: PUBLIC PROC [dir: Rope.ROPE, proc: UXIOExtras.NameProc] ~ {
fd: FD ~ RopeOpen[dir, magicFlags[read]];
dirProc: DirProc ~ TRUSTED {
i: NAT ← 0; p: SAFE PROC RETURNS [c: CHAR] ~ TRUSTED { c ← d.name[i]; i ← i+1 };
rope: Rope.ROPE ~ Rope.FromProc[d.namlen, p];
RETURN[proc[rope]];
};
DirEnumerate[fd, dirProc];
};
Handling of working directories is as in UFSImpl.mesa (not used to
wDirKey: ATOM ~ $WorkingDirectory;
ExpandName: PROC [name: ROPE] RETURNS [fullUName: ROPE] = {
IF NOT Rope.Equal[name, NIL] AND Rope.Fetch[name, 0] = '/
THEN
fullUName ¬ name
ELSE fullUName ¬ Rope.Flatten[Rope.Concat[GetWDir[NIL], name]];
};
GetWDir: PUBLIC PROC [wDir: ROPE] RETURNS [ROPE] = {
Returns the actual working directory implied by wDir. If wDir is empty, gets the current $WorkingDirectory property from the process property list; if that too is empty, uses the default working directory []<>. The result is expressed in brackets syntax, with a final "/".
IF Rope.IsEmpty[wDir] THEN {
propList: List.AList ~ ProcessProps.GetPropList[];
WITH List.Assoc[key: wDirKey, aList: propList] SELECT FROM
rope: ROPE => wDir ¬ rope;
ENDCASE;
};
IF Rope.IsEmpty[wDir] THEN wDir ¬ GetDefaultWDir[];
RETURN[wDir];
};
GetDefaultWDir: PROC RETURNS [wdir: Rope.ROPE] ~ {
RETURN[Rope.Concat[UnixEnviron.GetEnv["PWD"], "/"]];
};
RopeOpen: PROC [name: Rope.ROPE, flags: UnixTypes.FileFlags, mode: UnixTypes.Mode]
RETURNS [fd: FD] ~ {
IF Rope.Length[name]=0 THEN ERROR Error[[client, NIL, "empty name in RopeOpen"]]
ELSE {
expandedName: ROPE ¬ ExpandName[name];
scratch: REF TEXT ~ RefText.ObtainScratch[Rope.Length[expandedName]+1];
nameText: REF TEXT ~ RefText.AppendChar[RefText.AppendRope[scratch, expandedName], '\000];
namePointer: CHARPtr ~ LOOPHOLE[LOOPHOLE[nameText, POINTER]+SIZE[TEXT[0]]];
result: FD;
TRUSTED { result ¬ UnixSysCalls.Open[namePointer, flags, mode] };
RefText.ReleaseScratch[scratch];
IF result=error THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "open failed in RopeOpen"]];
};
RETURN[result];
};
};
RopeDelete: PROC [name: Rope.ROPE] ~ {
IF Rope.Length[name]=0 THEN ERROR Error[[client, NIL, "empty name in RopeDelete"]]
ELSE {
expandedName: ROPE ¬ ExpandName[name];
scratch: REF TEXT ~ RefText.ObtainScratch[Rope.Length[expandedName]+1];
nameText: REF TEXT ~ RefText.AppendChar[RefText.AppendRope[scratch, expandedName], '\000];
namePointer: CHARPtr ~ LOOPHOLE[LOOPHOLE[nameText, POINTER]+SIZE[TEXT[0]]];
result: RES;
TRUSTED { result ¬ UnixSysCalls.Unlink[namePointer] };
RefText.ReleaseScratch[scratch];
IF result=failure THEN {
errno: UnixErrno.Errno ¬ UnixErrno.GetErrno[];
ERROR Error[[client, NIL, "unlink failed in RopeDelete"]];
};
};
};
END.
CHauser February 16, 1989: added mode parameter to CreateFileStream and RopeOpen. Moved defaultMode to the interface.