THIS is Christoph's PHYSFS_stat() work.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 15 Feb 2010 14:02:36 -0500
changeset 1054 57f4af811ffb
parent 1053 a277a93ac1aa
child 1055 8974a4a352fd
THIS is Christoph's PHYSFS_stat() work. I've merged some basic ideas from the other patch, which was Indy Sam's work, and cleaned up a few things.
docs/CREDITS.txt
extras/physfs-swig.i
src/archiver_dir.c
src/archiver_grp.c
src/archiver_hog.c
src/archiver_lzma.c
src/archiver_mvl.c
src/archiver_qpak.c
src/archiver_wad.c
src/archiver_zip.c
src/physfs.c
src/physfs.h
src/physfs_internal.h
src/platform_os2.c
src/platform_pocketpc.c
src/platform_posix.c
src/platform_unix.c
src/platform_windows.c
test/test_physfs.c
--- a/docs/CREDITS.txt	Mon Feb 15 09:19:38 2010 -0500
+++ b/docs/CREDITS.txt	Mon Feb 15 14:02:36 2010 -0500
@@ -105,6 +105,10 @@
 Bug fixes:
     Patrice Mandin
 
+PHYSFS_stat() API:
+    Christoph Nelles
+    Indy Sams
+
 Other stuff:
     Your name here! Patches go to icculus@icculus.org ...
 
--- a/extras/physfs-swig.i	Mon Feb 15 09:19:38 2010 -0500
+++ b/extras/physfs-swig.i	Mon Feb 15 14:02:36 2010 -0500
@@ -83,6 +83,8 @@
 %rename(symbolicLinksPermitted) PHYSFS_symbolicLinksPermitted;
 %rename(mount) PHYSFS_mount;
 %rename(getMountPoint) PHYSFS_getMountPoint;
+%rename(Stat) PHYSFS_Stat;   /* !!! FIXME: case insensitive script languages? */
+%rename(stat) PHYSFS_stat;
 #endif  /* SWIGPERL */
 
 %include "../src/physfs.h"
--- a/src/archiver_dir.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_dir.c	Mon Feb 15 14:02:36 2010 -0500
@@ -243,6 +243,18 @@
 } /* DIR_dirClose */
 
 
+static int DIR_stat(fvoid *opaque, const char *name, int *exists,
+                    PHYSFS_Stat *stat)
+{
+    char *d = __PHYSFS_platformCvtToDependent((char *) opaque, name, NULL);
+    int retval = 0;
+
+    BAIL_IF_MACRO(d == NULL, NULL, 0);
+    retval = __PHYSFS_platformStat(d, exists, stat);
+    allocator.Free(d);
+    return retval;
+} /* DIR_stat */
+
 
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_DIR =
 {
@@ -253,7 +265,6 @@
 };
 
 
-
 const PHYSFS_Archiver __PHYSFS_Archiver_DIR =
 {
     &__PHYSFS_ArchiveInfo_DIR,
@@ -276,7 +287,8 @@
     DIR_tell,               /* tell() method           */
     DIR_seek,               /* seek() method           */
     DIR_fileLength,         /* fileLength() method     */
-    DIR_fileClose           /* fileClose() method      */
+    DIR_fileClose,          /* fileClose() method      */
+    DIR_stat                /* stat() method           */
 };
 
 /* end of dir.c ... */
--- a/src/archiver_grp.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_grp.c	Mon Feb 15 14:02:36 2010 -0500
@@ -316,7 +316,7 @@
 } /* GRP_enumerateFiles */
 
 
-static GRPentry *grp_find_entry(GRPinfo *info, const char *name)
+static GRPentry *grp_find_entry(const GRPinfo *info, const char *name)
 {
     char *ptr = strchr(name, '.');
     GRPentry *a = info->entries;
@@ -435,6 +435,27 @@
 } /* GRP_mkdir */
 
 
+static int GRP_stat(fvoid *opaque, const char *filename, int *exists,
+                    PHYSFS_Stat *stat)
+{
+    const GRPinfo *info = (const GRPinfo *) opaque;
+    const GRPentry *entry = grp_find_entry(info, filename);
+
+    *exists = (entry != 0);
+    if (!entry)
+        return 0;
+
+    stat->filesize = entry->size;
+    stat->filetype = PHYSFS_FILETYPE_REGULAR;
+    stat->modtime = info->last_mod_time;
+    stat->createtime = info->last_mod_time;
+    stat->accesstime = -1;
+    stat->readonly = 1;
+
+    return 0;
+} /* GRP_stat */
+
+
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_GRP =
 {
     "GRP",
@@ -466,7 +487,8 @@
     GRP_tell,               /* tell() method           */
     GRP_seek,               /* seek() method           */
     GRP_fileLength,         /* fileLength() method     */
-    GRP_fileClose           /* fileClose() method      */
+    GRP_fileClose,          /* fileClose() method      */
+    GRP_stat                /* stat() method           */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_GRP */
--- a/src/archiver_hog.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_hog.c	Mon Feb 15 14:02:36 2010 -0500
@@ -355,7 +355,7 @@
 } /* HOG_enumerateFiles */
 
 
-static HOGentry *hog_find_entry(HOGinfo *info, const char *name)
+static HOGentry *hog_find_entry(const HOGinfo *info, const char *name)
 {
     char *ptr = strchr(name, '.');
     HOGentry *a = info->entries;
@@ -474,6 +474,27 @@
 } /* HOG_mkdir */
 
 
