From b456badba3aa5baef25713f234d7e3fc0735c9c0 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 10 Jan 2016 17:00:46 -0500 Subject: [PATCH 1/2] Add testing-only `internals` subcommand I'd like to experiment with different things that end up reusing chunks of the rpm-ostree internals, such as libhif, the helpers we already have around RPM, etc. In this particular case I'm experimenting with unpacking/committing RPM packages as non-root. Eventually most of this should end up as internal private shared library, but it's convenient to have an ABI-unstable and hidden "internals" command to run things directly. This commit though just adds the scaffolding for "internals". --- Makefile-rpm-ostree.am | 1 + src/app/main.c | 4 +- src/app/rpmostree-builtin-internals.c | 147 +++++++++++++++++++++++++ src/app/rpmostree-builtins.h | 1 + src/app/rpmostree-internals-builtins.h | 30 +++++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/app/rpmostree-builtin-internals.c create mode 100644 src/app/rpmostree-internals-builtins.h diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index f983d509..eb924601 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -28,6 +28,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-builtin-deploy.c \ src/app/rpmostree-builtin-rebase.c \ src/app/rpmostree-builtin-status.c \ + src/app/rpmostree-builtin-internals.c \ src/app/rpmostree-builtin-db.c \ src/app/rpmostree-db-builtin-diff.c \ src/app/rpmostree-db-builtin-list.c \ diff --git a/src/app/main.c b/src/app/main.c index 944c3d39..0621b52b 100644 --- a/src/app/main.c +++ b/src/app/main.c @@ -43,6 +43,7 @@ static RpmOstreeCommand commands[] = { { "rollback", rpmostree_builtin_rollback }, { "status", rpmostree_builtin_status }, { "upgrade", rpmostree_builtin_upgrade }, + { "internals", rpmostree_builtin_internals }, { NULL } }; @@ -74,7 +75,8 @@ option_context_new_with_commands (void) while (command->name != NULL) { - g_string_append_printf (summary, "\n %s", command->name); + if (strcmp (command->name, "internals") != 0) + g_string_append_printf (summary, "\n %s", command->name); command++; } diff --git a/src/app/rpmostree-builtin-internals.c b/src/app/rpmostree-builtin-internals.c new file mode 100644 index 00000000..b882dde9 --- /dev/null +++ b/src/app/rpmostree-builtin-internals.c @@ -0,0 +1,147 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2015 Colin Walters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "rpmostree-internals-builtins.h" +#include "rpmostree-rpm-util.h" + +typedef struct { + const char *name; + int (*fn) (int argc, char **argv, GCancellable *cancellable, GError **error); +} RpmOstreeInternalsCommand; + +static RpmOstreeInternalsCommand internals_subcommands[] = { + { NULL, NULL } +}; + +static GOptionEntry global_entries[] = { + { NULL } +}; + +static GOptionContext * +internals_option_context_new_with_commands (void) +{ + RpmOstreeInternalsCommand *command = internals_subcommands; + GOptionContext *context; + GString *summary; + + context = g_option_context_new ("COMMAND"); + + summary = g_string_new ("Builtin \"internals\" Commands:"); + + while (command->name != NULL) + { + g_string_append_printf (summary, "\n %s", command->name); + command++; + } + + g_option_context_set_summary (context, summary->str); + + g_string_free (summary, TRUE); + + return context; +} + +int +rpmostree_builtin_internals (int argc, char **argv, GCancellable *cancellable, GError **error) +{ + RpmOstreeInternalsCommand *subcommand; + const char *subcommand_name = NULL; + gs_free char *prgname = NULL; + int exit_status = EXIT_SUCCESS; + int in, out; + + for (in = 1, out = 1; in < argc; in++, out++) + { + /* The non-option is the command, take it out of the arguments */ + if (argv[in][0] != '-') + { + if (subcommand_name == NULL) + { + subcommand_name = argv[in]; + out--; + continue; + } + } + + else if (g_str_equal (argv[in], "--")) + { + break; + } + + argv[out] = argv[in]; + } + + argc = out; + + subcommand = internals_subcommands; + while (subcommand->name) + { + if (g_strcmp0 (subcommand_name, subcommand->name) == 0) + break; + subcommand++; + } + + if (!subcommand->name) + { + GOptionContext *context; + gs_free char *help = NULL; + + context = internals_option_context_new_with_commands (); + + /* This will not return for some options (e.g. --version). */ + if (rpmostree_option_context_parse (context, NULL, + &argc, &argv, + RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, + cancellable, + NULL, + error)) + { + if (subcommand_name == NULL) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "No \"internals\" subcommand specified"); + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown \"internals\" subcommand '%s'", subcommand_name); + } + exit_status = EXIT_FAILURE; + } + + help = g_option_context_get_help (context, FALSE, NULL); + g_printerr ("%s", help); + + g_option_context_free (context); + + goto out; + } + + prgname = g_strdup_printf ("%s %s", g_get_prgname (), subcommand_name); + g_set_prgname (prgname); + + exit_status = subcommand->fn (argc, argv, cancellable, error); + + out: + return exit_status; +} + diff --git a/src/app/rpmostree-builtins.h b/src/app/rpmostree-builtins.h index faf770e0..e3d43b0b 100644 --- a/src/app/rpmostree-builtins.h +++ b/src/app/rpmostree-builtins.h @@ -48,6 +48,7 @@ BUILTINPROTO(rebase); BUILTINPROTO(rollback); BUILTINPROTO(status); BUILTINPROTO(db); +BUILTINPROTO(internals); #undef BUILTINPROTO diff --git a/src/app/rpmostree-internals-builtins.h b/src/app/rpmostree-internals-builtins.h new file mode 100644 index 00000000..1707ed15 --- /dev/null +++ b/src/app/rpmostree-internals-builtins.h @@ -0,0 +1,30 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2015 Colin Walters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include + +#include "rpmostree-builtins.h" + +G_BEGIN_DECLS + +G_END_DECLS + From ec4387afba9b4e6f0b76a90d31ee58e7986f707e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 11 Jan 2016 23:07:24 -0500 Subject: [PATCH 2/2] internals: New `unpack` verb This is part of taking over from librpm. The most important high level goal is fully unprivilged operation. Right now we're basically starting to do what http://libguestfs.org/supermin.1.html does, except in C, and faster. There's no reason that `compose tree` should require privileges. However right now, things like `%post` scripts will want to run in the target root - so we'd have to require `linux-user-chroot`. Regardless of unprivileged operation though, another major thing we can do is use our control over the unpacking process to do a lot more sophisticated caching. We can build up a precise mapping of (rpm ENVR, file path, selinux label) -> object and avoid rechecksumming each time. And even for files that aren't known, we can parallelize commit with unpacking, etc. (Ok assuming treecompose-post won't mutate anything). --- Makefile-libpriv.am | 2 + Makefile-rpm-ostree.am | 1 + libglnx | 2 +- src/app/rpmostree-builtin-internals.c | 3 + src/app/rpmostree-internals-builtin-unpack.c | 103 ++++ src/app/rpmostree-internals-builtins.h | 2 + src/libpriv/rpmostree-unpacker.c | 472 +++++++++++++++++++ src/libpriv/rpmostree-unpacker.h | 48 ++ 8 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 src/app/rpmostree-internals-builtin-unpack.c create mode 100644 src/libpriv/rpmostree-unpacker.c create mode 100644 src/libpriv/rpmostree-unpacker.h diff --git a/Makefile-libpriv.am b/Makefile-libpriv.am index fcea4340..29061bfc 100644 --- a/Makefile-libpriv.am +++ b/Makefile-libpriv.am @@ -35,6 +35,8 @@ librpmostreepriv_la_SOURCES = \ src/libpriv/rpmostree-cleanup.h \ src/libpriv/rpmostree-rpm-util.c \ src/libpriv/rpmostree-rpm-util.h \ + src/libpriv/rpmostree-unpacker.c \ + src/libpriv/rpmostree-unpacker.h \ $(NULL) librpmostreepriv_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/src/libpriv -I$(libglnx_srcpath) -DPKGLIBDIR=\"$(pkglibdir)\" $(PKGDEP_RPMOSTREE_CFLAGS) librpmostreepriv_la_LIBADD = $(AM_LDFLAGS) $(PKGDEP_RPMOSTREE_LIBS) libglnx.la $(CAP_LIBS) diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index eb924601..0905dc44 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -35,6 +35,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-db-builtin-version.c \ src/app/rpmostree-dbus-helpers.c \ src/app/rpmostree-dbus-helpers.h \ + src/app/rpmostree-internals-builtin-unpack.c \ src/app/rpmostree-libbuiltin.c \ src/app/rpmostree-libbuiltin.h \ $(NULL) diff --git a/libglnx b/libglnx index 91e06069..3c470803 160000 --- a/libglnx +++ b/libglnx @@ -1 +1 @@ -Subproject commit 91e060699f5559c49335ce19f4b23ba70dfd6bb3 +Subproject commit 3c470803d08477dab9c7faada29a5f4c59dd519e diff --git a/src/app/rpmostree-builtin-internals.c b/src/app/rpmostree-builtin-internals.c index b882dde9..10cbc2a0 100644 --- a/src/app/rpmostree-builtin-internals.c +++ b/src/app/rpmostree-builtin-internals.c @@ -29,12 +29,15 @@ typedef struct { } RpmOstreeInternalsCommand; static RpmOstreeInternalsCommand internals_subcommands[] = { + { "unpack", rpmostree_internals_builtin_unpack }, { NULL, NULL } }; +/* static GOptionEntry global_entries[] = { { NULL } }; +*/ static GOptionContext * internals_option_context_new_with_commands (void) diff --git a/src/app/rpmostree-internals-builtin-unpack.c b/src/app/rpmostree-internals-builtin-unpack.c new file mode 100644 index 00000000..228609dc --- /dev/null +++ b/src/app/rpmostree-internals-builtin-unpack.c @@ -0,0 +1,103 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2015 Colin Walters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpmostree-internals-builtins.h" +#include "rpmostree-util.h" +#include "rpmostree-hif.h" +#include "rpmostree-cleanup.h" +#include "rpmostree-libbuiltin.h" +#include "rpmostree-rpm-util.h" +#include "rpmostree-unpacker.h" + +#include "libgsystem.h" + +gboolean opt_suid_fcaps = FALSE; +gboolean opt_owner = FALSE; + +static GOptionEntry option_entries[] = { + { "suid-fcaps", 0, 0, G_OPTION_ARG_NONE, &opt_suid_fcaps, "Enable setting suid/sgid and capabilities", NULL }, + { "owner", 0, 0, G_OPTION_ARG_NONE, &opt_owner, "Enable chown", NULL }, + { NULL } +}; + +int +rpmostree_internals_builtin_unpack (int argc, + char **argv, + GCancellable *cancellable, + GError **error) +{ + int exit_status = EXIT_FAILURE; + GOptionContext *context = g_option_context_new ("ROOT RPM"); + RpmOstreeUnpackerFlags flags = 0; + glnx_unref_object RpmOstreeUnpacker *unpacker = NULL; + const char *rpmpath; + glnx_fd_close int rootfs_fd = -1; + + if (!rpmostree_option_context_parse (context, + option_entries, + &argc, &argv, + RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD, + cancellable, + NULL, + error)) + goto out; + + if (argc < 3) + { + rpmostree_usage_error (context, "ROOT and RPM must be specified", error); + goto out; + } + + if (!glnx_opendirat (AT_FDCWD, argv[1], TRUE, &rootfs_fd, error)) + goto out; + + rpmpath = argv[2]; + + /* suid implies owner too...anything else is dangerous, as we might write + * a setuid binary for the caller. + */ + if (opt_owner || opt_suid_fcaps) + flags |= RPMOSTREE_UNPACKER_FLAGS_OWNER; + if (opt_suid_fcaps) + flags |= RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS; + + unpacker = rpmostree_unpacker_new_at (AT_FDCWD, rpmpath, flags, error); + if (!unpacker) + goto out; + + if (!rpmostree_unpacker_unpack_to_dfd (unpacker, rootfs_fd, cancellable, error)) + goto out; + + exit_status = EXIT_SUCCESS; + out: + return exit_status; +} diff --git a/src/app/rpmostree-internals-builtins.h b/src/app/rpmostree-internals-builtins.h index 1707ed15..e7f44ca4 100644 --- a/src/app/rpmostree-internals-builtins.h +++ b/src/app/rpmostree-internals-builtins.h @@ -26,5 +26,7 @@ G_BEGIN_DECLS +gboolean rpmostree_internals_builtin_unpack (int argc, char **argv, GCancellable *cancellable, GError **error); + G_END_DECLS diff --git a/src/libpriv/rpmostree-unpacker.c b/src/libpriv/rpmostree-unpacker.c new file mode 100644 index 00000000..1e9c9f98 --- /dev/null +++ b/src/libpriv/rpmostree-unpacker.c @@ -0,0 +1,472 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * Implements unpacking an RPM. The design here is to reuse + * libarchive's RPM support for most of it. We do however need to + * look at file capabilities, which are part of the header. + * + * Hence we end up with two file descriptors open. + */ + +#include "config.h" + +#include +#include +#include +#include "rpmostree-unpacker.h" +#include +#include +#include +#include +#include +#include + + +#include +#include + +typedef GObjectClass RpmOstreeUnpackerClass; + +struct RpmOstreeUnpacker +{ + GObject parent_instance; + struct archive *archive; + int fd; + gboolean owns_fd; + Header hdr; + rpmfi fi; + GHashTable *fscaps; + RpmOstreeUnpackerFlags flags; +}; + +G_DEFINE_TYPE(RpmOstreeUnpacker, rpmostree_unpacker, G_TYPE_OBJECT) + +static void +propagate_libarchive_error (GError **error, + struct archive *a) +{ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + archive_error_string (a)); +} + +static void +rpmostree_unpacker_finalize (GObject *object) +{ + RpmOstreeUnpacker *self = (RpmOstreeUnpacker*)object; + if (self->archive) + archive_read_free (self->archive); + if (self->fi) + (void) rpmfiFree (self->fi); + if (self->owns_fd) + (void) close (self->fd); + + G_OBJECT_CLASS (rpmostree_unpacker_parent_class)->finalize (object); +} + +static void +rpmostree_unpacker_class_init (RpmOstreeUnpackerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = rpmostree_unpacker_finalize; +} + +static void +rpmostree_unpacker_init (RpmOstreeUnpacker *p) +{ +} + +typedef int(*archive_setup_func)(struct archive *); + +/** + * rpmostree_rpm2cpio: + * @fd: An open file descriptor for an RPM package + * @error: GError + * + * Parse CPIO content of @fd via libarchive. Note that the CPIO data + * does not capture all relevant filesystem content; for example, + * filesystem capabilities are part of a separate header, etc. + */ +static struct archive * +rpm2cpio (int fd, GError **error) +{ + gboolean success = FALSE; + struct archive *ret = NULL; + guint i; + + ret = archive_read_new (); + g_assert (ret); + + /* We only do the subset necessary for RPM */ + { archive_setup_func archive_setup_funcs[] = + { archive_read_support_filter_rpm, + archive_read_support_filter_lzma, + archive_read_support_filter_gzip, + archive_read_support_filter_xz, + archive_read_support_filter_bzip2, + archive_read_support_format_cpio }; + + for (i = 0; i < G_N_ELEMENTS (archive_setup_funcs); i++) + { + if (archive_setup_funcs[i](ret) != ARCHIVE_OK) + { + propagate_libarchive_error (error, ret); + goto out; + } + } + } + + if (archive_read_open_fd (ret, fd, 10240) != ARCHIVE_OK) + { + propagate_libarchive_error (error, ret); + goto out; + } + + success = TRUE; + out: + if (success) + return g_steal_pointer (&ret); + else + { + if (ret) + (void) archive_read_free (ret); + return NULL; + } +} + +static gboolean +rpm_parse_hdr_fi (int fd, rpmfi *out_fi, Header *out_header, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *abspath = g_strdup_printf ("/proc/self/fd/%d", fd); + FD_t rpmfd; + rpmfi fi = NULL; + Header hdr = NULL; + rpmts ts = NULL; + int r; + + ts = rpmtsCreate (); + rpmtsSetVSFlags (ts, _RPMVSF_NOSIGNATURES); + + /* librpm needs Fopenfd */ + rpmfd = Fopen (abspath, "r.fdio"); + if (rpmfd == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to open %s", abspath); + goto out; + } + if (Ferror (rpmfd)) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Opening %s: %s", + abspath, + Fstrerror (rpmfd)); + goto out; + } + + if ((r = rpmReadPackageFile (ts, rpmfd, abspath, &hdr)) != RPMRC_OK) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Verification of %s failed", + abspath); + goto out; + } + + fi = rpmfiNew (ts, hdr, RPMTAG_BASENAMES, (RPMFI_NOHEADER | RPMFI_FLAGS_INSTALL)); + fi = rpmfiInit (fi, 0); + *out_fi = g_steal_pointer (&fi); + *out_header = g_steal_pointer (&hdr); + ret = TRUE; + out: + if (fi != NULL) + rpmfiFree (fi); + if (hdr != NULL) + headerFree (hdr); + return ret; +} + +RpmOstreeUnpacker * +rpmostree_unpacker_new_fd (int fd, RpmOstreeUnpackerFlags flags, GError **error) +{ + RpmOstreeUnpacker *ret = NULL; + Header hdr = NULL; + rpmfi fi = NULL; + struct archive *archive; + + archive = rpm2cpio (fd, error); + if (archive == NULL) + goto out; + + rpm_parse_hdr_fi (fd, &fi, &hdr, error); + if (fi == NULL) + goto out; + + ret = g_object_new (RPMOSTREE_TYPE_UNPACKER, NULL); + ret->fd = fd; + ret->fi = g_steal_pointer (&fi); + ret->archive = g_steal_pointer (&archive); + ret->flags = flags; + + out: + if (archive) + archive_read_free (archive); + if (hdr) + headerFree (hdr); + if (fi) + rpmfiFree (fi); + return ret; +} + +RpmOstreeUnpacker * +rpmostree_unpacker_new_at (int dfd, const char *path, RpmOstreeUnpackerFlags flags, GError **error) +{ + RpmOstreeUnpacker *ret = NULL; + glnx_fd_close int fd = -1; + + fd = openat (dfd, path, O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + { + glnx_set_error_from_errno (error); + g_prefix_error (error, "Opening %s: ", path); + goto out; + } + + ret = rpmostree_unpacker_new_fd (fd, flags, error); + if (ret == NULL) + goto out; + + ret->owns_fd = TRUE; + fd = -1; + + out: + return ret; +} + +gboolean +rpmostree_unpacker_unpack_to_dfd (RpmOstreeUnpacker *self, + int rootfs_fd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + while (rpmfiNext (self->fi) >= 0) + { + int r; + struct archive_entry *entry; + rpmfileAttrs fflags = rpmfiFFlags (self->fi); + rpm_mode_t fmode = rpmfiFMode (self->fi); + rpm_loff_t fsize = rpmfiFSize (self->fi); + const char *hardlink; + const char *fn = rpmfiFN (self->fi); + const char *fuser = rpmfiFUser (self->fi); + const char *fgroup = rpmfiFGroup (self->fi); + const char *fcaps = rpmfiFCaps (self->fi); + const struct stat *archive_st; + g_autofree char *dname = NULL; + uid_t owner_uid = 0; + gid_t owner_gid = 0; + glnx_fd_close int destfd = -1; + + r = archive_read_next_header (self->archive, &entry); + if (r == ARCHIVE_EOF) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unexpected end of RPM cpio stream"); + goto out; + } + else if (r != ARCHIVE_OK) + { + propagate_libarchive_error (error, self->archive); + goto out; + } + + archive_st = archive_entry_stat (entry); + + g_print ("%s %s:%s mode=%u size=%" G_GUINT64_FORMAT " attrs=%u", + fn, fuser, fgroup, fmode, (guint64) fsize, fflags); + if (fcaps) + g_print (" fcaps=\"%s\"", fcaps); + g_print ("\n"); + + if (fn[0] == '/') + fn += 1; + dname = dirname (g_strdup (fn)); + + /* Ensure parent directories exist */ + if (!glnx_shutil_mkdir_p_at (rootfs_fd, dname, 0755, cancellable, error)) + goto out; + + if (archive_st->st_mode != fmode) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted RPM (mode: fi=%u, archive=%u", fmode, archive_st->st_mode); + goto out; + } + + if ((self->flags & RPMOSTREE_UNPACKER_FLAGS_OWNER) > 0) + { + struct passwd *pwent; + struct group *grent; + + pwent = getpwnam (fuser); + if (pwent == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown user '%s'", fuser); + goto out; + } + owner_uid = pwent->pw_uid; + + grent = getgrnam (fgroup); + if (grent == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown group '%s'", fgroup); + goto out; + } + owner_gid = grent->gr_gid; + } + + hardlink = archive_entry_hardlink (entry); + if (hardlink) + { + if (hardlink[0] == '/') + hardlink++; + if (linkat (rootfs_fd, hardlink, rootfs_fd, fn, 0) < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + else if (S_ISDIR (fmode)) + { + g_assert (fn[0] != '/'); + if (!glnx_shutil_mkdir_p_at (rootfs_fd, fn, fmode, cancellable, error)) + goto out; + } + else if (S_ISLNK (fmode)) + { + g_assert (fn[0] != '/'); + if (symlinkat (rpmfiFLink (self->fi), rootfs_fd, fn) < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + else if (S_ISREG (fmode)) + { + size_t remain = fsize; + + g_assert (fn[0] != '/'); + destfd = openat (rootfs_fd, fn, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600); + if (destfd < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + + while (remain) + { + const void *buf; + size_t size; + gint64 off; + + r = archive_read_data_block (self->archive, &buf, &size, &off); + if (r == ARCHIVE_EOF) + break; + if (r != ARCHIVE_OK) + { + propagate_libarchive_error (error, self->archive); + goto out; + } + + if (glnx_loop_write (destfd, buf, size) < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + remain -= size; + } + if (remain > 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unexpected end of RPM cpio stream reading '%s'", fn); + goto out; + } + } + else + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "RPM contains non-regular/non-symlink file %s", + fn); + goto out; + } + + if ((self->flags & RPMOSTREE_UNPACKER_FLAGS_OWNER) > 0 && + fchownat (rootfs_fd, fn, owner_uid, owner_gid, AT_SYMLINK_NOFOLLOW) < 0) + { + glnx_set_error_from_errno (error); + g_prefix_error (error, "fchownat: "); + goto out; + } + + if (S_ISREG (fmode)) + { + g_assert (destfd != -1); + + if ((self->flags & RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS) == 0) + fmode &= 0777; + else + { + if (fcaps != NULL && fcaps[0]) + { + cap_t caps = cap_from_text (fcaps); + if (cap_set_fd (destfd, caps) != 0) + { + glnx_set_error_from_errno (error); + g_prefix_error (error, "Setting capabilities: "); + goto out; + } + } + } + + if (fchmod (destfd, fmode) < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + } + + ret = TRUE; + out: + return ret; +} diff --git a/src/libpriv/rpmostree-unpacker.h b/src/libpriv/rpmostree-unpacker.h new file mode 100644 index 00000000..7c59cfce --- /dev/null +++ b/src/libpriv/rpmostree-unpacker.h @@ -0,0 +1,48 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2015 Colin Walters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include + +#include "libglnx.h" + +typedef struct RpmOstreeUnpacker RpmOstreeUnpacker; + +#define RPMOSTREE_TYPE_UNPACKER (rpmostree_unpacker_get_type ()) +#define RPMOSTREE_UNPACKER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), RPMOSTREE_TYPE_UNPACKER, RpmOstreeUnpacker)) +#define RPMOSTREE_IS_UNPACKER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), RPMOSTREE_TYPE_UNPACKER)) + +GType rpmostree_unpacker_get_type (void); + +typedef enum { + RPMOSTREE_UNPACKER_FLAGS_SUID_FSCAPS = (1 << 0), + RPMOSTREE_UNPACKER_FLAGS_OWNER = (1 << 1) +} RpmOstreeUnpackerFlags; +#define RPMOSTREE_UNPACKER_FLAGS_ALL = (RPMOSTREE_UNPACKER_SUID_FSCAPS | RPMOSTREE_UNPACKER_OWNER) + +RpmOstreeUnpacker *rpmostree_unpacker_new_fd (int fd, RpmOstreeUnpackerFlags flags, GError **error); + +RpmOstreeUnpacker *rpmostree_unpacker_new_at (int dfd, const char *path, RpmOstreeUnpackerFlags flags, GError **error); + +gboolean rpmostree_unpacker_unpack_to_dfd (RpmOstreeUnpacker *unpacker, + int dfd, + GCancellable *cancellable, + GError **error);