Bug 560564 – Replacing a symlink with its linked file truncates the

2009-02-18  Alexander Larsson  <alexl@redhat.com>

	Bug 560564 – Replacing a symlink with its linked file truncates the original file

        * gioenums.h:
	Add G_FILE_CREATE_REPLACE_DESTINATION

        * glocalfileoutputstream.c:
        (handle_overwrite_open):
        (_g_local_file_output_stream_replace):
	Handle G_FILE_CREATE_REPLACE_DESTINATION when overwriting files.

        * gfile.c:
        (file_copy_fallback):
	Pass G_FILE_CREATE_REPLACE_DESTINATION to g_file_replace when copying
	with overwrite.


svn path=/trunk/; revision=7880
This commit is contained in:
Alexander Larsson 2009-02-18 14:49:25 +00:00 committed by Alexander Larsson
parent 3fd881b5bb
commit 0fd66d7e22
4 changed files with 93 additions and 25 deletions

View File

@ -1,3 +1,20 @@
2009-02-18 Alexander Larsson <alexl@redhat.com>
Bug 560564 Replacing a symlink with its linked file truncates the original file
* gioenums.h:
Add G_FILE_CREATE_REPLACE_DESTINATION
* glocalfileoutputstream.c:
(handle_overwrite_open):
(_g_local_file_output_stream_replace):
Handle G_FILE_CREATE_REPLACE_DESTINATION when overwriting files.
* gfile.c:
(file_copy_fallback):
Pass G_FILE_CREATE_REPLACE_DESTINATION to g_file_replace when copying
with overwrite.
2009-02-17 Ryan Lortie <desrt@desrt.ca> 2009-02-17 Ryan Lortie <desrt@desrt.ca>
* gfileinfo.c: unref the destination's attribute matcher before * gfileinfo.c: unref the destination's attribute matcher before

View File

@ -2342,7 +2342,7 @@ file_copy_fallback (GFile *source,
out = (GOutputStream *)g_file_replace (destination, out = (GOutputStream *)g_file_replace (destination,
NULL, NULL,
flags & G_FILE_COPY_BACKUP, flags & G_FILE_COPY_BACKUP,
0, G_FILE_CREATE_REPLACE_DESTINATION,
cancellable, error); cancellable, error);
} }
else else

View File

