Package: src/packages/filesystem.fdoc

File System

key file
filename.flx share/lib/std/io/filename.flx
key file
filestat.flx share/lib/std/io/filestat.flx
posix_filestat.flx share/lib/std/posix/filestat.flx
win32_filestat.flx share/lib/std/win32/filestat.flx
key file
filesystem.flx share/lib/std/io/filesystem.flx
posix_filesystem.flx share/lib/std/posix/filesystem.flx
win32_filesystem.flx share/lib/std/win32/filesystem.flx
key file
directory.flx share/lib/std/io/directory.flx
posix_directory.flx share/lib/std/posix/directory.flx
win32_directory.flx share/lib/std/win32/directory.flx

Filename

//[filename.flx]

//$ Operations on filenames.
class Filename_class[os] {

  //$ The path separator.
  virtual fun sep: 1 -> string;
  virtual fun is_absolute_filename : string -> bool;
  virtual fun root_subdir : string -> string;

  virtual fun executable_extension : 1 -> string;
  virtual fun static_object_extension: 1 -> string;
  virtual fun dynamic_object_extension: 1 -> string;
  virtual fun static_library_extension: 1 -> string;
  virtual fun dynamic_library_extension: 1 -> string;



  //$ split1 returns a pair consisting of a directory name and basename
  //$ with the separator between them lost except in the special case
  //$ "/x" where the "/" is kept as the directory name.
  //$ If there is no separator, the path is the basename and
  //$ the directory name is the empty string (NOT . !!!)

