2 * @brief Implementation of the XDG basedir specification. */
10 #define MAX(a, b) ((b) > (a) ? (b) : (a))
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
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
;
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
))
48 /** Free all memory used by a NULL-terminated string list */
49 static void xdgFreeStringList(char** list
)
57 /** Free all data in the cache and set pointers to null. */
58 static void xdgFreeData(xdgCachedData
*cache
)
62 // the first element of the directory lists is usually the home directory
63 if (cache
->searchableDataDirectories
[0] != cache
->dataHome
)
64 free(cache
->dataHome
);
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
);
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
92 static char* xdgGetEnv(const char* name
, const char* defaultValue
)
100 if (!(value
= (char*)malloc(strlen(env
)+1))) return 0;
105 if (!(value
= (char*)malloc(strlen(defaultValue
)+1))) return 0;
106 strcpy(value
, defaultValue
);
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
;
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
);
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
];
146 itemlist
[i
][k
] = ptr
[j
];
149 if (*ptr
== ':') ptr
++; // skip seperator
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
, ...)
172 if (!(item
= (char*)malloc(strlen(env
)+1))) return 0;
175 itemlist
= xdgSplitPath(item
);
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; }
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
)
206 env
= getenv("HOME");
209 if (!(home
= (char*)malloc(strlen(env
)+1))) return false;
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;
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
)
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
);
253 cache
->searchableDataDirectories
[0] = cache
->dataHome
;
254 memcpy(&(cache
->searchableDataDirectories
[1]), itemlist
, sizeof(char*)*(size
+1));
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
);
265 cache
->searchableConfigDirectories
[0] = cache
->configHome
;
266 memcpy(&(cache
->searchableConfigDirectories
[1]), itemlist
, sizeof(char*)*(size
+1));
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
;
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
)
302 char * returnString
= 0;
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
);
315 strcpy(fullPath
, *item
);
316 strcat(fullPath
, relativePath
);
317 testFile
= fopen(fullPath
, "r");
320 if (!(tmpString
= (char*)realloc(returnString
, strLen
+strlen(fullPath
)+2)))
326 returnString
= tmpString
;
327 strcpy(&returnString
[strLen
], fullPath
);
328 strLen
= strLen
+strlen(fullPath
)+1;
335 returnString
[strLen
+1] = 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
)
352 const char * const * item
;
354 for (item
= dirList
; *item
; item
++)
356 if (fullPath
= (char*)malloc(strlen(*item
)+strlen(relativePath
)+1))
358 strcpy(fullPath
, *item
);
359 strcat(fullPath
, relativePath
);
360 testFile
= fopen(fullPath
, mode
);
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
));