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 Base Directory specification. */
28 #if defined(HAVE_CONFIG_H) || defined(_DOXYGEN)
32 #if STDC_HEADERS || HAVE_STDLIB_H || !defined(HAVE_CONFIG_H)
35 #if HAVE_MEMORY_H || !defined(HAVE_CONFIG_H)
38 #if HAVE_STRING_H || !defined(HAVE_CONFIG_H)
56 #if HAVE_MEMSET || !defined(HAVE_CONFIG_H)
57 # define xdgZeroMemory(p, n) memset(p, 0, n)
59 # define xdgZeroMemory(p, n) bzero(p, n)
61 static void xdgZeroMemory(void* p
, size_t n
)
63 while (n
> 0) { ((char*)p
)[n
] = 0; ++n
; }
67 #if defined _WIN32 && !defined __CYGWIN__
68 /* Use Windows separators on all _WIN32 defining
69 environments, except Cygwin. */
70 # define DIR_SEPARATOR_CHAR '\\'
71 # define DIR_SEPARATOR_STR "\\"
72 # define PATH_SEPARATOR_CHAR ';'
73 # define PATH_SEPARATOR_STR ";"
74 # define NO_ESCAPES_IN_PATHS
76 # define DIR_SEPARATOR_CHAR '/'
77 # define DIR_SEPARATOR_STR "/"
78 # define PATH_SEPARATOR_CHAR ':'
79 # define PATH_SEPARATOR_STR ":"
80 # define NO_ESCAPES_IN_PATHS
84 #include <basedir_fs.h>
87 #define MAX(a, b) ((b) > (a) ? (b) : (a))
91 DefaultRelativeDataHome
[] = DIR_SEPARATOR_STR
".local" DIR_SEPARATOR_STR
"share",
92 DefaultRelativeConfigHome
[] = DIR_SEPARATOR_STR
".config",
93 DefaultDataDirectories1
[] = DIR_SEPARATOR_STR
"usr" DIR_SEPARATOR_STR
"local" DIR_SEPARATOR_STR
"share",
94 DefaultDataDirectories2
[] = DIR_SEPARATOR_STR
"usr" DIR_SEPARATOR_STR
"share",
95 DefaultConfigDirectories
[] = DIR_SEPARATOR_STR
"etc" DIR_SEPARATOR_STR
"xdg",
96 DefaultRelativeCacheHome
[] = DIR_SEPARATOR_STR
".cache";
99 *DefaultDataDirectoriesList
[] = { DefaultDataDirectories1
, DefaultDataDirectories2
, NULL
},
100 *DefaultConfigDirectoriesList
[] = { DefaultConfigDirectories
, NULL
};
102 typedef struct _xdgCachedData
107 /* Note: string lists are null-terminated and all items */
108 /* except the first are assumed to be allocated using malloc. */
109 /* The first item is assumed to be allocated by malloc only if */
110 /* it is not equal to the appropriate home directory string above. */
111 char ** searchableDataDirectories
;
112 char ** searchableConfigDirectories
;
115 /** Get cache object associated with a handle */
116 static xdgCachedData
* xdgGetCache(xdgHandle
*handle
)
118 return ((xdgCachedData
*)(handle
->reserved
));
121 xdgHandle
* xdgInitHandle(xdgHandle
*handle
)
123 if (!handle
) return 0;
124 handle
->reserved
= 0; /* So xdgUpdateData() doesn't free it */
125 if (xdgUpdateData(handle
))
130 /** Free all memory used by a NULL-terminated string list */
131 static void xdgFreeStringList(char** list
)
140 /** Free all data in the cache and set pointers to null. */
141 static void xdgFreeData(xdgCachedData
*cache
)
143 if (cache
->dataHome
);
145 /* the first element of the directory lists is usually the home directory */
146 if (cache
->searchableDataDirectories
&& cache
->searchableDataDirectories
[0] != cache
->dataHome
)
147 free(cache
->dataHome
);
150 if (cache
->configHome
);
152 if (cache
->searchableConfigDirectories
&& cache
->searchableConfigDirectories
[0] != cache
->configHome
)
153 free(cache
->configHome
);
154 cache
->configHome
= 0;
156 if (cache
->cacheHome
)
158 free(cache
->cacheHome
);
159 cache
->cacheHome
= 0;
161 xdgFreeStringList(cache
->searchableDataDirectories
);
162 cache
->searchableDataDirectories
= 0;
163 xdgFreeStringList(cache
->searchableConfigDirectories
);
164 cache
->searchableConfigDirectories
= 0;
167 void xdgWipeHandle(xdgHandle
*handle
)
169 xdgCachedData
* cache
= xdgGetCache(handle
);
174 /** Split string at ':', return null-terminated list of resulting strings.
175 * @param string String to be split
177 static char** xdgSplitPath(const char* string
)
179 unsigned int size
, i
, j
, k
;
182 /* Get the number of paths */
183 size
=2; /* One item more than seperators + terminating null item */
184 for (i
= 0; string
[i
]; ++i
)
186 #ifndef NO_ESCAPES_IN_PATHS
187 if (string
[i
] == '\\' && string
[i
+1])
189 /* skip escaped characters including seperators */
194 if (string
[i
] == PATH_SEPARATOR_CHAR
) ++size
;
197 if (!(itemlist
= (char**)malloc(sizeof(char*)*size
))) return 0;
198 xdgZeroMemory(itemlist
, sizeof(char*)*size
);
200 for (i
= 0; *string
; ++i
)
202 /* get length of current string */
203 for (j
= 0; string
[j
] && string
[j
] != PATH_SEPARATOR_CHAR
; ++j
)
204 #ifndef NO_ESCAPES_IN_PATHS
205 if (string
[j
] == '\\' && string
[j
+1]) ++j
209 if (!(itemlist
[i
] = (char*)malloc(j
+1))) { xdgFreeStringList(itemlist
); return 0; }
211 /* transfer string, unescaping any escaped seperators */
212 for (k
= j
= 0; string
[j
] && string
[j
] != PATH_SEPARATOR_CHAR
; ++j
, ++k
)
214 #ifndef NO_ESCAPES_IN_PATHS
215 if (string
[j
] == '\\' && string
[j
+1] == PATH_SEPARATOR_CHAR
) ++j
; /* replace escaped ':' with just ':' */
216 else if (string
[j
] == '\\' && string
[j
+1]) /* skip escaped characters so escaping remains aligned to pairs. */
218 itemlist
[i
][k
]=string
[j
];
222 itemlist
[i
][k
] = string
[j
];
224 itemlist
[i
][k
] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
225 /* move to next string */
227 if (*string
== PATH_SEPARATOR_CHAR
) string
++; /* skip seperator */
232 /** Get $PATH-style environment variable as list of strings.
233 * If $name is unset or empty, use default strings specified by variable arguments.
234 * @param name Name of environment variable
235 * @param defaults NULL-terminated list of strings to be copied and used as defaults
237 static char** xdgGetPathListEnv(const char* name
, const char ** defaults
)
247 if (!(item
= (char*)malloc(strlen(env
)+1))) return NULL
;
250 itemlist
= xdgSplitPath(item
);
255 if (!defaults
) return NULL
;
256 for (size
= 0; defaults
[size
]; ++size
) ; ++size
;
257 if (!(itemlist
= (char**)malloc(sizeof(char*)*size
))) return NULL
;
258 xdgZeroMemory(itemlist
, sizeof(char*)*(size
));
260 /* Copy defaults into itemlist. */
261 /* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
262 for (i
= 0; defaults
[i
]; ++i
)
264 if (!(item
= (char*)malloc(strlen(defaults
[i
])+1))) { xdgFreeStringList(itemlist
); return NULL
; }
265 strcpy(item
, defaults
[i
]);
272 /** Get value of an environment variable.
273 * Sets @c errno to @c EINVAL if variable is not set or empty.
274 * @param name Name of environment variable.
275 * @return The environment variable or NULL if an error occurs.
277 static char* xdgGetEnv(const char *name
)
279 char *env
= getenv(name
);
282 /* What errno signifies missing env var? */
287 /** Duplicate an environment variable.
288 * Sets @c errno to @c ENOMEM if unable to allocate duplicate string.
289 * Sets @c errno to @c EINVAL if variable is not set or empty.
290 * @return The duplicated string or NULL if an error occurs.
292 static char* xdgEnvDup(const char *name
)
295 if ((env
= xdgGetEnv(name
)))
301 /** Update all *Home variables of cache.
302 * This includes xdgCachedData::dataHome, xdgCachedData::configHome and xdgCachedData::cacheHome.
303 * @param cache Data cache to be updated
305 static int xdgUpdateHomeDirectories(xdgCachedData
* cache
)
309 unsigned int homelen
;
310 static const unsigned int extralen
=
311 MAX(MAX(sizeof(DefaultRelativeDataHome
),
312 sizeof(DefaultRelativeConfigHome
)),
313 sizeof(DefaultRelativeCacheHome
));
315 if (!(cache
->dataHome
= xdgEnvDup("XDG_DATA_HOME")) && errno
== ENOMEM
) return FALSE
;
316 if (!(cache
->configHome
= xdgEnvDup("XDG_CONFIG_HOME")) && errno
== ENOMEM
) return FALSE
;
317 if (!(cache
->cacheHome
= xdgEnvDup("XDG_CACHE_HOME")) && errno
== ENOMEM
) return FALSE
;
320 if (cache
->dataHome
&& cache
->configHome
&& cache
->cacheHome
) return TRUE
;
322 if (!(homeenv
= xdgGetEnv("HOME")))
325 /* Allocate maximum needed for any of the 3 default values */
326 if (!(value
= (char*)malloc((homelen
= strlen(homeenv
))+extralen
))) return FALSE
;
327 memcpy(value
, homeenv
, homelen
+1);
329 if (!cache
->dataHome
)
331 memcpy(value
+homelen
, DefaultRelativeDataHome
, sizeof(DefaultRelativeDataHome
));
332 cache
->dataHome
= strdup(value
);
335 if (!cache
->configHome
)
337 memcpy(value
+homelen
, DefaultRelativeConfigHome
, sizeof(DefaultRelativeConfigHome
));
338 cache
->configHome
= strdup(value
);
341 if (!cache
->cacheHome
)
343 memcpy(value
+homelen
, DefaultRelativeCacheHome
, sizeof(DefaultRelativeCacheHome
));
344 cache
->cacheHome
= strdup(value
);
349 /* free does not change errno, and the prev call *must* have been a strdup,
350 * so errno is already set. */
351 return cache
->dataHome
&& cache
->configHome
&& cache
->cacheHome
;
354 /** Get directory lists with initial home directory.
355 * @param envname Environment variable with colon-seperated directories.
356 * @param homedir Home directory for this directory list or NULL. This
357 * parameter should be allocated on the heap. The returned list
358 * will start with this path, and should be considered as owning
360 * @param defaults Default directories if environment variable is not set.
361 * @return An array of strings. Both the array and its contents are allocated
362 * with malloc(). The function xdgFreeStringList is provided for
363 * conveniantly free()-ing the list and all its elements.
365 static char** xdgGetDirectoryLists(const char *envname
, char *homedir
, const char **defaults
)
371 if (!(envlist
= xdgGetPathListEnv(envname
, defaults
)))
374 for (size
= 0; envlist
[size
]; size
++) ; /* Get list size */
375 if (!(dirlist
= (char**)malloc(sizeof(char*)*(size
+1+!!homedir
))))
377 xdgFreeStringList(envlist
);
380 /* "home" directory has highest priority according to spec */
382 dirlist
[0] = homedir
;
383 memcpy(dirlist
+!!homedir
, envlist
, sizeof(char*)*(size
+1));
384 /* only free the envlist since its elements are now referenced by dirlist */
390 /** Update all *Directories variables of cache.
391 * This includes xdgCachedData::searchableDataDirectories and xdgCachedData::searchableConfigDirectories.
392 * @param cache Data cache to be updated.
394 static int xdgUpdateDirectoryLists(xdgCachedData
* cache
)
396 if (!(cache
->searchableDataDirectories
= xdgGetDirectoryLists(
397 "XDG_DATA_DIRS", cache
->dataHome
, DefaultDataDirectoriesList
)))
399 if (!(cache
->searchableConfigDirectories
= xdgGetDirectoryLists(
400 "XDG_CONFIG_DIRS", cache
->configHome
, DefaultConfigDirectoriesList
)))
406 int xdgUpdateData(xdgHandle
*handle
)
408 xdgCachedData
* cache
= (xdgCachedData
*)malloc(sizeof(xdgCachedData
));
409 xdgCachedData
* oldCache
;
410 if (!cache
) return FALSE
;
411 xdgZeroMemory(cache
, sizeof(xdgCachedData
));
413 if (xdgUpdateHomeDirectories(cache
) &&
414 xdgUpdateDirectoryLists(cache
))
416 /* Update successful, replace pointer to old cache with pointer to new cache */
417 oldCache
= xdgGetCache(handle
);
418 handle
->reserved
= cache
;
421 xdgFreeData(oldCache
);
428 /* Update failed, discard new cache and leave old cache unmodified */
435 /** Find all existing files corresponding to relativePath relative to each item in dirList.
436 * @param relativePath Relative path to search for.
437 * @param dirList <tt>NULL</tt>-terminated list of directory paths.
438 * @return A sequence of null-terminated strings terminated by a
439 * double-<tt>NULL</tt> (empty string) and allocated using malloc().
441 static char * xdgFindExisting(const char * relativePath
, const char * const * dirList
)
444 char * returnString
= 0;
448 const char * const * item
;
450 for (item
= dirList
; *item
; item
++)
452 if (!(fullPath
= (char*)malloc(strlen(*item
)+strlen(relativePath
)+2)))
454 if (returnString
) free(returnString
);
457 strcpy(fullPath
, *item
);
458 if (fullPath
[strlen(fullPath
)-1] != DIR_SEPARATOR_CHAR
)
459 strcat(fullPath
, DIR_SEPARATOR_STR
);
460 strcat(fullPath
, relativePath
);
461 testFile
= fopen(fullPath
, "r");
464 if (!(tmpString
= (char*)realloc(returnString
, strLen
+strlen(fullPath
)+2)))
470 returnString
= tmpString
;
471 strcpy(&returnString
[strLen
], fullPath
);
472 strLen
= strLen
+strlen(fullPath
)+1;
478 returnString
[strLen
] = 0;
481 if ((returnString
= (char*)malloc(2)))
482 strcpy(returnString
, "\0");
487 /** Open first possible config file corresponding to relativePath.
488 * @param relativePath Path to scan for.
489 * @param mode Mode with which to attempt to open files (see fopen modes).
490 * @param dirList <tt>NULL</tt>-terminated list of paths in which to search for relativePath.
491 * @return File pointer if successful else @c NULL. Client must use @c fclose to close file.
493 static FILE * xdgFileOpen(const char * relativePath
, const char * mode
, const char * const * dirList
)
497 const char * const * item
;
499 for (item
= dirList
; *item
; item
++)
501 if (!(fullPath
= (char*)malloc(strlen(*item
)+strlen(relativePath
)+2)))
503 strcpy(fullPath
, *item
);
504 if (fullPath
[strlen(fullPath
)-1] != DIR_SEPARATOR_CHAR
)
505 strcat(fullPath
, DIR_SEPARATOR_STR
);
506 strcat(fullPath
, relativePath
);
507 testFile
= fopen(fullPath
, mode
);
515 int xdgMakePath(const char * path
, mode_t mode
)
517 int length
= strlen(path
);
522 if (length
== 0 || (length
== 1 && path
[0] == DIR_SEPARATOR_CHAR
))
525 if (!(tmpPath
= (char*)malloc(length
+1)))
530 strcpy(tmpPath
, path
);
531 if (tmpPath
[length
-1] == DIR_SEPARATOR_CHAR
)
532 tmpPath
[length
-1] = '\0';
534 /* skip tmpPath[0] since if it's a seperator we have an absolute path */
535 for (tmpPtr
= tmpPath
+1; *tmpPtr
; ++tmpPtr
)
537 if (*tmpPtr
== DIR_SEPARATOR_CHAR
)
540 if (mkdir(tmpPath
, mode
) == -1)
548 *tmpPtr
= DIR_SEPARATOR_CHAR
;
551 ret
= mkdir(tmpPath
, mode
);
556 /** Get a home directory from the environment or a fallback relative to @c \$HOME.
557 * Sets @c errno to @c ENOMEM if unable to allocate duplicate string.
558 * Sets @c errno to @c EINVAL if variable is not set or empty.
559 * @param envname Name of environment variable.
560 * @param relativefallback Path starting with "/" and relative to @c \$HOME to use as fallback.
561 * @param fallbacklength @c strlen(relativefallback).
562 * @return The home directory path or @c NULL of an error occurs.
564 static char * xdgGetRelativeHome(const char *envname
, const char *relativefallback
, unsigned int fallbacklength
)
567 if (!(relhome
= xdgEnvDup(envname
)) && errno
!= ENOMEM
)
571 unsigned int homelen
;
572 if (!(home
= xdgGetEnv("HOME")))
574 if (!(relhome
= (char*)malloc((homelen
= strlen(home
))+fallbacklength
))) return NULL
;
575 memcpy(relhome
, home
, homelen
);
576 memcpy(relhome
+homelen
, relativefallback
, fallbacklength
+1);
581 const char * xdgDataHome(xdgHandle
*handle
)
584 return xdgGetCache(handle
)->dataHome
;
586 return xdgGetRelativeHome("XDG_DATA_HOME", DefaultRelativeDataHome
, sizeof(DefaultRelativeDataHome
)-1);
588 const char * xdgConfigHome(xdgHandle
*handle
)
591 return xdgGetCache(handle
)->configHome
;
593 return xdgGetRelativeHome("XDG_CONFIG_HOME", DefaultRelativeConfigHome
, sizeof(DefaultRelativeConfigHome
)-1);
595 const char * const * xdgDataDirectories(xdgHandle
*handle
)
598 return (const char * const *)&(xdgGetCache(handle
)->searchableDataDirectories
[1]);
600 return (const char * const *)xdgGetDirectoryLists("XDG_DATA_DIRS", NULL
, DefaultDataDirectoriesList
);
602 const char * const * xdgSearchableDataDirectories(xdgHandle
*handle
)
605 return (const char * const *)xdgGetCache(handle
)->searchableDataDirectories
;
608 char *datahome
= (char*)xdgDataHome(NULL
);
610 if (datahome
&& !(datadirs
= xdgGetDirectoryLists("XDG_DATA_DIRS", datahome
, DefaultDataDirectoriesList
)))
612 return (const char * const *)datadirs
;
615 const char * const * xdgConfigDirectories(xdgHandle
*handle
)
618 return (const char * const *)&(xdgGetCache(handle
)->searchableConfigDirectories
[1]);
620 return (const char * const *)xdgGetDirectoryLists("XDG_CONFIG_DIRS", NULL
, DefaultConfigDirectoriesList
);
622 const char * const * xdgSearchableConfigDirectories(xdgHandle
*handle
)
625 return (const char * const *)xdgGetCache(handle
)->searchableConfigDirectories
;
628 char *confighome
= (char*)xdgConfigHome(NULL
);
629 char **configdirs
= 0;
630 if (confighome
&& !(configdirs
= xdgGetDirectoryLists("XDG_CONFIG_DIRS", confighome
, DefaultConfigDirectoriesList
)))
632 return (const char * const *)configdirs
;
635 const char * xdgCacheHome(xdgHandle
*handle
)
638 return xdgGetCache(handle
)->cacheHome
;
640 return xdgGetRelativeHome("XDG_CACHE_HOME", DefaultRelativeCacheHome
, sizeof(DefaultRelativeCacheHome
)-1);
642 char * xdgDataFind(const char * relativePath
, xdgHandle
*handle
)
644 const char * const * dirs
= xdgSearchableDataDirectories(handle
);
645 char * result
= xdgFindExisting(relativePath
, dirs
);
646 if (!handle
) xdgFreeStringList((char**)dirs
);
649 char * xdgConfigFind(const char * relativePath
, xdgHandle
*handle
)
651 const char * const * dirs
= xdgSearchableConfigDirectories(handle
);
652 char * result
= xdgFindExisting(relativePath
, dirs
);
653 if (!handle
) xdgFreeStringList((char**)dirs
);
656 FILE * xdgDataOpen(const char * relativePath
, const char * mode
, xdgHandle
*handle
)
658 const char * const * dirs
= xdgSearchableDataDirectories(handle
);
659 FILE * result
= xdgFileOpen(relativePath
, mode
, dirs
);
660 if (!handle
) xdgFreeStringList((char**)dirs
);
663 FILE * xdgConfigOpen(const char * relativePath
, const char * mode
, xdgHandle
*handle
)
665 const char * const * dirs
= xdgSearchableConfigDirectories(handle
);
666 FILE * result
= xdgFileOpen(relativePath
, mode
, dirs
);
667 if (!handle
) xdgFreeStringList((char**)dirs
);