The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
/*============================================================================
 *
 * UTCFileTime.xs
 *
 * DESCRIPTION
 *   C and XS portions of Win32::UTCFileTime module.
 *
 * COPYRIGHT
 *   Copyright (C) 2003-2006, 2008, 2012, 2014 Steve Hay.  All rights reserved.
 *   Portions Copyright (C) 2001 Jonathan M Gilligan.  Used with permission.
 *   Portions Copyright (C) 2001 Tony M Hoyle.  Used with permission.
 *
 * LICENCE
 *   You may distribute under the terms of either the GNU General Public License
 *   or the Artistic License, as specified in the LICENCE file.
 *
 *============================================================================*/

/*============================================================================
 * C CODE SECTION
 *============================================================================*/

                                        /* For _getdrive().                   */
#ifdef __BORLANDC__
#  include <dos.h>
#else
#  include <direct.h>
#endif
#include <fcntl.h>                      /* For the O_* flags.                 */
#include <io.h>                         /* For open() and close().            */
#include <stdarg.h>                     /* For va_list/va_start()/va_end().   */
#include <stdio.h>                      /* For sprintf().                     */
#include <stdlib.h>                     /* For errno.                         */
#include <string.h>                     /* For the str*() functions.          */
#include <sys/stat.h>                   /* For struct stat and the S_* flags. */

#define WIN32_LEAN_AND_MEAN             /* To exclude unnecessary headers.    */
#include <windows.h>                    /* For the Win32 API stuff.           */

#define PERL_NO_GET_CONTEXT             /* To get interp context efficiently. */
#define PERLIO_NOT_STDIO 0              /* To allow use of PerlIO and stdio.  */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#define NEED_newSVpvn_flags             /* For newSVpvs_flags().              */
#include "ppport.h"

#include "const-c.inc"

#define MY_CXT_KEY "Win32::UTCFileTime::_guts" XS_VERSION

typedef struct {
    int saved_errno;
    DWORD saved_error;
    char err_str[BUFSIZ];
} my_cxt_t;

START_MY_CXT

/* Borland C++ 5.5.1 doesn't have _dev_t. */
#ifdef __BORLANDC__
typedef dev_t _dev_t;
#endif

/* Macros to save and restore the value of the standard C library errno variable
 * and the Win32 API last-error code for use when cleaning up before returning
 * failure. */
#define WIN32_UTCFILETIME_SAVE_ERRS    STMT_START { \
    MY_CXT.saved_errno = errno;                     \
    MY_CXT.saved_error = GetLastError();            \
} STMT_END
#define WIN32_UTCFILETIME_RESTORE_ERRS STMT_START { \
    errno = MY_CXT.saved_errno;                     \
    SetLastError(MY_CXT.saved_error);               \
} STMT_END

#define WIN32_UTCFILETIME_SYS_ERR_STR (strerror(errno))
#define WIN32_UTCFILETIME_WIN_ERR_STR \
    (Win32UTCFileTime_StrWinError(aTHX_ aMY_CXT_ GetLastError()))

static bool Win32UTCFileTime_IsWinNT(pTHX_ pMY_CXT);
static bool Win32UTCFileTime_FileTimeToUnixTime(pTHX_ pMY_CXT_
    const FILETIME *ft, time_t *ut);
static bool Win32UTCFileTime_UnixTimeToFileTime(pTHX_ pMY_CXT_
    const time_t ut, FILETIME *ft);
static unsigned short Win32UTCFileTime_FileAttributesToUnixMode(pTHX_ pMY_CXT_
    const DWORD fa, const char *name);
static int Win32UTCFileTime_AltStat(pTHX_ pMY_CXT_ const char *name,
    struct stat *st_buf);
static bool Win32UTCFileTime_GetUTCFileTimes(pTHX_ pMY_CXT_ const char *name,
    time_t *u_atime_t, time_t *u_mtime_t, time_t *u_ctime_t);
static bool Win32UTCFileTime_SetUTCFileTimes(pTHX_ pMY_CXT_ const char *name,
    const time_t u_atime_t, const time_t u_mtime_t);
