libxdg-basedir-1.2.0  1.2.0
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     char * runtimeDirectory;
00109     /* Note: string lists are null-terminated and all items */
00110     /* except the first are assumed to be allocated using malloc. */
00111     /* The first item is assumed to be allocated by malloc only if */
00112     /* it is not equal to the appropriate home directory string above. */
00113     char ** searchableDataDirectories;
00114     char ** searchableConfigDirectories; 
00115 } xdgCachedData;
00116 
00118 static xdgCachedData* xdgGetCache(xdgHandle *handle)
00119 {
00120     return ((xdgCachedData*)(handle->reserved));
00121 }
00122 
00123 xdgHandle * xdgInitHandle(xdgHandle *handle)
00124 {
00125     if (!handle) return 0;
00126     handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
00127     if (xdgUpdateData(handle))
00128         return handle;
00129     return 0;
00130 }
00131 
00133 static void xdgFreeStringList(char** list)
00134 {
00135     char** ptr = list;
00136     if (!list) return;
00137     for (; *ptr; ptr++)
00138         free(*ptr);
00139     free(list);
00140 }
00141 
00143 static void xdgFreeData(xdgCachedData *cache)
00144 {
00145     if (cache->dataHome);
00146     {
00147         /* the first element of the directory lists is usually the home directory */
00148         if (cache->searchableDataDirectories && cache->searchableDataDirectories[0] != cache->dataHome)
00149             free(cache->dataHome);
00150         cache->dataHome = 0;
00151     }
00152     if (cache->configHome);
00153     {
00154         if (cache->searchableConfigDirectories && cache->searchableConfigDirectories[0] != cache->configHome)
00155             free(cache->configHome);
00156         cache->configHome = 0;
00157     }
00158     if (cache->cacheHome)
00159     {
00160         free(cache->cacheHome);
00161         cache->cacheHome = 0;
00162     }
00163     xdgFreeStringList(cache->searchableDataDirectories);
00164     cache->searchableDataDirectories = 0;
00165     xdgFreeStringList(cache->searchableConfigDirectories);
00166     cache->searchableConfigDirectories = 0;
00167 }
00168 
00169 void xdgWipeHandle(xdgHandle *handle)
00170 {
00171     xdgCachedData* cache = xdgGetCache(handle);
00172     xdgFreeData(cache);
00173     free(cache);
00174 }
00175 
00179 static char** xdgSplitPath(const char* string)
00180 {
00181     unsigned int size, i, j, k;
00182     char** itemlist;
00183 
00184     /* Get the number of paths */
00185     size=2; /* One item more than seperators + terminating null item */
00186     for (i = 0; string[i]; ++i)
00187     {
00188 #ifndef NO_ESCAPES_IN_PATHS
00189         if (string[i] == '\\' && string[i+1])
00190         {
00191             /* skip escaped characters including seperators */
00192             ++i;
00193             continue;
00194         }
00195 #endif
00196         if (string[i] == PATH_SEPARATOR_CHAR) ++size;
00197     }
00198     
00199     if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
00200     xdgZeroMemory(itemlist, sizeof(char*)*size);
00201 
00202     for (i = 0; *string; ++i)
00203     {
00204         /* get length of current string  */
00205         for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
00206 #ifndef NO_ESCAPES_IN_PATHS
00207             if (string[j] == '\\' && string[j+1]) ++j
00208 #endif
00209             ;
00210     
00211         if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
00212 
00213         /* transfer string, unescaping any escaped seperators */
00214         for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
00215         {
00216 #ifndef NO_ESCAPES_IN_PATHS
00217             if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
00218             else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
00219             {
00220                 itemlist[i][k]=string[j];
00221                 ++j, ++k;
00222             }
00223 #endif
00224             itemlist[i][k] = string[j];
00225         }
00226         itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
00227         /* move to next string */
00228         string += j;
00229         if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
00230     }
00231     return itemlist;
00232 }
00233 
00239 static char** xdgGetPathListEnv(const char* name, const char ** defaults)
00240 {
00241     const char* env;
00242     char* item;
00243     char** itemlist;
00244     int i, size;
00245 
00246     env = getenv(name);
00247     if (env && env[0])
00248     {
00249         if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
00250         strcpy(item, env);
00251 
00252         itemlist = xdgSplitPath(item);
00253         free(item);
00254     }
00255     else
00256     {
00257         if (!defaults) return NULL;
00258         for (size = 0; defaults[size]; ++size) ; ++size;
00259         if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
00260         xdgZeroMemory(itemlist, sizeof(char*)*(size));
00261 
00262         /* Copy defaults into itemlist. */
00263         /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
00264         for (i = 0; defaults[i]; ++i)
00265         {
00266             if (!(item = (char*)malloc(strlen(defaults[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
00267             strcpy(item, defaults[i]);
00268             itemlist[i] = item;
00269         }
00270     }
00271     return itemlist;
00272 }
00273 
00279 static char* xdgGetEnv(const char *name)
00280 {
00281     char *env = getenv(name);
00282     if (env && env[0])
00283         return env;
00284     /* What errno signifies missing env var? */
00285     errno = EINVAL;
00286     return NULL;
00287 }
00288 
00294 static char* xdgEnvDup(const char *name)
00295 {
00296     const char *env;
00297     if ((env = xdgGetEnv(name)))
00298         return strdup(env);
00299     else
00300         return NULL;
00301 }
00302 
00307 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
00308 {
00309     const char *homeenv;
00310     char *value;
00311     unsigned int homelen;
00312     static const unsigned int extralen =
00313         MAX(MAX(sizeof(DefaultRelativeDataHome),
00314                     sizeof(DefaultRelativeConfigHome)),
00315                 sizeof(DefaultRelativeCacheHome));
00316 
00317     if (!(cache->dataHome = xdgEnvDup("XDG_DATA_HOME")) && errno == ENOMEM) return FALSE;
00318     if (!(cache->configHome = xdgEnvDup("XDG_CONFIG_HOME")) && errno == ENOMEM) return FALSE;
00319     if (!(cache->cacheHome = xdgEnvDup("XDG_CACHE_HOME")) && errno == ENOMEM) return FALSE;
00320     if (!(cache->runtimeDirectory = xdgEnvDup("XDG_RUNTIME_DIR")) && errno == ENOMEM) return FALSE;
00321     errno = 0;
00322 
00323     if (cache->dataHome && cache->configHome && cache->cacheHome) return TRUE;
00324 
00325     if (!(homeenv = xdgGetEnv("HOME")))
00326         return FALSE;
00327 
00328     /* Allocate maximum needed for any of the 3 default values */
00329     if (!(value = (char*)malloc((homelen = strlen(homeenv))+extralen))) return FALSE;
00330     memcpy(value, homeenv, homelen+1);
00331 
00332     if (!cache->dataHome)
00333     {
00334         memcpy(value+homelen, DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome));
00335         cache->dataHome = strdup(value);
00336     }
00337 
00338     if (!cache->configHome)
00339     {
00340         memcpy(value+homelen, DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome));
00341         cache->configHome = strdup(value);
00342     }
00343 
00344     if (!cache->cacheHome)
00345     {
00346         memcpy(value+homelen, DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome));
00347         cache->cacheHome = strdup(value);
00348     }
00349 
00350     free(value);
00351 
00352     /* free does not change errno, and the prev call *must* have been a strdup,
00353      * so errno is already set. */
00354     return cache->dataHome && cache->configHome && cache->cacheHome;
00355 }
00356 
00368 static char** xdgGetDirectoryLists(const char *envname, char *homedir, const char **defaults)
00369 {
00370     char **envlist;
00371     char **dirlist;
00372     unsigned int size;
00373 
00374     if (!(envlist = xdgGetPathListEnv(envname, defaults)))
00375         return NULL;
00376 
00377     for (size = 0; envlist[size]; size++) ; /* Get list size */
00378     if (!(dirlist = (char**)malloc(sizeof(char*)*(size+1+!!homedir))))
00379     {
00380         xdgFreeStringList(envlist);
00381         return NULL;
00382     }
00383     /* "home" directory has highest priority according to spec */
00384     if (homedir)
00385         dirlist[0] = homedir;
00386     memcpy(dirlist+!!homedir, envlist, sizeof(char*)*(size+1));
00387     /* only free the envlist since its elements are now referenced by dirlist */
00388     free(envlist);
00389 
00390     return dirlist;
00391 }
00392 
00397 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
00398 {
00399     if (!(cache->searchableDataDirectories = xdgGetDirectoryLists(
00400             "XDG_DATA_DIRS", cache->dataHome, DefaultDataDirectoriesList)))
00401         return FALSE;
00402     if (!(cache->searchableConfigDirectories = xdgGetDirectoryLists(
00403             "XDG_CONFIG_DIRS", cache->configHome, DefaultConfigDirectoriesList)))
00404         return FALSE;
00405 
00406     return TRUE;
00407 }
00408 
00409 int xdgUpdateData(xdgHandle *handle)
00410 {
00411     xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
00412     xdgCachedData* oldCache;
00413     if (!cache) return FALSE;
00414     xdgZeroMemory(cache, sizeof(xdgCachedData));
00415 
00416     if (xdgUpdateHomeDirectories(cache) &&
00417         xdgUpdateDirectoryLists(cache))
00418     {
00419         /* Update successful, replace pointer to old cache with pointer to new cache */
00420         oldCache = xdgGetCache(handle);
00421         handle->reserved = cache;
00422         if (oldCache)
00423         {
00424             xdgFreeData(oldCache);
00425             free(oldCache);
00426         }
00427         return TRUE;
00428     }
00429     else
00430     {
00431         /* Update failed, discard new cache and leave old cache unmodified */
00432         xdgFreeData(cache);
00433         free(cache);
00434         return FALSE;
00435     }
00436 }
00437 
00444 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
00445 {
00446     char * fullPath;
00447     char * returnString = 0;
00448     char * tmpString;
00449     int strLen = 0;
00450     FILE * testFile;
00451     const char * const * item;
00452 
00453     for (item = dirList; *item; item++)
00454     {
00455         if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
00456         {
00457             if (returnString) free(returnString);
00458             return 0;
00459         }
00460         strcpy(fullPath, *item);
00461         if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
00462             strcat(fullPath, DIR_SEPARATOR_STR);
00463         strcat(fullPath, relativePath);
00464         testFile = fopen(fullPath, "r");
00465         if (testFile)
00466         {
00467             if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
00468             {
00469                 free(returnString);
00470                 free(fullPath);
00471                 return 0;
00472             }
00473             returnString = tmpString;
00474             strcpy(&returnString[strLen], fullPath);
00475             strLen = strLen+strlen(fullPath)+1;
00476             fclose(testFile);
00477         }
00478         free(fullPath);
00479     }
00480     if (returnString)
00481         returnString[strLen] = 0;
00482     else
00483     {
00484         if ((returnString = (char*)malloc(2)))
00485             strcpy(returnString, "\0");
00486     }
00487     return returnString;
00488 }
00489 
00496 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
00497 {
00498     char * fullPath;
00499     FILE * testFile;
00500     const char * const * item;
00501 
00502     for (item = dirList; *item; item++)
00503     {
00504         if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
00505             return 0;
00506         strcpy(fullPath, *item);
00507         if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
00508             strcat(fullPath, DIR_SEPARATOR_STR);
00509         strcat(fullPath, relativePath);
00510         testFile = fopen(fullPath, mode);
00511         free(fullPath);
00512         if (testFile)
00513             return testFile;
00514     }
00515     return 0;
00516 }
00517 
00518 int xdgMakePath(const char * path, mode_t mode)
00519 {
00520     int length = strlen(path);
00521     char * tmpPath;
00522     char * tmpPtr;
00523     int ret;
00524 
00525     if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
00526         return 0;
00527 
00528     if (!(tmpPath = (char*)malloc(length+1)))
00529     {
00530         errno = ENOMEM;
00531         return -1;
00532     }
00533     strcpy(tmpPath, path);
00534     if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
00535         tmpPath[length-1] = '\0';
00536 
00537     /* skip tmpPath[0] since if it's a seperator we have an absolute path */
00538     for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
00539     {
00540         if (*tmpPtr == DIR_SEPARATOR_CHAR)
00541         {
00542             *tmpPtr = '\0';
00543             if (mkdir(tmpPath, mode) == -1)
00544             {
00545                 if (errno != EEXIST)
00546                 {
00547                     free(tmpPath);
00548                     return -1;
00549                 }
00550             }
00551             *tmpPtr = DIR_SEPARATOR_CHAR;
00552         }
00553     }
00554     ret = mkdir(tmpPath, mode);
00555     free(tmpPath);
00556     return ret;
00557 }
00558 
00567 static char * xdgGetRelativeHome(const char *envname, const char *relativefallback, unsigned int fallbacklength)
00568 {
00569     char *relhome;
00570     if (!(relhome = xdgEnvDup(envname)) && errno != ENOMEM)
00571     {
00572         errno = 0;
00573         const char *home;
00574         unsigned int homelen;
00575         if (!(home = xdgGetEnv("HOME")))
00576             return NULL;
00577         if (!(relhome = (char*)malloc((homelen = strlen(home))+fallbacklength))) return NULL;
00578         memcpy(relhome, home, homelen);
00579         memcpy(relhome+homelen, relativefallback, fallbacklength+1);
00580     }
00581     return relhome;
00582 }
00583 
00584 const char * xdgDataHome(xdgHandle *handle)
00585 {
00586     if (handle)
00587         return xdgGetCache(handle)->dataHome;
00588     else
00589         return xdgGetRelativeHome("XDG_DATA_HOME", DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome)-1);
00590 }
00591 const char * xdgConfigHome(xdgHandle *handle)
00592 {
00593     if (handle)
00594         return xdgGetCache(handle)->configHome;
00595     else
00596         return xdgGetRelativeHome("XDG_CONFIG_HOME", DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome)-1);
00597 }
00598 const char * const * xdgDataDirectories(xdgHandle *handle)
00599 {
00600     if (handle)
00601         return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
00602     else
00603         return (const char * const *)xdgGetDirectoryLists("XDG_DATA_DIRS", NULL, DefaultDataDirectoriesList);
00604 }
00605 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
00606 {
00607     if (handle)
00608         return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
00609     else
00610     {
00611         char *datahome = (char*)xdgDataHome(NULL);
00612         char **datadirs = 0;
00613         if (datahome && !(datadirs = xdgGetDirectoryLists("XDG_DATA_DIRS", datahome, DefaultDataDirectoriesList)))
00614             free(datahome);
00615         return (const char * const *)datadirs;
00616     }
00617 }
00618 const char * const * xdgConfigDirectories(xdgHandle *handle)
00619 {
00620     if (handle)
00621         return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
00622     else
00623         return (const char * const *)xdgGetDirectoryLists("XDG_CONFIG_DIRS", NULL, DefaultConfigDirectoriesList);
00624 }
00625 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
00626 {
00627     if (handle)
00628         return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
00629     else
00630     {
00631         char *confighome = (char*)xdgConfigHome(NULL);
00632         char **configdirs = 0;
00633         if (confighome && !(configdirs = xdgGetDirectoryLists("XDG_CONFIG_DIRS", confighome, DefaultConfigDirectoriesList)))
00634             free(confighome);
00635         return (const char * const *)configdirs;
00636     }
00637 }
00638 const char * xdgCacheHome(xdgHandle *handle)
00639 {
00640     if (handle)
00641         return xdgGetCache(handle)->cacheHome;
00642     else
00643         return xdgGetRelativeHome("XDG_CACHE_HOME", DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome)-1);
00644 }
00645 const char * xdgRuntimeDirectory(xdgHandle *handle)
00646 {
00647     if (handle)
00648         return xdgGetCache(handle)->runtimeDirectory;
00649     else
00650         return xdgEnvDup("XDG_RUNTIME_DIRECTORY");
00651 }
00652 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
00653 {
00654     const char * const * dirs = xdgSearchableDataDirectories(handle);
00655     char * result = xdgFindExisting(relativePath, dirs);
00656     if (!handle) xdgFreeStringList((char**)dirs);
00657     return result;
00658 }
00659 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
00660 {
00661     const char * const * dirs = xdgSearchableConfigDirectories(handle);
00662     char * result = xdgFindExisting(relativePath, dirs);
00663     if (!handle) xdgFreeStringList((char**)dirs);
00664     return result;
00665 }
00666 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
00667 {
00668     const char * const * dirs = xdgSearchableDataDirectories(handle);
00669     FILE * result = xdgFileOpen(relativePath, mode, dirs);
00670     if (!handle) xdgFreeStringList((char**)dirs);
00671     return result;
00672 }
00673 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
00674 {
00675     const char * const * dirs = xdgSearchableConfigDirectories(handle);
00676     FILE * result = xdgFileOpen(relativePath, mode, dirs);
00677     if (!handle) xdgFreeStringList((char**)dirs);
00678     return result;
00679 }
00680