diff options
| author | Mattias Andrée <m@maandree.se> | 2025-03-07 20:33:18 +0100 | 
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2025-03-07 20:33:18 +0100 | 
| commit | 46ab347b651f869025b3c5a351bc9e944c39985c (patch) | |
| tree | 9547c937d079eaa35b5683b45b43328555187f63 /src/config-ini.c | |
| parent | New README (diff) | |
| download | redshift-ng-46ab347b651f869025b3c5a351bc9e944c39985c.tar.gz redshift-ng-46ab347b651f869025b3c5a351bc9e944c39985c.tar.bz2 redshift-ng-46ab347b651f869025b3c5a351bc9e944c39985c.tar.xz | |
Misc improvements
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'src/config-ini.c')
| -rw-r--r-- | src/config-ini.c | 480 | 
1 files changed, 278 insertions, 202 deletions
| diff --git a/src/config-ini.c b/src/config-ini.c index df7d1a1..db94f22 100644 --- a/src/config-ini.c +++ b/src/config-ini.c @@ -1,257 +1,332 @@  /* config-ini.c -- INI config file parser -   This file is part of redshift-ng. + * This file is part of redshift-ng. + *  + * 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/>. + *  + * Copyright (c) 2010-2018  Jon Lund Steffensen <jonlst@gmail.com> + * Copyright (c) 2025       Mattias Andrée <m@maandree.se> + */ +#include "common.h" -   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. +/** + * Specification for a path that consists of a two parts: + * the first being defined by the environment, and the + * seocnd being a static string + */ +struct env_path { +	/** +	 * Whether the environment variable referenced by `.prefix` +	 * should be split at each colon (:) into multiple paths to +	 * test +	 */ +	int multidir_env; -   You should have received a copy of the GNU General Public License -   along with redshift-ng.  If not, see <http://www.gnu.org/licenses/>. +	/** +	 * Environment variable to use as the first part of the path +	 *  +	 * `NULL` if the user's home directory should be used +	 *  +	 * The empty string if `.suffix` should be used as is +	 */ +	const char *prefix_env; -   Copyright (c) 2010-2018  Jon Lund Steffensen <jonlst@gmail.com> -   Copyright (c) 2025       Mattias Andrée <m@maandree.se> -*/ -#include "common.h" +	/** +	 * The second part of the path +	 */ +	const char *suffix; +}; -#define MAX_CONFIG_PATH  4096 -#define MAX_LINE_LENGTH   512 +/** + * 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"}, +#ifdef 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"}, +	{0, "", "/etc/redshift-ng/redshift.conf"}, +	{0, "", "/etc/redshift/redshift.conf"}, +	{0, "", "/etc/redshift.conf"} +}; -static FILE * -open_config_file(const char *filepath) -{ -	FILE *f = NULL; -	/* If a path is not specified (filepath is NULL) then -	   the configuration file is searched for in the directories -	   specified by the XDG Base Directory Specification -	   <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>. +/** + * Remove trailing whitespace + *  + * @param   s    The string to trim, will be truncated + * @param   end  The current end of `s`; will be looked up if `NULL` + * @return       `s` + */ +static char * +rtrim(char *s, char *end) +{ +	end = end ? end : strchr(s, '\0'); +	while (end != s && (end[-1] == ' ' || end[-1] == '\t')) +		end--; +	*end = '\0'; +	return s; +} -	   If HOME is not set, getpwuid() is consulted for the home directory. On -	   windows platforms the %localappdata% is used in place of XDG_CONFIG_HOME. -	*/ -	if (filepath == NULL) { -		char cp[MAX_CONFIG_PATH]; -		char *env; +/** + * Remove leading whitespace + *  + * @param   s  The string to trim (will not be modified) + * @return     An offset of `s` + */ +GCC_ONLY(__attribute__((__warn_unused_result__))) +static char * +ltrim(char *s) +{ +	return &s[strspn(s, " \t")]; +} -		if (f == NULL && (env = getenv("XDG_CONFIG_HOME")) != NULL && -		    env[0] != '\0') { -			snprintf(cp, sizeof(cp), "%s/redshift/redshift.conf", env); -			f = fopen(cp, "r"); -			if (f == NULL) { -				/* Fall back to formerly used path. */ -				snprintf(cp, sizeof(cp), "%s/redshift.conf", env); -				f = fopen(cp, "r"); -			} -		} +/** + * Get the user's home directory + * + * This function looks up the user's home directory + * once and caches the result, later calls to this + * function will use the cached result + *  + * @param  The user's home directory; the empty string if not found + */ +static const char * +get_home(void) +{  #ifdef WINDOWS -		if (f == NULL && (env = getenv("localappdata")) != NULL && -		    env[0] != '\0') { -			snprintf(cp, sizeof(cp), "%s\\redshift.conf", env); -			f = fopen(cp, "r"); +	return NULL; +#else +	static const char *home = NULL; +	struct passwd *pw; +	if (!home) { +		pw = getpwuid(getuid()); +		if (pw) { +			home = pw->pw_dir; +			if (home && *home) +				return home; +			weprintf(_("Cannot determine your home directory, " +			           "it is from the system's user table.")); +		} else if (errno) { +			weprintf("getpwuid:"); +		} else { +			weprintf(_("Cannot determine your home directory, your" +			           " user ID is missing from the system's user table.")); +			/* `errno` can either be set to any number of error codes, +			 * or be zero if the user does not have a passwd entry */  		} +		home = ""; +	} +	return home;  #endif -		if (f == NULL && (env = getenv("HOME")) != NULL && -		    env[0] != '\0') { -			snprintf(cp, sizeof(cp), "%s/.config/redshift/redshift.conf", env); -			f = fopen(cp, "r"); -			if (f == NULL) { -				/* Fall back to formerly used path. */ -				snprintf(cp, sizeof(cp), "%s/.config/redshift.conf", env); -				f = fopen(cp, "r"); -			} -		} -#ifndef WINDOWS +} -		if (f == NULL) { -			struct passwd *pwd = getpwuid(getuid()); -			if (pwd != NULL) { -				char *home = pwd->pw_dir; -				if ((home != NULL) && (*home != '\0')) { -					snprintf(cp, sizeof(cp), "%s/.config/redshift/redshift.conf", home); -					f = fopen(cp, "r"); -					if (!f) { -						/* Fall back to formerly used path. */ -						snprintf(cp, sizeof(cp), "%s/.config/redshift.conf", home); -						f = fopen(cp, "r"); -					} -				} else { -					weprintf(_("Cannot determine your home directory," -					           " it is from the system's user table.")); -				} -			} else if (errno) { -				weprintf("getpwuid:"); -			} else { -				weprintf(_("Cannot determine your home directory, your" -				           " user ID is missing from the system's user table.")); -				/* errno can either be set to any number of error codes, -				   or be zero if the user does not have a passwd entry. */ -			} -		} -		if (f == NULL && (env = getenv("XDG_CONFIG_DIRS")) != NULL && *env) { -			char *begin = env; -			char *end; -			int len; -			for (;;) { -				end = strchr(begin, ':'); -				if (end == NULL) end = strchr(begin, '\0'); +/** + * Search for a file and open it for reading + *  + * @param   path_spec  Specification for the path to try + * @param   path_out   Output parameter for the file path + * @return             `FILE` object for the reading the file; `NULL` if not found + */ +static FILE * +try_path(const struct env_path *path_spec, char **path_out) +{ +	const char *prefix, *p, *q; +	char *path; +	size_t len; +	FILE *f; + +	if (!path_spec->prefix_env) +		prefix = get_home(); +	else if (!*path_spec->prefix_env) +		return fopen(path_spec->suffix, "r"); +	else +		prefix = getenv(path_spec->prefix_env); +	if (!prefix || !*prefix) +		return NULL; -				len = (int)(end - begin); -				if (len > 0) { -					snprintf(cp, sizeof(cp), "%.*s/redshift/redshift.conf", len, begin); -					f = fopen(cp, "r"); -					if (f == NULL) { -						/* Fall back to formerly used path. */ -						snprintf(cp, sizeof(cp), "%.*s/redshift.conf", len, begin); -						f = fopen(cp, "r"); -					} -					if (f != NULL) -						break; -				} +	path = emalloc(strlen(prefix) + strlen(path_spec->suffix) + 1U); + +	if (path_spec->multidir_env) { +		for (p = prefix; *p; p = &q[!!*q]) { +#ifdef strchrnul +			q = strchrnul(p, ':'); +#else +			q = strchr(p, ':'); +			q = q ? q : strchr(p, '\0'); +#endif +			len = (size_t)(q - p); +			if (!len) +				continue; -				if (*end) -					break; -				begin = &end[1]; +			memcpy(path, p, len); +			stpcpy(&path[len], path_spec->suffix); +			f = fopen(path, "r"); +			if (f) { +				weprintf(_("Found configuration file `%s'.")); +				break; +			} else if (errno != ENOENT) { +				eprintf("fopen %s \"%s\":", path);  			}  		} +	} else { +		stpcpy(stpcpy(path, prefix), path_spec->suffix); +		f = fopen(path, "r"); +		if (f) +			weprintf(_("Found configuration file `%s'.")); +		else if (errno != ENOENT) +			eprintf("fopen %s \"%s\":", path); +	} -		if (f == NULL) { -			snprintf(cp, sizeof(cp), "%s/redshift.conf", "/etc"); -			f = fopen(cp, "r"); -		} -#endif +	if (f) { +		*path_out = path;  	} else { -		f = fopen(filepath, "r"); -		if (f == NULL) { -			weprintf("fopen:"); -			return NULL; -		} +		free(path); +		*path_out = NULL; +	} +	return f; +} + + +/** + * 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 + * @param   pathbuf_out  Output parameter for the memory allocation for `*path_out`; + *                       will be set to `NULL` unless `path` is `NULL` + * @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) +{ +	FILE *f = NULL; +	size_t i; + +	if (!path) { +		for (i = 0; !f && i < ELEMSOF(paths); i++) +			f = try_path(&paths[i], pathbuf_out); +		if (!f) +			weprintf(_("No configuration file found.")); +		*path_out = *pathbuf_out; +	} else { +		f = fopen(path, "r"); +		if (!f) +			eprintf("fopen %s \"r\":", path); +		*path_out = path; +		*pathbuf_out = NULL;  	}  	return f;  } -int -config_ini_init(struct config_ini_state *state, const char *filepath) + +void +config_ini_init(struct config_ini_state *state, const char *path)  {  	struct config_ini_section *section = NULL; -	char line[MAX_LINE_LENGTH]; -	char *s; +	struct config_ini_setting *setting; +	char *line = NULL, *s, *p, *value, *end, *pathbuf; +	char *next_line = NULL, *name; +	size_t size = 0; +	ssize_t len;  	FILE *f;  	state->sections = NULL; -	f = open_config_file(filepath); -	if (f == NULL) { -		/* Only a serious error if a file was explicitly requested. */ -		return filepath ? 0 : -1; -	} +	f = open_config_file(path, &path, &pathbuf); +	if (!f) +		return; +	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.")); -	for (;;) { -		/* Handle the file input linewise. */ -		char *r = fgets(line, sizeof(line), f); -		if (!r) -			break; - -		/* Strip leading blanks and trailing newline. */ -		s = line + strspn(line, " \t"); -		s[strcspn(s, "\r\n")] = '\0'; - -		/* Skip comments and empty lines. */ -		if (s[0] == ';' || s[0] == '#' || s[0] == '\0') -			continue; +		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); -		if (s[0] == '[') { -			/* Read name of section. */ -			const char *name = &s[1]; -			char *end = strchr(s, ']'); -			if (end == NULL || end[1] != '\0' || end == name) { -				weprintf(_("Malformed section header in config file.")); -				fclose(f); -				config_ini_free(state); -				return -1; -			} +		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'; - -			/* Create section. */  			section = emalloc(sizeof(*section)); - -			/* Insert into section list. */ -			section->name = NULL; +			section->name = estrdup(name);  			section->settings = NULL;  			section->next = state->sections;  			state->sections = section; +			break; -			/* Copy section name. */ -			section->name = malloc(end - name + 1); -			if (section->name == NULL) { -				fclose(f); -				config_ini_free(state); -				return -1; -			} - -			memcpy(section->name, name, end - name + 1); -		} else { -			char *value, *end; -			size_t value_len; -			struct config_ini_setting *setting; - -			/* Split assignment at equals character. */ -			end = strchr(s, '='); -			if (!end || end == s) { -				weprintf(_("Malformed assignment in config file.")); -				fclose(f); -				config_ini_free(state); -				return -1; -			} - -			*end++ = '\0'; -			value = end; - -			if (!section) { -				weprintf(_("Assignment outside section in config file.")); -				fclose(f); -				config_ini_free(state); -				return -1; -			} - -			/* Create section. */ +		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)); - -			/* Insert into section list. */ -			setting->name = NULL; -			setting->value = NULL; +			setting->name = estrdup(rtrim(name, p)); +			setting->value = estrdup(rtrim(ltrim(value), NULL));  			setting->next = section->settings;  			section->settings = setting; - -			/* Copy name of setting. */ -			setting->name = emalloc(end - s); - -			memcpy(setting->name, s, end - s); - -			/* Copy setting value. */ -			value_len = strlen(value) + 1; -			setting->value = emalloc(value_len); - -			memcpy(setting->value, value, value_len); +			break;  		}  	} - +	if (ferror(f)) +		eprintf("getline %s:", path); +	free(pathbuf); +	free(line);  	fclose(f); - -	return 0;  } +  void  config_ini_free(struct config_ini_state *state)  { @@ -270,6 +345,7 @@ config_ini_free(struct config_ini_state *state)  	}  } +  struct config_ini_section *  config_ini_get_section(struct config_ini_state *state, const char *name)  { | 
