cminstall/src/OS.m3


 Copyright 1996-2000, Critical Mass, Inc.  All rights reserved. 
 See file COPYRIGHT-CMASS for details. 

MODULE OS;

IMPORT Atom, AtomList, Env, File, FileWr, FS, Msg, Pathname;
IMPORT OSError, RegularFile, Text2, Text, Thread, Wr;

PROCEDURE IsDirectory (file: TEXT): BOOLEAN =
  BEGIN
    TRY
      WITH stat = FS.Status (file) DO
        RETURN stat.type = FS.DirectoryFileType;
      END
    EXCEPT
    | OSError.E => RETURN FALSE;
    END
  END IsDirectory;

PROCEDURE IsExecutable (file: TEXT): BOOLEAN =
  BEGIN
    TRY
      WITH stat = FS.Status (file) DO
        RETURN stat.type = RegularFile.FileType;
      END
    EXCEPT
    | OSError.E => RETURN FALSE;
    END
  END IsExecutable;

PROCEDURE FileNameEq (a, b: TEXT): BOOLEAN =
  BEGIN
    IF (on_unix)
      THEN  RETURN Text.Equal (a, b);     (* POSIX *)
      ELSE  RETURN Text2.CIEqual (a, b);  (* WIN32 *)
    END;
  END FileNameEq;

