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);
72strclone(
const char *src,
size_t n)
77 dest = malloc(strlen(src) + 1);
92 perror(
"canfigger: malloc");
137canfigger_free_current_key_node_advance(
struct Canfigger **node)
141 if ((*node)->attributes)
143 if ((*node)->attributes->current)
145 free((*node)->attributes->current);
146 (*node)->attributes->current = NULL;
149 if ((*node)->attributes->str)
151 free((*node)->attributes->str);
152 (*node)->attributes->str = NULL;
155 free((*node)->attributes);
156 (*node)->attributes = NULL;
161 free((*node)->value);
162 (*node)->value = NULL;
168 struct Canfigger *temp_node = (*node)->next;
199erase_lead_char(
const int lc,
char *haystack)
201 char *ptr = haystack;
213truncate_whitespace(
char *str)
231 while (isspace((
unsigned char) *str))
245grab_str_segment(
char *a,
char **dest,
const int c)
247 a = erase_lead_char(
' ', a);
249 char *b = strchr(a, c);
252 *dest = strclone(a, 0);
257 *dest = strclone(a, len);
261 truncate_whitespace(*dest);
266malloc_wrap(
size_t size)
268 void *retval = malloc(size);
272 perror(
"canfigger: malloc");
285 (*cur_node)->next = tmp_node;
289 *cur_node = tmp_node;
296read_entire_file(
const char *filename)
302 FILE *fp = fopen(filename,
"rb");
305 fprintf(stderr,
"canfigger: Failed to open %s: %s\n", filename,
310 if (fseek(fp, 0, SEEK_END) != 0 || (file_size = ftell(fp)) < 0
311 || fseek(fp, 0, SEEK_SET) != 0)
313 fprintf(stderr,
"canfigger: Failed to determine size of %s: %s\n",
314 filename, strerror(errno));
318 buffer = malloc_wrap(file_size + 1);
322 n_bytes = fread(buffer, 1, file_size, fp);
323 if (n_bytes != (
size_t) file_size)
326 fprintf(stderr,
"canfigger: Error reading %s: %s\n", filename,
330 "canfigger: Partial read of %s: expected %ld bytes, got %zu bytes\n",
331 filename, file_size, n_bytes);
336 buffer[file_size] =
'\0';
345free_incomplete_node(
struct Canfigger **node)
353 free((*node)->value);
355 if ((*node)->attributes)
357 free((*node)->attributes->str);
358 free((*node)->attributes);
370 struct Canfigger *root = NULL, *cur_node = NULL;
372 char *file_contents = read_entire_file(file);
373 if (file_contents == NULL)
383 char *parse_start = file_contents;
384 if ((
unsigned char) parse_start[0] == 0xEF &&
385 (
unsigned char) parse_start[1] == 0xBB &&
386 (
unsigned char) parse_start[2] == 0xBF)
390 line.start = parse_start;
392 bool node_complete =
false;
396 line.end = strchr(line.start,
'\n');
398 line.end ? (size_t) (line.end - line.start) : strlen(line.start);
401 if (line.len == 0 && !line.end)
404 char *tmp_line = malloc_wrap(line.len + 1);
407 canfigger_free_list(&root);
412 memcpy(tmp_line, line.start, line.len);
413 tmp_line[line.len] =
'\0';
414 line.start = line.end ? line.end + 1 : line.start + line.len;
416 char *line_ptr = tmp_line;
417 truncate_whitespace(line_ptr);
419 while (isspace((
unsigned char) *line_ptr))
420 line_ptr = erase_lead_char(*line_ptr, line_ptr);
422 if (*line_ptr ==
'\0' || *line_ptr ==
'#' || *line_ptr ==
'[')
430 node_complete =
false;
432 add_key_node(&root, &cur_node);
433 if (cur_node == prev_node)
440 cur_node->key = NULL;
441 line_ptr = grab_str_segment(line_ptr, &cur_node->key,
'=');
445 free_incomplete_node(&cur_node);
450 cur_node->value = NULL;
454 line_ptr = grab_str_segment(line_ptr, &cur_node->value, delimiter);
455 if (!cur_node->value)
458 free_incomplete_node(&cur_node);
466 cur_node->attributes = malloc_wrap(
sizeof(
struct attributes));
467 if (!cur_node->attributes)
470 free_incomplete_node(&cur_node);
474 struct attributes *attr_ptr = cur_node->attributes;
475 attr_ptr->current = NULL;
477 attr_ptr->str = strclone(line_ptr, 0);
481 free_incomplete_node(&cur_node);
485 attr_ptr->iter_ptr = attr_ptr->str;
489 char *delimiter_ptr = strchr(attr_ptr->iter_ptr, delimiter);
490 while (delimiter_ptr)
492 *delimiter_ptr =
'\n';
493 delimiter_ptr = strchr(delimiter_ptr, delimiter);
497 cur_node->attributes = NULL;
499 cur_node->next = NULL;
500 node_complete =
true;
526xdg_base_dir(
const char *xdg_env,
const char *fallback)
528 const char *base = getenv(xdg_env);
533 if (base && *base ==
'/')
534 return strclone(base, 0);
536 const char *home = getenv(
"HOME");
540 size_t len = strlen(home) + 1 + strlen(fallback) + 1;
541 char *result = malloc_wrap(len);
544 snprintf(result, len,
"%s/%s", home, fallback);
568dir_for_appname(
const char *appname,
int csidl,
const char *subdir)
570 if (!appname || *appname ==
'\0')
574 if (FAILED(SHGetFolderPathA(NULL, csidl, NULL, 0, base)))
577 size_t len = strlen(base) + 1 + strlen(appname) + 1;
579 len += strlen(subdir) + 1;
580 char *result = malloc_wrap(len);
584 snprintf(result, len,
"%s\\%s\\%s", base, appname, subdir);
586 snprintf(result, len,
"%s\\%s", base, appname);
591dir_for_appname(
const char *appname,
const char *xdg_env,
592 const char *xdg_fallback)
594 if (!appname || *appname ==
'\0')
597 char *base = xdg_base_dir(xdg_env, xdg_fallback);
611 return dir_for_appname(appname, CSIDL_APPDATA, NULL);
613 return dir_for_appname(appname,
"XDG_CONFIG_HOME",
".config");
621 if (!filename || !*filename)
626 if (FAILED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base)))
629 size_t len = strlen(base) + 1 + strlen(filename) + 1;
630 char *result = malloc_wrap(len);
633 snprintf(result, len,
"%s\\%s", base, filename);
636 char *base = xdg_base_dir(
"XDG_CONFIG_HOME",
".config");
650 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA, NULL);
652 return dir_for_appname(appname,
"XDG_DATA_HOME",
".local/share");
661 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA,
"Cache");
663 return dir_for_appname(appname,
"XDG_CACHE_HOME",
".cache");
672 return dir_for_appname(appname, CSIDL_LOCAL_APPDATA,
"State");
674 return dir_for_appname(appname,
"XDG_STATE_HOME",
".local/state");
682 if (!appname || *appname ==
'\0')
692 const char *base = getenv(
"XDG_RUNTIME_DIR");
693 if (!base || *base !=
'/')
702 if (stat(base, &st) != 0)
704 if (!S_ISDIR(st.st_mode))
706 if (st.st_uid != getuid())
708 if ((st.st_mode & 07777) != 0700)
711 return canfigger_path_join(base, appname);
723common_appdata_list(
void)
726 if (FAILED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, base)))
729 char **list = malloc_wrap(2 *
sizeof(*list));
733 list[0] = strclone(base, 0);
748xdg_dir_list(
const char *xdg_env,
const char *fallback)
750 const char *value = getenv(xdg_env);
751 if (!value || *value ==
'\0')
756 for (p = value; *p !=
'\0'; p++)
760 char **list = malloc_wrap(max *
sizeof(*list));
765 const char *start = value;
768 const char *end = strchr(start,
':');
769 size_t len = end ? (size_t) (end - start) : strlen(start);
775 if (len > 0 && *start ==
'/')
777 list[n] = strclone(start, len);
781 canfigger_free_dirs(list);
799canfigger_config_dirs(
void)
802 return common_appdata_list();
804 return xdg_dir_list(
"XDG_CONFIG_DIRS",
"/etc/xdg");
810canfigger_data_dirs(
void)
813 return common_appdata_list();
815 return xdg_dir_list(
"XDG_DATA_DIRS",
"/usr/local/share:/usr/share");
821canfigger_free_dirs(
char **dirs)
827 for (i = 0; dirs[i] != NULL; i++)
834canfigger_path_join(
const char *dir,
const char *file)
836 if (!dir || !*dir || !file || !*file)
839 size_t dirlen = strlen(dir);
840 size_t filelen = strlen(file);
841 bool needs_sep = dirlen > 0 && dir[dirlen - 1] !=
'/'
842 && dir[dirlen - 1] !=
'\\';
843 size_t total = dirlen + (needs_sep ? 1 : 0) + filelen + 1;
845 char *result = malloc_wrap(total);
850 const char sep =
'\\';
852 const char sep =
'/';
856 snprintf(result, total,
"%s%c%s", dir, sep, file);
858 snprintf(result, total,
"%s%s", dir, file);
868path_is_file(
const char *path)
871 DWORD attr = GetFileAttributesA(path);
872 return attr != INVALID_FILE_ATTRIBUTES
873 && !(attr & FILE_ATTRIBUTE_DIRECTORY);
876 return stat(path, &st) == 0 && S_ISREG(st.st_mode);
882canfigger_find_config_file(
const char *appname,
const char *filename)
884 if (!filename || !*filename)
887 char *candidate = NULL;
890 if (appname && *appname)
892 char *dir = canfigger_config_dir(appname);
895 candidate = canfigger_path_join(dir, filename);
900 candidate = canfigger_config_file(filename);
902 if (candidate && path_is_file(candidate))
906 char **dirs = canfigger_config_dirs();
912 for (i = 0; dirs[i] != NULL && !found; i++)
915 if (appname && *appname)
917 base = canfigger_path_join(dirs[i], appname);
922 candidate = canfigger_path_join(base ? base : dirs[i], filename);
925 if (candidate && path_is_file(candidate))
931 canfigger_free_dirs(dirs);
941static const char *
const user_dir_keys[] = {
942 [CANFIGGER_USER_DIR_DESKTOP] =
"XDG_DESKTOP_DIR",
943 [CANFIGGER_USER_DIR_DOWNLOAD] =
"XDG_DOWNLOAD_DIR",
944 [CANFIGGER_USER_DIR_TEMPLATES] =
"XDG_TEMPLATES_DIR",
945 [CANFIGGER_USER_DIR_PUBLICSHARE] =
"XDG_PUBLICSHARE_DIR",
946 [CANFIGGER_USER_DIR_DOCUMENTS] =
"XDG_DOCUMENTS_DIR",
947 [CANFIGGER_USER_DIR_MUSIC] =
"XDG_MUSIC_DIR",
948 [CANFIGGER_USER_DIR_PICTURES] =
"XDG_PICTURES_DIR",
949 [CANFIGGER_USER_DIR_VIDEOS] =
"XDG_VIDEOS_DIR"
960user_dir_from_file(
const char *key)
962 char *path = canfigger_config_file(
"user-dirs.dirs");
966 FILE *fp = fopen(path,
"r");
971 static const char home_prefix[] =
"$HOME/";
972 const char *home = getenv(
"HOME");
973 size_t keylen = strlen(key);
977 while (!result && fgets(line,
sizeof line, fp) != NULL)
980 while (isspace((
unsigned char) *p))
982 if (strncmp(p, key, keylen) != 0)
986 while (isspace((
unsigned char) *p))
991 while (isspace((
unsigned char) *p))
997 bool relative_to_home =
false;
998 if (strncmp(p, home_prefix,
sizeof home_prefix - 1) == 0)
1000 relative_to_home =
true;
1001 p +=
sizeof home_prefix - 1;
1009 if (relative_to_home)
1011 if (!home || !*home)
1013 prefix = strlen(home) + 1;
1016 char *out = malloc_wrap(prefix + strlen(p) + 1);
1019 if (relative_to_home)
1020 snprintf(out, prefix + 1,
"%s/", home);
1022 char *d = out + prefix;
1023 while (*p !=
'\0' && *p !=
'"')
1025 if (*p ==
'\\' && *(p + 1) !=
'\0')
1050static const int user_dir_csidl[] = {
1051 [CANFIGGER_USER_DIR_DESKTOP] = CSIDL_DESKTOPDIRECTORY,
1052 [CANFIGGER_USER_DIR_DOWNLOAD] = CSIDL_PROFILE,
1053 [CANFIGGER_USER_DIR_TEMPLATES] = CSIDL_TEMPLATES,
1054 [CANFIGGER_USER_DIR_PUBLICSHARE] = CSIDL_COMMON_DOCUMENTS,
1055 [CANFIGGER_USER_DIR_DOCUMENTS] = CSIDL_PERSONAL,
1056 [CANFIGGER_USER_DIR_MUSIC] = CSIDL_MYMUSIC,
1057 [CANFIGGER_USER_DIR_PICTURES] = CSIDL_MYPICTURES,
1058 [CANFIGGER_USER_DIR_VIDEOS] = CSIDL_MYVIDEO
1067 if ((
unsigned) which >=
sizeof user_dir_csidl /
sizeof *user_dir_csidl)
1070 char base[MAX_PATH];
1071 if (FAILED(SHGetFolderPathA(NULL, user_dir_csidl[which], NULL, 0, base)))
1074 if (which == CANFIGGER_USER_DIR_DOWNLOAD)
1075 return canfigger_path_join(base,
"Downloads");
1076 return strclone(base, 0);
1078 if ((
unsigned) which >=
sizeof user_dir_keys /
sizeof *user_dir_keys)
1081 if (!user_dir_keys[which])
1084 char *dir = user_dir_from_file(user_dir_keys[which]);
1088 const char *home = getenv(
"HOME");
1089 if (!home || !*home)
1094 if (which == CANFIGGER_USER_DIR_DESKTOP)
1095 return canfigger_path_join(home,
"Desktop");
1096 return strclone(home, 0);
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.
A single node in the parsed configuration linked list.
Internal iteration state for a node's attribute list.