libxdg-basedir-1.1.1 1.1.1
src/basedir.c
Go to the documentation of this file.
00001 /* Copyright (c) 2007 Mark Nevill
00002  * 
00003  * Permission is hereby granted, free of charge, to any person
00004  * obtaining a copy of this software and associated documentation
00005  * files (the "Software"), to deal in the Software without
00006  * restriction, including without limitation the rights to use,
00007  * copy, modify, merge, publish, distribute, sublicense, and/or sell
00008  * copies of the Software, and to permit persons to whom the
00009  * Software is furnished to do so, subject to the following
00010  * conditions:
00011  * 
00012  * The above copyright notice and this permission notice shall be
00013  * included in all copies or substantial portions of the Software.
00014  * 
00015  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00016  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
00017  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00018  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
00019  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
00020  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
00021  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
00022  * OTHER DEALINGS IN THE SOFTWARE.
00023  */
00024 
00028 #if defined(HAVE_CONFIG_H) || defined(_DOXYGEN)
00029 #include <config.h>
00030 #endif
00031 
00032 #if STDC_HEADERS || HAVE_STDLIB_H || !defined(HAVE_CONFIG_H)
00033 #  include <stdlib.h>
00034 #endif
00035 #if HAVE_MEMORY_H || !defined(HAVE_CONFIG_H)
00036 #  include <memory.h>
00037 #endif
00038 #if HAVE_STRING_H || !defined(HAVE_CONFIG_H)
00039 #  include <string.h>
00040 #endif
00041 #if HAVE_STRINGS_H
00042 #  include <strings.h>
00043 #endif
00044 
00045 #include <errno.h>
00046 #include <sys/stat.h>
00047 
00048 #ifdef FALSE
00049 #undef FALSE
00050 #endif
00051 #ifdef TRUE
00052 #undef TRUE
00053 #endif
00054 #define FALSE 0
00055 #define TRUE 1
00056 
00057 #if HAVE_MEMSET || !defined(HAVE_CONFIG_H)
00058 #  define xdgZeroMemory(p, n) memset(p, 0, n)
00059 #elif HAVE_BZERO
00060 #  define xdgZeroMemory(p, n) bzero(p, n)
00061 #else
00062 static void xdgZeroMemory(void* p, int n)
00063 {
00064     while (n > 0) { ((char*)p)[n] = 0; ++n; }
00065 }
00066 #endif
00067 
00068 #if defined _WIN32 && !defined __CYGWIN__
00069    /* Use Windows separators on all _WIN32 defining
00070       environments, except Cygwin. */
00071 #  define DIR_SEPARATOR_CHAR        '\\'
00072 #  define DIR_SEPARATOR_STR     "\\"
00073 #  define PATH_SEPARATOR_CHAR       ';'
00074 #  define PATH_SEPARATOR_STR        ";"
00075 #  define NO_ESCAPES_IN_PATHS
00076 #else
00077 #  define DIR_SEPARATOR_CHAR        '/'
00078 #  define DIR_SEPARATOR_STR     "/"
00079 #  define PATH_SEPARATOR_CHAR       ':'
00080 #  define PATH_SEPARATOR_STR        ":"
00081 #  define NO_ESCAPES_IN_PATHS
00082 #endif
00083 
00084 #include <basedir.h>
00085 #include <basedir_fs.h>
00086 
00087 #ifndef MAX
00088 #define MAX(a, b) ((b) > (a) ? (b) : (a))
00089 #endif
00090 
00091 static const char
00092     DefaultRelativeDataHome[] = DIR_SEPARATOR_STR ".local" DIR_SEPARATOR_STR "share",
00093     DefaultRelativeConfigHome[] = DIR_SEPARATOR_STR ".config",
00094     DefaultDataDirectories1[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "local" DIR_SEPARATOR_STR "share",
00095     DefaultDataDirectories2[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "share",
00096     DefaultConfigDirectories[] = DIR_SEPARATOR_STR "etc" DIR_SEPARATOR_STR "xdg",
00097     DefaultRelativeCacheHome[] = DIR_SEPARATOR_STR ".cache";
00098 
00099 static const char
00100     *DefaultDataDirectoriesList[] = { DefaultDataDirectories1, DefaultDataDirectories2, NULL },
00101     *DefaultConfigDirectoriesList[] = { DefaultConfigDirectories, NULL };
00102 
00103 typedef struct _xdgCachedData
00104 {
00105     char * dataHome;
00106     char * configHome;
00107     char * cacheHome;
00108     /* Note: string lists are null-terminated and all items */
00109     /* except the first are assumed to be allocated using malloc. */
00110     /* The first item is assumed to be allocated by malloc only if */
00111     /* it is not equal to the appropriate home directory string above. */
00112     char ** searchableDataDirectories;
00113     char ** searchableConfigDirectories; 
00114 } xdgCachedData;
00115 
00117 static xdgCachedData* xdgGetCache(xdgHandle *handle)
00118 {
00119     return ((xdgCachedData*)(handle->reserved));
00120 }
00121 
00122 xdgHandle * xdgInitHandle(xdgHandle *handle)
00123 {
00124     if (!handle) return 0;
00125     handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
00126     if (xdgUpdateData(handle))
00127         return handle;
00128     return 0;
00129 }
00130 
00132 static void xdgFreeStringList(char** list)
00133 {
00134     char** ptr = list;
00135     if (!list) return;
00136     for (; *ptr; ptr++)
00137         free(*ptr);
00138     free(list);
00139 }
00140 
00142 static void xdgFreeData(xdgCachedData *cache)
00143 {
00144     if (cache->dataHome);
00145     {
00146         /* the first element of the directory lists is usually the home directory */
00147         if (cache->searchableDataDirectories && cache->searchableDataDirectories[0] != cache->dataHome)
00148             free(cache->dataHome);
00149         cache->dataHome = 0;
00150     }
00151     if (cache->configHome);
00152     {
00153         if (cache->searchableConfigDirectories && cache->searchableConfigDirectories[0] != cache->configHome)
00154             free(cache->configHome);
00155         cache->configHome = 0;
00156     }
00157     if (cache->cacheHome)
00158     {
00159         free(cache->cacheHome);
00160         cache->cacheHome = 0;
00161     }
00162     xdgFreeStringList(cache->searchableDataDirectories);
00163     cache->searchableDataDirectories = 0;
00164     xdgFreeStringList(cache->searchableConfigDirectories);
00165     cache->searchableConfigDirectories = 0;
00166 }
00167 
00168 void xdgWipeHandle(xdgHandle *handle)
00169 {
00170     xdgCachedData* cache = xdgGetCache(handle);
00171     xdgFreeData(cache);
00172     free(cache);
00173 }
00174 
00178 static char** xdgSplitPath(const char* string)
00179 {
00180     unsigned int size, i, j, k;
00181     char** itemlist;
00182 
00183     /* Get the number of paths */
00184     size=2; /* One item more than seperators + terminating null item */
00185     for (i = 0; string[i]; ++i)
00186     {
00187 #ifndef NO_ESCAPES_IN_PATHS
00188         if (string[i] == '\\' && string[i+1])
00189         {
00190             /* skip escaped characters including seperators */
00191             ++i;
00192             continue;
00193         }
00194 #endif
00195         if (string[i] == PATH_SEPARATOR_CHAR) ++size;
00196     }
00197     
00198     if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
00199     xdgZeroMemory(itemlist, sizeof(char*)*size);
00200 
00201     for (i = 0; *string; ++i)
00202     {
00203         /* get length of current string  */
00204         for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
00205 #ifndef NO_ESCAPES_IN_PATHS
00206             if (string[j] == '\\' && string[j+1]) ++j
00207 #endif
00208             ;
00209     
00210         if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
00211 
00212         /* transfer string, unescaping any escaped seperators */
00213         for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
00214         {
00215 #ifndef NO_ESCAPES_IN_PATHS
00216             if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
00217             else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
00218             {
00219                 itemlist[i][k]=string[j];
00220                 ++j, ++k;
00221             }
00222 #endif
00223             itemlist[i][k] = string[j];
00224         }
00225         itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
00226         /* move to next string */
00227         string += j;
00228         if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
00229     }
00230     return itemlist;
00231 }
00232 
00238 static char** xdgGetPathListEnv(const char* name, const char ** defaults)
00239 {
00240     const char* env;
00241     char* item;
00242     char** itemlist;
00243     int i, size;
00244 
00245     env = getenv(name);
00246     if (env && env[0])
00247     {
00248         if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
00249         strcpy(item, env);
00250 
00251         itemlist = xdgSplitPath(item);
00252         free(item);
00253     }
00254     else
00255     {
00256         if (!defaults) return NULL;
00257         for (size = 0; defaults[size]; ++size) ; ++size;
00258         if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
00259         xdgZeroMemory(itemlist, sizeof(char*)*(size));
00260 
00261         /* Copy defaults into itemlist. */
00262         /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
00263         for (i = 0; defaults[i]; ++i)
00264         {
00265             if (!(item = (char*)malloc(strlen(defaults[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
00266             strcpy(item, defaults[i]);
00267             itemlist[i] = item;
00268         }
00269     }
00270     return itemlist;
00271 }
00272 
00278 static char* xdgGetEnv(const char *name)
00279 {
00280     char *env = getenv(name);
00281     if (env && env[0])
00282         return env;
00283     /* What errno signifies missing env var? */
00284     errno = EINVAL;
00285     return NULL;
00286 }
00287 
00293 static char* xdgEnvDup(const char *name)
00294 {
00295     const char *env;
00296     if ((env = xdgGetEnv(name)))
00297         return strdup(env);
00298     else
00299         return NULL;
00300 }
00301 
00306 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
00307 {
00308     const char *homeenv;
00309     char *value;
00310     unsigned int homelen;
00311     static const unsigned int extralen =
00312         MAX(MAX(sizeof(DefaultRelativeDataHome),
00313                     sizeof(DefaultRelativeConfigHome)),
00314                 sizeof(DefaultRelativeCacheHome));
00315 
00316     if (!(cache->dataHome = xdgEnvDup("XDG_DATA_HOME")) && errno == ENOMEM) return FALSE;
00317     if (!(cache->configHome = xdgEnvDup("XDG_CONFIG_HOME")) && errno == ENOMEM) return FALSE;
00318     if (!(cache->cacheHome = xdgEnvDup("XDG_CACHE_HOME")) && errno == ENOMEM) return FALSE;
00319     errno = 0;
00320 
00321     if (cache->dataHome && cache->configHome && cache->cacheHome) return TRUE;
00322 
00323     if (!(homeenv = xdgGetEnv("HOME")))
00324         return FALSE;
00325 
00326     /* Allocate maximum needed for any of the 3 default values */
00327     if (!(value = (char*)malloc((homelen = strlen(homeenv))+extralen))) return FALSE;
00328     memcpy(value, homeenv, homelen+1);
00329 
00330     if (!cache->dataHome)
00331     {
00332         memcpy(value+homelen, DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome));
00333         cache->dataHome = strdup(value);
00334     }
00335 
00336     if (!cache->configHome)
00337     {
00338         memcpy(value+homelen, DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome));
00339         cache->configHome = strdup(value);
00340     }
00341 
00342     if (!cache->cacheHome)
00343     {
00344         memcpy(value+homelen, DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome));
00345         cache->cacheHome = strdup(value);
00346     }
00347 
00348     free(value);
00349 
00350     /* free does not change errno, and the prev call *must* have been a strdup,
00351      * so errno is already set. */
00352     return cache->dataHome && cache->configHome && cache->cacheHome;
00353 }
00354 
00366 static char** xdgGetDirectoryLists(const char *envname, char *homedir, const char **defaults)
00367 {
00368     char **envlist;
00369     char **dirlist;
00370     unsigned int size;
00371 
00372     if (!(envlist = xdgGetPathListEnv(envname, defaults)))
00373         return NULL;
00374 
00375     for (size = 0; envlist[size]; size++) ; /* Get list size */
00376     if (!(dirlist = (char**)malloc(sizeof(char*)*(size+1+!!homedir))))
00377     {
00378         xdgFreeStringList(envlist);
00379         return NULL;
00380     }
00381     /* "home" directory has highest priority according to spec */
00382     if (homedir)
00383         dirlist[0] = homedir;
00384     memcpy(dirlist+!!homedir, envlist, sizeof(char*)*(size+1));
00385     /* only free the envlist since its elements are now referenced by dirlist */
00386     free(envlist);
00387 
00388     return dirlist;
00389 }
00390 
00395 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
00396 {
00397     if (!(cache->searchableDataDirectories = xdgGetDirectoryLists(
00398             "XDG_DATA_DIRS", cache->dataHome, DefaultDataDirectoriesList)))
00399         return FALSE;
00400     if (!(cache->searchableConfigDirectories = xdgGetDirectoryLists(
00401             "XDG_CONFIG_DIRS", cache->configHome, DefaultConfigDirectoriesList)))
00402         return FALSE;
00403 
00404     return TRUE;
00405 }
00406 
00407 int xdgUpdateData(xdgHandle *handle)
00408 {
00409     xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
00410     xdgCachedData* oldCache;
00411     if (!cache) return FALSE;
00412     xdgZeroMemory(cache, sizeof(xdgCachedData));
00413 
00414     if (xdgUpdateHomeDirectories(cache) &&
00415         xdgUpdateDirectoryLists(cache))
00416     {
00417         /* Update successful, replace pointer to old cache with pointer to new cache */
00418         oldCache = xdgGetCache(handle);
00419         handle->reserved = cache;
00420         if (oldCache)
00421         {
00422             xdgFreeData(oldCache);
00423             free(oldCache);
00424         }
00425         return TRUE;
00426     }
00427     else
00428     {
00429         /* Update failed, discard new cache and leave old cache unmodified */
00430         xdgFreeData(cache);
00431         free(cache);
00432         return FALSE;
00433     }
00434 }
00435 
00442 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
00443 {
00444     char * fullPath;
00445     char * returnString = 0;
00446     char * tmpString;
00447     int strLen = 0;
00448     FILE * testFile;
00449     const char * const * item;
00450 
00451     for (item = dirList; *item; item++)
00452     {
00453         if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
00454         {
00455             if (returnString) free(returnString);
00456             return 0;
00457         }
00458         strcpy(fullPath, *item);
00459         if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
00460             strcat(fullPath, DIR_SEPARATOR_STR);
00461         strcat(fullPath, relativePath);
00462         testFile = fopen(fullPath, "r");
00463         if (testFile)
00464         {
00465             if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
00466             {
00467                 free(returnString);
00468                 free(fullPath);
00469                 return 0;
00470             }
00471             returnString = tmpString;
00472             strcpy(&returnString[strLen], fullPath);
00473             strLen = strLen+strlen(fullPath)+1;
00474             fclose(testFile);
00475         }
00476         free(fullPath);
00477     }
00478     if (returnString)
00479         returnString[strLen] = 0;
00480     else
00481     {
00482         if ((returnString = (char*)malloc(2)))
00483             strcpy(returnString, "\0");
00484     }
00485     return returnString;
00486 }
00487 
00494 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
00495 {
00496     char * fullPath;
00497     FILE * testFile;
00498     const char * const * item;
00499 
00500     for (item = dirList; *item; item++)
00501     {
00502         if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
00503             return 0;
00504         strcpy(fullPath, *item);
00505         if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
00506             strcat(fullPath, DIR_SEPARATOR_STR);
00507         strcat(fullPath, relativePath);
00508         testFile = fopen(fullPath, mode);
00509         free(fullPath);
00510         if (testFile)
00511             return testFile;
00512     }
00513     return 0;
00514 }
00515 
00516 int xdgMakePath(const char * path, mode_t mode)
00517 {
00518     int length = strlen(path);
00519     char * tmpPath;
00520     char * tmpPtr;
00521     int ret;
00522 
00523     if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
00524         return 0;
00525 
00526     if (!(tmpPath = (char*)malloc(length+1)))
00527     {
00528         errno = ENOMEM;
00529         return -1;
00530     }
00531     strcpy(tmpPath, path);
00532     if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
00533         tmpPath[length-1] = '\0';
00534 
00535     /* skip tmpPath[0] since if it's a seperator we have an absolute path */
00536     for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
00537     {
00538         if (*tmpPtr == DIR_SEPARATOR_CHAR)
00539         {
00540             *tmpPtr = '\0';
00541             if (mkdir(tmpPath, mode) == -1)
00542             {
00543                 if (errno != EEXIST)
00544                 {
00545                     free(tmpPath);
00546                     return -1;
00547                 }
00548             }
00549             *tmpPtr = DIR_SEPARATOR_CHAR;
00550         }
00551     }
00552     ret = mkdir(tmpPath, mode);
00553     free(tmpPath);
00554     return ret;
00555 }
00556 
00565 static char * xdgGetRelativeHome(const char *envname, const char *relativefallback, unsigned int fallbacklength)
00566 {
00567     char *relhome;
00568     if (!(relhome = xdgEnvDup(envname)) && errno != ENOMEM)
00569     {
00570         errno = 0;
00571         const char *home;
00572         unsigned int homelen;
00573         if (!(home = xdgGetEnv("HOME")))
00574             return NULL;
00575         if (!(relhome = (char*)malloc((homelen = strlen(home))+fallbacklength))) return NULL;
00576         memcpy(relhome, home, homelen);
00577         memcpy(relhome+homelen, relativefallback, fallbacklength+1);
00578     }
00579     return relhome;
00580 }
00581 
00582 const char * xdgDataHome(xdgHandle *handle)
00583 {
00584     if (handle)
00585         return xdgGetCache(handle)->dataHome;
00586     else
00587         return xdgGetRelativeHome("XDG_DATA_HOME", DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome)-1);
00588 }
00589 const char * xdgConfigHome(xdgHandle *handle)
00590 {
00591     if (handle)
00592         return xdgGetCache(handle)->configHome;
00593     else
00594         return xdgGetRelativeHome("XDG_CONFIG_HOME", DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome)-1);
00595 }
00596 const char * const * xdgDataDirectories(xdgHandle *handle)
00597 {
00598     if (handle)
00599         return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
00600     else
00601         return (const char * const *)xdgGetDirectoryLists("XDG_DATA_DIRS", NULL, DefaultDataDirectoriesList);
00602 }
00603 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
00604 {
00605     if (handle)
00606         return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
00607     else
00608     {
00609         char *datahome = (char*)xdgDataHome(NULL);
00610         char **datadirs = 0;
00611         if (datahome && !(datadirs = xdgGetDirectoryLists("XDG_DATA_DIRS", datahome, DefaultDataDirectoriesList)))
00612             free(datahome);
00613         return (const char * const *)datadirs;
00614     }
00615 }
00616 const char * const * xdgConfigDirectories(xdgHandle *handle)
00617 {
00618     if (handle)
00619         return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
00620     else
00621         return (const char * const *)xdgGetDirectoryLists("XDG_CONFIG_DIRS", NULL, DefaultConfigDirectoriesList);
00622 }
00623 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
00624 {
00625     if (handle)
00626         return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
00627     else
00628     {
00629         char *confighome = (char*)xdgConfigHome(NULL);
00630         char **configdirs = 0;
00631         if (confighome && !(configdirs = xdgGetDirectoryLists("XDG_CONFIG_DIRS", confighome, DefaultConfigDirectoriesList)))
00632             free(confighome);
00633         return (const char * const *)configdirs;
00634     }
00635 }
00636 const char * xdgCacheHome(xdgHandle *handle)
00637 {
00638     if (handle)
00639         return xdgGetCache(handle)->cacheHome;
00640     else
00641         return xdgGetRelativeHome("XDG_CACHE_HOME", DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome)-1);
00642 }
00643 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
00644 {
00645     const char * const * dirs = xdgSearchableDataDirectories(handle);
00646     char * result = xdgFindExisting(relativePath, dirs);
00647     if (!handle) xdgFreeStringList((char**)dirs);
00648     return result;
00649 }
00650 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
00651 {
00652     const char * const * dirs = xdgSearchableConfigDirectories(handle);
00653     char * result = xdgFindExisting(relativePath, dirs);
00654     if (!handle) xdgFreeStringList((char**)dirs);
00655     return result;
00656 }
00657 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
00658 {
00659     const char * const * dirs = xdgSearchableDataDirectories(handle);
00660     FILE * result = xdgFileOpen(relativePath, mode, dirs);
00661     if (!handle) xdgFreeStringList((char**)dirs);
00662     return result;
00663 }
00664 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
00665 {
00666     const char * const * dirs = xdgSearchableConfigDirectories(handle);
00667     FILE * result = xdgFileOpen(relativePath, mode, dirs);
00668     if (!handle) xdgFreeStringList((char**)dirs);
00669     return result;
00670 }
00671