/*
* Copyright © 2010 Codethink 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
*/
unowned Gvdb.Item get_parent (Gvdb.HashTable table, string name) {
unowned Gvdb.Item parent;
int end = 0;
for (int i = 1; name[i] != '\0'; i++) {
if (name[i - 1] == '/') {
end = i;
}
}
assert (end != 0);
var parent_name = name.substring (0, end);
parent = table.lookup (parent_name);
if (parent == null) {
parent = table.insert (parent_name);
parent.set_parent (get_parent (table, parent_name));
}
return parent;
}
SList? list_directory (string dirname, Posix.mode_t mode) throws GLib.Error {
var list = new SList ();
var dir = Dir.open (dirname);
unowned string? name;
while ((name = dir.read_name ()) != null) {
if (name.has_prefix (".")) {
continue;
}
var filename = Path.build_filename (dirname, name);
Posix.Stat buf;
// only files of the requested type
if (Posix.stat (filename, out buf) < 0 || (buf.st_mode & Posix.S_IFMT) != mode) {
continue;
}
list.prepend (filename);
}
return list;
}
Gvdb.HashTable? read_locks_directory (string dirname) throws GLib.Error {
SList? files;
try {
files = list_directory (dirname, Posix.S_IFREG);
} catch (FileError.NOENT e) {
/* If locks directory is missing, there are just no locks... */
return null;
}
var table = new Gvdb.HashTable ();
foreach (var filename in files) {
string contents;
FileUtils.get_contents (filename, out contents, null);
foreach (var line in contents.split ("\n")) {
if (line.has_prefix ("/")) {
table.insert_string (line, "");
}
}
}
return table;
}
Gvdb.HashTable read_directory (string dirname) throws GLib.Error {
var table = new Gvdb.HashTable ();
table.insert ("/");
var files = list_directory (dirname, Posix.S_IFREG);
files.sort (strcmp);
files.reverse ();
foreach (var filename in files) {
var kf = new KeyFile ();
try {
kf.load_from_file (filename, KeyFileFlags.NONE);
} catch (GLib.Error e) {
e.message = "warning: Failed to read keyfile '%s': %s".printf (filename, e.message);
throw e;
}
try {
foreach (var group in kf.get_groups ()) {
if (group.has_prefix ("/") || group.has_suffix ("/") || "//" in group) {
stderr.printf ("%s: ignoring invalid group name: %s\n", filename, group);
continue;
}
foreach (var key in kf.get_keys (group)) {
if ("/" in key) {
throw new KeyFileError.INVALID_VALUE ("%s: [%s]: invalid key name: %s",
filename, group, key);
}
var path = "/" + group + "/" + key;
if (table.lookup (path) != null) {
/* We process the files in reverse alphabetical order. If the key is already set then
* it must have been set from a file with higher precedence so we should ignore this
* one.
*/
continue;
}
var text = kf.get_value (group, key);
try {
var value = Variant.parse (null, text);
unowned Gvdb.Item item = table.insert (path);
item.set_parent (get_parent (table, path));
item.set_value (value);
} catch (VariantParseError e) {
e.message = "%s: [%s]: %s: invalid value: %s (%s)".printf (filename, group, key, text, e.message);
throw e;
}
}
}
} catch (KeyFileError e) {
/* This should never happen... */
warning ("unexpected keyfile error: %s. Please file a bug.", e.message);
assert_not_reached ();
}
}
var locks = read_locks_directory (dirname + "/locks");
if (locks != null) {
unowned Gvdb.Item item = table.insert (".locks");
item.set_hash_table (locks);
}
return table;
}
void maybe_update_from_directory (string dirname) throws GLib.Error {
Posix.Stat dir_buf;
if (Posix.stat (dirname, out dir_buf) == 0 && Posix.S_ISDIR (dir_buf.st_mode)) {
Posix.Stat lockdir_buf;
Posix.Stat file_buf;
var filename = dirname.substring (0, dirname.length - 2);
if (Posix.stat (dirname + "/locks", out lockdir_buf) == 0 && lockdir_buf.st_mtime > dir_buf.st_mtime) {
// if the lock directory has been updated more recently then consider its timestamp instead
dir_buf.st_mtime = lockdir_buf.st_mtime;
}
if (Posix.stat (filename, out file_buf) == 0 && file_buf.st_mtime > dir_buf.st_mtime) {
return;
}
var table = read_directory (dirname);
var fd = Posix.open (filename, Posix.O_WRONLY);
if (fd < 0 && errno != Posix.ENOENT) {
var saved_error = errno;
printerr ("warning: Failed to open '%s' for replacement: %s\n", filename, strerror (saved_error));
return;
}
// We expect that fd < 0 here if ENOENT (ie: the db merely didn't exist yet)
try {
table.write_contents (filename);
if (fd >= 0) {
Posix.write (fd, "\0\0\0\0\0\0\0\0", 8);
}
} catch (Error e) {
printerr ("warning: %s\n", e.message);
return;
} finally {
if (fd >= 0) {
Posix.close (fd);
}
}
try {
var system_bus = Bus.get_sync (BusType.SYSTEM);
system_bus.emit_signal (null, "/ca/desrt/dconf/Writer/" + Path.get_basename (filename),
"ca.desrt.dconf.Writer", "WritabilityNotify", new Variant ("(s)", "/"));
system_bus.flush_sync ();
} catch {
/* if we can't, ... don't. */
}
}
}
void update_all (string dirname) throws GLib.Error {
foreach (var name in list_directory (dirname, Posix.S_IFDIR)) {
if (name.has_suffix (".d")) {
try {
maybe_update_from_directory (name);
} catch (Error e) {
printerr ("unable to compile %s: %s\n", name, e.message);
}
}
}
}
void dconf_compile (string[] args) throws GLib.Error {
if (args[2] == null || args[3] == null || args[4] != null) {
throw new OptionError.FAILED ("must give output file and .d dir");
}
try {
// We always write the result of "dconf compile" as little endian
// so that it can be installed in /usr/share
var table = read_directory (args[3]);
var should_byteswap = (BYTE_ORDER == ByteOrder.BIG_ENDIAN);
table.write_contents (args[2], should_byteswap);
} catch (Error e) {
printerr ("%s\n", e.message);
Process.exit (1);
}
}
[CCode (cname = "SYSCONFDIR")]
extern const string CONFIG_SYSCONFDIR;
void dconf_update (string[] args) throws GLib.Error {
update_all (CONFIG_SYSCONFDIR + "/dconf/db");
}
// vim:noet ts=4 sw=4