/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-movemail.c: mbox copying function */
/*
* Author:
* Dan Winship <danw@helixcode.com>
*
* Copyright 2000 Helix Code, Inc. (http://www.helixcode.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <config.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include "camel-movemail.h"
#include "camel-exception.h"
/**
* camel_movemail: Copy an mbox file from a shared spool directory to a
* new folder in a Camel store
* @source: source file
* @dest: destination file
* @ex: a CamelException
*
* This copies an mbox file from a shared directory with multiple
* readers and writers into a private (presumably Camel-controlled)
* directory. Dot locking is used on the source file (but not the
* destination).
*
* Return value: 1 if mail was copied, 0 if the source file contained
* no mail, -1 if an error occurred.
**/
int
camel_movemail (const char *source, const char *dest, CamelException *ex)
{
gboolean locked, error;
int sfd, dfd, tmpfd;
char *locktmpfile, *lockfile;
struct stat st;
time_t now, timeout;
int nread, nwrote;
char buf[BUFSIZ];
camel_exception_clear (ex);
/* Stat and then open the spool file. If it doesn't exist or
* is empty, the user has no mail. (There's technically a race
* condition here in that an MDA might have just now locked it
* to deliver a message, but we don't care. In that case,
* assuming it's unlocked is equivalent to pretending we were
* called a fraction earlier.)
*/
if (stat (source, &st) == -1) {
if (errno == ENOENT)
return 0;
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not check mail file %s: %s",
source, g_strerror (errno));
return -1;
}
if (st.st_size == 0)
return 0;
sfd = open (source, O_RDWR);
if (sfd == -1) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not open mail file %s: %s",
source, g_strerror (errno));
return -1;
}
dfd = open (dest, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (dfd == -1) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not open temporary mail "
"file %s: %s", dest, g_strerror (errno));
close (sfd);
return -1;
}
/* Create the unique lock file. */
locktmpfile = g_strdup_printf ("%s.lock.XXXXXX", source);
#ifdef HAVE_MKSTEMP
tmpfd = mkstemp (locktmpfile);
#else
if (mktemp (locktmpfile)) {
tmpfd = open (locktmpfile, O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR);
} else
tmpfd = -1;
#endif
if (tmpfd == -1) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not create lock file "
"for %s: %s", source, g_strerror (errno));
close (sfd);
close (dfd);
unlink (dest);
return -1;
}
close (tmpfd);
lockfile = g_strdup_printf ("%s.lock", source);
locked = FALSE;
time (&timeout);
timeout += 30;
/* Loop trying to lock the file for 30 seconds. */
while (time (&now) < timeout) {
/* Try to make the lock. */
if (symlink (locktmpfile, lockfile) == 0) {
locked = TRUE;
break;
}
/* If we fail for a reason other than that someone
* else has the lock, then abort.
*/
if (errno != EEXIST) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not create lock "
"file for %s: %s", source,
g_strerror (errno));
break;
}
/* Check the modtime on the lock file. */
if (stat (lockfile, &st) == -1) {
/* If the lockfile disappeared, try again. */
if (errno == ENOENT)
continue;
/* Some other error. Abort. */
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Could not test lock "
"file for %s: %s", source,
g_strerror (errno));
break;
}
/* If the lock file is stale, remove it and try again. */
if (st.st_mtime < now - 60) {
unlink (lockfile);
continue;
}
/* Otherwise, sleep and try again. */
sleep (5);
}
if (!locked) {
/* Something has gone awry. */
if (now >= timeout) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Timed out trying to get "
"lock file on %s. Try again "
"later.", source);
}
g_free (lockfile);
unlink (locktmpfile);
g_free (locktmpfile);
close (sfd);
close (dfd);
unlink (dest);
return -1;
}
/* OK. We have the file locked now. */
/* FIXME: Set a timer to keep the file locked. */
error = FALSE;
while (1) {
int written = 0;
nread = read (sfd, buf, sizeof (buf));
if (nread == 0)
break;
else if (nread == -1) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Error reading mail file: %s",
g_strerror (errno));
error = TRUE;
break;
}
while (nread) {
nwrote = write (dfd, buf + written, nread);
if (nwrote == -1) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Error writing "
"mail temp file: %s",
g_strerror (errno));
error = TRUE;
break;
}
written += nwrote;
nread -= nwrote;
}
}
/* If no errors occurred copying the data, and we successfully
* close the destination file, then truncate the source file.
* If there is some sort of error, delete the destination file.
*/
if (!error) {
if (close (dfd) == 0)
ftruncate (sfd, 0);
else {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
"Failed to store mail in "
"temp file %s: %s", dest,
g_strerror (errno));
unlink (dest);
error = TRUE;
}
} else {
close (dfd);
unlink (dest);
}
close (sfd);
/* Clean up lock files. */
unlink (lockfile);
g_free (lockfile);
unlink (locktmpfile);
g_free (locktmpfile);
return error ? -1 : 1;
}