/*
Copyright (C) 2001-2010, Parrot Foundation.
$Id: os.pmc 46300 2010-05-05 03:58:50Z jimmy $

=head1 NAME

src/pmc/os.pmc - Files and Directories PMC

=head1 DESCRIPTION

C<OS> is a singleton class which provides access to the filesystem
files and directories.

=head2 Methods

=over 4

=cut

*/

#if defined(_MSC_VER)
#  include <direct.h>
#  include <io.h>
#  include <tchar.h>
#  include <windows.h>
#elif defined(__BORLANDC__)
#  include <dir.h>
#  include <dirent.h>
#else
#  include <dirent.h>
#endif

/* XXX Check if we need to deallocate strerror strings */
/* XXX apparently, strerror_r is thread-safe and should be used instead.*/

/* HEADERIZER HFILE: none */
/* HEADERIZER BEGIN: static */
/* HEADERIZER END: static */

static PMC *OS_PMC;
pmclass OS dynpmc singleton {

/*

=item C<void *get_pointer()>

=item C<void set_pointer(void *ptr)>

These two functions are part of the singleton creation interface. For more
information see F<src/pmc.c>.

=cut

*/

    void class_init() {
        OS_PMC = NULL;
    }

    VTABLE void *get_pointer() {
        return OS_PMC;
    }

    VTABLE void set_pointer(void *ptr) {
        OS_PMC = (PMC *)ptr;
    }


/*

=item C<STRING *cwd()>

Returns the current working directory.

=cut

*/

    METHOD cwd() {
        char *cwd;
#ifdef _MSC_VER
        cwd = _getcwd(NULL, 0);
        /* capitalize the drive letter */
        cwd[0] = (char)toupper((unsigned char)cwd[0]);
#else
#  ifdef PATH_MAX
        cwd = getcwd(NULL, PATH_MAX+1);
#  else
        cwd = getcwd(NULL, 0);
#  endif
#endif
        if (cwd) {
            STRING * const scwd = Parrot_str_new(INTERP, cwd, strlen(cwd));
            free(cwd);
            RETURN(STRING *scwd);
        }
        else {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
    }

/*

=item C<void chdir(STRING *path)>

Changes the current working directory to the one specified by C<path>.

=cut

*/

    METHOD chdir(STRING *path) {
        int error;
        char * const cpath = Parrot_str_to_cstring(INTERP, path);
#ifdef _MSC_VER
        error = _chdir(cpath);
#else
        error = chdir(cpath);
#endif
        Parrot_str_free_cstring(cpath);
        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
    }

/*

=item C<void rm(STRING *path)>

Calls C<remove> to remove the file or empty directory specified by
C<path>.

=cut

*/

    METHOD rm(STRING *path) {
        struct stat info;
        char * const cpath = Parrot_str_to_cstring(INTERP, path);
        int          error = stat(cpath, &info);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_str_free_cstring(cpath);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }

        if (S_ISDIR(info.st_mode)) {
#ifdef _MSC_VER
            error = _rmdir(cpath);
#else
            error = rmdir(cpath);
#endif
            Parrot_str_free_cstring(cpath);
            if (error) {
                const char * const errmsg = strerror(errno);
                Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                    errmsg);
            }
        }
        else {
            error = remove(cpath);
            Parrot_str_free_cstring(cpath);
            if (error) {
                const char * const errmsg = strerror(errno);
                Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                    errmsg);
            }
        }
    }

/*

=item C<void mkdir(STRING *path, STRING *mode)>

Creates a directory specified by C<path> with mode C<mode>.

=cut

*/

    METHOD mkdir(STRING *path, INTVAL mode) {
        char * const cpath = Parrot_str_to_cstring(INTERP, path);
        /* should we validate mode? */
#ifdef WIN32
        const int error = _mkdir(cpath);
#else
        const int error = mkdir(cpath, (mode_t)mode);
#endif
        Parrot_str_free_cstring(cpath);
        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
    }

