As discussed here and on IRC.
I have replaced the link() call in deliver-maildir.c by a call to a new
`safemove()' function.
This function move a file with link() + unlink() or if it fails on EXDEV by
using rename().
The patch is joined with this mail. I have tested it on AFS and ext3; the
regressions test suite successfully passed on OpenBSD 4.5.
Nicholas, I let you write the bits in the user manual about using fdm with
this
patch on AFS. I believe that it will be more clear if you do it.
Thanks a lot for your help about this.
I am open to any suggestion.
--
Louis Opter
diff -u fdm-1.5.orig/deliver-maildir.c fdm-1.5-3.debian.louis/deliver-maildir.c
--- fdm-1.5.orig/deliver-maildir.c 2008-03-06 10:25:32.000000000 +0100
+++ fdm-1.5-3.debian.louis/deliver-maildir.c 2009-07-25 16:30:52.127297406 +0200
@@ -190,7 +190,7 @@
log_debug2("%s: writing to %s", a->name, src);
n = write(fd, m->data, m->size);
if (n < 0 || (size_t) n != m->size || fsync(fd) != 0)
- goto error_unlink;
+ goto error_cleanup;
close(fd);
fd = -1;
@@ -199,24 +199,18 @@
* back to find another name in the tmp directory.
*/
if (ppath(dst, sizeof dst, "%s/new/%s", path, name) != 0)
- goto error_unlink;
+ goto error_cleanup;
log_debug2(
- "%s: linking .../tmp/%s to .../new/%s", a->name, name, name);
- if (link(src, dst) != 0) {
+ "%s: moving .../tmp/%s to .../new/%s", a->name, name, name);
+ if (safemove(src, dst) != 0) {
if (errno == EEXIST) {
- log_debug2("%s: %s: link failed", a->name, src);
- if (unlink(src) != 0)
- fatal("unlink failed");
+ log_debug2("%s: %s: moving failed", a->name, src);
cleanup_deregister(src);
goto restart;
}
- goto error_unlink;
+ goto error_cleanup;
}
- /* Unlink the original tmp file. */
- log_debug2("%s: unlinking .../tmp/%s", a->name, name);
- if (unlink(src) != 0)
- goto error_unlink;
cleanup_deregister(src);
/* Save the mail file as a tag. */
@@ -226,9 +220,7 @@
xfree(path);
return (DELIVER_SUCCESS);
-error_unlink:
- if (unlink(src) != 0)
- fatal("unlink failed");
+error_cleanup:
cleanup_deregister(src);
error_log:
diff -u fdm-1.5.orig/fdm.h fdm-1.5-3.debian.louis/fdm.h
--- fdm-1.5.orig/fdm.h 2008-03-06 10:25:32.000000000 +0100
+++ fdm-1.5-3.debian.louis/fdm.h 2009-07-25 16:37:12.643309617 +0200
@@ -844,6 +844,7 @@
const char *checkmode(struct stat *, mode_t);
const char *checkowner(struct stat *, uid_t);
const char *checkgroup(struct stat *, gid_t);
+int safemove(const char *, const char *);
/* mail.c */
int mail_open(struct mail *, size_t);
diff -u fdm-1.5.orig/file.c fdm-1.5-3.debian.louis/file.c
--- fdm-1.5.orig/file.c 2008-03-06 10:25:32.000000000 +0100
+++ fdm-1.5-3.debian.louis/file.c 2009-07-25 17:09:36.353255268 +0200
@@ -314,3 +314,39 @@
"bad group: %lu, should be %lu", (u_long) sb->st_gid, (u_long) gid);
return (msg);
}
+
+/*
+ * Move file oldpath to newpath. oldpath is always removed even in case of
+ * failure.
+ *
+ * It returns 0 on success and -1 on failure with errno set.
+ *
+ * This function use link + unlink or stat + rename if link fail with EXDEV.
+ * (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=538194)
+ */
+int
+safemove(const char *oldpath, const char *newpath)
+{
+ int ret;
+ int errsave;
+ struct stat sb;
+
+ ret = link(oldpath, newpath);
+ if (ret != 0 && errno == EXDEV) {
+ ret = stat(newpath, &sb);
+ if (ret == -1) {
+ if (errno == ENOENT)
+ ret = rename(oldpath, newpath);
+ } else {
+ ret = -1;
+ errno = EEXIST;
+ }
+ }
+
+ errsave = errno;
+ if (unlink(oldpath) != 0 && errno != ENOENT)
+ fatal("unlink failed");
+ errno = errsave;
+
+ return (ret);
+}