libm3/src/os/POSIX/FilePosix.m3


 Copyright (C) 1993, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              
 Last modified on Thu Mar 30 14:12:11 PST 1995 by mcjones    
      modified on Sun Jan  8 14:13:46 PST 1995 by kalsow     
      modified on Wed Mar 16 14:33:40 PST 1994 by wobber     
      modified on Mon Feb  8 12:35:46 PST 1993 by mjordan    

UNSAFE MODULE FilePosix;

IMPORT Cerrno, Ctypes, File, FS, M3toC, OSError, OSErrorPosix, Pipe,
  RegularFile, SchedulerPosix, Terminal, Uerror, Unix, Ustat, Uuio,
  Word, Utypes, FilePosixC;

REVEAL
  File.T = T BRANDED OBJECT OVERRIDES
    close := FileClose;
    status := FileStatus
  END;

TYPE IntermittentFile = File.T BRANDED OBJECT OVERRIDES
    read := IntermittentRead;
    write := IntermittentWrite
  END;

REVEAL
  Pipe.T = IntermittentFile BRANDED OBJECT END;
  Terminal.T = IntermittentFile BRANDED OBJECT END;
  RegularFile.T = RegularFile.Public BRANDED OBJECT OVERRIDES
    read := RegularFileRead;
    write := RegularFileWrite;
    seek := RegularFileSeek;
    flush := RegularFileFlush;
    lock := RegularFileLock;
    unlock := RegularFileUnlock
  END;

PROCEDURE FileTypeFromStatbuf(READONLY statbuf: Ustat.struct_stat)
  : File.Type =
  BEGIN
    WITH type = Word.And(statbuf.st_mode, Ustat.S_IFMT) DO
      IF type = Ustat.S_IFCHR THEN
        IF IsDevNull(statbuf)
          THEN RETURN RegularFile.FileType
          ELSE RETURN Terminal.FileType
        END;
      ELSIF (type = Ustat.S_IFIFO) OR (type = Ustat.S_IFSOCK) THEN
        RETURN Pipe.FileType;
      ELSIF type = Ustat.S_IFDIR THEN
        RETURN FS.DirectoryFileType
      ELSE
        RETURN RegularFile.FileType
      END
    END;
  END FileTypeFromStatbuf;

PROCEDURE New(fd: INTEGER; ds: DirectionSet): File.T RAISES {OSError.E} =
  VAR statbuf: Ustat.struct_stat; type: File.Type;
  BEGIN
    IF Ustat.fstat(fd, ADR(statbuf)) # 0 THEN OSErrorPosix.Raise() END;
    type := FileTypeFromStatbuf(statbuf);
    IF type = RegularFile.FileType THEN
      RETURN NEW(RegularFile.T, fd := fd, ds := ds)
    END;
    IF type = Terminal.FileType THEN
      RETURN NEW(Terminal.T, fd := fd, ds := ds)
    END;
    IF type = Pipe.FileType THEN
      RETURN NEW(Pipe.T, fd := fd, ds := ds)
    END;
    IF type = FS.DirectoryFileType THEN
      OSErrorPosix.Raise0(Uerror.EISDIR)
    END;
    (* Other... *)
    RETURN NEW(RegularFile.T, fd := fd, ds := ds)
  END New;

PROCEDURE NewPipe(fd: INTEGER; ds: DirectionSet): Pipe.T =
  BEGIN
    RETURN NEW(Pipe.T, fd := fd, ds := ds)
  END NewPipe;
---------------------------File methods------------------------------------

PROCEDURE FileClose(h: File.T) RAISES {OSError.E} =
  BEGIN
    IF Unix.close(h.fd) < 0 THEN OSErrorPosix.Raise() END
  END FileClose;

