1 /* Copyright (c) 2007 Mark Nevill
3 * Permission is hereby granted, free of charge, to any person
4 * obtaining a copy of this software and associated documentation
5 * files (the "Software"), to deal in the Software without
6 * restriction, including without limitation the rights to use,
7 * copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
26 * @brief Implementation of the XDG basedir specification. */
32 #if STDC_HEADERS || HAVE_STDLIB_H
43 # endif /* !HAVE_STRINGS_H */
44 #endif /* !HAVE_STRING_H */
46 #if defined _WIN32 && !defined __CYGWIN__
47 /* Use Windows separators on all _WIN32 defining
48 environments, except Cygwin. */
49 # define DIR_SEPARATOR_CHAR '\\'
50 # define DIR_SEPARATOR_STR "\\"
51 # define PATH_SEPARATOR_CHAR ';'
52 # define PATH_SEPARATOR_STR ";"
53 # define NO_ESCAPES_IN_PATHS
55 # define DIR_SEPARATOR_CHAR '/'
56 # define DIR_SEPARATOR_STR "/"
57 # define PATH_SEPARATOR_CHAR ':'
58 # define PATH_SEPARATOR_STR ":"
65 #define MAX(a, b) ((b) > (a) ? (b) : (a))
69 DefaultRelativeDataHome
[] = DIR_SEPARATOR_STR
".local" DIR_SEPARATOR_STR
"share",
70 DefaultRelativeConfigHome
[] = DIR_SEPARATOR_STR
".config",
71 DefaultDataDirectories1
[] = DIR_SEPARATOR_STR
"usr" DIR_SEPARATOR_STR
"local" DIR_SEPARATOR_STR
"share",
72 DefaultDataDirectories2
[] = DIR_SEPARATOR_STR
"usr" DIR_SEPARATOR_STR
"share",
73 DefaultConfigDirectories
[] = DIR_SEPARATOR_STR
"etc" DIR_SEPARATOR_STR
"xdg",
74 DefaultRelativeCacheHome
[] = DIR_SEPARATOR_STR
".cache";
76 typedef struct _xdgCachedData
81 /* Note: string lists are null-terminated and all items */
82 /* except the first are assumed to be allocated using malloc. */
83 /* The first item is assumed to be allocated by malloc only if */
84 /* it is not equal to the appropriate home directory string above. */
85 char ** searchableDataDirectories
;
86 char ** searchableConfigDirectories
;
89 #define GET_CACHE(handle) ((xdgCachedData*)(handle->reserved))
91 xdgHandle
xdgAllocHandle()
93 xdgHandle handle
= (xdgHandle
)malloc(sizeof(*handle
));
94 if (!handle
) return 0;
95 handle
->reserved
= 0; /* So xdgUpdateData() doesn't free it */
96 if (xdgUpdateData(handle
))
103 /** Free all memory used by a NULL-terminated string list */
104 static void xdgFreeStringList(char** list
)
113 /** Free all data in the cache and set pointers to null. */
114 static void xdgFreeData(xdgCachedData
*cache
)
116 if (cache
->dataHome
);
118 /* the first element of the directory lists is usually the home directory */
119 if (cache
->searchableDataDirectories
[0] != cache
->dataHome
)
120 free(cache
->dataHome
);
123 if (cache
->configHome
);
125 if (cache
->searchableConfigDirectories
[0] != cache
->configHome
)
126 free(cache
->configHome
);
127 cache
->configHome
= 0;
129 xdgFreeStringList(cache
->searchableDataDirectories
);
130 cache
->searchableDataDirectories
= 0;
131 xdgFreeStringList(cache
->searchableConfigDirectories
);
132 cache
->searchableConfigDirectories
= 0;
135 void xdgFreeHandle(xdgHandle handle
)
137 xdgCachedData
* cache
= (xdgCachedData
*)(handle
->reserved
);
143 /** Get value for environment variable $name, defaulting to "defaultValue".
144 * @param name Name of environment variable.
145 * @param defaultValue Value to assume for environment variable if it is
148 static char* xdgGetEnv(const char* name
, const char* defaultValue
)
156 if (!(value
= (char*)malloc(strlen(env
)+1))) return 0;
161 if (!(value
= (char*)malloc(strlen(defaultValue
)+1))) return 0;
162 strcpy(value
, defaultValue
);
167 /** Split string at ':', return null-terminated list of resulting strings.
168 * @param string String to be split
170 static char** xdgSplitPath(const char* string
)
172 unsigned int size
, i
, j
, k
;
175 /* Get the number of paths */
176 size
=2; /* One item more than seperators + terminating null item */
177 for (i
= 0; string
[i
]; ++i
)
179 #ifndef NO_ESCAPES_IN_PATHS
180 if (string
[i
] == '\\' && string
[i
+1]) ++i
; /* skip escaped characters including seperators */
182 else if (string
[i
] == PATH_SEPARATOR_CHAR
) ++size
;
185 if (!(itemlist
= (char**)malloc(sizeof(char*)*size
))) return 0;
186 memset(itemlist
, 0, sizeof(char*)*size
);
188 for (i
= 0; *string
; ++i
)
190 /* get length of current string */
191 for (j
= 0; string
[j
] && string
[j
] != PATH_SEPARATOR_CHAR
; ++j
)
192 #ifndef NO_ESCAPES_IN_PATHS
193 if (string
[j
] == '\\' && string
[j
+1]) ++j
197 if (!(itemlist
[i
] = (char*)malloc(j
+1))) { xdgFreeStringList(itemlist
); return 0; }
199 /* transfer string, unescaping any escaped seperators */
200 for (k
= j
= 0; string
[j
] && string
[j
] != PATH_SEPARATOR_CHAR
; ++j
, ++k
)
202 #ifndef NO_ESCAPES_IN_PATHS
203 if (string
[j
] == '\\' && string
[j
+1] == PATH_SEPARATOR_CHAR
) ++j
; /* replace escaped ':' with just ':' */
204 else if (string
[j
] == '\\' && string
[j
+1]) /* skip escaped characters so escaping remains aligned to pairs. */
206 itemlist
[i
][k
]=string
[j
];
210 itemlist
[i
][k
] = string
[j
];
212 /* move to next string */
214 if (*string
== PATH_SEPARATOR_CHAR
) string
++; /* skip seperator */
219 /** Get $PATH-style environment variable as list of strings.
220 * If $name is unset or empty, use default strings specified by variable arguments.
221 * @param name Name of environment variable
222 * @param numDefaults Number of default paths in variable argument list
223 * @param ... numDefaults number of strings to be copied and used as defaults
225 static char** xdgGetPathListEnv(const char* name
, int numDefaults
, ...)
237 if (!(item
= (char*)malloc(strlen(env
)+1))) return 0;
240 itemlist
= xdgSplitPath(item
);
245 if (!(itemlist
= (char**)malloc(sizeof(char*)*numDefaults
+1))) return 0;
246 memset(itemlist
, 0, sizeof(char*)*(numDefaults
+1));
248 /* Copy the varargs into the itemlist */
249 va_start(ap
, numDefaults
);
250 for (i
= 0; i
< numDefaults
; i
++)
252 arg
= va_arg(ap
, const char*);
253 if (!(item
= (char*)malloc(strlen(arg
)+1))) { xdgFreeStringList(itemlist
); return 0; }
262 /** Update all *Home variables of cache.
263 * This includes xdgCachedData::dataHome, xdgCachedData::configHome and xdgCachedData::cacheHome.
264 * @param cache Data cache to be updated
266 static bool xdgUpdateHomeDirectories(xdgCachedData
* cache
)
271 env
= getenv("HOME");
274 if (!(home
= (char*)malloc(strlen(env
)+1))) return false;
277 /* Allocate maximum needed for any of the 3 default values */
278 defVal
= (char*)malloc(strlen(home
)+
279 MAX(MAX(sizeof(DefaultRelativeDataHome
), sizeof(DefaultRelativeConfigHome
)), sizeof(DefaultRelativeCacheHome
)));
280 if (!defVal
) return false;
282 strcpy(defVal
, home
);
283 strcat(defVal
, DefaultRelativeDataHome
);
284 if (!(cache
->dataHome
= xdgGetEnv("XDG_DATA_HOME", defVal
))) return false;
286 defVal
[strlen(home
)] = 0;
287 strcat(defVal
, DefaultRelativeConfigHome
);
288 if (!(cache
->configHome
= xdgGetEnv("XDG_CONFIG_HOME", defVal
))) return false;
290 defVal
[strlen(home
)] = 0;
291 strcat(defVal
, DefaultRelativeCacheHome
);
292 if (!(cache
->cacheHome
= xdgGetEnv("XDG_CACHE_HOME", defVal
))) return false;
300 /** Update all *Directories variables of cache.
301 * This includes xdgCachedData::searchableDataDirectories and xdgCachedData::searchableConfigDirectories.
302 * @param cache Data cache to be updated.
304 static bool xdgUpdateDirectoryLists(xdgCachedData
* cache
)
309 itemlist
= xdgGetPathListEnv("XDG_DATA_DIRS", 2,
310 DefaultDataDirectories1
, DefaultDataDirectories2
);
311 if (!itemlist
) return false;
312 for (size
= 0; itemlist
[size
]; size
++) ; /* Get list size */
313 if (!(cache
->searchableDataDirectories
= (char**)malloc(sizeof(char*)*(size
+2))))
315 xdgFreeStringList(itemlist
);
318 /* "home" directory has highest priority according to spec */
319 cache
->searchableDataDirectories
[0] = cache
->dataHome
;
320 memcpy(&(cache
->searchableDataDirectories
[1]), itemlist
, sizeof(char*)*(size
+1));
323 itemlist
= xdgGetPathListEnv("XDG_CONFIG_DIRS", 1, DefaultConfigDirectories
);
324 if (!itemlist
) return false;
325 for (size
= 0; itemlist
[size
]; size
++) ; /* Get list size */
326 if (!(cache
->searchableConfigDirectories
= (char**)malloc(sizeof(char*)*(size
+2))))
328 xdgFreeStringList(itemlist
);
331 cache
->searchableConfigDirectories
[0] = cache
->configHome
;
332 memcpy(&(cache
->searchableConfigDirectories
[1]), itemlist
, sizeof(char*)*(size
+1));
338 bool xdgUpdateData(xdgHandle handle
)
340 xdgCachedData
* cache
= (xdgCachedData
*)malloc(sizeof(xdgCachedData
));
341 if (!cache
) return false;
342 memset(cache
, 0, sizeof(xdgCachedData
));
344 if (xdgUpdateHomeDirectories(cache
) &&
345 xdgUpdateDirectoryLists(cache
))
347 /* Update successful, replace pointer to old cache with pointer to new cache */
348 if (handle
->reserved
) free(handle
->reserved
);
349 handle
->reserved
= cache
;
354 /* Update failed, discard new cache and leave old cache unmodified */
361 /** Find all existing files corresponding to relativePath relative to each item in dirList.
362 * @param relativePath Relative path to search for.
363 * @param dirList <tt>NULL</tt>-terminated list of directory paths.
364 * @return A sequence of null-terminated strings terminated by a
365 * double-<tt>NULL</tt> (empty string) and allocated using malloc().
367 static const char* xdgFindExisting(const char * relativePath
, const char * const * dirList
)
370 char * returnString
= 0;
374 const char * const * item
;
376 for (item
= dirList
; *item
; item
++)
378 if (!(fullPath
= (char*)malloc(strlen(*item
)+strlen(relativePath
)+2)))
380 if (returnString
) free(returnString
);
383 strcpy(fullPath
, *item
);
384 if (fullPath
[strlen(fullPath
)-1] != DIR_SEPARATOR_CHAR
)
385 strcat(fullPath
, DIR_SEPARATOR_STR
);
386 strcat(fullPath
, relativePath
);
387 testFile
= fopen(fullPath
, "r");
390 if (!(tmpString
= (char*)realloc(returnString
, strLen
+strlen(fullPath
)+2)))
396 returnString
= tmpString
;
397 strcpy(&returnString
[strLen
], fullPath
);
398 strLen
= strLen
+strlen(fullPath
)+1;
404 returnString
[strLen
] = 0;
407 if ((returnString
= (char*)malloc(2)))
408 strcpy(returnString
, "\0");
413 /** Open first possible config file corresponding to relativePath.
414 * @param relativePath Path to scan for.
415 * @param mode Mode with which to attempt to open files (see fopen modes).
416 * @param dirList <tt>NULL</tt>-terminated list of paths in which to search for relativePath.
417 * @return File pointer if successful else @c NULL. Client must use @c fclose to close file.
419 static FILE * xdgFileOpen(const char * relativePath
, const char * mode
, const char * const * dirList
)
423 const char * const * item
;
425 for (item
= dirList
; *item
; item
++)
427 if (fullPath
= (char*)malloc(strlen(*item
)+strlen(relativePath
)+2))
429 strcpy(fullPath
, *item
);
430 if (fullPath
[strlen(fullPath
)-1] != DIR_SEPARATOR_CHAR
)
431 strcat(fullPath
, DIR_SEPARATOR_STR
);
432 strcat(fullPath
, relativePath
);
433 testFile
= fopen(fullPath
, mode
);
441 const char * xdgDataHome(xdgHandle handle
)
443 return GET_CACHE(handle
)->dataHome
;
445 const char * xdgConfigHome(xdgHandle handle
)
447 return GET_CACHE(handle
)->configHome
;
449 const char * const * xdgDataDirectories(xdgHandle handle
)
451 return &(GET_CACHE(handle
)->searchableDataDirectories
[1]);
453 const char * const * xdgSearchableDataDirectories(xdgHandle handle
)
455 return GET_CACHE(handle
)->searchableDataDirectories
;
457 const char * const * xdgConfigDirectories(xdgHandle handle
)
459 return &(GET_CACHE(handle
)->searchableConfigDirectories
[1]);
461 const char * const * xdgSearchableConfigDirectories(xdgHandle handle
)
463 return GET_CACHE(handle
)->searchableConfigDirectories
;
465 const char * xdgCacheHome(xdgHandle handle
)
467 return GET_CACHE(handle
)->cacheHome
;
469 const char * xdgDataFind(const char * relativePath
, xdgHandle handle
)
471 return xdgFindExisting(relativePath
, xdgSearchableDataDirectories(handle
));
473 const char * xdgConfigFind(const char * relativePath
, xdgHandle handle
)
475 return xdgFindExisting(relativePath
, xdgSearchableConfigDirectories(handle
));
477 FILE * xdgDataOpen(const char * relativePath
, const char * mode
, xdgHandle handle
)
479 return xdgFileOpen(relativePath
, mode
, xdgSearchableDataDirectories(handle
));
481 FILE * xdgConfigOpen(const char * relativePath
, const char * mode
, xdgHandle handle
)
483 return xdgFileOpen(relativePath
, mode
, xdgSearchableConfigDirectories(handle
));