+static int HOG_stat(fvoid *opaque, const char *filename, int *exists,
+                    PHYSFS_Stat *stat)
+{
+    const HOGinfo *info = (const HOGinfo *) opaque;
+    const HOGentry *entry = hog_find_entry(info, filename);
+
+    *exists = (entry != 0);
+    if (!entry)
+        return 0;
+
+    stat->filesize = entry->size;
+    stat->filetype = PHYSFS_FILETYPE_REGULAR;
+    stat->modtime = info->last_mod_time;
+    stat->createtime = info->last_mod_time;
+    stat->accesstime = -1;
+    stat->readonly = 1;
+
+    return 0;
+} /* HOG_stat */
+
+
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_HOG =
 {
     "HOG",
@@ -505,7 +526,8 @@
     HOG_tell,               /* tell() method           */
     HOG_seek,               /* seek() method           */
     HOG_fileLength,         /* fileLength() method     */
-    HOG_fileClose           /* fileClose() method      */
+    HOG_fileClose,          /* fileClose() method      */
+    HOG_stat                /* stat() method           */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_HOG */
--- a/src/archiver_lzma.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_lzma.c	Mon Feb 15 14:02:36 2010 -0500
@@ -207,7 +207,7 @@
 /*
  * Find entry 'name' in 'archive'
  */
-static LZMAfile * lzma_find_file(LZMAarchive *archive, const char *name)
+static LZMAfile * lzma_find_file(const LZMAarchive *archive, const char *name)
 {
     LZMAfile *file = bsearch(name, archive->files, archive->db.Database.NumFiles, sizeof(*archive->files), lzma_file_cmp_stdlib); /* FIXME: Should become __PHYSFS_search!!! */
 
@@ -695,6 +695,42 @@
     BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
 } /* LZMA_mkdir */
 
+static int LZMA_stat(fvoid *opaque, const char *filename, int *exists,
+                     PHYSFS_Stat *stat)
+{
+    const LZMAarchive *archive = (const LZMAarchive *) opaque;
+    const LZMAfile *file = lzma_find_file(archive, filename);
+
+    *exists = (file != 0);
+    if (!file)
+        return 0;
+
+    if(file->item->IsDirectory)
+    {
+        stat->filesize = 0;
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+    } /* if */
+    else
+    {
+        stat->filesize = (PHYSFS_sint64) file->item->Size;
+        stat->filetype = PHYSFS_FILETYPE_REGULAR;
+    } /* else */
+
+    /* !!! FIXME: the 0's should be -1's? */
+    if (file->item->IsLastWriteTimeDefined)
+        stat->modtime = lzma_filetime_to_unix_timestamp(&file->item->LastWriteTime);
+    else
+        stat->modtime = 0;
+
+    /* real create and accesstype are currently not in the lzma SDK */
+    stat->createtime = stat->modtime;
+    stat->accesstime = 0;
+
+    stat->readonly = 1;  /* 7zips are always read only */
+
+    return 0;
+} /* LZMA_stat */
+
 
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_LZMA =
 {
@@ -727,7 +763,8 @@
     LZMA_tell,               /* tell() method           */
     LZMA_seek,               /* seek() method           */
     LZMA_fileLength,         /* fileLength() method     */
-    LZMA_fileClose           /* fileClose() method      */
+    LZMA_fileClose,          /* fileClose() method      */
+    LZMA_stat         /* stat() method    */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_7Z */
--- a/src/archiver_mvl.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_mvl.c	Mon Feb 15 14:02:36 2010 -0500
@@ -312,7 +312,7 @@
 } /* MVL_enumerateFiles */
 
 
-static MVLentry *mvl_find_entry(MVLinfo *info, const char *name)
+static MVLentry *mvl_find_entry(const MVLinfo *info, const char *name)
 {
     char *ptr = strchr(name, '.');
     MVLentry *a = info->entries;
@@ -431,6 +431,27 @@
 } /* MVL_mkdir */
 
 
+static int MVL_stat(fvoid *opaque, const char *filename, int *exists,
+                    PHYSFS_Stat *stat)
+{
+    const MVLinfo *info = (const MVLinfo *) opaque;
+    const MVLentry *entry = mvl_find_entry(info, filename);
+
+    *exists = (entry != 0);
+    if (!entry)
+        return 0;
+
+    stat->filesize = entry->size;
+    stat->filetype = PHYSFS_FILETYPE_REGULAR;
+    stat->modtime = info->last_mod_time;
+    stat->createtime = info->last_mod_time;
+    stat->accesstime = 0;
+    stat->readonly = 1;
+
+    return 0;
+} /* MVL_stat */
+
+
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_MVL =
 {
     "MVL",
@@ -462,7 +483,8 @@
     MVL_tell,               /* tell() method           */
     MVL_seek,               /* seek() method           */
     MVL_fileLength,         /* fileLength() method     */
-    MVL_fileClose           /* fileClose() method      */
+    MVL_fileClose,          /* fileClose() method      */
+    MVL_stat                /* stat() method           */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_MVL */
--- a/src/archiver_qpak.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_qpak.c	Mon Feb 15 14:02:36 2010 -0500
@@ -445,7 +445,8 @@
  *  notation. Directories don't have QPAKentries associated with them, but 
  *  (*isDir) will be set to non-zero if a dir was hit.
  */
-static QPAKentry *qpak_find_entry(QPAKinfo *info, const char *path, int *isDir)
+static QPAKentry *qpak_find_entry(const QPAKinfo *info, const char *path,
+                                  int *isDir)
 {
     QPAKentry *a = info->entries;
     PHYSFS_sint32 pathlen = strlen(path);
@@ -590,6 +591,37 @@
 } /* QPAK_mkdir */
 
 
+static int QPAK_stat(fvoid *opaque, const char *filename, int *exists,
+                     PHYSFS_Stat *stat)
+{
+    int isDir = 0;
+    const QPAKinfo *info = (const QPAKinfo *) opaque;
+    const QPAKentry *entry = qpak_find_entry(info, filename, &isDir);
+
+    *exists = ((isDir) || (entry != NULL));
+    if (!exists)
+        return 0;
+
+    if (isDir)
+    {
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+        stat->filesize = 0;
+    } /* if */
+    else
+    {
+        stat->filetype = PHYSFS_FILETYPE_REGULAR;
+        stat->filesize = entry->size;
+    } /* else */
+
+    stat->modtime = info->last_mod_time;
+    stat->createtime = info->last_mod_time;
+    stat->accesstime = 0;
+    stat->readonly = 1;
+
+    return 0;
+} /* QPAK_stat */
+
+
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_QPAK =
 {
     "PAK",
@@ -621,7 +653,8 @@
     QPAK_tell,               /* tell() method           */
     QPAK_seek,               /* seek() method           */
     QPAK_fileLength,         /* fileLength() method     */
-    QPAK_fileClose           /* fileClose() method      */
+    QPAK_fileClose,          /* fileClose() method      */
+    QPAK_stat                /* stat() method           */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_QPAK */
--- a/src/archiver_wad.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_wad.c	Mon Feb 15 14:02:36 2010 -0500
@@ -361,7 +361,7 @@
 } /* WAD_enumerateFiles */
 
 
-static WADentry *wad_find_entry(WADinfo *info, const char *name)
+static WADentry *wad_find_entry(const WADinfo *info, const char *name)
 {
     WADentry *a = info->entries;
     PHYSFS_sint32 lo = 0;
@@ -494,6 +494,27 @@
 } /* WAD_mkdir */
 
 
+static int WAD_stat(fvoid *opaque, const char *filename, int *exists,
+                    PHYSFS_Stat *stat)
+{
+    const WADinfo *info = (const WADinfo *) opaque;
+    const WADentry *entry = wad_find_entry(info, filename);
+
+    *exists = (entry != 0);
+    if (!entry)
+        return 0;
+
+    stat->filesize = entry->size;
+    stat->filetype = PHYSFS_FILETYPE_REGULAR;
+    stat->accesstime = 0;
+    stat->modtime = ((WADinfo *) opaque)->last_mod_time;
+    stat->createtime = ((WADinfo *) opaque)->last_mod_time;
+    stat->readonly = 1; /* WADs are always readonly */
+
+    return 0;
+} /* WAD_stat */
+
+
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_WAD =
 {
     "WAD",
@@ -525,7 +546,8 @@
     WAD_tell,               /* tell() method           */
     WAD_seek,               /* seek() method           */
     WAD_fileLength,         /* fileLength() method     */
-    WAD_fileClose           /* fileClose() method      */
+    WAD_fileClose,          /* fileClose() method      */
+    WAD_stat                /* stat() method           */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_WAD */
--- a/src/archiver_zip.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/archiver_zip.c	Mon Feb 15 14:02:36 2010 -0500
@@ -509,7 +509,8 @@
  *  notation. Directories don't have ZIPentries associated with them, but 
  *  (*isDir) will be set to non-zero if a dir was hit.
  */
-static ZIPentry *zip_find_entry(ZIPinfo *info, const char *path, int *isDir)
+static ZIPentry *zip_find_entry(const ZIPinfo *info, const char *path,
+                                int *isDir)
 {
     ZIPentry *a = info->entries;
     PHYSFS_sint32 pathlen = strlen(path);
@@ -1406,6 +1407,44 @@
 } /* ZIP_mkdir */
 
 
+static int ZIP_stat(fvoid *opaque, const char *filename, int *exists,
+                    PHYSFS_Stat *stat)
+{
+    int isDir = 0;
+    const ZIPinfo *info = (const ZIPinfo *) opaque;
+    const ZIPentry *entry = zip_find_entry(info, filename, &isDir);
+
+    *exists = isDir || (entry != 0);
+    if (!*exists)
+        return 0;
+
+    if (isDir)
+    {
+        stat->filesize = 0;
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+    } /* if */
+
+    else if (zip_entry_is_symlink(entry))
+    {
+        stat->filesize = 0;
+        stat->filetype = PHYSFS_FILETYPE_SYMLINK;
+    } /* else if */
+
+    else
+    {
+        stat->filesize = entry->uncompressed_size;
+        stat->filetype = PHYSFS_FILETYPE_REGULAR;
+    } /* else */
+
+    stat->modtime = ((entry) ? entry->last_mod_time : 0);
+    stat->createtime = stat->modtime;
+    stat->accesstime = 0;
+    stat->readonly = 1; /* .zip files are always read only */
+
+    return 0;
+} /* ZIP_stat */
+
+
 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_ZIP =
 {
     "ZIP",
@@ -1437,7 +1476,8 @@
     ZIP_tell,               /* tell() method           */
     ZIP_seek,               /* seek() method           */
     ZIP_fileLength,         /* fileLength() method     */
-    ZIP_fileClose           /* fileClose() method      */
+    ZIP_fileClose,          /* fileClose() method      */
+    ZIP_stat                /* stat() method           */
 };
 
 #endif  /* defined PHYSFS_SUPPORTS_ZIP */
--- a/src/physfs.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/physfs.c	Mon Feb 15 14:02:36 2010 -0500
@@ -2144,6 +2144,56 @@
 } /* PHYSFS_flush */
 
 
+int PHYSFS_stat(const char *_fname, PHYSFS_Stat *stat)
+{
+    int retval = 0;
+    char *fname;
+    size_t len;
+
+    BAIL_IF_MACRO(_fname == NULL, ERR_INVALID_ARGUMENT, -1);
+    BAIL_IF_MACRO(stat == NULL, ERR_INVALID_ARGUMENT, -1);
+    len = strlen(_fname) + 1;
+    fname = (char *) __PHYSFS_smallAlloc(len);
+    BAIL_IF_MACRO(fname == NULL, ERR_OUT_OF_MEMORY, -1);
+
+    /* !!! FIXME: what should this be set to if we fail completely? */
+    memset(stat, '\0', sizeof (PHYSFS_Stat));
+
+    if (sanitizePlatformIndependentPath(_fname, fname))
+    {
+        if (*fname == '\0')
+        {
+            stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+            stat->readonly = !writeDir; /* Writeable if we have a writeDir */
+            retval = 0;
+        } /* if */
+        else
+        {
+            DirHandle *i;
+            int exists = 0;
+            __PHYSFS_platformGrabMutex(stateLock);
+            for (i = searchPath; ((i != NULL) && (!exists)); i = i->next)
+            {
+                char *arcfname = fname;
+                exists = partOfMountPoint(i, arcfname);
+                if (exists)
+                    retval = 1; /* !!! FIXME: What's the right value? */
+                else if (verifyPath(i, &arcfname, 0))
+                {
+                    stat->readonly = !(writeDir &&
+                                 (strcmp(writeDir->dirName, i->dirName) == 0));
+                    retval = i->funcs->stat(i->opaque, arcfname, &exists, stat);
+                } /* else if */
+            } /* for */
+            __PHYSFS_platformReleaseMutex(stateLock);
+        } /* else */
+    } /* if */
+
+    __PHYSFS_smallFree(fname);
+    return retval;
+} /* PHYSFS_stat */
+
+
 int PHYSFS_setAllocator(const PHYSFS_Allocator *a)
 {
     BAIL_IF_MACRO(initialized, ERR_IS_INITIALIZED, 0);
--- a/src/physfs.h	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/physfs.h	Mon Feb 15 14:02:36 2010 -0500
@@ -417,7 +417,6 @@
 } PHYSFS_Version;
 
 
-
 #ifndef SWIG  /* not available from scripting languages. */
 
 #ifndef DOXYGEN_SHOULD_IGNORE_THIS
@@ -2462,6 +2461,60 @@
 
 #endif  /* SWIG */
 
+/**
+ * \enum PHYSFS_FileType
+ * \brief Type of a File
+ *
+ * Possible types of a file.
+ *
+ * \sa PHYSFS_stat
+ */
+typedef enum PHYSFS_FileType
+{
+	PHYSFS_FILETYPE_REGULAR, /**< a normal file */
+	PHYSFS_FILETYPE_DIRECTORY, /**< a directory */
+	PHYSFS_FILETYPE_SYMLINK, /**< a symlink */
+	PHYSFS_FILETYPE_OTHER /**< something completely different like a device */
+} PHYSFS_FileType;
+
+/**
+ * \struct PHYSFS_Stat
+ * \brief Meta data for a file or directory
+ *
+ * Container for various meta data about a file in the virtual file system.
+ * PHYSFS_stat() uses this structure for returning the information. The time
+ * data will be either a real timestamp or -1 if there is none. So every value
+ * is at least epoch. The FileSize is only valid for real files. And the
+ * readonly tells you whether when you open a file for writing you are writing
+ * to the same file as if you were opening it, given you have enough
+ * filesystem rights to do that.
+ *
+ * \sa PHYSFS_stat
+ * \sa PHYSFS_FileType
+ */
+typedef struct PHYSFS_Stat
+{
+	PHYSFS_sint64 filesize; /**< size in bytes, -1 for non-files and unknown */
+	PHYSFS_sint64 modtime;  /**< same value as PHYSFS_getLastModTime() */
+	PHYSFS_sint64 createtime; /**< like modtime, but for file creation time */
+	PHYSFS_sint64 accesstime; /**< like modtime, but for file access time */
+	PHYSFS_FileType filetype; /**< File? Directory? Symlink? */
+	int readonly; /**< non-zero if read only, zero if writable. */
+} PHYSFS_Stat;
+
+/**
+ * \fn int PHYSFS_stat(const char *fname, PHYSFS_Stat *stat)
+ * \brief Get various information about a directory or a file.
+ *
+ * Obtain various information about a file or directory from the meta data.
+ *
+ *    \param fname filename to check, in platform-indepedent notation.
+ *    \param stat pointer to structure to fill in with data about (fname).
+ *   \return 0 on success, non-zero on error.
+ *
+ * \sa PHYSFS_Stat
+ */
+PHYSFS_DECL int PHYSFS_stat(const char *fname, PHYSFS_Stat *stat);
 
 /* Everything above this line is part of the PhysicsFS 2.1 API. */
 
--- a/src/physfs_internal.h	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/physfs_internal.h	Mon Feb 15 14:02:36 2010 -0500
@@ -937,6 +937,13 @@
          *  file. On failure, call __PHYSFS_setError().
          */
     int (*fileClose)(fvoid *opaque);
+
+        /* !!! FIXME: return info (may be|is) wrong.
+         * Obtain basic file metadata.
+         *  Returns non-zero on success, zero if can't close
+         *  file. On failure, call __PHYSFS_setError().
+         */
+    int (*stat)(fvoid *opaque, const char *fn, int *exists, PHYSFS_Stat *stat);
 } PHYSFS_Archiver;
 
 
@@ -1244,6 +1251,13 @@
  */
 PHYSFS_sint64 __PHYSFS_platformFileLength(void *handle);
 
+
+/*
+ * !!! FIXME: comment me.
+ */
+int __PHYSFS_platformStat(const char *fn, int *exists, PHYSFS_Stat *stat);
+
+
 /*
  * Determine if a file is at EOF. (opaque) should be cast to whatever data
  *  type your platform uses.
--- a/src/platform_os2.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/platform_os2.c	Mon Feb 15 14:02:36 2010 -0500
@@ -631,6 +631,25 @@
 } /* __PHYSFS_platformDelete */
 
 
+/* Convert to a format PhysicsFS can grok... */
+PHYSFS_sint64 os2TimeToUnixTime(const FDATE *date, const FTIME *time)
+{
+    struct tm tm;
+
+    tm.tm_sec = ((PHYSFS_uint32) time->.twosecs) * 2;
+    tm.tm_min = time->minutes;
+    tm.tm_hour = time->hours;
+    tm.tm_mday = date->day;
+    tm.tm_mon = date->month;
+    tm.tm_year = ((PHYSFS_uint32) date->year) + 80;
+    tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
+    tm.tm_yday = -1;
+    tm.tm_isdst = -1;
+
+    return (PHYSFS_sint64) mktime(&tm);
+} /* os2TimeToUnixTime */
+
+
 PHYSFS_sint64 __PHYSFS_platformGetLastModTime(const char *_fname)
 {
     const unsigned char *fname = (const unsigned char *) _fname;
@@ -640,24 +659,63 @@
     APIRET rc = DosQueryPathInfo(fname, FIL_STANDARD, &fs, sizeof (fs));
     BAIL_IF_MACRO(os2err(rc) != NO_ERROR, NULL, -1);
 
-    /* Convert to a format that mktime() can grok... */
-    tm.tm_sec = ((PHYSFS_uint32) fs.ftimeLastWrite.twosecs) * 2;
-    tm.tm_min = fs.ftimeLastWrite.minutes;
-    tm.tm_hour = fs.ftimeLastWrite.hours;
-    tm.tm_mday = fs.fdateLastWrite.day;
-    tm.tm_mon = fs.fdateLastWrite.month;
-    tm.tm_year = ((PHYSFS_uint32) fs.fdateLastWrite.year) + 80;
-    tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
-    tm.tm_yday = -1;
-    tm.tm_isdst = -1;
+    /* Convert to a format PhysicsFS can grok... */
+    retval = os2TimeToUnixTime(&fs.fdateLastWrite, &fs.ftimeLastWrite);
 
-    /* Convert to a format PhysicsFS can grok... */
-    retval = (PHYSFS_sint64) mktime(&tm);
     BAIL_IF_MACRO(retval == -1, strerror(errno), -1);
     return retval;
 } /* __PHYSFS_platformGetLastModTime */
 
 
+static int __PHYSFS_platformStat(const char *_fname, int *exists,
+                                 PHYSFS_Stat *stat)
+{
+    struct tm tm;
+    FILESTATUS3 fs;
+    const unsigned char *fname = (const unsigned char *) _fname;
+    const APIRET rc = DosQueryPathInfo(fname, FIL_STANDARD, &fs, sizeof (fs));
+
+    if (rc != NO_ERROR)
+    {
+        if (rc == ERROR_PATH_NOT_FOUND)
+        {
+            *exists = 0;
+            return 0;
+        } /* if */
+        BAIL_MACRO(get_os2_error_string(rc), -1);
+    } /* if */
+
+    *exists = 1;
+
+    if (fs.attrFile & FILE_DIRECTORY)
+    {
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+        stat->filesize = 0;
+    } /* if */
+    else
+    {
+        stat->filetype = PHYSFS_FILETYPE_REGULAR;
+        stat->filesize = fs.cbFile;
+    } /* else */
+
+    stat->modtime = os2TimeToUnixTime(&fs.fdateLastWrite, &fs.ftimeLastWrite);
+    if (stat->modtime < 0)
+        stat->modtime = 0;
+
+    stat->accesstime = os2TimeToUnixTime(&fs.fdateLastAccess, &fs.ftimeLastAccess);
+    if (stat->accesstime < 0)
+        stat->accesstime = 0;
+
+    stat->createtime = os2TimeToUnixTime(&fs.fdateCreation, &fs.ftimeCreation);
+    if (stat->createtime < 0)
+        stat->createtime = 0;
+
+    stat->readonly = ((fs.attrFile & FILE_READONLY) == FILE_READONLY);
+
+    return 0;
+} /* __PHYSFS_platformStat */
+
+
 void *__PHYSFS_platformGetThreadID(void)
 {
     PTIB ptib;
--- a/src/platform_pocketpc.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/platform_pocketpc.c	Mon Feb 15 14:02:36 2010 -0500
@@ -561,6 +561,103 @@
 } /* __PHYSFS_platformDelete */
 
 
+/* Shamelessly copied from platform_windows.c */
+static PHYSFS_sint64 FileTimeToPhysfsTime(const FILETIME *ft)
+{
+    SYSTEMTIME st_utc;
+    SYSTEMTIME st_localtz;
+    TIME_ZONE_INFORMATION tzi;
+    DWORD tzid;
+    PHYSFS_sint64 retval;
+    struct tm tm;
+
+    BAIL_IF_MACRO(!FileTimeToSystemTime(ft, &st_utc), winApiStrError(), -1);
+    tzid = GetTimeZoneInformation(&tzi);
+    BAIL_IF_MACRO(tzid == TIME_ZONE_ID_INVALID, winApiStrError(), -1);
+
+    /* (This API is unsupported and fails on non-NT systems. */
+    if (!SystemTimeToTzSpecificLocalTime(&tzi, &st_utc, &st_localtz))
+    {
+        /* do it by hand. Grumble... */
+        ULARGE_INTEGER ui64;
+        FILETIME new_ft;
+        ui64.LowPart = ft->dwLowDateTime;
+        ui64.HighPart = ft->dwHighDateTime;
+
+        if (tzid == TIME_ZONE_ID_STANDARD)
+            tzi.Bias += tzi.StandardBias;
+        else if (tzid == TIME_ZONE_ID_DAYLIGHT)
+            tzi.Bias += tzi.DaylightBias;
+
+        /* convert from minutes to 100-nanosecond increments... */
+        ui64.QuadPart -= (((LONGLONG) tzi.Bias) * (600000000));
+
+        /* Move it back into a FILETIME structure... */
+        new_ft.dwLowDateTime = ui64.LowPart;
+        new_ft.dwHighDateTime = ui64.HighPart;
+
+        /* Convert to something human-readable... */
+        if (!FileTimeToSystemTime(&new_ft, &st_localtz))
+            BAIL_MACRO(winApiStrError(), -1);
+    } /* if */
+
+    /* Convert to a format that mktime() can grok... */
+    tm.tm_sec = st_localtz.wSecond;
+    tm.tm_min = st_localtz.wMinute;
+    tm.tm_hour = st_localtz.wHour;
+    tm.tm_mday = st_localtz.wDay;
+    tm.tm_mon = st_localtz.wMonth - 1;
+    tm.tm_year = st_localtz.wYear - 1900;
+    tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
+    tm.tm_yday = -1;
+    tm.tm_isdst = -1;
+
+    /* Convert to a format PhysicsFS can grok... */
+    retval = (PHYSFS_sint64) mktime(&tm);
+    BAIL_IF_MACRO(retval == -1, strerror(errno), -1);
+    return retval;
+} /* FileTimeToPhysfsTime */
+
+
+int __PHYSFS_platformStat(const char *filename, int *exists, PHYSFS_Stat *stat)
+{
+    WIN32_FIND_DATA winstat;
+    const HANDLE searchhandle = FindFirstFile(filename, &winstat);
+
+    if (searchhandle == INVALID_HANDLE_VALUE)  /* call failed? */
+    {
+        /* !!! FIXME: FindFirstFile() doesn't set errno. Use GetLastError()?. */
+        if (errno == ERROR_FILE_NOT_FOUND)
+        {
+            *exists = 0;
+            return 0;
+        } /* if */
+        BAIL_MACRO(win32strerror, -1);
+    } /* if */
+
+    FindClose(searchhandle);  /* close handle, not needed anymore */
+
+    *exists = 1;
+
+    if(winstat.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+    else if (winstat.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_ROMMODULE))
+        stat->filetype = PHYSFS_FILETYPE_OTHER;
+    else
+        stat->filetype = PHYSFS_FILETYPE_OTHER;  /* !!! FIXME: _REGULAR? */
+
+    if (stat->filetype == PHYSFS_FILETYPE_REGULAR)
+        stat->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
+
+    stat->modtime = FileTimeToPhysfsTime(&winstat.ftLastWriteTime);
+    stat->accesstime = FileTimeToPhysfsTime(&winstat.ftLastAccessTime);
+    stat->createtime = FileTimeToPhysfsTime(&winstat.ftCreationTime);
+    stat->readonly = ((winstat.dwFileAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_INROM)) != 0);
+
+    return 0;
+} /* __PHYSFS_platformStat */
+
+
 /*
  * !!! FIXME: why aren't we using Critical Sections instead of Mutexes?
  * !!! FIXME:  mutexes on Windows are for cross-process sync. CritSects are
@@ -568,7 +665,7 @@
  */
 void *__PHYSFS_platformCreateMutex(void)
 {
-    return (void * CreateMutex(NULL, FALSE, NULL));
+    return ((void *) CreateMutex(NULL, FALSE, NULL));
 } /* __PHYSFS_platformCreateMutex */
 
 