PROCEDURE FileStatus(h: File.T): File.Status RAISES {OSError.E} =
  VAR statBuf: Ustat.struct_stat;
    status: File.Status;
  BEGIN
    IF Ustat.fstat(h.fd, ADR(statBuf)) < 0 THEN OSErrorPosix.Raise() END;
    TYPECASE h OF
    | RegularFile.T => status.type := RegularFile.FileType
    | Pipe.T => status.type := Pipe.FileType
    | Terminal.T => status.type := Terminal.FileType
    ELSE <* ASSERT FALSE *>
    END;
    status.modificationTime := FLOAT(statBuf.st_mtime, LONGREAL);
    WITH size = statBuf.st_size DO
      IF size < 0L THEN OSErrorPosix.Raise() END;
      status.size := size;
    END;
    RETURN status
  END FileStatus;
---------------------------RegularFile methods-----------------------------

PROCEDURE RegularFileRead(
    h: RegularFile.T;
    VAR (*OUT*) b: ARRAY OF File.Byte;
    <*UNUSED*> mayBlock: BOOLEAN := TRUE)
  : INTEGER RAISES {OSError.E} =
  VAR p_b: ADDRESS := ADR(b[0]);
  BEGIN
    IF NOT(Direction.Read IN h.ds) THEN BadDirection(); END;
    WITH bytesRead = Uuio.read(h.fd, p_b, NUMBER(b)) DO
      IF bytesRead < 0 THEN OSErrorPosix.Raise() END;
      RETURN bytesRead
    END
  END RegularFileRead;

PROCEDURE RegularFileWrite(
    h: RegularFile.T;
    READONLY b: ARRAY OF File.Byte)
  RAISES {OSError.E} =
  VAR
    p_b: ADDRESS := ADR(b[0]);
    bytes := NUMBER(b);
    bytesWritten: INTEGER;  BEGIN
    IF NOT(Direction.Write IN h.ds) THEN BadDirection(); END;
    LOOP
      bytesWritten := Uuio.write(h.fd, p_b, bytes);
      IF bytesWritten < 0 THEN OSErrorPosix.Raise() END;
      (* Partial write if media is full, quota exceeded, etc. *)
      IF bytesWritten = bytes THEN EXIT END;
      <* ASSERT bytesWritten > 0 *>
      INC(p_b, bytesWritten);
      DEC(bytes, bytesWritten);
    END
  END RegularFileWrite;

PROCEDURE RegularFileSeek(
    h: RegularFile.T; origin: RegularFile.Origin; offset: INTEGER)
  : INTEGER RAISES {OSError.E} =
  BEGIN
    WITH result = Unix.lseek(h.fd, VAL(offset, Utypes.off_t), ORD(origin)) DO
      IF result < VAL(0, Utypes.off_t) THEN OSErrorPosix.Raise() END;
      RETURN VAL(result, INTEGER)
    END
  END RegularFileSeek;

PROCEDURE RegularFileFlush(h: RegularFile.T) RAISES {OSError.E} =
  BEGIN
    IF Unix.fsync(h.fd) < 0 THEN OSErrorPosix.Raise() END
  END RegularFileFlush;