/*

=item C<fixedpmcarray *stat(STRING *path)>

Stats a file, and returns a 13 position array as in Perl:

     0 dev      device number of filesystem
     1 ino      inode number
     2 mode     file mode  (type and permissions)
     3 nlink    number of (hard) links to the file
     4 uid      numeric user ID of file's owner
     5 gid      numeric group ID of file's owner
     6 rdev     the device identifier (special files only)
     7 size     total size of file, in bytes
     8 atime    last access time in seconds since the epoch
     9 mtime    last modify time in seconds since the epoch
    10 ctime    inode change time in seconds since the epoch (*)
    11 blksize  preferred block size for file system I/O
    12 blocks   actual number of blocks allocated

11 and 12 are not available under Windows.

=cut

TT #849:  Provide a mechanism for setting 'mtime' and 'atime':
https://trac.parrot.org/parrot/ticket/849

*/

    METHOD stat(STRING *path) {
        struct stat   info;
        char  * const cpath = Parrot_str_to_cstring(INTERP, path);
        const int     error = stat(cpath, &info);

        Parrot_str_free_cstring(cpath);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
        else {
            PMC * const array = Parrot_pmc_new(INTERP, enum_class_FixedPMCArray);
            VTABLE_set_integer_native(INTERP, array, 13);

            VTABLE_set_integer_keyed_int(INTERP, array,  0,
                    (INTVAL)info.st_dev);
            VTABLE_set_integer_keyed_int(INTERP, array,  1,
                    (INTVAL)info.st_ino);
            VTABLE_set_integer_keyed_int(INTERP, array,  2,
                    (INTVAL)info.st_mode);
            VTABLE_set_integer_keyed_int(INTERP, array,  3,
                    (INTVAL)info.st_nlink);
            VTABLE_set_integer_keyed_int(INTERP, array,  4,
                    (INTVAL)info.st_uid);
            VTABLE_set_integer_keyed_int(INTERP, array,  5,
                    (INTVAL)info.st_gid);
            VTABLE_set_integer_keyed_int(INTERP, array,  6,
                    (INTVAL)info.st_rdev);
            VTABLE_set_integer_keyed_int(INTERP, array,  7,
                    (INTVAL)info.st_size);
            VTABLE_set_integer_keyed_int(INTERP, array,  8,
                    (INTVAL)info.st_atime);
            VTABLE_set_integer_keyed_int(INTERP, array,  9,
                    (INTVAL)info.st_mtime);
            VTABLE_set_integer_keyed_int(INTERP, array, 10,
                    (INTVAL)info.st_ctime);
#ifndef WIN32
            VTABLE_set_integer_keyed_int(INTERP, array, 11,
                    (INTVAL)info.st_blksize);
            VTABLE_set_integer_keyed_int(INTERP, array, 12,
                    (INTVAL)info.st_blocks);
#endif
            RETURN(PMC *array);
        }
    }

