Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 139 additions & 125 deletions src/builder-flatpak-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>

#include <glib.h>
#include "libglnx.h"
#include <gio/gunixoutputstream.h>
#include <gio/gunixinputstream.h>

typedef struct {
dev_t dev;
ino_t ino;
} CpSkipFilesInode;


GFile *
flatpak_file_new_tmp_in (GFile *dir,
Expand Down Expand Up @@ -760,158 +766,166 @@ flatpak_file_is_in (GFile *file,
g_file_has_prefix (canonical_file, canonical_toplevel);
}

gboolean
flatpak_cp_a (GFile *src,
GFile *dest,
GFile *keep_in_toplevel,
FlatpakCpFlags flags,
GPtrArray *skip_files,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GFileEnumerator *enumerator = NULL;
GFileInfo *src_info = NULL;
GFile *dest_child = NULL;
int dest_dfd = -1;
gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0;
gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0;
gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0;
g_autoptr(GFileInfo) child_info = NULL;
GError *temp_error = NULL;
int r;

enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!enumerator)
goto out;

src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
"time::modified,time::modified-usec,time::access,time::access-usec",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!src_info)
goto out;

do
r = mkdir (flatpak_file_get_path_cached (dest), 0755);
while (G_UNLIKELY (r == -1 && errno == EINTR));
if (r == -1)
{
if (!merge || errno != EEXIST)
{
glnx_set_error_from_errno (error);
goto out;
}
static gboolean
flatpak_cp_a_at (int src_dir_fd,
int dst_parent_fd,
const char *dst_name,
const GArray *skip_inodes,
FlatpakCpFlags flags,
GCancellable *cancellable,
GError **error)
{
const gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0;

/* When merging, ensure the new dir is inside the toplevel instead of a symlink outside */
if (keep_in_toplevel != NULL && !flatpak_file_is_in (dest, keep_in_toplevel))
{
flatpak_fail (error, "Recursive copy outside destination bounds");
goto out;
}
}
struct stat src_stat;
glnx_autofd int dst_dir_fd = -1;
g_auto(GLnxDirFdIterator) dir_iter = { 0, };

if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dest), TRUE,
&dest_dfd, error))
goto out;
if (fstat (src_dir_fd, &src_stat) != 0)
return glnx_throw_errno_prefix (error, "fstat");

if (!no_chown)
if (TEMP_FAILURE_RETRY (mkdirat (dst_parent_fd, dst_name, 0755)) < 0)
{
do
r = fchown (dest_dfd,
g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
while (G_UNLIKELY (r == -1 && errno == EINTR));
if (r == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
if (!(merge && errno == EEXIST))
return glnx_throw_errno_prefix (error, "mkdirat(%s)", dst_name);
}

do
r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
while (G_UNLIKELY (r == -1 && errno == EINTR));
if (!glnx_opendirat (dst_parent_fd, dst_name, FALSE, &dst_dir_fd, error))
return FALSE;

if (dest_dfd != -1)
{
(void) close (dest_dfd);
dest_dfd = -1;
}
if (TEMP_FAILURE_RETRY (fchmod (dst_dir_fd, src_stat.st_mode & 07777)) != 0)
return glnx_throw_errno_prefix (error, "fchmod");

while ((child_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error)))
if (!glnx_dirfd_iterator_init_at (src_dir_fd, ".", FALSE, &dir_iter, error))
return FALSE;

while (TRUE)
{
const char *name = g_file_info_get_name (child_info);
g_autoptr(GFile) src_child = g_file_get_child (src, name);
gboolean skip = FALSE;
int i;
struct dirent *dir_entry;
const char *entry_name;
gboolean should_skip = FALSE;
glnx_autofd int chase_fd = -1;
struct glnx_statx stx;

if (!glnx_dirfd_iterator_next_dent (&dir_iter, &dir_entry,
cancellable, error))
return FALSE;

if (dir_entry == NULL)
break;

for (i = 0; skip_files != NULL && i < skip_files->len; i++)
entry_name = dir_entry->d_name;

chase_fd = glnx_chase_and_statxat (dir_iter.fd, entry_name,
GLNX_CHASE_NOFOLLOW,
GLNX_STATX_TYPE | GLNX_STATX_INO,
&stx, error);
if (chase_fd < 0)
return FALSE;

if (skip_inodes != NULL && skip_inodes->len > 0)
{
if (g_file_equal (src_child, g_ptr_array_index (skip_files, i)))
for (size_t i = 0; i < skip_inodes->len; i++)
{
skip = TRUE;
break;
const CpSkipFilesInode *skip = &g_array_index (skip_inodes, CpSkipFilesInode, i);

if (stx.stx_ino == skip->ino &&
stx.stx_dev_major == major (skip->dev) &&
stx.stx_dev_minor == minor (skip->dev))
{
should_skip = TRUE;
break;
}
}
}

if (dest_child)
g_object_unref (dest_child);
dest_child = g_file_get_child (dest, name);
if (should_skip)
continue;

if (skip)
{
/* skip src */
}
else if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
if (S_ISDIR (stx.stx_mode))
{
if (!flatpak_cp_a (src_child, dest_child, keep_in_toplevel, flags, skip_files,
cancellable, error))
goto out;
glnx_autofd int src_child_fd = -1;

src_child_fd = glnx_fd_reopen (chase_fd, O_RDONLY | O_DIRECTORY, error);
if (src_child_fd < 0)
return glnx_prefix_error (error, "Failed to reopen directory %s", entry_name);

if (!flatpak_cp_a_at (src_child_fd, dst_dir_fd, entry_name, skip_inodes,
flags, cancellable, error))
return FALSE;
}
else
{
(void) unlink (flatpak_file_get_path_cached (dest_child));
GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
if (!no_chown)
copyflags |= G_FILE_COPY_ALL_METADATA;
if (move)
{
if (!g_file_move (src_child, dest_child, copyflags,
cancellable, NULL, NULL, error))
goto out;
}
else
{
if (!g_file_copy (src_child, dest_child, copyflags,
cancellable, NULL, NULL, error))
goto out;
}
GLnxFileCopyFlags cp_flags = GLNX_FILE_COPY_OVERWRITE | GLNX_FILE_COPY_NOCHOWN;

(void) unlinkat (dst_dir_fd, entry_name, 0);

if (!glnx_file_copy_at (dir_iter.fd, entry_name, NULL,
dst_dir_fd, entry_name, cp_flags,
cancellable, error))
return FALSE;
}
}

g_clear_object (&child_info);
return TRUE;
}

