mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
Merge branch 'file-permissions-redux' into 'master'
Fix handling of G_FILE_COPY_TARGET_DEFAULT_PERMISSIONS in g_file_copy() Closes #174 See merge request GNOME/glib!1134
This commit is contained in:
commit
406c8b04fc
@ -226,6 +226,10 @@
|
||||
<term><option>-P</option>, <option>--no-dereference</option></term>
|
||||
<listitem><para>Never follow symbolic links.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--default-permissions</option></term>
|
||||
<listitem><para>Use the default permissions of the current process for the destination file, rather than copying the permissions of the source file.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect3>
|
||||
</listitem>
|
||||
|
33
gio/gfile.c
33
gio/gfile.c
@ -3188,6 +3188,7 @@ file_copy_fallback (GFile *source,
|
||||
const char *target;
|
||||
char *attrs_to_read;
|
||||
gboolean do_set_attributes = FALSE;
|
||||
GFileCreateFlags create_flags;
|
||||
|
||||
/* need to know the file type */
|
||||
info = g_file_query_info (source,
|
||||
@ -3277,19 +3278,38 @@ file_copy_fallback (GFile *source,
|
||||
*
|
||||
* If a future API like g_file_replace_with_info() is added, switch
|
||||
* this code to use that.
|
||||
*
|
||||
* Use %G_FILE_CREATE_PRIVATE unless
|
||||
* - we were told to create the file with default permissions (i.e. the
|
||||
* process’ umask),
|
||||
* - or if the source file is on a file system which doesn’t support
|
||||
* `unix::mode` (in which case it probably also makes sense to create the
|
||||
* destination with default permissions because the source cannot be
|
||||
* private),
|
||||
* - or if the destination file is a `GLocalFile`, in which case we can
|
||||
* directly open() it with the permissions from the source file.
|
||||
*/
|
||||
create_flags = G_FILE_CREATE_NONE;
|
||||
if (!(flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) &&
|
||||
g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE) &&
|
||||
!G_IS_LOCAL_FILE (destination))
|
||||
create_flags |= G_FILE_CREATE_PRIVATE;
|
||||
if (flags & G_FILE_COPY_OVERWRITE)
|
||||
create_flags |= G_FILE_CREATE_REPLACE_DESTINATION;
|
||||
|
||||
if (G_IS_LOCAL_FILE (destination))
|
||||
{
|
||||
if (flags & G_FILE_COPY_OVERWRITE)
|
||||
out = (GOutputStream*)_g_local_file_output_stream_replace (_g_local_file_get_filename (G_LOCAL_FILE (destination)),
|
||||
FALSE, NULL,
|
||||
flags & G_FILE_COPY_BACKUP,
|
||||
G_FILE_CREATE_REPLACE_DESTINATION |
|
||||
G_FILE_CREATE_PRIVATE, info,
|
||||
create_flags,
|
||||
(flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) ? NULL : info,
|
||||
cancellable, error);
|
||||
else
|
||||
out = (GOutputStream*)_g_local_file_output_stream_create (_g_local_file_get_filename (G_LOCAL_FILE (destination)),
|
||||
FALSE, G_FILE_CREATE_PRIVATE, info,
|
||||
FALSE, create_flags,
|
||||
(flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) ? NULL : info,
|
||||
cancellable, error);
|
||||
}
|
||||
else if (flags & G_FILE_COPY_OVERWRITE)
|
||||
@ -3297,13 +3317,12 @@ file_copy_fallback (GFile *source,
|
||||
out = (GOutputStream *)g_file_replace (destination,
|
||||
NULL,
|
||||
flags & G_FILE_COPY_BACKUP,
|
||||
G_FILE_CREATE_REPLACE_DESTINATION |
|
||||
G_FILE_CREATE_PRIVATE,
|
||||
create_flags,
|
||||
cancellable, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
out = (GOutputStream *)g_file_create (destination, G_FILE_CREATE_PRIVATE, cancellable, error);
|
||||
out = (GOutputStream *)g_file_create (destination, create_flags, cancellable, error);
|
||||
}
|
||||
|
||||
if (!out)
|
||||
@ -4027,7 +4046,7 @@ g_file_make_symbolic_link (GFile *file,
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
_("Operation not supported"));
|
||||
_("Symbolic links not supported"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -111,10 +111,13 @@ typedef struct _GFileIface GFileIface;
|
||||
* @make_directory: Makes a directory.
|
||||
* @make_directory_async: Asynchronously makes a directory.
|
||||
* @make_directory_finish: Finishes making a directory asynchronously.
|
||||
* @make_symbolic_link: Makes a symbolic link.
|
||||
* @make_symbolic_link: (nullable): Makes a symbolic link. %NULL if symbolic
|
||||
* links are unsupported.
|
||||
* @_make_symbolic_link_async: Asynchronously makes a symbolic link
|
||||
* @_make_symbolic_link_finish: Finishes making a symbolic link asynchronously.
|
||||
* @copy: Copies a file.
|
||||
* @copy: (nullable): Copies a file. %NULL if copying is unsupported, which will
|
||||
* cause `GFile` to use a fallback copy method where it reads from the
|
||||
* source and writes to the destination.
|
||||
* @copy_async: Asynchronously copies a file.
|
||||
* @copy_finish: Finishes an asynchronous copy operation.
|
||||
* @move: Moves a file.
|
||||
|
@ -37,6 +37,7 @@ static gboolean interactive = FALSE;
|
||||
static gboolean preserve = FALSE;
|
||||
static gboolean backup = FALSE;
|
||||
static gboolean no_dereference = FALSE;
|
||||
static gboolean default_permissions = FALSE;
|
||||
|
||||
static const GOptionEntry entries[] = {
|
||||
{ "no-target-directory", 'T', 0, G_OPTION_ARG_NONE, &no_target_directory, N_("No target directory"), NULL },
|
||||
@ -45,6 +46,7 @@ static const GOptionEntry entries[] = {
|
||||
{ "preserve", 'p', 0, G_OPTION_ARG_NONE, &preserve, N_("Preserve all attributes"), NULL },
|
||||
{ "backup", 'b', 0, G_OPTION_ARG_NONE, &backup, N_("Backup existing destination files"), NULL },
|
||||
{ "no-dereference", 'P', 0, G_OPTION_ARG_NONE, &no_dereference, N_("Never follow symbolic links"), NULL },
|
||||
{ "default-permissions", 0, 0, G_OPTION_ARG_NONE, &default_permissions, N_("Use default permissions for the destination"), NULL },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
@ -175,6 +177,8 @@ handle_copy (int argc, char *argv[], gboolean do_help)
|
||||
flags |= G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
||||
if (preserve)
|
||||
flags |= G_FILE_COPY_ALL_METADATA;
|
||||
if (default_permissions)
|
||||
flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS;
|
||||
|
||||
error = NULL;
|
||||
start_time = g_get_monotonic_time ();
|
||||
|
@ -2330,13 +2330,13 @@ g_local_file_make_directory (GFile *file,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SYMLINK
|
||||
static gboolean
|
||||
g_local_file_make_symbolic_link (GFile *file,
|
||||
const char *symlink_value,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
#ifdef HAVE_SYMLINK
|
||||
GLocalFile *local = G_LOCAL_FILE (file);
|
||||
|
||||
if (symlink (symlink_value, local->filename) == -1)
|
||||
@ -2359,26 +2359,8 @@ g_local_file_make_symbolic_link (GFile *file,
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
#else
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Symbolic links not supported"));
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
g_local_file_copy (GFile *source,
|
||||
GFile *destination,
|
||||
GFileCopyFlags flags,
|
||||
GCancellable *cancellable,
|
||||
GFileProgressCallback progress_callback,
|
||||
gpointer progress_callback_data,
|
||||
GError **error)
|
||||
{
|
||||
/* Fall back to default copy */
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Copy not supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
g_local_file_move (GFile *source,
|
||||
@ -2979,8 +2961,9 @@ g_local_file_file_iface_init (GFileIface *iface)
|
||||
iface->delete_file = g_local_file_delete;
|
||||
iface->trash = g_local_file_trash;
|
||||
iface->make_directory = g_local_file_make_directory;
|
||||
#ifdef HAVE_SYMLINK
|
||||
iface->make_symbolic_link = g_local_file_make_symbolic_link;
|
||||
iface->copy = g_local_file_copy;
|
||||
#endif
|
||||
iface->move = g_local_file_move;
|
||||
iface->monitor_dir = g_local_file_monitor_dir;
|
||||
iface->monitor_file = g_local_file_monitor_file;
|
||||
|
@ -984,7 +984,7 @@ set_info_from_stat (GFileInfo *info,
|
||||
/* Mostly pointless on Windows.
|
||||
* Still, it allows for S_ISREG/S_ISDIR and IWRITE (read-only) checks.
|
||||
*/
|
||||
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_MODE, statbuf->st_mode);
|
||||
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_MODE, statbuf->st_mode & ~S_IFMT);
|
||||
#if defined (HAVE_STRUCT_STAT_ST_BLKSIZE)
|
||||
_g_file_info_set_attribute_uint32_by_id (info, G_FILE_ATTRIBUTE_ID_UNIX_BLOCK_SIZE, statbuf->st_blksize);
|
||||
#endif
|
||||
|
@ -979,7 +979,7 @@ handle_overwrite_open (const char *filename,
|
||||
fchown (tmpfd, original_stat.st_uid, original_stat.st_gid) == -1 ||
|
||||
#endif
|
||||
#ifdef HAVE_FCHMOD
|
||||
fchmod (tmpfd, original_stat.st_mode) == -1 ||
|
||||
fchmod (tmpfd, original_stat.st_mode & ~S_IFMT) == -1 ||
|
||||
#endif
|
||||
0
|
||||
)
|
||||
|
128
gio/tests/file.c
128
gio/tests/file.c
@ -846,58 +846,102 @@ test_async_delete (void)
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
static void
|
||||
test_copy_preserve_mode (void)
|
||||
{
|
||||
GFile *tmpfile;
|
||||
GFile *dest_tmpfile;
|
||||
GFileInfo *dest_info;
|
||||
GFileIOStream *iostream;
|
||||
GError *local_error = NULL;
|
||||
GError **error = &local_error;
|
||||
guint32 romode = S_IFREG | 0600;
|
||||
guint32 dest_mode;
|
||||
#ifdef G_OS_UNIX
|
||||
mode_t current_umask = umask (0);
|
||||
const struct
|
||||
{
|
||||
guint32 source_mode;
|
||||
guint32 expected_destination_mode;
|
||||
gboolean create_destination_before_copy;
|
||||
GFileCopyFlags copy_flags;
|
||||
}
|
||||
vectors[] =
|
||||
{
|
||||
/* Overwriting the destination file should copy the permissions from the
|
||||
* source file, even if %G_FILE_COPY_ALL_METADATA is set: */
|
||||
{ 0600, 0600, TRUE, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA },
|
||||
{ 0600, 0600, TRUE, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS },
|
||||
/* The same behaviour should hold if the destination file is not being
|
||||
* overwritten because it doesn’t already exist: */
|
||||
{ 0600, 0600, FALSE, G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA },
|
||||
{ 0600, 0600, FALSE, G_FILE_COPY_NOFOLLOW_SYMLINKS },
|
||||
/* Anything with %G_FILE_COPY_TARGET_DEFAULT_PERMS should use the current
|
||||
* umask for the destination file: */
|
||||
{ 0600, 0666 & ~current_umask, TRUE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA },
|
||||
{ 0600, 0666 & ~current_umask, TRUE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS },
|
||||
{ 0600, 0666 & ~current_umask, FALSE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA },
|
||||
{ 0600, 0666 & ~current_umask, FALSE, G_FILE_COPY_TARGET_DEFAULT_PERMS | G_FILE_COPY_NOFOLLOW_SYMLINKS },
|
||||
};
|
||||
gsize i;
|
||||
|
||||
tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX",
|
||||
&iostream, error);
|
||||
g_assert_no_error (local_error);
|
||||
g_io_stream_close ((GIOStream*)iostream, NULL, error);
|
||||
g_assert_no_error (local_error);
|
||||
g_clear_object (&iostream);
|
||||
/* Reset the umask after querying it above. There’s no way to query it without
|
||||
* changing it. */
|
||||
umask (current_umask);
|
||||
g_test_message ("Current umask: %u", current_umask);
|
||||
|
||||
g_file_set_attribute (tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_ATTRIBUTE_TYPE_UINT32,
|
||||
&romode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
NULL, error);
|
||||
g_assert_no_error (local_error);
|
||||
for (i = 0; i < G_N_ELEMENTS (vectors); i++)
|
||||
{
|
||||
GFile *tmpfile;
|
||||
GFile *dest_tmpfile;
|
||||
GFileInfo *dest_info;
|
||||
GFileIOStream *iostream;
|
||||
GError *local_error = NULL;
|
||||
guint32 romode = vectors[i].source_mode;
|
||||
guint32 dest_mode;
|
||||
|
||||
dest_tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX",
|
||||
&iostream, error);
|
||||
g_assert_no_error (local_error);
|
||||
g_io_stream_close ((GIOStream*)iostream, NULL, error);
|
||||
g_assert_no_error (local_error);
|
||||
g_clear_object (&iostream);
|
||||
g_test_message ("Vector %" G_GSIZE_FORMAT, i);
|
||||
|
||||
g_file_copy (tmpfile, dest_tmpfile, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
|
||||
NULL, NULL, NULL, error);
|
||||
g_assert_no_error (local_error);
|
||||
tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX",
|
||||
&iostream, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
g_io_stream_close ((GIOStream*)iostream, NULL, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
g_clear_object (&iostream);
|
||||
|
||||
dest_info = g_file_query_info (dest_tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
NULL, error);
|
||||
g_assert_no_error (local_error);
|
||||
g_file_set_attribute (tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_ATTRIBUTE_TYPE_UINT32,
|
||||
&romode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
NULL, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
|
||||
dest_mode = g_file_info_get_attribute_uint32 (dest_info, G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
|
||||
g_assert_cmpint (dest_mode, ==, romode);
|
||||
dest_tmpfile = g_file_new_tmp ("tmp-copy-preserve-modeXXXXXX",
|
||||
&iostream, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
g_io_stream_close ((GIOStream*)iostream, NULL, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
g_clear_object (&iostream);
|
||||
|
||||
(void) g_file_delete (tmpfile, NULL, NULL);
|
||||
(void) g_file_delete (dest_tmpfile, NULL, NULL);
|
||||
|
||||
g_clear_object (&tmpfile);
|
||||
g_clear_object (&dest_tmpfile);
|
||||
g_clear_object (&dest_info);
|
||||
}
|
||||
if (!vectors[i].create_destination_before_copy)
|
||||
{
|
||||
g_file_delete (dest_tmpfile, NULL, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
}
|
||||
|
||||
g_file_copy (tmpfile, dest_tmpfile, vectors[i].copy_flags,
|
||||
NULL, NULL, NULL, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
|
||||
dest_info = g_file_query_info (dest_tmpfile, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
NULL, &local_error);
|
||||
g_assert_no_error (local_error);
|
||||
|
||||
dest_mode = g_file_info_get_attribute_uint32 (dest_info, G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
|
||||
g_assert_cmpint (dest_mode, ==, vectors[i].expected_destination_mode);
|
||||
|
||||
(void) g_file_delete (tmpfile, NULL, NULL);
|
||||
(void) g_file_delete (dest_tmpfile, NULL, NULL);
|
||||
|
||||
g_clear_object (&tmpfile);
|
||||
g_clear_object (&dest_tmpfile);
|
||||
g_clear_object (&dest_info);
|
||||
}
|
||||
#else /* if !G_OS_UNIX */
|
||||
g_test_skip ("File permissions tests can only be run on Unix")
|
||||
#endif
|
||||
}
|
||||
|
||||
static gchar *
|
||||
splice_to_string (GInputStream *stream,
|
||||
@ -1755,9 +1799,7 @@ main (int argc, char *argv[])
|
||||
g_test_add_func ("/file/replace-load", test_replace_load);
|
||||
g_test_add_func ("/file/replace-cancel", test_replace_cancel);
|
||||
g_test_add_func ("/file/async-delete", test_async_delete);
|
||||
#ifdef G_OS_UNIX
|
||||
g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode);
|
||||
#endif
|
||||
g_test_add_func ("/file/measure", test_measure);
|
||||
g_test_add_func ("/file/measure-async", test_measure_async);
|
||||
g_test_add_func ("/file/load-bytes", test_load_bytes);
|
||||
|
Loading…
Reference in New Issue
Block a user