@ -156,12 +156,21 @@ typedef enum {
* @G_FILE_CREATE_NONE: No flags set. * @G_FILE_CREATE_NONE: No flags set.
* @G_FILE_CREATE_PRIVATE: Create a file that can only be * @G_FILE_CREATE_PRIVATE: Create a file that can only be
* accessed by the current user. * accessed by the current user.
* @G_FILE_CREATE_REPLACE_DESTINATION: Replace the destination
* as if it didn't exist before. Don't try to keep any old
* permissions, replace instead of following links. This
* is generally useful if you're doing a "copy over"
* rather than a "save new version of" replace operation.
* You can think of it as "unlink destination" before
* writing to it, although the implementation may not
* be exactly like that.
* *
* Flags used when an operation may create a file. * Flags used when an operation may create a file.
*/ */
typedef enum { typedef enum {
G_FILE_CREATE_NONE = 0, G_FILE_CREATE_NONE = 0,
G_FILE_CREATE_PRIVATE = (1 << 0) G_FILE_CREATE_PRIVATE = (1 << 0),
G_FILE_CREATE_REPLACE_DESTINATION = (1 << 1)
} GFileCreateFlags; } GFileCreateFlags;

View File

@ -644,6 +644,7 @@ handle_overwrite_open (const char *filename,
const char *etag, const char *etag,
gboolean create_backup, gboolean create_backup,
char **temp_filename, char **temp_filename,
GFileCreateFlags flags,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
@ -653,6 +654,12 @@ handle_overwrite_open (const char *filename,
gboolean is_symlink; gboolean is_symlink;
int open_flags; int open_flags;
int res; int res;
int mode;
if (flags & G_FILE_CREATE_PRIVATE)
mode = 0600;
else
mode = 0666;
/* We only need read access to the original file if we are creating a backup. /* We only need read access to the original file if we are creating a backup.
* We also add O_CREATE to avoid a race if the file was just removed */ * We also add O_CREATE to avoid a race if the file was just removed */
@ -665,16 +672,16 @@ handle_overwrite_open (const char *filename,
* when finding out if the file we opened was a symlink */ * when finding out if the file we opened was a symlink */
#ifdef O_NOFOLLOW #ifdef O_NOFOLLOW
is_symlink = FALSE; is_symlink = FALSE;
fd = g_open (filename, open_flags | O_NOFOLLOW, 0666); fd = g_open (filename, open_flags | O_NOFOLLOW, mode);
if (fd == -1 && errno == ELOOP) if (fd == -1 && errno == ELOOP)
{ {
/* Could be a symlink, or it could be a regular ELOOP error, /* Could be a symlink, or it could be a regular ELOOP error,
* but then the next open will fail too. */ * but then the next open will fail too. */
is_symlink = TRUE; is_symlink = TRUE;
fd = g_open (filename, open_flags, 0666); fd = g_open (filename, open_flags, mode);
} }
#else #else
fd = g_open (filename, open_flags, 0666); fd = g_open (filename, open_flags, mode);
/* This is racy, but we do it as soon as possible to minimize the race */ /* This is racy, but we do it as soon as possible to minimize the race */
is_symlink = g_file_test (filename, G_FILE_TEST_IS_SYMLINK); is_symlink = g_file_test (filename, G_FILE_TEST_IS_SYMLINK);
#endif #endif
@ -751,7 +758,8 @@ handle_overwrite_open (const char *filename,
* to a backup file and rewrite the contents of the file. * to a backup file and rewrite the contents of the file.
*/ */
if (!(original_stat.st_nlink > 1) && !is_symlink) if ((flags & G_FILE_CREATE_REPLACE_DESTINATION) ||
(!(original_stat.st_nlink > 1) && !is_symlink))
{ {
char *dirname, *tmp_filename; char *dirname, *tmp_filename;
int tmpfd; int tmpfd;
@ -767,16 +775,18 @@ handle_overwrite_open (const char *filename,
goto fallback_strategy; goto fallback_strategy;
} }
/* try to keep permissions */ /* try to keep permissions (unless replacing) */
if ( if ( ! (flags & G_FILE_CREATE_REPLACE_DESTINATION) &&
(
#ifdef HAVE_FCHOWN #ifdef HAVE_FCHOWN
fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 || fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 ||
#endif #endif
#ifdef HAVE_FCHMOD #ifdef HAVE_FCHMOD
fchmod (tmpfd, original_stat.st_mode) == -1 || fchmod (tmpfd, original_stat.st_mode) == -1 ||
#endif #endif
0 0
)
) )
{ {
struct stat tmp_statbuf; struct stat tmp_statbuf;
@ -899,26 +909,58 @@ handle_overwrite_open (const char *filename,
} }
} }
/* Truncate the file at the start */ if (flags & G_FILE_CREATE_REPLACE_DESTINATION)
#ifdef G_OS_WIN32
if (g_win32_ftruncate (fd, 0) == -1)
#else
if (ftruncate (fd, 0) == -1)
#endif
{ {
int errsv = errno; close (fd);
g_set_error (error, G_IO_ERROR, if (g_unlink (filename) != 0)
g_io_error_from_errno (errsv), {
_("Error truncating file: %s"), int errsv = errno;
g_strerror (errsv));
goto err_out; g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (errsv),
_("Error removing old file: %s"),
g_strerror (errsv));
goto err_out2;
}
fd = g_open (filename, O_WRONLY | O_CREAT | O_BINARY, mode);
if (fd == -1)
{
int errsv = errno;
char *display_name = g_filename_display_name (filename);
g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (errsv),
_("Error opening file '%s': %s"),
display_name, g_strerror (errsv));
g_free (display_name);
goto err_out2;
}
}
else
{
/* Truncate the file at the start */
#ifdef G_OS_WIN32
if (g_win32_ftruncate (fd, 0) == -1)
#else
if (ftruncate (fd, 0) == -1)
#endif
{
int errsv = errno;
g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (errsv),
_("Error truncating file: %s"),
g_strerror (errsv));
goto err_out;
}
} }
return fd; return fd;
err_out: err_out:
close (fd); close (fd);
err_out2:
return -1; return -1;
} }
@ -952,7 +994,7 @@ _g_local_file_output_stream_replace (const char *filename,
{ {
/* The file already exists */ /* The file already exists */
fd = handle_overwrite_open (filename, etag, create_backup, &temp_file, fd = handle_overwrite_open (filename, etag, create_backup, &temp_file,
cancellable, error); flags, cancellable, error);
if (fd == -1) if (fd == -1)
return NULL; return NULL;
} }