text: /*
   * Posix-esque support routines for PhysicsFS.
   *
   * Please see the file LICENSE.txt in the source's root directory.
   *
   *  This file written by Ryan C. Gordon.
   */
  
  /* !!! FIXME: check for EINTR? */
  
  #define __PHYSICSFS_INTERNAL__
  #include "physfs_platforms.h"
  
  #ifdef PHYSFS_PLATFORM_POSIX
  
  #include <unistd.h>
  #include <ctype.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <pwd.h>
  #include <dirent.h>
  #include <errno.h>
  #include <fcntl.h>
  #include <pthread.h>
  
  #include "physfs_internal.h"
  
  
  static PHYSFS_ErrorCode errcodeFromErrnoError(const int err)
  {
      switch (err)
      {
          case 0: return PHYSFS_ERR_OK;
          case EACCES: return PHYSFS_ERR_PERMISSION;
          case EPERM: return PHYSFS_ERR_PERMISSION;
          case EDQUOT: return PHYSFS_ERR_NO_SPACE;
          case EIO: return PHYSFS_ERR_IO;
          case ELOOP: return PHYSFS_ERR_SYMLINK_LOOP;
          case EMLINK: return PHYSFS_ERR_NO_SPACE;
          case ENAMETOOLONG: return PHYSFS_ERR_BAD_FILENAME;
          case ENOENT: return PHYSFS_ERR_NOT_FOUND;
          case ENOSPC: return PHYSFS_ERR_NO_SPACE;
          case ENOTDIR: return PHYSFS_ERR_NOT_FOUND;
          case EISDIR: return PHYSFS_ERR_NOT_A_FILE;
          case EROFS: return PHYSFS_ERR_READ_ONLY;
          case ETXTBSY: return PHYSFS_ERR_BUSY;
          case EBUSY: return PHYSFS_ERR_BUSY;
          case ENOMEM: return PHYSFS_ERR_OUT_OF_MEMORY;
          case ENOTEMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY;
          default: return PHYSFS_ERR_OS_ERROR;
      } /* switch */
  } /* errcodeFromErrnoError */
  
  
  static inline PHYSFS_ErrorCode errcodeFromErrno(void)
  {
      return errcodeFromErrnoError(errno);
  } /* errcodeFromErrno */
  
  
  static char *getUserDirByUID(void)
  {
      uid_t uid = getuid();
      struct passwd *pw;
      char *retval = NULL;
  
      pw = getpwuid(uid);
      if ((pw != NULL) && (pw->pw_dir != NULL) && (*pw->pw_dir != '\0'))
      {
          const size_t dlen = strlen(pw->pw_dir);
          const size_t add_dirsep = (pw->pw_dir[dlen-1] != '/') ? 1 : 0;
          retval = (char *) allocator.Malloc(dlen + 1 + add_dirsep);
          if (retval != NULL)
          {
              strcpy(retval, pw->pw_dir);
              if (add_dirsep)
              {
                  retval[dlen] = '/';
                  retval[dlen+1] = '\0';
              } /* if */
          } /* if */
      } /* if */
      
      return retval;
  } /* getUserDirByUID */
  
  
  char *__PHYSFS_platformCalcUserDir(void)
  {
  #ifdef PHYSFS_PLATFORM_SWITCH
      return __PHYSFS_switchCalcUserDir();
  #endif
      char *retval = NULL;
      char *envr = getenv("HOME");
  
      /* if the environment variable was set, make sure it's really a dir. */
      if (envr != NULL)
      {
          struct stat statbuf;
          if ((stat(envr, &statbuf) != -1) && (S_ISDIR(statbuf.st_mode)))
          {
              const size_t envrlen = strlen(envr);
              const size_t add_dirsep = (envr[envrlen-1] != '/') ? 1 : 0;
              retval = allocator.Malloc(envrlen + 1 + add_dirsep);
              if (retval)
              {
                  strcpy(retval, envr);
                  if (add_dirsep)
                  {
                      retval[envrlen] = '/';
                      retval[envrlen+1] = '\0';
                  } /* if */
              } /* if */
          } /* if */
      } /* if */
  
      if (retval == NULL)
          retval = getUserDirByUID();
  
      return retval;
  } /* __PHYSFS_platformCalcUserDir */
  
  
  PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
                                 PHYSFS_EnumerateCallback callback,
                                 const char *origdir, void *callbackdata)
  {
      DIR *dir;
      struct dirent *ent;
      PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
  
      dir = opendir(dirname);
      BAIL_IF(dir == NULL, errcodeFromErrno(), PHYSFS_ENUM_ERROR);
  
      while ((retval == PHYSFS_ENUM_OK) && ((ent = readdir(dir)) != NULL))
      {
          const char *name = ent->d_name;
          if (name[0] == '.')  /* ignore "." and ".." */
          {
              if ((name[1] == '\0') || ((name[1] == '.') && (name[2] == '\0')))
                  continue;
          } /* if */
  
          retval = callback(callbackdata, origdir, name);
          if (retval == PHYSFS_ENUM_ERROR)
              PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
      } /* while */
  
      closedir(dir);
  
      return retval;
  } /* __PHYSFS_platformEnumerate */
  
  
  int __PHYSFS_platformMkDir(const char *path)
  {
      const int rc = mkdir(path, S_IRWXU);
      BAIL_IF(rc == -1, errcodeFromErrno(), 0);
      return 1;
  } /* __PHYSFS_platformMkDir */
  
  
  static void *doOpen(const char *filename, int mode)
  {
      const int appending = (mode & O_APPEND);
      int fd;
      int *retval;
      errno = 0;
  
      /* O_APPEND doesn't actually behave as we'd like. */
      mode &= ~O_APPEND;
  
      fd = open(filename, mode, S_IRUSR | S_IWUSR);
      BAIL_IF(fd < 0, errcodeFromErrno(), NULL);
  
      if (appending)
      {
          if (lseek(fd, 0, SEEK_END) < 0)
          {
              const int err = errno;
              close(fd);
              BAIL(errcodeFromErrnoError(err), NULL);
          } /* if */
      } /* if */
  
      retval = (int *) allocator.Malloc(sizeof (int));
      if (!retval)
      {
          close(fd);
          BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
      } /* if */
  
      *retval = fd;
      return ((void *) retval);
  } /* doOpen */
  
  
  void *__PHYSFS_platformOpenRead(const char *filename)
  {
      return doOpen(filename, O_RDONLY);
  } /* __PHYSFS_platformOpenRead */
  
  
  void *__PHYSFS_platformOpenWrite(const char *filename)
  {
      return doOpen(filename, O_WRONLY | O_CREAT | O_TRUNC);
  } /* __PHYSFS_platformOpenWrite */
  
  
  void *__PHYSFS_platformOpenAppend(const char *filename)
  {
      return doOpen(filename, O_WRONLY | O_CREAT | O_APPEND);
  } /* __PHYSFS_platformOpenAppend */
  
  
  PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer,
                                      PHYSFS_uint64 len)
  {
      const int fd = *((int *) opaque);
      ssize_t rc = 0;
  
      if (!__PHYSFS_ui64FitsAddressSpace(len))
          BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
  
      rc = read(fd, buffer, (size_t) len);
      BAIL_IF(rc == -1, errcodeFromErrno(), -1);
      assert(rc >= 0);
      assert(rc <= len);
      return (PHYSFS_sint64) rc;
  } /* __PHYSFS_platformRead */
  
  
  PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
                                       PHYSFS_uint64 len)
  {
      const int fd = *((int *) opaque);
      ssize_t rc = 0;
  
      if (!__PHYSFS_ui64FitsAddressSpace(len))
          BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
  
      rc = write(fd, (void *) buffer, (size_t) len);
      BAIL_IF(rc == -1, errcodeFromErrno(), rc);
      assert(rc >= 0);
      assert(rc <= len);
      return (PHYSFS_sint64) rc;
  } /* __PHYSFS_platformWrite */
  
  
  int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
  {
      const int fd = *((int *) opaque);
      const off_t rc = lseek(fd, (off_t) pos, SEEK_SET);
      BAIL_IF(rc == -1, errcodeFromErrno(), 0);
      return 1;
  } /* __PHYSFS_platformSeek */
  
  
  PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
  {
      const int fd = *((int *) opaque);
      PHYSFS_sint64 retval;
      retval = (PHYSFS_sint64) lseek(fd, 0, SEEK_CUR);
      BAIL_IF(retval == -1, errcodeFromErrno(), -1);
      return retval;
  } /* __PHYSFS_platformTell */
  
  
  PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
  {
      const int fd = *((int *) opaque);
      struct stat statbuf;
      BAIL_IF(fstat(fd, &statbuf) == -1, errcodeFromErrno(), -1);
      return ((PHYSFS_sint64) statbuf.st_size);
  } /* __PHYSFS_platformFileLength */
  
  
  int __PHYSFS_platformFlush(void *opaque)
  {
      const int fd = *((int *) opaque);
      if ((fcntl(fd, F_GETFL) & O_ACCMODE) != O_RDONLY)
          BAIL_IF(fsync(fd) == -1, errcodeFromErrno(), 0);
      return 1;
  } /* __PHYSFS_platformFlush */
  
  
  void __PHYSFS_platformClose(void *opaque)
  {
      const int fd = *((int *) opaque);
      (void) close(fd);  /* we don't check this. You should have used flush! */
      allocator.Free(opaque);
  } /* __PHYSFS_platformClose */
  
  
  int __PHYSFS_platformDelete(const char *path)
  {
      BAIL_IF(remove(path) == -1, errcodeFromErrno(), 0);
      return 1;
  } /* __PHYSFS_platformDelete */
  
  
  int __PHYSFS_platformStat(const char *fname, PHYSFS_Stat *st, const int follow)
  {
      struct stat statbuf;
      const int rc = follow ? stat(fname, &statbuf) : lstat(fname, &statbuf);
      BAIL_IF(rc == -1, errcodeFromErrno(), 0);
  
      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 if(S_ISLNK(statbuf.st_mode))
      {
          st->filetype = PHYSFS_FILETYPE_SYMLINK;
          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;
  
  #ifdef PHYSFS_PLATFORM_SWITCH
      /* shortcut */
      st->readonly = !(statbuf.st_mode & S_IWRITE);
  #else
      st->readonly = (access(fname, W_OK) == -1);
  #endif
      return 1;
  } /* __PHYSFS_platformStat */
  
  
  typedef struct
  {
      pthread_mutex_t mutex;
      pthread_t owner;
      PHYSFS_uint32 count;
  } PthreadMutex;
  
  
  void *__PHYSFS_platformGetThreadID(void)
  {
      return ( (void *) ((size_t) pthread_self()) );
  } /* __PHYSFS_platformGetThreadID */
  
  
  void *__PHYSFS_platformCreateMutex(void)
  {
      int rc;
      PthreadMutex *m = (PthreadMutex *) allocator.Malloc(sizeof (PthreadMutex));
      BAIL_IF(!m, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
      rc = pthread_mutex_init(&m->mutex, NULL);
      if (rc != 0)
      {
          allocator.Free(m);
          BAIL(PHYSFS_ERR_OS_ERROR, NULL);
      } /* if */
  
      m->count = 0;
      m->owner = (pthread_t) 0xDEADBEEF;
      return ((void *) m);
  } /* __PHYSFS_platformCreateMutex */
  
  
  void __PHYSFS_platformDestroyMutex(void *mutex)
  {
      PthreadMutex *m = (PthreadMutex *) mutex;
  
      /* Destroying a locked mutex is a bug, but we'll try to be helpful. */
      if ((m->owner == pthread_self()) && (m->count > 0))
          pthread_mutex_unlock(&m->mutex);
  
      pthread_mutex_destroy(&m->mutex);
      allocator.Free(m);
  } /* __PHYSFS_platformDestroyMutex */
  
  
  int __PHYSFS_platformGrabMutex(void *mutex)
  {
      PthreadMutex *m = (PthreadMutex *) mutex;
      pthread_t tid = pthread_self();
      if (m->owner != tid)
      {
          if (pthread_mutex_lock(&m->mutex) != 0)
              return 0;
          m->owner = tid;
      } /* if */
  
      m->count++;
      return 1;
  } /* __PHYSFS_platformGrabMutex */
  
  
  void __PHYSFS_platformReleaseMutex(void *mutex)
  {
      PthreadMutex *m = (PthreadMutex *) mutex;
      assert(m->owner == pthread_self());  /* catch programming errors. */
      assert(m->count > 0);  /* catch programming errors. */
      if (m->owner == pthread_self())
      {
          if (--m->count == 0)
          {
              m->owner = (pthread_t) 0xDEADBEEF;
              pthread_mutex_unlock(&m->mutex);
          } /* if */
      } /* if */
  } /* __PHYSFS_platformReleaseMutex */
  
  #endif  /* PHYSFS_PLATFORM_POSIX */
  
  /* end of physfs_platform_posix.c ... */
  
  