diff --git a/glib/gfileutils.c b/glib/gfileutils.c index 03b0462a9..db377ca74 100644 --- a/glib/gfileutils.c +++ b/glib/gfileutils.c @@ -46,6 +46,10 @@ #define O_BINARY 0 #endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + #include "gfileutils.h" #include "gstdio.h" @@ -1023,8 +1027,9 @@ g_file_get_contents (const gchar *filename, static gboolean rename_file (const char *old_name, - const char *new_name, - GError **err) + const char *new_name, + gboolean do_fsync, + GError **err) { errno = 0; if (g_rename (old_name, new_name) == -1) @@ -1046,7 +1051,29 @@ rename_file (const char *old_name, return FALSE; } - + + /* In order to guarantee that the *new* contents of the file are seen in + * future, fsync() the directory containing the file. Otherwise if the file + * system was unmounted cleanly now, it would be undefined whether the old + * or new contents of the file were visible after recovery. + * + * This assumes the @old_name and @new_name are in the same directory. */ +#ifdef HAVE_FSYNC + if (do_fsync) + { + gchar *dir = g_path_get_dirname (new_name); + int dir_fd = g_open (dir, O_RDONLY, 0); + + if (dir_fd >= 0) + { + fsync (dir_fd); + g_close (dir_fd, NULL); + } + + g_free (dir); + } +#endif /* HAVE_FSYNC */ + return TRUE; } @@ -1065,21 +1092,44 @@ fd_should_be_fsynced (int fd, /* On Linux, on btrfs, skip the fsync since rename-over-existing is * guaranteed to be atomic and this is the only case in which we * would fsync() anyway. + * + * See https://btrfs.wiki.kernel.org/index.php/FAQ#What_are_the_crash_guarantees_of_overwrite-by-rename.3F */ - if (fstatfs (fd, &buf) == 0 && buf.f_type == BTRFS_SUPER_MAGIC) + if ((flags & G_FILE_SET_CONTENTS_CONSISTENT) && + fstatfs (fd, &buf) == 0 && buf.f_type == BTRFS_SUPER_MAGIC) return FALSE; } #endif /* BTRFS_SUPER_MAGIC */ - errno = 0; /* If the final destination exists and is > 0 bytes, 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.) + * + * There is no difference (in file system terms) if the old file doesn’t + * already exist, apart from the fact that if the system crashes and the new + * data hasn’t been fsync()ed, there is only one bit of old data to lose (that + * the file didn’t exist in the first place). In some situations, such as + * trashing files, the old file never exists, so it seems reasonable to avoid + * the fsync(). This is not a widely applicable optimisation though. */ - return (g_lstat (test_file, &statbuf) == 0 && statbuf.st_size > 0); + if ((flags & (G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_DURABLE)) && + (flags & G_FILE_SET_CONTENTS_ONLY_EXISTING)) + { + errno = 0; + if (g_lstat (test_file, &statbuf) == 0) + return (statbuf.st_size > 0); + else if (errno == ENOENT) + return FALSE; + else + return TRUE; /* lstat() failed; be cautious */ + } + else + { + return (flags & (G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_DURABLE)); + } #else /* if !HAVE_FSYNC */ return FALSE; #endif /* !HAVE_FSYNC */ @@ -1159,43 +1209,6 @@ steal_fd (int *fd_ptr) return fd; } -static gchar * -write_to_temp_file (const gchar *contents, - gsize length, - const gchar *dest_file, - GError **err) -{ - gchar *tmp_name = NULL; - int fd; - gboolean do_fsync; - - tmp_name = g_strdup_printf ("%s.XXXXXX", dest_file); - - errno = 0; - fd = g_mkstemp_full (tmp_name, O_RDWR | O_BINARY, 0666); - - if (fd == -1) - { - int saved_errno = errno; - set_file_error (err, - tmp_name, _("Failed to create file “%s”: %s"), - saved_errno); - g_free (tmp_name); - return NULL; - } - - do_fsync = fd_should_be_fsynced (fd, dest_file, flags); - if (!write_to_file (contents, length, steal_fd (&fd), tmp_name, do_fsync, err)) - { - g_unlink (tmp_name); - g_free (tmp_name); - - return NULL; - } - - return g_steal_pointer (&tmp_name); -} - /** * g_file_set_contents: * @filename: (type filename): name of a file to write @contents to, in the GLib file name @@ -1235,8 +1248,20 @@ g_file_set_contents (const gchar *filename, * Writes all of @contents to a file named @filename, with good error checking. * If a file called @filename already exists it will be overwritten. * - * This write is atomic in the sense that it is first written to a temporary - * file which is then renamed to the final name. Notes: + * @flags control the properties of the write operation: whether it’s atomic, + * and what the tradeoff is between returning quickly or being resilient to + * system crashes. + * + * As this function performs file I/O, it is recommended to not call it anywhere + * where blocking would cause problems, such as in the main loop of a graphical + * application. In particular, if @flags has any value other than + * %G_FILE_SET_CONTENTS_NONE then this function may call `fsync()`. + * + * If %G_FILE_SET_CONTENTS_CONSISTENT is set in @flags, the operation is atomic + * in the sense that it is first written to a temporary file which is then + * renamed to the final name. + * + * Notes: * * - On UNIX, if @filename already exists hard links to @filename will break. * Also since the file is recreated, existing permissions, access control @@ -1244,15 +1269,17 @@ g_file_set_contents (const gchar *filename, * the link itself will be replaced, not the linked file. * * - On UNIX, if @filename already exists and is non-empty, and if the system - * supports it (via a journalling filesystem or equivalent), the fsync() - * call (or equivalent) will be used to ensure atomic replacement: @filename + * supports it (via a journalling filesystem or equivalent), and if + * %G_FILE_SET_CONTENTS_CONSISTENT is set in @flags, the `fsync()` call (or + * equivalent) will be used to ensure atomic replacement: @filename * will contain either its old contents or @contents, even in the face of * system power loss, the disk being unsafely removed, etc. * * - On UNIX, if @filename does not already exist or is empty, there is a * possibility that system power loss etc. after calling this function will * leave @filename empty or full of NUL bytes, depending on the underlying - * filesystem. + * filesystem, unless %G_FILE_SET_CONTENTS_DURABLE and + * %G_FILE_SET_CONTENTS_CONSISTENT are set in @flags. * * - On Windows renaming a file will not remove an existing file with the * new name, so on Windows there is a race condition between the existing @@ -1280,78 +1307,155 @@ g_file_set_contents_full (const gchar *filename, GFileSetContentsFlags flags, GError **error) { - gchar *tmp_filename; - gboolean retval; - GError *rename_error = NULL; - g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (contents != NULL || length == 0, FALSE); g_return_val_if_fail (length >= -1, FALSE); + /* @flags are handled as follows: + * - %G_FILE_SET_CONTENTS_NONE: write directly to @filename, no fsync()s + * - %G_FILE_SET_CONTENTS_CONSISTENT: write to temp file, fsync() it, rename() + * - %G_FILE_SET_CONTENTS_CONSISTENT | ONLY_EXISTING: as above, but skip the + * fsync() if @filename doesn’t exist or is empty + * - %G_FILE_SET_CONTENTS_DURABLE: write directly to @filename, fsync() it + * - %G_FILE_SET_CONTENTS_DURABLE | ONLY_EXISTING: as above, but skip the + * fsync() if @filename doesn’t exist or is empty + * - %G_FILE_SET_CONTENTS_CONSISTENT | DURABLE: write to temp file, fsync() + * it, rename(), fsync() containing directory + * - %G_FILE_SET_CONTENTS_CONSISTENT | DURABLE | ONLY_EXISTING: as above, but + * skip both fsync()s if @filename doesn’t exist or is empty + */ + if (length < 0) length = strlen (contents); - tmp_filename = write_to_temp_file (contents, (gsize) length, filename, error); - - if (!tmp_filename) + if (flags & G_FILE_SET_CONTENTS_CONSISTENT) { - retval = FALSE; - goto out; - } + gchar *tmp_filename = NULL; + GError *rename_error = NULL; + gboolean retval; + int fd; + gboolean do_fsync; - if (!rename_file (tmp_filename, filename, &rename_error)) - { -#ifndef G_OS_WIN32 + tmp_filename = g_strdup_printf ("%s.XXXXXX", filename); - g_unlink (tmp_filename); - g_propagate_error (error, rename_error); - retval = FALSE; - goto out; + errno = 0; + fd = g_mkstemp_full (tmp_filename, O_RDWR | O_BINARY, 0666); -#else /* G_OS_WIN32 */ - - /* Renaming failed, but on Windows this may just mean - * the file already exists. So if the target file - * exists, try deleting it and do the rename again. - */ - if (!g_file_test (filename, G_FILE_TEST_EXISTS)) - { - g_unlink (tmp_filename); - g_propagate_error (error, rename_error); - retval = FALSE; - goto out; - } - - g_error_free (rename_error); - - if (g_unlink (filename) == -1) - { + if (fd == -1) + { int saved_errno = errno; set_file_error (error, - filename, - _("Existing file “%s” could not be removed: g_unlink() failed: %s"), + tmp_filename, _("Failed to create file “%s”: %s"), saved_errno); - g_unlink (tmp_filename); - retval = FALSE; - goto out; - } - - if (!rename_file (tmp_filename, filename, error)) - { - g_unlink (tmp_filename); - retval = FALSE; - goto out; - } + retval = FALSE; + goto consistent_out; + } + do_fsync = fd_should_be_fsynced (fd, filename, flags); + if (!write_to_file (contents, length, steal_fd (&fd), tmp_filename, do_fsync, error)) + { + g_unlink (tmp_filename); + retval = FALSE; + goto consistent_out; + } + + if (!rename_file (tmp_filename, filename, do_fsync, &rename_error)) + { +#ifndef G_OS_WIN32 + + g_unlink (tmp_filename); + g_propagate_error (error, rename_error); + retval = FALSE; + goto consistent_out; + +#else /* G_OS_WIN32 */ + + /* Renaming failed, but on Windows this may just mean + * the file already exists. So if the target file + * exists, try deleting it and do the rename again. + */ + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) + { + g_unlink (tmp_filename); + g_propagate_error (error, rename_error); + retval = FALSE; + goto consistent_out; + } + + g_error_free (rename_error); + + if (g_unlink (filename) == -1) + { + int saved_errno = errno; + set_file_error (error, + filename, + _("Existing file “%s” could not be removed: g_unlink() failed: %s"), + saved_errno); + g_unlink (tmp_filename); + retval = FALSE; + goto consistent_out; + } + + if (!rename_file (tmp_filename, filename, flags, error)) + { + g_unlink (tmp_filename); + retval = FALSE; + goto consistent_out; + } + +#endif /* G_OS_WIN32 */ + } + + retval = TRUE; + +consistent_out: + g_free (tmp_filename); + return retval; + } + else + { + int direct_fd; + int open_flags; + gboolean do_fsync; + + open_flags = O_RDWR | O_BINARY | O_CREAT | O_CLOEXEC; +#ifdef O_NOFOLLOW + /* Windows doesn’t have symlinks, so O_NOFOLLOW is unnecessary there. */ + open_flags |= O_NOFOLLOW; #endif + + errno = 0; + direct_fd = g_open (filename, open_flags, 0666); + + if (direct_fd < 0) + { + int saved_errno = errno; + +#ifdef O_NOFOLLOW + /* ELOOP indicates that @filename is a symlink, since we used + * O_NOFOLLOW (alternately it could indicate that @filename contains + * looping or too many symlinks). In either case, try again on the + * %G_FILE_SET_CONTENTS_CONSISTENT code path. */ + if (saved_errno == ELOOP) + return g_file_set_contents_full (filename, contents, length, + flags | G_FILE_SET_CONTENTS_CONSISTENT, + error); +#endif + + set_file_error (error, + filename, _("Failed to open file “%s”: %s"), + saved_errno); + return FALSE; + } + + do_fsync = fd_should_be_fsynced (direct_fd, filename, flags); + if (!write_to_file (contents, length, steal_fd (&direct_fd), filename, + do_fsync, error)) + return FALSE; } - retval = TRUE; - - out: - g_free (tmp_filename); - return retval; + return TRUE; } /*