/* * Copyright © 2010 Codethink Limited * Copyright © 2012 Canonical Limited * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the licence, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . * * Author: Ryan Lortie */ #include "config.h" #include "dconf-engine-profile.h" #include "dconf-engine-mockable.h" #include #include #include #include "dconf-engine-source.h" #define MANDATORY_DIR "/run/dconf/user/" /* + getuid () */ #define RUNTIME_PROFILE /* XDG_RUNTIME_DIR + */ "/dconf/profile" /* This comment attempts to document the exact semantics of * profile-loading. * * In no situation should the result of profile loading be an abort. * There must be a defined outcome for all possible situations. * Warnings may be issued to stderr, however. * * The first step is to determine what profile is to be used. If a * profile is explicitly specified by the API then it has the top * priority. Otherwise, if the DCONF_PROFILE environment variable is * set, it takes next priority. * * In both of those cases, if the named profile starts with a slash * character then it is taken to be an absolute pathname. If it does * not start with a slash then it is assumed to specify a profile file * relative to /etc/dconf/profile/ or * XDG_DATA_DIRS/dconf/profile/, * taking the file in /etc in preference (ie: DCONF_PROFILE=test looks * first for profile file /etc/dconf/profile/test, falling back to * /usr/local/share/dconf/profile/test, then to * /usr/share/dconf/profile/test). * * If opening the profile file fails then the null profile is used. * This is a profile that contains zero sources. All keys will be * unwritable and all reads will return NULL. * * In the case that no explicit profile was given and DCONF_PROFILE is * unset, dconf attempts to open and use a profile called "user" (ie: * /etc/dconf/profile/user or XDG_DATA_DIRS/dconf/profile/user). If * that fails then the fallback is to act as if the profile file existed * and contained a single line: "user-db:user". * * Note that the fallback case for a missing profile file is different * in the case where a profile was explicitly specified (either by the * API or the environment) and the case where one was not. * * Once a profile file is opened, each line is treated as a possible * source. Comments and empty lines are ignored. * * All valid source specification lines need to start with 'user-db:', * 'system-db:', 'service-db:' or 'file-db:'. If a line doesn't start * with one of these then it gets ignored. If all the lines in the file * get ignored then the result is effectively the null profile. * * If the first source is a "user-db:" or "service-db:" then the * resulting profile will be writable. No profile starting with a * "system-db:" or "file-db:" source can ever be writable. * * Note: even if the source fails to initialise (due to a missing file, * for example) it will remain in the source list. This could have a * performance cost: in the case of a system-db, for example, dconf will * check if the file has come into existence on every read. */ static DConfEngineSource ** dconf_engine_null_profile (gint *n_sources) { *n_sources = 0; return NULL; } static DConfEngineSource ** dconf_engine_default_profile (gint *n_sources) { DConfEngineSource **sources; sources = g_new (DConfEngineSource *, 1); sources[0] = dconf_engine_source_new_default (); *n_sources = 1; return sources; } static DConfEngineSource * dconf_engine_profile_handle_line (gchar *line) { DConfEngineSource *source; gchar *end; /* remove whitespace at the front */ while (g_ascii_isspace (*line)) line++; /* find the end of the line (or start of comments) */ end = line + strcspn (line, "#\n"); /* remove whitespace at the end */ while (end > line && g_ascii_isspace (end[-1])) end--; /* if we're left with nothing, return NULL */ if (line == end) return NULL; *end = '\0'; source = dconf_engine_source_new (line); if (source == NULL) g_warning ("unknown dconf database description: %s", line); return source; } static DConfEngineSource ** dconf_engine_read_profile_file (FILE *file, gint *n_sources) { DConfEngineSource **sources; gchar line[80]; gint n = 0, a; sources = g_new (DConfEngineSource *, (a = 4)); while (fgets (line, sizeof line, file)) { DConfEngineSource *source; /* The input file has long lines. */ if G_UNLIKELY (!strchr (line, '\n')) { GString *long_line; long_line = g_string_new (line); while (fgets (line, sizeof line, file)) { g_string_append (long_line, line); if (strchr (line, '\n')) break; } source = dconf_engine_profile_handle_line (long_line->str); g_string_free (long_line, TRUE); } else source = dconf_engine_profile_handle_line (line); if (source != NULL) { if (n == a) sources = g_renew (DConfEngineSource *, sources, a *= 2); sources[n++] = source; } } *n_sources = n; return g_realloc_n (sources, n, sizeof (DConfEngineSource *)); } /* Find a profile file with the name given in 'profile' and open it. */ static FILE * dconf_engine_open_profile_file (const gchar *profile) { const gchar * const *xdg_data_dirs; const gchar *prefix = SYSCONFDIR; FILE *fp; xdg_data_dirs = g_get_system_data_dirs (); /* First time through, we check SYSCONFDIR, then we check XDG_DATA_DIRS, * in order. We stop looking as soon as we successfully open a file * or in the case that we run out of XDG_DATA_DIRS. * * If we hit an error other than ENOENT then we warn about that and * exit immediately. We should only attempt fallback in the case that * the file in the higher-precedence directory is non-existent. */ do { gchar *filename; filename = g_build_filename (prefix, "dconf/profile", profile, NULL); fp = dconf_engine_fopen (filename, "r"); /* If it wasn't ENOENT then we don't want to continue on to check * other paths. Fail immediately. */ if (fp == NULL && errno != ENOENT) { g_warning ("Unable to open %s: %s", filename, g_strerror (errno)); g_free (filename); return NULL; } g_free (filename); } while (fp == NULL && (prefix = *xdg_data_dirs++)); /* If we didn't find it, this could be NULL. That's OK. */ return fp; } static FILE * dconf_engine_open_mandatory_profile (void) { gchar path[20 + sizeof MANDATORY_DIR]; gint mdlen = strlen (MANDATORY_DIR); memcpy (path, MANDATORY_DIR, mdlen); snprintf (path + mdlen, 20, "%u", (guint) getuid ()); return dconf_engine_fopen (path, "r"); } static FILE * dconf_engine_open_runtime_profile (void) { const gchar *runtime_dir; gchar *path; gint rdlen; runtime_dir = g_get_user_runtime_dir (); rdlen = strlen (runtime_dir); path = g_alloca (rdlen + sizeof RUNTIME_PROFILE); memcpy (path, runtime_dir, rdlen); memcpy (path + rdlen, RUNTIME_PROFILE, sizeof RUNTIME_PROFILE); return dconf_engine_fopen (path, "r"); } DConfEngineSource ** dconf_engine_profile_open (const gchar *profile, gint *n_sources) { DConfEngineSource **sources; FILE *file = NULL; /* We must consider a few different possibilities for the dconf * profile file. We proceed until we have either * * a) a profile name; or * * b) a profile file is open * * If we get a profile name, even if the file is missing, we will use * that name rather than falling back to another possibility. In this * case, we will issue a warning. * * Therefore, at each step, we ensure that there is no profile name or * file yet open before checking the next possibility. * * Note that @profile is an argument to this function, so we will end * up trying none of the five possibilities if that is given. */ /* 1. Mandatory profile */ if (profile == NULL) file = dconf_engine_open_mandatory_profile (); /* 2. Environment variable */ if (profile == NULL && file == NULL) profile = g_getenv ("DCONF_PROFILE"); /* 3. Runtime profile */ if (profile == NULL && file == NULL) file = dconf_engine_open_runtime_profile (); /* 4. User profile */ if (profile == NULL && file == NULL) file = dconf_engine_open_profile_file ("user"); /* 5. Default profile */ if (profile == NULL && file == NULL) return dconf_engine_default_profile (n_sources); /* At this point either we have a profile name or file open, but never * both. If it's a profile name, we try to open it. */ if (profile != NULL) { g_assert (file == NULL); if (profile[0] != '/') file = dconf_engine_open_profile_file (profile); else file = dconf_engine_fopen (profile, "r"); } if (file != NULL) { sources = dconf_engine_read_profile_file (file, n_sources); fclose (file); } else { g_warning ("unable to open named profile (%s): using the null configuration.", profile); sources = dconf_engine_null_profile (n_sources); } return sources; }