static char *Win32UTCFileTime_StrWinError(pTHX_ pMY_CXT_ DWORD err_num);
static void Win32UTCFileTime_SetErrStr(pTHX_ const char *value, ...);

/* Number of "clunks" (100-nanosecond intervals) in one second. */
static const ULONGLONG win32_utcfiletime_clunks_per_second = 10000000L;

/* The epoch of time_t values (00:00:00 Jan 01 1970 UTC) as a SYSTEMTIME. */
static const SYSTEMTIME win32_utcfiletime_base_st = {
    1970,    /* wYear         */
    1,       /* wMonth        */
    0,       /* wDayOfWeek    */
    1,       /* wDay          */
    0,       /* wHour         */
    0,       /* wMinute       */
    0,       /* wSecond       */
    0        /* wMilliseconds */
};

/* The epoch of time_t values (00:00:00 Jan 01 1970 UTC) as a FILETIME.  This is
 * set at boot time from the SYSTEMTIME above and is not subsequently changed,
 * so is virtually a "const" and is therefore thread-safe. */
static FILETIME win32_utcfiletime_base_ft;

/*
 * Function to determine whether the operating system platform is Windows NT (as
 * opposed to Win32s, Windows [95/98/ME] or Windows CE).
 */

static bool Win32UTCFileTime_IsWinNT(pTHX_ pMY_CXT) {
    /* These statics are set "on demand" and are not subsequently changed, so
     * are virtually "consts"s and are therefore thread-safe. */
    static bool initialized = FALSE;
    static bool is_winnt;
    OSVERSIONINFO osver;

    if (!initialized) {
        Zero(&osver, 1, OSVERSIONINFO);
        osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        if (GetVersionEx(&osver)) {
            is_winnt = (osver.dwPlatformId == VER_PLATFORM_WIN32_NT);
        }
        else {
            warn("Can't determine operating system platform: %s.  Assuming the "
                 "platform is Windows NT", WIN32_UTCFILETIME_WIN_ERR_STR);
            is_winnt = TRUE;
        }
        initialized = TRUE;
    }

    return is_winnt;
}

/*
 * Function to convert a FILETIME to a time_t.  The FILETIME and the time_t are
 * both UTC-based.
 *
 * This function is based on code written by Jonathan M Gilligan.
 */

static bool Win32UTCFileTime_FileTimeToUnixTime(pTHX_ pMY_CXT_
    const FILETIME *ft, time_t *ut)
{
    ULARGE_INTEGER it;

    /* Convert the FILETIME (which is expressed as the number of clunks
     * since 00:00:00 Jan 01 1601 UTC) to a time_t value by subtracting the
     * FILETIME representation of the epoch of time_t values and then
     * converting clunks to seconds. */
    it.QuadPart  = ((ULARGE_INTEGER *)ft)->QuadPart;
    it.QuadPart -= ((ULARGE_INTEGER *)&win32_utcfiletime_base_ft)->QuadPart;
    it.QuadPart /= win32_utcfiletime_clunks_per_second;

    *ut = it.LowPart;
    return TRUE;
}

/*
 * Function to convert a time_t to a FILETIME.  The time_t and the FILETIME are
 * both UTC-based.
 *
 * This function is based on code written by Tony M Hoyle.
 */

static bool Win32UTCFileTime_UnixTimeToFileTime(pTHX_ pMY_CXT_
    const time_t ut, FILETIME *ft)
{
    ULARGE_INTEGER it;

    /* Convert the time_t value to a FILETIME (which is expressed as the
     * number of clunks since 00:00:00 Jan 01 1601 UTC) by converting
     * seconds to clunks and then adding the FILETIME representation of the
     * epoch of time_t values. */
    it.LowPart   = (DWORD)ut;
    it.HighPart  = 0;
    it.QuadPart *= win32_utcfiletime_clunks_per_second;
    it.QuadPart += ((ULARGE_INTEGER *)&win32_utcfiletime_base_ft)->QuadPart;

    *(ULARGE_INTEGER *)ft = it;
    return TRUE;
}

