misc/libphysfs/platform_unix.c
changeset 13881 99b265e0d1d0
parent 13880 5f819b90d479
child 13882 b172a5d40eee
equal deleted inserted replaced
13880:5f819b90d479 13881:99b265e0d1d0
     1 /*
       
     2  * Unix support routines for PhysicsFS.
       
     3  *
       
     4  * Please see the file LICENSE.txt in the source's root directory.
       
     5  *
       
     6  *  This file written by Ryan C. Gordon.
       
     7  */
       
     8 
       
     9 #define __PHYSICSFS_INTERNAL__
       
    10 #include "physfs_platforms.h"
       
    11 
       
    12 #ifdef PHYSFS_PLATFORM_UNIX
       
    13 
       
    14 #include <ctype.h>
       
    15 #include <unistd.h>
       
    16 #include <stdlib.h>
       
    17 #include <sys/types.h>
       
    18 #include <pwd.h>
       
    19 #include <sys/stat.h>
       
    20 #include <sys/param.h>
       
    21 #include <dirent.h>
       
    22 #include <time.h>
       
    23 #include <errno.h>
       
    24 #include <limits.h>
       
    25 
       
    26 #if PHYSFS_PLATFORM_LINUX && !defined(PHYSFS_HAVE_MNTENT_H)
       
    27 #define PHYSFS_HAVE_MNTENT_H 1
       
    28 #elif PHYSFS_PLATFORM_SOLARIS && !defined(PHYSFS_HAVE_SYS_MNTTAB_H)
       
    29 #define PHYSFS_HAVE_SYS_MNTTAB_H 1
       
    30 #elif PHYSFS_PLATFORM_BSD && !defined(PHYSFS_HAVE_SYS_UCRED_H)
       
    31 #define PHYSFS_HAVE_SYS_UCRED_H 1
       
    32 #endif
       
    33 
       
    34 #ifdef PHYSFS_HAVE_SYS_UCRED_H
       
    35 #  ifdef PHYSFS_HAVE_MNTENT_H
       
    36 #    undef PHYSFS_HAVE_MNTENT_H /* don't do both... */
       
    37 #  endif
       
    38 #  include <sys/mount.h>
       
    39 #  include <sys/ucred.h>
       
    40 #endif
       
    41 
       
    42 #ifdef PHYSFS_HAVE_MNTENT_H
       
    43 #include <mntent.h>
       
    44 #endif
       
    45 
       
    46 #ifdef PHYSFS_HAVE_SYS_MNTTAB_H
       
    47 #include <sys/mnttab.h>
       
    48 #endif
       
    49 
       
    50 #include "physfs_internal.h"
       
    51 
       
    52 int __PHYSFS_platformInit(void)
       
    53 {
       
    54     return 1;  /* always succeed. */
       
    55 } /* __PHYSFS_platformInit */
       
    56 
       
    57 
       
    58 int __PHYSFS_platformDeinit(void)
       
    59 {
       
    60     return 1;  /* always succeed. */
       
    61 } /* __PHYSFS_platformDeinit */
       
    62 
       
    63 
       
    64 /* Stub version for platforms without CD-ROM support. */
       
    65 void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
       
    66 {
       
    67 #if (defined PHYSFS_NO_CDROM_SUPPORT)
       
    68     /* no-op. */
       
    69 
       
    70 #elif (defined PHYSFS_HAVE_SYS_UCRED_H)
       
    71     int i;
       
    72     struct statfs *mntbufp = NULL;
       
    73     int mounts = getmntinfo(&mntbufp, MNT_WAIT);
       
    74 
       
    75     for (i = 0; i < mounts; i++)
       
    76     {
       
    77         int add_it = 0;
       
    78 
       
    79         if (strcmp(mntbufp[i].f_fstypename, "iso9660") == 0)
       
    80             add_it = 1;
       
    81         else if (strcmp( mntbufp[i].f_fstypename, "cd9660") == 0)
       
    82             add_it = 1;
       
    83 
       
    84         /* add other mount types here */
       
    85 
       
    86         if (add_it)
       
    87             cb(data, mntbufp[i].f_mntonname);
       
    88     } /* for */
       
    89 
       
    90 #elif (defined PHYSFS_HAVE_MNTENT_H)
       
    91     FILE *mounts = NULL;
       
    92     struct mntent *ent = NULL;
       
    93 
       
    94     mounts = setmntent("/etc/mtab", "r");
       
    95     BAIL_IF_MACRO(mounts == NULL, PHYSFS_ERR_IO, /*return void*/);
       
    96 
       
    97     while ( (ent = getmntent(mounts)) != NULL )
       
    98     {
       
    99         int add_it = 0;
       
   100         if (strcmp(ent->mnt_type, "iso9660") == 0)
       
   101             add_it = 1;
       
   102         else if (strcmp(ent->mnt_type, "udf") == 0)
       
   103             add_it = 1;
       
   104 
       
   105         /* !!! FIXME: these might pick up floppy drives, right? */
       
   106         else if (strcmp(ent->mnt_type, "auto") == 0)
       
   107             add_it = 1;
       
   108         else if (strcmp(ent->mnt_type, "supermount") == 0)
       
   109             add_it = 1;
       
   110 
       
   111         /* !!! FIXME: udf? automount? */
       
   112 
       
   113         /* add other mount types here */
       
   114 
       
   115         if (add_it)
       
   116             cb(data, ent->mnt_dir);
       
   117     } /* while */
       
   118 
       
   119     endmntent(mounts);
       
   120 
       
   121 #elif (defined PHYSFS_HAVE_SYS_MNTTAB_H)
       
   122     FILE *mounts = fopen(MNTTAB, "r");
       
   123     struct mnttab ent;
       
   124 
       
   125     BAIL_IF_MACRO(mounts == NULL, PHYSFS_ERR_IO, /*return void*/);
       
   126     while (getmntent(mounts, &ent) == 0)
       
   127     {
       
   128         int add_it = 0;
       
   129         if (strcmp(ent.mnt_fstype, "hsfs") == 0)
       
   130             add_it = 1;
       
   131 
       
   132         /* add other mount types here */
       
   133 
       
   134         if (add_it)
       
   135             cb(data, ent.mnt_mountp);
       
   136     } /* while */
       
   137 
       
   138     fclose(mounts);
       
   139 
       
   140 #else
       
   141 #error Unknown platform. Should have defined PHYSFS_NO_CDROM_SUPPORT, perhaps.
       
   142 #endif
       
   143 } /* __PHYSFS_platformDetectAvailableCDs */
       
   144 
       
   145 
       
   146 /*
       
   147  * See where program (bin) resides in the $PATH specified by (envr).
       
   148  *  returns a copy of the first element in envr that contains it, or NULL
       
   149  *  if it doesn't exist or there were other problems. PHYSFS_SetError() is
       
   150  *  called if we have a problem.
       
   151  *
       
   152  * (envr) will be scribbled over, and you are expected to allocator.Free() the
       
   153  *  return value when you're done with it.
       
   154  */
       
   155 static char *findBinaryInPath(const char *bin, char *envr)
       
   156 {
       
   157     size_t alloc_size = 0;
       
   158     char *exe = NULL;
       
   159     char *start = envr;
       
   160     char *ptr;
       
   161 
       
   162     assert(bin != NULL);
       
   163     assert(envr != NULL);
       
   164 
       
   165     do
       
   166     {
       
   167         size_t size;
       
   168         size_t binlen;
       
   169 
       
   170         ptr = strchr(start, ':');  /* find next $PATH separator. */
       
   171         if (ptr)
       
   172             *ptr = '\0';
       
   173 
       
   174         binlen = strlen(bin);
       
   175         size = strlen(start) + binlen + 2;
       
   176         if (size >= alloc_size)
       
   177         {
       
   178             char *x = (char *) allocator.Realloc(exe, size);
       
   179             if (!x)
       
   180             {
       
   181                 if (exe != NULL)
       
   182                     allocator.Free(exe);
       
   183                 BAIL_MACRO(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
       
   184             } /* if */
       
   185 
       
   186             alloc_size = size;
       
   187             exe = x;
       
   188         } /* if */
       
   189 
       
   190         /* build full binary path... */
       
   191         strcpy(exe, start);
       
   192         if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
       
   193             strcat(exe, "/");
       
   194         strcat(exe, bin);
       
   195 
       
   196         if (access(exe, X_OK) == 0)  /* Exists as executable? We're done. */
       
   197         {
       
   198             exe[(size - binlen) - 1] = '\0'; /* chop off filename, leave '/' */
       
   199             return exe;
       
   200         } /* if */
       
   201 
       
   202         start = ptr + 1;  /* start points to beginning of next element. */
       
   203     } while (ptr != NULL);
       
   204 
       
   205     if (exe != NULL)
       
   206         allocator.Free(exe);
       
   207 
       
   208     return NULL;  /* doesn't exist in path. */
       
   209 } /* findBinaryInPath */
       
   210 
       
   211 
       
   212 static char *readSymLink(const char *path)
       
   213 {
       
   214     ssize_t len = 64;
       
   215     ssize_t rc = -1;
       
   216     char *retval = NULL;
       
   217 
       
   218     while (1)
       
   219     {
       
   220          char *ptr = (char *) allocator.Realloc(retval, (size_t) len);
       
   221          if (ptr == NULL)
       
   222              break;   /* out of memory. */
       
   223          retval = ptr;
       
   224 
       
   225          rc = readlink(path, retval, len);
       
   226          if (rc == -1)
       
   227              break;  /* not a symlink, i/o error, etc. */
       
   228 
       
   229          else if (rc < len)
       
   230          {
       
   231              retval[rc] = '\0';  /* readlink doesn't null-terminate. */
       
   232              return retval;  /* we're good to go. */
       
   233          } /* else if */
       
   234 
       
   235          len *= 2;  /* grow buffer, try again. */
       
   236     } /* while */
       
   237 
       
   238     if (retval != NULL)
       
   239         allocator.Free(retval);
       
   240     return NULL;
       
   241 } /* readSymLink */
       
   242 
       
   243 
       
   244 char *__PHYSFS_platformCalcBaseDir(const char *argv0)
       
   245 {
       
   246     char *retval = NULL;
       
   247     const char *envr = NULL;
       
   248 
       
   249     /* Try to avoid using argv0 unless forced to. Try system-specific stuff. */
       
   250     
       
   251     #if PHYSFS_PLATFORM_FREEBSD
       
   252     {
       
   253         char fullpath[PATH_MAX];
       
   254         size_t buflen = sizeof (fullpath);
       
   255         int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
       
   256         if (sysctl(mib, 4, fullpath, &buflen, NULL, 0) != -1)
       
   257             retval = __PHYSFS_strdup(fullpath);
       
   258     }
       
   259     #elif PHYSFS_PLATFORM_SOLARIS
       
   260     {
       
   261         const char *path = getexecname();
       
   262         if ((path != NULL) && (path[0] == '/'))  /* must be absolute path... */
       
   263             retval = __PHYSFS_strdup(path);
       
   264     }
       
   265     #endif
       
   266 
       
   267     if (retval)
       
   268         return retval;   /* already got it. */
       
   269 
       
   270     /* If there's a Linux-like /proc filesystem, you can get the full path to
       
   271      *  the current process from a symlink in there.
       
   272      */
       
   273 
       
   274     if (access("/proc", F_OK) == 0)
       
   275     {
       
   276         retval = readSymLink("/proc/self/exe");
       
   277         if (!retval) retval = readSymLink("/proc/curproc/file");
       
   278         if (!retval) retval = readSymLink("/proc/curproc/exe");
       
   279         if (retval == NULL)
       
   280         {
       
   281             /* older kernels don't have /proc/self ... try PID version... */
       
   282             const unsigned long long pid = (unsigned long long) getpid();
       
   283             char path[64];
       
   284             const int rc = (int) snprintf(path,sizeof(path),"/proc/%llu/exe",pid);
       
   285             if ( (rc > 0) && (rc < sizeof(path)) )
       
   286                 retval = readSymLink(path);
       
   287         } /* if */
       
   288     } /* if */
       
   289 
       
   290     if (retval != NULL)  /* chop off filename. */
       
   291     {
       
   292         char *ptr = strrchr(retval, '/');
       
   293         if (ptr != NULL)
       
   294             *(ptr+1) = '\0';
       
   295         else  /* shouldn't happen, but just in case... */
       
   296         {
       
   297             allocator.Free(retval);
       
   298             retval = NULL;
       
   299         } /* else */
       
   300     } /* if */
       
   301 
       
   302     /* No /proc/self/exe, etc, but we have an argv[0] we can parse? */
       
   303     if ((retval == NULL) && (argv0 != NULL))
       
   304     {
       
   305         /* fast path: default behaviour can handle this. */
       
   306         if (strchr(argv0, '/') != NULL)
       
   307             return NULL;  /* higher level parses out real path from argv0. */
       
   308 
       
   309         /* If there's no dirsep on argv0, then look through $PATH for it. */
       
   310         envr = getenv("PATH");
       
   311         if (envr != NULL)
       
   312         {
       
   313             char *path = (char *) __PHYSFS_smallAlloc(strlen(envr) + 1);
       
   314             BAIL_IF_MACRO(!path, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
       
   315             strcpy(path, envr);
       
   316             retval = findBinaryInPath(argv0, path);
       
   317             __PHYSFS_smallFree(path);
       
   318         } /* if */
       
   319     } /* if */
       
   320 
       
   321     if (retval != NULL)
       
   322     {
       
   323         /* try to shrink buffer... */
       
   324         char *ptr = (char *) allocator.Realloc(retval, strlen(retval) + 1);
       
   325         if (ptr != NULL)
       
   326             retval = ptr;  /* oh well if it failed. */
       
   327     } /* if */
       
   328 
       
   329     return retval;
       
   330 } /* __PHYSFS_platformCalcBaseDir */
       
   331 
       
   332 
       
   333 char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
       
   334 {
       
   335     /*
       
   336      * We use XDG's base directory spec, even if you're not on Linux.
       
   337      *  This isn't strictly correct, but the results are relatively sane
       
   338      *  in any case.
       
   339      *
       
   340      * https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
       
   341      */
       
   342     const char *envr = getenv("XDG_DATA_HOME");
       
   343     const char *append = "/";
       
   344     char *retval = NULL;
       
   345     size_t len = 0;
       
   346 
       
   347     if (!envr)
       
   348     {
       
   349         /* You end up with "$HOME/.local/share/Game Name 2" */
       
   350         envr = __PHYSFS_getUserDir();
       
   351         BAIL_IF_MACRO(!envr, ERRPASS, NULL);  /* oh well. */
       
   352         append = ".local/share/";
       
   353     } /* if */
       
   354 
       
   355     len = strlen(envr) + strlen(append) + strlen(app) + 2;
       
   356     retval = (char *) allocator.Malloc(len);
       
   357     BAIL_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
       
   358     snprintf(retval, len, "%s%s%s/", envr, append, app);
       
   359     return retval;
       
   360 } /* __PHYSFS_platformCalcPrefDir */
       
   361 
       
   362 
       
   363 int __PHYSFS_platformSetDefaultAllocator(PHYSFS_Allocator *a)
       
   364 {
       
   365     return 0;  /* just use malloc() and friends. */
       
   366 } /* __PHYSFS_platformSetDefaultAllocator */
       
   367 
       
   368 #endif /* PHYSFS_PLATFORM_UNIX */
       
   369 
       
   370 /* end of unix.c ... */
       
   371