/* 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);
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,
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:
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:
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);
* 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,
- };
-
- n_xa = 0;
+ 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;
+ }
}
}