/*

=item C<fixedpmcarray *lstat(STRING *path)>

Stats a file, and returns a 13 position array as in Perl:

     0 dev      device number of filesystem
     1 ino      inode number
     2 mode     file mode  (type and permissions)
     3 nlink    number of (hard) links to the file
     4 uid      numeric user ID of file's owner
     5 gid      numeric group ID of file's owner
     6 rdev     the device identifier (special files only)
     7 size     total size of file, in bytes
     8 atime    last access time in seconds since the epoch
     9 mtime    last modify time in seconds since the epoch
    10 ctime    inode change time in seconds since the epoch (*)
    11 blksize  preferred block size for file system I/O
    12 blocks   actual number of blocks allocated

11 and 12 are not available under Windows.

=cut

*/

    METHOD lstat(STRING *path) {
        struct stat info;

        char * const cpath = Parrot_str_to_cstring(INTERP, path);
#ifdef WIN32
        const int error = stat(cpath, &info);
#else
        const int error = lstat(cpath, &info);
#endif
        Parrot_str_free_cstring(cpath);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
        else {
            PMC * const array = Parrot_pmc_new(INTERP, enum_class_FixedPMCArray);
            VTABLE_set_integer_native(INTERP, array, 13);

            VTABLE_set_integer_keyed_int(INTERP, array,  0,
                    (INTVAL)info.st_dev);
            VTABLE_set_integer_keyed_int(INTERP, array,  1,
                    (INTVAL)info.st_ino);
            VTABLE_set_integer_keyed_int(INTERP, array,  2,
                    (INTVAL)info.st_mode);
            VTABLE_set_integer_keyed_int(INTERP, array,  3,
                    (INTVAL)info.st_nlink);
            VTABLE_set_integer_keyed_int(INTERP, array,  4,
                    (INTVAL)info.st_uid);
            VTABLE_set_integer_keyed_int(INTERP, array,  5,
                    (INTVAL)info.st_gid);
            VTABLE_set_integer_keyed_int(INTERP, array,  6,
                    (INTVAL)info.st_rdev);
            VTABLE_set_integer_keyed_int(INTERP, array,  7,
                    (INTVAL)info.st_size);
            VTABLE_set_integer_keyed_int(INTERP, array,  8,
                    (INTVAL)info.st_atime);
            VTABLE_set_integer_keyed_int(INTERP, array,  9,
                    (INTVAL)info.st_mtime);
            VTABLE_set_integer_keyed_int(INTERP, array, 10,
                    (INTVAL)info.st_ctime);
#ifndef WIN32
            VTABLE_set_integer_keyed_int(INTERP, array, 11,
                    (INTVAL)info.st_blksize);
            VTABLE_set_integer_keyed_int(INTERP, array, 12,
                    (INTVAL)info.st_blocks);
#endif
            RETURN(PMC *array);
        }
    }

/*

=item C<void symlink(STRING *from, STRING *to)>

Creates a symlink, where available

=cut

*/

    METHOD symlink(STRING *from, STRING *to) {
#ifndef WIN32
        char * const cfrom = Parrot_str_to_cstring(INTERP, from);
        char * const cto   = Parrot_str_to_cstring(INTERP, to);
        const int error    = symlink(cfrom, cto);

        Parrot_str_free_cstring(cfrom);
        Parrot_str_free_cstring(cto);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
#else
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_UNIMPLEMENTED,
                "Win32 does not support symlinks!");
#endif
    }

/*

=item C<void link(STRING *from, STRING *to)>

Creates a hard link, where available(?)

=cut

*/

    METHOD link(STRING *from, STRING *to) {
#ifndef WIN32
        char * const cfrom = Parrot_str_to_cstring(INTERP, from);
        char * const cto   = Parrot_str_to_cstring(INTERP, to);
        const int    error = link(cfrom, cto);

        Parrot_str_free_cstring(cfrom);
        Parrot_str_free_cstring(cto);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                "link() failed for OS PMC: %s\n", errmsg);
        }
#else
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INTERNAL_NOT_IMPLEMENTED,
                "Win32 is not POSIX. Need win32 developer!");
#endif
    }

/*

=item C<INTVAL umask(INTVAL mask)>

umask sets the process's file mode creation mask (and returns the
previous one).

=cut

*/

    METHOD umask(INTVAL mask) {
#ifndef _MSC_VER
        const INTVAL old = umask((mode_t)mask);
        RETURN(INTVAL old);
#else
        Parrot_ex_throw_from_c_args(INTERP, NULL,
            EXCEPTION_UNIMPLEMENTED,
            "Win32 is not POSIX. Need Win32 developer!");
#endif
    }

/*

=item C<INTVAL chroot(STRING *path)>

it makes the named directory the new root directory for all further
pathnames that begin with a "/" by your process and all its children.

B<NOTE>: perl restricts this operation to superusers. It might be a good
idea to do the same with parrot.

=cut

*/

    METHOD chroot(STRING *path) {
#ifndef WIN32
        char * const cpath = Parrot_str_to_cstring(INTERP, path);
        const int    error = chroot(cpath);

        Parrot_str_free_cstring(cpath);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
#else
        Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_INTERNAL_NOT_IMPLEMENTED,
                "Win32 is not POSIX. Need Win32 developer!");
