tar-util: add support for extracting OCI compatible whiteouts, and turn them into overlayfs whiteouts
This commit is contained in:
+136
-20
@@ -147,8 +147,8 @@ static int open_inode_finalize(OpenInode *of) {
|
||||
/* We adjust the UID/GID right before the mode, since doing this might affect the mode (drops
|
||||
* suid/sgid bits).
|
||||
*
|
||||
* We adjust the mode only when leaving a dir, because if we are unpriv we might lose the
|
||||
* ability to enter it once we do this. */
|
||||
* We adjust the mode only when leaving a dir, because if we are unprivileged we might lose
|
||||
* the ability to enter it once we do this. */
|
||||
|
||||
if (uid_is_valid(of->uid) || gid_is_valid(of->gid) || of->mode != MODE_INVALID) {
|
||||
k = fchmod_and_chown_with_fallback(of->fd, /* path= */ NULL, of->mode, of->uid, of->gid);
|
||||
@@ -277,6 +277,91 @@ static int archive_unpack_regular(
|
||||
return TAKE_FD(fd);
|
||||
}
|
||||
|
||||
static int overlayfs_fsetfattr(
|
||||
const char *path, /* purely decorative, for log purposes */
|
||||
int fd,
|
||||
const char *name, /* xattr key name */
|
||||
const char *value /* xattr value */) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(path);
|
||||
assert(name);
|
||||
assert(value);
|
||||
|
||||
/* overlayfs knows magic {user|trusted}.overlay.* xattrs for whiteouts and opaque directories. The
|
||||
* 'user.overlay.*' ones are only checked if overlayfs is mounted with "userxattr". We only set that
|
||||
* one because we want to operate unprivileged. Ideally, we'd set both here, to maximize the chance
|
||||
* that things work both in privileged and unprivileged scenarios, but unfortunately this has the
|
||||
* effect that the privileged ones are ignored (and visible in the overlayfs mount). */
|
||||
_cleanup_free_ char *n = strjoin("user.overlay.", name);
|
||||
if (!n)
|
||||
return log_oom();
|
||||
|
||||
r = xsetxattr(fd, /* path= */ NULL, AT_EMPTY_PATH, n, value);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set '%s' xattr on file '%s': %m", n, path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int archive_unpack_whiteout(
|
||||
struct archive *a,
|
||||
struct archive_entry *entry,
|
||||
int parent_fd,
|
||||
const char *parent_path, /* Full path of 'parent_fd', purely decorative for log purposes */
|
||||
const char *filename, /* Just the filename we are supposed to whiteout */
|
||||
const char *path /* Full path of the whiteout file, purely decorative for log purposes */) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(a);
|
||||
assert(entry);
|
||||
assert(parent_fd >= 0);
|
||||
assert(parent_path);
|
||||
assert(filename);
|
||||
assert(path);
|
||||
|
||||
_cleanup_free_ char *tmp = NULL;
|
||||
_cleanup_close_ int fd = open_tmpfile_linkable_at(parent_fd, filename, O_CLOEXEC|O_WRONLY, &tmp);
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to create whiteout file for '%s': %m", path);
|
||||
|
||||
CLEANUP_TMPFILE_AT(parent_fd, tmp);
|
||||
|
||||
r = overlayfs_fsetfattr(path, fd, "whiteout", "y");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* As per https://docs.kernel.org/filesystems/overlayfs.html also mark the parent */
|
||||
r = overlayfs_fsetfattr(parent_path, parent_fd, "opaque", "x");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = link_tmpfile_at(fd, parent_fd, tmp, filename, LINK_TMPFILE_REPLACE);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to install regular file '%s': %m", path);
|
||||
|
||||
tmp = mfree(tmp); /* disarm CLEANUP_TMPFILE_AT */
|
||||
return 0; /* we do not return an fd here, because this kills an inode, and doesn't synthesize one */
|
||||
}
|
||||
|
||||
static int archive_unpack_opaque(
|
||||
struct archive *a,
|
||||
struct archive_entry *entry,
|
||||
int parent_fd,
|
||||
const char *parent_path) {
|
||||
|
||||
assert(a);
|
||||
assert(entry);
|
||||
assert(parent_fd >= 0);
|
||||
assert(parent_path);
|
||||
|
||||
/* we do not return an fd here either */
|
||||
|
||||
return overlayfs_fsetfattr(parent_path, parent_fd, "opaque", "y");
|
||||
}
|
||||
|
||||
static int archive_unpack_directory(
|
||||
struct archive *a,
|
||||
struct archive_entry *entry,
|
||||
@@ -908,15 +993,45 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
|
||||
switch (filetype) {
|
||||
|
||||
case S_IFREG:
|
||||
if (FLAGS_SET(flags, TAR_OCI_WHITEOUTS)) {
|
||||
if (streq(e, ".wh..wh..opq")) {
|
||||
r = archive_unpack_opaque(a, entry, parent_fd, empty_to_root(parent_path));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* NB: this does not create an inode! */
|
||||
break;
|
||||
}
|
||||
|
||||
const char *w = startswith(e, ".wh.");
|
||||
if (w) {
|
||||
if (!filename_is_valid(w))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Invalid whiteout file entry '%s', refusing.", e);
|
||||
|
||||
r = archive_unpack_whiteout(a, entry, parent_fd, empty_to_root(parent_path), w, j);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* NB: this does not create an inode! */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fd = archive_unpack_regular(a, entry, parent_fd, e, j, fflags);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
break;
|
||||
|
||||
case S_IFDIR:
|
||||
fd = archive_unpack_directory(a, entry, parent_fd, e, j, fflags);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
break;
|
||||
|
||||
case S_IFLNK:
|
||||
fd = archive_unpack_symlink(a, entry, parent_fd, e, j);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
break;
|
||||
|
||||
case S_IFCHR:
|
||||
@@ -924,6 +1039,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
|
||||
case S_IFIFO:
|
||||
case S_IFSOCK:
|
||||
fd = archive_unpack_special_inode(a, entry, parent_fd, e, j, filetype);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -931,9 +1048,6 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
|
||||
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Unexpected file type %i of '%s', refusing.", (int) filetype, j);
|
||||
}
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
} else {
|
||||
/* This is some intermediary node in the path that we haven't opened yet. Create it with default attributes */
|
||||
fd = open_mkdir_at(parent_fd, e, O_CLOEXEC, 0700);
|
||||
@@ -949,22 +1063,24 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
|
||||
* fully done with the inode (i.e. after creating further inodes inside of dir inodes
|
||||
* for example), due to permission problems this might create or that the mtime
|
||||
* changes we do might still be affected by our changes. */
|
||||
open_inodes[n_open_inodes++] = (OpenInode) {
|
||||
.fd = TAKE_FD(fd),
|
||||
.path = TAKE_PTR(j),
|
||||
.filetype = filetype,
|
||||
.mode = mode,
|
||||
.mtime = mtime,
|
||||
.uid = uid,
|
||||
.gid = gid,
|
||||
.fflags = fflags,
|
||||
.acl_access = TAKE_PTR(acl_access),
|
||||
.acl_default = TAKE_PTR(acl_default),
|
||||
.xattr = TAKE_PTR(xa),
|
||||
.n_xattr = n_xa,
|
||||
};
|
||||
if (fd >= 0) {
|
||||
open_inodes[n_open_inodes++] = (OpenInode) {
|
||||
.fd = TAKE_FD(fd),
|
||||
.path = TAKE_PTR(j),
|
||||
.filetype = filetype,
|
||||
.mode = mode,
|
||||
.mtime = mtime,
|
||||
.uid = uid,
|
||||
.gid = gid,
|
||||
.fflags = fflags,
|
||||
.acl_access = TAKE_PTR(acl_access),
|
||||
.acl_default = TAKE_PTR(acl_default),
|
||||
.xattr = TAKE_PTR(xa),
|
||||
.n_xattr = n_xa,
|
||||
};
|
||||
|
||||
n_xa = 0;
|
||||
n_xa = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
typedef enum TarFlags {
|
||||
TAR_SELINUX = 1 << 0, /* Include SELinux xattr in tarball, or unpack it */
|
||||
TAR_SQUASH_UIDS_ABOVE_64K = 1 << 1, /* Squash UIDs/GIDs above 64K when packing/unpacking to the nobody user */
|
||||
TAR_OCI_WHITEOUTS = 1 << 2, /* Turn OCI/aufs whiteout inodes into overlayfs whiteouts */
|
||||
} TarFlags;
|
||||
|
||||
int tar_x(int input_fd, int tree_fd, TarFlags flags);
|
||||
|
||||
Reference in New Issue
Block a user