1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-movemail.c: mbox copying function */
6 * Dan Winship <danw@helixcode.com>
8 * Copyright 2000 Helix Code, Inc. (http://www.helixcode.com)
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of the
13 * License, or (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
39 #include "camel-movemail.h"
40 #include "camel-exception.h"
42 #include "camel-mime-parser.h"
43 #include "camel-mime-filter.h"
44 #include "camel-mime-filter-from.h"
51 static void movemail_external (const char *source, const char *dest,
55 /* these could probably be exposed as a utility? (but only mbox needs it) */
56 static int camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter);
57 static int camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes);
60 * camel_movemail: Copy an mbox file from a shared spool directory to a
61 * new folder in a Camel store
62 * @source: source file
63 * @dest: destination file
64 * @ex: a CamelException
66 * This copies an mbox file from a shared directory with multiple
67 * readers and writers into a private (presumably Camel-controlled)
68 * directory. Dot locking is used on the source file (but not the
72 camel_movemail (const char *source, const char *dest, CamelException *ex)
76 char *locktmpfile, *lockfile;
82 camel_exception_clear (ex);
84 /* Stat and then open the spool file. If it doesn't exist or
85 * is empty, the user has no mail. (There's technically a race
86 * condition here in that an MDA might have just now locked it
87 * to deliver a message, but we don't care. In that case,
88 * assuming it's unlocked is equivalent to pretending we were
89 * called a fraction earlier.)
91 if (stat (source, &st) == -1) {
92 if (errno != ENOENT) {
93 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
94 "Could not check mail file "
103 /* Create the unique lock file. */
104 locktmpfile = g_strdup_printf ("%s.lock.XXXXXX", source);
106 tmpfd = mkstemp (locktmpfile);
108 if (mktemp (locktmpfile)) {
109 tmpfd = open (locktmpfile, O_RDWR | O_CREAT | O_EXCL,
115 g_free (locktmpfile);
117 if (errno == EACCES) {
118 /* movemail_external will fail if the dest file
119 * already exists, so if it does, return now,
120 * let the fetch code process the mail that's
121 * already there, and then the user can try again.
123 if (stat (dest, &st) == 0)
126 movemail_external (source, dest, ex);
130 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
131 "Could not create lock file "
132 "for %s: %s", source, g_strerror (errno));
137 sfd = open (source, O_RDWR);
139 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
140 "Could not open mail file %s: %s",
141 source, g_strerror (errno));
142 unlink (locktmpfile);
143 g_free (locktmpfile);
147 dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
149 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
150 "Could not open temporary mail "
151 "file %s: %s", dest, g_strerror (errno));
153 unlink (locktmpfile);
154 g_free (locktmpfile);
158 lockfile = g_strdup_printf ("%s.lock", source);
163 /* Loop trying to lock the file for 30 seconds. */
164 while (time (&now) < timeout) {
165 /* Try to make the lock. */
166 if (symlink (locktmpfile, lockfile) == 0) {
171 /* If we fail for a reason other than that someone
172 * else has the lock, then abort.
174 if (errno != EEXIST) {
175 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
176 "Could not create lock "
177 "file for %s: %s", source,
182 /* Check the modtime on the lock file. */
183 if (stat (lockfile, &st) == -1) {
184 /* If the lockfile disappeared, try again. */
188 /* Some other error. Abort. */
189 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
190 "Could not test lock "
191 "file for %s: %s", source,
196 /* If the lock file is stale, remove it and try again. */
197 if (st.st_mtime < now - 60) {
202 /* Otherwise, sleep and try again. */
207 /* Something has gone awry. */
208 if (now >= timeout) {
209 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
210 "Timed out trying to get "
211 "lock file on %s. Try again "
215 unlink (locktmpfile);
216 g_free (locktmpfile);
222 /* OK. We have the file locked now. */
224 /* FIXME: Set a timer to keep the file locked. */
229 nread = read (sfd, buf, sizeof (buf));
232 else if (nread == -1) {
235 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
236 "Error reading mail file: %s",
242 nwrote = write (dfd, buf + written, nread);
245 continue; /* continues inner loop */
246 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
248 "mail temp file: %s",
257 /* If no errors occurred copying the data, and we successfully
258 * close the destination file, then truncate the source file.
260 if (!camel_exception_is_set (ex)) {
261 if (close (dfd) == 0)
264 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
265 "Failed to store mail in "
266 "temp file %s: %s", dest,
273 /* Clean up lock files. */
276 unlink (locktmpfile);
277 g_free (locktmpfile);
282 movemail_external (const char *source, const char *dest, CamelException *ex)
284 sigset_t mask, omask;
286 int fd[2], len = 0, nread, status;
287 char buf[BUFSIZ], *output = NULL;
289 /* Block SIGCHLD so the app can't mess us up. */
291 sigaddset (&mask, SIGCHLD);
292 sigprocmask (SIG_BLOCK, &mask, &omask);
294 if (pipe (fd) == -1) {
295 sigprocmask (SIG_SETMASK, &omask, NULL);
296 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
297 "Could not create pipe: %s",
307 sigprocmask (SIG_SETMASK, &omask, NULL);
308 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
309 "Could not fork: %s",
316 close (STDIN_FILENO);
317 dup2 (fd[1], STDOUT_FILENO);
318 dup2 (fd[1], STDERR_FILENO);
320 execl (MOVEMAIL_PATH, MOVEMAIL_PATH, source, dest, NULL);
331 /* Read movemail's output. */
332 while ((nread = read (fd[0], buf, sizeof (buf))) > 0) {
333 output = g_realloc (output, len + nread + 1);
334 memcpy (output + len, buf, nread);
340 /* Now get the exit status. */
341 while (waitpid (pid, &status, 0) == -1 && errno == EINTR)
343 sigprocmask (SIG_SETMASK, &omask, NULL);
345 if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
346 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
347 "Movemail program failed: %s",
348 output ? output : "(Unknown error)");
356 camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes)
361 d(printf("writing %d bytes ... ", bytes));
363 if (lseek(fromfd, start, SEEK_SET) != start)
375 towrite = read(fromfd, buffer, toread);
376 } while (towrite == -1 && errno == EINTR);
381 /* check for 'end of file' */
383 d(printf("end of file?\n"));
388 toread = write(tofd, buffer, towrite);
389 } while (toread == -1 && errno == EINTR);
398 d(printf("written %d bytes\n", written));
403 #define PRE_SIZE (32)
406 camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter)
408 char buffer[4096+PRE_SIZE];
411 int filterlen, filterpre;
413 d(printf("writing %d bytes ... ", bytes));
415 camel_mime_filter_reset(filter);
417 if (lseek(fromfd, start, SEEK_SET) != start)
429 towrite = read(fromfd, buffer+PRE_SIZE, toread);
430 } while (towrite == -1 && errno == EINTR);
435 /* check for 'end of file' */
437 d(printf("end of file?\n"));
438 camel_mime_filter_complete(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
439 &filterbuffer, &filterlen, &filterpre);
444 camel_mime_filter_filter(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
445 &filterbuffer, &filterlen, &filterpre);
450 toread = write(tofd, filterbuffer, towrite);
451 } while (toread == -1 && errno == EINTR);
460 d(printf("written %d bytes\n", written));
465 /* write the headers back out again, but not he Content-Length header, because we dont
466 want to maintain it! */
468 solaris_header_write(int fd, struct _header_raw *header)
473 iv[1].iov_base = ":";
475 iv[3].iov_base = "\n";
479 if (strcasecmp(header->name, "Content-Length")) {
480 iv[0].iov_base = header->name;
481 iv[0].iov_len = strlen(header->name);
482 iv[2].iov_base = header->value;
483 iv[2].iov_len = strlen(header->value);
486 len = writev(fd, iv, 4);
487 } while (len == -1 && errno == EINTR);
493 header = header->next;
497 len = write(fd, "\n", 1);
498 } while (len == -1 && errno == EINTR);
505 d(printf("Wrote %d bytes of headers\n", outlen));
510 /* Well, since Solaris is a tad broken wrt its 'mbox' folder format,
511 we must convert it to a real mbox format. Thankfully this is
512 mostly pretty easy */
514 camel_movemail_solaris (int sfd, int dfd, CamelException *ex)
519 CamelMimeFilterFrom *ffrom;
522 mp = camel_mime_parser_new();
523 camel_mime_parser_scan_from(mp, TRUE);
524 camel_mime_parser_init_with_fd(mp, sfd);
526 ffrom = camel_mime_filter_from_new();
528 while (camel_mime_parser_step(mp, &buffer, &len) == HSCAN_FROM) {
529 if (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_FROM_END) {
537 start = camel_mime_parser_tell_start_from(mp);
538 body = camel_mime_parser_tell(mp);
540 /* write out headers, but NOT content-length header */
541 solaris_header_write(dfd, camel_mime_parser_headers_raw(mp));
543 cl = camel_mime_parser_header(mp, "content-length", NULL);
545 g_warning("Required Content-Length header is missing from solaris mail box @ %d", (int)camel_mime_parser_tell(mp));
546 camel_mime_parser_drop_step(mp);
547 camel_mime_parser_drop_step(mp);
548 camel_mime_parser_step(mp, &buffer, &len);
549 camel_mime_parser_unstep(mp);
550 length = camel_mime_parser_tell_start_from(mp) - body;
554 camel_mime_parser_drop_step(mp);
555 camel_mime_parser_drop_step(mp);
556 newpos = length+body;
558 /* copy body->length converting From lines */
559 if (camel_movemail_copy_filter(sfd, dfd, body, length, (CamelMimeFilter *)ffrom) == -1)
562 camel_mime_parser_seek(mp, newpos, SEEK_SET);
564 g_error("Inalid parser state: %d", camel_mime_parser_state(mp));
568 camel_object_unref((CamelObject *)mp);
569 camel_object_unref((CamelObject *)ffrom);
574 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
576 "mail temp file: %s",
580 camel_object_unref((CamelObject *)mp);
581 camel_object_unref((CamelObject *)ffrom);