gboolean
flatpak_cp_a (GFile *src,
GFile *dest,
GFile *keep_in_toplevel,
FlatpakCpFlags flags,
GPtrArray *skip_files,
GCancellable *cancellable,
GError **error)
{
glnx_autofd int src_dfd = -1;
glnx_autofd int dest_parent_fd = -1;
const char *src_path;
const char *dest_path;
g_autofree char *dest_dirname = NULL;
g_autofree char *dest_basename = NULL;
g_autoptr(GArray) skip_inodes = NULL;

if (keep_in_toplevel != NULL && !flatpak_file_is_in (dest, keep_in_toplevel))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
"Recursive copy outside destination");
return FALSE;
}

if (temp_error != NULL)
src_path = flatpak_file_get_path_cached (src);
dest_path = flatpak_file_get_path_cached (dest);

if (!glnx_opendirat (AT_FDCWD, src_path, TRUE, &src_dfd, error))
return FALSE;

dest_dirname = g_path_get_dirname (dest_path);
dest_basename = g_path_get_basename (dest_path);

dest_parent_fd = glnx_chaseat (AT_FDCWD, dest_dirname, GLNX_CHASE_MUST_BE_DIRECTORY, error);
if (dest_parent_fd < 0)
return FALSE;

if (skip_files != NULL && skip_files->len > 0)
{
g_propagate_error (error, temp_error);
goto out;
skip_inodes = g_array_sized_new (FALSE, FALSE, sizeof (CpSkipFilesInode), skip_files->len);
for (size_t i = 0; i < skip_files->len; i++)
{
GFile *skip_file = g_ptr_array_index (skip_files, i);
const char *skip_path = flatpak_file_get_path_cached (skip_file);
struct stat st;

if (skip_path != NULL && stat (skip_path, &st) == 0)
{
CpSkipFilesInode si = { .dev = st.st_dev, .ino = st.st_ino };
g_array_append_val (skip_inodes, si);
}
}
}

if (move &&
!g_file_delete (src, NULL, error))
goto out;

ret = TRUE;
out:
if (dest_dfd != -1)
(void) close (dest_dfd);
g_clear_object (&src_info);
g_clear_object (&enumerator);
g_clear_object (&dest_child);
return ret;
return flatpak_cp_a_at (src_dfd, dest_parent_fd, dest_basename,
skip_inodes, flags, cancellable, error);
}

gboolean
Expand Down
16 changes: 7 additions & 9 deletions src/builder-flatpak-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,15 @@ gboolean flatpak_file_is_in (GFile *file,
typedef enum {
FLATPAK_CP_FLAGS_NONE = 0,
FLATPAK_CP_FLAGS_MERGE = 1<<0,
FLATPAK_CP_FLAGS_NO_CHOWN = 1<<1,
FLATPAK_CP_FLAGS_MOVE = 1<<2,
} FlatpakCpFlags;

gboolean flatpak_cp_a (GFile *src,
GFile *dest,
GFile *keep_in_toplevel,
FlatpakCpFlags flags,
GPtrArray *skip_files,
GCancellable *cancellable,
GError **error);
gboolean flatpak_cp_a (GFile *src,
GFile *dest,
GFile *keep_in_toplevel,
FlatpakCpFlags flags,
GPtrArray *skip_files,
GCancellable *cancellable,
GError **error);

gboolean flatpak_zero_mtime (int parent_dfd,
const char *rel_path,
Expand Down
4 changes: 2 additions & 2 deletions src/builder-source-dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ builder_source_dir_extract (BuilderSource *source,

skip = builder_source_dir_get_skip (source, context);
if (!flatpak_cp_a (src, dest, source_dir,
FLATPAK_CP_FLAGS_MERGE|FLATPAK_CP_FLAGS_NO_CHOWN,
FLATPAK_CP_FLAGS_MERGE,
skip, NULL, error))
return FALSE;

Expand Down Expand Up @@ -251,7 +251,7 @@ builder_source_dir_bundle (BuilderSource *source,
skip = builder_source_dir_get_skip (source, context);
g_mkdir_with_parents (flatpak_file_get_path_cached (dest), 0755);
if (!flatpak_cp_a (src, dest, NULL,
FLATPAK_CP_FLAGS_MERGE|FLATPAK_CP_FLAGS_NO_CHOWN,
FLATPAK_CP_FLAGS_MERGE,
skip, NULL, error))
return FALSE;

Expand Down