
(********************************************************************)
(*                                                                  *)
(*  osfiles.s7i   Functions to handle operating system files        *)
(*  Copyright (C) 1989 - 2011  Thomas Mertes                        *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "enable_io.s7i";
include "time.s7i";
include "bigint.s7i";


const proc: GET_ATIME (in string param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "CMD_GET_ATIME";

const proc: GET_CTIME (in string param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "CMD_GET_CTIME";

const proc: GET_MTIME (in string param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout integer param, inout integer param,
    inout integer param, inout integer param, inout boolean param) is action "CMD_GET_MTIME";

const proc: SET_ATIME (in string param, in integer param, in integer param,
    in integer param, in integer param, in integer param, in integer param,
    in integer param, in integer param) is action "CMD_SET_ATIME";

const proc: SET_MTIME (in string param, in integer param, in integer param,
    in integer param, in integer param, in integer param, in integer param,
    in integer param, in integer param) is action "CMD_SET_MTIME";


const type: fileType is integer;

const fileType: FILE_ABSENT  is 0; # A component of path does not exist
const fileType: FILE_UNKNOWN is 1; # File exists but has an unknown type
const fileType: FILE_REGULAR is 2;
const fileType: FILE_DIR     is 3;
const fileType: FILE_CHAR    is 4;
const fileType: FILE_BLOCK   is 5;
const fileType: FILE_FIFO    is 6;
const fileType: FILE_SYMLINK is 7;
const fileType: FILE_SOCKET  is 8;

const type: filePermission is new enum
    EXEC_OTHER,
    WRITE_OTHER,
    READ_OTHER,
    EXEC_GROUP,
    WRITE_GROUP,
    READ_GROUP,
    EXEC_USER,
    WRITE_USER,
    READ_USER
  end enum;

const type: fileMode is set of filePermission;

const func integer: (attr integer) conv (in fileMode: mode) is
  return integer conv (bitset conv mode);

const func fileMode: (attr fileMode) conv (in integer: int_mode) is
  return fileMode conv (bitset conv int_mode);

const func string: str (in fileMode: aFileMode) is func
  result
    var string: result is "rwxrwxrwx";
  local
    var filePermission: aPermission is READ_USER;
  begin
    for aPermission range READ_USER downto EXEC_OTHER do
      if aPermission not in aFileMode then
        result @:= [9 - ord(aPermission)] '-';
      end if;
    end for;
  end func;

enable_output(fileMode);


(**
 *  Determine the filenames in a directory.
 *  The files "." and ".." are left out from the result.
 *  Note that the function returns only the filenames.
 *  Additional information must be obtained with other calls. 
 *  @return a string-array containing the filenames in the directory.
 *  @exception MEMORY_ERROR Not enough memory to convert 'dirPath'
 *             to the system path type or not enough memory to
 *             represent the result 'string array'.
 *  @exception RANGE_ERROR 'dirPath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const func array string: readDir (in string: dirPath)        is action "CMD_LS";


const func array string: read_dir (in string: dirPath)       is action "CMD_LS";


(**
 *  Determine the current working directory of the calling process.
 *  @return The absolute path of the current working directory.
 *  @exception MEMORY_ERROR Not enough memory to represent the
 *             result 'string'.
 *  @exception FILE_ERROR The system function returns an error.
 *)
const func string: getcwd                                    is action "CMD_GETCWD";


(**
 *  Changes the current working directory of the calling process.
 *  @exception MEMORY_ERROR Not enough memory to convert 'name' to
 *             the system path type.
 *  @exception RANGE_ERROR 'name' is not representable in the system
 *             path type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: chdir (in string: name)                          is action "CMD_CHDIR";


(**
 *  Creates a new directory.
 *  @exception MEMORY_ERROR Not enough memory to convert 'name' to
 *             the system path type.
 *  @exception RANGE_ERROR 'name' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: mkdir (in string: name)                          is action "CMD_MKDIR";


(**
 *  Determine the type of a file.
 *  The function does follow symbolic links.
 *  Therefore it never returns FILE_SYMLINK.
 *  @return the type of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR The system function returns an error other
 *             than ENOENT or ENOTDIR. 
 *)
const func fileType: fileType (in string: filePath)          is action "CMD_FILETYPE";


(**
 *  Determine the type of a file.
 *  The function does not follow symbolic links.
 *  Therefore it may return FILE_SYMLINK.
 *  @return the type of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR The system function returns an error other
 *             than ENOENT or ENOTDIR. 
 *)
const func fileType: fileTypeSL (in string: filePath)        is action "CMD_FILETYPE_SL";


(**
 *  Determine the file mode (permissions) of a file.
 *  @return the file mode.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const func fileMode: fileMode (in string: filePath)          is action "CMD_FILEMODE";


(**
 *  Change the file mode (permissions) of a file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: setFileMode (in string: filePath, in fileMode: mode) is action "CMD_SET_FILEMODE";


(**
 *  Determine the size of a file.
 *  The file size is measured in bytes.
 *  For directories a size of 0 is returned.
 *  @return the size of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception RANGE_ERROR The file size is not representable as integer.
 *  @exception FILE_ERROR It was not possible to determine the file size.
 *)
const func integer: fileSize (in string: filePath)           is action "CMD_FILESIZE";


(**
 *  Determine the size of a file.
 *  The file size is measured in bytes.
 *  For directories a size of 0 is returned.
 *  @return the size of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR It was not possible to determine the file size.
 *)
const func bigInteger: bigFileSize (in string: filePath)     is action "CMD_BIG_FILESIZE";


(**
 *  Determine the access time of a file.
 *  @return the access time of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error. 
 *)
const func time: getATime (in string: filePath) is func
  result
    var time: accessTime is time.value;
  begin
    GET_ATIME(filePath, accessTime.year, accessTime.month, accessTime.day,
        accessTime.hour, accessTime.minute, accessTime.second,
        accessTime.micro_second, accessTime.timeZone,
        accessTime.daylightSavingTime);
  end func;


(**
 *  Determine the change time of a file.
 *  @return the change time of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error. 
 *)
const func time: getCTime (in string: filePath) is func
  result
    var time: changeTime is time.value;
  begin
    GET_CTIME(filePath, changeTime.year, changeTime.month, changeTime.day,
        changeTime.hour, changeTime.minute, changeTime.second,
        changeTime.micro_second, changeTime.timeZone,
        changeTime.daylightSavingTime);
  end func;


(**
 *  Determine the modification time of a file.
 *  @return the modification time of the file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR A system function returns an error. 
 *)
const func time: getMTime (in string: filePath) is func
  result
    var time: modificationTime is time.value;
  begin
    GET_MTIME(filePath, modificationTime.year, modificationTime.month, modificationTime.day,
        modificationTime.hour, modificationTime.minute, modificationTime.second,
        modificationTime.micro_second, modificationTime.timeZone,
        modificationTime.daylightSavingTime);
  end func;


(**
 *  Set the access time of a file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type or 'aTime' is invalid or cannot be
 *             converted to the system file time.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: setATime (in string: filePath, in time: aTime) is func
  begin
    SET_ATIME(filePath, aTime.year, aTime.month, aTime.day,
        aTime.hour, aTime.minute, aTime.second,
        aTime.micro_second, aTime.timeZone);
  end func;


(**
 *  Set the modification time of a file.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type or 'aTime' is invalid or cannot be
 *             converted to the system file time.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: setMTime (in string: filePath, in time: aTime) is func
  begin
    SET_MTIME(filePath, aTime.year, aTime.month, aTime.day,
        aTime.hour, aTime.minute, aTime.second,
        aTime.micro_second, aTime.timeZone);
  end func;


(**
 *  Reads the destination of a symbolic link.
 *  @return The destination refered by the symbolic link.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath'
 *             to the system path type or not enough memory to
 *             represent the result string.
 *  @exception RANGE_ERROR 'filePath' is not representable in the
 *             system path type.
 *  @exception FILE_ERROR The file described with the path does not
 *             exist or is not a symbolic link.
 *)
const func string: readlink (in string: filePath)            is action "CMD_READLINK";


(**
 *  Create a symbolic link.
 *  The symbolic link 'dest' will refer to 'source' afterwards.
 *  @param source String to be contained in the symbolic link.
 *  @param dest Name of the symbolic link to be created.
 *  @exception MEMORY_ERROR Not enough memory to convert source or
 *             dest to the system path type.
 *  @exception RANGE_ERROR Source or dest is not representable in
 *             the system path type.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: symlink (in string: source, in string: dest)     is action "CMD_SYMLINK";


(**
 *  Remove a file or empty directory.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath' to
 *             the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the system
 *             path type.
 *  @exception FILE_ERROR The file does not exist or a system function
 *             returns an error.
 *)
const proc: removeFile (in string: filePath)                 is action "CMD_REMOVE";


(**
 *  Removes a file independent of its file type.
 *  @exception MEMORY_ERROR Not enough memory to convert 'filePath' to
 *             the system path type.
 *  @exception RANGE_ERROR 'filePath' is not representable in the system
 *             path type.
 *  @exception FILE_ERROR The file does not exist or a system function
 *             returns an error.
 *)
const proc: removeAnyFile (in string: filePath)              is action "CMD_REMOVE_ANY_FILE";


(**
 *  Copies a file or directory tree.
 *  Permissions/mode, ownership and timestamps of the destination file
 *  are determined independent of the corresponding source properties.
 *  The destination file gets the permissions/mode defined by umask.
 *  The user executing the program is the owner of the destination file.
 *  The timestamps of the destination file are set to the current time.
 *  Symbolic links in sourcePath are always followed.
 *  Therefore 'copyFile' will never create a symbolic link.
 *  Note that 'copyFile' does not preserve hard links (they are
 *  resolved to distinct files).
 *  @exception MEMORY_ERROR Not enough memory to convert 'sourcePath'
 *             or 'destPath' to the system path type.
 *  @exception RANGE_ERROR 'sourcePath' or 'destPath' is not
 *             representable in the system path type.
 *  @exception FILE_ERROR Source file does not exist, destination file
 *             already exists or a system function returns an error.
 *)
const proc: copyFile (in string: sourcePath, in string: destPath)  is action "CMD_COPY_FILE";


(**
 *  Clone a file or directory tree.
 *  Permissions/mode, ownership and timestamps of the original are
 *  preserved. Symlinks are not followed. Instead the symlink is
 *  copied. Note that 'cloneFile' does not preserve hard links (they
 *  are resolved to distinct files).
 *  @exception MEMORY_ERROR Not enough memory to convert
 *             'sourcePath' or 'destPath' to the system path type.
 *  @exception RANGE_ERROR 'sourcePath' or 'destPath' is not
 *             representable in the system path type.
 *  @exception FILE_ERROR Source file does not exist, destination file
 *             already exists or a system function returns an error.
 *)
const proc: cloneFile (in string: sourcePath, in string: destPath) is action "CMD_CLONE_FILE";


(**
 *  Move and rename a file or directory tree.
 *  The function uses the C 'rename()' function. When 'rename()' fails
 *  the file (or directory tree) is cloned with 'cloneFile' (which
 *  preserves permissions/mode, ownership and timestamps) to the new
 *  place and with the new name. When 'cloneFile' succeeds the original
 *  file is deleted. When 'cloneFile' fails (no space on device or
 *  other reason) all remains of the failed clone are removed. Note
 *  that 'cloneFile' works for symbolic links but does not preserve
 *  hard links (they are resolved to distinct files).
 *  @exception MEMORY_ERROR Not enough memory to convert 'sourcePath'
 *             or 'destPath' to the system path type.
 *  @exception RANGE_ERROR 'sourcePath' or 'destPath' is not
 *             representable in the system path type.
 *  @exception FILE_ERROR Source file does not exist, destination file
 *             already exists or a system function returns an error.
 *)
const proc: moveFile (in string: sourcePath, in string: destPath)  is action "CMD_MOVE";


const func string: configValue (in string param)             is action "CMD_CONFIG_VALUE";


const func string: convDosPath (in string: path) is func
  result
    var string: stdPath is "";
  local
    var integer: length is 0;
  begin
    # Path delimiter is slash
    stdPath := replace(path, "\\", "/");
    # Convert drive letters to standard path
    if (length(stdPath) = 2 or (length(stdPath) >= 3 and stdPath[3] = '/')) and
        stdPath[2] = ':' and lower(stdPath[1]) >= 'a' and lower(stdPath[1]) <= 'z' then
      stdPath @:= [2] lower(stdPath[1]);
      stdPath @:= [1] '/';
    end if;
    # Remove double slashes
    repeat
      length := length(stdPath);
      stdPath := replace(stdPath, "//", "/");
    until length(stdPath) = length;
    # Remove slash at end of path
    while stdPath <> "/" and endsWith(stdPath, "/") do
      stdPath := stdPath[.. pred(length(stdPath))];
    end while;
  end func;


const func string: toStdPath (in string: path) is func
  result
    var string: stdPath is "";
  local
    var integer: length is 0;
  begin
    stdPath := path;
    # Remove double slashes
    repeat
      length := length(stdPath);
      stdPath := replace(stdPath, "//", "/");
    until length(stdPath) = length;
    # Remove slash at end of path
    while stdPath <> "/" and endsWith(stdPath, "/") do
      stdPath := stdPath[.. pred(length(stdPath))];
    end while;
  end func;
