+2009-03-16 Alexander Larsson <alexl@redhat.com>
+
+ Bug 575555 – Use fsync() when replacing files to avoid data loss on crash
+
+ * configure.in:
+ Look for fsync().
+
+ * glib/gfileutils.c:
+ (write_to_temp_file):
+ fsync temp file if destination file exists
+
2009-03-13 Matthias Clasen <mclasen@redhat.com>
* configure.in: Bump version
AC_CHECK_FUNCS(posix_memalign)
AC_CHECK_FUNCS(memalign)
AC_CHECK_FUNCS(valloc)
+AC_CHECK_FUNCS(fsync)
AC_CHECK_FUNCS(atexit on_exit)
+2009-03-16 Alexander Larsson <alexl@redhat.com>
+
+ Bug 575555 – Use fsync() when replacing files to avoid data loss on crash
+
+ * glocalfileoutputstream.c:
+ (g_local_file_output_stream_close):
+ (_g_local_file_output_stream_replace):
+ fsync temp file before closing if replacing target file
+
2009-03-13 Matthias Clasen <mclasen@redhat.com>
* === Released 2.20.0 ===
char *original_filename;
char *backup_filename;
char *etag;
+ gboolean sync_on_close;
int fd;
};
file = G_LOCAL_FILE_OUTPUT_STREAM (stream);
+#ifdef HAVE_FSYNC
+ if (file->priv->sync_on_close &&
+ fsync (file->priv->fd) != 0)
+ {
+ int errsv = errno;
+
+ g_set_error (error, G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ _("Error writing to file: %s"),
+ g_strerror (errsv));
+ goto err_out;
+ }
+#endif
+
#ifdef G_OS_WIN32
/* Must close before renaming on Windows, so just do the close first
int mode;
int fd;
char *temp_file;
+ gboolean sync_on_close;
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return NULL;
mode = 0600;
else
mode = 0666;
+ sync_on_close = FALSE;
/* If the file doesn't exist, create it */
fd = g_open (filename, O_CREAT | O_EXCL | O_WRONLY | O_BINARY, mode);
flags, cancellable, error);
if (fd == -1)
return NULL;
+
+ /* If the final destination exists, we want to sync the newly written
+ * file to ensure the data is on disk when we rename over the destination.
+ * otherwise if we get a system crash we can lose both the new and the
+ * old file on some filesystems. (I.E. those that don't guarantee the
+ * data is written to the disk before the metadata.)
+ */
+ sync_on_close = TRUE;
}
else if (fd == -1)
{
stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL);
stream->priv->fd = fd;
+ stream->priv->sync_on_close = sync_on_close;
stream->priv->tmp_filename = temp_file;
if (create_backup)
stream->priv->backup_filename = create_backup_filename (filename);
static gchar *
write_to_temp_file (const gchar *contents,
gssize length,
- const gchar *template,
+ const gchar *dest_file,
GError **err)
{
gchar *tmp_name;
retval = NULL;
- tmp_name = g_strdup_printf ("%s.XXXXXX", template);
+ tmp_name = g_strdup_printf ("%s.XXXXXX", dest_file);
errno = 0;
fd = create_temp_file (tmp_name, 0666);
goto out;
}
}
-
+
+ errno = 0;
+ if (fflush (file) != 0)
+ {
+ save_errno = errno;
+
+ g_set_error (err,
+ G_FILE_ERROR,
+ g_file_error_from_errno (save_errno),
+ _("Failed to write file '%s': fflush() failed: %s"),
+ display_name,
+ g_strerror (save_errno));
+
+ g_unlink (tmp_name);
+
+ goto out;
+ }
+
+#ifdef HAVE_FSYNC
+ errno = 0;
+ /* If the final destination exists, we want to sync the newly written
+ * file to ensure the data is on disk when we rename over the destination.
+ * otherwise if we get a system crash we can lose both the new and the
+ * old file on some filesystems. (I.E. those that don't guarantee the
+ * data is written to the disk before the metadata.)
+ */
+ if (g_file_test (dest_file, G_FILE_TEST_EXISTS) &&
+ fsync (fileno (file)) != 0)
+ {
+ save_errno = errno;
+
+ g_set_error (err,
+ G_FILE_ERROR,
+ g_file_error_from_errno (save_errno),
+ _("Failed to write file '%s': fsync() failed: %s"),
+ display_name,
+ g_strerror (save_errno));
+
+ g_unlink (tmp_name);
+
+ goto out;
+ }
+#endif
+
errno = 0;
if (fclose (file) == EOF)
{
- save_errno = 0;
+ save_errno = errno;
g_set_error (err,
G_FILE_ERROR,