/*
 * Function to convert file attributes as returned by the Win32 API function
 * GetFileAttributes() into a Unix mode as stored in the st_mode field of a
 * struct stat.
 *
 * This function is based on code taken from the wnt_stat() function in CVSNT
 * (version 2.0.4) the win32_stat() function in Perl (version 5.19.10).
 */

static unsigned short Win32UTCFileTime_FileAttributesToUnixMode(pTHX_ pMY_CXT_
    const DWORD fa, const char *name)
{
    unsigned short st_mode = 0;
    unsigned short perms;
    size_t len;
    const char *p;

    if (fa & FILE_ATTRIBUTE_DIRECTORY)
        st_mode |= _S_IFDIR;
    else
        st_mode |= _S_IFREG;

    if (fa & FILE_ATTRIBUTE_READONLY)
        st_mode |= _S_IREAD;
    else
        st_mode |= _S_IREAD | _S_IWRITE;

    /* Write permission used to be turned on for directories with Borland, but
     * only until a change in 5.11.0, backported to 5.8.9 and 5.10.1. */
    if (fa & FILE_ATTRIBUTE_DIRECTORY)
#if defined(__BORLANDC__) && PERL_REVISION == 5 && \
            ((PERL_VERSION ==  8 && PERL_SUBVERSION < 9) || \
              PERL_VERSION ==  9 || \
             (PERL_VERSION == 10 && PERL_SUBVERSION < 1))
        st_mode |= _S_IEXEC | _S_IWRITE;
#else
        st_mode |= _S_IEXEC;
#endif

    len = strlen(name);
    if (len >= 4 && (*(p = name + len - 4) == '.') &&
            (!stricmp(p, ".exe") ||  !stricmp(p, ".bat") ||
             !stricmp(p, ".com") || (!stricmp(p, ".cmd") &&
                                     Win32UTCFileTime_IsWinNT(aTHX_ aMY_CXT))))
        st_mode |= _S_IEXEC;

    /* Propagate permissions to "group" and "others". */
#ifdef __BORLANDC__
    if (!(fa & FILE_ATTRIBUTE_DIRECTORY)) {
#endif
    perms = st_mode & (_S_IREAD | _S_IWRITE | _S_IEXEC);
    st_mode |= (perms >> 3) | (perms >> 6);
#ifdef __BORLANDC__
    }
#endif

    return st_mode;
}

/*
 * Function to emulate the standard C library function stat(), setting the
 * last access time, last modification time and creation time members of the
 * given "stat" structure to UTC-based time_t values, whatever filesystem the
 * file is stored in.
 *
 * This function is based on code taken from the wnt_stat() function in CVSNT
 * (version 2.0.4) and the win32_stat() function in Perl (version 5.19.10).
 */

static int Win32UTCFileTime_AltStat(pTHX_ pMY_CXT_ const char *name,
    struct stat *st_buf)
{
    int drive;
    HANDLE hndl;
    BY_HANDLE_FILE_INFORMATION bhfi;

    /* Return an error if a wildcard has been specified. */
    if (strpbrk(name, "?*")) {
        Win32UTCFileTime_SetErrStr(aTHX_ "Wildcard in filename '%s'", name);
        errno = ENOENT;
        return -1;
    }

    Zero(&bhfi, 1, BY_HANDLE_FILE_INFORMATION);