--- a/src/platform_posix.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/platform_posix.c	Mon Feb 15 14:02:36 2010 -0500
@@ -405,6 +405,52 @@
     return statbuf.st_mtime;
 } /* __PHYSFS_platformGetLastModTime */
 
+
+int __PHYSFS_platformStat(const char *filename, int *exists, PHYSFS_Stat *st)
+{
+    struct stat statbuf;
+
+    /* !!! FIXME: lstat()? */
+    if (stat(filename, &statbuf))
+    {
+        if (errno == ENOENT)
+        {
+            *exists = 0;
+            return 0;
+        } /* if */
+        else
+        {
+            BAIL_MACRO(strerror(errno), -1);
+        } /* else */
+    } /* if */
+
+    if (S_ISREG(statbuf.st_mode))
+    {
+        st->filetype = PHYSFS_FILETYPE_REGULAR;
+        st->filesize = statbuf.st_size;
+    } /* if */
+
+    else if(S_ISDIR(statbuf.st_mode))
+    {
+        st->filetype = PHYSFS_FILETYPE_DIRECTORY;
+        st->filesize = 0;
+    } /* else if */
+
+    else
+    {
+        st->filetype = PHYSFS_FILETYPE_OTHER;
+        st->filesize = statbuf.st_size;
+    } /* else */
+
+    st->modtime = statbuf.st_mtime;
+    st->createtime = statbuf.st_ctime;
+    st->accesstime = statbuf.st_atime;
+
+    /* !!! FIXME: maybe we should just report full permissions? */
+    st->readonly = access(filename, W_OK);
+    return 0;
+} /* __PHYSFS_platformStat */
+
 #endif  /* PHYSFS_PLATFORM_POSIX */
 
 /* end of posix.c ... */