  fun split1(s:string)=> match find_last_of(s,#sep) with
    | Some pos =>
      if pos==0uz then #sep else s.[to pos] endif,
      s.[pos+#sep.len to]
    | #None => "",s
    endmatch
  ;

  private fun split(s:string, acc:List::list[string]):List::list[string]=>
    let d,b = split1 s in
    if d == "" then List::Cons(b,acc)
    elif d == #sep then List::Cons(d, List::Cons(b,acc))
    else split (d, List::Cons (b, acc))
    endif
  ;

  //$ split a filename into a list of components.
  fun split(s:string)=> split (s, List::Empty[string]);

  //$ Join two pathnames into a single pathname.
  //$ split and join are logical inverses, however join is not
  //$ not associative: join("x", join("","y")) = "x/y"
  //$ whereas join(join("x",""),"y") = "x//y"
  //$ since split pulls components off from the RHS we have to
  //$ fold them back from the left

  fun join(p:string, b:string)=>
    if p == "" then b
    elif p == #sep then p+b
    elif p.[-1] == #sep.[0] then p+b
    else p+#sep+b
    endif
  ;

  //$ Get the basename of a path (last component).
  fun basename(s:string)=> match split1(s) with | _,b => b endmatch;

  //$ Get the directory name of a path (all but the last component).
  fun dirname(s:string)=> match split1(s) with | d,_ => d endmatch;

  //$ Return a list of all the directory names in a path.
  //$ For example a/b/c gives "a", "a/b"
  fun directories (s:string) : list[string] =>
     let d,b = split1 s in
     if d == "" then Empty[string]
     elif d == #sep then Empty[string]
     else directories d + d
  ;

  //$ Join 3 and 4 strings into a pathname.
  fun join(a:string, b:string, c:string)=> join(join(a,b),c);
  fun join(a:string, b:string, c:string,d:string)=> join(join(join(a,b),c),d);

  //$ Join 2 strings into a pathname (curried form).
  fun join(x:string) (y:string) => join(x,y);

  //$ Join all the strings in a list into a pathname.
  fun join(ps: List::list[string])=> List::fold_left Filename::join of (string) "" ps;

  //$ Split off extension. Includes the dot.
  //$ Invariant: input = basename + extension.
  //$ Works backwards until it hits a dot, path separator,
  //$ or end of data. If a dot, strip it and the tail of the string,
  //$ otherwise return the original string.
  fun split_extension (s:string): string * string = {
     var n = s.len;
     if n > 0uz do
       for var i in s.len - 1uz downto 0uz do
         var ch = s.[i];
         if ch == char "." return s.[to i],s.[i to];
         if ch == char #sep return s,"";
       done
     done
     return s,"";
  }

  //$ Remove an extension from a filename if there is one.
  fun strip_extension (s:string) => s.split_extension.0;

  //$ Get extension if there is one. Includes the dot.
  fun get_extension (s:string) => s.split_extension.1;

}

//$ Windows Filenames
class Win32Filename
{
  inherit Filename_class[Win32];
  instance Filename_class[Win32] {
    fun sep() => "\\";
    fun executable_extension ()=> ".exe";
    fun static_object_extension() => ".obj";
    fun dynamic_object_extension() => ".obj";
    fun static_library_extension() => ".lib";
    fun dynamic_library_extension() => ".dll";
    fun is_absolute_filename (f:string) =>
      f.[0] == "\\".char or // no drive letter
      f.[1] == ":".char and f.[2] == "\\".char // with drive letter
    ;
   fun root_subdir (s:string) => "C:\\"+s;

  }
}

//$ OSX Filenames
class OsxFilename
{
  inherit Filename_class[Osx];
  instance Filename_class[Osx] {
    fun sep() => "/";
    fun executable_extension ()=> "";
    fun static_object_extension() => ".o";
    fun dynamic_object_extension() => ".os";
    fun static_library_extension() => ".a";
    fun dynamic_library_extension() => ".dylib";
    fun is_absolute_filename (f:string) => f.[0] == "/";
    fun root_subdir (s:string) => "/"+s;

  }
}

//$ Posix Filenames
class PosixFilename
{
  inherit Filename_class[Posix];
  instance Filename_class[Posix] {
    fun sep() => "/";
    fun executable_extension ()=> "";
    fun static_object_extension() => ".o";
    fun dynamic_object_extension() => ".os";
    fun static_library_extension() => ".a";
    fun dynamic_library_extension() => ".so";
    fun is_absolute_filename (f:string) => f.[0] == "/";
    fun root_subdir (s:string) => "/"+s;
  }
}

//$ Host Filenames.
class Filename
{
if PLAT_WIN32 do
  inherit Win32Filename;
elif PLAT_MACOSX do
  inherit OsxFilename;
else
  inherit PosixFilename;
done
}

Filestat

//[filestat.flx]

//$ Filesystem file kind query functions parametrised
//$ by operating system, status type and mode type.
class FileStat_class[OS,stat_t, mode_t]
{
  //$ Get information about a file into a status buffer.
  //$ Sets error code at argument 3 pointer.
  virtual proc stat: string * &stat_t * ∫

  //$ set access and modification time of a file.
  //$ Sets error code at argument 4 pointer.
  //$ Times are in seconds, nominally from Epoch (Jan 1 1970).
  virtual proc utime: string * double * double * ∫

  //$ Change read,write permissions for group, owner etc.
  //$ Return 0 on success.
  //$ On Windows this function may silently fail to obey
  //$ unsupported operations.
  virtual gen chmod: string * mode_t -> int;

  //$ set mask for subsequent permissions.
  //$ On Windows this function may silently fail to obey
  //$ unsupported operations.
  virtual gen umask: mode_t -> mode_t;

  //$ Abstracted platform independent file type taxonomy.
  variant file_type_t =
    | PIPE
    | STREAM
    | DIRECTORY
    | BLOCK
    | REGULAR
    | SYMLINK
    | SOCKET
    | INDETERMINATE
    | NONEXISTANT
    | NOPERMISSION
  ;

  //$ Get the file type from a file stat buffer.
  virtual fun file_type: &stat_t -> file_type_t;

  //$ Fill a stat buffer with information about a file.
  gen stat(file: string, statbuf:&stat_t) = {
    var res: int;
    stat(file, statbuf, &res);
    return res == 0;
  }

  //$ Get a file last modification time from a stat buffer.
  //$ Time is in seconds.
  fun mtime: &stat_t -> double = "(double)($1->st_mtime)";

  //$ Get a file creation time from a stat buffer.
  //$ Note: not available on Unix.
  //$ Time is in seconds.
  fun ctime: &stat_t -> double = "(double)($1->st_ctime)";

  //$ Get modification time of a file by name.
  //$ Time is in seconds.
  fun filetime(f:string):double =
  {
    var b: stat_t;
    var err:int;
    stat(f,&b,&err);
    return if err == 0 then mtime (&b) else 0.0 endif;
  }

  //$ Set the last access and modification time of a file by name.
  gen utime(f:string, a:double, m:double): bool = {
    var r:int;
    utime(f,a,m,&r);
    return r == 0;
  }

  //$ Set the last access and modification time of a file by name,
  //$ where the two times are given by a single argument.
  gen utime(f:string, t:double) => utime(f,t,t);

  //$ Check if a file exists.
  fun fileexists(f:string):bool=> filetime f != 0.0;

  //$ Find the type of a file.
  fun filetype(f:string):file_type_t =
  {
    var b:stat_t;
    var err:int;
    stat(f,&b,&err);
    return
      if err == 0 then file_type (&b)
      elif errno == EACCES then NOPERMISSION
      elif errno == ENOENT then NONEXISTANT
      else INDETERMINATE
      endif
    ;
  }

  fun past_time () => -1.0;
  fun future_time () => double(ulong(-1)); // a hacky way to get a big number

  fun strfiletime0 (x:double) = {
    return
      if x == #past_time then "BIG BANG"
      elif x == #future_time then "BIG CRUNCH"
      else fmt (x, fixed (0,3))
      endif
    ;
  }

  fun strfiletime (x:double) = {
    assert x != 0.0;
    return strfiletime0 x;
  }

  fun dfiletime(var f:string, dflt:double)=
  {
    var x = FileStat::filetime (f);
    x = if x == 0.0 then dflt else x endif;
    //debugln$ "Time of file '" + f + "' is " + strfiletime x;
    return x;
  }


}

//$ Platform dependent operations for host file system.
class FileStat {
if PLAT_WIN32 do
  inherit Win32FileStat;
else
  inherit PosixFileStat;
done
}

Posix FileStat

//[posix_filestat.flx]

class PosixFileStat
{
  pod type stat_t = "struct stat" requires Posix_headers::sys_stat_h;

  pod type mode_t = "mode_t" requires Posix_headers::sys_types_h;
  instance Bits[mode_t] {} // defaults to C operators
  instance Eq[mode_t] { fun == : mode_t * mode_t -> bool = "$1==$2"; }
  open Eq[mode_t];
  open Bits[mode_t];

  //------------------------------------------------------------
  // file mode: type and permissions
  //------------------------------------------------------------
  // file types
  const S_IFMT  : mode_t; // file type mask
  const S_IFIFO : mode_t;
  const S_IFCHR : mode_t;
  const S_IFDIR : mode_t;
  const S_IFBLK : mode_t;
  const S_IFREG : mode_t;
  const S_IFLNK : mode_t;
  const S_IFSOCK: mode_t;

  // permissions
  const S_IRWXU : mode_t; // RWX mask: owner
  const S_IRUSR : mode_t;
  const S_IWUSR : mode_t;
  const S_IXUSR : mode_t;

  const S_IRWXG : mode_t; // RWX mask: group
  const S_IRGRP : mode_t;
  const S_IWGRP : mode_t;
  const S_IXGRP : mode_t;

  const S_IRWXO : mode_t; // RWX mask: other
  const S_IROTH : mode_t;
  const S_IWOTH : mode_t;
  const S_IXOTH : mode_t;

  const S_ISUID : mode_t; // set user id on execute
  const S_ISGID : mode_t; // set group id on execute
  const S_ISVXT : mode_t; // sticky bit
  val access_mask = S_IXOTH \| S_IXGRP \| S_IXUSR;


  fun raw_mode: &stat_t -> mode_t = "$1->st_mode";
  fun file_type(m:mode_t)=>m \& S_IFMT;
  fun file_perm(m:mode_t)=>m \& ~S_IFMT;

  ctor uint: mode_t = "(unsigned int)$1";



  inherit FileStat_class[Posix, stat_t, mode_t];
  instance FileStat_class[Posix, stat_t, mode_t]
  {
    proc stat: string * &stat_t * &int = "*$3=stat($1.c_str(),$2);";

    proc utime: string * double * double * &int =
    """
      {
      utimbuf u;
      u.actime=(time_t)$2;
      u.modtime=(time_t)$3;
      *$4 = utime($1.c_str(),&u);
      }
    """

    requires Posix_headers::utime_h;

    gen chmod: string * mode_t -> int = "chmod($1.c_str(),$2)" requires Posix_headers::sys_stat_h;
    gen umask: mode_t -> mode_t = "umask($1)";

    fun file_type (s:&stat_t): file_type_t =>
      let m = file_type$ raw_mode s in
      if m == S_IFIFO then PIPE
      elif m == S_IFCHR then STREAM
      elif m == S_IFDIR then DIRECTORY
      elif m == S_IFBLK then BLOCK
      elif m == S_IFREG then REGULAR
      elif m == S_IFLNK then SYMLINK
      elif m == S_IFSOCK then SOCKET
      else INDETERMINATE
      endif
    ;

  } // instance
}

Win32 FileStat

//[win32_filestat.flx]

class Win32FileStat
{
  //2 things:
  //
  // (1) AFAICT, Windows doesn't define mode_t and uses unsigned int.
  // (2) We still pull in sys/types.h because sys/stat.h uses it (and
  //     it must come first).
  //
  //(source http://msdn.microsoft.com/en-US/library/14h5k7ff(v=vs.80)).

  //pod type mode_t = "mode_t" requires Posix_headers::sys_types_h;

  pod type mode_t = "int";
  pod type stat_t = "struct __stat64" requires Posix_headers::sys_stat_h;

  instance Bits[mode_t] {} // defaults to C operators
  instance Eq[mode_t] { fun == : mode_t * mode_t -> bool = "$1==$2"; }
  open Eq[mode_t];
  open Bits[mode_t];

  // file types
  const _S_IFMT  : mode_t; // file type mask
  const _S_IFDIR : mode_t;
  const _S_IFREG : mode_t;

  // permissions
  const _S_IWRITE: mode_t; // RWX mask: owner
  const _S_IREAD  : mode_t;
  val access_mask = _S_IREAD \| _S_IWRITE;


  fun raw_mode: &stat_t -> mode_t = "$1->st_mode";
  fun file_type(m:mode_t)=>m \& _S_IFMT;
  fun file_perm(m:mode_t)=>m \& ~_S_IFMT;

  ctor uint: mode_t = "(unsigned int)$1";


  inherit FileStat_class[Win32, stat_t, mode_t];

  instance FileStat_class[Win32, stat_t, mode_t]
  {
    proc stat: string * &stat_t * &int = "*$3=_stat64($1.c_str(),$2);";
    // set access and modification time of a file
    proc utime: string * double * double * &int =
    """
      {
      __utimbuf64 u;
      u.actime=(time_t)$2;
      u.modtime=(time_t)$3;
      *$4 = _utime64($1.c_str(),&u);
      }
    """

    requires Win32_headers::sys_utime_h;

    gen chmod: string * mode_t -> int = "_chmod($1.c_str(),$2)" requires Win32_headers::io_h;
    gen umask: mode_t -> mode_t = "_umask($1)";

    fun file_type (s:&stat_t): file_type_t =>
      let m = file_type$ raw_mode s in
      if m == _S_IFDIR then DIRECTORY
      elif m == _S_IFREG then REGULAR
      else INDETERMINATE
      endif
    ;

  } // instance
}

File Syetem

//[filesystem.flx]

//$ Filesystem operations parametrised by operating system.
//$ YET TO BE DONE.
class FileSystem_class[os]
{
}

//$ Platform dependent filesystem operations for host file system.
class FileSystem {
if PLAT_WIN32 do
  inherit Win32FileSystem;
else
  inherit PosixFileSystem;
done

  proc unlink(f:string)
  {
    proc aux (d:string) (b:string)
    {
      if b == "." or b == ".." return;
      var f = if d == "" then b else Filename::join (d,b);
      match FileStat::filetype f with
      | #PIPE => ;
      | #STREAM => ;
      | #DIRECTORY =>
        match Directory::filesin f with
        | #None => ;
        | Some files =>
          for file in files do
            aux f file;
          done
          C_hack::ignore$ Directory::unlink_empty_dir f;
        endmatch;
      | #BLOCK => ;
      | #REGULAR => C_hack::ignore$ unlink_file f;
      | #SYMLINK => C_hack::ignore$ unlink_file f;
      | #SOCKET => ;
      | #INDETERMINATE => ;
      | #NONEXISTANT => ;
      | #NOPERMISSION => ;
      endmatch;
    }
    aux "" f;
  }

  proc rm (f:string) => unlink f;

  //$ Find a file in a list of directories.
  fun find_in_path(x:string, path:list[string]):opt[string]=>
    match path with
    | #Empty => None[string]
    | Cons (d,t) =>
      let p =  Filename::join(d,x) in
      match FileStat::fileexists p with
      | true => Some p
      | false => find_in_path (x,t)
      endmatch
    endmatch
  ;

  //$ Find all the files matching an RE2-regular expression
  //$ in a given directory.
  //$ NOTE: this search finds files in descendant directories too.
  //$ The search is recursive, but the whole pathname within
  //$ the specified directory must match the regexp.
  //$ For example to find all *.flx files in src use:
  //$   regfilesin("src", ".*[.]flx")
  //$ To find the files only in the given directory, on Unix use instead
  //$   regfilesin("src", "[^/]*[.]flx")
  //$ to exclude files in child directories.
  fun regfilesin(dname:string, re:string): list[string] => regfilesin(dname, Re2::RE2 re);

  //$ Find all the files matching a compiled RE2-regular expression.
  fun regfilesin(dname:string, re:RE2): list[string] = {
    //eprintln$ "regfilesin " + dname+ " with some kind of regexp .. ";

    var foundfiles = Empty[string];
    proc rfi(dname2: string) {

      //eprintln$ "rf() : dname2=" +dname2;

      if dname2 == "." or dname2 == ".." return;

      var newpath = if dname2 == "" then dname else Filename::join (dname,dname2);

      //eprintln$ "newpath = "+newpath ;

      var newfiles = Directory::filesin(newpath);

      //eprintln$ "returned from filesin" ;

      match newfiles with
      | #None => return;
      | Some files =>
        //eprintln$ "got files in " + newpath;
        for f in files do
          if f == "." or f == ".." do ;
          else
            //eprintln$ "Processing file " + f;
            var d = Filename::join (dname2,f);
            //eprintln$ "Relpath " + d;
            var fullpath = Filename::join (dname,d);
            //eprintln$ "fullpath " + fullpath;
            var t = FileStat::filetype fullpath;
            match t with
              | #REGULAR =>
                //eprintln ("Regular file " + d);
                var result = d in re;
                if result do
                      //eprintln$ d + " Matches";
                            foundfiles = Cons (d, foundfiles);
                done
              | #DIRECTORY =>
                //eprintln ("found directory " + d);
                rfi (d);
              | _ => ;
            endmatch;
          done
        done
      endmatch;
    }
    rfi ("");
    return rev foundfiles;
  }

}

Posix File Syetem

//[posix_filesystem.flx]

class PosixFileSystem
{
  //------------------------------------------------------------
  // File access and create modes
  //------------------------------------------------------------
  pod type file_perm_t = "int" requires Posix_headers::fcntl_h;
  const O_RDONLY     : file_perm_t;
  const O_WRONLY     : file_perm_t;
  const O_RDWR       : file_perm_t;
  const O_NONBLOCK   : file_perm_t;
  const O_APPEND     : file_perm_t;
  const O_CREAT      : file_perm_t;
  const O_TRUNC      : file_perm_t;
  const O_EXCL       : file_perm_t;
  const O_SHLOCK     : file_perm_t;
  const O_EXLOCK     : file_perm_t;
  const O_NOFOLLOW   : file_perm_t;
  const O_SYMLINK    : file_perm_t;
  const O_EVTONLY    : file_perm_t;
  fun \& : file_perm_t * file_perm_t -> file_perm_t = "$1&$2";
  fun \|  : file_perm_t * file_perm_t -> file_perm_t = "$1|$2";

  //------------------------------------------------------------
  // File I/O functions
  //------------------------------------------------------------
  pod type posix_file = "int" requires Posix_headers::unistd_h;
  fun valid: posix_file -> bool = "$1 != -1";
  ctor int : posix_file = "$1";
  const fd0 : posix_file = "0";
  const fd1 : posix_file = "1";
  const fd2 : posix_file = "2";

  gen open: string * file_perm_t * PosixFileStat::mode_t -> posix_file = "open($1.c_str(), $2, $3)";
  gen open: string * file_perm_t -> posix_file = "open($1.c_str(), $2)";

  gen ropen: string -> posix_file = 'open($1.c_str(), O_RDONLY,0)' requires Posix_headers::fcntl_h, Posix_headers::sys_stat_h;
  gen wopen: string -> posix_file = 'open($1.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)' requires Posix_headers::fcntl_h, Posix_headers::sys_stat_h;
  gen rwopen: string -> posix_file = 'open($1.c_str(), O_RDWR,0)' requires Posix_headers::fcntl_h, Posix_headers::sys_stat_h;
  gen creat: string * PosixFileStat::mode_t-> posix_file = 'open($1.c_str(), O_WRONLY | O_CREAT | O_TRUNC, $2)' requires Posix_headers::fcntl_h, Posix_headers::sys_stat_h;

  gen close: posix_file -> int = "close($1)";
  gen read: posix_file * &char * size -> size = "read($1, $2, $3)";
  gen write: posix_file * &char * size -> size = "write($1, $2, $3)";

  gen dup: posix_file -> posix_file = "dup($1)" requires Posix_headers::unistd_h;
  gen dup2: posix_file * posix_file -> posix_file = "dup2($1,$2)" requires Posix_headers::unistd_h;
  header piper_def = """
    struct _piper_hack { int i; int o; };
  """;
  body piper_def = """
    _piper_hack _piper() {
      _piper_hack p;
      pipe((int*)(void*)&p);
      return p;
    }
  """ requires Posix_headers::unistd_h;
  private cstruct _piper_hack { i:posix_file; o:posix_file; };
  private gen _piper: 1 -> _piper_hack requires piper_def;
  private fun _mkpair (x: _piper_hack) => x.i, x.o;
  gen pipe () => _mkpair #_piper;

  gen fdopen_input: posix_file ->  ifile = 'fdopen($1,"r")';
  gen fdopen_output: posix_file ->  ofile = 'fdopen($1,"w")';

  //------------------------------------------------------------
  // delete (unlink) a file
  //------------------------------------------------------------
  gen unlink_file: string -> int = "::unlink($1.c_str())"
    requires Posix_headers::unistd_h;

  //------------------------------------------------------------
  // rename a file
  //------------------------------------------------------------
  gen rename_file: string * string -> int = "::rename($1.c_str(),$2.c_str())"
    requires Posix_headers::unistd_h;

  //------------------------------------------------------------
  // copy a file, preserving last access and modification times
  // owner, group, and permissions
  //------------------------------------------------------------
  gen filecopy(src: string, dst: string) :  bool =
  {
    if Env::getenv ("FLX_REPORT_FILECOPY") != "" do
      eprintln$ "[PosixFileSystem::filecopy] '" + src + "' -> '" + dst+ "'";
    done
    val now = Time::time(); // seconds
    var stat_buf: PosixFileStat::stat_t;
    if not PosixFileStat::stat (src, &stat_buf) do
      eprintln$ "[PosixFileSystem::filecopy] Can't stat source file " + src;
      return false;
    done;
    val permissions = PosixFileStat::file_perm$ PosixFileStat::raw_mode (&stat_buf);
    val last_modification = PosixFileStat::filetime(src);
    var fsrc = open (src,O_RDONLY );
    if not valid fsrc do
      eprintln$ "[PosixFileSystem::filecopy] Bad src file in Filesystem::filecopy " + src;
      return false;
    done
    var fdst = open (dst,O_WRONLY \| O_CREAT \| O_TRUNC, permissions);
    if not valid fdst do
      eprintln$ "[PosixFileSystem::filecopy] Bad dst file in Filesystem::filecopy " + dst + ", Error: " + str errno + "=" + #strerror;
      return false;
    done
    bsiz := size (4096 * 1024); // 4 Meg
    var buffer = C_hack::cast[&char] (Memory::malloc(bsiz)); // 4 MEG
    var bread = read (fsrc, buffer, bsiz);
    while bread > size 0 do
      var bwrite = write (fdst,buffer,bread);
      if bread != bwrite do
        if bwrite.int == -1 do
          eprintln$
            "[PosixFileSystem::filecopy] Dest des = " + str fdst.int+ " "+
            "Attempt to copy " + str bread + " bytes from " + src + " to " + dst +
            " failed with errno = " + str errno + ": " + strerror()
          ;
        else
          eprintln$
            "[PosixFileSystem::filecopy] Attempt to copy " + str bread + " bytes from " + src + " to " + dst +
            " failed with " +  str bwrite + " only copied!"
          ;
        done
      done
      bread = read (fsrc, buffer, bsiz);
    done
    var res = close fsrc;
    if res != 0 do
      eprintln$ "[PosixFileSystem::filecopy] close on src " + src + " failed: " + str errno + "=" + #strerror;
    done
    res = close fdst;
    if res != 0 do
      eprintln$ "[PosixFileSystem::filecopy] close on dst " + dst + " failed: " + str errno + "=" + #strerror;
    done
    C_hack::ignore(PosixFileStat::utime(dst,now,last_modification));
    Memory::free(C_hack::cast[address] buffer);
    return true;
  }

  //------------------------------------------------------------
  // generate temporary file name
  //------------------------------------------------------------
  body tmpnam = """
    std::string flx_tmpnam() {
      char tmpn[] = "/tmp/flx_XXXXXX";
      close(mkstemp(tmpn));
      return std::string(tmpn);
     }
  """ requires header '#include <unistd.h>';

  gen tmp_filename: 1 -> string = "flx_tmpnam()" requires tmpnam;

}

Win32 File Syetem

//[win32_filesystem.flx]

class Win32FileSystem
{
  //------------------------------------------------------------
  // File access and create modes
  //------------------------------------------------------------
  pod type file_perm_t = "int" requires Posix_headers::fcntl_h;
  const _O_BINARY     : file_perm_t;
  const _O_RDONLY     : file_perm_t;
  const _O_WRONLY     : file_perm_t;
  const _O_RDWR       : file_perm_t;
  const _O_NONBLOCK   : file_perm_t;
  const _O_APPEND     : file_perm_t;
  const _O_CREAT      : file_perm_t;
  const _O_TRUNC      : file_perm_t;
  const _O_EXCL       : file_perm_t;
  const _O_SHLOCK     : file_perm_t;
  const _O_EXLOCK     : file_perm_t;
  const _O_NOFOLLOW   : file_perm_t;
  const _O_SYMLINK    : file_perm_t;
  const _O_EVTONLY    : file_perm_t;
  fun \& : file_perm_t * file_perm_t -> file_perm_t = "$1&$2";
  fun \|  : file_perm_t * file_perm_t -> file_perm_t = "$1|$2";

  //------------------------------------------------------------
  // File I/O functions
  //------------------------------------------------------------
  pod type posix_file = "int" requires Win32_headers::io_h;
  fun valid: posix_file -> bool = "$1 != -1";
  ctor int : posix_file = "$1";
  const fd0 : posix_file = "0";
  const fd1 : posix_file = "1";
  const fd2 : posix_file = "2";

  gen open: string * file_perm_t * Win32FileStat::mode_t -> posix_file = "_open($1.c_str(), $2, $3)";
  gen open: string * file_perm_t -> posix_file = "_open($1.c_str(), $2)";

  gen ropen: string -> posix_file = 'open($1.c_str(), _O_RDONLY | _O_BINARY,0)' requires Posix_headers::fcntl_h, Posix_headers::sys_stat_h;
  gen wopen: string -> posix_file = 'open($1.c_str(), _O_WRONLY  | _O_BINARY | _O_CREAT | _O_TRUNC, S_IRUSR | S_IWUSR)' requires Win32_headers::io_h, Posix_headers::sys_stat_h;
  gen rwopen: string -> posix_file = 'open($1.c_str(), _O_RDWR | _O_BINARY,0)' requires Win32_headers::io_h, Posix_headers::sys_stat_h;
  gen creat: string * Win32FileStat::mode_t-> posix_file = 'open($1.c_str(), _O_WRONLY | _O_BINARY | _O_CREAT | _O_TRUNC, $2)' requires Win32_headers::io_h, Posix_headers::sys_stat_h;

  gen close: posix_file -> int = "_close($1)";
  gen read: posix_file * &char * size -> size = "read($1, $2, $3)";
  gen write: posix_file * &char * size -> size = "write($1, $2, $3)";

  gen dup: posix_file -> posix_file = "dup($1)" requires Win32_headers::io_h;
  gen dup2: posix_file * posix_file -> posix_file = "dup2($1,$2)" requires Win32_headers::io_h;
  header piper_def = """
    struct _piper_hack { int i; int o; };
  """;
  body piper_def = """
    _piper_hack _piper() {
      _piper_hack p;
      pipe((int*)(void*)&p);
      return p;
    }
  """ requires Posix_headers::unistd_h;
  private cstruct _piper_hack { i:posix_file; o:posix_file; };
  private gen _piper: 1 -> _piper_hack requires piper_def;
  private fun _mkpair (x: _piper_hack) => x.i, x.o;
  gen pipe () => _mkpair #_piper;

  gen fdopen_input: posix_file ->  ifile = 'fdopen($1,"r")';
  gen fdopen_output: posix_file ->  ofile = 'fdopen($1,"w")';

  //------------------------------------------------------------
  // delete (unlink) a file
  //------------------------------------------------------------
  gen unlink_file: string -> int = "unlink($1.c_str())";

  //------------------------------------------------------------
  // rename a file
  //------------------------------------------------------------
  gen rename_file: string * string -> int = "rename($1.c_str(),$2.c_str())";

  //------------------------------------------------------------
  // copy a file, preserving last access and modification times
  // owner, group, and permissions
  //------------------------------------------------------------
  gen filecopy(src: string, dst: string) :  bool =
  {
    //eprintln$ "Copy " + src + " -> " + dst;
    if Env::getenv ("FLX_REPORT_FILECOPY") != "" do
      eprintln$ "[Win32FileSystem::filecopy] '" + src + "' -> '" + dst+ "'";
    done

    val now = Time::time(); // seconds
    var stat_buf: Win32FileStat::stat_t;
    if not Win32FileStat::stat (src, &stat_buf) do
      eprintln$ "Can't stat source file " + src;
      return false;
    done;
    val permissions = Win32FileStat::file_perm$ Win32FileStat::raw_mode (&stat_buf);
    val last_modification = Win32FileStat::filetime(src);
    var fsrc = open (src,_O_RDONLY \| _O_BINARY);
    if not valid fsrc do
      eprintln$ " Bad src file in Filesystem::filecopy " + src;
      return false;
    done
    var fdst = open (dst,_O_WRONLY \| _O_BINARY \| _O_CREAT \| _O_TRUNC, permissions);
    if not valid fdst do
      eprintln$ " Bad dst file in Filesystem::filecopy " + dst + ", Error: " + str errno + "=" + #strerror;
      return false;
    done
    bsiz := size (4096 * 1024); // 4 Meg
    var buffer = C_hack::cast[&char] (Memory::malloc(bsiz)); // 4 MEG
    var bread = read (fsrc, buffer, bsiz);
    while bread > size 0 do
      var bwrite = write (fdst,buffer,bread);
      if bread != bwrite do
        if bwrite.int == -1 do
          eprintln$
            "Dest des = " + str fdst.int+ " "+
            "Attempt to copy " + str bread + " bytes from " + src + " to " + dst +
            " failed with errno = " + str errno + ": " + strerror()
          ;
        else
          eprintln$
            "Attempt to copy " + str bread + " bytes from " + src + " to " + dst +
            " failed with " +  str bwrite + " only copied!"
          ;
        done
      done
      bread = read (fsrc, buffer, bsiz);
    done
    var res = close fsrc;
    if res != 0 do
      eprintln$ "In filesystem::filecopy close on src " + src + " failed: " + str errno + "=" + #strerror;
    done
    res = close fdst;
    if res != 0 do
      eprintln$ "In filesystem::filecopy close on dst " + dst + " failed: " + str errno + "=" + #strerror;
    done
    C_hack::ignore(Win32FileStat::utime(dst,now,last_modification));
    Memory::free(C_hack::cast[address] buffer);
    return true;
  }


  //------------------------------------------------------------
  // generate temporary file name
  //------------------------------------------------------------
  body tmpnam = """
    std::string flx_tmpnam() {
      char tmpn[] = "/tmp/flx_XXXXXX";
      close(mkstemp(tmpn));
      return std::string(tmpn);
     }
  """ requires header '#include <unistd.h>';

  gen tmp_filename: 1 -> string = "flx_tmpnam()" requires tmpnam;

}

Directory

//[directory.flx]

//$ File system directory services,
//$ Parametrised  by operating system and mode type.
class Directory_class[os,mode_t]
{
  //$ Create a directory with specified mode.
  //$ Returns 0 if successful.
  virtual gen mkdir: string * mode_t -> int;

  //$ Create a directory with default mode.
  //$ Returns 0 if successful.
  virtual gen mkdir: string -> int;

  //$ Try to ensure all the directories in a path exist.
  //$ Does not return any error indication.
  virtual proc mkdirs: string;

  virtual gen unlink_empty_dir: string -> int;

  //$ Return an option list of all the regular files in a given directory.
  //$ Returns None if the directory does not exist or isn't accessible.
  //$ Returns Some files if the directory exists and is accessible.
  //$ If the directory has no regular files, the list is Empty.
  virtual fun filesin:string -> opt[List::list[string]];

  //$ Get the absolute pathname of the current working directory.
  virtual fun getcwd: 1 -> string;

  //$ Convert a relative filename to an absolute pathname.
  virtual fun mk_absolute_filename: string -> string;
}

//$ Host file system directory services.
//$ Platform dependent.
class Directory {
if PLAT_WIN32 do
  inherit Win32Directory;
else
  inherit PosixDirectory;
done
}

Posix Directory Services

//[posix_directory.flx]

class PosixDirectory
{
  // Posix specific stuff.
  type dirent_t = "struct dirent*" requires Posix_headers::dirent_h;
  type DIR_t = "DIR*" requires Posix_headers::dirent_h;
  proc opendir: string * &DIR_t = "*$2=opendir($1.c_str());";
  fun isNULL: DIR_t -> bool = "$1==0";
  fun isNULL: dirent_t -> bool = "$1==0";
  proc readdir: DIR_t * dirent_t * &dirent_t * &int = "*$4=readdir_r($1, $2, $3);";
  proc closedir: DIR_t = "closedir($1);";
  fun filename: dirent_t -> string = "std::string($1->d_name)";
  private fun getcwd: +char * size -> +char = "getcwd($1,$2)" requires Posix_headers::unistd_h;

  // inherit generic stuff
  inherit Directory_class[Posix, PosixFileStat::mode_t];

  // instantiate generic stuff
  instance Directory_class[Posix, PosixFileStat::mode_t] {
    gen mkdir: string * PosixFileStat::mode_t -> int = "mkdir($1.c_str(), $2)" requires Posix_headers::sys_stat_h;
    gen mkdir: string  -> int = "mkdir($1.c_str(), 0777)" requires Posix_headers::sys_stat_h;
    proc mkdirs (s:string)
    {
      if s == "" or s == "." or s == ".." or s == "/" do
         return;
      done
      mkdirs$ Filename::dirname s;
      C_hack::ignore$ mkdir s;
    }

    // Delete an empty directory.
    gen unlink_empty_dir : string -> int = "rmdir ($1.c_str())" requires Posix_headers::unistd_h;


    fun getcwd():string = {
      var b: array[char,1024];
      var p = getcwd((&b).stl_begin,size 1024);
      return if C_hack::isNULL p then "" else string p endif;
    }
    fun mk_absolute_filename(s:string) =>
       if PosixFilename::is_absolute_filename s then s else
       #getcwd + "/" + s
    ;
    fun filesin(dname:string): opt[List::list[string]] = {
      //println$ "filesin " + dname;
      var d:DIR_t;
      var e: dirent_t = C_hack::cast[dirent_t]$ Memory::malloc 5000;
      var eret = e;
      var err:int = 0;
      var files = List::Empty[string];
      opendir(dname,&d);
      if isNULL d do
        println "Error opening dir";
        Memory::free$ C_hack::cast[address] e;
        return None[List::list[string]];
      else
      //println$ "Opened dir " + dname;
  next:>
        readdir(d,e,&eret, &err);
        if err != 0 do
          println "Error reading dir"; fflush;
          closedir d;
          Memory::free$ C_hack::cast[address] e;
          return None[List::list[string]];
        elif isNULL eret do
          //println "End of dir";
          closedir d;
          Memory::free$ C_hack::cast[address] e;
          return Some files;
        else
          //println "Think we got a file?";
          assert err == 0;
          //println$ "Found a file " + filename e;
          files += filename e;
          goto next;
        done
      done
    }
  }
}

Win32 Directory Services

//[win32_directory.flx]

class Win32Directory
{
  //Win32 specific stuff.

  type DIR_t = "intptr_t" requires Win32_headers::io_h ;
  type FINDDATA_t = "struct _finddata_t" requires Win32_headers::io_h ;

  proc findfirst: string * &FINDDATA_t * &DIR_t = "*$3=_findfirst($1.c_str(), $2);" ;
  proc findnext: DIR_t * &FINDDATA_t * &int = "*$3=_findnext($1, $2);" ;
  proc findclose : DIR_t = "_findclose($1);" ;

  fun findfailed : DIR_t -> bool = "int($1) == -1" ;
  fun filename : FINDDATA_t -> string = "std::string($1.name)" ;

  private fun getcwd: +char * size -> +char = "_getcwd($1,(int)$2)" requires Win32_headers::direct_h;

  // Generic stuff.

  inherit Directory_class[Win32, Win32FileStat::mode_t];

  // Instantiate generics.

  instance Directory_class[Win32, Win32FileStat::mode_t]
  {
    //Make a directory.

    // warning: ignores the mode!
    gen mkdir: string * Win32FileStat::mode_t -> int = "_mkdir($1.c_str())" requires Win32_headers::direct_h;
    gen mkdir: string  -> int = "_mkdir($1.c_str())" requires Win32_headers::direct_h;
    proc mkdirs (s:string)
    {
      if s == "" or s == "." or s == ".." or s.[-1] == char "\\" do
         return;
      done
      mkdirs$ Win32Filename::dirname s;
      C_hack::ignore$ mkdir s;
    }

    gen unlink_empty_dir: string->int=  "(int)RemoveDirectory($1.c_str())" requires Win32_headers::windows_h;


    //Get the current working directory.

    fun getcwd():string =
    {
      var b: array[char,1024];
      var p = getcwd((&b).stl_begin,size 1024);
      return if C_hack::isNULL p then "" else string p endif;
    }

    //Is the given path absolute?

    // this is wrong, because D:filename will have the
    // current directory prepended instead of the
    // current directory for drive D, so it could end up
    // referring to drive C instead ..
    // also none of this works with network names
    fun mk_absolute_filename(s:string) =>
       if Win32Filename::is_absolute_filename s then s else
       #getcwd + "\\" + s
    ;

    //List the files in a directory.

    fun filesin(dname:string): opt[list[string]] =
    {
      //eprintln$ "hi in filesin dname=\""+dname+"\"" ;

      var d : DIR_t ;
      var fileinfo : FINDDATA_t ;
      var files = Empty[string];

      //eprintln$ "calling findfirst with expression = " + dname+"*";
      findfirst (dname+"\\*", &fileinfo, &d) ;
      //eprintln$ "returned from findfirst" ;

      if findfailed d  do
        if errno == ENOENT or errno == EINVAL do
          //eprintln$ "findfirst() failed with ENOENT or EINVAL" ;
          return None[list[string]] ;
        done
        eprintln$ "findfirst() failed unexpectedly" ;
        assert false ;
      done

      var stat : int ;

    harvestnext:>

      var f : string  = filename fileinfo ;
      if f != ".." and f != "." do
        //println$ "Adding file" + (filename fileinfo) ;
        files += filename fileinfo ;
      done

      findnext(d, &fileinfo, &stat) ;
      if stat == 0 goto harvestnext ;

      if stat == -1 do
        if errno == ENOENT goto harvestexit ;
        assert false ;
      else
        println "Error reading dir"; fflush;
        findclose d ;
        return None[list[string]] ;
      done

    harvestexit:>

      //eprintln$ "Leaving normally with some files" ;

      findclose d ;
      return Some files ;
    }
  }
}