libxdg-basedir-1.0.2  1.0.2
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 
00047 #ifdef FALSE
00048 #undef FALSE
00049 #endif
00050 #ifdef TRUE
00051 #undef TRUE
00052 #endif
00053 #define FALSE 0
00054 #define TRUE 1
00055 
00056 #if HAVE_MEMSET || !defined(HAVE_CONFIG_H)
00057 #  define xdgZeroMemory(p, n) memset(p, 0, n)
00058 #elif HAVE_BZERO
00059 #  define xdgZeroMemory(p, n) bzero(p, n)
00060 #else
00061 static void xdgZeroMemory(void* p, size_t n)
00062 {
00063     while (n > 0) { ((char*)p)[n] = 0; ++n; }
00064 }
00065 #endif
00066 
00067 #if defined _WIN32 && !defined __CYGWIN__
00068    /* Use Windows separators on all _WIN32 defining
00069       environments, except Cygwin. */
00070 #  define DIR_SEPARATOR_CHAR        '\\'
00071 #  define DIR_SEPARATOR_STR     "\\"
00072 #  define PATH_SEPARATOR_CHAR       ';'
00073 #  define PATH_SEPARATOR_STR        ";"
00074 #  define NO_ESCAPES_IN_PATHS
00075 #else
00076 #  define DIR_SEPARATOR_CHAR        '/'
00077 #  define DIR_SEPARATOR_STR     "/"
00078 #  define PATH_SEPARATOR_CHAR       ':'
00079 #  define PATH_SEPARATOR_STR        ":"
00080 #  define NO_ESCAPES_IN_PATHS
00081 #endif
00082 
00083 #include <basedir.h>
00084 #include <basedir_fs.h>
00085 
00086 #ifndef MAX
00087 #define MAX(a, b) ((b) > (a) ? (b) : (a))
00088 #endif
00089 
00090 static const char
00091     DefaultRelativeDataHome[] = DIR_SEPARATOR_STR ".local" DIR_SEPARATOR_STR "share",
00092     DefaultRelativeConfigHome[] = DIR_SEPARATOR_STR ".config",
00093     DefaultDataDirectories1[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "local" DIR_SEPARATOR_STR "share",
00094     DefaultDataDirectories2[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "share",
00095     DefaultConfigDirectories[] = DIR_SEPARATOR_STR "etc" DIR_SEPARATOR_STR "xdg",
00096     DefaultRelativeCacheHome[] = DIR_SEPARATOR_STR ".cache";
00097 
00098 static const char
00099     *DefaultDataDirectoriesList[] = { DefaultDataDirectories1, DefaultDataDirectories2, NULL },
00100     *DefaultConfigDirectoriesList[] = { DefaultConfigDirectories, NULL };
00101 
00102 typedef struct _xdgCachedData
00103 {
00104     char * dataHome;
00105     char * configHome;
00106     char * cacheHome;
00107     /* Note: string lists are null-terminated and all items */
00108     /* except the first are assumed to be allocated using malloc. */
00109     /* The first item is assumed to be allocated by malloc only if */
00110     /* it is not equal to the appropriate home directory string above. */
00111     char ** searchableDataDirectories;
00112     char ** searchableConfigDirectories; 
00113 } xdgCachedData;
00114 
00116 static xdgCachedData* xdgGetCache(xdgHandle *handle)
00117 {
00118     return ((xdgCachedData*)(handle->reserved));
00119 }
00120 
00121 xdgHandle * xdgInitHandle(xdgHandle *handle)
00122 {
00123     if (!handle) return 0;
00124     handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
00125     if (xdgUpdateData(handle))
00126         return handle;
00127     return 0;
00128 }
00129 
00131 static void xdgFreeStringList(char** list)
00132 {
00133     char** ptr = list;
00134     if (!list) return;
00135     for (; *ptr; ptr++)
00136         free(*ptr);
00137     free(list);
00138 }
00139 
00141 static void xdgFreeData(xdgCachedData *cache)
00142 {
00143     if (cache->dataHome);
00144     {
00145         /* the first element of the directory lists is usually the home directory */
00146         if (cache->searchableDataDirectories[0] != cache->dataHome)
00147             free(cache->dataHome);
00148         cache->dataHome = 0;
00149     }
00150     if (cache->configHome);
00151     {
00152         if (cache->searchableConfigDirectories[0] != cache->configHome)
00153             free(cache->configHome);
00154         cache->configHome = 0;
00155     }
00156     if (cache->cacheHome)
00157     {
00158         free(cache->cacheHome);
00159         cache->cacheHome = 0;
00160     }
00161     xdgFreeStringList(cache->searchableDataDirectories);
00162     cache->searchableDataDirectories = 0;
00163     xdgFreeStringList(cache->searchableConfigDirectories);
00164     cache->searchableConfigDirectories = 0;
00165 }
00166 
00167 void xdgWipeHandle(xdgHandle *handle)
00168 {
00169     xdgCachedData* cache = xdgGetCache(handle);
00170     xdgFreeData(cache);
00171     free(cache);
00172 }
00173 
00179 static char* xdgGetEnv(const char* name, const char* defaultValue)
00180 {
00181     const char* env;
00182     char* value;
00183 
00184     env = getenv(name);
00185     if (env && env[0])
00186     {
00187         if (!(value = (char*)malloc(strlen(env)+1))) return 0;
00188         strcpy(value, env);
00189     }
00190     else
00191     {
00192         if (!(value = (char*)malloc(strlen(defaultValue)+1))) return 0;
00193         strcpy(value, defaultValue);
00194     }
00195     return value;
00196 }
00197 
00201 static char** xdgSplitPath(const char* string)
00202 {
00203     unsigned int size, i, j, k;
00204     char** itemlist;
00205 
00206     /* Get the number of paths */
00207     size=2; /* One item more than seperators + terminating null item */
00208     for (i = 0; string[i]; ++i)
00209     {
00210 #ifndef NO_ESCAPES_IN_PATHS
00211         if (string[i] == '\\' && string[i+1])
00212         {
00213             /* skip escaped characters including seperators */
00214             ++i;
00215             continue;
00216         }
00217 #endif
00218         if (string[i] == PATH_SEPARATOR_CHAR) ++size;
00219     }
00220     
00221     if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
00222     xdgZeroMemory(itemlist, sizeof(char*)*size);
00223 
00224     for (i = 0; *string; ++i)
00225     {
00226         /* get length of current string  */
00227         for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
00228 #ifndef NO_ESCAPES_IN_PATHS
00229             if (string[j] == '\\' && string[j+1]) ++j
00230 #endif
00231             ;
00232     
00233         if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
00234 
00235         /* transfer string, unescaping any escaped seperators */
00236         for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
00237         {
00238 #ifndef NO_ESCAPES_IN_PATHS
00239             if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
00240             else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
00241             {
00242                 itemlist[i][k]=string[j];
00243                 ++j, ++k;
00244             }
00245 #endif
00246             itemlist[i][k] = string[j];
00247         }
00248         itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
00249         /* move to next string */
00250         string += j;
00251         if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
00252     }
00253     return itemlist;
00254 }
00255 
00261 static char** xdgGetPathListEnv(const char* name, const char ** strings)
00262 {
00263     const char* env;
00264     char* item;
00265     char** itemlist;
00266     int i, size;
00267 
00268     env = getenv(name);
00269     if (env && env[0])
00270     {
00271         if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
00272         strcpy(item, env);
00273 
00274         itemlist = xdgSplitPath(item);
00275         free(item);
00276     }
00277     else
00278     {
00279         if (!strings) return NULL;
00280         for (size = 0; strings[size]; ++size) ; ++size;
00281         if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
00282         xdgZeroMemory(itemlist, sizeof(char*)*(size));
00283 
00284         /* Copy defaults into itemlist. */
00285         /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
00286         for (i = 0; strings[i]; ++i)
00287         {
00288             if (!(item = (char*)malloc(strlen(strings[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
00289             strcpy(item, strings[i]);
00290             itemlist[i] = item;
00291         }
00292     }
00293     return itemlist;
00294 }
00295 
00300 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
00301 {
00302     const char* env;
00303     char* home, *defVal;
00304 
00305     env = getenv("HOME");
00306     if (!env || !env[0])
00307         return FALSE;
00308     if (!(home = (char*)malloc(strlen(env)+1))) return FALSE;
00309     strcpy(home, env);
00310 
00311     /* Allocate maximum needed for any of the 3 default values */
00312     defVal = (char*)malloc(strlen(home)+
00313         MAX(MAX(sizeof(DefaultRelativeDataHome), sizeof(DefaultRelativeConfigHome)), sizeof(DefaultRelativeCacheHome)));
00314     if (!defVal) return FALSE;
00315 
00316     strcpy(defVal, home);
00317     strcat(defVal, DefaultRelativeDataHome);
00318     if (!(cache->dataHome = xdgGetEnv("XDG_DATA_HOME", defVal))) return FALSE;
00319 
00320     defVal[strlen(home)] = 0;
00321     strcat(defVal, DefaultRelativeConfigHome);
00322     if (!(cache->configHome = xdgGetEnv("XDG_CONFIG_HOME", defVal))) return FALSE;
00323 
00324     defVal[strlen(home)] = 0;
00325     strcat(defVal, DefaultRelativeCacheHome);
00326     if (!(cache->cacheHome = xdgGetEnv("XDG_CACHE_HOME", defVal))) return FALSE;
00327 
00328     free(defVal);
00329     free(home);
00330 
00331     return TRUE;
00332 }
00333 
00338 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
00339 {
00340     char** itemlist;
00341     int size;
00342 
00343     itemlist = xdgGetPathListEnv("XDG_DATA_DIRS", DefaultDataDirectoriesList);
00344 
00345     if (!itemlist) return FALSE;
00346     for (size = 0; itemlist[size]; size++) ; /* Get list size */
00347     if (!(cache->searchableDataDirectories = (char**)malloc(sizeof(char*)*(size+2))))
00348     {
00349         xdgFreeStringList(itemlist);
00350         return FALSE;
00351     }
00352     /* "home" directory has highest priority according to spec */
00353     cache->searchableDataDirectories[0] = cache->dataHome;
00354     memcpy(&(cache->searchableDataDirectories[1]), itemlist, sizeof(char*)*(size+1));
00355     free(itemlist);
00356     
00357     itemlist = xdgGetPathListEnv("XDG_CONFIG_DIRS", DefaultConfigDirectoriesList);
00358     if (!itemlist) return FALSE;
00359     for (size = 0; itemlist[size]; size++) ; /* Get list size */
00360     if (!(cache->searchableConfigDirectories = (char**)malloc(sizeof(char*)*(size+2))))
00361     {
00362         xdgFreeStringList(itemlist);
00363         return FALSE;
00364     }
00365     cache->searchableConfigDirectories[0] = cache->configHome;
00366     memcpy(&(cache->searchableConfigDirectories[1]), itemlist, sizeof(char*)*(size+1));
00367     free(itemlist);
00368 
00369     return TRUE;
00370 }
00371 
00372 int xdgUpdateData(xdgHandle *handle)
00373 {
00374     xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
00375     xdgCachedData* oldCache;
00376     if (!cache) return FALSE;
00377     xdgZeroMemory(cache, sizeof(xdgCachedData));
00378 
00379     if (xdgUpdateHomeDirectories(cache) &&
00380         xdgUpdateDirectoryLists(cache))
00381     {
00382         /* Update successful, replace pointer to old cache with pointer to new cache */
00383         oldCache = xdgGetCache(handle);
00384         handle->reserved = cache;
00385         if (oldCache)
00386         {
00387             xdgFreeData(oldCache);
00388             free(oldCache);
00389         }
00390         return TRUE;
00391     }
00392     else
00393     {
00394         /* Update failed, discard new cache and leave old cache unmodified */
00395         xdgFreeData(cache);
00396         free(cache);
00397         return FALSE;
00398     }
00399 }
00400 
00407 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
00408 {
00409     char * fullPath;
00410     char * returnString = 0;
00411     char * tmpString;
00412     int strLen = 0;
00413     FILE * testFile;
00414     const char * const * item;
00415 
00416     for (item = dirList; *item; item++)
00417     {
00418         if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
00419         {
00420             if (returnString) free(returnString);
00421             return 0;
00422         }
00423         strcpy(fullPath, *item);
00424         if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
00425             strcat(fullPath, DIR_SEPARATOR_STR);
00426         strcat(fullPath, relativePath);
00427         testFile = fopen(fullPath, "r");
00428         if (testFile)
00429         {
00430             if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
00431             {
00432                 free(returnString);
00433                 free(fullPath);
00434                 return 0;
00435             }
00436             returnString = tmpString;
00437             strcpy(&returnString[strLen], fullPath);
00438             strLen = strLen+strlen(fullPath)+1;
00439             fclose(testFile);
00440         }
00441         free(fullPath);
00442     }
00443     if (returnString)
00444         returnString[strLen] = 0;
00445     else
00446     {
00447         if ((returnString = (char*)malloc(2)))
00448             strcpy(returnString, "\0");
00449     }
00450     return returnString;
00451 }
00452 
00459 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
00460 {
00461     char * fullPath;
00462     FILE * testFile;
00463     const char * const * item;
00464 
00465     for (item = dirList; *item; item++)
00466     {
00467         if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
00468             return 0;
00469         strcpy(fullPath, *item);
00470         if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
00471             strcat(fullPath, DIR_SEPARATOR_STR);
00472         strcat(fullPath, relativePath);
00473         testFile = fopen(fullPath, mode);
00474         free(fullPath);
00475         if (testFile)
00476             return testFile;
00477     }
00478     return 0;
00479 }
00480 
00481 int xdgMakePath(const char * path, mode_t mode)
00482 {
00483     int length = strlen(path);
00484     char * tmpPath;
00485     char * tmpPtr;
00486     int ret;
00487 
00488     if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
00489         return 0;
00490 
00491     if (!(tmpPath = (char*)malloc(length+1)))
00492     {
00493         errno = ENOMEM;
00494         return -1;
00495     }
00496     strcpy(tmpPath, path);
00497     if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
00498         tmpPath[length-1] = '\0';
00499 
00500     /* skip tmpPath[0] since if it's a seperator we have an absolute path */
00501     for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
00502     {
00503         if (*tmpPtr == DIR_SEPARATOR_CHAR)
00504         {
00505             *tmpPtr = '\0';
00506             if (mkdir(tmpPath, mode) == -1)
00507             {
00508                 if (errno != EEXIST)
00509                 {
00510                     free(tmpPath);
00511                     return -1;
00512                 }
00513             }
00514             *tmpPtr = DIR_SEPARATOR_CHAR;
00515         }
00516     }
00517     ret = mkdir(tmpPath, mode);
00518     free(tmpPath);
00519     return ret;
00520 }
00521 
00522 const char * xdgDataHome(xdgHandle *handle)
00523 {
00524     return xdgGetCache(handle)->dataHome;
00525 }
00526 const char * xdgConfigHome(xdgHandle *handle)
00527 {
00528     return xdgGetCache(handle)->configHome;
00529 }
00530 const char * const * xdgDataDirectories(xdgHandle *handle)
00531 {
00532     return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
00533 }
00534 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
00535 {
00536     return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
00537 }
00538 const char * const * xdgConfigDirectories(xdgHandle *handle)
00539 {
00540     return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
00541 }
00542 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
00543 {
00544     return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
00545 }
00546 const char * xdgCacheHome(xdgHandle *handle)
00547 {
00548     return xdgGetCache(handle)->cacheHome;
00549 }
00550 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
00551 {
00552     return xdgFindExisting(relativePath, xdgSearchableDataDirectories(handle));
00553 }
00554 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
00555 {
00556     return xdgFindExisting(relativePath, xdgSearchableConfigDirectories(handle));
00557 }
00558 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
00559 {
00560     return xdgFileOpen(relativePath, mode, xdgSearchableDataDirectories(handle));
00561 }
00562 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
00563 {
00564     return xdgFileOpen(relativePath, mode, xdgSearchableConfigDirectories(handle));
00565 }
00566