--- a/src/platform_unix.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/platform_unix.c	Mon Feb 15 14:02:36 2010 -0500
@@ -24,7 +24,7 @@
 #include <time.h>
 #include <errno.h>
 
-#if (!defined PHYSFS_NO_THREAD_SUPPORT)
+#if (!defined PHYSFS_NO_PTHREADS_SUPPORT)
 #include <pthread.h>
 #endif
 
@@ -345,7 +345,7 @@
 } /* __PHYSFS_platformSetDefaultAllocator */
 
 
-#if (defined PHYSFS_NO_THREAD_SUPPORT)
+#if (defined PHYSFS_NO_PTHREADS_SUPPORT)
 
 void *__PHYSFS_platformGetThreadID(void) { return ((void *) 0x0001); }
 void *__PHYSFS_platformCreateMutex(void) { return ((void *) 0x0001); }
@@ -429,7 +429,7 @@
     } /* if */
 } /* __PHYSFS_platformReleaseMutex */
 
-#endif /* !PHYSFS_NO_THREAD_SUPPORT */
+#endif /* !PHYSFS_NO_PTHREADS_SUPPORT */
 
 #endif /* PHYSFS_PLATFORM_UNIX */
 
--- a/src/platform_windows.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/src/platform_windows.c	Mon Feb 15 14:02:36 2010 -0500
@@ -1390,6 +1390,127 @@
 } /* __PHYSFS_platformGetLastModTime */
 
 
