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:
Philip Withnall 2019-10-02 15:40:09 +00:00
commit 406c8b04fc
8 changed files with 130 additions and 75 deletions

View File

@ -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>

View File

@ -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 doesnt 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;
}

View File

@ -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.

View 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 ();

View File

@ -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;

View 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

View File

@ -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
)

View File

@ -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 doesnt 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. Theres 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);