gfileutils: Add a missing ftruncate() call when writing files

When calling `g_file_set_contents_full()` without
`G_FILE_SET_CONTENTS_CONSISTENT`, the file is written by opening it,
`write()`ing to it, then closing it.

This is fine as long as the file is not longer than the new content you
want to set its contents to. If it is, the last bit of the old content
remains, because `g_file_set_contents_full()` was missing an
`ftruncate()` call.

Fix that, and change the tests to catch truncation failures in future.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>

Fixes: #3144
This commit is contained in:
Philip Withnall 2023-10-17 17:01:39 +01:00 committed by Philip Withnall
parent 6ad6180b3c
commit 3f705ffa12
2 changed files with 36 additions and 2 deletions

View File

@ -1139,6 +1139,36 @@ fd_should_be_fsynced (int fd,
#endif /* !HAVE_FSYNC */
}
static gboolean
truncate_file (int fd,
off_t length,
const char *dest_file,
GError **error)
{
while (
#ifdef G_OS_WIN32
g_win32_ftruncate (fd, length) < 0
#else
ftruncate (fd, length) < 0
#endif
)
{
int saved_errno = errno;
if (saved_errno == EINTR)
continue;
if (error != NULL)
set_file_error (error,
dest_file,
_("Failed to write file “%s”: ftruncate() failed: %s"),
saved_errno);
return FALSE;
}
return TRUE;
}
/* closes @fd once its finished (on success or error) */
static gboolean
write_to_file (const gchar *contents,
@ -1475,6 +1505,8 @@ consistent_out:
}
do_fsync = fd_should_be_fsynced (direct_fd, filename, flags);
if (!truncate_file (direct_fd, 0, filename, error))
return FALSE;
if (!write_to_file (contents, length, g_steal_fd (&direct_fd), filename,
do_fsync, error))
return FALSE;

View File

@ -1603,6 +1603,8 @@ test_set_contents_full (void)
gsize len;
gboolean ret;
GStatBuf statbuf;
const gchar *original_contents = "a string which is longer than what will be overwritten on it";
size_t original_contents_len = strlen (original_contents);
g_test_message ("Flags %d and test %" G_GSIZE_FORMAT, flags, i);
@ -1617,7 +1619,7 @@ test_set_contents_full (void)
fd = g_file_open_tmp (NULL, &file_name, &error);
g_assert_no_error (error);
g_assert_cmpint (write (fd, "a", 1), ==, 1);
g_assert_cmpint (write (fd, original_contents, original_contents_len), ==, original_contents_len);
g_assert_no_errno (g_fsync (fd));
close (fd);
@ -1713,7 +1715,7 @@ test_set_contents_full (void)
g_file_get_contents (file_name, &target_contents, NULL, &error);
g_assert_no_error (error);
g_assert_cmpstr (target_contents, ==, "a");
g_assert_cmpstr (target_contents, ==, original_contents);
g_free (target_contents);
}