1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2013 Peter Wang
5 * Based in part on notmuch-deliver
6 * Copyright © 2010 Ali Polatel
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see http://www.gnu.org/licenses/ .
21 * Author: Peter Wang <novalazy@gmail.com>
24 #include "notmuch-client.h"
27 #include <sys/types.h>
31 static volatile sig_atomic_t interrupted;
34 handle_sigint (unused (int sig))
36 static char msg[] = "Stopping... \n";
38 /* This write is "opportunistic", so it's okay to ignore the
39 * result. It is not required for correctness, and if it does
40 * fail or produce a short write, we want to get out of the signal
41 * handler as quickly as possible, not retry it. */
42 IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
46 /* Like gethostname but guarantees that a null-terminated hostname is
47 * returned, even if it has to make one up. Invalid characters are
48 * substituted such that the hostname can be used within a filename.
51 safe_gethostname (char *hostname, size_t len)
55 if (gethostname (hostname, len) == -1) {
56 strncpy (hostname, "unknown", len);
58 hostname[len - 1] = '\0';
60 for (p = hostname; *p != '\0'; p++) {
61 if (*p == '/' || *p == ':')
66 /* Call fsync() on a directory path. */
68 sync_dir (const char *dir)
73 fd = open (dir, O_RDONLY);
75 fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));
78 ret = (fsync (fd) == 0);
80 fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));
86 /* Check the specified folder name does not contain a directory
87 * component ".." to prevent writes outside of the Maildir hierarchy. */
89 check_folder_name (const char *folder)
91 const char *p = folder;
94 if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))
103 /* Open a unique file in the 'tmp' sub-directory of dir.
104 * Returns the file descriptor on success, or -1 on failure.
105 * On success, file paths for the message in the 'tmp' and 'new'
106 * directories are returned via tmppath and newpath,
107 * and the path of the 'new' directory itself in newdir. */
109 maildir_open_tmp_file (void *ctx, const char *dir,
110 char **tmppath, char **newpath, char **newdir)
118 /* We follow the Dovecot file name generation algorithm. */
120 safe_gethostname (hostname, sizeof (hostname));
122 gettimeofday (&tv, NULL);
123 filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",
124 tv.tv_sec, tv.tv_usec, pid, hostname);
126 fprintf (stderr, "Out of memory\n");
130 *tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);
132 fprintf (stderr, "Out of memory\n");
136 fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
137 } while (fd == -1 && errno == EEXIST);
140 fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));
144 *newdir = talloc_asprintf (ctx, "%s/new", dir);
145 *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);
146 if (! *newdir || ! *newpath) {
147 fprintf (stderr, "Out of memory\n");
153 talloc_free (filename);
158 /* Copy the contents of standard input (fdin) into fdout.
159 * Returns TRUE if a non-empty file was written successfully.
160 * Otherwise, return FALSE. */
161 static notmuch_bool_t
162 copy_stdin (int fdin, int fdout)
164 notmuch_bool_t empty = TRUE;
166 while (! interrupted) {
171 remain = read (fdin, buf, sizeof (buf));
177 fprintf (stderr, "Error: reading from standard input: %s\n",
184 ssize_t written = write (fdout, p, remain);
185 if (written < 0 && errno == EINTR)
188 fprintf (stderr, "Error: writing to temporary file: %s",
195 } while (remain > 0);
198 return (!interrupted && !empty);
201 /* Add the specified message file to the notmuch database, applying tags.
202 * The file is renamed to encode notmuch tags as maildir flags. */
204 add_file_to_database (notmuch_database_t *notmuch, const char *path,
205 tag_op_list_t *tag_ops)
207 notmuch_message_t *message;
208 notmuch_status_t status;
210 status = notmuch_database_add_message (notmuch, path, &message);
212 case NOTMUCH_STATUS_SUCCESS:
213 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
216 case NOTMUCH_STATUS_FILE_NOT_EMAIL:
217 case NOTMUCH_STATUS_READ_ONLY_DATABASE:
218 case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
219 case NOTMUCH_STATUS_OUT_OF_MEMORY:
220 case NOTMUCH_STATUS_FILE_ERROR:
221 case NOTMUCH_STATUS_NULL_POINTER:
222 case NOTMUCH_STATUS_TAG_TOO_LONG:
223 case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
224 case NOTMUCH_STATUS_UNBALANCED_ATOMIC:
225 case NOTMUCH_STATUS_LAST_STATUS:
226 fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",
227 path, notmuch_status_to_string (status));
231 if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
232 /* Don't change tags of an existing message. */
233 status = notmuch_message_tags_to_maildir_flags (message);
234 if (status != NOTMUCH_STATUS_SUCCESS)
235 fprintf (stderr, "Error: failed to sync tags to maildir flags\n");
237 tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);
240 notmuch_message_destroy (message);
243 static notmuch_bool_t
244 insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,
245 const char *dir, tag_op_list_t *tag_ops)
253 fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);
257 cleanup_path = tmppath;
259 if (! copy_stdin (fdin, fdout))
262 if (fsync (fdout) != 0) {
263 fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));
270 /* Atomically move the new message file from the Maildir 'tmp' directory
271 * to the 'new' directory. We follow the Dovecot recommendation to
272 * simply use rename() instead of link() and unlink().
273 * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery
275 if (rename (tmppath, newpath) != 0) {
276 fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));
280 cleanup_path = newpath;
282 if (! sync_dir (newdir))
285 /* Even if adding the message to the notmuch database fails,
286 * the message is on disk and we consider the delivery completed. */
287 add_file_to_database (notmuch, newpath, tag_ops);
294 unlink (cleanup_path);
299 notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
301 notmuch_database_t *notmuch;
302 struct sigaction action;
304 const char **new_tags;
305 size_t new_tags_length;
306 tag_op_list_t *tag_ops;
307 char *query_string = NULL;
308 const char *folder = NULL;
314 notmuch_opt_desc_t options[] = {
315 { NOTMUCH_OPT_STRING, &folder, "folder", 0, 0 },
316 { NOTMUCH_OPT_END, 0, 0, 0, 0 }
319 opt_index = parse_arguments (argc, argv, options, 1);
322 /* diagnostics already printed */
326 db_path = notmuch_config_get_database_path (config);
327 new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
329 tag_ops = tag_op_list_create (config);
330 if (tag_ops == NULL) {
331 fprintf (stderr, "Out of memory.\n");
334 for (i = 0; i < new_tags_length; i++) {
335 if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
339 if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
340 &query_string, tag_ops))
343 if (*query_string != '\0') {
344 fprintf (stderr, "Error: unexpected query string: %s\n", query_string);
348 if (folder == NULL) {
351 if (! check_folder_name (folder)) {
352 fprintf (stderr, "Error: bad folder name: %s\n", folder);
355 maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
357 fprintf (stderr, "Out of memory\n");
362 /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
363 * from standard input may be interrupted. */
364 memset (&action, 0, sizeof (struct sigaction));
365 action.sa_handler = handle_sigint;
366 sigemptyset (&action.sa_mask);
368 sigaction (SIGINT, &action, NULL);
370 if (notmuch_database_open (notmuch_config_get_database_path (config),
371 NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
374 ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);
376 notmuch_database_destroy (notmuch);
378 return (ret) ? 0 : 1;