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  xdgFreeStringList(cache->searchableDataDirectories);
164  cache->searchableDataDirectories = 0;
165  xdgFreeStringList(cache->searchableConfigDirectories);
166  cache->searchableConfigDirectories = 0;
167 }
168 
170 {
171  xdgCachedData* cache = xdgGetCache(handle);
172  xdgFreeData(cache);
173  free(cache);
174 }
175 
179 static char** xdgSplitPath(const char* string)
180 {
181  unsigned int size, i, j, k;
182  char** itemlist;
183 
184  /* Get the number of paths */
185  size=2; /* One item more than seperators + terminating null item */
186  for (i = 0; string[i]; ++i)
187  {
188 #ifndef NO_ESCAPES_IN_PATHS
189  if (string[i] == '\\' && string[i+1])
190  {
191  /* skip escaped characters including seperators */
192  ++i;
193  continue;
194  }
195 #endif
196  if (string[i] == PATH_SEPARATOR_CHAR) ++size;
197  }
198 
199  if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
200  xdgZeroMemory(itemlist, sizeof(char*)*size);
201 
202  for (i = 0; *string; ++i)
203  {
204  /* get length of current string */
205  for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
206 #ifndef NO_ESCAPES_IN_PATHS
207  if (string[j] == '\\' && string[j+1]) ++j
208 #endif
209  ;
210 
211  if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
212 
213  /* transfer string, unescaping any escaped seperators */
214  for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
215  {
216 #ifndef NO_ESCAPES_IN_PATHS
217  if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
218  else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
219  {
220  itemlist[i][k]=string[j];
221  ++j, ++k;
222  }
223 #endif
224  itemlist[i][k] = string[j];
225  }
226  itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
227  /* move to next string */
228  string += j;
229  if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
230  }
231  return itemlist;
232 }
233 
239 static char** xdgGetPathListEnv(const char* name, const char ** defaults)
240 {
241  const char* env;
242  char* item;
243  char** itemlist;
244  int i, size;
245 
246  env = getenv(name);
247  if (env && env[0])
248  {
249  if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
250  strcpy(item, env);
251 
252  itemlist = xdgSplitPath(item);
253  free(item);
254  }
255  else
256  {
257  if (!defaults) return NULL;
258  for (size = 0; defaults[size]; ++size) ; ++size;
259  if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
260  xdgZeroMemory(itemlist, sizeof(char*)*(size));
261 
262  /* Copy defaults into itemlist. */
263  /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
264  for (i = 0; defaults[i]; ++i)
265  {
266  if (!(item = (char*)malloc(strlen(defaults[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
267  strcpy(item, defaults[i]);
268  itemlist[i] = item;
269  }
270  }
271  return itemlist;
272 }
273 
279 static char* xdgGetEnv(const char *name)
280 {
281  char *env = getenv(name);
282  if (env && env[0])
283  return env;
284  /* What errno signifies missing env var? */
285  errno = EINVAL;
286  return NULL;
287 }
288 
294 static char* xdgEnvDup(const char *name)
295 {
296  const char *env;
297  if ((env = xdgGetEnv(name)))
298  return strdup(env);
299  else
300  return NULL;
301 }
302 
307 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
308 {
309  const char *homeenv;
310  char *value;
311  unsigned int homelen;
312  static const unsigned int extralen =
313  MAX(MAX(sizeof(DefaultRelativeDataHome),
314  sizeof(DefaultRelativeConfigHome)),
315  sizeof(DefaultRelativeCacheHome));
316 
317  if (!(cache->dataHome = xdgEnvDup("XDG_DATA_HOME")) && errno == ENOMEM) return FALSE;
318  if (!(cache->configHome = xdgEnvDup("XDG_CONFIG_HOME")) && errno == ENOMEM) return FALSE;
319  if (!(cache->cacheHome = xdgEnvDup("XDG_CACHE_HOME")) && errno == ENOMEM) return FALSE;
320  if (!(cache->runtimeDirectory = xdgEnvDup("XDG_RUNTIME_DIR")) && errno == ENOMEM) return FALSE;
321  errno = 0;
322 
323  if (cache->dataHome && cache->configHome && cache->cacheHome) return TRUE;
324 
325  if (!(homeenv = xdgGetEnv("HOME")))
326  return FALSE;
327 
328  /* Allocate maximum needed for any of the 3 default values */
329  if (!(value = (char*)malloc((homelen = strlen(homeenv))+extralen))) return FALSE;
330  memcpy(value, homeenv, homelen+1);
331 
332  if (!cache->dataHome)
333  {
334  memcpy(value+homelen, DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome));
335  cache->dataHome = strdup(value);
336  }
337 
338  if (!cache->configHome)
339  {
340  memcpy(value+homelen, DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome));
341  cache->configHome = strdup(value);
342  }
343 
344  if (!cache->cacheHome)
345  {
346  memcpy(value+homelen, DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome));
347  cache->cacheHome = strdup(value);
348  }
349 
350  free(value);
351 
352  /* free does not change errno, and the prev call *must* have been a strdup,
353  * so errno is already set. */
354  return cache->dataHome && cache->configHome && cache->cacheHome;
355 }
356 
368 static char** xdgGetDirectoryLists(const char *envname, char *homedir, const char **defaults)
369 {
370  char **envlist;
371  char **dirlist;
372  unsigned int size;
373 
374  if (!(envlist = xdgGetPathListEnv(envname, defaults)))
375  return NULL;
376 
377  for (size = 0; envlist[size]; size++) ; /* Get list size */
378  if (!(dirlist = (char**)malloc(sizeof(char*)*(size+1+!!homedir))))
379  {
380  xdgFreeStringList(envlist);
381  return NULL;
382  }
383  /* "home" directory has highest priority according to spec */
384  if (homedir)
385  dirlist[0] = homedir;
386  memcpy(dirlist+!!homedir, envlist, sizeof(char*)*(size+1));
387  /* only free the envlist since its elements are now referenced by dirlist */
388  free(envlist);
389 
390  return dirlist;
391 }
392 
397 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
398 {
399  if (!(cache->searchableDataDirectories = xdgGetDirectoryLists(
400  "XDG_DATA_DIRS", cache->dataHome, DefaultDataDirectoriesList)))
401  return FALSE;
402  if (!(cache->searchableConfigDirectories = xdgGetDirectoryLists(
403  "XDG_CONFIG_DIRS", cache->configHome, DefaultConfigDirectoriesList)))
404  return FALSE;
405 
406  return TRUE;
407 }
408 
410 {
411  xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
412  xdgCachedData* oldCache;
413  if (!cache) return FALSE;
414  xdgZeroMemory(cache, sizeof(xdgCachedData));
415 
416  if (xdgUpdateHomeDirectories(cache) &&
418  {
419  /* Update successful, replace pointer to old cache with pointer to new cache */
420  oldCache = xdgGetCache(handle);
421  handle->reserved = cache;
422  if (oldCache)
423  {
424  xdgFreeData(oldCache);
425  free(oldCache);
426  }
427  return TRUE;
428  }
429  else
430  {
431  /* Update failed, discard new cache and leave old cache unmodified */
432  xdgFreeData(cache);
433  free(cache);
434  return FALSE;
435  }
436 }
437 
444 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
445 {
446  char * fullPath;
447  char * returnString = 0;
448  char * tmpString;
449  int strLen = 0;
450  FILE * testFile;
451  const char * const * item;
452 
453  for (item = dirList; *item; item++)
454  {
455  if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
456  {
457  if (returnString) free(returnString);
458  return 0;
459  }
460  strcpy(fullPath, *item);
461  if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
462  strcat(fullPath, DIR_SEPARATOR_STR);
463  strcat(fullPath, relativePath);
464  testFile = fopen(fullPath, "r");
465  if (testFile)
466  {
467  if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
468  {
469  free(returnString);
470  free(fullPath);
471  return 0;
472  }
473  returnString = tmpString;
474  strcpy(&returnString[strLen], fullPath);
475  strLen = strLen+strlen(fullPath)+1;
476  fclose(testFile);
477  }
478  free(fullPath);
479  }
480  if (returnString)
481  returnString[strLen] = 0;
482  else
483  {
484  if ((returnString = (char*)malloc(2)))
485  strcpy(returnString, "\0");
486  }
487  return returnString;
488 }
489 
496 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
497 {
498  char * fullPath;
499  FILE * testFile;
500  const char * const * item;
501 
502  for (item = dirList; *item; item++)
503  {
504  if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
505  return 0;
506  strcpy(fullPath, *item);
507  if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
508  strcat(fullPath, DIR_SEPARATOR_STR);
509  strcat(fullPath, relativePath);
510  testFile = fopen(fullPath, mode);
511  free(fullPath);
512  if (testFile)
513  return testFile;
514  }
515  return 0;
516 }
517 
518 int xdgMakePath(const char * path, mode_t mode)
519 {
520  int length = strlen(path);
521  char * tmpPath;
522  char * tmpPtr;
523  int ret;
524 
525  if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
526  return 0;
527 
528  if (!(tmpPath = (char*)malloc(length+1)))
529  {
530  errno = ENOMEM;
531  return -1;
532  }
533  strcpy(tmpPath, path);
534  if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
535  tmpPath[length-1] = '\0';
536 
537  /* skip tmpPath[0] since if it's a seperator we have an absolute path */
538  for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
539  {
540  if (*tmpPtr == DIR_SEPARATOR_CHAR)
541  {
542  *tmpPtr = '\0';
543  if (mkdir(tmpPath, mode) == -1)
544  {
545  if (errno != EEXIST)
546  {
547  free(tmpPath);
548  return -1;
549  }
550  }
551  *tmpPtr = DIR_SEPARATOR_CHAR;
552  }
553  }
554  ret = mkdir(tmpPath, mode);
555  free(tmpPath);
556  return ret;
557 }
558 
567 static char * xdgGetRelativeHome(const char *envname, const char *relativefallback, unsigned int fallbacklength)
568 {
569  char *relhome;
570  if (!(relhome = xdgEnvDup(envname)) && errno != ENOMEM)
571  {
572  errno = 0;
573  const char *home;
574  unsigned int homelen;
575  if (!(home = xdgGetEnv("HOME")))
576  return NULL;
577  if (!(relhome = (char*)malloc((homelen = strlen(home))+fallbacklength))) return NULL;
578  memcpy(relhome, home, homelen);
579  memcpy(relhome+homelen, relativefallback, fallbacklength+1);
580  }
581  return relhome;
582 }
583 
584 const char * xdgDataHome(xdgHandle *handle)
585 {
586  if (handle)
587  return xdgGetCache(handle)->dataHome;
588  else
589  return xdgGetRelativeHome("XDG_DATA_HOME", DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome)-1);
590 }
591 const char * xdgConfigHome(xdgHandle *handle)
592 {
593  if (handle)
594  return xdgGetCache(handle)->configHome;
595  else
596  return xdgGetRelativeHome("XDG_CONFIG_HOME", DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome)-1);
597 }
598 const char * const * xdgDataDirectories(xdgHandle *handle)
599 {
600  if (handle)
601  return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
602  else
603  return (const char * const *)xdgGetDirectoryLists("XDG_DATA_DIRS", NULL, DefaultDataDirectoriesList);
604 }
605 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
606 {
607  if (handle)
608  return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
609  else
610  {
611  char *datahome = (char*)xdgDataHome(NULL);
612  char **datadirs = 0;
613  if (datahome && !(datadirs = xdgGetDirectoryLists("XDG_DATA_DIRS", datahome, DefaultDataDirectoriesList)))
614  free(datahome);
615  return (const char * const *)datadirs;
616  }
617 }
618 const char * const * xdgConfigDirectories(xdgHandle *handle)
619 {
620  if (handle)
621  return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
622  else
623  return (const char * const *)xdgGetDirectoryLists("XDG_CONFIG_DIRS", NULL, DefaultConfigDirectoriesList);
624 }
625 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
626 {
627  if (handle)
628  return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
629  else
630  {
631  char *confighome = (char*)xdgConfigHome(NULL);
632  char **configdirs = 0;
633  if (confighome && !(configdirs = xdgGetDirectoryLists("XDG_CONFIG_DIRS", confighome, DefaultConfigDirectoriesList)))
634  free(confighome);
635  return (const char * const *)configdirs;
636  }
637 }
638 const char * xdgCacheHome(xdgHandle *handle)
639 {
640  if (handle)
641  return xdgGetCache(handle)->cacheHome;
642  else
643  return xdgGetRelativeHome("XDG_CACHE_HOME", DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome)-1);
644 }
645 const char * xdgRuntimeDirectory(xdgHandle *handle)
646 {
647  if (handle)
648  return xdgGetCache(handle)->runtimeDirectory;
649  else
650  return xdgEnvDup("XDG_RUNTIME_DIRECTORY");
651 }
652 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
653 {
654  const char * const * dirs = xdgSearchableDataDirectories(handle);
655  char * result = xdgFindExisting(relativePath, dirs);
656  if (!handle) xdgFreeStringList((char**)dirs);
657  return result;
658 }
659 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
660 {
661  const char * const * dirs = xdgSearchableConfigDirectories(handle);
662  char * result = xdgFindExisting(relativePath, dirs);
663  if (!handle) xdgFreeStringList((char**)dirs);
664  return result;
665 }
666 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
667 {
668  const char * const * dirs = xdgSearchableDataDirectories(handle);
669  FILE * result = xdgFileOpen(relativePath, mode, dirs);
670  if (!handle) xdgFreeStringList((char**)dirs);
671  return result;
672 }
673 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
674 {
675  const char * const * dirs = xdgSearchableConfigDirectories(handle);
676  FILE * result = xdgFileOpen(relativePath, mode, dirs);
677  if (!handle) xdgFreeStringList((char**)dirs);
678  return result;
679 }
680