PROCEDURE CleanDirName (dir: TEXT): TEXT =
  CONST Slash = ARRAY BOOLEAN OF CHAR { '\134', '/' }[on_unix];
  VAR len: INTEGER;
  BEGIN
    IF (dir # NIL) AND NOT FileNameEq (Pathname.Prefix (dir), dir) THEN
      len := Text.Length (dir);
      IF (len > 1) AND Text.GetChar (dir, len-1) = Slash THEN
        dir := Text.Sub (dir, 0, len-1);
      END;
    END;
    RETURN dir;
  END CleanDirName;

PROCEDURE FindExecutable (file: TEXT): TEXT =
  CONST UnixExts = ARRAY OF TEXT { NIL };
  CONST WinExts = ARRAY OF TEXT { NIL, "exe", "com", "cmd", "bat" };
  VAR path := Env.Get ("PATH");
  BEGIN
    IF on_unix
      THEN RETURN SearchPath (file, path, ':', UnixExts);
      ELSE RETURN SearchPath (file, path, ';', WinExts);
    END;
  END FindExecutable;

PROCEDURE SearchPath (file, path: TEXT;   sep: CHAR;
                      READONLY exts: ARRAY OF  TEXT): TEXT =
  VAR dir, fn: TEXT;  s0, s1, len: INTEGER;  no_ext: BOOLEAN;
  BEGIN
    IF IsExecutable (file) THEN RETURN file; END;

    no_ext := Text.Equal (file, Pathname.Base (file));

    (* first try the file without looking at the path *)
    IF no_ext THEN
      FOR i := FIRST (exts) TO LAST (exts) DO
        fn := Pathname.Join (NIL, file, exts[i]);
        IF IsExecutable (fn) THEN RETURN fn; END;
      END;
    END;

    IF path = NIL THEN RETURN NIL; END;
    IF Pathname.Absolute (file) THEN RETURN NIL; END;

    (* try the search path *)
    len := Text.Length (path);  s0 := 0;
    WHILE (s0 < len) DO
      s1 := Text.FindChar (path, sep, s0);
      IF (s1 < 0) THEN s1 := len; END;
      IF (s0 < s1) THEN
        dir := Text.Sub (path, s0, s1 - s0);
        dir := CleanDirName (dir);
        IF no_ext THEN
          FOR i := FIRST (exts) TO LAST (exts) DO
            fn := Pathname.Join (dir, file, exts[i]);
            IF IsExecutable (fn) THEN RETURN fn; END;
          END;
        ELSE
          fn := Pathname.Join (dir, file, NIL);
          IF IsExecutable (fn) THEN RETURN fn; END;
        END;
      END;
      s0 := s1 + 1;
    END;

    (* failed *)
    RETURN NIL;
  END SearchPath;

PROCEDURE GetAbsolutePath (a, b: TEXT := NIL): TEXT =
  VAR path := MakePath (a, b);
  BEGIN
    IF Pathname.Absolute (path) THEN RETURN path; END;
    TRY
      RETURN FS.GetAbsolutePathname (path);
    EXCEPT OSError.E =>
      RETURN path;
    END;
  END GetAbsolutePath;

PROCEDURE MakePath (a, b, c, d: TEXT := NIL): TEXT =
  VAR path := a;

  PROCEDURE Join (a, b: TEXT): TEXT =
    BEGIN
      IF Pathname.Absolute(b) THEN
        RETURN b;
      ELSE
        RETURN Pathname.Join(a, b, NIL);
      END;
    END Join;

  BEGIN (* MakePath *)
    IF (b # NIL) THEN path := Join (path, b); END;
    IF (c # NIL) THEN path := Join (path, c); END;
    IF (d # NIL) THEN path := Join (path, d); END;
    RETURN path;
  END MakePath;

PROCEDURE MakeDir (dir: TEXT): BOOLEAN =
  VAR parent: TEXT;
  BEGIN
    IF dir = NIL OR Text.Length (dir) = 0 THEN dir := "."; END;
    IF IsDirectory (dir) THEN RETURN TRUE; END;

    parent := Pathname.Prefix (dir);
    IF (parent # NIL) AND NOT FileNameEq (parent, dir) THEN
      IF NOT MakeDir (parent) THEN RETURN FALSE; END;
    END;

    TRY
      FS.CreateDirectory (dir);
      RETURN TRUE;
    EXCEPT OSError.E =>
      RETURN FALSE;
    END;
  END MakeDir;

PROCEDURE WriteFile (name, contents: TEXT) =
  VAR wr: Wr.T;
  BEGIN
    TRY
      wr := FileWr.Open (name);
      Wr.PutText (wr, contents);
      Wr.Close (wr);
    EXCEPT
    | OSError.E (ec) =>
        Msg.Error (ec, "Unable to open the file: ", name);
    | Wr.Failure (ec) =>
        Msg.Error (ec, "Unable to write the file: ", name);
    | Thread.Alerted =>
        Msg.Error (NIL, "Interrupted while writing the file: ", name);
    END;
  END WriteFile;

PROCEDURE RemoveFile (file: TEXT) =
  BEGIN
    TRY
      FS.DeleteFile (file);
    EXCEPT OSError.E (ec) =>
      Msg.Error (ec, "Unable to remove file: ", file);
    END;
  END RemoveFile;

PROCEDURE MoveFile (src, dest: TEXT) =
  BEGIN
    TRY
      CopyFile (src, dest);
    EXCEPT OSError.E (ec) =>
      Msg.Error (ec, "Unable to copy file: ", src, " -> ", dest);
    END;
    RemoveFile (src);
  END MoveFile;

PROCEDURE CopyFile (src, dest: TEXT) RAISES {OSError.E} =
  VAR
    rd, wr : File.T := NIL;
    len    : INTEGER;
    buf    : ARRAY [0..4095] OF File.Byte;
  BEGIN
    TRY
      rd := FS.OpenFileReadonly (src);
      wr := OpenDestination (dest, rd);
      LOOP
        len := rd.read (buf);
        IF (len <= 0) THEN EXIT; END;
        wr.write (SUBARRAY (buf, 0, len));
      END;
    FINALLY
      IF (wr # NIL) THEN wr.close (); END;
      IF (rd # NIL) THEN rd.close (); END;
    END;
  END CopyFile;

PROCEDURE OpenDestination (dest: TEXT;  src: File.T): File.T RAISES {OSError.E} =
  BEGIN
    (* We need to preserve permission bits on Unix.  On Win32 it's too hard
       (Win95 doesn't have security, and WinNT w/o NTFS is broken too!),
       so we don't bother.  *)
    IF NOT on_unix THEN
      (* File permissions on Windows are broken... *)
      src := NIL;
    END;

    TRY RETURN FS.OpenFile (dest, template := src);
    EXCEPT OSError.E => (* nope. *)
    END;

    (* If we can't open the file for writing, try deleting it first
       and then opening it, sometimes that'll work instead... *)

    TRY FS.DeleteFile (dest);
    EXCEPT OSError.E => (* doesn't look very hopeful *)
    END;

    RETURN FS.OpenFile (dest, template := src);
  END OpenDestination;

PROCEDURE Err (args: AtomList.T): TEXT =
  VAR msg : TEXT := NIL;
  BEGIN
    WHILE (args # NIL) DO
      IF (msg = NIL) THEN  msg := ": ";  ELSE  msg := msg & "  ***  ";  END;
      msg  := msg & Atom.ToText (args.head);
      args := args.tail;
    END;
    IF (msg = NIL) THEN msg := ": ** NO INFO **"; END;
    RETURN msg;
  END Err;

BEGIN
END OS.

interface OS is in:


interface Msg is in:


interface Text2 is in: