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);
+}

Reply via email to