Re: [PATCH v4 01/16] add util/search-path.{c, h} to test for executables in $PATH
[notmuch-archives.git] / 83 / ebda68b232d1fad69fdefb266199e2af528e03
1 Return-Path: <novalazy@gmail.com>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id 86A9E431FBC\r
6         for <notmuch@notmuchmail.org>; Tue,  2 Apr 2013 15:42:33 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.099\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.099 tagged_above=-999 required=5\r
12         tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
13         FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001] autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id UUxSU7q4fmU7 for <notmuch@notmuchmail.org>;\r
17         Tue,  2 Apr 2013 15:42:30 -0700 (PDT)\r
18 Received: from mail-pd0-f177.google.com (mail-pd0-f177.google.com\r
19         [209.85.192.177]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
20         (No client certificate requested)\r
21         by olra.theworths.org (Postfix) with ESMTPS id 27364431FC2\r
22         for <notmuch@notmuchmail.org>; Tue,  2 Apr 2013 15:42:27 -0700 (PDT)\r
23 Received: by mail-pd0-f177.google.com with SMTP id u11so486031pdi.22\r
24         for <notmuch@notmuchmail.org>; Tue, 02 Apr 2013 15:42:26 -0700 (PDT)\r
25 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
26         h=x-received:from:to:cc:subject:date:message-id:x-mailer:in-reply-to\r
27         :references:mime-version:content-type:content-transfer-encoding;\r
28         bh=zRs7R1aE5gXB3AGJHSoTAdxGp1TZIs9MfQ8M+Dfl5ME=;\r
29         b=iQ3l+DDrH43HhMZQjDL9eg6+diKY7377MnHRyjXW8tDkB6NKTaQQWlAAINC//TRlhZ\r
30         UpYcZKAHZ2/a4gORlxLyMRnd2B4fm3grSXH4Z9AYqHpwd28szV29JC/BqIr3/r8MD6mp\r
31         sWCpCSoovzrQgRmmoSCZKteJ+Ng7R4H6yLpaIv56YeRAN/zxezU6JVmW3LmxygDoku5+\r
32         bU35+5b4ke9wR7ARA+0DoxubGHe8DN9VHwWrucM4g6K2oYxSz1MWViI267qxT9wQoekU\r
33         eSzGGENrNftUgFcVYl3jg5ywkFhlRVftjgpVXTH1yCvORQDwTesznPihHM6FZu78gG4L\r
34         KaEw==\r
35 X-Received: by 10.68.170.98 with SMTP id al2mr27124662pbc.87.1364942546332;\r
36         Tue, 02 Apr 2013 15:42:26 -0700 (PDT)\r
37 Received: from localhost (215.42.233.220.static.exetel.com.au.\r
38         [220.233.42.215])\r
39         by mx.google.com with ESMTPS id gn2sm3257803pbb.22.2013.04.02.15.42.22\r
40         (version=TLSv1.2 cipher=RC4-SHA bits=128/128);\r
41         Tue, 02 Apr 2013 15:42:25 -0700 (PDT)\r
42 From: Peter Wang <novalazy@gmail.com>\r
43 To: notmuch@notmuchmail.org\r
44 Subject: [PATCH v5 03/12] cli: add insert command\r
45 Date: Wed,  3 Apr 2013 09:41:48 +1100\r
46 Message-Id: <1364942517-6982-4-git-send-email-novalazy@gmail.com>\r
47 X-Mailer: git-send-email 1.7.12.1\r
48 In-Reply-To: <1364942517-6982-1-git-send-email-novalazy@gmail.com>\r
49 References: <1364942517-6982-1-git-send-email-novalazy@gmail.com>\r
50 MIME-Version: 1.0\r
51 Content-Type: text/plain; charset=UTF-8\r
52 Content-Transfer-Encoding: 8bit\r
53 X-BeenThere: notmuch@notmuchmail.org\r
54 X-Mailman-Version: 2.1.13\r
55 Precedence: list\r
56 List-Id: "Use and development of the notmuch mail system."\r
57         <notmuch.notmuchmail.org>\r
58 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
59         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
60 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
61 List-Post: <mailto:notmuch@notmuchmail.org>\r
62 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
63 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
64         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
65 X-List-Received-Date: Tue, 02 Apr 2013 22:42:33 -0000\r
66 \r
67 The notmuch insert command reads a message from standard input,\r
68 writes it to a Maildir folder, and then incorporates the message into\r
69 the notmuch database.  Essentially it moves the functionality of\r
70 notmuch-deliver into notmuch.\r
71 \r
72 Though it could be used as an alternative to notmuch new, the reason\r
73 I want this is to allow my notmuch frontend to add postponed or sent\r
74 messages to the mail store and notmuch database, without resorting to\r
75 another tool (e.g. notmuch-deliver) nor directly modifying the maildir.\r
76 ---\r
77  Makefile.local   |   1 +\r
78  notmuch-client.h |   3 +\r
79  notmuch-insert.c | 336 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
80  notmuch.c        |   3 +\r
81  4 files changed, 343 insertions(+)\r
82  create mode 100644 notmuch-insert.c\r
83 \r
84 diff --git a/Makefile.local b/Makefile.local\r
85 index c274f07..bb2381d 100644\r
86 --- a/Makefile.local\r
87 +++ b/Makefile.local\r
88 @@ -261,6 +261,7 @@ notmuch_client_srcs =               \\r
89         notmuch-config.c        \\r
90         notmuch-count.c         \\r
91         notmuch-dump.c          \\r
92 +       notmuch-insert.c        \\r
93         notmuch-new.c           \\r
94         notmuch-reply.c         \\r
95         notmuch-restore.c       \\r
96 diff --git a/notmuch-client.h b/notmuch-client.h\r
97 index 45749a6..da332f3 100644\r
98 --- a/notmuch-client.h\r
99 +++ b/notmuch-client.h\r
100 @@ -180,6 +180,9 @@ int\r
101  notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);\r
102  \r
103  int\r
104 +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);\r
105 +\r
106 +int\r
107  notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);\r
108  \r
109  int\r
110 diff --git a/notmuch-insert.c b/notmuch-insert.c\r
111 new file mode 100644\r
112 index 0000000..19b1cf9\r
113 --- /dev/null\r
114 +++ b/notmuch-insert.c\r
115 @@ -0,0 +1,336 @@\r
116 +/* notmuch - Not much of an email program, (just index and search)\r
117 + *\r
118 + * Copyright © 2013 Peter Wang\r
119 + *\r
120 + * Based in part on notmuch-deliver\r
121 + * Copyright © 2010 Ali Polatel\r
122 + *\r
123 + * This program is free software: you can redistribute it and/or modify\r
124 + * it under the terms of the GNU General Public License as published by\r
125 + * the Free Software Foundation, either version 3 of the License, or\r
126 + * (at your option) any later version.\r
127 + *\r
128 + * This program is distributed in the hope that it will be useful,\r
129 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
130 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
131 + * GNU General Public License for more details.\r
132 + *\r
133 + * You should have received a copy of the GNU General Public License\r
134 + * along with this program.  If not, see http://www.gnu.org/licenses/ .\r
135 + *\r
136 + * Author: Peter Wang <novalazy@gmail.com>\r
137 + */\r
138 +\r
139 +#include "notmuch-client.h"\r
140 +#include "tag-util.h"\r
141 +\r
142 +#include <sys/types.h>\r
143 +#include <sys/stat.h>\r
144 +#include <fcntl.h>\r
145 +\r
146 +static volatile sig_atomic_t interrupted;\r
147 +\r
148 +static void\r
149 +handle_sigint (unused (int sig))\r
150 +{\r
151 +    static char msg[] = "Stopping...         \n";\r
152 +\r
153 +    /* This write is "opportunistic", so it's okay to ignore the\r
154 +     * result.  It is not required for correctness, and if it does\r
155 +     * fail or produce a short write, we want to get out of the signal\r
156 +     * handler as quickly as possible, not retry it. */\r
157 +    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));\r
158 +    interrupted = 1;\r
159 +}\r
160 +\r
161 +/* Like gethostname but guarantees that a null-terminated hostname is\r
162 + * returned, even if it has to make one up. Invalid characters are\r
163 + * substituted such that the hostname can be used within a filename.\r
164 + */\r
165 +static void\r
166 +safe_gethostname (char *hostname, size_t len)\r
167 +{\r
168 +    char *p;\r
169 +\r
170 +    if (gethostname (hostname, len) == -1) {\r
171 +       strncpy (hostname, "unknown", len);\r
172 +    }\r
173 +    hostname[len - 1] = '\0';\r
174 +\r
175 +    for (p = hostname; *p != '\0'; p++) {\r
176 +       if (*p == '/' || *p == ':')\r
177 +           *p = '_';\r
178 +    }\r
179 +}\r
180 +\r
181 +/* Call fsync() on a directory path. */\r
182 +static notmuch_bool_t\r
183 +sync_dir (const char *dir)\r
184 +{\r
185 +    notmuch_bool_t ret;\r
186 +    int fd;\r
187 +\r
188 +    fd = open (dir, O_RDONLY);\r
189 +    if (fd == -1) {\r
190 +       fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));\r
191 +       return FALSE;\r
192 +    }\r
193 +    ret = (fsync (fd) == 0);\r
194 +    if (! ret) {\r
195 +       fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));\r
196 +    }\r
197 +    close (fd);\r
198 +    return ret;\r
199 +}\r
200 +\r
201 +/* Open a unique file in the Maildir 'tmp' directory.\r
202 + * Returns the file descriptor on success, or -1 on failure.\r
203 + * On success, file paths for the message in the 'tmp' and 'new'\r
204 + * directories are returned via tmppath and newpath,\r
205 + * and the path of the 'new' directory itself in newdir. */\r
206 +static int\r
207 +maildir_open_tmp_file (void *ctx, const char *dir,\r
208 +                      char **tmppath, char **newpath, char **newdir)\r
209 +{\r
210 +    pid_t pid;\r
211 +    char hostname[256];\r
212 +    struct timeval tv;\r
213 +    char *filename;\r
214 +    int fd = -1;\r
215 +\r
216 +    /* We follow the Dovecot file name generation algorithm. */\r
217 +    pid = getpid ();\r
218 +    safe_gethostname (hostname, sizeof (hostname));\r
219 +    do {\r
220 +       gettimeofday (&tv, NULL);\r
221 +       filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",\r
222 +                                   tv.tv_sec, tv.tv_usec, pid, hostname);\r
223 +       if (! filename) {\r
224 +           fprintf (stderr, "Out of memory\n");\r
225 +           return -1;\r
226 +       }\r
227 +\r
228 +       *tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);\r
229 +       if (! *tmppath) {\r
230 +           fprintf (stderr, "Out of memory\n");\r
231 +           return -1;\r
232 +       }\r
233 +\r
234 +       fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);\r
235 +    } while (fd == -1 && errno == EEXIST);\r
236 +\r
237 +    if (fd == -1) {\r
238 +       fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));\r
239 +       return -1;\r
240 +    }\r
241 +\r
242 +    *newdir = talloc_asprintf (ctx, "%s/new", dir);\r
243 +    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);\r
244 +    if (! *newdir || ! *newpath) {\r
245 +       fprintf (stderr, "Out of memory\n");\r
246 +       close (fd);\r
247 +       unlink (*tmppath);\r
248 +       return -1;\r
249 +    }\r
250 +\r
251 +    talloc_free (filename);\r
252 +\r
253 +    return fd;\r
254 +}\r
255 +\r
256 +/* Copy the contents of standard input (fdin) into fdout. */\r
257 +static notmuch_bool_t\r
258 +copy_stdin (int fdin, int fdout)\r
259 +{\r
260 +    char buf[4096];\r
261 +    char *p;\r
262 +    ssize_t remain;\r
263 +    ssize_t written;\r
264 +\r
265 +    while (! interrupted) {\r
266 +       remain = read (fdin, buf, sizeof (buf));\r
267 +       if (remain == 0)\r
268 +           break;\r
269 +       if (remain < 0) {\r
270 +           if (errno == EINTR)\r
271 +               continue;\r
272 +           fprintf (stderr, "Error: reading from standard input: %s\n",\r
273 +                    strerror (errno));\r
274 +           return FALSE;\r
275 +       }\r
276 +\r
277 +       p = buf;\r
278 +       do {\r
279 +           written = write (fdout, p, remain);\r
280 +           if (written < 0 && errno == EINTR)\r
281 +               continue;\r
282 +           if (written <= 0) {\r
283 +               fprintf (stderr, "Error: writing to temporary file: %s",\r
284 +                        strerror (errno));\r
285 +               return FALSE;\r
286 +           }\r
287 +           p += written;\r
288 +           remain -= written;\r
289 +       } while (remain > 0);\r
290 +    }\r
291 +\r
292 +    return ! interrupted;\r
293 +}\r
294 +\r
295 +/* Add the specified message file to the notmuch database, applying tags.\r
296 + * The file is renamed to encode notmuch tags as maildir flags. */\r
297 +static notmuch_bool_t\r
298 +add_file_to_database (notmuch_database_t *notmuch, const char *path,\r
299 +                     tag_op_list_t *tag_ops)\r
300 +{\r
301 +    notmuch_message_t *message;\r
302 +    notmuch_status_t status;\r
303 +\r
304 +    status = notmuch_database_add_message (notmuch, path, &message);\r
305 +    switch (status) {\r
306 +    case NOTMUCH_STATUS_SUCCESS:\r
307 +    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
308 +       break;\r
309 +    default:\r
310 +    case NOTMUCH_STATUS_FILE_NOT_EMAIL:\r
311 +    case NOTMUCH_STATUS_READ_ONLY_DATABASE:\r
312 +    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:\r
313 +    case NOTMUCH_STATUS_OUT_OF_MEMORY:\r
314 +    case NOTMUCH_STATUS_FILE_ERROR:\r
315 +    case NOTMUCH_STATUS_NULL_POINTER:\r
316 +    case NOTMUCH_STATUS_TAG_TOO_LONG:\r
317 +    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:\r
318 +    case NOTMUCH_STATUS_UNBALANCED_ATOMIC:\r
319 +    case NOTMUCH_STATUS_LAST_STATUS:\r
320 +       fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",\r
321 +                path, notmuch_status_to_string (status));\r
322 +       return FALSE;\r
323 +    }\r
324 +\r
325 +    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {\r
326 +       /* Don't change tags of an existing message. */\r
327 +       status = notmuch_message_tags_to_maildir_flags (message);\r
328 +       if (status != NOTMUCH_STATUS_SUCCESS)\r
329 +           fprintf (stderr, "Error: failed to sync tags to maildir flags\n");\r
330 +    } else {\r
331 +       status = tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);\r
332 +    }\r
333 +\r
334 +    notmuch_message_destroy (message);\r
335 +\r
336 +    return (status == NOTMUCH_STATUS_SUCCESS);\r
337 +}\r
338 +\r
339 +static notmuch_bool_t\r
340 +insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,\r
341 +               const char *dir, tag_op_list_t *tag_ops)\r
342 +{\r
343 +    char *tmppath;\r
344 +    char *newpath;\r
345 +    char *newdir;\r
346 +    int fdout;\r
347 +    char *cleanup_path;\r
348 +\r
349 +    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);\r
350 +    if (fdout < 0)\r
351 +       return FALSE;\r
352 +\r
353 +    cleanup_path = tmppath;\r
354 +\r
355 +    if (! copy_stdin (fdin, fdout))\r
356 +       goto FAIL;\r
357 +\r
358 +    if (fsync (fdout) != 0) {\r
359 +       fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));\r
360 +       goto FAIL;\r
361 +    }\r
362 +\r
363 +    close (fdout);\r
364 +    fdout = -1;\r
365 +\r
366 +    /* Atomically move the new message file from the Maildir 'tmp' directory\r
367 +     * to the 'new' directory.  We follow the Dovecot recommendation to\r
368 +     * simply use rename() instead of link() and unlink().\r
369 +     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery\r
370 +     */\r
371 +    if (rename (tmppath, newpath) != 0) {\r
372 +       fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));\r
373 +       goto FAIL;\r
374 +    }\r
375 +\r
376 +    cleanup_path = newpath;\r
377 +\r
378 +    if (! add_file_to_database (notmuch, newpath, tag_ops)) {\r
379 +       /* XXX add an option to keep the file in maildir? */\r
380 +       goto FAIL;\r
381 +    }\r
382 +\r
383 +    if (! sync_dir (newdir))\r
384 +       goto FAIL;\r
385 +\r
386 +    return TRUE;\r
387 +\r
388 +  FAIL:\r
389 +    if (fdout >= 0)\r
390 +       close (fdout);\r
391 +    unlink (cleanup_path);\r
392 +    return FALSE;\r
393 +}\r
394 +\r
395 +int\r
396 +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])\r
397 +{\r
398 +    notmuch_database_t *notmuch;\r
399 +    struct sigaction action;\r
400 +    const char *db_path;\r
401 +    const char **new_tags;\r
402 +    size_t new_tags_length;\r
403 +    tag_op_list_t *tag_ops;\r
404 +    char *query_string = NULL;\r
405 +    const char *maildir;\r
406 +    int opt_index = 1;\r
407 +    unsigned int i;\r
408 +    notmuch_bool_t ret;\r
409 +\r
410 +    db_path = notmuch_config_get_database_path (config);\r
411 +    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);\r
412 +\r
413 +    tag_ops = tag_op_list_create (config);\r
414 +    if (tag_ops == NULL) {\r
415 +       fprintf (stderr, "Out of memory.\n");\r
416 +       return 1;\r
417 +    }\r
418 +    for (i = 0; i < new_tags_length; i++) {\r
419 +       if (tag_op_list_append (tag_ops, new_tags[i], FALSE))\r
420 +           return 1;\r
421 +    }\r
422 +\r
423 +    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,\r
424 +                               &query_string, tag_ops))\r
425 +       return 1;\r
426 +\r
427 +    if (*query_string != '\0') {\r
428 +       fprintf (stderr, "Error: unexpected query string: %s\n", query_string);\r
429 +       return 1;\r
430 +    }\r
431 +\r
432 +    maildir = db_path;\r
433 +\r
434 +    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying\r
435 +     * from standard input may be interrupted. */\r
436 +    memset (&action, 0, sizeof (struct sigaction));\r
437 +    action.sa_handler = handle_sigint;\r
438 +    sigemptyset (&action.sa_mask);\r
439 +    action.sa_flags = 0;\r
440 +    sigaction (SIGINT, &action, NULL);\r
441 +\r
442 +    if (notmuch_database_open (notmuch_config_get_database_path (config),\r
443 +                              NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))\r
444 +       return 1;\r
445 +\r
446 +    ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);\r
447 +\r
448 +    notmuch_database_destroy (notmuch);\r
449 +\r
450 +    return (ret) ? 0 : 1;\r
451 +}\r
452 diff --git a/notmuch.c b/notmuch.c\r
453 index f51a84f..1a44f11 100644\r
454 --- a/notmuch.c\r
455 +++ b/notmuch.c\r
456 @@ -48,6 +48,9 @@ static command_t commands[] = {\r
457      { "new", notmuch_new_command, FALSE,\r
458        "[options...]",\r
459        "Find and import new messages to the notmuch database." },\r
460 +    { "insert", notmuch_insert_command, FALSE,\r
461 +      "[options...] [--] [+<tag>|-<tag> ...] < message",\r
462 +      "Add a new message into the maildir and notmuch database." },\r
463      { "search", notmuch_search_command, FALSE,\r
464        "[options...] <search-terms> [...]",\r
465        "Search for messages matching the given search terms." },\r
466 -- \r
467 1.7.12.1\r
468 \r