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
40 #include "camel-movemail.h"
41 #include "camel-exception.h"
43 #include "camel-mime-parser.h"
44 #include "camel-mime-filter.h"
45 #include "camel-mime-filter-from.h"
52 static void movemail_external (const char *source, const char *dest,
56 /* these could probably be exposed as a utility? (but only mbox needs it) */
58 static int camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter);
59 static int camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes);
63 * camel_movemail: Copy an mbox file from a shared spool directory to a
64 * new folder in a Camel store
65 * @source: source file
66 * @dest: destination file
67 * @ex: a CamelException
69 * This copies an mbox file from a shared directory with multiple
70 * readers and writers into a private (presumably Camel-controlled)
71 * directory. Dot locking is used on the source file (but not the
75 camel_movemail (const char *source, const char *dest, CamelException *ex)
79 char *locktmpfile, *lockfile;
85 camel_exception_clear (ex);
87 /* Stat and then open the spool file. If it doesn't exist or
88 * is empty, the user has no mail. (There's technically a race
89 * condition here in that an MDA might have just now locked it
90 * to deliver a message, but we don't care. In that case,
91 * assuming it's unlocked is equivalent to pretending we were
92 * called a fraction earlier.)
94 if (stat (source, &st) == -1) {
95 if (errno != ENOENT) {
96 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
97 _("Could not check mail file "
106 /* Create the unique lock file. */
107 locktmpfile = g_strdup_printf ("%s.lock.XXXXXX", source);
109 tmpfd = mkstemp (locktmpfile);
111 if (mktemp (locktmpfile)) {
112 tmpfd = open (locktmpfile, O_RDWR | O_CREAT | O_EXCL,
118 g_free (locktmpfile);
120 if (errno == EACCES) {
121 /* movemail_external will fail if the dest file
122 * already exists, so if it does, return now,
123 * let the fetch code process the mail that's
124 * already there, and then the user can try again.
126 if (stat (dest, &st) == 0)
129 movemail_external (source, dest, ex);
133 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
134 _("Could not create lock file "
135 "for %s: %s"), source,
141 sfd = open (source, O_RDWR);
143 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
144 _("Could not open mail file %s: %s"),
145 source, g_strerror (errno));
146 unlink (locktmpfile);
147 g_free (locktmpfile);
151 dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
153 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
154 _("Could not open temporary mail "
155 "file %s: %s"), dest,
158 unlink (locktmpfile);
159 g_free (locktmpfile);
163 lockfile = g_strdup_printf ("%s.lock", source);
168 /* Loop trying to lock the file for 30 seconds. */
169 while (time (&now) < timeout) {
170 /* Try to make the lock. */
171 if (symlink (locktmpfile, lockfile) == 0) {
176 /* If we fail for a reason other than that someone
177 * else has the lock, then abort.
179 if (errno != EEXIST) {
180 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
181 _("Could not create lock "
182 "file for %s: %s"), source,
187 /* Check the modtime on the lock file. */
188 if (stat (lockfile, &st) == -1) {
189 /* If the lockfile disappeared, try again. */
193 /* Some other error. Abort. */
194 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
195 _("Could not test lock "
196 "file for %s: %s"), source,
201 /* If the lock file is stale, remove it and try again. */
202 if (st.st_mtime < now - 60) {
207 /* Otherwise, sleep and try again. */
212 /* Something has gone awry. */
213 if (now >= timeout) {
214 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
215 _("Timed out trying to get "
216 "lock file on %s. Try again "
220 unlink (locktmpfile);
221 g_free (locktmpfile);
227 /* OK. We have the file locked now. */
229 /* FIXME: Set a timer to keep the file locked. */
234 nread = read (sfd, buf, sizeof (buf));
237 else if (nread == -1) {
240 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
241 _("Error reading mail file: %s"),
247 nwrote = write (dfd, buf + written, nread);
250 continue; /* continues inner loop */
251 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
253 "mail temp file: %s"),
262 /* If no errors occurred copying the data, and we successfully
263 * close the destination file, then truncate the source file.
265 if (!camel_exception_is_set (ex)) {
266 if (close (dfd) == 0)
269 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
270 _("Failed to store mail in "
271 "temp file %s: %s"), dest,
278 /* Clean up lock files. */
281 unlink (locktmpfile);
282 g_free (locktmpfile);
287 movemail_external (const char *source, const char *dest, CamelException *ex)
289 sigset_t mask, omask;
291 int fd[2], len = 0, nread, status;
292 char buf[BUFSIZ], *output = NULL;
294 /* Block SIGCHLD so the app can't mess us up. */
296 sigaddset (&mask, SIGCHLD);
297 sigprocmask (SIG_BLOCK, &mask, &omask);
299 if (pipe (fd) == -1) {
300 sigprocmask (SIG_SETMASK, &omask, NULL);
301 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
302 _("Could not create pipe: %s"),
312 sigprocmask (SIG_SETMASK, &omask, NULL);
313 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
314 _("Could not fork: %s"),
321 close (STDIN_FILENO);
322 dup2 (fd[1], STDOUT_FILENO);
323 dup2 (fd[1], STDERR_FILENO);
325 execl (MOVEMAIL_PATH, MOVEMAIL_PATH, source, dest, NULL);
336 /* Read movemail's output. */
337 while ((nread = read (fd[0], buf, sizeof (buf))) > 0) {
338 output = g_realloc (output, len + nread + 1);
339 memcpy (output + len, buf, nread);
345 /* Now get the exit status. */
346 while (waitpid (pid, &status, 0) == -1 && errno == EINTR)
348 sigprocmask (SIG_SETMASK, &omask, NULL);
350 if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
351 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
352 _("Movemail program failed: %s"),
353 output ? output : _("(Unknown error)"));
362 camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes)
367 d(printf("writing %d bytes ... ", bytes));
369 if (lseek(fromfd, start, SEEK_SET) != start)
381 towrite = read(fromfd, buffer, toread);
382 } while (towrite == -1 && errno == EINTR);
387 /* check for 'end of file' */
389 d(printf("end of file?\n"));
394 toread = write(tofd, buffer, towrite);
395 } while (toread == -1 && errno == EINTR);
404 d(printf("written %d bytes\n", written));
410 #define PRE_SIZE (32)
414 camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter)
416 char buffer[4096+PRE_SIZE];
419 int filterlen, filterpre;
421 d(printf("writing %d bytes ... ", bytes));
423 camel_mime_filter_reset(filter);
425 if (lseek(fromfd, start, SEEK_SET) != start)
437 towrite = read(fromfd, buffer+PRE_SIZE, toread);
438 } while (towrite == -1 && errno == EINTR);
443 /* check for 'end of file' */
445 d(printf("end of file?\n"));
446 camel_mime_filter_complete(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
447 &filterbuffer, &filterlen, &filterpre);
452 camel_mime_filter_filter(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
453 &filterbuffer, &filterlen, &filterpre);
458 toread = write(tofd, filterbuffer, towrite);
459 } while (toread == -1 && errno == EINTR);
468 d(printf("written %d bytes\n", written));
473 /* write the headers back out again, but not he Content-Length header, because we dont
474 want to maintain it! */
476 solaris_header_write(int fd, struct _header_raw *header)
481 iv[1].iov_base = ":";
483 iv[3].iov_base = "\n";
487 if (strcasecmp(header->name, "Content-Length")) {
488 iv[0].iov_base = header->name;
489 iv[0].iov_len = strlen(header->name);
490 iv[2].iov_base = header->value;
491 iv[2].iov_len = strlen(header->value);
494 len = writev(fd, iv, 4);
495 } while (len == -1 && errno == EINTR);
501 header = header->next;
505 len = write(fd, "\n", 1);
506 } while (len == -1 && errno == EINTR);
513 d(printf("Wrote %d bytes of headers\n", outlen));
518 /* Well, since Solaris is a tad broken wrt its 'mbox' folder format,
519 we must convert it to a real mbox format. Thankfully this is
520 mostly pretty easy */
522 camel_movemail_solaris (int sfd, int dfd, CamelException *ex)
527 CamelMimeFilterFrom *ffrom;
530 mp = camel_mime_parser_new();
531 camel_mime_parser_scan_from(mp, TRUE);
532 camel_mime_parser_init_with_fd(mp, sfd);
534 ffrom = camel_mime_filter_from_new();
536 while (camel_mime_parser_step(mp, &buffer, &len) == HSCAN_FROM) {
537 if (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_FROM_END) {
545 start = camel_mime_parser_tell_start_from(mp);
546 body = camel_mime_parser_tell(mp);
548 /* write out headers, but NOT content-length header */
549 solaris_header_write(dfd, camel_mime_parser_headers_raw(mp));
551 cl = camel_mime_parser_header(mp, "content-length", NULL);
553 g_warning("Required Content-Length header is missing from solaris mail box @ %d", (int)camel_mime_parser_tell(mp));
554 camel_mime_parser_drop_step(mp);
555 camel_mime_parser_drop_step(mp);
556 camel_mime_parser_step(mp, &buffer, &len);
557 camel_mime_parser_unstep(mp);
558 length = camel_mime_parser_tell_start_from(mp) - body;
562 camel_mime_parser_drop_step(mp);
563 camel_mime_parser_drop_step(mp);
564 newpos = length+body;
566 /* copy body->length converting From lines */
567 if (camel_movemail_copy_filter(sfd, dfd, body, length, (CamelMimeFilter *)ffrom) == -1)
570 camel_mime_parser_seek(mp, newpos, SEEK_SET);
572 g_error("Inalid parser state: %d", camel_mime_parser_state(mp));
576 camel_object_unref((CamelObject *)mp);
577 camel_object_unref((CamelObject *)ffrom);
582 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
584 "mail temp file: %s",
588 camel_object_unref((CamelObject *)mp);
589 camel_object_unref((CamelObject *)ffrom);