X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fglocalfileoutputstream.c;h=b8fd45c3564da31dbc5fee63d97b381bcdcf3168;hb=7a1aaaa1fa02679ecf335a19fffe3f55505921b5;hp=d0a30dcdae0f3e078b3ea6e29d0d53350b3cc6c4;hpb=4cb6eab803662aebb2a81fe97832dd7576391331;p=platform%2Fupstream%2Fglib.git diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c index d0a30dc..b8fd45c 100644 --- a/gio/glocalfileoutputstream.c +++ b/gio/glocalfileoutputstream.c @@ -13,21 +13,16 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General - * Public License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. + * Public License along with this library; if not, see . * * Author: Alexander Larsson */ -#include +#include "config.h" #include #include #include -#ifdef HAVE_UNISTD_H -#include -#endif #include #include @@ -35,9 +30,16 @@ #include #include "glibintl.h" #include "gioerror.h" +#include "gcancellable.h" #include "glocalfileoutputstream.h" +#include "gfileinfo.h" #include "glocalfileinfo.h" +#ifdef G_OS_UNIX +#include +#include "gfiledescriptorbased.h" +#endif + #ifdef G_OS_WIN32 #include #ifndef S_ISDIR @@ -48,25 +50,42 @@ #endif #endif -#include "gioalias.h" - -#define g_local_file_output_stream_get_type _g_local_file_output_stream_get_type -G_DEFINE_TYPE (GLocalFileOutputStream, g_local_file_output_stream, G_TYPE_FILE_OUTPUT_STREAM); - -/* Some of the file replacement code was based on the code from gedit, - * relicenced to LGPL with permissions from the authors. - */ - -#define BACKUP_EXTENSION "~" +#ifndef O_BINARY +#define O_BINARY 0 +#endif struct _GLocalFileOutputStreamPrivate { char *tmp_filename; char *original_filename; char *backup_filename; char *etag; + guint sync_on_close : 1; + guint do_close : 1; int fd; }; +#ifdef G_OS_UNIX +static void g_file_descriptor_based_iface_init (GFileDescriptorBasedIface *iface); +#endif + +#define g_local_file_output_stream_get_type _g_local_file_output_stream_get_type +#ifdef G_OS_UNIX +G_DEFINE_TYPE_WITH_CODE (GLocalFileOutputStream, g_local_file_output_stream, G_TYPE_FILE_OUTPUT_STREAM, + G_ADD_PRIVATE (GLocalFileOutputStream) + G_IMPLEMENT_INTERFACE (G_TYPE_FILE_DESCRIPTOR_BASED, + g_file_descriptor_based_iface_init)) +#else +G_DEFINE_TYPE_WITH_CODE (GLocalFileOutputStream, g_local_file_output_stream, G_TYPE_FILE_OUTPUT_STREAM, + G_ADD_PRIVATE (GLocalFileOutputStream)) +#endif + + +/* Some of the file replacement code was based on the code from gedit, + * relicenced to LGPL with permissions from the authors. + */ + +#define BACKUP_EXTENSION "~" + static gssize g_local_file_output_stream_write (GOutputStream *stream, const void *buffer, gsize count, @@ -76,7 +95,7 @@ static gboolean g_local_file_output_stream_close (GOutputStream *s GCancellable *cancellable, GError **error); static GFileInfo *g_local_file_output_stream_query_info (GFileOutputStream *stream, - char *attributes, + const char *attributes, GCancellable *cancellable, GError **error); static char * g_local_file_output_stream_get_etag (GFileOutputStream *stream); @@ -92,6 +111,9 @@ static gboolean g_local_file_output_stream_truncate (GFileOutputStream *s goffset size, GCancellable *cancellable, GError **error); +#ifdef G_OS_UNIX +static int g_local_file_output_stream_get_fd (GFileDescriptorBased *stream); +#endif static void g_local_file_output_stream_finalize (GObject *object) @@ -104,9 +126,8 @@ g_local_file_output_stream_finalize (GObject *object) g_free (file->priv->original_filename); g_free (file->priv->backup_filename); g_free (file->priv->etag); - - if (G_OBJECT_CLASS (g_local_file_output_stream_parent_class)->finalize) - (*G_OBJECT_CLASS (g_local_file_output_stream_parent_class)->finalize) (object); + + G_OBJECT_CLASS (g_local_file_output_stream_parent_class)->finalize (object); } static void @@ -115,9 +136,7 @@ g_local_file_output_stream_class_init (GLocalFileOutputStreamClass *klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass); GFileOutputStreamClass *file_stream_class = G_FILE_OUTPUT_STREAM_CLASS (klass); - - g_type_class_add_private (klass, sizeof (GLocalFileOutputStreamPrivate)); - + gobject_class->finalize = g_local_file_output_stream_finalize; stream_class->write_fn = g_local_file_output_stream_write; @@ -131,12 +150,19 @@ g_local_file_output_stream_class_init (GLocalFileOutputStreamClass *klass) file_stream_class->truncate_fn = g_local_file_output_stream_truncate; } +#ifdef G_OS_UNIX +static void +g_file_descriptor_based_iface_init (GFileDescriptorBasedIface *iface) +{ + iface->get_fd = g_local_file_output_stream_get_fd; +} +#endif + static void g_local_file_output_stream_init (GLocalFileOutputStream *stream) { - stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, - G_TYPE_LOCAL_FILE_OUTPUT_STREAM, - GLocalFileOutputStreamPrivate); + stream->priv = g_local_file_output_stream_get_instance_private (stream); + stream->priv->do_close = TRUE; } static gssize @@ -158,13 +184,15 @@ g_local_file_output_stream_write (GOutputStream *stream, res = write (file->priv->fd, buffer, count); if (res == -1) { - if (errno == EINTR) + int errsv = errno; + + if (errsv == EINTR) continue; g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error writing to file: %s"), - g_strerror (errno)); + g_strerror (errsv)); } break; @@ -173,16 +201,54 @@ g_local_file_output_stream_write (GOutputStream *stream, return res; } -static gboolean -g_local_file_output_stream_close (GOutputStream *stream, - GCancellable *cancellable, - GError **error) +void +_g_local_file_output_stream_set_do_close (GLocalFileOutputStream *out, + gboolean do_close) { - GLocalFileOutputStream *file; - struct stat final_stat; - int res; + out->priv->do_close = do_close; +} - file = G_LOCAL_FILE_OUTPUT_STREAM (stream); +gboolean +_g_local_file_output_stream_really_close (GLocalFileOutputStream *file, + GCancellable *cancellable, + GError **error) +{ + GLocalFileStat final_stat; + +#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 (errsv), + _("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 + * in all cases for now. + */ + if (_fstati64 (file->priv->fd, &final_stat) == 0) + file->priv->etag = _g_local_file_info_create_etag (&final_stat); + + if (!g_close (file->priv->fd, NULL)) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error closing file: %s"), + g_strerror (errsv)); + return FALSE; + } + +#endif if (file->priv->tmp_filename) { @@ -195,34 +261,44 @@ g_local_file_output_stream_close (GOutputStream *stream, if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto err_out; -#ifdef HAVE_LINK +#ifdef G_OS_UNIX /* create original -> backup link, the original is then renamed over */ - if (unlink (file->priv->backup_filename) != 0 && + if (g_unlink (file->priv->backup_filename) != 0 && errno != ENOENT) { + int errsv = errno; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, _("Error removing old backup link: %s"), - g_strerror (errno)); + g_strerror (errsv)); goto err_out; } if (link (file->priv->original_filename, file->priv->backup_filename) != 0) { - g_set_error (error, G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Error creating backup link: %s"), - g_strerror (errno)); - goto err_out; + /* link failed or is not supported, try rename */ + if (g_rename (file->priv->original_filename, file->priv->backup_filename) != 0) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Error creating backup copy: %s"), + g_strerror (errsv)); + goto err_out; + } } #else /* If link not supported, just rename... */ - if (!rename (file->priv->original_filename, file->priv->backup_filename) != 0) + if (g_rename (file->priv->original_filename, file->priv->backup_filename) != 0) { + int errsv = errno; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP, _("Error creating backup copy: %s"), - g_strerror (errno)); + g_strerror (errsv)); goto err_out; } #endif @@ -233,43 +309,71 @@ g_local_file_output_stream_close (GOutputStream *stream, goto err_out; /* tmp -> original */ - if (rename (file->priv->tmp_filename, file->priv->original_filename) != 0) + if (g_rename (file->priv->tmp_filename, file->priv->original_filename) != 0) { + int errsv = errno; + g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error renaming temporary file: %s"), - g_strerror (errno)); + g_strerror (errsv)); goto err_out; } + + g_clear_pointer (&file->priv->tmp_filename, g_free); } if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto err_out; +#ifndef G_OS_WIN32 /* Already did the fstat() and close() above on Win32 */ + if (fstat (file->priv->fd, &final_stat) == 0) file->priv->etag = _g_local_file_info_create_etag (&final_stat); - while (1) + if (!g_close (file->priv->fd, NULL)) { - res = close (file->priv->fd); - if (res == -1) - { - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), - _("Error closing file: %s"), - g_strerror (errno)); - } - break; + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error closing file: %s"), + g_strerror (errsv)); + goto err_out; } - - return res != -1; +#endif + + return TRUE; err_out: + +#ifndef G_OS_WIN32 /* A simple try to close the fd in case we fail before the actual close */ - close (file->priv->fd); + (void) g_close (file->priv->fd, NULL); +#endif + if (file->priv->tmp_filename) + g_unlink (file->priv->tmp_filename); + return FALSE; } + +static gboolean +g_local_file_output_stream_close (GOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *file; + + file = G_LOCAL_FILE_OUTPUT_STREAM (stream); + + if (file->priv->do_close) + return _g_local_file_output_stream_really_close (file, + cancellable, + error); + return TRUE; +} + static char * g_local_file_output_stream_get_etag (GFileOutputStream *stream) { @@ -306,8 +410,7 @@ g_local_file_output_stream_can_seek (GFileOutputStream *stream) pos = lseek (file->priv->fd, 0, SEEK_CUR); - if (pos == (off_t)-1 && - errno == ESPIPE) + if (pos == (off_t)-1 && errno == ESPIPE) return FALSE; return TRUE; @@ -346,10 +449,12 @@ g_local_file_output_stream_seek (GFileOutputStream *stream, if (pos == (off_t)-1) { + int errsv = errno; + g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error seeking in file: %s"), - g_strerror (errno)); + g_strerror (errsv)); return FALSE; } @@ -383,7 +488,9 @@ g_local_file_output_stream_truncate (GFileOutputStream *stream, if (res == -1) { - if (errno == EINTR) + int errsv = errno; + + if (errsv == EINTR) { if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; @@ -391,9 +498,9 @@ g_local_file_output_stream_truncate (GFileOutputStream *stream, } g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error truncating file: %s"), - g_strerror (errno)); + g_strerror (errsv)); return FALSE; } @@ -403,7 +510,7 @@ g_local_file_output_stream_truncate (GFileOutputStream *stream, static GFileInfo * g_local_file_output_stream_query_info (GFileOutputStream *stream, - char *attributes, + const char *attributes, GCancellable *cancellable, GError **error) { @@ -420,40 +527,51 @@ g_local_file_output_stream_query_info (GFileOutputStream *stream, } GFileOutputStream * -_g_local_file_output_stream_create (const char *filename, - GFileCreateFlags flags, - GCancellable *cancellable, - GError **error) +_g_local_file_output_stream_new (int fd) { GLocalFileOutputStream *stream; - int mode; - int fd; - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return NULL; + stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); + stream->priv->fd = fd; + return G_FILE_OUTPUT_STREAM (stream); +} - if (flags & G_FILE_CREATE_PRIVATE) - mode = 0600; - else - mode = 0666; +static void +set_error_from_open_errno (const char *filename, + GError **error) +{ + int errsv = errno; - fd = g_open (filename, - O_CREAT | O_EXCL | O_WRONLY, - 0666); - if (fd == -1) + if (errsv == EINVAL) + /* This must be an invalid filename, on e.g. FAT */ + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_INVALID_FILENAME, + _("Invalid filename")); + else { - 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); + } +} - if (errsv == EINVAL) - /* This must be an invalid filename, on e.g. FAT */ - g_set_error (error, G_IO_ERROR, - G_IO_ERROR_INVALID_FILENAME, - _("Invalid filename")); - else - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errsv), - _("Error opening file '%s': %s"), - filename, g_strerror (errsv)); +static GFileOutputStream * +output_stream_open (const char *filename, + gint open_flags, + guint mode, + GCancellable *cancellable, + GError **error) +{ + GLocalFileOutputStream *stream; + gint fd; + + fd = g_open (filename, open_flags, mode); + if (fd == -1) + { + set_error_from_open_errno (filename, error); return NULL; } @@ -463,14 +581,69 @@ _g_local_file_output_stream_create (const char *filename, } GFileOutputStream * +_g_local_file_output_stream_open (const char *filename, + gboolean readable, + GCancellable *cancellable, + GError **error) +{ + int open_flags; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + open_flags = O_BINARY; + if (readable) + open_flags |= O_RDWR; + else + open_flags |= O_WRONLY; + + return output_stream_open (filename, open_flags, 0666, cancellable, error); +} + +static gint +mode_from_flags_or_info (GFileCreateFlags flags, + GFileInfo *reference_info) +{ + if (flags & G_FILE_CREATE_PRIVATE) + return 0600; + else if (reference_info && g_file_info_has_attribute (reference_info, "unix::mode")) + return g_file_info_get_attribute_uint32 (reference_info, "unix::mode") & (~S_IFMT); + else + return 0666; +} + +GFileOutputStream * +_g_local_file_output_stream_create (const char *filename, + gboolean readable, + GFileCreateFlags flags, + GFileInfo *reference_info, + GCancellable *cancellable, + GError **error) +{ + int mode; + int open_flags; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return NULL; + + mode = mode_from_flags_or_info (flags, reference_info); + + open_flags = O_CREAT | O_EXCL | O_BINARY; + if (readable) + open_flags |= O_RDWR; + else + open_flags |= O_WRONLY; + + return output_stream_open (filename, open_flags, mode, cancellable, error); +} + +GFileOutputStream * _g_local_file_output_stream_append (const char *filename, GFileCreateFlags flags, GCancellable *cancellable, GError **error) { - GLocalFileOutputStream *stream; int mode; - int fd; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return NULL; @@ -480,30 +653,8 @@ _g_local_file_output_stream_append (const char *filename, else mode = 0666; - fd = g_open (filename, - O_CREAT | O_APPEND | O_WRONLY, - mode); - if (fd == -1) - { - int errsv = errno; - - if (errsv == EINVAL) - /* This must be an invalid filename, on e.g. FAT */ - g_set_error (error, G_IO_ERROR, - G_IO_ERROR_INVALID_FILENAME, - _("Invalid filename")); - else - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errsv), - _("Error opening file '%s': %s"), - filename, g_strerror (errsv)); - return NULL; - } - - stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); - stream->priv->fd = fd; - - return G_FILE_OUTPUT_STREAM (stream); + return output_stream_open (filename, O_CREAT | O_APPEND | O_WRONLY | O_BINARY, mode, + cancellable, error); } static char * @@ -533,13 +684,15 @@ copy_file_data (gint sfd, bytes_read = read (sfd, buffer, BUFSIZE); if (bytes_read == -1) { - if (errno == EINTR) + int errsv = errno; + + if (errsv == EINTR) continue; g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error reading from file: %s"), - g_strerror (errno)); + g_strerror (errsv)); ret = FALSE; break; } @@ -552,13 +705,15 @@ copy_file_data (gint sfd, bytes_written = write (dfd, write_buffer, bytes_to_write); if (bytes_written == -1) { - if (errno == EINTR) + int errsv = errno; + + if (errsv == EINTR) continue; g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error writing to file: %s"), - g_strerror (errno)); + g_strerror (errsv)); ret = FALSE; break; } @@ -577,58 +732,77 @@ copy_file_data (gint sfd, static int handle_overwrite_open (const char *filename, + gboolean readable, const char *etag, gboolean create_backup, char **temp_filename, + GFileCreateFlags flags, + GFileInfo *reference_info, GCancellable *cancellable, GError **error) { int fd = -1; - struct stat original_stat; + GLocalFileStat original_stat; char *current_etag; gboolean is_symlink; int open_flags; + int res; + int mode; + + mode = mode_from_flags_or_info (flags, reference_info); /* 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 */ - if (create_backup) - open_flags = O_RDWR | O_CREAT; + if (create_backup || readable) + open_flags = O_RDWR | O_CREAT | O_BINARY; else - open_flags = O_WRONLY | O_CREAT; + open_flags = O_WRONLY | O_CREAT | O_BINARY; /* Some systems have O_NOFOLLOW, which lets us avoid some races * when finding out if the file we opened was a symlink */ #ifdef O_NOFOLLOW 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) { /* Could be a symlink, or it could be a regular ELOOP error, * but then the next open will fail too. */ is_symlink = TRUE; - fd = g_open (filename, open_flags, 0666); + fd = g_open (filename, open_flags, mode); } #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 */ is_symlink = g_file_test (filename, G_FILE_TEST_IS_SYMLINK); #endif 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 (errno), + g_io_error_from_errno (errsv), _("Error opening file '%s': %s"), - filename, g_strerror (errno)); + display_name, g_strerror (errsv)); + g_free (display_name); return -1; } - if (fstat (fd, &original_stat) != 0) +#ifdef G_OS_WIN32 + res = _fstati64 (fd, &original_stat); +#else + res = fstat (fd, &original_stat); +#endif + + if (res != 0) { + int errsv = errno; + char *display_name = g_filename_display_name (filename); g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), - _("Error stating file '%s': %s"), - filename, g_strerror (errno)); + g_io_error_from_errno (errsv), + _("Error when getting information for file '%s': %s"), + display_name, g_strerror (errsv)); + g_free (display_name); goto err_out; } @@ -636,15 +810,15 @@ handle_overwrite_open (const char *filename, if (!S_ISREG (original_stat.st_mode)) { if (S_ISDIR (original_stat.st_mode)) - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_IS_DIRECTORY, - _("Target file is a directory")); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Target file is a directory")); else - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_REGULAR_FILE, - _("Target file is not a regular file")); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_REGULAR_FILE, + _("Target file is not a regular file")); goto err_out; } @@ -653,10 +827,10 @@ handle_overwrite_open (const char *filename, current_etag = _g_local_file_info_create_etag (&original_stat); if (strcmp (etag, current_etag) != 0) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_WRONG_ETAG, - _("The file was externally modified")); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_WRONG_ETAG, + _("The file was externally modified")); g_free (current_etag); goto err_out; } @@ -674,7 +848,8 @@ handle_overwrite_open (const char *filename, * 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; int tmpfd; @@ -683,41 +858,49 @@ handle_overwrite_open (const char *filename, tmp_filename = g_build_filename (dirname, ".goutputstream-XXXXXX", NULL); g_free (dirname); - tmpfd = g_mkstemp (tmp_filename); + tmpfd = g_mkstemp_full (tmp_filename, (readable ? O_RDWR : O_WRONLY) | O_BINARY, mode); if (tmpfd == -1) { g_free (tmp_filename); goto fallback_strategy; } - /* try to keep permissions */ + /* try to keep permissions (unless replacing) */ - if ( -#ifdef F_CHOWN - fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 || + if ( ! (flags & G_FILE_CREATE_REPLACE_DESTINATION) && + ( +#ifdef HAVE_FCHOWN + fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 || #endif -#ifdef F_CHMOD - fchmod (tmpfd, original_stat.st_mode) == -1 || +#ifdef HAVE_FCHMOD + fchmod (tmpfd, original_stat.st_mode) == -1 || #endif - 0 + 0 + ) ) { - struct stat tmp_statbuf; - + GLocalFileStat tmp_statbuf; + int tres; + +#ifdef G_OS_WIN32 + tres = _fstati64 (tmpfd, &tmp_statbuf); +#else + tres = fstat (tmpfd, &tmp_statbuf); +#endif /* Check that we really needed to change something */ - if (fstat (tmpfd, &tmp_statbuf) != 0 || + if (tres != 0 || original_stat.st_uid != tmp_statbuf.st_uid || original_stat.st_gid != tmp_statbuf.st_gid || original_stat.st_mode != tmp_statbuf.st_mode) { - close (tmpfd); - unlink (tmp_filename); + (void) g_close (tmpfd, NULL); + g_unlink (tmp_filename); g_free (tmp_filename); goto fallback_strategy; } } - close (fd); + (void) g_close (fd, NULL); *temp_filename = tmp_filename; return tmpfd; } @@ -726,32 +909,34 @@ handle_overwrite_open (const char *filename, if (create_backup) { +#if defined(HAVE_FCHOWN) && defined(HAVE_FCHMOD) struct stat tmp_statbuf; +#endif char *backup_filename; int bfd; backup_filename = create_backup_filename (filename); - if (unlink (backup_filename) == -1 && errno != ENOENT) + if (g_unlink (backup_filename) == -1 && errno != ENOENT) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Backup file creation failed")); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); g_free (backup_filename); goto err_out; } bfd = g_open (backup_filename, - O_WRONLY | O_CREAT | O_EXCL, + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, original_stat.st_mode & 0777); if (bfd == -1) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Backup file creation failed")); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); g_free (backup_filename); goto err_out; } @@ -760,14 +945,14 @@ handle_overwrite_open (const char *filename, * original file. If this fails, set the protection * bits for the group same as the protection bits for * others. */ -#ifdef HAVE_FCHOWN +#if defined(HAVE_FCHOWN) && defined(HAVE_FCHMOD) if (fstat (bfd, &tmp_statbuf) != 0) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Backup file creation failed")); - unlink (backup_filename); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_unlink (backup_filename); g_free (backup_filename); goto err_out; } @@ -779,12 +964,12 @@ handle_overwrite_open (const char *filename, (original_stat.st_mode & 0707) | ((original_stat.st_mode & 07) << 3)) != 0) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Backup file creation failed")); - unlink (backup_filename); - close (bfd); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_unlink (backup_filename); + (void) g_close (bfd, NULL); g_free (backup_filename); goto err_out; } @@ -793,57 +978,99 @@ handle_overwrite_open (const char *filename, if (!copy_file_data (fd, bfd, NULL)) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Backup file creation failed")); - unlink (backup_filename); - close (bfd); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_CANT_CREATE_BACKUP, + _("Backup file creation failed")); + g_unlink (backup_filename); + (void) g_close (bfd, NULL); g_free (backup_filename); goto err_out; } - close (bfd); + (void) g_close (bfd, NULL); g_free (backup_filename); /* Seek back to the start of the file after the backup copy */ if (lseek (fd, 0, SEEK_SET) == -1) { + int errsv = errno; + g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), + g_io_error_from_errno (errsv), _("Error seeking in file: %s"), - g_strerror (errno)); + g_strerror (errsv)); goto err_out; } } - /* Truncate the file at the start */ + if (flags & G_FILE_CREATE_REPLACE_DESTINATION) + { + (void) g_close (fd, NULL); + + if (g_unlink (filename) != 0) + { + int errsv = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errsv), + _("Error removing old file: %s"), + g_strerror (errsv)); + goto err_out2; + } + + if (readable) + open_flags = O_RDWR | O_CREAT | O_BINARY; + else + open_flags = O_WRONLY | O_CREAT | O_BINARY; + fd = g_open (filename, open_flags, 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) + if (g_win32_ftruncate (fd, 0) == -1) #else - if (ftruncate (fd, 0) == -1) + if (ftruncate (fd, 0) == -1) #endif - { - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errno), - _("Error truncating file: %s"), - g_strerror (errno)); - goto err_out; + { + 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; err_out: - close (fd); + (void) g_close (fd, NULL); + err_out2: return -1; } GFileOutputStream * _g_local_file_output_stream_replace (const char *filename, + gboolean readable, const char *etag, gboolean create_backup, GFileCreateFlags flags, + GFileInfo *reference_info, GCancellable *cancellable, GError **error) { @@ -851,50 +1078,53 @@ _g_local_file_output_stream_replace (const char *filename, int mode; int fd; char *temp_file; + gboolean sync_on_close; + int open_flags; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return NULL; temp_file = NULL; - if (flags & G_FILE_CREATE_PRIVATE) - mode = 0600; - else - mode = 0666; + mode = mode_from_flags_or_info (flags, reference_info); + sync_on_close = FALSE; /* If the file doesn't exist, create it */ - fd = g_open (filename, - O_CREAT | O_EXCL | O_WRONLY, - mode); + open_flags = O_CREAT | O_EXCL | O_BINARY; + if (readable) + open_flags |= O_RDWR; + else + open_flags |= O_WRONLY; + fd = g_open (filename, open_flags, mode); if (fd == -1 && errno == EEXIST) { /* The file already exists */ - fd = handle_overwrite_open (filename, etag, create_backup, &temp_file, - cancellable, error); + fd = handle_overwrite_open (filename, readable, etag, + create_backup, &temp_file, + flags, reference_info, + 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) { - int errsv = errno; - - if (errsv == EINVAL) - /* This must be an invalid filename, on e.g. FAT */ - g_set_error (error, G_IO_ERROR, - G_IO_ERROR_INVALID_FILENAME, - _("Invalid filename")); - else - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errsv), - _("Error opening file '%s': %s"), - filename, g_strerror (errsv)); + set_error_from_open_errno (filename, error); return NULL; } 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); @@ -902,3 +1132,19 @@ _g_local_file_output_stream_replace (const char *filename, return G_FILE_OUTPUT_STREAM (stream); } + +gint +_g_local_file_output_stream_get_fd (GLocalFileOutputStream *stream) +{ + g_return_val_if_fail (G_IS_LOCAL_FILE_OUTPUT_STREAM (stream), -1); + return stream->priv->fd; +} + +#ifdef G_OS_UNIX +static int +g_local_file_output_stream_get_fd (GFileDescriptorBased *fd_based) +{ + GLocalFileOutputStream *stream = G_LOCAL_FILE_OUTPUT_STREAM (fd_based); + return _g_local_file_output_stream_get_fd (stream); +} +#endif