gfileutils: Preserve mode during atomic updates

If g_file_set_contents{_full,} is replacing an existing file, require
that the tmpfile have the same mode as the existing file.

This prevents the umask from taking effect for consistent writes to
existing files.

Closes GNOME/dconf#76
This commit is contained in:
Wesley Hershberger 2025-04-22 16:37:49 -05:00
parent 437f3a134d
commit 3cc0c0de33
No known key found for this signature in database
GPG Key ID: F92880ACE23A1DB9
2 changed files with 33 additions and 6 deletions

View File

@ -1318,8 +1318,8 @@ g_file_set_contents (const gchar *filename,
* to 7 characters to @filename. * to 7 characters to @filename.
* *
* If the file didnt exist before and is created, it will be given the * If the file didnt exist before and is created, it will be given the
* permissions from @mode. Otherwise, the permissions of the existing file may * permissions from @mode. Otherwise, the permissions of the existing file will
* be changed to @mode depending on @flags, or they may remain unchanged. * remain unchanged.
* *
* Returns: %TRUE on success, %FALSE if an error occurred * Returns: %TRUE on success, %FALSE if an error occurred
* *
@ -1362,6 +1362,7 @@ g_file_set_contents_full (const gchar *filename,
gboolean retval; gboolean retval;
int fd; int fd;
gboolean do_fsync; gboolean do_fsync;
GStatBuf old_stat;
tmp_filename = g_strdup_printf ("%s.XXXXXX", filename); tmp_filename = g_strdup_printf ("%s.XXXXXX", filename);
@ -1379,6 +1380,26 @@ g_file_set_contents_full (const gchar *filename,
goto consistent_out; goto consistent_out;
} }
/* Maintain the permissions of the file if it exists */
if (!g_stat (filename, &old_stat))
{
#ifndef G_OS_WIN32
if (fchmod (fd, old_stat.st_mode))
#else /* G_OS_WIN32 */
if (chmod (tmp_filename, old_stat.st_mode))
#endif /* G_OS_WIN32 */
{
int saved_errno = errno;
if (error)
set_file_error (error,
tmp_filename, _ ("Failed to set permissions of “%s”: %s"),
saved_errno);
g_unlink (tmp_filename);
retval = FALSE;
goto consistent_out;
}
}
do_fsync = fd_should_be_fsynced (fd, filename, flags); do_fsync = fd_should_be_fsynced (fd, filename, flags);
if (!write_to_file (contents, length, g_steal_fd (&fd), tmp_filename, do_fsync, error)) if (!write_to_file (contents, length, g_steal_fd (&fd), tmp_filename, do_fsync, error))
{ {

View File

@ -1687,7 +1687,9 @@ test_set_contents_full (void)
EXISTING_FILE_DIRECTORY, EXISTING_FILE_DIRECTORY,
} }
existing_file; existing_file;
int new_mode; /* only relevant if @existing_file is %EXISTING_FILE_NONE */ /* only relevant if @existing_file is
* %EXISTING_FILE_NONE or %EXISTING_FILE_REGULAR */
int mode;
gboolean use_strlen; gboolean use_strlen;
gboolean expected_success; gboolean expected_success;
@ -1698,6 +1700,8 @@ test_set_contents_full (void)
{ EXISTING_FILE_NONE, 0644, FALSE, TRUE, 0 }, { EXISTING_FILE_NONE, 0644, FALSE, TRUE, 0 },
{ EXISTING_FILE_NONE, 0644, TRUE, TRUE, 0 }, { EXISTING_FILE_NONE, 0644, TRUE, TRUE, 0 },
{ EXISTING_FILE_NONE, 0600, FALSE, TRUE, 0 }, { EXISTING_FILE_NONE, 0600, FALSE, TRUE, 0 },
// Assume umask is 022, ensures that we preserve perms with, eg. 077
{ EXISTING_FILE_REGULAR, 0666, FALSE, TRUE, 0 },
{ EXISTING_FILE_REGULAR, 0644, FALSE, TRUE, 0 }, { EXISTING_FILE_REGULAR, 0644, FALSE, TRUE, 0 },
#ifndef G_OS_WIN32 #ifndef G_OS_WIN32
{ EXISTING_FILE_SYMLINK, 0644, FALSE, TRUE, 0 }, { EXISTING_FILE_SYMLINK, 0644, FALSE, TRUE, 0 },
@ -1742,6 +1746,8 @@ test_set_contents_full (void)
g_assert_no_errno (g_fsync (fd)); g_assert_no_errno (g_fsync (fd));
close (fd); close (fd);
g_assert_no_errno (chmod (file_name, tests[i].mode));
#ifndef G_OS_WIN32 #ifndef G_OS_WIN32
/* Pass an existing symlink to g_file_set_contents_full() to see /* Pass an existing symlink to g_file_set_contents_full() to see
* what it does. */ * what it does. */
@ -1785,7 +1791,7 @@ test_set_contents_full (void)
/* Set the file contents */ /* Set the file contents */
ret = g_file_set_contents_full (set_contents_name, "b", ret = g_file_set_contents_full (set_contents_name, "b",
tests[i].use_strlen ? -1 : 1, tests[i].use_strlen ? -1 : 1,
flags, tests[i].new_mode, &error); flags, tests[i].mode, &error);
if (!tests[i].expected_success) if (!tests[i].expected_success)
{ {
@ -1809,10 +1815,10 @@ test_set_contents_full (void)
g_assert_no_errno (g_lstat (set_contents_name, &statbuf)); g_assert_no_errno (g_lstat (set_contents_name, &statbuf));
if (tests[i].existing_file == EXISTING_FILE_NONE) if (tests[i].existing_file & (EXISTING_FILE_NONE | EXISTING_FILE_REGULAR))
{ {
int mode = statbuf.st_mode & ~S_IFMT; int mode = statbuf.st_mode & ~S_IFMT;
int new_mode = tests[i].new_mode; int new_mode = tests[i].mode;
#ifdef G_OS_WIN32 #ifdef G_OS_WIN32
/* on windows, group and others perms handling is different */ /* on windows, group and others perms handling is different */
/* only check the rwx user permissions */ /* only check the rwx user permissions */