    /* Use CreateFile() and GetFileInformationByHandle(), rather than
     * FindFirstFile() like Microsoft's stat() does, for three reasons:
     * (1) CreateFile() does not require "List Folder Contents" permission on
     *     the parent directory like FindFirstFile() does;
     * (2) The BY_HANDLE_FILE_INFORMATION structure returned by
     *     GetFileInformationByHandle() contains the number of links to the
     *     file, which the WIN32_FIND_DATA structure returned by FindFirstFile()
     *     does not; and most importantly
     * (3) The file times in that structure are correct UTC times on both NTFS
     *     and FAT, whereas on FAT the file times in the WIN32_FIND_DATA
     *     structure are sometimes wrong w.r.t. DST season changes
     * Pass the FILE_FLAG_BACKUP_SEMANTICS flag to allow directory handles to be
     * obtained (provided that this is a Windows NT platform). */
    if ((hndl = CreateFile(name, 0, 0, NULL, OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE)
    {
        /* If this is a valid directory (presumably under a Windows 95 platform
         * on which the FILE_FLAG_BACKUP_SEMANTICS flag does not do the trick)
         * then set all the fields except st_mode to zero and return TRUE, like
         * Perl's built-in functions do in this case.  Save the Win32 API last-
         * error code from the failed CreateFile() call first in case this is
         * not a directory. */
        DWORD le = GetLastError();
        DWORD fa = GetFileAttributes(name);
        if (fa != 0xFFFFFFFF && (fa & FILE_ATTRIBUTE_DIRECTORY)) {
            Zero(st_buf, 1, struct stat);
            st_buf->st_mode =
                Win32UTCFileTime_FileAttributesToUnixMode(aTHX_ aMY_CXT_
                                                          fa, name);
            errno = 0;
            return 0;
        }
        else {
            Win32UTCFileTime_SetErrStr(aTHX_
                "Can't open file '%s' for reading: %s",
                name, Win32UTCFileTime_StrWinError(aTHX_ aMY_CXT_ le)
            );
            return -1;
        }
    }

    if (!GetFileInformationByHandle(hndl, &bhfi)) {
        Win32UTCFileTime_SetErrStr(aTHX_
            "Can't get file information for file '%s': %s",
            name, WIN32_UTCFILETIME_WIN_ERR_STR
        );
        WIN32_UTCFILETIME_SAVE_ERRS;
        CloseHandle(hndl);
        WIN32_UTCFILETIME_RESTORE_ERRS;
        return -1;
    }

    if (!CloseHandle(hndl))
        warn("Can't close file object handle '%lu' for file '%s' after "
             "reading: %s", hndl, name, WIN32_UTCFILETIME_WIN_ERR_STR);

    if (!(Win32UTCFileTime_FileTimeToUnixTime(aTHX_ aMY_CXT_
                &bhfi.ftLastAccessTime, &st_buf->st_atime) &&
            Win32UTCFileTime_FileTimeToUnixTime(aTHX_ aMY_CXT_
                &bhfi.ftLastWriteTime, &st_buf->st_mtime) &&
            Win32UTCFileTime_FileTimeToUnixTime(aTHX_ aMY_CXT_
                &bhfi.ftCreationTime, &st_buf->st_ctime)))
        return -1;

    st_buf->st_mode =
        Win32UTCFileTime_FileAttributesToUnixMode(aTHX_ aMY_CXT_
                                                  bhfi.dwFileAttributes, name);

    if (bhfi.nNumberOfLinks > SHRT_MAX) {
        warn("Overflow: Too many links (%lu) to file '%s'",
             bhfi.nNumberOfLinks, name);
        st_buf->st_nlink = SHRT_MAX;
    }
    else {
        st_buf->st_nlink = (short)bhfi.nNumberOfLinks;
    }

    st_buf->st_size = bhfi.nFileSizeLow;

    /* Get the drive from the name, or use the current drive. */
    if (strlen(name) >= 2 && isALPHA(name[0]) && name[1] == ':')
        drive = toLOWER(name[0]) - 'a' + 1;
    else
        drive = _getdrive();

    st_buf->st_dev = st_buf->st_rdev = (_dev_t)(drive - 1);

    st_buf->st_ino = st_buf->st_uid = st_buf->st_gid = 0;

    return 0;
}

/*
 * Function to get the last access time, last modification time and creation
 * time of a given file.  The values are all returned as UTC-based time_t
 * values, whatever filesystem the file is stored in.
 *
 * This function is based on code written by Jonathan M Gilligan.
 */

static bool Win32UTCFileTime_GetUTCFileTimes(pTHX_ pMY_CXT_ const char *name,
    time_t *u_atime_t, time_t *u_mtime_t, time_t *u_ctime_t)
{
    HANDLE hndl;
    BY_HANDLE_FILE_INFORMATION bhfi;

    /* Use CreateFile() and GetFileInformationByHandle(), rather than
     * FindFirstFile() like Microsoft's stat() does, for the reasons listed in
     * the Win32UTCFileTime_AltStat() function above.  (Do not use the more
     * obvious GetFileTime() to avoid a problem with that caching UTC time
     * values on FAT volumes.) */
    if ((hndl = CreateFile(name, 0, 0, NULL, OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE)
    {
        /* This function is only ever called after a call to Perl's built-in
         * stat() or lstat() function has already succeeded on the same name, so
         * this must just be a directory under a Windows 95 platform on which
         * the FILE_FLAG_BACKUP_SEMANTICS flag does not do the trick.  Set all
         * the file times to zero and return TRUE, like Perl's built-in
         * functions do in this case. */
        *u_atime_t = 0;
        *u_mtime_t = 0;
        *u_ctime_t = 0;
        return TRUE;
    }

    if (!GetFileInformationByHandle(hndl, &bhfi)) {
        Win32UTCFileTime_SetErrStr(aTHX_
            "Can't get file information for file '%s': %s",
            name, WIN32_UTCFILETIME_WIN_ERR_STR
        );
        WIN32_UTCFILETIME_SAVE_ERRS;
        CloseHandle(hndl);
        WIN32_UTCFILETIME_RESTORE_ERRS;
        return FALSE;
    }

    if (!CloseHandle(hndl))
        warn("Can't close file object handle '%lu' for file '%s' after "
             "reading: %s", hndl, name, WIN32_UTCFILETIME_WIN_ERR_STR);

    return(Win32UTCFileTime_FileTimeToUnixTime(aTHX_ aMY_CXT_
                &bhfi.ftLastAccessTime, u_atime_t) &&
            Win32UTCFileTime_FileTimeToUnixTime(aTHX_ aMY_CXT_
                &bhfi.ftLastWriteTime, u_mtime_t) &&
            Win32UTCFileTime_FileTimeToUnixTime(aTHX_ aMY_CXT_
                &bhfi.ftCreationTime, u_ctime_t));
}

/*
 * Function to set the last access time and last modification time of a given
 * file.  The values should both be supplied as UTC-based time_t values,
 * whatever filesystem the file is stored in.
 */

static bool Win32UTCFileTime_SetUTCFileTimes(pTHX_ pMY_CXT_ const char *name,
    const time_t u_atime_t, const time_t u_mtime_t)
{
    int fd;
    bool ret = FALSE;
    HANDLE hndl;
    FILETIME u_atime_ft;
    FILETIME u_mtime_ft;

    /* Try opening the file normally first, like Microsoft's utime(), and hence
     * Perl's win32_utime(), does.  Note that this will fail (with errno EACCES)
     * if name specifies a directory or a read-only file. */
    if ((fd = open(name, O_RDWR | O_BINARY)) < 0) {
        /* If name is a directory then open() will fail.  However, CreateFile()
         * can open directory handles (provided that this is a Windows NT
         * platform and the FILE_FLAG_BACKUP_SEMANTICS flag is passed to allow
         * directory handles to be obtained), so try that instead like Perl's
         * win32_utime() does in case that was the cause of the failure.  This
         * will (and should) still fail on read-only files. */
        if ((hndl = CreateFile(name, GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE)
        {
            Win32UTCFileTime_SetErrStr(aTHX_
                "Can't open file '%s' for updating: %s",
                name, WIN32_UTCFILETIME_WIN_ERR_STR
            );
            return FALSE;
        }
    }
    else if ((hndl = (HANDLE)win32_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) {
        Win32UTCFileTime_SetErrStr(aTHX_
            "Can't get file object handle after opening file '%s' for "
            "updating as file descriptor '%d': %s",
            name, WIN32_UTCFILETIME_SYS_ERR_STR
        );
        WIN32_UTCFILETIME_SAVE_ERRS;
        close(fd);
        WIN32_UTCFILETIME_RESTORE_ERRS;
        return FALSE;
    }

    /* Use NULL for the creation time passed to SetFileTime() like Microsoft's
     * utime() does.  This simply means that the information is not changed.
     * There is no need to retrieve the existing value first in order to reset
     * it like Perl's win32_utime() does. */

    if (Win32UTCFileTime_UnixTimeToFileTime(aTHX_ aMY_CXT_
            u_atime_t, &u_atime_ft) &&
        Win32UTCFileTime_UnixTimeToFileTime(aTHX_ aMY_CXT_
            u_mtime_t, &u_mtime_ft))
    {
        if (!SetFileTime(hndl, NULL, &u_atime_ft, &u_mtime_ft)) {
            Win32UTCFileTime_SetErrStr(aTHX_
                "Can't set file times for file '%s': %s",
                name, WIN32_UTCFILETIME_WIN_ERR_STR
            );
            ret = FALSE;
        }
        else {
            ret = TRUE;
        }
    }
    else {
        ret = FALSE;
    }

    /* Close the file descriptor if it is open.  This closes the underlying file
     * object handle as well, so there is no need to call CloseHandle() too.
     * Otherwise we only have a file object handle open, so just close that
     * directly. */
    if (fd >= 0) {
        if (close(fd) < 0)
            warn("Can't close file descriptor '%d' for file '%s' after "
                 "updating: %s", fd, name, WIN32_UTCFILETIME_SYS_ERR_STR);
    }
    else {
        if (!CloseHandle(hndl))
            warn("Can't close file object handle '%lu' for file '%s' after "
                 "updating: %s", hndl, name, WIN32_UTCFILETIME_WIN_ERR_STR);
    }

    return ret;
}

/*
 * Function to get a message string for the given Win32 API last-error code.
 * Returns a pointer to a buffer containing the string.
 * Note that the buffer is a (thread-safe) static, so subsequent calls to this
 * function from a given thread will overwrite the string.
 *
 * This function is based on the win32_str_os_error() function in Perl (version
 * 5.19.10).
 */

static char *Win32UTCFileTime_StrWinError(pTHX_ pMY_CXT_ DWORD err_num) {
    DWORD len;

    len = FormatMessage(
        FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
        err_num, 0, MY_CXT.err_str, sizeof MY_CXT.err_str, NULL
    );

    if (len > 0) {
        /* Remove the trailing newline (and any other whitespace).  Note that
         * the len returned by FormatMessage() does not include the NUL
         * terminator, so decrement len by one immediately. */
        do {
            --len;
        } while (len > 0 && isSPACE(MY_CXT.err_str[len]));

        /* Increment len by one unless the last character is a period, and then
         * add a NUL terminator, so that any trailing period is also removed. */
        if (MY_CXT.err_str[len] != '.')
            ++len;

        MY_CXT.err_str[len] = '\0';
    }
    else {
        sprintf(MY_CXT.err_str, "Unknown error #0x%lX", err_num);
    }

    return MY_CXT.err_str;
}

/*
 * Function to set the Perl module's $ErrStr variable to the given value.
 */

static void Win32UTCFileTime_SetErrStr(pTHX_ const char *value, ...) {
    va_list args;

    /* Get the Perl module's $ErrStr variable and set an appropriate value in
     * it. */
    va_start(args, value);
    sv_vsetpvf(get_sv("Win32::UTCFileTime::ErrStr", TRUE), value, &args);
    va_end(args);
}

/*============================================================================*/

MODULE = Win32::UTCFileTime PACKAGE = Win32::UTCFileTime        

#===============================================================================
# XS CODE SECTION
#===============================================================================

PROTOTYPES:   ENABLE
VERSIONCHECK: ENABLE

INCLUDE: const-xs.inc

BOOT:
{
    MY_CXT_INIT;

    /* Get the epoch of time_t values as a FILETIME.  This calculation only
     * needs to be done once, and is required by all four functions (stat(),
     * lstat(), alt_stat() and utime()), so we might as well do it here. */
    if (!SystemTimeToFileTime(&win32_utcfiletime_base_st,
            &win32_utcfiletime_base_ft))
        croak("Can't convert base SYSTEMTIME to FILETIME: %s",
              WIN32_UTCFILETIME_WIN_ERR_STR);
}

void
CLONE(...)
    PPCODE:
    {
        MY_CXT_CLONE;
    }

# Private function to expose the Win32UTCFileTime_AltStat() function above.
# This function is based on code taken from the pp_stat() function in Perl
# (version 5.19.10).

void
_alt_stat(file)
    PROTOTYPE: $

    INPUT:
        const char *file

    PPCODE:
    {
        dMY_CXT;
        U32 gimme = GIMME_V;
        struct stat st_buf;

        if (Win32UTCFileTime_AltStat(aTHX_ aMY_CXT_ file, &st_buf) == 0) {
            if (gimme == G_SCALAR) {
                XSRETURN_YES;
            }
            else if (gimme == G_ARRAY) {
                EXTEND(SP, 13);

                mPUSHi((IV)st_buf.st_dev);

                /* ST_INO_SIZE (4) <= IVSIZE (4 or 8) and ST_INO_SIGN == 1 */
                mPUSHu((UV)st_buf.st_ino);

                mPUSHu((UV)st_buf.st_mode);
                mPUSHu((UV)st_buf.st_nlink);

                /* Uid_t_size (4) <= IVSIZE (4 or 8) and Uid_t_sign == -1 */
                mPUSHi((IV)st_buf.st_uid);

                /* Gid_t_size (4) <= IVSIZE (4 or 8) and Gid_t_sign == -1 */
                mPUSHi((IV)st_buf.st_gid);

                /* USE_STAT_RDEV is defined */
                mPUSHi((IV)st_buf.st_rdev);

                /* Off_t_size (4 or 8) <= IVSIZE (4 or 8) */
                mPUSHi((IV)st_buf.st_size);

                /* BIG_TIME not defined */
                mPUSHi((IV)st_buf.st_atime);
                mPUSHi((IV)st_buf.st_mtime);
                mPUSHi((IV)st_buf.st_ctime);
                
                /* USE_STAT_BLOCKS is not defined */
                PUSHs(newSVpvs_flags("", SVs_TEMP));
                PUSHs(newSVpvs_flags("", SVs_TEMP));

                XSRETURN(13);
            }
            else {
                XSRETURN_EMPTY;
            }
        }
        else {
            XSRETURN_EMPTY;
        }
    }

# Private function to expose the Win32UTCFileTime_GetUTCFileTimes() function
# above.

void
_get_utc_file_times(file)
    PROTOTYPE: $

    INPUT:
        const char *file;

    PPCODE:
    {
        dMY_CXT;
        time_t atime;
        time_t mtime;
        time_t ctime;

        if (Win32UTCFileTime_GetUTCFileTimes(aTHX_ aMY_CXT_
                file, &atime, &mtime, &ctime))
        {
            EXTEND(SP, 3);

            /* BIG_TIME not defined */
            mPUSHi((IV)atime);
            mPUSHi((IV)mtime);
            mPUSHi((IV)ctime);

            XSRETURN(3);
        }
        else {
            XSRETURN_EMPTY;
        }
    }

# Private function to expose the Win32UTCFileTime_SetUTCFileTimes() function
# above.

void
_set_utc_file_times(file, atime, mtime)
    PROTOTYPE: $$$

    INPUT:
        const char *file;
        const time_t atime;
        const time_t mtime;

    PPCODE:
    {
        dMY_CXT;

        if (Win32UTCFileTime_SetUTCFileTimes(aTHX_ aMY_CXT_
                file, atime, mtime)) {
            XSRETURN_YES;
        }
        else {
            XSRETURN_EMPTY;
        }
    }

# Private function to expose the Win32 API function SetErrorMode().

UINT
_set_error_mode(umode)
    PROTOTYPE: $

    INPUT:
        const UINT umode;

    CODE:
        RETVAL = SetErrorMode(umode);

    OUTPUT:
        RETVAL

#===============================================================================