libxdg-basedir-1.2.0
1.2.0
|
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