--- /dev/null
+Return-Path: <bremner@tesseract.cs.unb.ca>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id AEFF1431FD0\r
+ for <notmuch@notmuchmail.org>; Sun, 28 Sep 2014 11:29:13 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
+ autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id vttyfHCfwrR7 for <notmuch@notmuchmail.org>;\r
+ Sun, 28 Sep 2014 11:29:08 -0700 (PDT)\r
+Received: from yantan.tethera.net (yantan.tethera.net [199.188.72.155])\r
+ (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits))\r
+ (No client certificate requested)\r
+ by olra.theworths.org (Postfix) with ESMTPS id 7F2FE431FD2\r
+ for <notmuch@notmuchmail.org>; Sun, 28 Sep 2014 11:29:01 -0700 (PDT)\r
+Received: from remotemail by yantan.tethera.net with local (Exim 4.80)\r
+ (envelope-from <bremner@tesseract.cs.unb.ca>)\r
+ id 1XYJDR-0005gE-6N; Sun, 28 Sep 2014 15:29:01 -0300\r
+Received: (nullmailer pid 31290 invoked by uid 1000); Sun, 28 Sep 2014\r
+ 18:28:49 -0000\r
+From: David Bremner <david@tethera.net>\r
+To: notmuch@notmuchmail.org\r
+Subject: [WIP 3/3] lib: add configuration framework.\r
+Date: Sun, 28 Sep 2014 20:28:19 +0200\r
+Message-Id: <1411928899-29625-4-git-send-email-david@tethera.net>\r
+X-Mailer: git-send-email 2.1.0\r
+In-Reply-To: <1411928899-29625-1-git-send-email-david@tethera.net>\r
+References: <87iok8vog6.fsf@steelpick.2x.cz>\r
+ <1411928899-29625-1-git-send-email-david@tethera.net>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Sun, 28 Sep 2014 18:29:14 -0000\r
+\r
+Allow clients to atomically get and set key value pairs.\r
+---\r
+ lib/Makefile.local | 1 +\r
+ lib/config.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++\r
+ lib/notmuch.h | 7 +++\r
+ test/Makefile.local | 7 +++\r
+ test/T560-lib-config.sh | 15 +++++\r
+ test/config-test.c | 28 +++++++++\r
+ 6 files changed, 214 insertions(+)\r
+ create mode 100644 lib/config.c\r
+ create mode 100755 test/T560-lib-config.sh\r
+ create mode 100644 test/config-test.c\r
+\r
+diff --git a/lib/Makefile.local b/lib/Makefile.local\r
+index 4120390..7ca2b3b 100644\r
+--- a/lib/Makefile.local\r
++++ b/lib/Makefile.local\r
+@@ -54,6 +54,7 @@ lib := $(dir)\r
+ \r
+ libnotmuch_c_srcs = \\r
+ $(notmuch_compat_srcs) \\r
++ $(dir)/config.c \\r
+ $(dir)/filenames.c \\r
+ $(dir)/string-list.c \\r
+ $(dir)/libsha1.c \\r
+diff --git a/lib/config.c b/lib/config.c\r
+new file mode 100644\r
+index 0000000..c3b8f39\r
+--- /dev/null\r
++++ b/lib/config.c\r
+@@ -0,0 +1,156 @@\r
++#include "notmuch-private.h"\r
++#include "string-util.h"\r
++#include "file-util.h"\r
++#include <talloc.h>\r
++\r
++static notmuch_status_t \r
++compute_paths (void *ctx, const char *notmuch_path, const char *key,\r
++ char **parent_out, char **dest_out) {\r
++\r
++ char *parent, *dest, *final_component, *last_slash;\r
++\r
++ parent = talloc_asprintf (ctx, "%s/config/%s", notmuch_path, key);\r
++ if (!parent)\r
++ return NOTMUCH_STATUS_OUT_OF_MEMORY;\r
++\r
++ last_slash = strrchr (parent, '/');\r
++ *last_slash = '\0';\r
++\r
++ final_component = talloc_strdup (ctx, last_slash + 1);\r
++ \r
++ dest = talloc_asprintf(ctx, "%s/_%s", parent, final_component);\r
++ if (!dest)\r
++ return NOTMUCH_STATUS_OUT_OF_MEMORY;\r
++\r
++ *parent_out = parent;\r
++ *dest_out = dest;\r
++ \r
++ return NOTMUCH_STATUS_SUCCESS;\r
++\r
++}\r
++\r
++notmuch_status_t\r
++notmuch_config_get (const char *notmuch_path, const char *key, const char **val){\r
++\r
++ char *line = NULL;\r
++ size_t line_size;\r
++ ssize_t line_len;\r
++ char *buf = NULL;\r
++ char *file_name, *parent;\r
++ notmuch_status_t status;\r
++ void *local = NULL;\r
++ FILE *file_ptr = NULL;\r
++\r
++ if (notmuch_path == NULL || key == NULL || val == NULL)\r
++ return NOTMUCH_STATUS_NULL_POINTER;\r
++\r
++ local = talloc_new (NULL);\r
++ \r
++ status = compute_paths (local, notmuch_path, key, &parent, &file_name);\r
++ if (status) \r
++ goto DONE;\r
++\r
++ file_ptr = fopen (file_name, "r");\r
++ if (file_ptr == NULL) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++\r
++ while ((line_len = getline (&line, &line_size, file_ptr)) != -1) {\r
++\r
++ if (buf)\r
++ buf = talloc_asprintf (local, "%s%s", buf, line);\r
++ else \r
++ buf = talloc_strdup (local, line);\r
++ \r
++ if (buf == NULL) {\r
++ status = NOTMUCH_STATUS_OUT_OF_MEMORY;\r
++ goto DONE;\r
++ }\r
++\r
++ }\r
++\r
++\r
++ /* remove the last newline. Convenient for the single line case. */\r
++ chomp_newline (buf);\r
++\r
++ *val = buf;\r
++ status = NOTMUCH_STATUS_SUCCESS;\r
++ \r
++ DONE:\r
++ if (line)\r
++ free (line);\r
++ \r
++ if (file_ptr)\r
++ fclose (file_ptr);\r
++\r
++ talloc_free (local);\r
++\r
++ return status;\r
++}\r
++\r
++notmuch_status_t\r
++notmuch_config_set (const char *notmuch_path, const char *key, const char *val){\r
++\r
++ char *parent, *path, *temp_path;\r
++ int out_fd = -1;\r
++ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;\r
++ void *local = NULL;\r
++ FILE *out_file;\r
++ \r
++ if (notmuch_path == NULL || key == NULL || val == NULL)\r
++ return NOTMUCH_STATUS_NULL_POINTER;\r
++\r
++ if (has_double_dot_component (key))\r
++ return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;\r
++\r
++ local = talloc_new (NULL);\r
++\r
++ status = compute_paths (local, notmuch_path, key, &parent, &path);\r
++ if (status) \r
++ goto DONE;\r
++\r
++ if (! mkdir_recursive (local, parent, 0700)) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++\r
++ temp_path = talloc_asprintf (local, "%s/tmp.XXXXXX", parent);\r
++ if (temp_path == NULL) {\r
++ status = NOTMUCH_STATUS_OUT_OF_MEMORY;\r
++ goto DONE;\r
++ }\r
++\r
++ out_fd = mkstemp (temp_path);\r
++ if (out_fd == -1) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++ \r
++ out_file = fdopen (out_fd, "w");\r
++ if (out_file == NULL) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++\r
++ if (fputs (val, out_file) == EOF) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++\r
++ if (fclose (out_file)) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++\r
++ if (rename (temp_path, path) < 0) {\r
++ status = NOTMUCH_STATUS_FILE_ERROR;\r
++ goto DONE;\r
++ }\r
++ \r
++ DONE:\r
++\r
++ talloc_free(local);\r
++ \r
++ return status;\r
++}\r
+diff --git a/lib/notmuch.h b/lib/notmuch.h\r
+index fe2340b..9a5f9df 100644\r
+--- a/lib/notmuch.h\r
++++ b/lib/notmuch.h\r
+@@ -192,6 +192,13 @@ typedef struct _notmuch_directory notmuch_directory_t;\r
+ typedef struct _notmuch_filenames notmuch_filenames_t;\r
+ #endif /* __DOXYGEN__ */\r
+ \r
++\r
++notmuch_status_t\r
++notmuch_config_get (const char *notmuch_path, const char *key, const char **val);\r
++\r
++notmuch_status_t\r
++notmuch_config_set (const char *notmuch_path, const char *key, const char *val);\r
++\r
+ /**\r
+ * Create a new, empty notmuch database located at 'path'.\r
+ *\r
+diff --git a/test/Makefile.local b/test/Makefile.local\r
+index a2d58fc..8a203f0 100644\r
+--- a/test/Makefile.local\r
++++ b/test/Makefile.local\r
+@@ -23,6 +23,9 @@ random_corpus_deps = $(dir)/random-corpus.o $(dir)/database-test.o \\r
+ lib/libnotmuch.a util/libutil.a \\r
+ parse-time-string/libparse-time-string.a\r
+ \r
++config_test_deps = $(dir)/config-test.o \\r
++ lib/libnotmuch.a util/libutil.a\r
++\r
+ $(dir)/random-corpus: $(random_corpus_deps)\r
+ $(call quiet,CXX) $(CFLAGS_FINAL) $^ -o $@ $(CONFIGURE_LDFLAGS)\r
+ \r
+@@ -38,6 +41,9 @@ $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o\r
+ $(dir)/make-db-version: $(dir)/make-db-version.o\r
+ $(call quiet,CXX) $^ -o $@ $(XAPIAN_LDFLAGS)\r
+ \r
++$(dir)/config-test: $(config_test_deps)\r
++ $(call quiet,CXX) $(CFLAGS_FINAL) $^ -o $@ $(CONFIGURE_LDFLAGS)\r
++\r
+ .PHONY: test check\r
+ \r
+ test_main_srcs=$(dir)/arg-test.c \\r
+@@ -47,6 +53,7 @@ test_main_srcs=$(dir)/arg-test.c \\r
+ $(dir)/smtp-dummy.c \\r
+ $(dir)/symbol-test.cc \\r
+ $(dir)/make-db-version.cc \\r
++ $(dir)/config-test.c \\r
+ \r
+ test_srcs=$(test_main_srcs) $(dir)/database-test.c\r
+ \r
+diff --git a/test/T560-lib-config.sh b/test/T560-lib-config.sh\r
+new file mode 100755\r
+index 0000000..ec8ddbe\r
+--- /dev/null\r
++++ b/test/T560-lib-config.sh\r
+@@ -0,0 +1,15 @@\r
++#!/usr/bin/env bash\r
++test_description="library config handling"\r
++\r
++. ./test-lib.sh\r
++\r
++test_begin_subtest "getting and setting"\r
++${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch set a foo\r
++${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch set a/b bar\r
++${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch set b/a fub\r
++${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch get a >> OUTPUT\r
++${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch get a/b >> OUTPUT\r
++${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch get b/a >> OUTPUT\r
++test_expect_equal "$(cat OUTPUT)" "foobarfub"\r
++\r
++test_done\r
+diff --git a/test/config-test.c b/test/config-test.c\r
+new file mode 100644\r
+index 0000000..d9a1116\r
+--- /dev/null\r
++++ b/test/config-test.c\r
+@@ -0,0 +1,28 @@\r
++#include <stdio.h>\r
++#include <string.h>\r
++\r
++#include "notmuch.h"\r
++\r
++int\r
++main (int argc, char **argv) {\r
++ const char *val;\r
++ notmuch_status_t status;\r
++\r
++ if (argc == 4 && strcmp (argv[2], "get") == 0) {\r
++ \r
++ status = notmuch_config_get (argv[1], argv[3], &val);\r
++ if (status) \r
++ return status;\r
++ fputs (val, stdout);\r
++ return 0;\r
++\r
++ } else if (argc == 5 && strcmp (argv[2], "set") == 0) {\r
++ \r
++ status = notmuch_config_set (argv[1], argv[3], argv[4]);\r
++ if (status) \r
++ return status;\r
++ return 0;\r
++ }\r
++\r
++ return 1;\r
++}\r
+-- \r
+2.1.0\r
+\r