diff --git a/.cci.jenkinsfile b/.cci.jenkinsfile index a0d7545375..079eb058dd 100644 --- a/.cci.jenkinsfile +++ b/.cci.jenkinsfile @@ -7,6 +7,7 @@ parallel rpms: { shwrap(""" # fetch tags so `git describe` gives a nice NEVRA when building the RPM git fetch origin --tags + ci/install-extra-builddeps.sh ci/installdeps.sh git submodule update --init diff --git a/Cargo.lock b/Cargo.lock index 96474d660d..e977d61aad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "cxx" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76e80fb6d39f044d141223d7b56c468beaab53e7698a45cc535b780c2382b3c" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3bfe71a2767344b8ca945162df2445ed5a0a8244992723b9c68dd98a98ab32" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a66e61d3db394b82ffa7e7849732d57e38e1bc25509527a5d30bfc2c39b5e6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "drop_bomb" version = "0.1.5" @@ -564,6 +593,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f96aa785c87218ec773df6c510af203872b34e2df2cf47d6e908e5f36231e354" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -949,6 +987,7 @@ dependencies = [ "chrono", "clap", "curl", + "cxx", "envsubst", "gio", "gio-sys", @@ -1115,9 +1154,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.45" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556" +checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 64658a5af6..cf8a0b72a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ serde_derive = "1.0.118" serde_json = "1.0.60" serde_yaml = "0.8.14" libc = "0.2.81" +cxx = "1.0.18" nix = "0.19.1" glib-sys = "0.10.1" glib = "0.10.3" diff --git a/Makefile-libpriv.am b/Makefile-libpriv.am index 0d4449097e..49d5e2c9b9 100644 --- a/Makefile-libpriv.am +++ b/Makefile-libpriv.am @@ -75,11 +75,6 @@ CLEANFILES += $(gperf_gperf_sources:-gperf.gperf=-gperf.c) nodist_librpmostreepriv_sources = src/libpriv/rpmostree-script-gperf.c -rpmostree-libpriv-gresources.c: src/libpriv/gresources.xml Makefile $(shell glib-compile-resources --sourcedir=$(srcdir)/src/libpriv --generate-dependencies $(srcdir)/src/libpriv/gresources.xml) - $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir)/src/libpriv --generate-source --c-name _rpmostree_ $< -BUILT_SOURCES += rpmostree-libpriv-gresources.c -librpmostreepriv_sources += rpmostree-libpriv-gresources.c - AM_V_GPERF = $(AM_V_GPERF_$(V)) AM_V_GPERF_ = $(AM_V_GPERF_$(AM_DEFAULT_VERBOSITY)) AM_V_GPERF_0 = @echo " GPERF " $@; @@ -87,3 +82,13 @@ AM_V_GPERF_0 = @echo " GPERF " $@; src/%.c: src/%.gperf Makefile $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_GPERF)$(GPERF) < $< > $@.tmp && mv $@.tmp $@ + +# Also we now use cxx.rs +rpmostree-cxxrs.h: rust/src/lib.rs + $(AM_V_GEN) cxxbridge rust/src/lib.rs --header > $@ +rpmostree-cxxrs.cxx: rust/src/lib.rs + $(AM_V_GEN) cxxbridge --include rpmostree-cxxrs.h rust/src/lib.rs > $@ +cxxrs_sources = rpmostree-cxxrs.h rpmostree-cxxrs.cxx +librpmostreepriv_sources += $(cxxrs_sources) +BUILT_SOURCES += $(cxxrs_sources) +GITIGNOREFILES += $(cxxrs_sources) diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index 7a27a53135..0fadb19c13 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -17,7 +17,12 @@ bin_PROGRAMS += rpm-ostree -rpm_ostree_SOURCES = src/app/main.cxx \ +rpm_ostree_SOURCES = src/app/main.cxx + +noinst_LTLIBRARIES += librpmostreeinternals.la + +librpmostreeinternals_la_SOURCES = \ + src/app/libmain.cxx \ src/app/rpmostree-builtins.h \ src/app/rpmostree-db-builtins.h \ src/app/rpmostree-compose-builtins.h \ @@ -65,25 +70,30 @@ rpm_ostree_SOURCES = src/app/main.cxx \ $(NULL) if BUILDOPT_ROJIG -rpm_ostree_SOURCES += \ +librpmostreeinternals_la_SOURCES += \ src/app/rpmostree-ex-builtin-commit2rojig.cxx \ src/app/rpmostree-ex-builtin-rojig2commit.cxx \ src/app/rpmostree-compose-builtin-rojig.cxx \ $(NULL) endif -nodist_rpm_ostree_SOURCES = $(dbus_built_sources) $(nodist_librpmostreepriv_sources) +nodist_librpmostreeinternals_la_SOURCES = $(dbus_built_sources) $(nodist_librpmostreepriv_sources) rpmostree_common_cflags = -I$(srcdir)/src/app -I$(srcdir)/src/daemon \ -I$(srcdir)/src/lib -I$(srcdir)/src/libpriv -I$(libglnx_srcpath) \ - -fvisibility=hidden \ -DG_LOG_DOMAIN=\"rpm-ostreed\" \ -DLIBDIR=\"$(libdir)\" -DPKGLIBDIR=\"$(pkglibdir)\" \ - $(PKGDEP_RPMOSTREE_CFLAGS) -rpm_ostree_CFLAGS = $(AM_CFLAGS) $(rpmostree_common_cflags) -rpm_ostree_CXXFLAGS = $(AM_CXXFLAGS) $(rpmostree_common_cflags) -rpm_ostree_LDADD = $(PKGDEP_RPMOSTREE_LIBS) $(CAP_LIBS) libglnx.la librpmostree-1.la + $(PKGDEP_RPMOSTREE_CFLAGS) $(PKGDEP_RPMOSTREE_RS_CFLAGS) +rpmostree_bin_common_cflags = $(rpmostree_common_cflags) +rpmostree_bin_common_libs = $(PKGDEP_RPMOSTREE_LIBS) $(CAP_LIBS) libglnx.la librpmostree-1.la $(librpmostree_rust_path) $(PKGDEP_RPMOSTREE_RS_LIBS) -lstdc++ +rpm_ostree_CFLAGS = $(AM_CFLAGS) $(rpmostree_bin_common_cflags) +rpm_ostree_CXXFLAGS = $(AM_CXXFLAGS) $(rpmostree_bin_common_cflags) +rpm_ostree_LDADD = librpmostreeinternals.la $(rpmostree_bin_common_libs) EXTRA_rpm_ostree_DEPENDENCIES = libdnf.so.2 +librpmostreeinternals_la_CFLAGS = $(AM_CFLAGS) $(rpmostree_common_cflags) +librpmostreeinternals_la_CXXFLAGS = $(AM_CXXFLAGS) $(rpmostree_common_cflags) +librpmostreeinternals_la_LIBADD = $(rpmostree_bin_common_libs) +EXTRA_librpmostreeinternals_la_DEPENDENCIES = libdnf.so.2 privdatadir=$(pkglibdir) privdata_DATA = src/app/rpm-ostree-0-integration.conf @@ -107,7 +117,7 @@ endif endif GITIGNOREFILES += tooling/target/ rpmostree-bindgen -librpmostree_rust_path = @abs_top_builddir@/target/@RUST_TARGET_SUBDIR@/librpmostree_rust.a +librpmostree_rust_path = $(top_srcdir)/target/@RUST_TARGET_SUBDIR@/librpmostree_rust.a # If the target directory exists, and isn't owned by our uid, then # we exit with a fatal error, since someone probably did `make && sudo make install`, # and in this case cargo will download into ~/.root which we don't want. @@ -133,9 +143,6 @@ endif endif BUILT_SOURCES += rpmostree-rust.h -rpm_ostree_CFLAGS += $(PKGDEP_RPMOSTREE_RS_CFLAGS) -rpm_ostree_LDADD += $(librpmostree_rust_path) $(PKGDEP_RPMOSTREE_RS_LIBS) -lstdc++ - # Wraps `cargo test`. This is always a debug non-release build; # the main thing here is we still drop the `target` dir in our build # directory, since we nominally support srcdir != builddir. diff --git a/Makefile-tests.am b/Makefile-tests.am index 48bf866162..da77a52922 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -20,39 +20,38 @@ endif GITIGNOREFILES += ssh-config ansible-inventory.yml vmcheck-logs/ test-compose-logs/ tests/vmcheck/image.qcow2 -# Disabled for now because we dropped librpmostreepriv.la -# testbin_cppflags = $(AM_CPPFLAGS) -I $(srcdir)/src/lib -I $(srcdir)/src/libpriv -I $(srcdir)/libglnx -I $(srcdir)/tests/common -# testbin_cflags = $(AM_CFLAGS) -fvisibility=hidden $(PKGDEP_RPMOSTREE_CFLAGS) -# testbin_ldadd = $(PKGDEP_RPMOSTREE_LIBS) librpmostree-1.la librpmostreepriv.la -lstdc++ -# -# noinst_LTLIBRARIES += libtest.la -# libtest_la_SOURCES = tests/common/libtest.c -# libtest_la_CPPFLAGS = $(testbin_cppflags) -# libtest_la_CFLAGS = $(testbin_cflags) -# libtest_la_LIBADD = $(testbin_ldadd) -# -# tests_check_jsonutil_CPPFLAGS = $(testbin_cppflags) -# tests_check_jsonutil_CFLAGS = $(testbin_cflags) -# tests_check_jsonutil_LDADD = $(testbin_ldadd) libtest.la -# -# tests_check_postprocess_CPPFLAGS = $(testbin_cppflags) -# tests_check_postprocess_CFLAGS = $(testbin_cflags) -# tests_check_postprocess_LDADD = $(testbin_ldadd) libtest.la -# -# tests_check_test_utils_CPPFLAGS = $(testbin_cppflags) -# tests_check_test_utils_CFLAGS = $(testbin_cflags) -# tests_check_test_utils_LDADD = $(testbin_ldadd) libtest.la -# -# tests_check_test_sysusers_CPPFLAGS = $(testbin_cppflags) -# tests_check_test_sysusers_CFLAGS = $(testbin_cflags) -# tests_check_test_sysusers_LDADD = $(testbin_ldadd) libtest.la -# -# uninstalled_test_programs = \ -# tests/check/jsonutil \ -# tests/check/postprocess \ -# tests/check/test-utils \ -# tests/check/test-sysusers \ -# $(NULL) +testbin_cppflags = $(AM_CPPFLAGS) -I $(srcdir)/src/lib -I $(srcdir)/src/libpriv -I $(srcdir)/libglnx -I $(srcdir)/tests/common +testbin_cflags = $(AM_CFLAGS) $(rpmostree_bin_common_cflags) +testbin_ldadd = librpmostreeinternals.la $(rpmostree_bin_common_libs) + +noinst_LTLIBRARIES += libtest.la +libtest_la_SOURCES = tests/common/libtest.c +libtest_la_CPPFLAGS = $(testbin_cppflags) +libtest_la_CFLAGS = $(testbin_cflags) +libtest_la_LIBADD = $(testbin_ldadd) + +tests_check_jsonutil_CPPFLAGS = $(testbin_cppflags) +tests_check_jsonutil_CFLAGS = $(testbin_cflags) +tests_check_jsonutil_LDADD = $(testbin_ldadd) libtest.la + +tests_check_postprocess_CPPFLAGS = $(testbin_cppflags) +tests_check_postprocess_CFLAGS = $(testbin_cflags) +tests_check_postprocess_LDADD = $(testbin_ldadd) libtest.la + +tests_check_test_utils_CPPFLAGS = $(testbin_cppflags) +tests_check_test_utils_CFLAGS = $(testbin_cflags) +tests_check_test_utils_LDADD = $(testbin_ldadd) libtest.la + +tests_check_test_sysusers_CPPFLAGS = $(testbin_cppflags) +tests_check_test_sysusers_CFLAGS = $(testbin_cflags) +tests_check_test_sysusers_LDADD = $(testbin_ldadd) libtest.la + +uninstalled_test_programs = \ + tests/check/jsonutil \ + tests/check/postprocess \ + tests/check/test-utils \ + tests/check/test-sysusers \ + $(NULL) uninstalled_test_scripts = \ tests/check/test-lib-introspection.sh \ diff --git a/ci/build.sh b/ci/build.sh index 2cbcbee7e6..db083e84b0 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -6,6 +6,7 @@ set -xeuo pipefail dn=$(dirname $0) . ${dn}/libbuild.sh +${dn}/install-extra-builddeps.sh ${dn}/installdeps.sh # create an unprivileged user for testing diff --git a/ci/install-extra-builddeps.sh b/ci/install-extra-builddeps.sh new file mode 100755 index 0000000000..cc0d5ac838 --- /dev/null +++ b/ci/install-extra-builddeps.sh @@ -0,0 +1,7 @@ +#!/usr/bin/bash +# Install build dependencies not in the cosa buildroot already +set -xeuo pipefail +if ! command -v cxxbridge; then + ver=$(cargo metadata --format-version 1 | jq -r '.packages[]|select(.name == "cxx").version') + cargo install --root=/usr cxxbridge-cmd --version "${ver}" +fi diff --git a/configure.ac b/configure.ac index bd3e3d415c..86ade3c5a9 100644 --- a/configure.ac +++ b/configure.ac @@ -41,7 +41,7 @@ CC_CHECK_FLAGS_APPEND([WARN_CFLAGS], [CFLAGS], [\ -Werror=parenthesis \ -Werror=undef \ -Werror=misleading-indentation \ - -Werror=missing-include-dirs -Werror=aggregate-return \ + -Werror=missing-include-dirs \ -Wstrict-aliasing=2 \ -Werror=unused-result \ ])]) diff --git a/rust/src/core.rs b/rust/src/core.rs index b5e64d2494..318d588536 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -1,5 +1,6 @@ -pub use self::ffi::*; use crate::ffiutil; +use anyhow::Result; +use ffiutil::ffi_view_openat_dir; use openat_ext::OpenatDirExt; /// Guard for running logic in a context with temporary /etc. @@ -13,27 +14,27 @@ pub struct TempEtcGuard { renamed_etc: bool, } -impl TempEtcGuard { - /// Create a context with a temporary /etc, and return a guard to it. - pub fn undo_usretc(rootfs: openat::Dir) -> anyhow::Result { - let has_usretc = rootfs.exists("usr/etc")?; - if has_usretc { - // In general now, we place contents in /etc when running scripts - rootfs.local_rename("usr/etc", "etc")?; - // But leave a compat symlink, as we used to bind mount, so scripts - // could still use that too. - rootfs.symlink("usr/etc", "../etc")?; - } - - let guard = Self { - rootfs, - renamed_etc: has_usretc, - }; - Ok(guard) +pub fn prepare_tempetc_guard(rootfs: i32) -> Result> { + let rootfs = ffi_view_openat_dir(rootfs); + let has_usretc = rootfs.exists("usr/etc")?; + let mut renamed_etc = false; + if has_usretc { + // In general now, we place contents in /etc when running scripts + rootfs.local_rename("usr/etc", "etc")?; + // But leave a compat symlink, as we used to bind mount, so scripts + // could still use that too. + rootfs.symlink("usr/etc", "../etc")?; + renamed_etc = true; } + Ok(Box::new(TempEtcGuard { + rootfs, + renamed_etc, + })) +} +impl TempEtcGuard { /// Remove the temporary /etc, and destroy the guard. - pub fn redo_usretc(self) -> anyhow::Result<()> { + pub fn undo(&self) -> anyhow::Result<()> { if self.renamed_etc { /* Remove the symlink and swap back */ self.rootfs.remove_file("usr/etc")?; @@ -43,28 +44,28 @@ impl TempEtcGuard { } } -mod ffi { - use super::*; - use glib_sys::GError; +pub(crate) fn get_systemctl_wrapper() -> &'static [u8] { + include_bytes!("../../src/libpriv/systemctl-wrapper.sh") +} - #[no_mangle] - pub extern "C" fn ror_tempetc_undo_usretc( - rootfs: libc::c_int, - gerror: *mut *mut GError, - ) -> *mut TempEtcGuard { - let fd = ffiutil::ffi_view_openat_dir(rootfs); - let res = TempEtcGuard::undo_usretc(fd).map(Box::new); - ffiutil::ptr_glib_error(res, gerror) - } +#[cfg(test)] +mod test { + use super::*; + use std::os::unix::prelude::*; - #[no_mangle] - pub extern "C" fn ror_tempetc_redo_usretc( - guard_ptr: *mut TempEtcGuard, - gerror: *mut *mut GError, - ) -> libc::c_int { - assert!(!guard_ptr.is_null()); - let guard = unsafe { Box::from_raw(guard_ptr) }; - let res = guard.redo_usretc(); - ffiutil::int_glib_error(res, gerror) + #[test] + fn basic() -> Result<()> { + let td = tempfile::tempdir()?; + let d = openat::Dir::open(td.path())?; + let g = super::prepare_tempetc_guard(d.as_raw_fd())?; + g.undo()?; + d.ensure_dir_all("usr/etc/foo", 0o755)?; + assert!(!d.exists("etc/foo")?); + let g = super::prepare_tempetc_guard(d.as_raw_fd())?; + assert!(d.exists("etc/foo")?); + g.undo()?; + assert!(!d.exists("etc")?); + assert!(d.exists("usr/etc/foo")?); + Ok(()) } } diff --git a/rust/src/initramfs.rs b/rust/src/initramfs.rs index dd433ac2cb..13fb67502f 100644 --- a/rust/src/initramfs.rs +++ b/rust/src/initramfs.rs @@ -118,6 +118,11 @@ fn generate_initramfs_overlay_etc>( generate_initramfs_overlay(&root, files, cancellable) } +pub(crate) fn get_dracut_random_cpio() -> &'static [u8] { + // Generated with: fakeroot /bin/sh -c 'cd dracut-urandom && find . -print0 | sort -z | (mknod dev/random c 1 8 && mknod dev/urandom c 1 9 && cpio -o --null -H newc -R 0:0 --reproducible --quiet -D . -O /tmp/dracut-urandom.cpio)' + include_bytes!("../../src/libpriv/dracut-random.cpio.gz") +} + #[cfg(test)] mod test { use super::*; @@ -143,7 +148,7 @@ mod test { } } -pub mod ffi { +mod ffi { use super::*; use crate::ffiutil::error_to_glib; use glib::translate::*; @@ -177,3 +182,4 @@ pub mod ffi { } } } +pub use self::ffi::*; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index ece0228d02..7732d6f64f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -10,17 +10,41 @@ mod ffiutil; mod includes; +#[cxx::bridge(namespace = "rpmostreecxx")] +mod ffi { + // core.rs + extern "Rust" { + type TempEtcGuard; + + fn prepare_tempetc_guard(rootfs: i32) -> Result>; + fn undo(self: &TempEtcGuard) -> Result<()>; + + fn get_systemctl_wrapper() -> &'static [u8]; + } + + // initramfs.rs + extern "Rust" { + fn get_dracut_random_cpio() -> &'static [u8]; + } + + // utils.rs + extern "Rust" { + fn download_to_fd(url: &str) -> Result; + } +} + mod cliwrap; pub use cliwrap::*; mod composepost; pub use self::composepost::*; mod core; +use crate::core::*; mod history; pub use self::history::*; mod journal; pub use self::journal::*; mod initramfs; -pub use self::initramfs::ffi::*; +pub use self::initramfs::*; mod lockfile; pub use self::lockfile::*; mod livefs; diff --git a/rust/src/utils.rs b/rust/src/utils.rs index 9ca5c4ba38..18127a3c7f 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -206,19 +206,8 @@ mod ffi { use std::os::unix::io::IntoRawFd; use std::ptr; - #[no_mangle] - pub extern "C" fn ror_download_to_fd( - url: *const libc::c_char, - gerror: *mut *mut glib_sys::GError, - ) -> libc::c_int { - let url: Borrowed = unsafe { from_glib_borrow(url) }; - match download_url_to_tmpfile(url.as_str()) { - Ok(f) => f.into_raw_fd(), - Err(e) => { - error_to_glib(&e, gerror); - -1 - } - } + pub(crate) fn download_to_fd(url: &str) -> Result { + download_url_to_tmpfile(url).map(|f| f.into_raw_fd()) } #[no_mangle] diff --git a/src/app/libmain.cxx b/src/app/libmain.cxx new file mode 100644 index 0000000000..39130fd756 --- /dev/null +++ b/src/app/libmain.cxx @@ -0,0 +1,546 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2014 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. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "rpmostree-util.h" +#include "rpmostree-builtins.h" +#include "rpmostree-polkit-agent.h" +#include "rpmostreemain.h" + +#include "libglnx.h" + +static RpmOstreeCommand commands[] = { + { "compose", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | + RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT), + "Commands to compose a tree", + rpmostree_builtin_compose }, + { "cleanup", static_cast(0), + "Clear cached/pending data", + rpmostree_builtin_cleanup }, + { "db", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD), + "Commands to query the RPM database", + rpmostree_builtin_db }, + { "deploy", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), + "Deploy a specific commit", + rpmostree_builtin_deploy }, + { "rebase", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), + "Switch to a different tree", + rpmostree_builtin_rebase }, + { "rollback", static_cast(0), + "Revert to the previously booted tree", + rpmostree_builtin_rollback }, + { "status", static_cast(0), + "Get the version of the booted system", + rpmostree_builtin_status }, + { "upgrade", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), + "Perform a system upgrade", + rpmostree_builtin_upgrade }, + { "update", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS | RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + "Alias for upgrade", + rpmostree_builtin_upgrade }, + { "reload", static_cast(0), + "Reload configuration", + rpmostree_builtin_reload }, + { "usroverlay", static_cast(RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT), + "Apply a transient overlayfs to /usr", + rpmostree_builtin_usroverlay }, + /* Let's be "cognitively" compatible with `ostree admin unlock` */ + { "unlock", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, + rpmostree_builtin_usroverlay }, + { "cancel", static_cast(0), + "Cancel an active transaction", + rpmostree_builtin_cancel }, + { "initramfs", static_cast(0), + "Enable or disable local initramfs regeneration", + rpmostree_builtin_initramfs }, + { "install", static_cast(0), + "Overlay additional packages", + rpmostree_builtin_install }, + { "uninstall", static_cast(0), + "Remove overlayed additional packages", + rpmostree_builtin_uninstall }, + { "override", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD), + "Manage base package overrides", rpmostree_builtin_override }, + { "reset", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), + "Remove all mutations", + rpmostree_builtin_reset }, + { "refresh-md", static_cast(0), + "Generate rpm repo metadata", + rpmostree_builtin_refresh_md }, + { "kargs", static_cast(0), + "Query or modify kernel arguments", + rpmostree_builtin_kargs }, + /* Legacy aliases */ + { "pkg-add", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_install }, + { "pkg-remove", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_uninstall }, + { "rpm", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | + RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_db }, + /* Compat with dnf */ + { "remove", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_uninstall }, + { "makecache", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_refresh_md }, + /* Hidden */ + { "ex", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | + RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + "Experimental commands that may change or be removed in the future", + rpmostree_builtin_ex }, + { "testutils", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | + RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_testutils }, + { "shlib-backend", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | + RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_shlib_backend }, + { "start-daemon", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | + RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT | + RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_start_daemon }, + { "finalize-deployment", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_finalize_deployment }, + { "cliwrap", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | RPM_OSTREE_BUILTIN_FLAG_HIDDEN), + NULL, rpmostree_builtin_cliwrap }, + { NULL } +}; + +static gboolean opt_version; +static gboolean opt_force_peer; +static char *opt_sysroot; +static gchar **opt_install; +static gchar **opt_uninstall; + +static GOptionEntry global_entries[] = { + { "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print version information and exit", NULL }, + { NULL } +}; + +static GOptionEntry daemon_entries[] = { + { "sysroot", 0, 0, G_OPTION_ARG_STRING, &opt_sysroot, "Use system root SYSROOT (default: /)", "SYSROOT" }, + { "peer", 0, 0, G_OPTION_ARG_NONE, &opt_force_peer, "Force a peer-to-peer connection instead of using the system message bus", NULL }, + { NULL } +}; + +static GOptionEntry pkg_entries[] = { + { "install", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_install, "Overlay additional package", "PKG" }, + { "uninstall", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_uninstall, "Remove overlayed additional package", "PKG" }, + { NULL } +}; + +static GOptionContext * +option_context_new_with_commands (RpmOstreeCommandInvocation *invocation, + RpmOstreeCommand *commands) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("COMMAND"); + g_autoptr(GString) summary = g_string_new (NULL); + + if (invocation) + { + if (invocation->command->description != NULL) + g_string_append_printf (summary, "%s\n\n", + invocation->command->description); + + g_string_append_printf (summary, "Builtin \"%s\" Commands:", + invocation->command->name); + } + else /* top level */ + g_string_append (summary, "Builtin Commands:"); + + for (RpmOstreeCommand *command = commands; command->name != NULL; command++) + { + gboolean hidden = (command->flags & RPM_OSTREE_BUILTIN_FLAG_HIDDEN) > 0; + if (!hidden) + { + g_string_append_printf (summary, "\n %-17s", command->name); + if (command->description != NULL) + g_string_append_printf (summary, "%s", command->description); + } + } + + g_option_context_set_summary (context, summary->str); + return util::move_nullify (context); +} + +gboolean +rpmostree_option_context_parse (GOptionContext *context, + const GOptionEntry *main_entries, + int *argc, + char ***argv, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, + const char *const* *out_install_pkgs, + const char *const* *out_uninstall_pkgs, + RPMOSTreeSysroot **out_sysroot_proxy, + GPid *out_peer_pid, + GBusType *out_bus_type, + GError **error) +{ + /* with --version there's no command, don't require a daemon for it */ + const RpmOstreeBuiltinFlags flags = + invocation ? invocation->command->flags : RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD; + gboolean use_daemon = ((flags & RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD) == 0); + + if (invocation && invocation->command->description != NULL) + { + /* The extra summary explanation is only provided for commands with description */ + const char* context_summary = g_option_context_get_summary (context); + + /* check whether the summary has been set earlier */ + if (context_summary == NULL) + g_option_context_set_summary (context, invocation->command->description); + } + + if (main_entries != NULL) + g_option_context_add_main_entries (context, main_entries, NULL); + + if (use_daemon) + g_option_context_add_main_entries (context, daemon_entries, NULL); + + if ((flags & RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS) > 0) + g_option_context_add_main_entries (context, pkg_entries, NULL); + + g_option_context_add_main_entries (context, global_entries, NULL); + + if (!g_option_context_parse (context, argc, argv, error)) + return FALSE; + + if (opt_version) + { + /* This should now be YAML, like `docker version`, so it's both nice to read + * possible to parse. The canonical implementation of this is in + * ostree/ot-main.c. + */ + g_auto(GStrv) features = g_strsplit (RPM_OSTREE_FEATURES, " ", -1); + g_print ("%s:\n", PACKAGE_NAME); + g_print (" Version: '%s'\n", PACKAGE_VERSION); + if (strlen (RPM_OSTREE_GITREV) > 0) + g_print (" Git: %s\n", RPM_OSTREE_GITREV); + g_print (" Features:\n"); + for (char **iter = features; iter && *iter; iter++) + g_print (" - %s\n", *iter); + exit (EXIT_SUCCESS); + } + + if ((flags & RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT) > 0 + && getuid () != 0 + && getenv ("RPMOSTREE_SUPPRESS_REQUIRES_ROOT_CHECK") == NULL) + return glnx_throw (error, "This command requires root privileges"); + + if (use_daemon) + { + /* More gracefully handle the case where + * no --sysroot option was specified and we're not booted via ostree + * https://github.com/projectatomic/rpm-ostree/issues/1537 + */ + if (!opt_sysroot) + { + if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error)) + return FALSE; + if (errno == ENOENT) + return glnx_throw (error, "This system was not booted via libostree; cannot operate"); + } + + /* root never needs to auth */ + if (getuid () != 0) + /* ignore errors; we print out a warning if we fail to spawn pkttyagent */ + (void)rpmostree_polkit_agent_open (); + + if (!rpmostree_load_sysroot (opt_sysroot, + opt_force_peer, + cancellable, + out_sysroot_proxy, + out_peer_pid, + out_bus_type, + error)) + return FALSE; + } + + if (out_install_pkgs) + *out_install_pkgs = (const char *const*)opt_install; + if (out_uninstall_pkgs) + *out_uninstall_pkgs = (const char *const*)opt_uninstall; + + return TRUE; +} + +static RpmOstreeCommand * +lookup_command (const char *name) +{ + RpmOstreeCommand *command = commands; + + while (command->name) + { + if (g_strcmp0 (name, command->name) == 0) + return command; + command++; + } + return NULL; +} + +static const char * +rpmostree_subcommand_parse (int *inout_argc, + char **inout_argv) +{ + const int argc = *inout_argc; + const char *command_name = NULL; + 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 (inout_argv[in][0] != '-') + { + if (command_name == NULL) + { + command_name = inout_argv[in]; + out--; + continue; + } + } + + else if (g_str_equal (inout_argv[in], "--")) + { + break; + } + + inout_argv[out] = inout_argv[in]; + } + + *inout_argc = out; + return command_name; +} + +gboolean +rpmostree_handle_subcommand (int argc, char **argv, + RpmOstreeCommand *subcommands, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, GError **error) +{ + const char *subcommand_name = + rpmostree_subcommand_parse (&argc, argv); + + RpmOstreeCommand *subcommand = subcommands; + while (subcommand->name) + { + if (g_strcmp0 (subcommand_name, subcommand->name) == 0) + break; + subcommand++; + } + + if (!subcommand->name) + { + g_autoptr(GOptionContext) context = + option_context_new_with_commands (invocation, subcommands); + + /* This will not return for some options (e.g. --version). */ + (void) rpmostree_option_context_parse (context, NULL, + &argc, &argv, + invocation, + cancellable, + NULL, NULL, NULL, NULL, NULL, NULL); + if (subcommand_name == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "No \"%s\" subcommand specified", + invocation->command->name); + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown \"%s\" subcommand \"%s\"", + invocation->command->name, + subcommand_name); + } + + g_autofree char *help = g_option_context_get_help (context, FALSE, NULL); + g_printerr ("%s", help); + return FALSE; + } + + g_autofree char *prgname = + g_strdup_printf ("%s %s", g_get_prgname (), subcommand_name); + g_set_prgname (prgname); + + /* We need a new sub-invocation with the new command, which also carries a new + * exit code, but we'll proxy the latter. */ + RpmOstreeCommandInvocation sub_invocation = { .command = subcommand, + .command_line = invocation->command_line, + .exit_code = -1 }; + gboolean ret = subcommand->fn (argc, argv, &sub_invocation, cancellable, error); + /* Proxy the exit code */ + invocation->exit_code = sub_invocation.exit_code; + return ret; +} + +static char* +rebuild_command_line (int argc, + char **argv) +{ + /* be nice and quote args as needed instead of just g_strjoinv() */ + g_autoptr(GString) command = g_string_new (NULL); + for (int i = 1; i < argc; i++) + { + g_autofree char *quoted = rpmostree_maybe_shell_quote (argv[i]); + g_string_append (command, quoted ?: argv[i]); + g_string_append_c (command, ' '); + } + return g_string_free (util::move_nullify (command), FALSE); +} + +/* See comment in main.cxx */ +int +rpmostree_main (int argc, + char **argv) +{ + RpmOstreeCommand *command; + RpmOstreeCommandInvocation invocation; + const char *command_name = NULL; + g_autofree char *prgname = NULL; + GError *local_error = NULL; + gboolean funcres; + /* We can leave this function with an error status from both a command + * invocation, as well as an option processing failure. Keep an alias to the + * two places that hold status codes. + */ + int exit_status = EXIT_SUCCESS; + int *exit_statusp = &exit_status; + + /* avoid gvfs (http://bugzilla.gnome.org/show_bug.cgi?id=526454) */ + g_setenv ("GIO_USE_VFS", "local", TRUE); + /* There's not really a "root dconf" right now; otherwise we might + * try to spawn one via GSocketClient → GProxyResolver → GSettings. + * + * https://github.com/projectatomic/rpm-ostree/pull/312 + * https://bugzilla.gnome.org/show_bug.cgi?id=767183 + **/ + if (getuid () == 0) + g_assert (g_setenv ("GSETTINGS_BACKEND", "memory", TRUE)); + g_set_prgname (argv[0]); + + setlocale (LC_ALL, ""); + + /* We don't support /etc/dnf/dnf.conf, so tell libdnf to not look for it. The function + * name here is misleading; it's not attached to a `DnfContext` object, but instead + * controls a global var. And it's not just the `DnfContext` that uses it, but e.g. + * `DnfSack` and Repo too. So just do this upfront. XXX: Clean up that API so it's always + * attached to a context object. */ + dnf_context_set_config_file_path(""); + + GCancellable *cancellable = g_cancellable_new (); + + g_autofree char *command_line = rebuild_command_line (argc, argv); + + /* + * Parse the global options. We rearrange the options as + * necessary, in order to pass relevant options through + * to the commands, but also have them take effect globally. + */ + command_name = rpmostree_subcommand_parse (&argc, argv); + + command = lookup_command (command_name); + + if (!command) + { + g_autoptr(GOptionContext) context = + option_context_new_with_commands (NULL, commands); + g_autofree char *help = NULL; + + /* This will not return for some options (e.g. --version). */ + (void) rpmostree_option_context_parse (context, NULL, &argc, &argv, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL); + if (command_name == NULL) + { + local_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + "No command specified"); + } + else + { + local_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + "Unknown command '%s'", command_name); + } + + help = g_option_context_get_help (context, FALSE, NULL); + g_printerr ("%s", help); + exit_status = EXIT_FAILURE; + goto out; + } + + prgname = g_strdup_printf ("%s %s", g_get_prgname (), command_name); + g_set_prgname (prgname); + + invocation = { .command = command, + .command_line = command_line, + .exit_code = -1 }; + exit_statusp = &(invocation.exit_code); + try { + funcres = command->fn (argc, argv, &invocation, cancellable, &local_error); + } catch (std::exception& e) { + // Translate exceptions into GError + funcres = glnx_throw (&local_error, "%s", e.what()); + } + if (!funcres) + { + if (invocation.exit_code == -1) + invocation.exit_code = EXIT_FAILURE; + g_assert (local_error); + goto out; + } + else + { + if (invocation.exit_code == -1) + invocation.exit_code = EXIT_SUCCESS; + else + g_assert (invocation.exit_code != EXIT_SUCCESS); + } + + out: + if (local_error != NULL) + { + int is_tty = isatty (1); + const char *prefix = ""; + const char *suffix = ""; + if (is_tty) + { + prefix = "\x1b[31m\x1b[1m"; /* red, bold */ + suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ + } + g_dbus_error_strip_remote_error (local_error); + g_printerr ("%serror: %s%s\n", prefix, suffix, local_error->message); + g_error_free (local_error); + } + + rpmostree_polkit_agent_close (); + + return *exit_statusp; +} diff --git a/src/app/main.cxx b/src/app/main.cxx index 0fbb6c16f8..83cd97dcb8 100644 --- a/src/app/main.cxx +++ b/src/app/main.cxx @@ -1,6 +1,6 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * - * Copyright (C) 2014 Colin Walters + * Copyright (C) 2020 Colin Walters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,526 +19,13 @@ */ #include "config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "rpmostree-util.h" -#include "rpmostree-builtins.h" -#include "rpmostree-polkit-agent.h" - -#include "libglnx.h" - -static RpmOstreeCommand commands[] = { - { "compose", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | - RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT), - "Commands to compose a tree", - rpmostree_builtin_compose }, - { "cleanup", static_cast(0), - "Clear cached/pending data", - rpmostree_builtin_cleanup }, - { "db", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD), - "Commands to query the RPM database", - rpmostree_builtin_db }, - { "deploy", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), - "Deploy a specific commit", - rpmostree_builtin_deploy }, - { "rebase", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), - "Switch to a different tree", - rpmostree_builtin_rebase }, - { "rollback", static_cast(0), - "Revert to the previously booted tree", - rpmostree_builtin_rollback }, - { "status", static_cast(0), - "Get the version of the booted system", - rpmostree_builtin_status }, - { "upgrade", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), - "Perform a system upgrade", - rpmostree_builtin_upgrade }, - { "update", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS | RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - "Alias for upgrade", - rpmostree_builtin_upgrade }, - { "reload", static_cast(0), - "Reload configuration", - rpmostree_builtin_reload }, - { "usroverlay", static_cast(RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT), - "Apply a transient overlayfs to /usr", - rpmostree_builtin_usroverlay }, - /* Let's be "cognitively" compatible with `ostree admin unlock` */ - { "unlock", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, - rpmostree_builtin_usroverlay }, - { "cancel", static_cast(0), - "Cancel an active transaction", - rpmostree_builtin_cancel }, - { "initramfs", static_cast(0), - "Enable or disable local initramfs regeneration", - rpmostree_builtin_initramfs }, - { "install", static_cast(0), - "Overlay additional packages", - rpmostree_builtin_install }, - { "uninstall", static_cast(0), - "Remove overlayed additional packages", - rpmostree_builtin_uninstall }, - { "override", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD), - "Manage base package overrides", rpmostree_builtin_override }, - { "reset", static_cast(RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS), - "Remove all mutations", - rpmostree_builtin_reset }, - { "refresh-md", static_cast(0), - "Generate rpm repo metadata", - rpmostree_builtin_refresh_md }, - { "kargs", static_cast(0), - "Query or modify kernel arguments", - rpmostree_builtin_kargs }, - /* Legacy aliases */ - { "pkg-add", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_install }, - { "pkg-remove", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_uninstall }, - { "rpm", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | - RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_db }, - /* Compat with dnf */ - { "remove", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_uninstall }, - { "makecache", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_refresh_md }, - /* Hidden */ - { "ex", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | - RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - "Experimental commands that may change or be removed in the future", - rpmostree_builtin_ex }, - { "testutils", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | - RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_testutils }, - { "shlib-backend", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | - RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_shlib_backend }, - { "start-daemon", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | - RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT | - RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_start_daemon }, - { "finalize-deployment", static_cast(RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_finalize_deployment }, - { "cliwrap", static_cast(RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD | RPM_OSTREE_BUILTIN_FLAG_HIDDEN), - NULL, rpmostree_builtin_cliwrap }, - { NULL } -}; - -static gboolean opt_version; -static gboolean opt_force_peer; -static char *opt_sysroot; -static gchar **opt_install; -static gchar **opt_uninstall; - -static GOptionEntry global_entries[] = { - { "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print version information and exit", NULL }, - { NULL } -}; - -static GOptionEntry daemon_entries[] = { - { "sysroot", 0, 0, G_OPTION_ARG_STRING, &opt_sysroot, "Use system root SYSROOT (default: /)", "SYSROOT" }, - { "peer", 0, 0, G_OPTION_ARG_NONE, &opt_force_peer, "Force a peer-to-peer connection instead of using the system message bus", NULL }, - { NULL } -}; - -static GOptionEntry pkg_entries[] = { - { "install", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_install, "Overlay additional package", "PKG" }, - { "uninstall", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_uninstall, "Remove overlayed additional package", "PKG" }, - { NULL } -}; - -static GOptionContext * -option_context_new_with_commands (RpmOstreeCommandInvocation *invocation, - RpmOstreeCommand *commands) -{ - g_autoptr(GOptionContext) context = g_option_context_new ("COMMAND"); - g_autoptr(GString) summary = g_string_new (NULL); - - if (invocation) - { - if (invocation->command->description != NULL) - g_string_append_printf (summary, "%s\n\n", - invocation->command->description); - - g_string_append_printf (summary, "Builtin \"%s\" Commands:", - invocation->command->name); - } - else /* top level */ - g_string_append (summary, "Builtin Commands:"); - - for (RpmOstreeCommand *command = commands; command->name != NULL; command++) - { - gboolean hidden = (command->flags & RPM_OSTREE_BUILTIN_FLAG_HIDDEN) > 0; - if (!hidden) - { - g_string_append_printf (summary, "\n %-17s", command->name); - if (command->description != NULL) - g_string_append_printf (summary, "%s", command->description); - } - } - - g_option_context_set_summary (context, summary->str); - return util::move_nullify (context); -} - -gboolean -rpmostree_option_context_parse (GOptionContext *context, - const GOptionEntry *main_entries, - int *argc, - char ***argv, - RpmOstreeCommandInvocation *invocation, - GCancellable *cancellable, - const char *const* *out_install_pkgs, - const char *const* *out_uninstall_pkgs, - RPMOSTreeSysroot **out_sysroot_proxy, - GPid *out_peer_pid, - GBusType *out_bus_type, - GError **error) -{ - /* with --version there's no command, don't require a daemon for it */ - const RpmOstreeBuiltinFlags flags = - invocation ? invocation->command->flags : RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD; - gboolean use_daemon = ((flags & RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD) == 0); - - if (invocation && invocation->command->description != NULL) - { - /* The extra summary explanation is only provided for commands with description */ - const char* context_summary = g_option_context_get_summary (context); - - /* check whether the summary has been set earlier */ - if (context_summary == NULL) - g_option_context_set_summary (context, invocation->command->description); - } - - if (main_entries != NULL) - g_option_context_add_main_entries (context, main_entries, NULL); - - if (use_daemon) - g_option_context_add_main_entries (context, daemon_entries, NULL); - - if ((flags & RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS) > 0) - g_option_context_add_main_entries (context, pkg_entries, NULL); - - g_option_context_add_main_entries (context, global_entries, NULL); - - if (!g_option_context_parse (context, argc, argv, error)) - return FALSE; - - if (opt_version) - { - /* This should now be YAML, like `docker version`, so it's both nice to read - * possible to parse. The canonical implementation of this is in - * ostree/ot-main.c. - */ - g_auto(GStrv) features = g_strsplit (RPM_OSTREE_FEATURES, " ", -1); - g_print ("%s:\n", PACKAGE_NAME); - g_print (" Version: '%s'\n", PACKAGE_VERSION); - if (strlen (RPM_OSTREE_GITREV) > 0) - g_print (" Git: %s\n", RPM_OSTREE_GITREV); - g_print (" Features:\n"); - for (char **iter = features; iter && *iter; iter++) - g_print (" - %s\n", *iter); - exit (EXIT_SUCCESS); - } - - if ((flags & RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT) > 0 - && getuid () != 0 - && getenv ("RPMOSTREE_SUPPRESS_REQUIRES_ROOT_CHECK") == NULL) - return glnx_throw (error, "This command requires root privileges"); - - if (use_daemon) - { - /* More gracefully handle the case where - * no --sysroot option was specified and we're not booted via ostree - * https://github.com/projectatomic/rpm-ostree/issues/1537 - */ - if (!opt_sysroot) - { - if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error)) - return FALSE; - if (errno == ENOENT) - return glnx_throw (error, "This system was not booted via libostree; cannot operate"); - } - - /* root never needs to auth */ - if (getuid () != 0) - /* ignore errors; we print out a warning if we fail to spawn pkttyagent */ - (void)rpmostree_polkit_agent_open (); - - if (!rpmostree_load_sysroot (opt_sysroot, - opt_force_peer, - cancellable, - out_sysroot_proxy, - out_peer_pid, - out_bus_type, - error)) - return FALSE; - } - - if (out_install_pkgs) - *out_install_pkgs = (const char *const*)opt_install; - if (out_uninstall_pkgs) - *out_uninstall_pkgs = (const char *const*)opt_uninstall; - - return TRUE; -} - -static RpmOstreeCommand * -lookup_command (const char *name) -{ - RpmOstreeCommand *command = commands; - - while (command->name) - { - if (g_strcmp0 (name, command->name) == 0) - return command; - command++; - } - return NULL; -} - -static const char * -rpmostree_subcommand_parse (int *inout_argc, - char **inout_argv) -{ - const int argc = *inout_argc; - const char *command_name = NULL; - 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 (inout_argv[in][0] != '-') - { - if (command_name == NULL) - { - command_name = inout_argv[in]; - out--; - continue; - } - } - - else if (g_str_equal (inout_argv[in], "--")) - { - break; - } - - inout_argv[out] = inout_argv[in]; - } - - *inout_argc = out; - return command_name; -} - -gboolean -rpmostree_handle_subcommand (int argc, char **argv, - RpmOstreeCommand *subcommands, - RpmOstreeCommandInvocation *invocation, - GCancellable *cancellable, GError **error) -{ - const char *subcommand_name = - rpmostree_subcommand_parse (&argc, argv); - - RpmOstreeCommand *subcommand = subcommands; - while (subcommand->name) - { - if (g_strcmp0 (subcommand_name, subcommand->name) == 0) - break; - subcommand++; - } - - if (!subcommand->name) - { - g_autoptr(GOptionContext) context = - option_context_new_with_commands (invocation, subcommands); - - /* This will not return for some options (e.g. --version). */ - (void) rpmostree_option_context_parse (context, NULL, - &argc, &argv, - invocation, - cancellable, - NULL, NULL, NULL, NULL, NULL, NULL); - if (subcommand_name == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "No \"%s\" subcommand specified", - invocation->command->name); - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unknown \"%s\" subcommand \"%s\"", - invocation->command->name, - subcommand_name); - } - - g_autofree char *help = g_option_context_get_help (context, FALSE, NULL); - g_printerr ("%s", help); - return FALSE; - } - - g_autofree char *prgname = - g_strdup_printf ("%s %s", g_get_prgname (), subcommand_name); - g_set_prgname (prgname); - - /* We need a new sub-invocation with the new command, which also carries a new - * exit code, but we'll proxy the latter. */ - RpmOstreeCommandInvocation sub_invocation = { .command = subcommand, - .command_line = invocation->command_line, - .exit_code = -1 }; - gboolean ret = subcommand->fn (argc, argv, &sub_invocation, cancellable, error); - /* Proxy the exit code */ - invocation->exit_code = sub_invocation.exit_code; - return ret; -} - -static char* -rebuild_command_line (int argc, - char **argv) -{ - /* be nice and quote args as needed instead of just g_strjoinv() */ - g_autoptr(GString) command = g_string_new (NULL); - for (int i = 1; i < argc; i++) - { - g_autofree char *quoted = rpmostree_maybe_shell_quote (argv[i]); - g_string_append (command, quoted ?: argv[i]); - g_string_append_c (command, ' '); - } - return g_string_free (util::move_nullify (command), FALSE); -} +#include "rpmostreemain.h" int -main (int argc, - char **argv) +main (int argc, char **argv) { - RpmOstreeCommand *command; - RpmOstreeCommandInvocation invocation; - const char *command_name = NULL; - g_autofree char *prgname = NULL; - GError *local_error = NULL; - gboolean funcres; - /* We can leave this function with an error status from both a command - * invocation, as well as an option processing failure. Keep an alias to the - * two places that hold status codes. - */ - int exit_status = EXIT_SUCCESS; - int *exit_statusp = &exit_status; - - /* avoid gvfs (http://bugzilla.gnome.org/show_bug.cgi?id=526454) */ - g_setenv ("GIO_USE_VFS", "local", TRUE); - /* There's not really a "root dconf" right now; otherwise we might - * try to spawn one via GSocketClient → GProxyResolver → GSettings. - * - * https://github.com/projectatomic/rpm-ostree/pull/312 - * https://bugzilla.gnome.org/show_bug.cgi?id=767183 - **/ - if (getuid () == 0) - g_assert (g_setenv ("GSETTINGS_BACKEND", "memory", TRUE)); - g_set_prgname (argv[0]); - - setlocale (LC_ALL, ""); - - /* We don't support /etc/dnf/dnf.conf, so tell libdnf to not look for it. The function - * name here is misleading; it's not attached to a `DnfContext` object, but instead - * controls a global var. And it's not just the `DnfContext` that uses it, but e.g. - * `DnfSack` and Repo too. So just do this upfront. XXX: Clean up that API so it's always - * attached to a context object. */ - dnf_context_set_config_file_path(""); - - GCancellable *cancellable = g_cancellable_new (); - - g_autofree char *command_line = rebuild_command_line (argc, argv); - - /* - * Parse the global options. We rearrange the options as - * necessary, in order to pass relevant options through - * to the commands, but also have them take effect globally. + /* All of the code is an internal static library, so we can use + * the GLib unit testing framework with automake. */ - command_name = rpmostree_subcommand_parse (&argc, argv); - - command = lookup_command (command_name); - - if (!command) - { - g_autoptr(GOptionContext) context = - option_context_new_with_commands (NULL, commands); - g_autofree char *help = NULL; - - /* This will not return for some options (e.g. --version). */ - (void) rpmostree_option_context_parse (context, NULL, &argc, &argv, - NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL); - if (command_name == NULL) - { - local_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, - "No command specified"); - } - else - { - local_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, - "Unknown command '%s'", command_name); - } - - help = g_option_context_get_help (context, FALSE, NULL); - g_printerr ("%s", help); - exit_status = EXIT_FAILURE; - goto out; - } - - prgname = g_strdup_printf ("%s %s", g_get_prgname (), command_name); - g_set_prgname (prgname); - - invocation = { .command = command, - .command_line = command_line, - .exit_code = -1 }; - exit_statusp = &(invocation.exit_code); - try { - funcres = command->fn (argc, argv, &invocation, cancellable, &local_error); - } catch (std::exception& e) { - // Translate exceptions into GError - funcres = glnx_throw (&local_error, "%s", e.what()); - } - if (!funcres) - { - if (invocation.exit_code == -1) - invocation.exit_code = EXIT_FAILURE; - g_assert (local_error); - goto out; - } - else - { - if (invocation.exit_code == -1) - invocation.exit_code = EXIT_SUCCESS; - else - g_assert (invocation.exit_code != EXIT_SUCCESS); - } - - out: - if (local_error != NULL) - { - int is_tty = isatty (1); - const char *prefix = ""; - const char *suffix = ""; - if (is_tty) - { - prefix = "\x1b[31m\x1b[1m"; /* red, bold */ - suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ - } - g_dbus_error_strip_remote_error (local_error); - g_printerr ("%serror: %s%s\n", prefix, suffix, local_error->message); - g_error_free (local_error); - } - - rpmostree_polkit_agent_close (); - - return *exit_statusp; + return rpmostree_main (argc, argv); } diff --git a/src/app/rpmostree-dbus-helpers.cxx b/src/app/rpmostree-dbus-helpers.cxx index 3fdd5a6055..b4aba38544 100644 --- a/src/app/rpmostree-dbus-helpers.cxx +++ b/src/app/rpmostree-dbus-helpers.cxx @@ -35,6 +35,7 @@ #include "rpmostree-util.h" #include "rpmostree-rpm-util.h" #include "rpmostree-rust.h" +#include "rpmostree-cxxrs.h" #define RPMOSTREE_CLI_ID "cli" @@ -1056,18 +1057,20 @@ rpmostree_sort_pkgs_strv (const char *const* pkgs, g_auto(GVariantBuilder) builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("ah")); - for (const char *const* pkg = pkgs; pkg && *pkg; pkg++) + for (const char *const* pkgiter = pkgs; pkgiter && *pkgiter; pkgiter++) { - if (g_str_has_prefix (*pkg, "http://") || - g_str_has_prefix (*pkg, "https://")) + auto pkg = *pkgiter; + if (g_str_has_prefix (pkg, "http://") || + g_str_has_prefix (pkg, "https://")) { - g_print ("Downloading '%s'... ", *pkg); - glnx_autofd int fd = ror_download_to_fd (*pkg, error); - if (fd < 0) - { - g_print ("failed!\n"); - return FALSE; - } + g_print ("Downloading '%s'... ", pkg); + glnx_autofd int fd = -1; + try { + fd = rpmostreecxx::download_to_fd (pkg); + } catch (std::exception& e) { + g_print ("failed!\n"); + throw; + } g_print ("done!\n"); int idx = g_unix_fd_list_append (fd_list, fd, error); @@ -1076,12 +1079,12 @@ rpmostree_sort_pkgs_strv (const char *const* pkgs, g_variant_builder_add (&builder, "h", idx); } - else if (!g_str_has_suffix (*pkg, ".rpm")) - g_ptr_array_add (repo_pkgs, g_strdup (*pkg)); + else if (!g_str_has_suffix (pkg, ".rpm")) + g_ptr_array_add (repo_pkgs, g_strdup (pkg)); else { glnx_autofd int fd = -1; - if (!glnx_openat_rdonly (AT_FDCWD, *pkg, TRUE, &fd, error)) + if (!glnx_openat_rdonly (AT_FDCWD, pkg, TRUE, &fd, error)) return FALSE; int idx = g_unix_fd_list_append (fd_list, fd, error); diff --git a/src/app/rpmostreemain.h b/src/app/rpmostreemain.h new file mode 100644 index 0000000000..986f872e70 --- /dev/null +++ b/src/app/rpmostreemain.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +int rpmostree_main (int argc, char **argv); + +G_END_DECLS diff --git a/src/libpriv/gresources.xml b/src/libpriv/gresources.xml deleted file mode 100644 index 53d57b9cd6..0000000000 --- a/src/libpriv/gresources.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - systemctl-wrapper.sh - - dracut-random.cpio.gz - - diff --git a/src/libpriv/rpmostree-core.cxx b/src/libpriv/rpmostree-core.cxx index 6969207edc..a66dd00582 100644 --- a/src/libpriv/rpmostree-core.cxx +++ b/src/libpriv/rpmostree-core.cxx @@ -43,6 +43,7 @@ #include "rpmostree-importer.h" #include "rpmostree-output.h" #include "rpmostree-rust.h" +#include "rpmostree-cxxrs.h" #define RPMOSTREE_MESSAGE_COMMIT_STATS SD_ID128_MAKE(e6,37,2e,38,41,21,42,a9,bc,13,b6,32,b3,f8,93,44) #define RPMOSTREE_MESSAGE_SELINUX_RELABEL SD_ID128_MAKE(5a,e0,56,34,f2,d7,49,3b,b1,58,79,b7,0c,02,e6,5d) @@ -4322,9 +4323,7 @@ rpmostree_context_assemble (RpmOstreeContext *self, gboolean skip_sanity_check = FALSE; g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check); - RORTempEtcGuard * etc_guard = ror_tempetc_undo_usretc (tmprootfs_dfd, error); - if (etc_guard == NULL) - return FALSE; + auto etc_guard = rpmostreecxx::prepare_tempetc_guard (tmprootfs_dfd); /* NB: we're not running scripts right now for removals, so this is only for overlays and * replacements */ @@ -4370,15 +4369,9 @@ rpmostree_context_assemble (RpmOstreeContext *self, else { have_systemctl = TRUE; - g_autoptr(GBytes) systemctl_wrapper = g_resources_lookup_data ("/rpmostree/systemctl-wrapper.sh", - G_RESOURCE_LOOKUP_FLAGS_NONE, - error); - if (!systemctl_wrapper) - return FALSE; - size_t len; - auto buf = static_cast(g_bytes_get_data (systemctl_wrapper, &len)); + auto systemctl_wrapper = rpmostreecxx::get_systemctl_wrapper (); if (!glnx_file_replace_contents_with_perms_at (tmprootfs_dfd, "usr/bin/systemctl", - buf, len, 0755, (uid_t) -1, (gid_t) -1, + systemctl_wrapper.data(), systemctl_wrapper.length(), 0755, (uid_t) -1, (gid_t) -1, GLNX_FILE_REPLACE_NODATASYNC, cancellable, error)) return FALSE; @@ -4576,8 +4569,7 @@ rpmostree_context_assemble (RpmOstreeContext *self, } /* Undo the /etc move above */ - if (!ror_tempetc_redo_usretc (etc_guard, error)) - return FALSE; + etc_guard->undo(); /* And clean up var/tmp, we don't want it in commits */ if (!glnx_shutil_rm_rf_at (tmprootfs_dfd, "var/tmp", cancellable, error)) diff --git a/src/libpriv/rpmostree-kernel.cxx b/src/libpriv/rpmostree-kernel.cxx index 83e78ceb2b..953f598eef 100644 --- a/src/libpriv/rpmostree-kernel.cxx +++ b/src/libpriv/rpmostree-kernel.cxx @@ -39,6 +39,7 @@ #include "rpmostree-kernel.h" #include "rpmostree-bwrap.h" #include "rpmostree-rust.h" +#include "rpmostree-cxxrs.h" #include "rpmostree-util.h" static const char usrlib_ostreeboot[] = "usr/lib/ostree-boot"; @@ -524,15 +525,13 @@ rpmostree_run_dracut (int rootfs_dfd, g_autoptr(RpmOstreeBwrap) bwrap = NULL; g_autoptr(GPtrArray) rebuild_argv = NULL; g_auto(GLnxTmpfile) tmpf = { 0, }; - g_autoptr(GBytes) random_cpio_data = NULL; /* We need to have /etc/passwd since dracut doesn't have altfiles * today. Though maybe in the future we should add it, but * in the end we want to use systemd-sysusers of course. **/ - RORTempEtcGuard * etc_guard = ror_tempetc_undo_usretc (rootfs_dfd, error); - if (etc_guard == NULL) - return FALSE; + auto etc_guard = rpmostreecxx::prepare_tempetc_guard (rootfs_dfd); + gboolean have_passwd = FALSE; if (!rpmostree_passwd_prepare_rpm_layering (rootfs_dfd, NULL, @@ -627,16 +626,10 @@ rpmostree_run_dracut (int rootfs_dfd, * https://bugzilla.redhat.com/show_bug.cgi?id=1401444 * https://bugzilla.redhat.com/show_bug.cgi?id=1380866 * */ - random_cpio_data = g_resources_lookup_data ("/rpmostree/dracut-random.cpio.gz", - G_RESOURCE_LOOKUP_FLAGS_NONE, - error); - if (!random_cpio_data) - return FALSE; - gsize random_cpio_data_len = 0; - auto random_cpio_data_p = static_cast(g_bytes_get_data (random_cpio_data, &random_cpio_data_len)); + auto random_cpio_data = rpmostreecxx::get_dracut_random_cpio (); if (lseek (tmpf.fd, 0, SEEK_END) < 0) return glnx_throw_errno_prefix (error, "lseek"); - if (glnx_loop_write (tmpf.fd, random_cpio_data_p, random_cpio_data_len) < 0) + if (glnx_loop_write (tmpf.fd, random_cpio_data.data(), random_cpio_data.length()) < 0) return glnx_throw_errno_prefix (error, "write"); if (rebuild_from_initramfs) @@ -645,8 +638,7 @@ rpmostree_run_dracut (int rootfs_dfd, if (have_passwd && !rpmostree_passwd_complete_rpm_layering (rootfs_dfd, error)) return FALSE; - if (!ror_tempetc_redo_usretc (etc_guard, error)) - return FALSE; + etc_guard->undo(); *out_initramfs_tmpf = tmpf; tmpf.initialized = FALSE; /* Transfer */ return TRUE;