Fixed bug in xdgSplitPath: no return statement
[libxdg-basedir.git] / src / basedir.c
blobace61e6a2182d8d001d3c884a2e21a5a0e559405
1 /** @file basedir.c
2 * @brief Implementation of the XDG basedir specification. */
4 #include <stdlib.h>
5 #include <string.h>
6 #include <stdarg.h>
7 #include <basedir.h>
9 #ifndef MAX
10 #define MAX(a, b) ((b) > (a) ? (b) : (a))
11 #endif
13 static const char
14 DefaultRelativeDataHome[] = "/.local/share",
15 DefaultRelativeConfigHome[] = "/.config",
16 DefaultDataDirectories1[] = "/usr/local/share",
17 DefaultDataDirectories2[] = "/usr/share",
18 DefaultConfigDirectories[] = "/etc/xdg",
19 DefaultRelativeCacheHome[] = "/.cache";
21 typedef struct _xdgCachedData
23 char * dataHome;
24 char * configHome;
25 char * cacheHome;
26 // Note: string lists are null-terminated and all items
27 // except the first are assumed to be allocated using malloc.
28 // The first item is assumed to be allocated by malloc only if
29 // it is not equal to the appropriate home directory string above.
30 char ** searchableDataDirectories;
31 char ** searchableConfigDirectories;
32 } xdgCachedData;
34 #define GET_CACHE(handle) ((xdgCachedData*)(handle->reserved))
36 xdgHandle xdgAllocHandle()
38 xdgHandle handle = (xdgHandle)malloc(sizeof(*handle));
39 if (!handle) return 0;
40 handle->reserved = 0; // So xdgUpdateData() doesn't free it
41 if (xdgUpdateData(handle))
42 return handle;
43 else
44 free(handle);
45 return 0;
48 /** Free all memory used by a NULL-terminated string list */
49 static void xdgFreeStringList(char** list)
51 if (!list) return;
52 for (; *list; list++)
53 free(*list);
54 free(list);
57 /** Free all data in the cache and set pointers to null. */
58 static void xdgFreeData(xdgCachedData *cache)
60 if (cache->dataHome);
62 // the first element of the directory lists is usually the home directory
63 if (cache->searchableDataDirectories[0] != cache->dataHome)
64 free(cache->dataHome);
65 cache->dataHome = 0;
67 if (cache->configHome);
69 if (cache->searchableConfigDirectories[0] != cache->configHome)
70 free(cache->configHome);
71 cache->configHome = 0;
73 xdgFreeStringList(cache->searchableDataDirectories);
74 cache->searchableDataDirectories = 0;
75 xdgFreeStringList(cache->searchableConfigDirectories);
76 cache->searchableConfigDirectories = 0;
79 void xdgFreeHandle(xdgHandle handle)
81 xdgCachedData* cache = (xdgCachedData*)(handle->reserved);
82 xdgFreeData(cache);
83 free(cache);
84 free(handle);
87 /** Get value for environment variable $name, defaulting to "defaultValue".
88 * @param name Name of environment variable.
89 * @param defaultValue Value to assume for environment variable if it is
90 * unset or empty.
92 static char* xdgGetEnv(const char* name, const char* defaultValue)
94 const char* env;
95 char* value;
97 env = getenv(name);
98 if (env && env[0])
100 if (!(value = (char*)malloc(strlen(env)+1))) return 0;
101 strcpy(value, env);
103 else
105 if (!(value = (char*)malloc(strlen(defaultValue)+1))) return 0;
106 strcpy(value, defaultValue);
108 return value;
111 /** Split string at ':', return null-terminated list of resulting strings.
112 * @param string String to be split
114 static char** xdgSplitPath(const char* string)
116 unsigned int size, i, j, k;
117 char** itemlist;
118 const char* ptr;
120 // Get the number of paths
121 size=2; // One item more than seperators + terminating null item
122 for (i = 0; string[i]; ++i)
124 if (string[i] == '\\' && string[i+1]) ++i; // skip escaped characters including seperators
125 else if (string[i] == ':') ++size;
128 if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return 0;
129 memset(itemlist, 0, sizeof(char*)*size);
131 ptr = string;
132 i = 0;
133 while (*ptr)
135 for (j = 0; ptr[j] && ptr[j] != ':'; ++j)
136 if (ptr[j] == '\\' && ptr[j+1]) ++j;
137 if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return 0; }
138 for (k = j = 0; ptr[j] && ptr[j] != ':'; ++j, ++k)
140 if (ptr[j] == '\\' && ptr[j+1] == ':') ++j; // replace escaped ':' with just ':'
141 else if (ptr[j] == '\\' && ptr[j+1]) // skip escaped characters so escaping remains aligned to pairs.
143 itemlist[i][k]=ptr[j];
144 ++j, ++k;
146 itemlist[i][k] = ptr[j];
148 ptr += j;
149 if (*ptr == ':') ptr++; // skip seperator
151 return itemlist;
154 /** Get $PATH-style environment variable as list of strings.
155 * If $name is unset or empty, use default strings specified by variable arguments.
156 * @param name Name of environment variable
157 * @param numDefaults Number of default paths in variable argument list
158 * @param ... numDefaults number of strings to be copied and used as defaults
160 static char** xdgGetPathListEnv(const char* name, int numDefaults, ...)
162 const char* env;
163 va_list ap;
164 char* item;
165 const char* arg;
166 char** itemlist;
167 int i;
169 env = getenv(name);
170 if (env && env[0])
172 if (!(item = (char*)malloc(strlen(env)+1))) return 0;
173 strcpy(item, env);
175 itemlist = xdgSplitPath(item);
176 free(item);
178 else
180 if (!(itemlist = (char**)malloc(sizeof(char*)*numDefaults+1))) return 0;
181 memset(itemlist, 0, sizeof(char*)*(numDefaults+1));
183 // Copy the varargs into the itemlist
184 va_start(ap, numDefaults);
185 for (i = 0; i < numDefaults; i++)
187 arg = va_arg(ap, const char*);
188 if (!(item = (char*)malloc(strlen(arg)+1))) { xdgFreeStringList(itemlist); return 0; }
189 strcpy(item, arg);
190 itemlist[i] = item;
192 va_end(ap);
194 return itemlist;
197 /** Update all *Home variables of cache.
198 * This includes xdgCachedData::dataHome, xdgCachedData::configHome and xdgCachedData::cacheHome.
199 * @param cache Data cache to be updated
201 static bool xdgUpdateHomeDirectories(xdgCachedData* cache)
203 const char* env;
204 char* home, *defVal;
206 env = getenv("HOME");
207 if (!env || !env[0])
208 return false;
209 if (!(home = (char*)malloc(strlen(env)+1))) return false;
210 strcpy(home, env);
212 // Allocate maximum needed for any of the 3 default values
213 defVal = (char*)malloc(strlen(home)+
214 MAX(MAX(sizeof(DefaultRelativeDataHome), sizeof(DefaultRelativeConfigHome)), sizeof(DefaultRelativeCacheHome)));
215 if (!defVal) return false;
217 strcpy(defVal, home);
218 strcat(defVal, DefaultRelativeDataHome);
219 if (!(cache->dataHome = xdgGetEnv("XDG_DATA_HOME", defVal))) return false;
221 defVal[strlen(home)] = 0;
222 strcat(defVal, DefaultRelativeConfigHome);
223 if (!(cache->configHome = xdgGetEnv("XDG_CONFIG_HOME", defVal))) return false;
225 defVal[strlen(home)] = 0;
226 strcat(defVal, DefaultRelativeCacheHome);
227 if (!(cache->cacheHome = xdgGetEnv("XDG_CACHE_HOME", defVal))) return false;
229 free(defVal);
230 free(home);
232 return true;
235 /** Update all *Directories variables of cache.
236 * This includes xdgCachedData::searchableDataDirectories and xdgCachedData::searchableConfigDirectories.
237 * @param cache Data cache to be updated.
239 static bool xdgUpdateDirectoryLists(xdgCachedData* cache)
241 char** itemlist;
242 int size;
244 itemlist = xdgGetPathListEnv("XDG_DATA_DIRS", 2,
245 DefaultDataDirectories1, DefaultDataDirectories2);
246 if (!itemlist) return false;
247 for (size = 0; itemlist[size]; size++) ; // Get list size
248 if (!(cache->searchableDataDirectories = (char**)malloc(sizeof(char*)*(size+2))))
250 xdgFreeStringList(itemlist);
251 return false;
253 cache->searchableDataDirectories[0] = cache->dataHome;
254 memcpy(&(cache->searchableDataDirectories[1]), itemlist, sizeof(char*)*(size+1));
255 free(itemlist);
257 itemlist = xdgGetPathListEnv("XDG_CONFIG_DIRS", 1, DefaultConfigDirectories);
258 if (!itemlist) return false;
259 for (size = 0; itemlist[size]; size++) ; // Get list size
260 if (!(cache->searchableConfigDirectories = (char**)malloc(sizeof(char*)*(size+2))))
262 xdgFreeStringList(itemlist);
263 return false;
265 cache->searchableConfigDirectories[0] = cache->configHome;
266 memcpy(&(cache->searchableConfigDirectories[1]), itemlist, sizeof(char*)*(size+1));
267 free(itemlist);
269 return true;
272 bool xdgUpdateData(xdgHandle handle)
274 xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
275 if (!cache) return false;
276 memset(cache, 0, sizeof(xdgCachedData));
278 if (xdgUpdateHomeDirectories(cache) &&
279 xdgUpdateDirectoryLists(cache))
281 if (handle->reserved) free(handle->reserved);
282 handle->reserved = cache;
283 return true;
285 else
287 xdgFreeData(cache);
288 free(cache);
289 return false;
293 /** Find all existing files corresponding to relativePath relative to each item in dirList.
294 * @param relativePath Relative path to search for.
295 * @param dirList <tt>NULL</tt>-terminated list of directory paths.
296 * @return A sequence of null-terminated strings terminated by a double-<tt>NULL</tt> (empty string)
297 * and allocated using malloc().
299 static const char* xdgFindExisting(const char * relativePath, const char * const * dirList)
301 char * fullPath;
302 char * returnString = 0;
303 char * tmpString;
304 int strLen = 0;
305 FILE * testFile;
306 const char * const * item;
308 for (item = dirList; *item; item++)
310 if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+1)))
312 if (returnString) free(returnString);
313 return 0;
315 strcpy(fullPath, *item);
316 strcat(fullPath, relativePath);
317 testFile = fopen(fullPath, "r");
318 if (testFile)
320 if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
322 free(returnString);
323 free(fullPath);
324 return 0;
326 returnString = tmpString;
327 strcpy(&returnString[strLen], fullPath);
328 strLen = strLen+strlen(fullPath)+1;
329 fclose(testFile);
331 free(fullPath);
333 if (returnString)
335 returnString[strLen+1] = 0;
336 return returnString;
338 else
339 return "\0";
342 /** Open first possible config file corresponding to relativePath.
343 * @param relativePath Path to scan for.
344 * @param mode Mode with which to attempt to open files (see fopen modes).
345 * @param dirList <tt>NULL</tt>-terminated list of paths in which to search for relativePath.
346 * @return File pointer if successful else @c NULL. Client must use @c fclose to close file.
348 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
350 char * fullPath;
351 FILE * testFile;
352 const char * const * item;
354 for (item = dirList; *item; item++)
356 if (fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+1))
357 return 0;
358 strcpy(fullPath, *item);
359 strcat(fullPath, relativePath);
360 testFile = fopen(fullPath, mode);
361 free(fullPath);
362 if (testFile)
363 return testFile;
365 return 0;
368 const char * xdgDataHome(xdgHandle handle)
370 return GET_CACHE(handle)->dataHome;
372 const char * xdgConfigHome(xdgHandle handle)
374 return GET_CACHE(handle)->configHome;
376 const char * const * xdgDataDirectories(xdgHandle handle)
378 return &(GET_CACHE(handle)->searchableDataDirectories[1]);
380 const char * const * xdgSearchableDataDirectories(xdgHandle handle)
382 return GET_CACHE(handle)->searchableDataDirectories;
384 const char * const * xdgConfigDirectories(xdgHandle handle)
386 return &(GET_CACHE(handle)->searchableConfigDirectories[1]);
388 const char * const * xdgSearchableConfigDirectories(xdgHandle handle)
390 return GET_CACHE(handle)->searchableConfigDirectories;
392 const char * xdgCacheHome(xdgHandle handle)
394 return GET_CACHE(handle)->cacheHome;
396 const char * xdgDataFind(const char * relativePath, xdgHandle handle)
398 return xdgFindExisting(relativePath, xdgSearchableDataDirectories(handle));
400 const char * xdgConfigFind(const char * relativePath, xdgHandle handle)
402 return xdgFindExisting(relativePath, xdgSearchableConfigDirectories(handle));
404 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle handle)
406 return xdgFileOpen(relativePath, mode, xdgSearchableDataDirectories(handle));
408 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle handle)
410 return xdgFileOpen(relativePath, mode, xdgSearchableConfigDirectories(handle));