diff options
author | Werner Koch <wk@gnupg.org> | 2012-08-01 17:23:19 +0200 |
---|---|---|
committer | Stef Walter <stefw@gnome.org> | 2012-08-09 11:33:29 +0200 |
commit | 7f31eebd35a84ed4c09970edb0b8d99a6a8af6f5 (patch) | |
tree | 5f5c7ac29917ca31ff29a460cb6433d69e89ca65 | |
parent | dff0a6edd2c02602b24476296855b4f63f99379f (diff) |
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
-rw-r--r-- | 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 a15f8100..34417883 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; } |