aboutsummaryrefslogtreecommitdiffstats
path: root/redshift/config-ini.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-03-27 18:36:26 +0100
committerMattias Andrée <m@maandree.se>2025-03-27 18:36:26 +0100
commit037b945a9f253b97faffc02d8475574e75203516 (patch)
treeb008e7d77e9daaeaaa8e7854728d715df5aafb77 /redshift/config-ini.c
parenttodo list housekeeping (diff)
downloadredshift-ng-037b945a9f253b97faffc02d8475574e75203516.tar.gz
redshift-ng-037b945a9f253b97faffc02d8475574e75203516.tar.bz2
redshift-ng-037b945a9f253b97faffc02d8475574e75203516.tar.xz
one dir per subproject
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'redshift/config-ini.c')
-rw-r--r--redshift/config-ini.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/redshift/config-ini.c b/redshift/config-ini.c
new file mode 100644
index 0000000..13aaa57
--- /dev/null
+++ b/redshift/config-ini.c
@@ -0,0 +1,267 @@
+/*-
+ * redshift-ng - Automatically adjust display colour temperature according the Sun
+ *
+ * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com>
+ * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se>
+ *
+ * redshift-ng is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * redshift-ng is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with redshift-ng. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "common.h"
+
+
+/**
+ * Paths, in order of priority, to test when looking for
+ * the configuration file for redshift
+ */
+static const struct env_path paths[] = {
+ {0, "XDG_CONFIG_HOME", "/redshift-ng/redshift.conf"},
+ {0, "XDG_CONFIG_HOME", "/redshift/redshift.conf"},
+ {0, "XDG_CONFIG_HOME", "/redshift.conf"},
+#if defined(WINDOWS)
+ {0, "localappdata", "/redshift-ng/redshift.conf"},
+ {0, "localappdata", "/redshift/redshift.conf"},
+ {0, "localappdata", "/redshift.conf"},
+#endif
+ {0, "HOME", "/.config/redshift-ng/redshift.conf"},
+ {0, "HOME", "/.config/redshift/redshift.conf"},
+ {0, "HOME", "/.config/redshift.conf"},
+ {0, "HOME", "/.redshift.conf"},
+ {0, NULL, "/.config/redshift-ng/redshift.conf"},
+ {0, NULL, "/.config/redshift/redshift.conf"},
+ {0, NULL, "/.config/redshift.conf"},
+ {0, NULL, "/.redshift.conf"},
+ {1, "XDG_CONFIG_DIRS", "/redshift-ng/redshift.conf"},
+ {1, "XDG_CONFIG_DIRS", "/redshift/redshift.conf"},
+ {1, "XDG_CONFIG_DIRS", "/redshift.conf"},
+#if !defined(WINDOWS)
+ {0, "", "/etc/redshift-ng/redshift.conf"},
+ {0, "", "/etc/redshift/redshift.conf"},
+ {0, "", "/etc/redshift.conf"}
+#endif
+};
+
+
+/**
+ * Open the configuration file for reading
+ *
+ * @param path The path to the configuration file, or `NULL` if the
+ * application should look for it in the default paths
+ * @param path_out Output parameter for the configuration file path
+ * @param pathbuf_out Output parameter for the memory allocation for `*path_out`;
+ * will be set to `NULL` unless `path` is `NULL`; shall be
+ * free(3)d by the caller
+ * @param should_close_out Output parameter for whether the file should be closed
+ * @return `FILE` object for the reading the file; `NULL` if not
+ * found and `path` is `NULL`
+ */
+static FILE *
+open_config_file(const char *path, const char **path_out, char **pathbuf_out, int *should_close_out)
+{
+ FILE *f = NULL;
+ size_t i;
+#ifndef WINDOWS
+ const char *s;
+ int fd, old_fd = -1;
+#endif
+
+ *path_out = path;
+ *pathbuf_out = NULL;
+ *should_close_out = 1;
+
+ if (!path) {
+ for (i = 0; !f && i < ELEMSOF(paths); i++)
+ f = try_path_fopen(&paths[i], path_out, pathbuf_out);
+ if (f)
+ weprintf(_("Found configuration file `%s'."), *path_out);
+ else
+ weprintf(_("No configuration file found."));
+ } else if (!strcmp(path, "/dev/null")) { /* needed to allow /dev/null to be specified on Windows */
+ return NULL;
+ } else if (!strcmp(path, "-")) {
+ *should_close_out = 0;
+ return stdin;
+#ifndef WINDOWS
+ } else if (!strcmp(path, "/dev/stdin")) {
+ *should_close_out = 0;
+ return stdin;
+ } else if (!strcmp(path, "/dev/stdout")) {
+ fd = STDOUT_FILENO;
+ goto use_fd;
+ } else if (!strcmp(path, "/dev/stderr")) {
+ fd = STDERR_FILENO;
+ goto use_fd;
+ } else if (!strncmp(path, "/dev/fd/", sizeof("/dev/fd/") - 1U)) {
+ s = &path[sizeof("/dev/fd/") - 1U];
+# if defined(__linux__)
+ goto parse_fd;
+ } else if (!strncmp(path, "/proc/self/fd/", sizeof("/proc/self/fd/") - 1U)) {
+ s = &path[sizeof("/proc/self/fd/") - 1U];
+ parse_fd:
+# endif
+ fd = 0;
+ if (!*s)
+ goto fallback;
+ while (isdigit(*s)) {
+ if (fd > (INT_MAX - (*s & 15)) / 10)
+ goto fallback;
+ fd = fd * 10 + (*s & 15);
+ }
+ if (*s)
+ goto fallback;
+ use_fd:
+ if (fd > 2) {
+ fd = dup(old_fd = fd);
+ if (fd < 0)
+ eprintf("dup %i:", old_fd);
+ }
+ f = fdopen(fd, "r");
+ if (!f) {
+ if (old_fd < 0)
+ eprintf("fdopen %i \"r\":", fd);
+ else
+ eprintf("fdopen <duplicate of %i> \"r\":", old_fd);
+ }
+#endif
+ } else {
+#ifndef WINDOWS
+ fallback:
+#endif
+ f = fopen(path, "r");
+ if (!f)
+ eprintf("fopen %s \"r\":", path);
+ }
+
+ return f;
+}
+
+
+const char *
+config_ini_init(struct config_ini_state *state, const char *path, char **pathbuf_out)
+{
+ struct config_ini_section *section = NULL;
+ struct config_ini_setting *setting;
+ char *line = NULL, *s, *p, *value, *end;
+ char *next_line = NULL, *name;
+ size_t size = 0;
+ ssize_t len = 0; /* initialised to silence false warning from clang */
+ FILE *f;
+ int should_close;
+
+ state->sections = NULL;
+ *pathbuf_out = NULL;
+
+ f = open_config_file(path, &path, pathbuf_out, &should_close);
+ if (!f)
+ return NULL;
+
+#ifndef WINDOWS
+again:
+#endif
+ while ((s = next_line) || (len = getline(&line, &size, f)) >= 0) {
+ if (!s && (s = line, strlen(s) != (size_t)len))
+ eprintf(_("Config file contains NUL byte."));
+
+ s = ltrim(s);
+ next_line = NULL;
+ end = &s[strcspn(s, "\r\n")];
+ if (*end) {
+ p = end;
+ do {
+ *p++ = '\0';
+ } while (*p == '\r' || *p == '\n');
+ if (*p)
+ next_line = p;
+ }
+ rtrim(s, end);
+
+ switch (*s) {
+ case ';': /* comment line */
+ case '#': /* comment line */
+ case '\0': /* blank line */
+ break;
+
+ case '[': /* "[%s]", section */
+ end = strchr(name = &s[1], ']');
+ if (!end || end[1] || end == name)
+ eprintf(_("Malformed section header in config file."));
+ *end = '\0';
+ section = emalloc(sizeof(*section));
+ section->name = estrdup(name);
+ section->settings = NULL;
+ section->next = state->sections;
+ state->sections = section;
+ break;
+
+ default: /* "%s = %s", name, value */
+ value = p = strchr(name = s, '=');
+ if (!value || value == name)
+ eprintf(_("Malformed assignment in config file."));
+ *value++ = '\0';
+ if (!section)
+ eprintf(_("Assignment outside section in config file."));
+ setting = emalloc(sizeof(*setting));
+ setting->name = estrdup(rtrim(name, p));
+ setting->value = estrdup(rtrim(ltrim(value), NULL));
+ setting->next = section->settings;
+ section->settings = setting;
+ break;
+ }
+ }
+ if (ferror(f)) {
+#ifndef WINDOWS
+ if (errno == EINTR) {
+ clearerr(f);
+ goto again;
+ }
+#endif
+ eprintf("getline %s:", path);
+ }
+
+ free(line);
+ if (should_close)
+ fclose(f);
+
+ return path;
+}
+
+
+void
+config_ini_free(struct config_ini_state *state)
+{
+ struct config_ini_section *section, *section_next;
+ struct config_ini_setting *setting, *setting_next;
+ for (section = state->sections; section; section = section_next) {
+ section_next = section->next;
+ for (setting = section->settings; setting; setting = setting_next) {
+ setting_next = setting->next;
+ free(setting->name);
+ free(setting->value);
+ free(setting);
+ }
+ free(section->name);
+ free(section);
+ }
+}
+
+
+struct config_ini_section *
+config_ini_get_section(struct config_ini_state *state, const char *name)
+{
+ /* TODO deal with multiple section definitions */
+ struct config_ini_section *section;
+ for (section = state->sections; section; section = section->next)
+ if (!strcasecmp(section->name, name))
+ return section;
+ return NULL;
+}