Copyright (C) 1994, Digital Equipment Corp.
Portions Copyright 1996, Critical Mass, Inc.
UNSAFE MODULE FileWin32;
IMPORT File, RegularFile, Terminal, Pipe, OSError;
IMPORT WinDef, WinError, WinNT, WinBase, WinCon;
IMPORT OSErrorWin32, OSWin32, TimeWin32;
REVEAL
File.T = T BRANDED OBJECT
ds: DirectionSet;
OVERRIDES
write := FileWrite;
close := FileClose;
status := FileStatus
END;
Pipe.T = File.T BRANDED OBJECT
OVERRIDES
read := PipeRead
END;
Terminal.T = File.T BRANDED OBJECT
inputEvents: InputEventBuffer := NIL;
isConsole: BOOLEAN := FALSE;
OVERRIDES
read := TerminalRead
END;
RegularFile.T = RegularFile.Public BRANDED OBJECT
OVERRIDES
read := RegularFileRead;
seek := RegularFileSeek;
flush := RegularFileFlush;
lock := RegularFileLock;
unlock := RegularFileUnlock
END;
TYPE
InputEventBuffer = REF ARRAY [0..99] OF WinCon.INPUT_RECORD;
PROCEDURE New(handle: WinNT.HANDLE; ds: DirectionSet)
: File.T RAISES {OSError.E}=
VAR
ft := WinBase.GetFileType(handle);
cm: WinDef.DWORD;
BEGIN
CASE ft OF
| WinBase.FILE_TYPE_DISK =>
RETURN NEW(RegularFile.T, handle := handle, ds := ds)
| WinBase.FILE_TYPE_CHAR =>
WITH isCon = (WinCon.GetConsoleMode(handle, ADR(cm)) = 1) DO
(* If GetConsoleMode succeeds, assume it's a console *)
RETURN NEW(Terminal.T, handle := handle, ds := ds, isConsole := isCon);
END;
| WinBase.FILE_TYPE_PIPE => RETURN NewPipe(handle, ds)
ELSE (* includes FILE_TYPE_UNKNOWN, FILE_TYPE_REMOTE *)
OSErrorWin32.Raise0(WinError.ERROR_INVALID_HANDLE);
<*ASSERT FALSE*>
END;
END New;
PROCEDURE NewPipe(handle: WinNT.HANDLE; ds: DirectionSet): Pipe.T =
BEGIN
RETURN NEW(Pipe.T, handle := handle, ds := ds)
END NewPipe;
---------------------------File methods------------------------------------
PROCEDURE FileWrite(h: File.T; READONLY b: ARRAY OF File.Byte)
RAISES {OSError.E} =
VAR nWritten: INTEGER;
BEGIN
IF NOT(Direction.Write IN h.ds) THEN BadDirection(); END;
IF WinBase.WriteFile(h.handle, ADR(b[0]), NUMBER(b),
ADR(nWritten), NIL) = 0 THEN
OSErrorWin32.Raise()
END;
<*ASSERT nWritten = NUMBER(b) *>
END FileWrite;
PROCEDURE FileClose(h: File.T) RAISES {OSError.E} =
BEGIN
IF WinBase.CloseHandle(h.handle) = 0 THEN OSErrorWin32.Raise() END
END FileClose;
PROCEDURE FileStatus(h: File.T): File.Status RAISES {OSError.E}=
VAR
ffd: WinBase.BY_HANDLE_FILE_INFORMATION;
status: File.Status;
ft := WinBase.GetFileType(h.handle);
BEGIN
CASE ft OF
| WinBase.FILE_TYPE_DISK =>
IF WinBase.GetFileInformationByHandle(h.handle, ADR(ffd)) = 0 THEN
OSErrorWin32.Raise();
END;
status.type := RegularFile.FileType;
status.modificationTime := TimeWin32.FromFileTime(ffd.ftLastWriteTime);
status.size := ffd.nFileSizeLow
| WinBase.FILE_TYPE_CHAR => status.type := Terminal.FileType
| WinBase.FILE_TYPE_PIPE => status.type := Pipe.FileType
ELSE (* includes FILE_TYPE_UNKNOWN, FILE_TYPE_REMOTE *)
<* ASSERT FALSE *>
END;
RETURN status
END FileStatus;
------------------------------Pipe methods---------------------------------
PROCEDURE PipeRead(
h: Pipe.T;
VAR (*out*) b: ARRAY OF File.Byte;
mayBlock: BOOLEAN := TRUE)
: INTEGER RAISES {OSError.E} =
VAR
numToRead: WinDef.DWORD := NUMBER(b);
numAvail, numRead: WinDef.DWORD;
err: INTEGER;
BEGIN
IF NOT(Direction.Read IN h.ds) THEN BadDirection(); END;
IF NOT mayBlock THEN
IF WinBase.PeekNamedPipe(
hNamedPipe := h.handle,
lpBuffer := NIL,
nBufferSize := 0,
lpBytesRead := NIL,
lpTotalBytesAvail := ADR(numAvail),
lpBytesLeftThisMessage := NIL) = 0 THEN
OSErrorWin32.Raise()
END;
IF numAvail = 0 THEN RETURN -1 END;
numToRead := MIN(numAvail, numToRead)
END;
IF WinBase.ReadFile(h.handle, ADR(b[0]), numToRead, ADR(numRead), NIL) = 0 THEN
err := WinBase.GetLastError();
IF err = WinError.ERROR_BROKEN_PIPE THEN RETURN 0 END;
(* *** What about ERROR_NO_DATA -- "The pipe is being closed."
or ERROR_PIPE_NOT_CONNECTED -- "No process is on the other
end of the pipe."? *)
OSErrorWin32.Raise0(err);
END;
RETURN numRead
END PipeRead;
----------------------------Terminal methods-------------------------------
PROCEDURE TerminalRead(
h: Terminal.T;
VAR (*out*) b: ARRAY OF File.Byte;
mayBlock: BOOLEAN := TRUE)
: INTEGER RAISES {OSError.E} =
VAR
numToRead: WinDef.DWORD := NUMBER(b);
numAvail, numRead: WinDef.DWORD;
err: INTEGER;
BEGIN
IF NOT h.isConsole THEN RETURN RegularFileRead(h, b, mayBlock) END;
IF NOT(Direction.Read IN h.ds) THEN BadDirection(); END;
IF NOT mayBlock THEN
(* count the characters waiting in the input buffer *)
IF (h.inputEvents = NIL) THEN h.inputEvents := NEW (InputEventBuffer); END;
IF WinCon.PeekConsoleInput (h.handle, ADR (h.inputEvents[0]),
NUMBER (h.inputEvents^), ADR(numRead)) = 0 THEN
OSErrorWin32.Raise();
END;
numAvail := 0;
FOR i := 0 TO numRead - 1 DO
WITH z = h.inputEvents[i] DO
IF (z.EventType = WinCon.KEY_EVENT)
AND (z.Event.bKeyDown # 0) THEN
INC (numAvail, MAX (1, z.Event.wRepeatCount));
END;
END;
END;
IF numAvail = 0 THEN RETURN -1 END;
numToRead := MIN(numAvail, numToRead);
END;
IF WinBase.ReadFile(h.handle, ADR(b[0]), numToRead,
ADR(numRead), NIL) = 0 THEN
err := WinBase.GetLastError();
IF err = WinError.ERROR_BROKEN_PIPE THEN RETURN 0 END;
(* *** What about ERROR_NO_DATA -- "The pipe is being closed."
or ERROR_PIPE_NOT_CONNECTED -- "No process is on the other
end of the pipe."? *)
OSErrorWin32.Raise0(err);
END;
RETURN numRead
END TerminalRead;
---------------------------RegularFile methods-----------------------------
PROCEDURE RegularFileRead(h: (*Regular*)File.T;
VAR (*out*) b: ARRAY OF File.Byte;
<* UNUSED *>mayBlock: BOOLEAN := TRUE): INTEGER RAISES {OSError.E} =
VAR numRead: INTEGER;
BEGIN
IF NOT(Direction.Read IN h.ds) THEN BadDirection(); END;
IF NUMBER(b) <= 0 THEN RETURN 0; END;
IF WinBase.ReadFile(h.handle, ADR(b[0]), NUMBER(b),
ADR(numRead), NIL) = 0 THEN
OSErrorWin32.Raise()
END;
RETURN numRead
END RegularFileRead;
PROCEDURE RegularFileSeek(
h: RegularFile.T; origin: RegularFile.Origin; offset: INTEGER)
: INTEGER RAISES {OSError.E} =
BEGIN
WITH res = WinBase.SetFilePointer(h.handle, offset, NIL, ORD(origin)) DO
IF res < 0 THEN OSErrorWin32.Raise() END;
RETURN res
END
END RegularFileSeek;
PROCEDURE RegularFileFlush(h: RegularFile.T) RAISES {OSError.E}=
BEGIN
IF WinBase.FlushFileBuffers(h.handle) = 0 THEN OSErrorWin32.Raise() END
END RegularFileFlush;
PROCEDURE RegularFileLock(h: RegularFile.T): BOOLEAN RAISES {OSError.E}=
VAR err: INTEGER;
BEGIN
IF OSWin32.Win95() THEN
IF WinBase.LockFile(
hFile := h.handle,
dwFileOffsetLow := 0,
dwFileOffsetHigh := 0,
nNumberOfBytesToLockLow := 1,
nNumberOfBytesToLockHigh := 0) = 0 THEN
err := WinBase.GetLastError();
IF err = WinError.ERROR_LOCK_VIOLATION THEN RETURN FALSE END;
OSErrorWin32.Raise0(err)
END;
ELSE
IF WinBase.LockFile(
hFile := h.handle,
dwFileOffsetLow := 0,
dwFileOffsetHigh := 0,
nNumberOfBytesToLockLow := LAST(WinDef.DWORD),
nNumberOfBytesToLockHigh := LAST(WinDef.DWORD)) = 0 THEN
err := WinBase.GetLastError();
IF err = WinError.ERROR_LOCK_VIOLATION THEN RETURN FALSE END;
OSErrorWin32.Raise0(err)
END;
END;
RETURN TRUE
END RegularFileLock;
PROCEDURE RegularFileUnlock(h: RegularFile.T) RAISES {OSError.E}=
BEGIN
IF OSWin32.Win95() THEN
IF WinBase.UnlockFile(
hFile := h.handle,
dwFileOffsetLow := 0,
dwFileOffsetHigh := 0,
nNumberOfBytesToUnlockLow := LAST(WinDef.DWORD),
nNumberOfBytesToUnlockHigh := LAST(WinDef.DWORD)) = 0 THEN
OSErrorWin32.Raise()
END;
ELSE
IF WinBase.UnlockFile(
hFile := h.handle,
dwFileOffsetLow := 0,
dwFileOffsetHigh := 0,
nNumberOfBytesToUnlockLow := 1,
nNumberOfBytesToUnlockHigh := 0) = 0 THEN
OSErrorWin32.Raise()
END;
END;
END RegularFileUnlock;
------------------------------------------------ checked runtime errors ---
EXCEPTION IllegalDirection;
PROCEDURE BadDirection () =
<*FATAL IllegalDirection*>
BEGIN
RAISE IllegalDirection;
END BadDirection;
BEGIN
END FileWin32.