Allow clients to atomically get and set key value pairs.
---
 lib/Makefile.local      |   1 +
 lib/config.c            | 156 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/notmuch.h           |   7 +++
 test/Makefile.local     |   7 +++
 test/T560-lib-config.sh |  15 +++++
 test/config-test.c      |  28 +++++++++
 6 files changed, 214 insertions(+)
 create mode 100644 lib/config.c
 create mode 100755 test/T560-lib-config.sh
 create mode 100644 test/config-test.c

diff --git a/lib/Makefile.local b/lib/Makefile.local
index 4120390..7ca2b3b 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -54,6 +54,7 @@ lib := $(dir)

 libnotmuch_c_srcs =            \
        $(notmuch_compat_srcs)  \
+       $(dir)/config.c         \
        $(dir)/filenames.c      \
        $(dir)/string-list.c    \
        $(dir)/libsha1.c        \
diff --git a/lib/config.c b/lib/config.c
new file mode 100644
index 0000000..c3b8f39
--- /dev/null
+++ b/lib/config.c
@@ -0,0 +1,156 @@
+#include "notmuch-private.h"
+#include "string-util.h"
+#include "file-util.h"
+#include <talloc.h>
+
+static notmuch_status_t 
+compute_paths (void *ctx, const char *notmuch_path, const char *key,
+              char **parent_out, char **dest_out) {
+
+    char *parent, *dest, *final_component, *last_slash;
+
+    parent = talloc_asprintf (ctx, "%s/config/%s", notmuch_path, key);
+    if (!parent)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    last_slash = strrchr (parent, '/');
+    *last_slash = '\0';
+
+    final_component = talloc_strdup (ctx, last_slash + 1);
+    
+    dest = talloc_asprintf(ctx, "%s/_%s", parent, final_component);
+    if (!dest)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    *parent_out = parent;
+    *dest_out = dest;
+       
+    return NOTMUCH_STATUS_SUCCESS;
+
+}
+
+notmuch_status_t
+notmuch_config_get (const char *notmuch_path, const char *key, const char 
**val){
+
+    char *line = NULL;
+    size_t line_size;
+    ssize_t line_len;
+    char *buf = NULL;
+    char *file_name, *parent;
+    notmuch_status_t status;
+    void *local = NULL;
+    FILE *file_ptr = NULL;
+
+    if (notmuch_path == NULL || key == NULL || val == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    local = talloc_new (NULL);
+    
+    status = compute_paths (local, notmuch_path, key, &parent, &file_name);
+    if (status) 
+       goto DONE;
+
+    file_ptr = fopen (file_name, "r");
+    if (file_ptr == NULL) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    while ((line_len = getline (&line, &line_size, file_ptr)) != -1) {
+
+       if (buf)
+           buf = talloc_asprintf (local, "%s%s", buf, line);
+       else 
+           buf = talloc_strdup (local, line);
+       
+       if (buf == NULL) {
+           status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+           goto DONE;
+       }
+
+    }
+
+
+    /* remove the last newline. Convenient for the single line case. */
+    chomp_newline (buf);
+
+    *val = buf;
+    status =  NOTMUCH_STATUS_SUCCESS;
+       
+ DONE:
+    if (line)
+       free (line);
+    
+    if (file_ptr)
+       fclose (file_ptr);
+
+    talloc_free (local);
+
+    return status;
+}
+
+notmuch_status_t
+notmuch_config_set (const char *notmuch_path, const char *key, const char 
*val){
+
+    char *parent, *path, *temp_path;
+    int out_fd = -1;
+    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+    void *local = NULL;
+    FILE *out_file;
+    
+    if (notmuch_path == NULL || key == NULL || val == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (has_double_dot_component (key))
+       return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
+
+    local = talloc_new (NULL);
+
+    status = compute_paths (local, notmuch_path, key, &parent, &path);
+    if (status) 
+       goto DONE;
+
+    if (! mkdir_recursive (local, parent, 0700)) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    temp_path = talloc_asprintf (local, "%s/tmp.XXXXXX", parent);
+    if (temp_path == NULL) {
+       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+       goto DONE;
+    }
+
+    out_fd = mkstemp (temp_path);
+    if (out_fd == -1) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+    
+    out_file = fdopen (out_fd, "w");
+    if (out_file == NULL) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (fputs (val, out_file) == EOF) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (fclose (out_file)) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    if (rename (temp_path, path) < 0) {
+       status = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+       
+ DONE:
+
+    talloc_free(local);
+    
+    return status;
+}
diff --git a/lib/notmuch.h b/lib/notmuch.h
index fe2340b..9a5f9df 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -192,6 +192,13 @@ typedef struct _notmuch_directory notmuch_directory_t;
 typedef struct _notmuch_filenames notmuch_filenames_t;
 #endif /* __DOXYGEN__ */

+
+notmuch_status_t
+notmuch_config_get (const char *notmuch_path, const char *key, const char 
**val);
+
+notmuch_status_t
+notmuch_config_set (const char *notmuch_path, const char *key, const char 
*val);
+
 /**
  * Create a new, empty notmuch database located at 'path'.
  *
diff --git a/test/Makefile.local b/test/Makefile.local
index a2d58fc..8a203f0 100644
--- a/test/Makefile.local
+++ b/test/Makefile.local
@@ -23,6 +23,9 @@ random_corpus_deps =  $(dir)/random-corpus.o  
$(dir)/database-test.o \
                        lib/libnotmuch.a util/libutil.a \
                        parse-time-string/libparse-time-string.a

+config_test_deps =  $(dir)/config-test.o  \
+                       lib/libnotmuch.a util/libutil.a
+
 $(dir)/random-corpus: $(random_corpus_deps)
        $(call quiet,CXX) $(CFLAGS_FINAL) $^ -o $@ $(CONFIGURE_LDFLAGS)

@@ -38,6 +41,9 @@ $(dir)/parse-time: $(dir)/parse-time.o 
parse-time-string/parse-time-string.o
 $(dir)/make-db-version: $(dir)/make-db-version.o
        $(call quiet,CXX) $^ -o $@ $(XAPIAN_LDFLAGS)

+$(dir)/config-test: $(config_test_deps)
+       $(call quiet,CXX) $(CFLAGS_FINAL) $^ -o $@ $(CONFIGURE_LDFLAGS)
+
 .PHONY: test check

 test_main_srcs=$(dir)/arg-test.c \
@@ -47,6 +53,7 @@ test_main_srcs=$(dir)/arg-test.c \
              $(dir)/smtp-dummy.c \
              $(dir)/symbol-test.cc \
              $(dir)/make-db-version.cc \
+             $(dir)/config-test.c \

 test_srcs=$(test_main_srcs) $(dir)/database-test.c

diff --git a/test/T560-lib-config.sh b/test/T560-lib-config.sh
new file mode 100755
index 0000000..ec8ddbe
--- /dev/null
+++ b/test/T560-lib-config.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+test_description="library config handling"
+
+. ./test-lib.sh
+
+test_begin_subtest "getting and setting"
+${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch set a foo
+${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch set a/b bar
+${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch set b/a fub
+${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch get a  >> OUTPUT
+${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch get a/b  >> OUTPUT
+${TEST_DIRECTORY}/config-test ${MAIL_DIR}/.notmuch get b/a  >> OUTPUT
+test_expect_equal "$(cat OUTPUT)" "foobarfub"
+
+test_done
diff --git a/test/config-test.c b/test/config-test.c
new file mode 100644
index 0000000..d9a1116
--- /dev/null
+++ b/test/config-test.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "notmuch.h"
+
+int
+main (int argc, char **argv) {
+    const char *val;
+    notmuch_status_t status;
+
+    if (argc == 4 && strcmp (argv[2], "get") == 0) {
+       
+       status = notmuch_config_get (argv[1], argv[3], &val);
+       if (status) 
+           return status;
+       fputs (val, stdout);
+       return 0;
+
+    } else  if (argc == 5 && strcmp (argv[2], "set") == 0) {
+       
+       status = notmuch_config_set (argv[1], argv[3], argv[4]);
+       if (status) 
+           return status;
+       return 0;
+    }
+
+    return 1;
+}
-- 
2.1.0

Reply via email to