libxdg-basedir-1.2.0  1.2.0
basedir.c
Go to the documentation of this file.
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 
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 <errno.h>
46 #include <sys/stat.h>
47 
48 #ifdef FALSE
49 #undef FALSE
50 #endif
51 #ifdef TRUE
52 #undef TRUE
53 #endif
54 #define FALSE 0
55 #define TRUE 1
56 
57 #if HAVE_MEMSET || !defined(HAVE_CONFIG_H)
58 # define xdgZeroMemory(p, n) memset(p, 0, n)
59 #elif HAVE_BZERO
60 # define xdgZeroMemory(p, n) bzero(p, n)
61 #else
62 static void xdgZeroMemory(void* p, int n)
63 {
64  while (n > 0) { ((char*)p)[n] = 0; ++n; }
65 }
66 #endif
67 
68 #if defined _WIN32 && !defined __CYGWIN__
69  /* Use Windows separators on all _WIN32 defining
70  environments, except Cygwin. */
71 # define DIR_SEPARATOR_CHAR '\\'
72 # define DIR_SEPARATOR_STR "\\"
73 # define PATH_SEPARATOR_CHAR ';'
74 # define PATH_SEPARATOR_STR ";"
75 # define NO_ESCAPES_IN_PATHS
76 #else
77 # define DIR_SEPARATOR_CHAR '/'
78 # define DIR_SEPARATOR_STR "/"
79 # define PATH_SEPARATOR_CHAR ':'
80 # define PATH_SEPARATOR_STR ":"
81 # define NO_ESCAPES_IN_PATHS
82 #endif
83 
84 #include <basedir.h>
85 #include <basedir_fs.h>
86 
87 #ifndef MAX
88 #define MAX(a, b) ((b) > (a) ? (b) : (a))
89 #endif
90 
91 static const char
92  DefaultRelativeDataHome[] = DIR_SEPARATOR_STR ".local" DIR_SEPARATOR_STR "share",
93  DefaultRelativeConfigHome[] = DIR_SEPARATOR_STR ".config",
94  DefaultDataDirectories1[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "local" DIR_SEPARATOR_STR "share",
95  DefaultDataDirectories2[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "share",
96  DefaultConfigDirectories[] = DIR_SEPARATOR_STR "etc" DIR_SEPARATOR_STR "xdg",
97  DefaultRelativeCacheHome[] = DIR_SEPARATOR_STR ".cache";
98 
99 static const char
100  *DefaultDataDirectoriesList[] = { DefaultDataDirectories1, DefaultDataDirectories2, NULL },
101  *DefaultConfigDirectoriesList[] = { DefaultConfigDirectories, NULL };
102 
103 typedef struct _xdgCachedData
104 {
105  char * dataHome;
106  char * configHome;
107  char * cacheHome;
108  char * runtimeDirectory;
109  /* Note: string lists are null-terminated and all items */
110  /* except the first are assumed to be allocated using malloc. */
111  /* The first item is assumed to be allocated by malloc only if */
112  /* it is not equal to the appropriate home directory string above. */
113  char ** searchableDataDirectories;
114  char ** searchableConfigDirectories;
115 } xdgCachedData;
116 
118 static xdgCachedData* xdgGetCache(xdgHandle *handle)
119 {
120  return ((xdgCachedData*)(handle->reserved));
121 }
122 
124 {
125  if (!handle) return 0;
126  handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
127  if (xdgUpdateData(handle))
128  return handle;
129  return 0;
130 }
131 
133 static void xdgFreeStringList(char** list)
134 {
135  char** ptr = list;
136  if (!list) return;
137  for (; *ptr; ptr++)
138  free(*ptr);
139  free(list);
140 }
141 
143 static void xdgFreeData(xdgCachedData *cache)
144 {
145  if (cache->dataHome);
146  {
147  /* the first element of the directory lists is usually the home directory */
148  if (cache->searchableDataDirectories && cache->searchableDataDirectories[0] != cache->dataHome)
149  free(cache->dataHome);
150  cache->dataHome = 0;
151  }
152  if (cache->configHome);
153  {
154  if (cache->searchableConfigDirectories && cache->searchableConfigDirectories[0] != cache->configHome)
155  free(cache->configHome);
156  cache->configHome = 0;
157  }
158  if (cache->cacheHome)
159  {
160  free(cache->cacheHome);
161  cache->cacheHome = 0;
162  }
163  if (cache->runtimeDirectory)
164  {
165  free(cache->runtimeDirectory);
166  cache->runtimeDirectory = 0;
167  }
168  xdgFreeStringList(cache->searchableDataDirectories);
169  cache->searchableDataDirectories = 0;
170  xdgFreeStringList(cache->searchableConfigDirectories);
171  cache->searchableConfigDirectories = 0;
172 }
173 
175 {
176  xdgCachedData* cache = xdgGetCache(handle);
177  xdgFreeData(cache);
178  free(cache);
179 }
180 
184 static char** xdgSplitPath(const char* string)
185 {
186  unsigned int size, i, j, k;
187  char** itemlist;
188 
189  /* Get the number of paths */
190  size=2; /* One item more than seperators + terminating null item */
191  for (i = 0; string[i]; ++i)
192  {
193 #ifndef NO_ESCAPES_IN_PATHS
194  if (string[i] == '\\' && string[i+1])
195  {
196  /* skip escaped characters including seperators */
197  ++i;
198  continue;
199  }
200 #endif
201  if (string[i] == PATH_SEPARATOR_CHAR) ++size;
202  }
203 
204  if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
205  xdgZeroMemory(itemlist, sizeof(char*)*size);
206 
207  for (i = 0; *string; ++i)
208  {
209  /* get length of current string */
210  for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
211 #ifndef NO_ESCAPES_IN_PATHS
212  if (string[j] == '\\' && string[j+1]) ++j
213 #endif
214  ;
215 
216  if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
217 
218  /* transfer string, unescaping any escaped seperators */
219  for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
220  {
221 #ifndef NO_ESCAPES_IN_PATHS
222  if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
223  else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
224  {
225  itemlist[i][k]=string[j];
226  ++j, ++k;
227  }
228 #endif
229  itemlist[i][k] = string[j];
230  }
231  itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
232  /* move to next string */
233  string += j;
234  if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
235  }
236  return itemlist;
237 }
238 
244 static char** xdgGetPathListEnv(const char* name, const char ** defaults)
245 {
246  const char* env;
247  char* item;
248  char** itemlist;
249  int i, size;
250 
251  env = getenv(name);
252  if (env && env[0])
253  {
254  if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
255  strcpy(item, env);
256 
257  itemlist = xdgSplitPath(item);
258  free(item);
259  }
260  else
261  {
262  if (!defaults) return NULL;
263  for (size = 0; defaults[size]; ++size) ; ++size;
264  if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
265  xdgZeroMemory(itemlist, sizeof(char*)*(size));
266 
267  /* Copy defaults into itemlist. */
268  /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
269  for (i = 0; defaults[i]; ++i)
270  {
271  if (!(item = (char*)malloc(strlen(defaults[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
272  strcpy(item, defaults[i]);
273  itemlist[i] = item;
274  }
275  }
276  return itemlist;
277 }
278 
284 static char* xdgGetEnv(const char *name)
285 {
286  char *env = getenv(name);
287  if (env && env[0])
288  return env;
289  /* What errno signifies missing env var? */
290  errno = EINVAL;
291  return NULL;
292 }
293 
299 static char* xdgEnvDup(const char *name)
300 {
301  const char *env;
302  if ((env = xdgGetEnv(name)))
303  return strdup(env);
304  else
305  return NULL;
306 }
307 
312 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
313 {
314  const char *homeenv;
315  char *value;
316  unsigned int homelen;
317  static const unsigned int extralen =
318  MAX(MAX(sizeof(DefaultRelativeDataHome),
319  sizeof(DefaultRelativeConfigHome)),
320  sizeof(DefaultRelativeCacheHome));
321 
322  if (!(cache->dataHome = xdgEnvDup("XDG_DATA_HOME")) && errno == ENOMEM) return FALSE;
323  if (!(cache->configHome = xdgEnvDup("XDG_CONFIG_HOME")) && errno == ENOMEM) return FALSE;
324  if (!(cache->cacheHome = xdgEnvDup("XDG_CACHE_HOME")) && errno == ENOMEM) return FALSE;
325  if (!(cache->runtimeDirectory = xdgEnvDup("XDG_RUNTIME_DIR")) && errno == ENOMEM) return FALSE;
326  errno = 0;
327 
328  if (cache->dataHome && cache->configHome && cache->cacheHome) return TRUE;
329 
330  if (!(homeenv = xdgGetEnv("HOME")))
331  return FALSE;
332 
333  /* Allocate maximum needed for any of the 3 default values */
334  if (!(value = (char*)malloc((homelen = strlen(homeenv))+extralen))) return FALSE;
335  memcpy(value, homeenv, homelen+1);
336 
337  if (!cache->dataHome)
338  {
339  memcpy(value+homelen, DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome));
340  cache->dataHome = strdup(value);
341  }
342 
343  if (!cache->configHome)
344  {
345  memcpy(value+homelen, DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome));
346  cache->configHome = strdup(value);
347  }
348 
349  if (!cache->cacheHome)
350  {
351  memcpy(value+homelen, DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome));
352  cache->cacheHome = strdup(value);
353  }
354 
355  free(value);
356 
357  /* free does not change errno, and the prev call *must* have been a strdup,
358  * so errno is already set. */
359  return cache->dataHome && cache->configHome && cache->cacheHome;
360 }
361 
373 static char** xdgGetDirectoryLists(const char *envname, char *homedir, const char **defaults)
374 {
375  char **envlist;
376  char **dirlist;
377  unsigned int size;
378 
379  if (!(envlist = xdgGetPathListEnv(envname, defaults)))
380  return NULL;
381 
382  for (size = 0; envlist[size]; size++) ; /* Get list size */
383  if (!(dirlist = (char**)malloc(sizeof(char*)*(size+1+!!homedir))))
384  {
385  xdgFreeStringList(envlist);
386  return NULL;
387  }
388  /* "home" directory has highest priority according to spec */
389  if (homedir)
390  dirlist[0] = homedir;
391  memcpy(dirlist+!!homedir, envlist, sizeof(char*)*(size+1));
392  /* only free the envlist since its elements are now referenced by dirlist */
393  free(envlist);
394 
395  return dirlist;
396 }
397 
402 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
403 {
404  if (!(cache->searchableDataDirectories = xdgGetDirectoryLists(
405  "XDG_DATA_DIRS", cache->dataHome, DefaultDataDirectoriesList)))
406  return FALSE;
407  if (!(cache->searchableConfigDirectories = xdgGetDirectoryLists(
408  "XDG_CONFIG_DIRS", cache->configHome, DefaultConfigDirectoriesList)))
409  return FALSE;
410 
411  return TRUE;
412 }
413 
415 {
416  xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
417  xdgCachedData* oldCache;
418  if (!cache) return FALSE;
419  xdgZeroMemory(cache, sizeof(xdgCachedData));
420 
421  if (xdgUpdateHomeDirectories(cache) &&
423  {
424  /* Update successful, replace pointer to old cache with pointer to new cache */
425  oldCache = xdgGetCache(handle);
426  handle->reserved = cache;
427  if (oldCache)
428  {
429  xdgFreeData(oldCache);
430  free(oldCache);
431  }
432  return TRUE;
433  }
434  else
435  {
436  /* Update failed, discard new cache and leave old cache unmodified */
437  xdgFreeData(cache);
438  free(cache);
439  return FALSE;
440  }
441 }
442 
449 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
450 {
451  char * fullPath;
452  char * returnString = 0;
453  char * tmpString;
454  int strLen = 0;
455  FILE * testFile;
456  const char * const * item;
457 
458  for (item = dirList; *item; item++)
459  {
460  if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
461  {
462  if (returnString) free(returnString);
463  return 0;
464  }
465  strcpy(fullPath, *item);
466  if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
467  strcat(fullPath, DIR_SEPARATOR_STR);
468  strcat(fullPath, relativePath);
469  testFile = fopen(fullPath, "r");
470  if (testFile)
471  {
472  if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
473  {
474  free(returnString);
475  free(fullPath);
476  return 0;
477  }
478  returnString = tmpString;
479  strcpy(&returnString[strLen], fullPath);
480  strLen = strLen+strlen(fullPath)+1;
481  fclose(testFile);
482  }
483  free(fullPath);
484  }
485  if (returnString)
486  returnString[strLen] = 0;
487  else
488  {
489  if ((returnString = (char*)malloc(2)))
490  strcpy(returnString, "\0");
491  }
492  return returnString;
493 }
494 
501 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
502 {
503  char * fullPath;
504  FILE * testFile;
505  const char * const * item;
506 
507  for (item = dirList; *item; item++)
508  {
509  if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
510  return 0;
511  strcpy(fullPath, *item);
512  if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
513  strcat(fullPath, DIR_SEPARATOR_STR);
514  strcat(fullPath, relativePath);
515  testFile = fopen(fullPath, mode);
516  free(fullPath);
517  if (testFile)
518  return testFile;
519  }
520  return 0;
521 }
522 
523 int xdgMakePath(const char * path, mode_t mode)
524 {
525  int length = strlen(path);
526  char * tmpPath;
527  char * tmpPtr;
528  int ret;
529 
530  if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
531  return 0;
532 
533  if (!(tmpPath = (char*)malloc(length+1)))
534  {
535  errno = ENOMEM;
536  return -1;
537  }
538  strcpy(tmpPath, path);
539  if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
540  tmpPath[length-1] = '\0';
541 
542  /* skip tmpPath[0] since if it's a seperator we have an absolute path */
543  for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
544  {
545  if (*tmpPtr == DIR_SEPARATOR_CHAR)
546  {
547  *tmpPtr = '\0';
548  if (mkdir(tmpPath, mode) == -1)
549  {
550  if (errno != EEXIST)
551  {
552  free(tmpPath);
553  return -1;
554  }
555  }
556  *tmpPtr = DIR_SEPARATOR_CHAR;
557  }
558  }
559  ret = mkdir(tmpPath, mode);
560  free(tmpPath);
561  return ret;
562 }
563 
572 static char * xdgGetRelativeHome(const char *envname, const char *relativefallback, unsigned int fallbacklength)
573 {
574  char *relhome;
575  if (!(relhome = xdgEnvDup(envname)) && errno != ENOMEM)
576  {
577  errno = 0;
578  const char *home;
579  unsigned int homelen;
580  if (!(home = xdgGetEnv("HOME")))
581  return NULL;
582  if (!(relhome = (char*)malloc((homelen = strlen(home))+fallbacklength))) return NULL;
583  memcpy(relhome, home, homelen);
584  memcpy(relhome+homelen, relativefallback, fallbacklength+1);
585  }
586  return relhome;
587 }
588 
589 const char * xdgDataHome(xdgHandle *handle)
590 {
591  if (handle)
592  return xdgGetCache(handle)->dataHome;
593  else
594  return xdgGetRelativeHome("XDG_DATA_HOME", DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome)-1);
595 }
596 const char * xdgConfigHome(xdgHandle *handle)
597 {
598  if (handle)
599  return xdgGetCache(handle)->configHome;
600  else
601  return xdgGetRelativeHome("XDG_CONFIG_HOME", DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome)-1);
602 }
603 const char * const * xdgDataDirectories(xdgHandle *handle)
604 {
605  if (handle)
606  return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
607  else
608  return (const char * const *)xdgGetDirectoryLists("XDG_DATA_DIRS", NULL, DefaultDataDirectoriesList);
609 }
610 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
611 {
612  if (handle)
613  return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
614  else
615  {
616  char *datahome = (char*)xdgDataHome(NULL);
617  char **datadirs = 0;
618  if (datahome && !(datadirs = xdgGetDirectoryLists("XDG_DATA_DIRS", datahome, DefaultDataDirectoriesList)))
619  free(datahome);
620  return (const char * const *)datadirs;
621  }
622 }
623 const char * const * xdgConfigDirectories(xdgHandle *handle)
624 {
625  if (handle)
626  return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
627  else
628  return (const char * const *)xdgGetDirectoryLists("XDG_CONFIG_DIRS", NULL, DefaultConfigDirectoriesList);
629 }
630 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
631 {
632  if (handle)
633  return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
634  else
635  {
636  char *confighome = (char*)xdgConfigHome(NULL);
637  char **configdirs = 0;
638  if (confighome && !(configdirs = xdgGetDirectoryLists("XDG_CONFIG_DIRS", confighome, DefaultConfigDirectoriesList)))
639  free(confighome);
640  return (const char * const *)configdirs;
641  }
642 }
643 const char * xdgCacheHome(xdgHandle *handle)
644 {
645  if (handle)
646  return xdgGetCache(handle)->cacheHome;
647  else
648  return xdgGetRelativeHome("XDG_CACHE_HOME", DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome)-1);
649 }
650 const char * xdgRuntimeDirectory(xdgHandle *handle)
651 {
652  if (handle)
653  return xdgGetCache(handle)->runtimeDirectory;
654  else
655  return xdgEnvDup("XDG_RUNTIME_DIRECTORY");
656 }
657 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
658 {
659  const char * const * dirs = xdgSearchableDataDirectories(handle);
660  char * result = xdgFindExisting(relativePath, dirs);
661  if (!handle) xdgFreeStringList((char**)dirs);
662  return result;
663 }
664 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
665 {
666  const char * const * dirs = xdgSearchableConfigDirectories(handle);
667  char * result = xdgFindExisting(relativePath, dirs);
668  if (!handle) xdgFreeStringList((char**)dirs);
669  return result;
670 }
671 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
672 {
673  const char * const * dirs = xdgSearchableDataDirectories(handle);
674  FILE * result = xdgFileOpen(relativePath, mode, dirs);
675  if (!handle) xdgFreeStringList((char**)dirs);
676  return result;
677 }
678 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
679 {
680  const char * const * dirs = xdgSearchableConfigDirectories(handle);
681  FILE * result = xdgFileOpen(relativePath, mode, dirs);
682  if (!handle) xdgFreeStringList((char**)dirs);
683  return result;
684 }
685