Added support for different path styles, i.e. win32.
[libxdg-basedir.git] / src / basedir.c
blobbd3b452499212c4079b3a981ac03f0848e0214ad
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:
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.
25 /** @file basedir.c
26 * @brief Implementation of the XDG basedir specification. */
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
32 #if STDC_HEADERS || HAVE_STDLIB_H
33 # include <stdlib.h>
34 #endif
35 #if HAVE_MEMORY_H
36 # include <memory.h>
37 #endif
38 #if HAVE_STRING_H
39 # include <string.h>
40 #else
41 # if HAVE_STRINGS_H
42 # include <strings.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
54 #else
55 # define DIR_SEPARATOR_CHAR '/'
56 # define DIR_SEPARATOR_STR "/"
57 # define PATH_SEPARATOR_CHAR ':'
58 # define PATH_SEPARATOR_STR ":"
59 #endif
61 #include <stdarg.h>
62 #include <basedir.h>
64 #ifndef MAX
65 #define MAX(a, b) ((b) > (a) ? (b) : (a))
66 #endif
68 static const char
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
78 char * dataHome;
79 char * configHome;
80 char * cacheHome;
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;
87 } xdgCachedData;
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))
97 return handle;
98 else
99 free(handle);
100 return 0;
103 /** Free all memory used by a NULL-terminated string list */
104 static void xdgFreeStringList(char** list)
106 char** ptr = list;
107 if (!list) return;
108 for (; *ptr; ptr++)
109 free(*ptr);
110 free(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);
121 cache->dataHome = 0;
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);
138 xdgFreeData(cache);
139 free(cache);
140 free(handle);
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
146 * unset or empty.
148 static char* xdgGetEnv(const char* name, const char* defaultValue)
150 const char* env;
151 char* value;
153 env = getenv(name);
154 if (env && env[0])
156 if (!(value = (char*)malloc(strlen(env)+1))) return 0;
157 strcpy(value, env);
159 else
161 if (!(value = (char*)malloc(strlen(defaultValue)+1))) return 0;
162 strcpy(value, defaultValue);
164 return value;
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;
173 char** itemlist;
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 */
181 #endif
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
194 #endif
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];
207 ++j, ++k;
209 #endif
210 itemlist[i][k] = string[j];
212 /* move to next string */
213 string += j;
214 if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
216 return itemlist;
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, ...)
227 const char* env;
228 va_list ap;
229 char* item;
230 const char* arg;
231 char** itemlist;
232 int i;
234 env = getenv(name);
235 if (env && env[0])
237 if (!(item = (char*)malloc(strlen(env)+1))) return 0;
238 strcpy(item, env);
240 itemlist = xdgSplitPath(item);
241 free(item);
243 else
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; }
254 strcpy(item, arg);
255 itemlist[i] = item;
257 va_end(ap);
259 return itemlist;
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)
268 const char* env;
269 char* home, *defVal;
271 env = getenv("HOME");
272 if (!env || !env[0])
273 return false;
274 if (!(home = (char*)malloc(strlen(env)+1))) return false;
275 strcpy(home, env);
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;
294 free(defVal);
295 free(home);
297 return true;
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)
306 char** itemlist;
307 int size;
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);
316 return false;
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));
321 free(itemlist);
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);
329 return false;
331 cache->searchableConfigDirectories[0] = cache->configHome;
332 memcpy(&(cache->searchableConfigDirectories[1]), itemlist, sizeof(char*)*(size+1));
333 free(itemlist);
335 return true;
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;
350 return true;
352 else
354 /* Update failed, discard new cache and leave old cache unmodified */
355 xdgFreeData(cache);
356 free(cache);
357 return false;
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)
369 char * fullPath;
370 char * returnString = 0;
371 char * tmpString;
372 int strLen = 0;
373 FILE * testFile;
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);
381 return 0;
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");
388 if (testFile)
390 if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
392 free(returnString);
393 free(fullPath);
394 return 0;
396 returnString = tmpString;
397 strcpy(&returnString[strLen], fullPath);
398 strLen = strLen+strlen(fullPath)+1;
399 fclose(testFile);
401 free(fullPath);
403 if (returnString)
404 returnString[strLen] = 0;
405 else
407 if ((returnString = (char*)malloc(2)))
408 strcpy(returnString, "\0");
410 return returnString;
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)
421 char * fullPath;
422 FILE * testFile;
423 const char * const * item;
425 for (item = dirList; *item; item++)
427 if (fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2))
428 return 0;
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);
434 free(fullPath);
435 if (testFile)
436 return testFile;
438 return 0;
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));