* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see http://www.gnu.org/licenses/ .
+ * along with this program. If not, see https://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth@cworth.org>
*/
#include <netdb.h>
#include <assert.h>
+#include "path-util.h"
+#include "unicode-util.h"
+
static const char toplevel_config_comment[] =
" .notmuch-config - Configuration file for the notmuch mail system\n"
"\n"
- " For more information about notmuch, see http://notmuchmail.org";
-
-static const char database_config_comment[] =
- " Database configuration\n"
- "\n"
- " The only value supported here is 'path' which should be the top-level\n"
- " directory where your mail currently exists and to where mail will be\n"
- " delivered in the future. Files should be individual email messages.\n"
- " Notmuch will store its database within a sub-directory of the path\n"
- " configured here named \".notmuch\".\n";
-
-static const char new_config_comment[] =
- " Configuration for \"notmuch new\"\n"
- "\n"
- " The following options are supported here:\n"
- "\n"
- "\ttags A list (separated by ';') of the tags that will be\n"
- "\t added to all messages incorporated by \"notmuch new\".\n"
- "\n"
- "\tignore A list (separated by ';') of file and directory names\n"
- "\t that will not be searched for messages by \"notmuch new\".\n"
- "\n"
- "\t NOTE: *Every* file/directory that goes by one of those names will\n"
- "\t be ignored, independent of its depth/location in the mail store.\n";
+ " For more information about notmuch, see https://notmuchmail.org";
-static const char user_config_comment[] =
- " User configuration\n"
- "\n"
- " Here is where you can let notmuch know how you would like to be\n"
- " addressed. Valid settings are\n"
- "\n"
- "\tname Your full name.\n"
- "\tprimary_email Your primary email address.\n"
- "\tother_email A list (separated by ';') of other email addresses\n"
- "\t at which you receive email.\n"
- "\n"
- " Notmuch will use the various email addresses configured here when\n"
- " formatting replies. It will avoid including your own addresses in the\n"
- " recipient list of replies, and will set the From address based on the\n"
- " address to which the original email was addressed.\n";
-
-static const char maildir_config_comment[] =
- " Maildir compatibility configuration\n"
- "\n"
- " The following option is supported here:\n"
- "\n"
- "\tsynchronize_flags Valid values are true and false.\n"
- "\n"
- "\tIf true, then the following maildir flags (in message filenames)\n"
- "\twill be synchronized with the corresponding notmuch tags:\n"
- "\n"
- "\t\tFlag Tag\n"
- "\t\t---- -------\n"
- "\t\tD draft\n"
- "\t\tF flagged\n"
- "\t\tP passed\n"
- "\t\tR replied\n"
- "\t\tS unread (added when 'S' flag is not present)\n"
- "\n"
- "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
- "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
- "\tcommands will notice tag changes and update flags in filenames\n";
-
-static const char search_config_comment[] =
- " Search configuration\n"
- "\n"
- " The following option is supported here:\n"
- "\n"
- "\texclude_tags\n"
- "\t\tA ;-separated list of tags that will be excluded from\n"
- "\t\tsearch results by default. Using an excluded tag in a\n"
- "\t\tquery will override that exclusion.\n";
+static const struct config_group {
+ const char *group_name;
+ const char *comment;
+} group_comment_table [] = {
+ {
+ "database",
+ " Database configuration\n"
+ "\n"
+ " The only value supported here is 'path' which should be the top-level\n"
+ " directory where your mail currently exists and to where mail will be\n"
+ " delivered in the future. Files should be individual email messages.\n"
+ " Notmuch will store its database within a sub-directory of the path\n"
+ " configured here named \".notmuch\".\n"
+ },
+ {
+ "user",
+ " User configuration\n"
+ "\n"
+ " Here is where you can let notmuch know how you would like to be\n"
+ " addressed. Valid settings are\n"
+ "\n"
+ "\tname Your full name.\n"
+ "\tprimary_email Your primary email address.\n"
+ "\tother_email A list (separated by ';') of other email addresses\n"
+ "\t at which you receive email.\n"
+ "\n"
+ " Notmuch will use the various email addresses configured here when\n"
+ " formatting replies. It will avoid including your own addresses in the\n"
+ " recipient list of replies, and will set the From address based on the\n"
+ " address to which the original email was addressed.\n"
+ },
+ {
+ "new",
+ " Configuration for \"notmuch new\"\n"
+ "\n"
+ " The following options are supported here:\n"
+ "\n"
+ "\ttags A list (separated by ';') of the tags that will be\n"
+ "\t added to all messages incorporated by \"notmuch new\".\n"
+ "\n"
+ "\tignore A list (separated by ';') of file and directory names\n"
+ "\t that will not be searched for messages by \"notmuch new\".\n"
+ "\n"
+ "\t NOTE: *Every* file/directory that goes by one of those\n"
+ "\t names will be ignored, independent of its depth/location\n"
+ "\t in the mail store.\n"
+ },
+ {
+ "search",
+ " Search configuration\n"
+ "\n"
+ " The following option is supported here:\n"
+ "\n"
+ "\texclude_tags\n"
+ "\t\tA ;-separated list of tags that will be excluded from\n"
+ "\t\tsearch results by default. Using an excluded tag in a\n"
+ "\t\tquery will override that exclusion.\n"
+ },
+ {
+ "maildir",
+ " Maildir compatibility configuration\n"
+ "\n"
+ " The following option is supported here:\n"
+ "\n"
+ "\tsynchronize_flags Valid values are true and false.\n"
+ "\n"
+ "\tIf true, then the following maildir flags (in message filenames)\n"
+ "\twill be synchronized with the corresponding notmuch tags:\n"
+ "\n"
+ "\t\tFlag Tag\n"
+ "\t\t---- -------\n"
+ "\t\tD draft\n"
+ "\t\tF flagged\n"
+ "\t\tP passed\n"
+ "\t\tR replied\n"
+ "\t\tS unread (added when 'S' flag is not present)\n"
+ "\n"
+ "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
+ "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
+ "\tcommands will notice tag changes and update flags in filenames\n"
+ },
+};
-struct _notmuch_config {
+struct _notmuch_conffile {
char *filename;
GKeyFile *key_file;
-
- char *database_path;
- char *user_name;
- char *user_primary_email;
- const char **user_other_email;
- size_t user_other_email_length;
- const char **new_tags;
- size_t new_tags_length;
- const char **new_ignore;
- size_t new_ignore_length;
- notmuch_bool_t maildir_synchronize_flags;
- const char **search_exclude_tags;
- size_t search_exclude_tags_length;
+ bool is_new;
};
static int
-notmuch_config_destructor (notmuch_config_t *config)
+notmuch_conffile_destructor (notmuch_conffile_t *config)
{
if (config->key_file)
g_key_file_free (config->key_file);
return 0;
}
-static char *
-get_name_from_passwd_file (void *ctx)
+static bool
+get_config_from_file (notmuch_conffile_t *config, bool create_new)
{
- long pw_buf_size;
- char *pw_buf;
- struct passwd passwd, *ignored;
- char *name;
- int e;
-
- pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (pw_buf_size == -1) pw_buf_size = 64;
- pw_buf = talloc_size (ctx, pw_buf_size);
-
- while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
- pw_buf_size, &ignored)) == ERANGE) {
- pw_buf_size = pw_buf_size * 2;
- pw_buf = talloc_zero_size(ctx, pw_buf_size);
+ #define BUF_SIZE 4096
+ char *config_str = NULL;
+ int config_len = 0;
+ int config_bufsize = BUF_SIZE;
+ size_t len;
+ GError *error = NULL;
+ bool ret = false;
+
+ FILE *fp = fopen (config->filename, "r");
+ if (fp == NULL) {
+ if (errno == ENOENT) {
+ /* If create_new is true, then the caller is prepared for a
+ * default configuration file in the case of FILE NOT FOUND.
+ */
+ if (create_new) {
+ config->is_new = true;
+ ret = true;
+ } else {
+ fprintf (stderr, "Configuration file %s not found.\n"
+ "Try running 'notmuch setup' to create a configuration.\n",
+ config->filename);
+ }
+ } else {
+ fprintf (stderr, "Error opening config file '%s': %s\n",
+ config->filename, strerror (errno));
+ }
+ goto out;
}
- if (e == 0) {
- char *comma = strchr (passwd.pw_gecos, ',');
- if (comma)
- name = talloc_strndup (ctx, passwd.pw_gecos,
- comma - passwd.pw_gecos);
- else
- name = talloc_strdup (ctx, passwd.pw_gecos);
- } else {
- name = talloc_strdup (ctx, "");
+ config_str = talloc_zero_array (config, char, config_bufsize);
+ if (config_str == NULL) {
+ fprintf (stderr, "Error reading '%s': Out of memory\n", config->filename);
+ goto out;
}
- talloc_free (pw_buf);
+ while ((len = fread (config_str + config_len, 1,
+ config_bufsize - config_len, fp)) > 0) {
+ config_len += len;
+ if (config_len == config_bufsize) {
+ config_bufsize += BUF_SIZE;
+ config_str = talloc_realloc (config, config_str, char, config_bufsize);
+ if (config_str == NULL) {
+ fprintf (stderr, "Error reading '%s': Failed to reallocate memory\n",
+ config->filename);
+ goto out;
+ }
+ }
+ }
- return name;
-}
+ if (ferror (fp)) {
+ fprintf (stderr, "Error reading '%s': I/O error\n", config->filename);
+ goto out;
+ }
-static char *
-get_username_from_passwd_file (void *ctx)
-{
- long pw_buf_size;
- char *pw_buf;
- struct passwd passwd, *ignored;
- char *name;
- int e;
-
- pw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (pw_buf_size == -1) pw_buf_size = 64;
- pw_buf = talloc_zero_size (ctx, pw_buf_size);
-
- while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
- pw_buf_size, &ignored)) == ERANGE) {
- pw_buf_size = pw_buf_size * 2;
- pw_buf = talloc_zero_size(ctx, pw_buf_size);
+ if (g_key_file_load_from_data (config->key_file, config_str, config_len,
+ G_KEY_FILE_KEEP_COMMENTS, &error)) {
+ ret = true;
+ goto out;
}
- if (e == 0)
- name = talloc_strdup (ctx, passwd.pw_name);
- else
- name = talloc_strdup (ctx, "");
+ fprintf (stderr, "Error parsing config file '%s': %s\n",
+ config->filename, error->message);
+
+ g_error_free (error);
- talloc_free (pw_buf);
+ out:
+ if (fp)
+ fclose (fp);
- return name;
+ if (config_str)
+ talloc_free (config_str);
+
+ return ret;
}
/* Open the named notmuch configuration file. If the filename is NULL,
*
* If is_new_ret is NULL, then a "file not found" message will be
* printed to stderr and NULL will be returned.
-
+ *
* If is_new_ret is non-NULL then a default configuration will be
* returned and *is_new_ret will be set to 1 on return so that
* the caller can recognize this case.
*
- * These default configuration settings are determined as
- * follows:
+ * These default configuration settings are determined as
+ * follows:
*
- * database_path: $HOME/mail
+ * database_path: $MAILDIR, otherwise $HOME/mail
*
- * user_name: From /etc/passwd
+ * user_name: $NAME variable if set, otherwise
+ * read from /etc/passwd
*
- * user_primary_mail: $EMAIL variable if set, otherwise
+ * user_primary_mail: $EMAIL variable if set, otherwise
* constructed from the username and
* hostname of the current machine.
*
* The default configuration also contains comments to guide the
* user in editing the file directly.
*/
-notmuch_config_t *
-notmuch_config_open (void *ctx,
- const char *filename,
- notmuch_bool_t *is_new_ret)
+notmuch_conffile_t *
+notmuch_conffile_open (notmuch_database_t *notmuch,
+ const char *filename,
+ bool create)
{
- GError *error = NULL;
- int is_new = 0;
- size_t tmp;
char *notmuch_config_env = NULL;
- int file_had_database_group;
- int file_had_new_group;
- int file_had_user_group;
- int file_had_maildir_group;
- int file_had_search_group;
- if (is_new_ret)
- *is_new_ret = 0;
+ notmuch_conffile_t *config = talloc_zero (notmuch, notmuch_conffile_t);
- notmuch_config_t *config = talloc (ctx, notmuch_config_t);
if (config == NULL) {
fprintf (stderr, "Out of memory.\n");
return NULL;
}
-
- talloc_set_destructor (config, notmuch_config_destructor);
+
+ talloc_set_destructor (config, notmuch_conffile_destructor);
if (filename) {
config->filename = talloc_strdup (config, filename);
config->key_file = g_key_file_new ();
- config->database_path = NULL;
- config->user_name = NULL;
- config->user_primary_email = NULL;
- config->user_other_email = NULL;
- config->user_other_email_length = 0;
- config->new_tags = NULL;
- config->new_tags_length = 0;
- config->new_ignore = NULL;
- config->new_ignore_length = 0;
- config->maildir_synchronize_flags = TRUE;
- config->search_exclude_tags = NULL;
- config->search_exclude_tags_length = 0;
-
- if (! g_key_file_load_from_file (config->key_file,
- config->filename,
- G_KEY_FILE_KEEP_COMMENTS,
- &error))
- {
- /* If the caller passed a non-NULL value for is_new_ret, then
- * the caller is prepared for a default configuration file in
- * the case of FILE NOT FOUND. Otherwise, any read failure is
- * an error.
- */
- if (is_new_ret &&
- error->domain == G_FILE_ERROR &&
- error->code == G_FILE_ERROR_NOENT)
- {
- g_error_free (error);
- is_new = 1;
- }
- else
- {
- fprintf (stderr, "Error reading configuration file %s: %s\n",
- config->filename, error->message);
- talloc_free (config);
- g_error_free (error);
- return NULL;
- }
- }
-
- /* Whenever we know of configuration sections that don't appear in
- * the configuration file, we add some comments to help the user
- * understand what can be done.
- *
- * It would be convenient to just add those comments now, but
- * apparently g_key_file will clear any comments when keys are
- * added later that create the groups. So we have to check for the
- * groups now, but add the comments only after setting all of our
- * values.
- */
- file_had_database_group = g_key_file_has_group (config->key_file,
- "database");
- file_had_new_group = g_key_file_has_group (config->key_file, "new");
- file_had_user_group = g_key_file_has_group (config->key_file, "user");
- file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
- file_had_search_group = g_key_file_has_group (config->key_file, "search");
-
-
- if (notmuch_config_get_database_path (config) == NULL) {
- char *path = talloc_asprintf (config, "%s/mail",
- getenv ("HOME"));
- notmuch_config_set_database_path (config, path);
- talloc_free (path);
- }
-
- if (notmuch_config_get_user_name (config) == NULL) {
- char *name = get_name_from_passwd_file (config);
- notmuch_config_set_user_name (config, name);
- talloc_free (name);
- }
-
- if (notmuch_config_get_user_primary_email (config) == NULL) {
- char *email = getenv ("EMAIL");
- if (email) {
- notmuch_config_set_user_primary_email (config, email);
- } else {
- char hostname[256];
- struct hostent *hostent;
- const char *domainname;
-
- char *username = get_username_from_passwd_file (config);
-
- gethostname (hostname, 256);
- hostname[255] = '\0';
-
- hostent = gethostbyname (hostname);
- if (hostent && (domainname = strchr (hostent->h_name, '.')))
- domainname += 1;
- else
- domainname = "(none)";
-
- email = talloc_asprintf (config, "%s@%s.%s",
- username, hostname, domainname);
-
- notmuch_config_set_user_primary_email (config, email);
-
- talloc_free (username);
- talloc_free (email);
- }
- }
-
- if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
- const char *tags[] = { "unread", "inbox" };
- notmuch_config_set_new_tags (config, tags, 2);
- }
-
- if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
- notmuch_config_set_new_ignore (config, NULL, 0);
- }
-
- if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
- if (is_new) {
- const char *tags[] = { "deleted", "spam" };
- notmuch_config_set_search_exclude_tags (config, tags, 2);
- } else {
- notmuch_config_set_search_exclude_tags (config, NULL, 0);
- }
- }
-
- error = NULL;
- config->maildir_synchronize_flags =
- g_key_file_get_boolean (config->key_file,
- "maildir", "synchronize_flags", &error);
- if (error) {
- notmuch_config_set_maildir_synchronize_flags (config, TRUE);
- g_error_free (error);
+ if (! get_config_from_file (config, create)) {
+ talloc_free (config);
+ return NULL;
}
- /* Whenever we know of configuration sections that don't appear in
- * the configuration file, we add some comments to help the user
- * understand what can be done. */
- if (is_new)
- {
+ if (config->is_new)
g_key_file_set_comment (config->key_file, NULL, NULL,
toplevel_config_comment, NULL);
- }
-
- if (! file_had_database_group)
- {
- g_key_file_set_comment (config->key_file, "database", NULL,
- database_config_comment, NULL);
- }
- if (! file_had_new_group)
- {
- g_key_file_set_comment (config->key_file, "new", NULL,
- new_config_comment, NULL);
- }
-
- if (! file_had_user_group)
- {
- g_key_file_set_comment (config->key_file, "user", NULL,
- user_config_comment, NULL);
- }
-
- if (! file_had_maildir_group)
- {
- g_key_file_set_comment (config->key_file, "maildir", NULL,
- maildir_config_comment, NULL);
- }
-
- if (! file_had_search_group) {
- g_key_file_set_comment (config->key_file, "search", NULL,
- search_config_comment, NULL);
+ for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
+ const char *name = group_comment_table[i].group_name;
+ if (! g_key_file_has_group (config->key_file, name)) {
+ /* Force group to exist before adding comment */
+ g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
+ g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
+ g_key_file_set_comment (config->key_file, name, NULL,
+ group_comment_table[i].comment, NULL);
+ }
}
-
- if (is_new_ret)
- *is_new_ret = is_new;
-
return config;
}
-/* Close the given notmuch_config_t object, freeing all resources.
- *
+/* Close the given notmuch_conffile_t object, freeing all resources.
+ *
* Note: Any changes made to the configuration are *not* saved by this
- * function. To save changes, call notmuch_config_save before
- * notmuch_config_close.
-*/
+ * function. To save changes, call notmuch_conffile_save before
+ * notmuch_conffile_close.
+ */
void
-notmuch_config_close (notmuch_config_t *config)
+notmuch_conffile_close (notmuch_conffile_t *config)
{
talloc_free (config);
}
* printing a description of the error to stderr).
*/
int
-notmuch_config_save (notmuch_config_t *config)
+notmuch_conffile_save (notmuch_conffile_t *config)
{
size_t length;
- char *data;
+ char *data, *filename;
GError *error = NULL;
data = g_key_file_to_data (config->key_file, &length, NULL);
return 1;
}
- if (! g_file_set_contents (config->filename, data, length, &error)) {
- fprintf (stderr, "Error saving configuration to %s: %s\n",
- config->filename, error->message);
+ /* Try not to overwrite symlinks. */
+ filename = notmuch_canonicalize_file_name (config->filename);
+ if (! filename) {
+ if (errno == ENOENT) {
+ filename = strdup (config->filename);
+ if (! filename) {
+ fprintf (stderr, "Out of memory.\n");
+ g_free (data);
+ return 1;
+ }
+ } else {
+ fprintf (stderr, "Error canonicalizing %s: %s\n", config->filename,
+ strerror (errno));
+ g_free (data);
+ return 1;
+ }
+ }
+
+ if (! g_file_set_contents (filename, data, length, &error)) {
+ if (strcmp (filename, config->filename) != 0) {
+ fprintf (stderr, "Error saving configuration to %s (-> %s): %s\n",
+ config->filename, filename, error->message);
+ } else {
+ fprintf (stderr, "Error saving configuration to %s: %s\n",
+ filename, error->message);
+ }
g_error_free (error);
+ free (filename);
g_free (data);
return 1;
}
+ free (filename);
g_free (data);
return 0;
}
-static const char **
-_config_get_list (notmuch_config_t *config,
- const char *section, const char *key,
- const char ***outlist, size_t *list_length, size_t *ret_length)
+bool
+notmuch_conffile_is_new (notmuch_conffile_t *config)
{
- assert(outlist);
-
- if (*outlist == NULL) {
-
- char **inlist = g_key_file_get_string_list (config->key_file,
- section, key, list_length, NULL);
- if (inlist) {
- unsigned int i;
-
- *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
-
- for (i = 0; i < *list_length; i++)
- (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
-
- (*outlist)[i] = NULL;
-
- g_strfreev (inlist);
- }
- }
-
- if (ret_length)
- *ret_length = *list_length;
-
- return *outlist;
+ return config->is_new;
}
static void
-_config_set_list (notmuch_config_t *config,
- const char *group, const char *name,
- const char *list[],
- size_t length, const char ***config_var )
-{
- g_key_file_set_string_list (config->key_file, group, name, list, length);
- talloc_free (*config_var);
- *config_var = NULL;
-}
-
-const char *
-notmuch_config_get_database_path (notmuch_config_t *config)
+_config_set (notmuch_conffile_t *config,
+ const char *group, const char *key, const char *value)
{
- char *path;
-
- if (config->database_path == NULL) {
- path = g_key_file_get_string (config->key_file,
- "database", "path", NULL);
- if (path) {
- config->database_path = talloc_strdup (config, path);
- free (path);
- }
- }
-
- return config->database_path;
+ g_key_file_set_string (config->key_file, group, key, value);
}
-void
-notmuch_config_set_database_path (notmuch_config_t *config,
- const char *database_path)
-{
- g_key_file_set_string (config->key_file,
- "database", "path", database_path);
-
- talloc_free (config->database_path);
- config->database_path = NULL;
-}
-
-const char *
-notmuch_config_get_user_name (notmuch_config_t *config)
+static void
+_config_set_list (notmuch_conffile_t *config,
+ const char *group, const char *key,
+ const char *list[],
+ size_t length)
{
- char *name;
-
- if (config->user_name == NULL) {
- name = g_key_file_get_string (config->key_file,
- "user", "name", NULL);
- if (name) {
- config->user_name = talloc_strdup (config, name);
- free (name);
- }
- }
-
- return config->user_name;
+ if (length > 1)
+ g_key_file_set_string_list (config->key_file, group, key, list, length);
+ else
+ g_key_file_set_string (config->key_file, group, key, list[0]);
}
void
-notmuch_config_set_user_name (notmuch_config_t *config,
- const char *user_name)
-{
- g_key_file_set_string (config->key_file,
- "user", "name", user_name);
-
- talloc_free (config->user_name);
- config->user_name = NULL;
-}
-
-const char *
-notmuch_config_get_user_primary_email (notmuch_config_t *config)
+notmuch_conffile_set_database_path (notmuch_conffile_t *config,
+ const char *database_path)
{
- char *email;
-
- if (config->user_primary_email == NULL) {
- email = g_key_file_get_string (config->key_file,
- "user", "primary_email", NULL);
- if (email) {
- config->user_primary_email = talloc_strdup (config, email);
- free (email);
- }
- }
-
- return config->user_primary_email;
+ _config_set (config, "database", "path", database_path);
}
void
-notmuch_config_set_user_primary_email (notmuch_config_t *config,
- const char *primary_email)
-{
- g_key_file_set_string (config->key_file,
- "user", "primary_email", primary_email);
-
- talloc_free (config->user_primary_email);
- config->user_primary_email = NULL;
-}
-
-const char **
-notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
-{
- return _config_get_list (config, "user", "other_email",
- &(config->user_other_email),
- &(config->user_other_email_length), length);
-}
-
-const char **
-notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
+notmuch_conffile_set_user_name (notmuch_conffile_t *config,
+ const char *user_name)
{
- return _config_get_list (config, "new", "tags",
- &(config->new_tags),
- &(config->new_tags_length), length);
-}
-
-const char **
-notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
-{
- return _config_get_list (config, "new", "ignore",
- &(config->new_ignore),
- &(config->new_ignore_length), length);
+ _config_set (config, "user", "name", user_name);
}
void
-notmuch_config_set_user_other_email (notmuch_config_t *config,
- const char *list[],
- size_t length)
+notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
+ const char *primary_email)
{
- _config_set_list (config, "user", "other_email", list, length,
- &(config->user_other_email));
+ _config_set (config, "user", "primary_email", primary_email);
}
void
-notmuch_config_set_new_tags (notmuch_config_t *config,
- const char *list[],
- size_t length)
+notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length)
{
- _config_set_list (config, "new", "tags", list, length,
- &(config->new_tags));
+ _config_set_list (config, "user", "other_email", list, length);
}
void
-notmuch_config_set_new_ignore (notmuch_config_t *config,
+notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
const char *list[],
size_t length)
{
- _config_set_list (config, "new", "ignore", list, length,
- &(config->new_ignore));
+ _config_set_list (config, "new", "tags", list, length);
}
-const char **
-notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
+void
+notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length)
{
- return _config_get_list (config, "search", "exclude_tags",
- &(config->search_exclude_tags),
- &(config->search_exclude_tags_length), length);
+ _config_set_list (config, "new", "ignore", list, length);
}
void
-notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
- const char *list[],
- size_t length)
+notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length)
{
- _config_set_list (config, "search", "exclude_tags", list, length,
- &(config->search_exclude_tags));
+ _config_set_list (config, "search", "exclude_tags", list, length);
}
+
/* Given a configuration item of the form <group>.<key> return the
* component group and key. If any error occurs, print a message on
* stderr and return 1. Otherwise, return 0.
*group = item;
- period = index (item, '.');
- if (period == NULL || *(period+1) == '\0') {
+ period = strchr (item, '.');
+ if (period == NULL || *(period + 1) == '\0') {
fprintf (stderr,
"Invalid configuration name: %s\n"
"(Should be of the form <section>.<item>)\n", item);
return 0;
}
-static int
-notmuch_config_command_get (void *ctx, char *item)
+/* These are more properly called Xapian fields, but the user facing
+ * docs call them prefixes, so make the error message match */
+static bool
+validate_field_name (const char *str)
{
- notmuch_config_t *config;
+ const char *key;
- config = notmuch_config_open (ctx, NULL, NULL);
- if (config == NULL)
- return 1;
+ if (! g_utf8_validate (str, -1, NULL)) {
+ fprintf (stderr, "Invalid utf8: %s\n", str);
+ return false;
+ }
- if (strcmp(item, "database.path") == 0) {
- printf ("%s\n", notmuch_config_get_database_path (config));
- } else if (strcmp(item, "user.name") == 0) {
- printf ("%s\n", notmuch_config_get_user_name (config));
- } else if (strcmp(item, "user.primary_email") == 0) {
- printf ("%s\n", notmuch_config_get_user_primary_email (config));
- } else if (strcmp(item, "user.other_email") == 0) {
- const char **other_email;
- size_t i, length;
-
- other_email = notmuch_config_get_user_other_email (config, &length);
- for (i = 0; i < length; i++)
- printf ("%s\n", other_email[i]);
- } else if (strcmp(item, "new.tags") == 0) {
- const char **tags;
- size_t i, length;
-
- tags = notmuch_config_get_new_tags (config, &length);
- for (i = 0; i < length; i++)
- printf ("%s\n", tags[i]);
- } else {
- char **value;
- size_t i, length;
- char *group, *key;
+ key = g_utf8_strrchr (str, -1, '.');
+ if (! key ) {
+ INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
+ }
- if (_item_split (item, &group, &key))
- return 1;
+ key++;
- value = g_key_file_get_string_list (config->key_file,
- group, key,
- &length, NULL);
- if (value == NULL) {
- fprintf (stderr, "Unknown configuration item: %s.%s\n",
- group, key);
- return 1;
+ if (! *key) {
+ fprintf (stderr, "Empty prefix name: %s\n", str);
+ return false;
+ }
+
+ if (! unicode_word_utf8 (key)) {
+ fprintf (stderr, "Non-word character in prefix name: %s\n", key);
+ return false;
+ }
+
+ if (key[0] >= 'a' && key[0] <= 'z') {
+ fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
+ return false;
+ }
+
+ return true;
+}
+
+#define BUILT_WITH_PREFIX "built_with."
+
+typedef struct config_key {
+ const char *name;
+ bool prefix;
+ bool (*validate)(const char *);
+} config_key_info_t;
+
+static const struct config_key
+ config_key_table[] = {
+ { "index.decrypt", false, NULL },
+ { "index.header.", true, validate_field_name },
+ { "query.", true, NULL },
+ { "squery.", true, validate_field_name },
+};
+
+static const config_key_info_t *
+_config_key_info (const char *item)
+{
+ for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
+ if (config_key_table[i].prefix &&
+ strncmp (item, config_key_table[i].name,
+ strlen (config_key_table[i].name)) == 0)
+ return config_key_table + i;
+ if (strcmp (item, config_key_table[i].name) == 0)
+ return config_key_table + i;
+ }
+ return NULL;
+}
+
+static int
+notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
+{
+ notmuch_config_values_t *list;
+
+ if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+ if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
+ puts ("true");
+ else
+ puts ("false");
+ } else {
+ for (list = notmuch_config_get_values_string (notmuch, item);
+ notmuch_config_values_valid (list);
+ notmuch_config_values_move_to_next (list)) {
+ const char *val = notmuch_config_values_get (list);
+ puts (val);
}
+ }
+ return EXIT_SUCCESS;
+}
- for (i = 0; i < length; i++)
- printf ("%s\n", value[i]);
+static int
+_set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
+{
+ const char *val = "";
- free (value);
+ if (argc > 1) {
+ /* XXX handle lists? */
+ fprintf (stderr, "notmuch config set: at most one value expected for %s\n", key);
+ return EXIT_FAILURE;
}
- notmuch_config_close (config);
+ if (argc > 0) {
+ val = argv[0];
+ }
- return 0;
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_reopen (notmuch,
+ NOTMUCH_DATABASE_MODE_READ_WRITE)))
+ return EXIT_FAILURE;
+
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_set_config (notmuch, key, val)))
+ return EXIT_FAILURE;
+
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_close (notmuch)))
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
}
static int
-notmuch_config_command_set (void *ctx, char *item, int argc, char *argv[])
+notmuch_config_command_set (notmuch_database_t *notmuch,
+ int argc, char *argv[])
{
- notmuch_config_t *config;
char *group, *key;
- int ret;
+ const config_key_info_t *key_info;
+ notmuch_conffile_t *config;
+ bool update_database = false;
+ int opt_index, ret;
+ char *item;
+
+ notmuch_opt_desc_t options[] = {
+ { .opt_bool = &update_database, .name = "database" },
+ { }
+ };
+
+ opt_index = parse_arguments (argc, argv, options, 1);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ argc -= opt_index;
+ argv += opt_index;
+
+ if (argc < 1) {
+ fprintf (stderr, "Error: notmuch config set requires at least "
+ "one argument.\n");
+ return EXIT_FAILURE;
+ }
+
+ item = argv[0];
+ argv++;
+ argc--;
+
+ if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+ fprintf (stderr, "Error: read only option: %s\n", item);
+ return 1;
+ }
+
+ key_info = _config_key_info (item);
+ if (key_info && key_info->validate && (! key_info->validate (item)))
+ return 1;
+
+ if (update_database) {
+ return _set_db_config (notmuch, item, argc, argv);
+ }
if (_item_split (item, &group, &key))
return 1;
- config = notmuch_config_open (ctx, NULL, NULL);
- if (config == NULL)
+ config = notmuch_conffile_open (notmuch,
+ notmuch_config_path (notmuch), false);
+ if (! config)
return 1;
/* With only the name of an item, we clear it from the
break;
}
- ret = notmuch_config_save (config);
- notmuch_config_close (config);
+ ret = notmuch_conffile_save (config);
+
+ notmuch_conffile_close (config);
return ret;
}
+static
+void
+_notmuch_config_list_built_with ()
+{
+ printf ("%scompact=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("compact") ? "true" : "false");
+ printf ("%sfield_processor=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("field_processor") ? "true" : "false");
+ printf ("%sretry_lock=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("retry_lock") ? "true" : "false");
+ printf ("%ssexp_queries=%s\n",
+ BUILT_WITH_PREFIX,
+ notmuch_built_with ("sexp_queries") ? "true" : "false");
+}
+
+static int
+notmuch_config_command_list (notmuch_database_t *notmuch)
+{
+ notmuch_config_pairs_t *list;
+
+ _notmuch_config_list_built_with ();
+ for (list = notmuch_config_get_pairs (notmuch, "");
+ notmuch_config_pairs_valid (list);
+ notmuch_config_pairs_move_to_next (list)) {
+ const char *value = notmuch_config_pairs_value (list);
+ if (value)
+ printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
+ }
+ notmuch_config_pairs_destroy (list);
+ return EXIT_SUCCESS;
+}
+
int
-notmuch_config_command (void *ctx, int argc, char *argv[])
+notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- argc--; argv++; /* skip subcommand argument */
+ int ret;
+ int opt_index;
- if (argc < 2) {
- fprintf (stderr, "Error: notmuch config requires at least two arguments.\n");
- return 1;
+ opt_index = notmuch_minimal_options ("config", argc, argv);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ /* skip at least subcommand argument */
+ argc -= opt_index;
+ argv += opt_index;
+
+ if (argc < 1) {
+ fprintf (stderr, "Error: notmuch config requires at least one argument.\n");
+ return EXIT_FAILURE;
}
- if (strcmp (argv[0], "get") == 0)
- return notmuch_config_command_get (ctx, argv[1]);
- else if (strcmp (argv[0], "set") == 0)
- return notmuch_config_command_set (ctx, argv[1], argc - 2, argv + 2);
+ if (strcmp (argv[0], "get") == 0) {
+ if (argc != 2) {
+ fprintf (stderr, "Error: notmuch config get requires exactly "
+ "one argument.\n");
+ return EXIT_FAILURE;
+ }
+ ret = notmuch_config_command_get (notmuch, argv[1]);
+ } else if (strcmp (argv[0], "set") == 0) {
+ ret = notmuch_config_command_set (notmuch, argc, argv);
+ } else if (strcmp (argv[0], "list") == 0) {
+ ret = notmuch_config_command_list (notmuch);
+ } else {
+ fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
+ argv[0]);
+ return EXIT_FAILURE;
+ }
- fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
- argv[0]);
- return 1;
-}
+ return ret ? EXIT_FAILURE : EXIT_SUCCESS;
-notmuch_bool_t
-notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
-{
- return config->maildir_synchronize_flags;
}
void
-notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
- notmuch_bool_t synchronize_flags)
+notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
+ bool synchronize_flags)
{
g_key_file_set_boolean (config->key_file,
"maildir", "synchronize_flags", synchronize_flags);
- config->maildir_synchronize_flags = synchronize_flags;
}