+static int __PHYSFS_platformStatOldWay(const char *filename, int *exists,
+                                       PHYSFS_Stat *stat)
+{
+    WIN32_FIND_DATA winstat;
+    const HANDLE searchhandle = FindFirstFile(filename, &winstat);
+
+    if (searchhandle == INVALID_HANDLE_VALUE)  /* call failed? */
+    {
+        /* !!! FIXME: not errno...try GetLastError() */
+        if (errno == ERROR_FILE_NOT_FOUND)
+        {
+            *exists = 0;
+            return 0;
+        } /* if */
+        BAIL_MACRO(strerror(errno), -1);
+    } /* if */
+
+    FindClose(searchhandle); /* close handle, not needed anymore */
+
+    *exists = 1;
+
+    if (winstat.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+    else if (winstat.dwFileAttributes  & FILE_ATTRIBUTE_OFFLINE)
+        stat->filetype = PHYSFS_FILETYPE_OTHER;
+    else
+        stat->filetype = PHYSFS_FILETYPE_OTHER; /* !!! FIXME: _REGULAR? */
+
+    if (stat->filetype == PHYSFS_FILETYPE_REGULAR)
+        stat->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
+
+    stat->modtime = FileTimeToPhysfsTime(&winstat.ftLastWriteTime);
+    stat->accesstime = FileTimeToPhysfsTime(&winstat.ftLastAccessTime);
+    stat->createtime = FileTimeToPhysfsTime(&winstat.ftCreationTime);
+    stat->readonly = ((winstat.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
+
+    return 0;
+} /* __PHYSFS_platformStatOldWay */
+
+
+static int __PHYSFS_platformStatNewWay(const char *filename, int *exists,
+                                       PHYSFS_Stat *stat)
+{
+    WIN32_FILE_ATTRIBUTE_DATA winstat;
+    WCHAR *wstr = NULL;
+    BOOL rc = 0;
+
+    UTF8_TO_UNICODE_STACK_MACRO(wstr, filename);
+    if (!wstr)  /* maybe better luck in the old way... */
+        return __PHYSFS_platformStatOldWay(filename, exists, stat);
+
+    if (pGetFileAttributesExW)
+        rc = pGetFileAttributesExW(wstr, GetFileExInfoStandard, &winstat);
+    else
+    {
+        const int len = (int) (wStrLen(wstr) + 1);
+        char *cp = (char *) __PHYSFS_smallAlloc(len);
+        if (cp)
+        {
+            WideCharToMultiByte(CP_ACP, 0, wstr, len, cp, len, 0, 0);
+            rc = pGetFileAttributesExA(cp, GetFileExInfoStandard, &winstat);
+        } /* if */
+    } /* else */
+
+    __PHYSFS_smallFree(wstr);
+
+    if (!rc)
+    {
+        if (errno == ERROR_FILE_NOT_FOUND)  /* !!! FIXME: errno is wrong */
+        {
+            *exists = 0;
+            return 0;
+        } /* if */
+        else
+        {
+            BAIL_MACRO(strerror(errno), -1);
+        } /* else */
+    } /* if */
+
+    *exists = 1;
+
+    stat->modtime = FileTimeToPhysfsTime(&winstat.ftLastWriteTime);
+    stat->accesstime = FileTimeToPhysfsTime(&winstat.ftLastAccessTime);
+    stat->createtime = FileTimeToPhysfsTime(&winstat.ftCreationTime);
+
+    if(winstat.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+    {
+        stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+        stat->filesize = 0;
+    } /* if */
+
+    else if(winstat.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_DEVICE))
+    {
+        /* !!! FIXME: what are reparse points? */
+        stat->filetype = PHYSFS_FILETYPE_OTHER;
+        /* !!! FIXME: don't rely on this */
+        stat->filesize = 0;
+    } /* else if */
+
+    /* !!! FIXME: check for symlinks on Vista. */
+
+    else
+    {
+        stat->filetype = PHYSFS_FILETYPE_REGULAR;
+        filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
+    } /* else */
+
+    stat->readonly = ((winstat.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
+
+    return 0;
+} /* __PHYSFS_platformStatNewWay */
+
+
+int __PHYSFS_platformStat(const char *filename, int *exists, PHYSFS_Stat *stat)
+{
+    if (pGetFileAttributesExW || pGetFileAttributesExA)
+        return __PHYSFS_platformStatNewWay(filename, exists, stat);
+    return __PHYSFS_platformStatOldWay(filename, exists, stat);
+} /* __PHYSFS_platformStat */
+
+
 /* !!! FIXME: Don't use C runtime for allocators? */
 int __PHYSFS_platformSetDefaultAllocator(PHYSFS_Allocator *a)
 {
--- a/test/test_physfs.c	Mon Feb 15 09:19:38 2010 -0500
+++ b/test/test_physfs.c	Mon Feb 15 14:02:36 2010 -0500
@@ -773,7 +773,6 @@
     return 1;
 } /* cmd_filelength */
 
-
 #define WRITESTR "The cat sat on the mat.\n\n"
 
 static int cmd_append(char *args)
@@ -872,12 +871,13 @@
 } /* cmd_write */
 
 
-static void modTimeToStr(PHYSFS_sint64 modtime, char *modstr, size_t strsize)
+static char* modTimeToStr(PHYSFS_sint64 modtime, char *modstr, size_t strsize)
 {
     time_t t = (time_t) modtime;
     char *str = ctime(&t);
     strncpy(modstr, str, strsize);
     modstr[strsize-1] = '\0';
+    return modstr;
 } /* modTimeToStr */
 
 
@@ -896,6 +896,44 @@
     return 1;
 } /* cmd_getLastModTime */
 
+static int cmd_stat(char *args)
+{
+    PHYSFS_Stat stat;
+    char timestring[65];
+
+    if (*args == '\"')
+    {
+        args++;
+        args[strlen(args) - 1] = '\0';
+    } /* if */
+
+    if(PHYSFS_stat(args, &stat))
+    {
+        printf("failed to stat. Reason [%s].\n", PHYSFS_getLastError());
+        return 1;
+    } /* if */
+
+    printf("Filename: %s\n", args);
+    printf("Size %d\n",(int) stat.filesize);
+
+    if(stat.filetype == PHYSFS_FILETYPE_REGULAR)
+        printf("Type: File\n");
+    else if(stat.filetype == PHYSFS_FILETYPE_DIRECTORY)
+        printf("Type: Directory\n");
+    else if(stat.filetype == PHYSFS_FILETYPE_SYMLINK)
+        printf("Type: Symlink\n");
+    else
+        printf("Type: Unknown\n");
+
+    printf("Created at: %s", modTimeToStr(stat.createtime, timestring, 64));
+    printf("Last modified at: %s", modTimeToStr(stat.modtime, timestring, 64));
+    printf("Last accessed at: %s", modTimeToStr(stat.accesstime, timestring, 64));
+    printf("Readonly: %s\n", stat.readonly ? "true" : "false");
+
+    return 1;
+} /* cmd_filelength */
+
+
 
 /* must have spaces trimmed prior to this call. */
 static int count_args(const char *str)
@@ -959,6 +997,7 @@
     { "issymlink",      cmd_issymlink,      1, "<fileToCheck>"              },
     { "cat",            cmd_cat,            1, "<fileToCat>"                },
     { "filelength",     cmd_filelength,     1, "<fileToCheck>"              },
+    { "stat",           cmd_stat,           1, "<fileToStat>"               },
     { "append",         cmd_append,         1, "<fileToAppend>"             },
     { "write",          cmd_write,          1, "<fileToCreateOrTrash>"      },
     { "getlastmodtime", cmd_getlastmodtime, 1, "<fileToExamine>"            },
@@ -1166,6 +1205,7 @@
     open_history_file();
 
     printf("Enter commands. Enter \"help\" for instructions.\n");
+    fflush(stdout);
 
     do
     {
@@ -1176,6 +1216,7 @@
         buf = (char *) malloc(512);
         memset(buf, '\0', 512);
         printf("> ");
+        fflush(stdout);
         for (i = 0; i < 511; i++)
         {
             int ch = fgetc(stdin);
@@ -1202,6 +1243,7 @@
 #endif
 
         rc = process_command(buf);
+        fflush(stdout);
         if (buf != NULL)
             free(buf);
     } while (rc);