[gnome-keyring] Provide fallback for file systems without working hardlinks



commit 7f31eebd35a84ed4c09970edb0b8d99a6a8af6f5
Author: Werner Koch <wk gnupg org>
Date:   Wed Aug 1 17:23:19 2012 +0200

    Provide fallback for file systems without working hardlinks
    
    * pkcs11/gkm/gkm-transaction.c (O_BINARY): Add fallback for Windows.
    (copy_to_temp_file): New.
    (begin_link_temporary_if_exists): Detect failure link(2) and resort to
    a copy file method.
    --
    
    See bug 657234 for a description of the problem.  The culprit is the
    CIFS implementation on EMC servers.  At least VFAT file systems should
    have the same problem, thus the patch is a general improvement.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=657234

 pkcs11/gkm/gkm-transaction.c |  146 ++++++++++++++++++++++++++++++++++++++---
 1 files changed, 135 insertions(+), 11 deletions(-)
---
diff --git a/pkcs11/gkm/gkm-transaction.c b/pkcs11/gkm/gkm-transaction.c
index a15f810..3441788 100644
--- a/pkcs11/gkm/gkm-transaction.c
+++ b/pkcs11/gkm/gkm-transaction.c
@@ -31,6 +31,10 @@
 #include <string.h>
 #include <unistd.h>
 
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
 enum {
 	PROP_0,
 	PROP_COMPLETED,
@@ -168,10 +172,93 @@ complete_link_temporary (GkmTransaction *self, GObject *unused, gpointer user_da
 	return ret;
 }
 
+
+/* Copy the file SRCNAME to the file DSTNAME.  If DSTNAME already
+   exists -1 is returned and ERRNO set to EEXIST.  Returns 0 on
+   success. */
+static int
+copy_to_temp_file (const char *dstname, const char *srcname)
+{
+	int dstfd, srcfd;
+	int nread, nwritten;
+	int saveerr;
+	char *bufp;
+	char buffer[512]; /* If you change this size, please also adjust */
+	                  /* test-transaction.c:test_write_large_file.   */
+
+	do {
+		srcfd = g_open (srcname, (O_RDONLY | O_BINARY));
+	} while (srcfd == -1 && errno == EINTR);
+	if (srcfd == -1) {
+		saveerr = errno;
+		g_warning ("couldn't open file to make temporary copy from: %s: %s",
+		           srcname, g_strerror (saveerr));
+		errno = saveerr;
+		return -1;
+	}
+
+	do {
+		dstfd = g_open (dstname,
+		                (O_WRONLY | O_CREAT | O_EXCL | O_BINARY),
+		                (S_IRUSR | S_IWUSR));
+	} while (dstfd == -1 && errno == EINTR);
+	if (dstfd == -1) {
+		saveerr = errno;
+		close (srcfd);
+		errno = saveerr;
+		return -1;
+	}
+
+	while ((nread = read (srcfd, buffer, sizeof buffer))) {
+		if (nread == -1 && errno == EINTR)
+			continue;
+		if (nread == -1) {
+			saveerr = errno;
+			g_warning ("error reading file to make temporary copy from: %s: %s",
+			           srcname, g_strerror (saveerr));
+			goto failure;
+		}
+
+		bufp = buffer;
+		do {
+			do {
+				nwritten = write (dstfd, bufp, nread);
+			} while (nwritten == -1 && errno == EINTR);
+			if (nwritten == -1) {
+				saveerr = errno;
+				g_warning ("error wrinting to temporary file: %s: %s",
+				           dstname, g_strerror (saveerr));
+				goto failure;
+			}
+			g_return_val_if_fail (nwritten <= nread, -1);
+			nread -= nwritten;
+			bufp += nwritten;
+		} while (nread > 0);
+	}
+	/* EOF reached.  */
+	if (close (dstfd)) {
+		saveerr = errno;
+		g_warning ("error closing temporary file: %s: %s",
+		           dstname, g_strerror (saveerr));
+		goto failure;
+	}
+	close (srcfd);
+	return 0;
+
+failure:
+	close (dstfd);  /* (Doesn't harm if we try a second time.) */
+	if (g_unlink (dstname))
+		g_warning ("couldn't remove temporary file: %s: %s",
+		           dstname, g_strerror (saveerr));
+	close (srcfd);
+	errno = saveerr;
+	return -1;
+}
+
+
 static gboolean
 begin_link_temporary_if_exists (GkmTransaction *self, const gchar *filename, gboolean *exists)
 {
-	gchar *result;
 	guint i = 0;
 
 	g_assert (GKM_IS_TRANSACTION (self));
@@ -180,27 +267,64 @@ begin_link_temporary_if_exists (GkmTransaction *self, const gchar *filename, gbo
 	g_assert (exists);
 
 	for (i = 0; i < MAX_TRIES; ++i) {
+		struct stat sb;
+		unsigned int nlink;
+		int stat_failed = 0;
 
 		*exists = TRUE;
 
-		/* Try to link to random temporary file names */
-		result = g_strdup_printf ("%s.temp-%d", filename, g_random_int_range (0, G_MAXINT));
-		if (link (filename, result) == 0) {
-			gkm_transaction_add (self, NULL, complete_link_temporary, result);
-			return TRUE;
-		}
+		/* Try to link to random temporary file names.  We try
+		 * to use a hardlink to create a copy but if that
+		 * fails (i.e. not supported by the FS), we copy the
+		 * entire file.  The result should be the same except
+		 * that the file times will change if we need to
+		 * rollback the transaction. */
+		if (stat (filename, &sb)) {
+			stat_failed = 1;
+		} else {
+			gchar *result;
+
+			result = g_strdup_printf ("%s.temp-%d", filename,
+			                          g_random_int_range (0, G_MAXINT));
+			nlink = (unsigned int)sb.st_nlink;
+			/* The result code of link(2) is not reliable.
+			 * Unless it fails with EEXIST we stat the
+			 * file to check for success.  Note that there
+			 * is a race here: If another process adds a
+			 * link to the source file between link and
+			 * stat, the check on the increased link count
+			 * will fail.  Fortunately the case for
+			 * hardlinks are not working solves it.  */
+			if (link (filename, result) && errno == EEXIST) {
+				/* This is probably a valid error.
+				 * Let us try another temporary file.  */
+			} else if (stat (filename, &sb)) {
+				stat_failed = 1;
+			} else {
+				if ((sb.st_nlink == nlink + 1)
+				    || !copy_to_temp_file (result, filename)) {
+					/* Either the link worked or
+					 * the copy succeeded.  */
+					gkm_transaction_add (self, NULL,
+					                     complete_link_temporary,
+					                     result);
+					return TRUE;
+				}
+			}
 
-		g_free (result);
+			g_free (result);
+		}
 
-		/* The original file does not exist */
-		if (errno == ENOENT || errno == ENOTDIR) {
+		if (stat_failed && (errno == ENOENT || errno == ENOTDIR)) {
+			/* The original file does not exist */
 			*exists = FALSE;
 			return TRUE;
 		}
 
 		/* If exists, try again, otherwise fail */
 		if (errno != EEXIST) {
-			g_warning ("couldn't create temporary file for: %s: %s", filename, g_strerror (errno));
+			g_warning ("couldn't create temporary file for: %s: %s",
+			           filename, g_strerror (errno));
 			gkm_transaction_fail (self, CKR_DEVICE_ERROR);
 			return FALSE;
 		}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]