1 /* Copyright (c) 2007 Mark Nevill
2 *
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
10 * conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
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.
23 */
24
25 /** @file basedir.c
26 * @brief Implementation of the XDG Base Directory specification. */
27
28 #if defined(HAVE_CONFIG_H) || defined(_DOXYGEN)
29 #include <config.h>
30 #endif
31
32 #if STDC_HEADERS || HAVE_STDLIB_H || !defined(HAVE_CONFIG_H)
33 # include <stdlib.h>
34 #endif
35 #if HAVE_MEMORY_H || !defined(HAVE_CONFIG_H)
36 # include <memory.h>
37 #endif
38 #if HAVE_STRING_H || !defined(HAVE_CONFIG_H)
39 # include <string.h>
40 #endif
41 #if HAVE_STRINGS_H
42 # include <strings.h>
43 #endif
44
45 #include <sys/stat.h>
46 #include <sys/types.h>
47
48 #include <errno.h>
49
50 #ifdef FALSE
51 #undef FALSE
52 #endif
53 #ifdef TRUE
54 #undef TRUE
55 #endif
56 #define FALSE 0
57 #define TRUE 1
58
59 #if HAVE_MEMSET || !defined(HAVE_CONFIG_H)
60 # define xdgZeroMemory(p, n) memset(p, 0, n)
61 #elif HAVE_BZERO
62 # define xdgZeroMemory(p, n) bzero(p, n)
63 #else
xdgZeroMemory(void * p,size_t n)64 static void xdgZeroMemory(void* p, size_t n)
65 {
66 while (n > 0) { ((char*)p)[n] = 0; ++n; }
67 }
68 #endif
69
70 #if defined _WIN32 && !defined __CYGWIN__
71 /* Use Windows separators on all _WIN32 defining
72 environments, except Cygwin. */
73 # define DIR_SEPARATOR_CHAR '\\'
74 # define DIR_SEPARATOR_STR "\\"
75 # define PATH_SEPARATOR_CHAR ';'
76 # define PATH_SEPARATOR_STR ";"
77 # define NO_ESCAPES_IN_PATHS
78 #else
79 # define DIR_SEPARATOR_CHAR '/'
80 # define DIR_SEPARATOR_STR "/"
81 # define PATH_SEPARATOR_CHAR ':'
82 # define PATH_SEPARATOR_STR ":"
83 # define NO_ESCAPES_IN_PATHS
84 #endif
85
86 #include <basedir.h>
87 #include <basedir_fs.h>
88
89 #ifndef MAX
90 #define MAX(a, b) ((b) > (a) ? (b) : (a))
91 #endif
92
93 static const char
94 DefaultRelativeDataHome[] = DIR_SEPARATOR_STR ".local" DIR_SEPARATOR_STR "share",
95 DefaultRelativeConfigHome[] = DIR_SEPARATOR_STR ".config",
96 DefaultDataDirectories1[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "local" DIR_SEPARATOR_STR "share",
97 DefaultDataDirectories2[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "share",
98 DefaultConfigDirectories[] = DIR_SEPARATOR_STR "etc" DIR_SEPARATOR_STR "xdg",
99 DefaultRelativeCacheHome[] = DIR_SEPARATOR_STR ".cache";
100
101 static const char
102 *DefaultDataDirectoriesList[] = { DefaultDataDirectories1, DefaultDataDirectories2, NULL },
103 *DefaultConfigDirectoriesList[] = { DefaultConfigDirectories, NULL };
104
105 typedef struct _xdgCachedData
106 {
107 char * dataHome;
108 char * configHome;
109 char * cacheHome;
110 /* Note: string lists are null-terminated and all items */
111 /* except the first are assumed to be allocated using malloc. */
112 /* The first item is assumed to be allocated by malloc only if */
113 /* it is not equal to the appropriate home directory string above. */
114 char ** searchableDataDirectories;
115 char ** searchableConfigDirectories;
116 } xdgCachedData;
117
118 /** Get cache object associated with a handle */
xdgGetCache(xdgHandle * handle)119 static xdgCachedData* xdgGetCache(xdgHandle *handle)
120 {
121 return ((xdgCachedData*)(handle->reserved));
122 }
123
xdgInitHandle(xdgHandle * handle)124 xdgHandle * xdgInitHandle(xdgHandle *handle)
125 {
126 if (!handle) return 0;
127 handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
128 if (xdgUpdateData(handle))
129 return handle;
130 return 0;
131 }
132
133 /** Free all memory used by a NULL-terminated string list */
xdgFreeStringList(char ** list)134 static void xdgFreeStringList(char** list)
135 {
136 char** ptr = list;
137 if (!list) return;
138 for (; *ptr; ptr++)
139 free(*ptr);
140 free(list);
141 }
142
143 /** Free all data in the cache and set pointers to null. */
xdgFreeData(xdgCachedData * cache)144 static void xdgFreeData(xdgCachedData *cache)
145 {
146 /* if (cache->dataHome) */
147 {
148 /* the first element of the directory lists is usually the home directory */
149 if (cache->searchableDataDirectories[0] != cache->dataHome)
150 free(cache->dataHome);
151 cache->dataHome = NULL;
152 }
153 /* if (cache->configHome) */
154 {
155 if (cache->searchableConfigDirectories[0] != cache->configHome)
156 free(cache->configHome);
157 cache->configHome = NULL;
158 }
159 if (cache->cacheHome)
160 {
161 free(cache->cacheHome);
162 cache->cacheHome = NULL;
163 }
164 xdgFreeStringList(cache->searchableDataDirectories);
165 cache->searchableDataDirectories = NULL;
166 xdgFreeStringList(cache->searchableConfigDirectories);
167 cache->searchableConfigDirectories = NULL;
168 }
169
xdgWipeHandle(xdgHandle * handle)170 void xdgWipeHandle(xdgHandle *handle)
171 {
172 xdgCachedData* cache = xdgGetCache(handle);
173 xdgFreeData(cache);
174 free(cache);
175 }
176
177 /** Get value for environment variable $name, defaulting to "defaultValue".
178 * @param name Name of environment variable.
179 * @param defaultValue Value to assume for environment variable if it is
180 * unset or empty.
181 */
xdgGetEnv(const char * name,const char * defaultValue)182 static char* xdgGetEnv(const char* name, const char* defaultValue)
183 {
184 const char* env;
185 char* value;
186
187 env = getenv(name);
188 if (env && env[0])
189 {
190 if (!(value = (char*)malloc(strlen(env)+1))) return NULL;
191 strcpy(value, env);
192 }
193 else
194 {
195 if (!(value = (char*)malloc(strlen(defaultValue)+1))) return NULL;
196 strcpy(value, defaultValue);
197 }
198 return value;
199 }
200
201 /** Split string at ':', return null-terminated list of resulting strings.
202 * @param string String to be split
203 */
xdgSplitPath(const char * string)204 static char** xdgSplitPath(const char* string)
205 {
206 unsigned int size, i, j, k;
207 char** itemlist;
208
209 /* Get the number of paths */
210 size=2; /* One item more than seperators + terminating null item */
211 for (i = 0; string[i]; ++i)
212 {
213 #ifndef NO_ESCAPES_IN_PATHS
214 if (string[i] == '\\' && string[i+1])
215 {
216 /* skip escaped characters including seperators */
217 ++i;
218 continue;
219 }
220 #endif
221 if (string[i] == PATH_SEPARATOR_CHAR) ++size;
222 }
223
224 if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
225 xdgZeroMemory(itemlist, sizeof(char*)*size);
226
227 for (i = 0; *string; ++i)
228 {
229 /* get length of current string */
230 for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
231 #ifndef NO_ESCAPES_IN_PATHS
232 if (string[j] == '\\' && string[j+1]) ++j
233 #endif
234 ;
235
236 if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return NULL; }
237
238 /* transfer string, unescaping any escaped seperators */
239 for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
240 {
241 #ifndef NO_ESCAPES_IN_PATHS
242 if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
243 else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
244 {
245 itemlist[i][k]=string[j];
246 ++j, ++k;
247 }
248 #endif
249 itemlist[i][k] = string[j];
250 }
251 itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
252 /* move to next string */
253 string += j;
254 if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
255 }
256 return itemlist;
257 }
258
259 /** Get $PATH-style environment variable as list of strings.
260 * If $name is unset or empty, use default strings specified by variable arguments.
261 * @param name Name of environment variable
262 * @param strings NULL-terminated list of strings to be copied and used as defaults
263 */
xdgGetPathListEnv(const char * name,const char ** strings)264 static char** xdgGetPathListEnv(const char* name, const char ** strings)
265 {
266 const char* env;
267 char* item;
268 char** itemlist;
269 int i, size;
270
271 env = getenv(name);
272 if (env && env[0])
273 {
274 if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
275 strcpy(item, env);
276
277 itemlist = xdgSplitPath(item);
278 free(item);
279 }
280 else
281 {
282 if (!strings) return NULL;
283 for (size = 0; strings[size]; ++size) ;
284 ++size;
285 if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
286 xdgZeroMemory(itemlist, sizeof(char*)*(size));
287
288 /* Copy defaults into itemlist. */
289 /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
290 for (i = 0; strings[i]; ++i)
291 {
292 if (!(item = (char*)malloc(strlen(strings[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
293 strcpy(item, strings[i]);
294 itemlist[i] = item;
295 }
296 }
297 return itemlist;
298 }
299
300 /** Update all *Home variables of cache.
301 * This includes xdgCachedData::dataHome, xdgCachedData::configHome and xdgCachedData::cacheHome.
302 * @param cache Data cache to be updated
303 */
xdgUpdateHomeDirectories(xdgCachedData * cache)304 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
305 {
306 const char* env;
307 char* home, *defVal = NULL;
308 int result = FALSE;
309
310 env = getenv("HOME");
311 if (!env || !env[0])
312 return FALSE;
313 if (!(home = (char*)malloc(strlen(env)+1))) goto out;
314 strcpy(home, env);
315
316 /* Allocate maximum needed for any of the 3 default values */
317 defVal = (char*)malloc(strlen(home)+
318 MAX(MAX(sizeof(DefaultRelativeDataHome), sizeof(DefaultRelativeConfigHome)), sizeof(DefaultRelativeCacheHome)));
319 if (!defVal) goto out;
320
321 strcpy(defVal, home);
322 strcat(defVal, DefaultRelativeDataHome);
323 if (!(cache->dataHome = xdgGetEnv("XDG_DATA_HOME", defVal))) goto out;
324
325 defVal[strlen(home)] = 0;
326 strcat(defVal, DefaultRelativeConfigHome);
327 if (!(cache->configHome = xdgGetEnv("XDG_CONFIG_HOME", defVal))) goto out;
328
329 defVal[strlen(home)] = 0;
330 strcat(defVal, DefaultRelativeCacheHome);
331 if (!(cache->cacheHome = xdgGetEnv("XDG_CACHE_HOME", defVal))) goto out;
332
333 result = TRUE;
334 out:
335 free(defVal);
336 free(home);
337
338 return result;
339 }
340
341 /** Update all *Directories variables of cache.
342 * This includes xdgCachedData::searchableDataDirectories and xdgCachedData::searchableConfigDirectories.
343 * @param cache Data cache to be updated.
344 */
xdgUpdateDirectoryLists(xdgCachedData * cache)345 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
346 {
347 char** itemlist;
348 int size;
349
350 itemlist = xdgGetPathListEnv("XDG_DATA_DIRS", DefaultDataDirectoriesList);
351
352 if (!itemlist) return FALSE;
353 for (size = 0; itemlist[size]; size++) ; /* Get list size */
354 if (!(cache->searchableDataDirectories = (char**)malloc(sizeof(char*)*(size+2))))
355 {
356 xdgFreeStringList(itemlist);
357 return FALSE;
358 }
359 /* "home" directory has highest priority according to spec */
360 cache->searchableDataDirectories[0] = cache->dataHome;
361 memcpy(&(cache->searchableDataDirectories[1]), itemlist, sizeof(char*)*(size+1));
362 free(itemlist);
363
364 itemlist = xdgGetPathListEnv("XDG_CONFIG_DIRS", DefaultConfigDirectoriesList);
365 if (!itemlist) return FALSE;
366 for (size = 0; itemlist[size]; size++) ; /* Get list size */
367 if (!(cache->searchableConfigDirectories = (char**)malloc(sizeof(char*)*(size+2))))
368 {
369 xdgFreeStringList(itemlist);
370 return FALSE;
371 }
372 cache->searchableConfigDirectories[0] = cache->configHome;
373 memcpy(&(cache->searchableConfigDirectories[1]), itemlist, sizeof(char*)*(size+1));
374 free(itemlist);
375
376 return TRUE;
377 }
378
xdgUpdateData(xdgHandle * handle)379 int xdgUpdateData(xdgHandle *handle)
380 {
381 xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
382 xdgCachedData* oldCache;
383 if (!cache) return FALSE;
384 xdgZeroMemory(cache, sizeof(xdgCachedData));
385
386 if (xdgUpdateHomeDirectories(cache) &&
387 xdgUpdateDirectoryLists(cache))
388 {
389 /* Update successful, replace pointer to old cache with pointer to new cache */
390 oldCache = xdgGetCache(handle);
391 handle->reserved = cache;
392 if (oldCache)
393 {
394 xdgFreeData(oldCache);
395 free(oldCache);
396 }
397 return TRUE;
398 }
399 else
400 {
401 /* Update failed, discard new cache and leave old cache unmodified */
402 xdgFreeData(cache);
403 free(cache);
404 return FALSE;
405 }
406 }
407
408 /** Find all existing files corresponding to relativePath relative to each item in dirList.
409 * @param relativePath Relative path to search for.
410 * @param dirList <tt>NULL</tt>-terminated list of directory paths.
411 * @return A sequence of null-terminated strings terminated by a
412 * double-<tt>NULL</tt> (empty string) and allocated using malloc().
413 */
xdgFindExisting(const char * relativePath,const char * const * dirList)414 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
415 {
416 char * fullPath;
417 char * returnString = 0;
418 char * tmpString;
419 int strLen = 0;
420 FILE * testFile;
421 const char * const * item;
422
423 for (item = dirList; *item; item++)
424 {
425 if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
426 {
427 if (returnString) free(returnString);
428 return NULL;
429 }
430 strcpy(fullPath, *item);
431 if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
432 strcat(fullPath, DIR_SEPARATOR_STR);
433 strcat(fullPath, relativePath);
434 testFile = fopen(fullPath, "r");
435 if (testFile)
436 {
437 fclose(testFile);
438 if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
439 {
440 free(returnString);
441 free(fullPath);
442 return NULL;
443 }
444 returnString = tmpString;
445 strcpy(&returnString[strLen], fullPath);
446 strLen = strLen+strlen(fullPath)+1;
447 }
448 free(fullPath);
449 }
450 if (returnString)
451 returnString[strLen] = 0;
452 else
453 {
454 if ((returnString = (char*)malloc(2)))
455 strcpy(returnString, "\0");
456 }
457 return returnString;
458 }
459
460 /** Open first possible config file corresponding to relativePath.
461 * @param relativePath Path to scan for.
462 * @param mode Mode with which to attempt to open files (see fopen modes).
463 * @param dirList <tt>NULL</tt>-terminated list of paths in which to search for relativePath.
464 * @return File pointer if successful else @c NULL. Client must use @c fclose to close file.
465 */
xdgFileOpen(const char * relativePath,const char * mode,const char * const * dirList)466 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
467 {
468 char * fullPath;
469 FILE * testFile;
470 const char * const * item;
471
472 for (item = dirList; *item; item++)
473 {
474 if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
475 return NULL;
476 strcpy(fullPath, *item);
477 if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
478 strcat(fullPath, DIR_SEPARATOR_STR);
479 strcat(fullPath, relativePath);
480 testFile = fopen(fullPath, mode);
481 free(fullPath);
482 if (testFile)
483 return testFile;
484 }
485 return NULL;
486 }
487
xdgMakePath(const char * path,mode_t mode)488 int xdgMakePath(const char * path, mode_t mode)
489 {
490 int length = strlen(path);
491 char * tmpPath;
492 char * tmpPtr;
493 int ret;
494
495 if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
496 return 0;
497
498 if (!(tmpPath = (char*)malloc(length+1)))
499 {
500 errno = ENOMEM;
501 return -1;
502 }
503 strcpy(tmpPath, path);
504 if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
505 tmpPath[length-1] = '\0';
506
507 /* skip tmpPath[0] since if it's a seperator we have an absolute path */
508 for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
509 {
510 if (*tmpPtr == DIR_SEPARATOR_CHAR)
511 {
512 *tmpPtr = '\0';
513 if (mkdir(tmpPath, mode) == -1)
514 {
515 if (errno != EEXIST)
516 {
517 free(tmpPath);
518 return -1;
519 }
520 }
521 *tmpPtr = DIR_SEPARATOR_CHAR;
522 }
523 }
524 ret = mkdir(tmpPath, mode);
525 free(tmpPath);
526 return ret;
527 }
528
xdgDataHome(xdgHandle * handle)529 const char * xdgDataHome(xdgHandle *handle)
530 {
531 return xdgGetCache(handle)->dataHome;
532 }
xdgConfigHome(xdgHandle * handle)533 const char * xdgConfigHome(xdgHandle *handle)
534 {
535 return xdgGetCache(handle)->configHome;
536 }
xdgDataDirectories(xdgHandle * handle)537 const char * const * xdgDataDirectories(xdgHandle *handle)
538 {
539 return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
540 }
xdgSearchableDataDirectories(xdgHandle * handle)541 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
542 {
543 return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
544 }
xdgConfigDirectories(xdgHandle * handle)545 const char * const * xdgConfigDirectories(xdgHandle *handle)
546 {
547 return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
548 }
xdgSearchableConfigDirectories(xdgHandle * handle)549 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
550 {
551 return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
552 }
xdgCacheHome(xdgHandle * handle)553 const char * xdgCacheHome(xdgHandle *handle)
554 {
555 return xdgGetCache(handle)->cacheHome;
556 }
xdgDataFind(const char * relativePath,xdgHandle * handle)557 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
558 {
559 return xdgFindExisting(relativePath, xdgSearchableDataDirectories(handle));
560 }
xdgConfigFind(const char * relativePath,xdgHandle * handle)561 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
562 {
563 return xdgFindExisting(relativePath, xdgSearchableConfigDirectories(handle));
564 }
xdgDataOpen(const char * relativePath,const char * mode,xdgHandle * handle)565 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
566 {
567 return xdgFileOpen(relativePath, mode, xdgSearchableDataDirectories(handle));
568 }
xdgConfigOpen(const char * relativePath,const char * mode,xdgHandle * handle)569 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
570 {
571 return xdgFileOpen(relativePath, mode, xdgSearchableConfigDirectories(handle));
572 }
573
574