PROCEDURE RegularFileLock(h: RegularFile.T): BOOLEAN RAISES {OSError.E} =
  VAR i: INTEGER;
  BEGIN
    i := FilePosixC.RegularFileLock(h.fd);
    IF i < 0 THEN
      OSErrorPosix.Raise();
    END;
    RETURN (i # 0);
  END RegularFileLock;

PROCEDURE RegularFileUnlock(h: RegularFile.T) RAISES {OSError.E} =
  VAR i: INTEGER;
  BEGIN
    i := FilePosixC.RegularFileUnlock(h.fd);
    IF i < 0 THEN
      OSErrorPosix.Raise();
    END;
  END RegularFileUnlock;
---------------------IntermittentFile methods------------------------------

PROCEDURE IntermittentRead(
    h: IntermittentFile;
    VAR (*OUT*) b: ARRAY OF File.Byte;
    mayBlock := TRUE)
  : INTEGER RAISES {OSError.E} =
  VAR
    status, errno: INTEGER;
    old_mode := Unix.fcntl(h.fd, Unix.F_GETFL, 0);
    new_mode := Word.Or(old_mode, Unix.M3_NONBLOCK);
    p_b: ADDRESS := ADR (b[0]);
  BEGIN
    IF NOT(Direction.Read IN h.ds) THEN BadDirection(); END;

    LOOP
      (* Make the read call non-blocking; we cannot set/reset the mode
         at creation/close time, because this may leave the file in an
         unexpected state in the case of a core dump elsewhere. *)

      IF Unix.fcntl(h.fd, Unix.F_SETFL, new_mode) # 0 THEN
        OSErrorPosix.Raise()
      END;

      status := Uuio.read(h.fd, p_b, NUMBER(b));
      errno := Cerrno.GetErrno();

      IF Unix.fcntl(h.fd, Unix.F_SETFL, old_mode) # 0 THEN
        OSErrorPosix.Raise()
      END;

      IF status >= 0 THEN
        RETURN status
      ELSIF (status = -1)
         AND (errno # Uerror.EWOULDBLOCK)
         AND (errno # Uerror.EAGAIN) THEN
        OSErrorPosix.Raise0(errno)
      ELSIF NOT mayBlock THEN
        RETURN -1
      END;

      EVAL SchedulerPosix.IOWait(h.fd, TRUE)
    END
  END IntermittentRead;

PROCEDURE IntermittentWrite(h: File.T; READONLY b: ARRAY OF File.Byte)
  RAISES {OSError.E} =
  VAR
    status, errno: INTEGER;
    old_mode := Unix.fcntl(h.fd, Unix.F_GETFL, 0);
    new_mode := Word.Or(old_mode, Unix.M3_NONBLOCK);
    p := LOOPHOLE (ADR(b[0]), Ctypes.char_star);
    n: Ctypes.int := NUMBER(b);
  BEGIN
    IF NOT(Direction.Write IN h.ds) THEN BadDirection (); END;

    LOOP
      (* Make the write call non-blocking; we cannot set/reset the mode
         at creation/close time, because this may leave the file in an
         unexpected state in the case of a core dump elsewhere. *)

      IF Unix.fcntl(h.fd, Unix.F_SETFL, new_mode) # 0 THEN
        OSErrorPosix.Raise()
      END;

      status := Uuio.write(h.fd, p, n);
      errno := Cerrno.GetErrno();

      IF Unix.fcntl(h.fd, Unix.F_SETFL, old_mode) # 0 THEN
        OSErrorPosix.Raise()
      END;

      IF status >= 0 THEN
        p := LOOPHOLE(LOOPHOLE(p, INTEGER) + status, Ctypes.char_star);
        n := n - status;
        IF n = 0 THEN EXIT END
      ELSIF (status = -1)
         AND (errno # Uerror.EWOULDBLOCK)
         AND (errno # Uerror.EAGAIN) THEN
        OSErrorPosix.Raise0(errno)
      END;

      EVAL SchedulerPosix.IOWait(h.fd, FALSE)
    END
  END IntermittentWrite;
-------------------------Support procedures--------------------------------

VAR
  null_done := FALSE;
  null_stat: Ustat.struct_stat;
  null_fd: INTEGER;

PROCEDURE IsDevNull(READONLY statbuf: Ustat.struct_stat): BOOLEAN RAISES {} =
  VAR result: INTEGER;
  BEGIN
    IF NOT null_done THEN
      null_fd := Unix.open(M3toC.FlatTtoS("/dev/null"), Unix.O_RDONLY, Unix.Mrwrwrw);
      IF null_fd < 0 THEN
        null_done := TRUE;
        RETURN FALSE
      ELSE
        result := Ustat.fstat(null_fd, ADR(null_stat));
        EVAL Unix.close(null_fd);
        IF result # 0 THEN
          null_fd := -1
        END
      END;
      null_done := TRUE;
    END;
    RETURN null_fd >= 0 AND statbuf.st_rdev = null_stat.st_rdev
  END IsDevNull;

EXCEPTION IllegalDirection;

PROCEDURE BadDirection () =
  <*FATAL IllegalDirection*>
  BEGIN
    RAISE IllegalDirection;
  END BadDirection;

BEGIN
END FilePosix.

interface Uerror is in:


interface Unix is in:


interface Uuio is in:


interface Utypes is in: