30#ifndef _POSIX_C_SOURCE
31#define _POSIX_C_SOURCE 200809L
55#include "canfigger_version.h"
59static char *grab_str_segment(
char *a,
char **dest,
const int c);
72malloc_wrap(
size_t size)
74 void *retval = malloc(size);
78 perror(
"canfigger: malloc");
89strclone(
const char *src,
size_t n)
91 char *dest = malloc_wrap(n + 1);
104strclone_str(
const char *src)
106 return strclone(src, strlen(src));
152canfigger_free_current_key_node_advance(
struct Canfigger **node)
156 if ((*node)->attributes)
158 if ((*node)->attributes->current)
160 free((*node)->attributes->current);
161 (*node)->attributes->current = NULL;
164 if ((*node)->attributes->str)
166 free((*node)->attributes->str);
167 (*node)->attributes->str = NULL;
170 free((*node)->attributes);
171 (*node)->attributes = NULL;
176 free((*node)->value);
177 (*node)->value = NULL;
206truncate_whitespace(
char *str)
224 while (isspace((
unsigned char) *str))
238grab_str_segment(
char *a,
char **dest,
const int c)
243 while (*a ==
' ' || *a ==
'\t')
246 char *b = strchr(a, c);
249 *dest = strclone_str(a);
254 *dest = strclone(a, len);
258 truncate_whitespace(*dest);
272 tmp_node->
key = NULL;
273 tmp_node->
value = NULL;
275 tmp_node->
next = NULL;
278 (*cur_node)->
next = tmp_node;
282 *cur_node = tmp_node;
289read_entire_file(
const char *filename)
295 FILE *fp = fopen(filename,
"rb");
298 fprintf(stderr,
"canfigger: Failed to open %s: %s\n", filename,
303 if (fseek(fp, 0, SEEK_END) != 0 || (file_size = ftell(fp)) < 0
304 || fseek(fp, 0, SEEK_SET) != 0)
306 fprintf(stderr,
"canfigger: Failed to determine size of %s: %s\n",
307 filename, strerror(errno));
311 buffer = malloc_wrap(file_size + 1);
315 n_bytes = fread(buffer, 1, file_size, fp);
316 if (n_bytes != (
size_t) file_size)
319 fprintf(stderr,
"canfigger: Error reading %s: %s\n", filename,
323 "canfigger: Partial read of %s: expected %ld bytes, got %zu bytes\n",
324 filename, file_size, n_bytes);
329 buffer[file_size] =
'\0';
340 struct Canfigger *root = NULL, *cur_node = NULL;
342 char *file_contents = read_entire_file(file);
343 if (file_contents == NULL)
353 char *parse_start = file_contents;
354 if ((
unsigned char) parse_start[0] == 0xEF &&
355 (
unsigned char) parse_start[1] == 0xBB &&
356 (
unsigned char) parse_start[2] == 0xBF)
360 line.start = parse_start;
362 bool node_complete =
false;
366 line.end = strchr(line.start,
'\n');
368 line.end ? (size_t) (line.end - line.start) : strlen(line.start);
371 if (line.len == 0 && !line.end)
374 char *tmp_line = malloc_wrap(line.len + 1);
377 canfigger_free_list(&root);
382 memcpy(tmp_line, line.start, line.len);
383 tmp_line[line.len] =
'\0';
384 line.start = line.end ? line.end + 1 : line.start + line.len;
386 char *line_ptr = tmp_line;
387 truncate_whitespace(line_ptr);
389 while (isspace((
unsigned char) *line_ptr))
392 if (*line_ptr ==
'\0' || *line_ptr ==
'#' || *line_ptr ==
'[')
400 node_complete =
false;
402 add_key_node(&root, &cur_node);
403 if (cur_node == prev_node)
410 line_ptr = grab_str_segment(line_ptr, &cur_node->key,
'=');
420 line_ptr = grab_str_segment(line_ptr, &cur_node->value, delimiter);
421 if (!cur_node->value)
431 cur_node->attributes = malloc_wrap(
sizeof(
struct attributes));
432 if (!cur_node->attributes)
438 struct attributes *attr_ptr = cur_node->attributes;
441 attr_ptr->
str = strclone_str(line_ptr);
452 char *delimiter_ptr = strchr(attr_ptr->
iter_ptr, delimiter);
453 while (delimiter_ptr)
455 *delimiter_ptr =
'\n';
456 delimiter_ptr = strchr(delimiter_ptr, delimiter);
460 node_complete =
true;
486xdg_base_dir(
const char *xdg_env,
const char *fallback)
488 const char *base = getenv(xdg_env);
493 if (base && *base ==
'/')
494 return strclone_str(base);
496 const char *home = getenv(
"HOME");
500 size_t len = strlen(home) + 1 + strlen(fallback) + 1;
501 char *result = malloc_wrap(len);
504 snprintf(result, len,
"%s/%s", home, fallback);
528dir_for_appname(
const char *appname,
int csidl,
const char *subdir)
530 if (!appname || *appname ==
'\0')
534 if (FAILED(SHGetFolderPathA(NULL, csidl, NULL, 0, base)))
537 size_t len = strlen(base) + 1 + strlen(appname) + 1;
539 len += strlen(subdir) + 1;
540 char *result = malloc_wrap(len);
544 snprintf(result, len,
"%s\\%s\\%s", base, appname, subdir);
546 snprintf(result, len,
"%s\\%s", base, appname);
551dir_for_appname(
const char *appname,
const char *xdg_env,
552 const char *xdg_fallback)
554 if (!appname || *appname ==
'\0')
557 char *base = xdg_base_dir(xdg_env, xdg_fallback);
571 return dir_for_appname(appname, CSIDL_APPDATA, NULL);
573 return dir_for_appname(appname,
"XDG_CONFIG_HOME",
".config");
581 if (!filename || !*filename)
586 if (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base)))
589 size_t len = strlen(base) + 1 + strlen(filename) + 1;
590 char *result = malloc_wrap(len);
593 snprintf(result, len,
"%s\\%s", base, filename);
596 char *base = xdg_base_dir(
"XDG_CONFIG_HOME",
".config");
610 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA, NULL);
612 return dir_for_appname(appname,
"XDG_DATA_HOME",
".local/share");
621 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA,
"Cache");
623 return dir_for_appname(appname,
"XDG_CACHE_HOME",
".cache");
632 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA,
"State");
634 return dir_for_appname(appname,
"XDG_STATE_HOME",
".local/state");
642 if (!appname || *appname ==
'\0')
652 const char *base = getenv(
"XDG_RUNTIME_DIR");
653 if (!base || *base !=
'/')
662 if (stat(base, &st) != 0)
664 if (!S_ISDIR(st.st_mode))
666 if (st.st_uid != getuid())
668 if ((st.st_mode & 07777) != 0700)
671 return canfigger_path_join(base, appname);
683common_appdata_list(
void)
686 if (FAILED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, base)))
689 char **list = malloc_wrap(2 *
sizeof(*list));
693 list[0] = strclone_str(base);
708xdg_dir_list(
const char *xdg_env,
const char *fallback)
710 const char *value = getenv(xdg_env);
711 if (!value || *value ==
'\0')
716 for (p = value; *p !=
'\0'; p++)
720 char **list = malloc_wrap(max *
sizeof(*list));
725 const char *start = value;
728 const char *end = strchr(start,
':');
729 size_t len = end ? (size_t) (end - start) : strlen(start);
735 if (len > 0 && *start ==
'/')
737 list[n] = strclone(start, len);
741 canfigger_free_dirs(list);
759canfigger_config_dirs(
void)
762 return common_appdata_list();
764 return xdg_dir_list(
"XDG_CONFIG_DIRS",
"/etc/xdg");
770canfigger_data_dirs(
void)
773 return common_appdata_list();
775 return xdg_dir_list(
"XDG_DATA_DIRS",
"/usr/local/share:/usr/share");
781canfigger_free_dirs(
char **dirs)
787 for (i = 0; dirs[i] != NULL; i++)
794canfigger_path_join(
const char *dir,
const char *file)
796 if (!dir || !*dir || !file || !*file)
799 size_t dirlen = strlen(dir);
800 size_t filelen = strlen(file);
801 bool needs_sep = dirlen > 0 && dir[dirlen - 1] !=
'/'
802 && dir[dirlen - 1] !=
'\\';
803 size_t total = dirlen + (needs_sep ? 1 : 0) + filelen + 1;
805 char *result = malloc_wrap(total);
810 const char sep =
'\\';
812 const char sep =
'/';
816 snprintf(result, total,
"%s%c%s", dir, sep, file);
818 snprintf(result, total,
"%s%s", dir, file);
828path_is_file(
const char *path)
831 DWORD attr = GetFileAttributesA(path);
832 return attr != INVALID_FILE_ATTRIBUTES
833 && !(attr & FILE_ATTRIBUTE_DIRECTORY);
836 return stat(path, &st) == 0 && S_ISREG(st.st_mode);
842canfigger_find_config_file(
const char *appname,
const char *filename)
844 if (!filename || !*filename)
847 char *candidate = NULL;
850 if (appname && *appname)
852 char *dir = canfigger_config_dir(appname);
855 candidate = canfigger_path_join(dir, filename);
860 candidate = canfigger_config_file(filename);
862 if (candidate && path_is_file(candidate))
866 char **dirs = canfigger_config_dirs();
872 for (i = 0; dirs[i] != NULL && !found; i++)
875 if (appname && *appname)
877 base = canfigger_path_join(dirs[i], appname);
882 candidate = canfigger_path_join(base ? base : dirs[i], filename);
885 if (candidate && path_is_file(candidate))
891 canfigger_free_dirs(dirs);
901static const char *
const user_dir_keys[] = {
902 [CANFIGGER_USER_DIR_DESKTOP] =
"XDG_DESKTOP_DIR",
903 [CANFIGGER_USER_DIR_DOWNLOAD] =
"XDG_DOWNLOAD_DIR",
904 [CANFIGGER_USER_DIR_TEMPLATES] =
"XDG_TEMPLATES_DIR",
905 [CANFIGGER_USER_DIR_PUBLICSHARE] =
"XDG_PUBLICSHARE_DIR",
906 [CANFIGGER_USER_DIR_DOCUMENTS] =
"XDG_DOCUMENTS_DIR",
907 [CANFIGGER_USER_DIR_MUSIC] =
"XDG_MUSIC_DIR",
908 [CANFIGGER_USER_DIR_PICTURES] =
"XDG_PICTURES_DIR",
909 [CANFIGGER_USER_DIR_VIDEOS] =
"XDG_VIDEOS_DIR"
920user_dir_from_file(
const char *key)
922 char *path = canfigger_config_file(
"user-dirs.dirs");
926 FILE *fp = fopen(path,
"r");
931 static const char home_prefix[] =
"$HOME/";
932 const char *home = getenv(
"HOME");
933 size_t keylen = strlen(key);
937 while (!result && fgets(line,
sizeof line, fp) != NULL)
940 while (isspace((
unsigned char) *p))
942 if (strncmp(p, key, keylen) != 0)
946 while (isspace((
unsigned char) *p))
951 while (isspace((
unsigned char) *p))
957 bool relative_to_home =
false;
958 if (strncmp(p, home_prefix,
sizeof home_prefix - 1) == 0)
960 relative_to_home =
true;
961 p +=
sizeof home_prefix - 1;
969 if (relative_to_home)
973 prefix = strlen(home) + 1;
976 char *out = malloc_wrap(prefix + strlen(p) + 1);
979 if (relative_to_home)
980 snprintf(out, prefix + 1,
"%s/", home);
982 char *d = out + prefix;
983 while (*p !=
'\0' && *p !=
'"')
985 if (*p ==
'\\' && *(p + 1) !=
'\0')
1010static const int user_dir_csidl[] = {
1011 [CANFIGGER_USER_DIR_DESKTOP] = CSIDL_DESKTOPDIRECTORY,
1012 [CANFIGGER_USER_DIR_DOWNLOAD] = CSIDL_PROFILE,
1013 [CANFIGGER_USER_DIR_TEMPLATES] = CSIDL_TEMPLATES,
1014 [CANFIGGER_USER_DIR_PUBLICSHARE] = CSIDL_COMMON_DOCUMENTS,
1015 [CANFIGGER_USER_DIR_DOCUMENTS] = CSIDL_PERSONAL,
1016 [CANFIGGER_USER_DIR_MUSIC] = CSIDL_MYMUSIC,
1017 [CANFIGGER_USER_DIR_PICTURES] = CSIDL_MYPICTURES,
1018 [CANFIGGER_USER_DIR_VIDEOS] = CSIDL_MYVIDEO
1027 if ((
unsigned) which >=
sizeof user_dir_csidl /
sizeof *user_dir_csidl)
1030 char base[MAX_PATH];
1031 if (FAILED(SHGetFolderPathA(NULL, user_dir_csidl[which], NULL, 0, base)))
1034 if (which == CANFIGGER_USER_DIR_DOWNLOAD)
1035 return canfigger_path_join(base,
"Downloads");
1036 return strclone_str(base);
1038 if ((
unsigned) which >=
sizeof user_dir_keys /
sizeof *user_dir_keys)
1041 if (!user_dir_keys[which])
1044 char *dir = user_dir_from_file(user_dir_keys[which]);
1048 const char *home = getenv(
"HOME");
1049 if (!home || !*home)
1054 if (which == CANFIGGER_USER_DIR_DESKTOP)
1055 return canfigger_path_join(home,
"Desktop");
1056 return strclone_str(home);
Public API for the Canfigger configuration file parser.
char * canfigger_config_file(const char *filename)
Return the path to a config file in the platform base config directory.
struct Canfigger * canfigger_parse_file(const char *file, const int delimiter)
Parse a configuration file into a linked list of key-value nodes.
char * canfigger_state_dir(const char *appname)
Return the platform state directory for an application.
char * canfigger_cache_dir(const char *appname)
Return the platform cache directory for an application.
char * canfigger_data_dir(const char *appname)
Return the platform data directory for an application.
canfigger_user_dir
The well-known user directories, as named by the XDG user directories specification.
void canfigger_free_list(struct Canfigger **node)
Free all remaining nodes in the list.
char * canfigger_config_dir(const char *appname)
Return the platform config directory for an application.
char * canfigger_path_join(const char *dir, const char *file)
Join a directory path and a filename with the platform separator.
void canfigger_free_current_key_node_advance(struct Canfigger **node)
Free the current node and advance the list pointer to the next node.
char * canfigger_runtime_dir(const char *appname)
Return the runtime directory for an application, or NULL.
struct attributes * attributes
A single node in the parsed configuration linked list.
Internal iteration state for a node's attribute list.