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 <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
xdgZeroMemory(void * p,int n)62 static void xdgZeroMemory(void* p, int n)
63 {
64 	while (n > 0) { ((char*)p)[--n] = 0; }
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 	/* are to be allocated using malloc. */
111 	char ** searchableDataDirectories;
112 	char ** searchableConfigDirectories;
113 } xdgCachedData;
114 
115 /** Get cache object associated with a handle */
xdgGetCache(xdgHandle * handle)116 static xdgCachedData* xdgGetCache(xdgHandle *handle)
117 {
118 	return ((xdgCachedData*)(handle->reserved));
119 }
120 
xdgInitHandle(xdgHandle * handle)121 xdgHandle * xdgInitHandle(xdgHandle *handle)
122 {
123 	if (!handle) return 0;
124 	handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
125 	if (xdgUpdateData(handle))
126 		return handle;
127 	return 0;
128 }
129 
130 /** Free all memory used by a NULL-terminated string list */
xdgFreeStringList(char ** list)131 static void xdgFreeStringList(char** list)
132 {
133 	char** ptr = list;
134 	if (!list) return;
135 	for (; *ptr; ptr++)
136 		free(*ptr);
137 	free(list);
138 }
139 
140 /** Free all data in the cache and set pointers to null. */
xdgFreeData(xdgCachedData * cache)141 static void xdgFreeData(xdgCachedData *cache)
142 {
143 	if (cache->dataHome)
144 	{
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);
148 		cache->dataHome = 0;
149 	}
150 	if (cache->configHome)
151 	{
152 		if (cache->searchableConfigDirectories && cache->searchableConfigDirectories[0] != cache->configHome)
153 			free(cache->configHome);
154 		cache->configHome = 0;
155 	}
156 	if (cache->cacheHome)
157 	{
158 		free(cache->cacheHome);
159 		cache->cacheHome = 0;
160 	}
161 	if (cache->runtimeDirectory)
162 	{
163 		free(cache->runtimeDirectory);
164 		cache->runtimeDirectory = 0;
165 	}
166 	xdgFreeStringList(cache->searchableDataDirectories);
167 	cache->searchableDataDirectories = 0;
168 	xdgFreeStringList(cache->searchableConfigDirectories);
169 	cache->searchableConfigDirectories = 0;
170 }
171 
xdgWipeHandle(xdgHandle * handle)172 void xdgWipeHandle(xdgHandle *handle)
173 {
174 	xdgCachedData* cache = xdgGetCache(handle);
175 	xdgFreeData(cache);
176 	free(cache);
177 }
178 
179 /** Split string at ':', return null-terminated list of resulting strings.
180  * @param string String to be split
181  */
xdgSplitPath(const char * string)182 static char** xdgSplitPath(const char* string)
183 {
184 	unsigned int size, i, j, k;
185 	char** itemlist;
186 
187 	/* Get the number of paths */
188 	size=2; /* One item more than seperators + terminating null item */
189 	for (i = 0; string[i]; ++i)
190 	{
191 #ifndef NO_ESCAPES_IN_PATHS
192 		if (string[i] == '\\' && string[i+1])
193 		{
194 			/* skip escaped characters including seperators */
195 			++i;
196 			continue;
197 		}
198 #endif
199 		if (string[i] == PATH_SEPARATOR_CHAR) ++size;
200 	}
201 
202 	if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
203 	xdgZeroMemory(itemlist, sizeof(char*)*size);
204 
205 	for (i = 0; *string; ++i)
206 	{
207 		/* get length of current string  */
208 		for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
209 #ifndef NO_ESCAPES_IN_PATHS
210 			if (string[j] == '\\' && string[j+1]) ++j
211 #endif
212 			;
213 
214 		if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
215 
216 		/* transfer string, unescaping any escaped seperators */
217 		for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
218 		{
219 #ifndef NO_ESCAPES_IN_PATHS
220 			if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
221 			else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
222 			{
223 				itemlist[i][k]=string[j];
224 				++j, ++k;
225 			}
226 #endif
227 			itemlist[i][k] = string[j];
228 		}
229 		itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
230 		/* move to next string */
231 		string += j;
232 		if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
233 	}
234 	return itemlist;
235 }
236 
237 /** Get $PATH-style environment variable as list of strings.
238  * If $name is unset or empty, use default strings specified by variable arguments.
239  * @param name Name of environment variable
240  * @param defaults NULL-terminated list of strings to be copied and used as defaults
241  */
xdgGetPathListEnv(const char * name,const char ** defaults)242 static char** xdgGetPathListEnv(const char* name, const char ** defaults)
243 {
244 	const char* env;
245 	char* item;
246 	char** itemlist;
247 	int i, size;
248 
249 	env = getenv(name);
250 	if (env && env[0])
251 	{
252 		if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
253 		strcpy(item, env);
254 
255 		itemlist = xdgSplitPath(item);
256 		free(item);
257 	}
258 	else
259 	{
260 		if (!defaults) return NULL;
261 		for (size = 0; defaults[size] != NULL; ++size);
262 		++size; // include NULL terminator
263 		if (!(itemlist = (char**)malloc(sizeof(char*) * size))) return NULL;
264 		xdgZeroMemory(itemlist, sizeof(char*)*(size));
265 
266 		/* Copy defaults into itemlist. */
267 		for (i = 0; defaults[i]; ++i)
268 		{
269 			if (!(item = strdup(defaults[i]))) { xdgFreeStringList(itemlist); return NULL; }
270 			itemlist[i] = item;
271 		}
272 	}
273 	return itemlist;
274 }
275 
276 /** Get value of an environment variable.
277  * Sets @c errno to @c EINVAL if variable is not set or empty.
278  * @param name Name of environment variable.
279  * @return The environment variable or NULL if an error occurs.
280  */
xdgGetEnv(const char * name)281 static char* xdgGetEnv(const char *name)
282 {
283 	char *env = getenv(name);
284 	if (env && env[0])
285 		return env;
286 	/* What errno signifies missing env var? */
287 	errno = EINVAL;
288 	return NULL;
289 }
290 
291 /** Duplicate an environment variable.
292  * Sets @c errno to @c ENOMEM if unable to allocate duplicate string.
293  * Sets @c errno to @c EINVAL if variable is not set or empty.
294  * @return The duplicated string or NULL if an error occurs.
295  */
xdgEnvDup(const char * name)296 static char* xdgEnvDup(const char *name)
297 {
298 	const char *env;
299 	if ((env = xdgGetEnv(name)))
300 		return strdup(env);
301 	else
302 		return NULL;
303 }
304 
305 /** Update all *Home variables of cache.
306  * This includes xdgCachedData::dataHome, xdgCachedData::configHome and xdgCachedData::cacheHome.
307  * @param cache Data cache to be updated
308  */
xdgUpdateHomeDirectories(xdgCachedData * cache)309 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
310 {
311 	const char *homeenv;
312 	char *value;
313 	unsigned int homelen;
314 	static const unsigned int extralen =
315 		MAX(MAX(sizeof(DefaultRelativeDataHome),
316 					sizeof(DefaultRelativeConfigHome)),
317 				sizeof(DefaultRelativeCacheHome));
318 
319 	if (!(cache->dataHome = xdgEnvDup("XDG_DATA_HOME")) && errno == ENOMEM) return FALSE;
320 	if (!(cache->configHome = xdgEnvDup("XDG_CONFIG_HOME")) && errno == ENOMEM) return FALSE;
321 	if (!(cache->cacheHome = xdgEnvDup("XDG_CACHE_HOME")) && errno == ENOMEM) return FALSE;
322 	if (!(cache->runtimeDirectory = xdgEnvDup("XDG_RUNTIME_DIR")) && errno == ENOMEM) return FALSE;
323 	errno = 0;
324 
325 	if (cache->dataHome && cache->configHome && cache->cacheHome) return TRUE;
326 
327 	if (!(homeenv = xdgGetEnv("HOME")))
328 		return FALSE;
329 
330 	/* Allocate maximum needed for any of the 3 default values */
331 	homelen = strlen(homeenv);
332 	value = malloc(homelen + extralen);
333 	if (value == NULL) return FALSE;
334 	memcpy(value, homeenv, homelen+1);
335 
336 	if (!cache->dataHome)
337 	{
338 		memcpy(value+homelen, DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome));
339 		cache->dataHome = strdup(value);
340 	}
341 
342 	if (!cache->configHome)
343 	{
344 		memcpy(value+homelen, DefaultRelativeConfigHome, sizeof(DefaultRelativeConfigHome));
345 		cache->configHome = strdup(value);
346 	}
347 
348 	if (!cache->cacheHome)
349 	{
350 		memcpy(value+homelen, DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome));
351 		cache->cacheHome = strdup(value);
352 	}
353 
354 	free(value);
355 
356 	/* free does not change errno, and the prev call *must* have been a strdup,
357 	 * so errno is already set. */
358 	return cache->dataHome && cache->configHome && cache->cacheHome;
359 }
360 
361 /** Get directory lists with initial home directory.
362  * @param envname Environment variable with colon-seperated directories.
363  * @param homedir Home directory for this directory list or NULL. This
364  *                parameter should be allocated on the heap. The returned list
365  *                will start with this path, and should be considered as owning
366  *                the memory.
367  * @param defaults Default directories if environment variable is not set.
368  * @return An array of strings. Both the array and its contents are allocated
369  *         with malloc(). The function xdgFreeStringList is provided for
370  *         conveniantly free()-ing the list and all its elements.
371  */
xdgGetDirectoryLists(const char * envname,char * homedir,const char ** defaults)372 static char** xdgGetDirectoryLists(const char *envname, char *homedir, const char **defaults)
373 {
374 	char **envlist;
375 	char **dirlist;
376 	unsigned int size;
377 
378 	if (!(envlist = xdgGetPathListEnv(envname, defaults)))
379 		return NULL;
380 
381 	for (size = 0; envlist[size]; size++) ; /* Get list size */
382 	if (!(dirlist = (char**)malloc(sizeof(char*)*(size+1+!!homedir))))
383 	{
384 		xdgFreeStringList(envlist);
385 		return NULL;
386 	}
387 	/* "home" directory has highest priority according to spec */
388 	if (homedir)
389 		dirlist[0] = homedir;
390 	memcpy(dirlist+!!homedir, envlist, sizeof(char*)*(size+1));
391 	/* only free the envlist since its elements are now referenced by dirlist */
392 	free(envlist);
393 
394 	return dirlist;
395 }
396 
397 /** Update all *Directories variables of cache.
398  * This includes xdgCachedData::searchableDataDirectories and xdgCachedData::searchableConfigDirectories.
399  * @param cache Data cache to be updated.
400  */
xdgUpdateDirectoryLists(xdgCachedData * cache)401 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
402 {
403 	if (!(cache->searchableDataDirectories = xdgGetDirectoryLists(
404 			"XDG_DATA_DIRS", cache->dataHome, DefaultDataDirectoriesList)))
405 		return FALSE;
406 	if (!(cache->searchableConfigDirectories = xdgGetDirectoryLists(
407 			"XDG_CONFIG_DIRS", cache->configHome, DefaultConfigDirectoriesList)))
408 		return FALSE;
409 
410 	return TRUE;
411 }
412 
xdgUpdateData(xdgHandle * handle)413 int xdgUpdateData(xdgHandle *handle)
414 {
415 	xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
416 	xdgCachedData* oldCache;
417 	if (!cache) return FALSE;
418 	xdgZeroMemory(cache, sizeof(xdgCachedData));
419 
420 	if (xdgUpdateHomeDirectories(cache) &&
421 		xdgUpdateDirectoryLists(cache))
422 	{
423 		/* Update successful, replace pointer to old cache with pointer to new cache */
424 		oldCache = xdgGetCache(handle);
425 		handle->reserved = cache;
426 		if (oldCache)
427 		{
428 			xdgFreeData(oldCache);
429 			free(oldCache);
430 		}
431 		return TRUE;
432 	}
433 	else
434 	{
435 		/* Update failed, discard new cache and leave old cache unmodified */
436 		xdgFreeData(cache);
437 		free(cache);
438 		return FALSE;
439 	}
440 }
441 
442 /** Find all existing files corresponding to relativePath relative to each item in dirList.
443   * @param relativePath Relative path to search for.
444   * @param dirList <tt>NULL</tt>-terminated list of directory paths.
445   * @return A sequence of null-terminated strings terminated by a
446   * 	double-<tt>NULL</tt> (empty string) and allocated using malloc().
447   */
xdgFindExisting(const char * relativePath,const char * const * dirList)448 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
449 {
450 	char * fullPath;
451 	char * returnString = 0;
452 	char * tmpString;
453 	int strLen = 0;
454 	FILE * testFile;
455 	const char * const * item;
456 
457 	for (item = dirList; *item; item++)
458 	{
459 		if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
460 		{
461 			if (returnString) free(returnString);
462 			return 0;
463 		}
464 		strcpy(fullPath, *item);
465 		if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
466 			strcat(fullPath, DIR_SEPARATOR_STR);
467 		strcat(fullPath, relativePath);
468 		testFile = fopen(fullPath, "r");
469 		if (testFile)
470 		{
471 			if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
472 			{
473 				free(returnString);
474 				free(fullPath);
475 				return 0;
476 			}
477 			returnString = tmpString;
478 			strcpy(&returnString[strLen], fullPath);
479 			strLen = strLen+strlen(fullPath)+1;
480 			fclose(testFile);
481 		}
482 		free(fullPath);
483 	}
484 	if (returnString)
485 		returnString[strLen] = 0;
486 	else
487 	{
488 		if ((returnString = (char*)malloc(2)))
489 			strcpy(returnString, "\0");
490 	}
491 	return returnString;
492 }
493 
494 /** Open first possible config file corresponding to relativePath.
495   * @param relativePath Path to scan for.
496   * @param mode Mode with which to attempt to open files (see fopen modes).
497   * @param dirList <tt>NULL</tt>-terminated list of paths in which to search for relativePath.
498   * @return File pointer if successful else @c NULL. Client must use @c fclose to close file.
499   */
xdgFileOpen(const char * relativePath,const char * mode,const char * const * dirList)500 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
501 {
502 	char * fullPath;
503 	FILE * testFile;
504 	const char * const * item;
505 
506 	for (item = dirList; *item; item++)
507 	{
508 		if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
509 			return 0;
510 		strcpy(fullPath, *item);
511 		if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
512 			strcat(fullPath, DIR_SEPARATOR_STR);
513 		strcat(fullPath, relativePath);
514 		testFile = fopen(fullPath, mode);
515 		free(fullPath);
516 		if (testFile)
517 			return testFile;
518 	}
519 	return 0;
520 }
521 
xdgMakePath(const char * path,mode_t mode)522 int xdgMakePath(const char * path, mode_t mode)
523 {
524 	int length = strlen(path);
525 	char * tmpPath;
526 	char * tmpPtr;
527 	int ret;
528 
529 	if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
530 		return 0;
531 
532 	if (!(tmpPath = (char*)malloc(length+1)))
533 	{
534 		errno = ENOMEM;
535 		return -1;
536 	}
537 	strcpy(tmpPath, path);
538 	if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
539 		tmpPath[length-1] = '\0';
540 
541 	/* skip tmpPath[0] since if it's a seperator we have an absolute path */
542 	for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
543 	{
544 		if (*tmpPtr == DIR_SEPARATOR_CHAR)
545 		{
546 			*tmpPtr = '\0';
547 			if (mkdir(tmpPath, mode) == -1)
548 			{
549 				if (errno != EEXIST)
550 				{
551 					free(tmpPath);
552 					return -1;
553 				}
554 			}
555 			*tmpPtr = DIR_SEPARATOR_CHAR;
556 		}
557 	}
558 	ret = mkdir(tmpPath, mode);
559 	free(tmpPath);
560 	return ret;
561 }
562 
563 /** Get a home directory from the environment or a fallback relative to @c \$HOME.
564  * Sets @c errno to @c ENOMEM if unable to allocate duplicate string.
565  * Sets @c errno to @c EINVAL if variable is not set or empty.
566  * @param envname Name of environment variable.
567  * @param relativefallback Path starting with "/" and relative to @c \$HOME to use as fallback.
568  * @param fallbacklength @c strlen(relativefallback).
569  * @return The home directory path or @c NULL of an error occurs.
570  */
xdgGetRelativeHome(const char * envname,const char * relativefallback,unsigned int fallbacklength)571 static char * xdgGetRelativeHome(const char *envname, const char *relativefallback, unsigned int fallbacklength)
572 {
573 	char *relhome;
574 	if (!(relhome = xdgEnvDup(envname)) && errno != ENOMEM)
575 	{
576 		errno = 0;
577 		const char *home;
578 		unsigned int homelen;
579 		if (!(home = xdgGetEnv("HOME")))
580 			return NULL;
581 		if (!(relhome = (char*)malloc((homelen = strlen(home))+fallbacklength+1))) return NULL;
582 		memcpy(relhome, home, homelen);
583 		memcpy(relhome+homelen, relativefallback, fallbacklength+1);
584 	}
585 	return relhome;
586 }
587 
xdgDataHome(xdgHandle * handle)588 const char * xdgDataHome(xdgHandle *handle)
589 {
590 	if (handle)
591 		return xdgGetCache(handle)->dataHome;
592 	else
593 		return xdgGetRelativeHome("XDG_DATA_HOME", DefaultRelativeDataHome, sizeof(DefaultRelativeDataHome)-1);
594 }
595 
xdgConfigHome(xdgHandle * handle)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 
xdgDataDirectories(xdgHandle * handle)604 const char * const * xdgDataDirectories(xdgHandle *handle)
605 {
606 	if (handle)
607 		return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
608 	else
609 		return (const char * const *)xdgGetDirectoryLists("XDG_DATA_DIRS", NULL, DefaultDataDirectoriesList);
610 }
611 
xdgSearchableDataDirectories(xdgHandle * handle)612 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
613 {
614 	if (handle)
615 		return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
616 	else
617 	{
618 		char *datahome = (char*)xdgDataHome(NULL);
619 		char **datadirs = 0;
620 		if (datahome && !(datadirs = xdgGetDirectoryLists("XDG_DATA_DIRS", datahome, DefaultDataDirectoriesList)))
621 			free(datahome);
622 		return (const char * const *)datadirs;
623 	}
624 }
625 
xdgConfigDirectories(xdgHandle * handle)626 const char * const * xdgConfigDirectories(xdgHandle *handle)
627 {
628 	if (handle)
629 		return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
630 	else
631 		return (const char * const *)xdgGetDirectoryLists("XDG_CONFIG_DIRS", NULL, DefaultConfigDirectoriesList);
632 }
633 
xdgSearchableConfigDirectories(xdgHandle * handle)634 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
635 {
636 	if (handle)
637 		return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
638 	else
639 	{
640 		char *confighome = (char*)xdgConfigHome(NULL);
641 		char **configdirs = 0;
642 		if (confighome && !(configdirs = xdgGetDirectoryLists("XDG_CONFIG_DIRS", confighome, DefaultConfigDirectoriesList)))
643 			free(confighome);
644 		return (const char * const *)configdirs;
645 	}
646 }
647 
xdgCacheHome(xdgHandle * handle)648 const char * xdgCacheHome(xdgHandle *handle)
649 {
650 	if (handle)
651 		return xdgGetCache(handle)->cacheHome;
652 	else
653 		return xdgGetRelativeHome("XDG_CACHE_HOME", DefaultRelativeCacheHome, sizeof(DefaultRelativeCacheHome)-1);
654 }
655 
xdgRuntimeDirectory(xdgHandle * handle)656 const char * xdgRuntimeDirectory(xdgHandle *handle)
657 {
658 	if (handle)
659 		return xdgGetCache(handle)->runtimeDirectory;
660 	else
661 		return xdgEnvDup("XDG_RUNTIME_DIRECTORY");
662 }
663 
xdgDataFind(const char * relativePath,xdgHandle * handle)664 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
665 {
666 	const char * const * dirs = xdgSearchableDataDirectories(handle);
667 	char * result = xdgFindExisting(relativePath, dirs);
668 	if (!handle) xdgFreeStringList((char**)dirs);
669 	return result;
670 }
671 
xdgConfigFind(const char * relativePath,xdgHandle * handle)672 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
673 {
674 	const char * const * dirs = xdgSearchableConfigDirectories(handle);
675 	char * result = xdgFindExisting(relativePath, dirs);
676 	if (!handle) xdgFreeStringList((char**)dirs);
677 	return result;
678 }
679 
xdgDataOpen(const char * relativePath,const char * mode,xdgHandle * handle)680 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
681 {
682 	const char * const * dirs = xdgSearchableDataDirectories(handle);
683 	FILE * result = xdgFileOpen(relativePath, mode, dirs);
684 	if (!handle) xdgFreeStringList((char**)dirs);
685 	return result;
686 }
687 
xdgConfigOpen(const char * relativePath,const char * mode,xdgHandle * handle)688 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
689 {
690 	const char * const * dirs = xdgSearchableConfigDirectories(handle);
691 	FILE * result = xdgFileOpen(relativePath, mode, dirs);
692 	if (!handle) xdgFreeStringList((char**)dirs);
693 	return result;
694 }
695