/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    ql_file.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/ql_file.h"
#include "qore/intern/QoreDir.h"

#include <cerrno>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef _Q_WINDOWS
#include <direct.h>
#include <io.h>
#else
#include <climits>
#include <cstdlib>
#include <sys/param.h>
#endif

#ifdef HAVE_GLOB_H
#include <glob.h>
#else
#include "qore/intern/qore_glob.h"
#endif

static bool check_stat(unsigned code, const QoreString& path) {
   return (q_get_mode(path) & S_IFMT) == code;
}

#ifdef HAVE_LSTAT
static bool check_lstat(unsigned code, const QoreString& path) {
   struct stat sbuf;
   int rc;

   if ((rc = lstat(path.getBuffer(), &sbuf)))
      return false;

   return (sbuf.st_mode & S_IFMT) == code;
}
#endif

static bool is_writable_intern(const QoreStringNode* path) {
#if defined HAVE_PWD_H
   struct stat sbuf;
   int rc;

   if ((rc = stat(path->getBuffer(), &sbuf)))
      return false;

   if (S_ISDIR(sbuf.st_mode)) { // If path is a directory.
      QoreStringMaker tfn("%s/xxx_qore_tmp_test_xxx", path->getBuffer());
      rc = open(tfn.getBuffer(), O_WRONLY | O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
      if (rc != -1) {
         close(rc);
         unlink(tfn.getBuffer());
         return true;
      }
      return false;
   }

   rc = open(path->getBuffer(), O_WRONLY);
   if (rc != -1) {
      close(rc);
      return true;
   }
   return false;

#elif defined(HAVE_ACCESS) && defined(_Q_WINDOWS)
   // only use access(2) on Windows
   return access(path->getBuffer(), W_OK) == 0;
#elif defined _Q_WINDOWS
   int ftype = q_get_mode(*path) & S_IFMT;
   if (ftype & S_ISDIR) {
      QoreStringMaker tfn("%s\\xxx_qore_tmp_test_xxx", path->getBuffer());
      HANDLE h = CreateFile(tfn.getBuffer(), GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_DELETE_ON_CLOSE, 0);
      if (h == INVALID_HANDLE_VALUE)
         return false;
      CloseHandle(h);
      return true;
   }
   else {
      // try to open the file with write permissions
      int rc = open(path->getBuffer(), O_WRONLY);
      if (rc == -1)
         return false;
      close(rc);
      return true;
   }
#error no is_writable() implementation for this platform
#endif
}

//! file status information hash as returned from @ref Qore::hstat() "hstat()", @ref Qore::hlstat() "hlstat()", @ref Qore::Dir::hstat() "Dir::hstat()", @ref Qore::FileLineIterator::hstat() "FileLineIterator::hstat()", @ref Qore::ReadOnlyFile::hstat() "ReadOnlyFile::hstat()", and @ref Qore::ReadOnlyFile::hlstat() "ReadOnlyFile::hlstat()"
/** @since %Qore 0.8.13
*/
hashdecl StatInfo {
    //! device inode number the file is on
    int dev;
    //! inode of the file
    int inode;
    //! inode protection mode
    int mode;
    //! number of hard links to this file
    int nlink;
    //! user ID of the owner
    int uid;
    //! group ID of the owner
    int gid;
    //! device type number
    int rdev;
    //! file size in bytes
    int size;
    //! last access time of the file
    date atime;
    //! last modified time of the file
    date mtime;
    //! last change time of the file's inode
    date ctime;
    //! block size; may be zero if the platform's internal %stat() (2) function does not provide this info
    int blksize;
    //! blocks allocated for the file; may be zero if the platform's internal %stat() (2) function does not provide this info
    int blocks;
    //! a string giving the file type; one of:\n - \c "BLOCK-DEVICE" \n - \c "DIRECTORY" \n - \c "CHARACTER-DEVICE" \n - \c "FIFO" \n - \c "SYMBOLIC-LINK" \n - \c "SOCKET" \n - \c "REGULAR" \n - \c "UNKNOWN"
    string type;
    //! a string giving UNIX-style permissions for the file (ex: \c "-rwxr-xr-x")
    string perm;
}

//! Filesystem info hash as returned by @ref Qore::statvfs() "statvfs", @ref Qore::Dir::statvfs() "Dir::statvfs", and @ref Qore::ReadOnlyFile::statvfs() "ReadOnlyFile::statvfs()""
/** @since %Qore 0.8.13
*/
hashdecl FilesystemInfo {
    //! The maximum length in bytes of file names on the filesystem
    int namemax;
    //! The filesystem ID; may not be set or meaningful on all filesystems/systems: see system documentation for statvfs()
    int fsid;
    //! The size in bytes of the minimum allocation unit on the filesystem
    int frsize;
    //! The filesystem's block size
    int bsize
    //! Flags describing mount options for the filesystem
    int flag;
    //! The total number of inodes on the filesystem
    int files;
    //! The number of free inodes available to unprivileged users
    int favail;
    //! The total number of free inodes available to privileged users
    int ffree;
    //! The total number of blocks on the filesystem (capacity in bytes = \c bsize * \c blocks)
    int blocks;
    //! The number of free blocks available to unprivileged users (bytes = \c bsize * \c bavail)
    int bavail;
    //! The total number of free indes available to privileged users (bytes = \c bsize * \c bfree)
    int bfree;
}

/** @defgroup filesystem_functions Filesystem Functions
    The following functions return information about or are related to the filesystem.

    All of the functions in this section (except umask()) are flagged with Qore::PO_NO_FILESYSTEM

    @see the @ref Qore::File "File" class for a class enabling files to be created, read or written, and the @ref Qore::Dir "Dir" class allowing directories to be manipulated

    @section stat_list Stat List
    |!Element|!Data Type|!Description
    |\c 0|@ref int_type|device inode number the file is on
    |\c 1|@ref int_type|inode of the file
    |\c 2|@ref int_type|inode protection mode
    |\c 3|@ref int_type|number of hard links to this file
    |\c 4|@ref int_type|user ID of the owner
    |\c 5|@ref int_type|group ID of the owner
    |\c 6|@ref int_type|device type number
    |\c 7|@ref int_type|file size in bytes
    |\c 8|@ref date_type|last access time of the file
    |\c 9|@ref date_type|last modified time of the file
    |\c 10|@ref date_type|last change time of the file's inode
    |\c 11|@ref int_type|block size; may be zero if the platform's internal %stat() (2) function does not provide this info
    |\c 12|@ref int_type|blocks allocated for the file; may be zero if the platform's internal %stat() (2) function does not provide this info
*/
///@{
//! Returns @ref True if the string passed identifies a regular file on the filesystem, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a regular file on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_file("/etc/hosts");
    @endcode
 */
bool is_file(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return check_stat(S_IFREG, *path);
}

//! Returns @ref True if the string passed identifies a directory on the filesystem, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a directory on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_dir("/tmp/mydir");
    @endcode
 */
bool is_dir(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return check_stat(S_IFDIR, *path);
}

//! Returns @ref True if the string passed identifies a socket on the filesystem, @ref False if not
/** @par Platform Availability
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @param path the path to check

    @return @ref True if the string passed identifies a socket on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_socket("/tmp/X0");
    @endcode
 */
bool is_socket(string path) [flags=CONSTANT;dom=FILESYSTEM] {
#ifdef S_IFSOCK
   return check_stat(S_IFSOCK, *path);
#else
   missing_function_error("is_socket", "UNIX_FILEMGT", xsink);
   return false;
#endif
}

//! Returns @ref True if the string passed identifies a pipe (FIFO) on the filesystem, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a pipe (FIFO) on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_pipe("/tmp/mypipe");
    @endcode
 */
bool is_pipe(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return check_stat(S_IFIFO, *path);
}

//! Returns @ref True if the string passed identifies a character device on the filesystem, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a character device on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_cdev("/tmp/tty");
    @endcode
 */
bool is_cdev(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return check_stat(S_IFCHR, *path);
}

//! Returns @ref True if the string passed identifies a block device on the filesystem, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a block device on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_bdev("/tmp/sda1");
    @endcode
 */
bool is_bdev(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return check_stat(S_IFBLK, *path);
}

//! Returns @ref True if the string passed identifies a device (either block or character) on the filesystem, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a device (either block or character) on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_dev("/tmp/scanner");
    @endcode
 */
bool is_dev(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   struct stat sbuf;
   int rc;

#ifdef HAVE_LSTAT
   if ((rc = lstat(path->getBuffer(), &sbuf)))
#else
   if ((rc = stat(path->getBuffer(), &sbuf)))
#endif
      return false;

   return ((sbuf.st_mode & S_IFMT) == S_IFCHR) || ((sbuf.st_mode & S_IFMT) == S_IFBLK);
}

//! Returns @ref True if the string passed identifies a symbolic link on the filesystem, @ref False if not
/** @par Platform Availability
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @param path the path to check

    @return @ref True if the string passed identifies a symbolic link on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_link("/etc/hosts");
    @endcode
 */
bool is_link(string path) [flags=CONSTANT;dom=FILESYSTEM] {
#ifdef HAVE_LSTAT
   return check_lstat(S_IFLNK, *path);
#else
   missing_function_error("is_link", "UNIX_FILEMGT", xsink);
   return false;
#endif
}

//! Returns @ref True if the string passed identifies a file or a directory readable by the current user, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a file or a directory readable by the current user, @ref False if not

    @par Example:
    @code{.py}
bool b = is_readable("/etc/hosts");
    @endcode
 */
bool is_readable(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return q_path_is_readable(path->getBuffer());
}

//! Returns @ref True if the string passed identifies a file or a directory writable by the current user, @ref False if not
/**
    @param path the path to check

    @return @ref True if the string passed identifies a file or a directory writable by the current user, @ref False if not

    @par Example:
    @code{.py}
bool b = is_writable("/etc/hosts");
    @endcode

    @since %Qore 0.8.12 this function works properly on Windows
 */
bool is_writable(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return is_writable_intern(path);
}

//! Returns @ref True if the string passed identifies a file or a directory writable by the current user (backwards-compatible misspelling of is_writable())
/**
    @param path the path to check

    @return @ref True if the string passed identifies a file or a directory writable by the current user, @ref False if not

    @par Example:
    @code{.py}
bool b = is_writeable("/etc/hosts");
    @endcode

    @since %Qore 0.8.12 this function works properly on Windows
 */
bool is_writeable(string path) [flags=CONSTANT;dom=FILESYSTEM] {
   return is_writable_intern(path);
}

//! Returns @ref True if the string passed identifies an executable on the filesystem, @ref False if not
/** @par Platform Availability
    @ref Qore::Option::HAVE_IS_EXECUTABLE

    @param path the path to check

    @return @ref True if the string passed identifies an executable on the filesystem, @ref False if not

    @par Example:
    @code{.py}
bool b = is_executable("/bin/login");
    @endcode
 */
bool is_executable(string path) [flags=CONSTANT;dom=FILESYSTEM] {
#ifdef HAVE_PWD_H
   struct stat sbuf;
   int rc;

   if ((rc = stat(path->getBuffer(), &sbuf)))
      return false;

   if (sbuf.st_mode & S_IXOTH
       || (geteuid() == sbuf.st_uid && (sbuf.st_mode & S_IXUSR))
       || (getegid() == sbuf.st_gid && (sbuf.st_mode & S_IXGRP)))
      return true;

   return false;
#else
   return missing_function_error("is_executable", xsink);
#endif
}

//! Renames (or moves) files or directories. Note that for this call to function properly, the %Qore process must have sufficient permissions and access to the given filesystem objects or paths to execute the rename operation
/** This function does not return any value; if any errors occur, an exception is thrown.

    @param old_path The original path for the file to move
    @param new_path The target path for the file

    @par Example:
    @code{.py}
rename("/tmp/myfile", "/tmp/myfile.txt");
    @endcode

    @throw RENAME-ERROR empty string passed for one of the arguments or the operating system returned an error

    @note Some operating systems do not allow moving files between filesystems (ex Solaris)
 */
nothing rename(string old_path, string new_path) [dom=FILESYSTEM] {
   if (old_path->empty())
      return xsink->raiseException("RENAME-ERROR", "empty path to current file name given as first argument");

   // new file name
   if (new_path->empty())
      return xsink->raiseException("RENAME-ERROR", "empty new file path given as second argument");

   int rc = rename(old_path->getBuffer(), new_path->getBuffer());
   if (rc)
      xsink->raiseErrnoException("RENAME-ERROR", errno, "rename('%s', '%s') failed", old_path->getBuffer(), new_path->getBuffer());

   return QoreValue();
}

//! Returns a hash of filesystem status values for the file or directory path passed
/** @par Platform Availability:
    @ref Qore::Option::HAVE_STATVFS

    @param path the path to the filesystem (or on the filesystem)

    @return a @ref FilesystemInfo hash; or, if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number

    @par Example:
    @code{.py}
*hash<FilesystemInfo> h = statvfs("/tmp")
    @endcode

    @see File::statvfs() for a static method in the @ref Qore::File "File class" that throws an exception instead of returning @ref nothing when errors occur
 */
*hash<FilesystemInfo> statvfs(string path) [dom=FILESYSTEM] {
#ifdef Q_HAVE_STATVFS
    struct statvfs vfs;
    if (statvfs(path->getBuffer(), &vfs))
        return QoreValue();

    return statvfs_to_hash(vfs);
#else
    return missing_function_error("statvfs", xsink);
#endif
}

//! Returns a @ref stat_list "list of file status values" for the path argument, following any symbolic links; if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number
/** @param path the path to retrieve information for

    @return a @ref stat_list "list of file status values" for the path argument, following any symbolic links; if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number

    @par Example:
    @code{.py}
*list<auto> l = stat("/tmp/file.txt");
if (!exists l)
    printf("could not stat /tmp/file.txt: %s\n", strerror());
    @endcode

    @see lstat() for a version of this function that does not follow symbolic links\n\n
    hstat() for a version of this function that returns a user-friendly hash instead of a list\n\n
    File::stat() for a static method in the File class that throws an exception instead of returning @ref nothing when errors occur
 */
*list<auto> stat(string path) [dom=FILESYSTEM] {
    struct stat sbuf;
    if (stat(path->getBuffer(), &sbuf))
        return QoreValue();

    return stat_to_list(sbuf);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing stat() [flags=RUNTIME_NOOP;dom=FILESYSTEM] {
}

//! Returns a @ref stat_list "list of file status values" for the path argument and does not follow symbolic links; if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number
/** @param path the path to retrieve information for

    @return a @ref stat_list "list of file status values" for the path argument and does not follow symbolic links; if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number
    @par Example:
    @code{.py}
*list<auto> l = lstat("/tmp/file.txt");
if (!exists l)
    printf("could not lstat /tmp/file.txt: %s\n", strerror());
    @endcode

    @note on platforms without symbolic links (such as with native Windows ports, for example), this function is identical to stat()

    @see stat() for a version of this function that follows symbolic links\n\n
    hlstat() for a version of this function that returns a user-friendly hash instead of a list\n\n
    File::lstat() for a static method in the File class that throws an exception instead of returning @ref nothing when errors occur
 */
*list<auto> lstat(string path) [dom=FILESYSTEM] {
   struct stat sbuf;
#ifdef HAVE_LSTAT
   if (lstat(path->getBuffer(), &sbuf))
#else
   if (stat(path->getBuffer(), &sbuf))
#endif
      return QoreValue();

   return stat_to_list(sbuf);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing lstat() [flags=RUNTIME_NOOP;dom=FILESYSTEM] {
}

//! Returns a @ref StatInfo hash for the path argument, following any symbolic links; if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number
/** @param path the path to retrieve information for

    @return a @ref Qore::StatInfo "StatInfo" hash providing information about the path argument, following any symbolic links; if any errors occur (such as if the target path is not readable or the file does not exist for example), @ref nothing is returned and errno() can be used to retrieve the error number

    @par Example:
    @code{.py}
*hash<StatInfo> h = hstat("/tmp/file.txt");
if (!h)
    printf("could not hstat /tmp/file.txt: %s\n", strerror());
    @endcode

    @see hlstat() for a version of this function that does not follow symbolic links\n\n
    stat() for a version of this function that returns a traditional @ref stat_list "list" instead of a user-friendly hash\n\n
    File::hstat() for a static method in the File class that throws an exception instead of returning @ref nothing when errors occur
 */
*hash<StatInfo> hstat(string path) [dom=FILESYSTEM] {
   struct stat sbuf;
   if (stat(path->getBuffer(), &sbuf))
      return QoreValue();

   return stat_to_hash(sbuf);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing hstat() [flags=RUNTIME_NOOP;dom=FILESYSTEM] {
}

//! Returns a @ref StatInfo hash for the path argument and does not follow symbolic links; if any errors occur, @ref nothing is returned and errno() can be used to retrieve the error number
/** @param path the path to retrieve information for

    @return a @ref Qore::StatInfo "StatInfo" hash providing information about the path argument, not following any symbolic links; if any errors occur (such as if the target path is not readable or the file does not exist for example), @ref nothing is returned and errno() can be used to retrieve the error number

    @par Example:
    @code{.py}
*hash<StatInfo> h = hlstat("/tmp/file.txt");
if (!exists h)
    printf("could not hlstat /tmp/file.txt: %s\n", strerror());
    @endcode

    @see hstat() for a version of this function that follows symbolic links\n\n
    lstat() for a version of this function that returns a traditional @ref stat_list "list" instead of a user-friendly hash\n\n
    File::hlstat() for a static method in the File class that throws an exception instead of returning @ref nothing when errors occur
 */
*hash<StatInfo> hlstat(string path) [dom=FILESYSTEM] {
   struct stat sbuf;
#ifdef HAVE_LSTAT
   if (lstat(path->getBuffer(), &sbuf))
#else
   if (stat(path->getBuffer(), &sbuf))
#endif
      return QoreValue();

   return stat_to_hash(sbuf);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing hlstat() [flags=RUNTIME_NOOP;dom=FILESYSTEM] {
}

//! Returns a list of files matching the string argument or @ref nothing if the call to glob() fails
/**
    @param glob_str the glob string, containing an optional path (in which case the entire path must be readable) and a glob filename pattern

    @return a list of files matching the string argument or @ref nothing if the call to glob() fails, in which case errno() can be used to get the error

    @par Example:
    @code{.py}
*list<string> gl = glob("*.txt");
if (!gl)
    printf("failed to glob *.txt: %s\n", strerror());
    @endcode
 */
*list<string> glob(string glob_str) [dom=FILESYSTEM] {
    glob_t globbuf;
    if (glob(glob_str->getBuffer(), 0, 0, &globbuf)) {
        globfree(&globbuf);
        return QoreValue();
    }

    QoreListNode *l = new QoreListNode(stringTypeInfo);
    for (unsigned i = 0; i < globbuf.gl_pathc; ++i)
        l->push(new QoreStringNode(globbuf.gl_pathv[i]), xsink);

    globfree(&globbuf);
    return l;
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing glob() [flags=RUNTIME_NOOP;dom=FILESYSTEM] {
}

//! Deletes a file and returns 0 for success, -1 for error (in which case errno() can be used to get the error)
/** Does not delete directories; see rmdir() for a similar function that removes directories

    @param path the path to the file to delete

    @return 0 for success, -1 for error (in which case errno() can be used to get the error)

    @par Example:
    @code{.py}
if (unlink(path))
    printf("%s: %s\n", path, strerror());
    @endcode
 */
int unlink(string path) [dom=FILESYSTEM] {
   return unlink(path->getBuffer());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing unlink() [flags=RUNTIME_NOOP;dom=FILESYSTEM] {
}

//! Sets the file creation mode mask for the process and returns the previous value of the file creation mode mask
/** @param mask the new file creation mode mask for the process

    @return the old file creation mode mask for the process

    @par Example:
    @code{.py}
if (umask(mask)) {
    printf("%s: %s\n", path, strerror());
}
    @endcode
 */
int umask(softint mask) [dom=PROCESS] {
   return umask((int)mask);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing umask() [flags=RUNTIME_NOOP;dom=PROCESS] {
}

//! Creates a directory, optionally specifying the mode
/** @param path the path to the directory to create
    @param mode the file mode of the new directory (which will be AND'ed with the umask)
    @param parents if True then parent directories are created

    @return if no error occurred returns 0; -1 if an error occurred, in which case errno() and/or strerror() can be
    used to retrieve the error

    @par Example:
    @code{.py}
if (mkdir("/tmp/newdir", 0755)) {
    printf("mkdir /tmp/newdir: %s\n", strerror());
}
    @endcode

    @see
    - mkdir_ex()
    - umask()

    @since
    - %Qore 0.8.13 added the <i>parents</i> parameter
    - %Qore 0.9.5 fixed error-handling and the return value to be sane and consistent independently of the args
 */
int mkdir(string path, softint mode = 0777, bool parents = False) [dom=FILESYSTEM] {
    if (parents) {
        QoreDir dir(xsink);
        if (dir.chdir(path->c_str()) == -1) {
            if (!errno) {
                errno = EPERM;
            }
            return -1;
        }

        // issue #3992: return an error code on failure, do not throw an exception
        int rc = dir.create((int)mode);
        if (rc < 0 && !errno) {
            errno = EPERM;
        }
        return rc < 0 ? -1 : 0;
    }
#ifdef _Q_WINDOWS
    return _mkdir(path->c_str());
#else
    return mkdir(path->c_str(), mode);
#endif
}

//! Creates a directory, optionally specifying the mode; throws an exception on error
/** @param path the path to the directory to create
    @param mode the file mode of the new directory (which will be AND'ed with the umask)
    @param parents if True then parent directories are created

    @par Example:
    @code{.py}
mkdir_ex("/tmp/newdir", 0755);
    @endcode

    @see
    - mkdir()
    - @ref Qore::Dir::create() "Dir::create()" for a similar method that will return the number of directories created
    - umask()

    @since %Qore 0.9.5
 */
nothing mkdir_ex(string path, softint mode = 0777, bool parents = False) [dom=FILESYSTEM] {
    if (parents) {
        QoreDir dir(xsink);
        if (dir.chdir(path->c_str(), xsink) == -1) {
            assert(*xsink);
            return -1;
        }

        dir.create((int)mode, xsink);
        return QoreValue();
    }
#ifdef _Q_WINDOWS
    int rc = _mkdir(path->c_str());
#else
    int rc = mkdir(path->c_str(), mode);
#endif
    if (rc) {
        xsink->raiseErrnoException("DIR-CREATE-FAILURE", errno, "cannot mkdir '%s'", path->c_str());
    }
    return QoreValue();
}

//! Removes a directory
/** @param path the path to the directory to remove

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (rmdir("/tmp/newdir"))
    printf("rmdir /tmp/newdir: %s\n", strerror());
    @endcode

    @see @ref Qore::Dir::rmdir()
 */
int rmdir(string path) [dom=FILESYSTEM] {
   return rmdir(path->getBuffer());
}

//! Changes the mode of a file or directory
/** @param path the path to the file or directory to change
    @param mode the new mode for the file or directory

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (chmod("/bin/login", 0755))
    printf("rmdir /tmp/newdir: %s\n", strerror());
    @endcode
 */
int chmod(string path, softint mode) [dom=FILESYSTEM] {
   return chmod(path->getBuffer(), (int)mode);
}

//! Changes the current working directory for the current process
/**
    @param path the new working directory for the current process

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (chdir(dir))
    printf("chdir %s: %s\n", dir, strerror());
    @endcode

    @since %Qore 0.8.4 this function is tagged with @ref Qore::PO_NO_PROCESS_CONTROL
 */
int chdir(string path) [dom=PROCESS,FILESYSTEM] {
   return chdir(path->getBuffer());
}

//! Returns a string giving the current working directory or @ref nothing if the current working directory could not be read
/** @return a string giving the current working directory or @ref nothing if the current working directory could not be read

    @par Example:
    @code{.py}
*string cwd = getcwd();
    @endcode

    @see getcwd2() for a similar function that throws an exception if an error occurs instead
 */
*string getcwd() [flags=CONSTANT;dom=FILESYSTEM,EXTERNAL_INFO] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode);
   return q_getcwd(**rv) ? 0 : rv.release();
}

//! Returns a string giving the current working directory; throws an exception if the current directory cannot be read
/**
    @par Example:
    @code{.py}
string cwd = getcwd2();
    @endcode

    @see getcwd() for a similar function that returns @ref nothing instead of throwing an exception if an error occurs
*/
string getcwd2() [flags=RET_VALUE_ONLY;dom=FILESYSTEM,EXTERNAL_INFO] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode);
   if (q_getcwd(**rv))
      return xsink->raiseErrnoException("GETCWD2-ERROR", errno, "getcwd() failed");

   return rv.release();
}

//! Creates a named pipe file with an optional file mode
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @param path the path to the new named pipe
    @param mode the file mode for the new named pipe

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (mkfifo("/tmp/pipe"))
    printf("mkfifo /tmp/pipe: %s\n", strerror());
    @endcode
 */
int mkfifo(string path, softint mode = 0600) [dom=FILESYSTEM] {
#ifdef HAVE_MKFIFO
   return mkfifo(path->getBuffer(), mode);
#else
   return missing_function_error("mkfifo", "UNIX_FILEMGT", xsink);
#endif
}

//! Changes the user and group owners of a file, if the current user has permission to do so (normally only the superuser can change the user owner), follows symbolic links
/** @par Platform Availability:
    Qore::Option::HAVE_UNIX_FILEMGT

    @param path path to the file or directory to change
    @param owner the uid of the new user owner; -1 means do not change user owner
    @param group the gid of the new group owner; -1 means do not change group owner

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (chown("/bin/login", 0, 0))
    printf("chown /bin/login: %s\n", strerror());
    @endcode

    @see lchown() for a version of this function that does not follow symbolic links
 */
int chown(string path, softint owner = -1, softint group = -1) [dom=FILESYSTEM] {
#ifdef HAVE_CHOWN
   return chown(path->getBuffer(), (uid_t)owner, (uid_t)group);
#else
   return missing_function_error("chown", "UNIX_FILEMGT", xsink);
#endif
}

//! Changes the user and group owners of a file, if the current user has permission to do so (normally only the superuser can change the user owner), does not follow symbolic links but rather operates on the symbolic link itself
/** @par Platform Availability:
    Qore::Option::HAVE_UNIX_FILEMGT

    @param path path to the file or directory to change
    @param uid the uid of the new user owner; -1 means do not change user owner
    @param gid the gid of the new group owner; -1 means do not change group owner

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (lchown("/tmp/socket", 0, 0))
    printf("lchown /tmp/socket: %s\n", strerror());
    @endcode

    @see chown() for a version of this function that follows symbolic links (ie operates on the target instead of on the link itself)
 */
int lchown(string path, softint uid = -1, softint gid = -1) [dom=FILESYSTEM] {
#ifdef HAVE_LCHOWN
   return lchown(path->getBuffer(), (uid_t)uid, (uid_t)gid);
#else
   return missing_function_error("lchown", "UNIX_FILEMGT", xsink);
#endif
}

//! Returns the target of a symbolic link; throws an exception if an error occurs (ex: file does not exist or is not a symbolic link)
/** @par Platform Availability:
    Qore::Option::HAVE_UNIX_FILEMGT

    @param path the path to the symbolic link

    @return the target of the link; throws an exception if the given path is not a symbolic link (or if another error occurs)

    @par Example:
    @code{.py}
string str = readlink("/tmp/symbolic_link");
    @endcode

    @throw READLINK-ERROR Invalid arguments or a system error occured (ex: file does not exist or is not a symbolic link)
 */
string readlink(string path) [dom=FILESYSTEM] {
#ifdef HAVE_READLINK
   char buf[QORE_PATH_MAX + 1];
   qore_offset_t len = readlink(path->getBuffer(), buf, QORE_PATH_MAX);
   if (len < 0)
      return xsink->raiseErrnoException("READLINK-ERROR", errno, "readlink('%s') failed", path->getBuffer());
   assert(len <= QORE_PATH_MAX);
   buf[len] = '\0';
   return new QoreStringNode(buf);
#else
   return missing_function_error("readlink", "UNIX_FILEMGT", xsink);
#endif
}

//! Creates a symbolic link to a directory path. Note that for this call to function properly, the %Qore process must have sufficient permissions and access to the given filesystem path to create the symbolic link
/** @par Platform Availability:
    Qore::Option::HAVE_UNIX_FILEMGT

    This function does not return any value; if any errors occur, an exception is thrown.
    If the target of the symbolic link does not exist, it is not an error; the symbolic link is created anyway.

    @param old_path The original path; the target of the link
    @param new_path The path to the location of the new symbolic link to be created with this function call

    @par Example:
    @code{.py}
symlink("/tmp/temporary-dir", "/users/oracle/install");
    @endcode

    @throw SYMLINK-ERROR empty string passed for one of the arguments or the operating system returned an error

    @since %Qore 0.8.5
 */
nothing symlink(string old_path, string new_path) [dom=FILESYSTEM] {
#ifdef HAVE_SYMLINK
   if (old_path->empty())
      return xsink->raiseException("SYMLINK-ERROR", "empty path to current file name given as first argument");

   // new file name
   if (new_path->empty())
      return xsink->raiseException("SYMLINK-ERROR", "empty new file path given as second argument");

   int rc = symlink(old_path->getBuffer(), new_path->getBuffer());
   if (rc)
      xsink->raiseErrnoException("SYMLINK-ERROR", errno, "symlink('%s', '%s') failed", old_path->getBuffer(), new_path->getBuffer());

   return QoreValue();
#else
   return missing_function_error("symlink", "UNIX_FILEMGT", xsink);
#endif
}

//! returns @ref True "True" if the argument is an absolute path on the current platform, @ref False "False" if not
/** @par Example:
    @code{.py}
bool abs = absolute_path(path);
    @endcode

    @param path the path to check

    @return @ref True "True" if the argument is an absolute path, @ref False "False" if not

    @see
    - @ref Qore::absolute_path_unix()
    - @ref Qore::absolute_path_windows()

    @since %Qore 0.8.12 this function is a native function in %Qore; moved from the <a href="../../modules/Util/html/index.html">Util</a> module
*/
bool absolute_path(string path) [flags=CONSTANT] {
   return q_absolute_path(path->getBuffer());
}

//! returns @ref True "True" if the argument is a UNIX absolute path, @ref False "False" if not
/** @par Example:
    @code{.py}
bool abs = absolute_path_unix(path);
    @endcode

    @param path the path to check

    @return @ref True "True" if the argument is a UNIX absolute path, @ref False "False" if not

    @see
    - @ref Qore::absolute_path()
    - @ref Qore::absolute_path_windows()

    @since %Qore 0.8.12 this function is a native function in %Qore; moved from the <a href="../../modules/Util/html/index.html">Util</a> module
*/
bool absolute_path_unix(string path) [flags=CONSTANT] {
   return q_absolute_path_unix(path->getBuffer());
}

//! returns @ref True "True" if the argument is a Windows absolute path, @ref False "False" if not
/** @par Example:
    @code{.py}
bool abs = absolute_path_windows(path);
    @endcode

    @param path the path to check

    @return @ref True "True" if the argument is a Windows absolute path, @ref False "False" if not

    @see
    - @ref Qore::absolute_path()
    - @ref Qore::absolute_path_unix()

    @since %Qore 0.8.12 this function is a native function in %Qore; moved from the <a href="../../modules/Util/html/index.html">Util</a> module
*/
bool absolute_path_windows(string path) [flags=CONSTANT] {
   return q_absolute_path_windows(path->getBuffer());
}

//! Returns the canonicalized absolute pathname from the given path
/** @par Example:
    @code{.py}
string realpath = realpath(path);
    @endcode

    @param path the path to process

    @return the corresponding absolute path with all relative references removed and all symbolic links resolved (if applicable for the current platform)

    @throw REALPATH-ERROR if an error occurs calculating the return value then this exception is thrown

    @since %Qore 0.8.12
 */
string realpath(string path) [dom=FILESYSTEM] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(path->getEncoding()));
   return q_realpath(*path, **rv, xsink) ? 0 : rv.release();
}
///@}