#endif
    }


/*

=item C<PMC *readdir(STRING *path)>

reads entries from a directory.

=cut

*/
    METHOD readdir(STRING *path) {
        PMC   * array = Parrot_pmc_new(INTERP, enum_class_ResizableStringArray);
#ifndef _MSC_VER
        char  * const cpath = Parrot_str_to_cstring(INTERP, path);
        DIR   * const   dir = opendir(cpath);
        struct dirent *dirent;

        Parrot_str_free_cstring(cpath);

        if (!dir) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }

        while ((dirent = readdir(dir)) != NULL) {
            VTABLE_push_string(INTERP, array, Parrot_str_new(INTERP, dirent->d_name, 0));
        }

        closedir(dir);
#else
        WIN32_FIND_DATA file_find_data;
        char * cpath;
        HANDLE hFind = INVALID_HANDLE_VALUE;

        /* Add \* to the directory name and start search. */
        STRING *last_char = Parrot_str_substr(INTERP, path,
                                              Parrot_str_length(INTERP, path) - 1, 1, NULL, 0);
        int trailing_slash = Parrot_str_equal(INTERP, last_char, string_from_literal(INTERP, "\\"))
                             ||
                             Parrot_str_equal(INTERP, last_char, string_from_literal(INTERP, "/"));
        cpath = Parrot_str_to_cstring(INTERP, Parrot_str_concat(INTERP,
                path, string_from_literal(INTERP, trailing_slash ? "*" : "\\*"), 0));
        hFind = FindFirstFile(cpath, &file_find_data);
        Parrot_str_free_cstring(cpath);
        if (hFind == INVALID_HANDLE_VALUE)
        {
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                "Unable to readdir");
        }

        /* Loop over all directories and add to result array. */
        do
        {
            VTABLE_push_string(INTERP, array, Parrot_str_new(INTERP,
                    file_find_data.cFileName, 0));
        }
        while (FindNextFile(hFind, &file_find_data) != 0);
        if (GetLastError() != ERROR_NO_MORE_FILES)
        {
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                "Error during readdir");
        }
        FindClose(hFind);
#endif
        RETURN(PMC *array);
    }
/*
=item C<rename(STRING *oldpath, STRING *newpath)>

This method is a wrapper for rename(2). On error a SystemError exception is
thrown.

=cut

*/
    METHOD rename(STRING *oldpath, STRING *newpath) {
        char * const coldpath = Parrot_str_to_cstring(INTERP, oldpath);
        char * const cnewpath = Parrot_str_to_cstring(INTERP, newpath);
        const int    ret      = rename(coldpath, cnewpath);

        Parrot_str_free_cstring(coldpath);
        Parrot_str_free_cstring(cnewpath);

        if (ret < 0) {
            const char * const errmsg = strerror(errno) ;
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                "%s", errmsg);
        }
    }

/*

=item C<chmod(STRING *path, INTVAL mode)>

=cut

*/

    METHOD chmod(STRING *path, INTVAL mode) {
        char * const cpath = Parrot_str_to_cstring(INTERP, path);

#ifndef WIN32
        const int    error = chmod(cpath, mode);
#else
        const int    error = _chmod(cpath, mode);
#endif

        Parrot_str_free_cstring(cpath);

        if (error) {
            const char * const errmsg = strerror(errno);
            Parrot_ex_throw_from_c_args(INTERP, NULL, EXCEPTION_EXTERNAL_ERROR,
                errmsg);
        }
    }

}

/*

=back

=head1 SEE ALS0

   chdir(2), getcwd(3), unlink(2), mkdir(2), stat(2), lstat(2),
   symlink(2), link(2), umask(2), chroot(2)

=cut

*/

/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4:
 */
