rpm-ostree/src/app/main.c
Colin Walters af5c74f9ea app/main: Give an immediate error if no /run/ostree-booted
Let's be more graceful here for people who `yum install rpm-ostree`
and expect magical[1] things to happen.

Closes: https://github.com/projectatomic/rpm-ostree/issues/1537

[1] Understandably if one doesn't know lots of background

Closes: #1538
Approved by: cgwalters
2018-09-08 21:51:22 +00:00

494 lines
16 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2014 Colin Walters <walters@verbum.org>
*
* 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 <gio/gio.h>
#include <glib-unix.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include "rpmostree-builtins.h"
#include "rpmostree-polkit-agent.h"
#include "libglnx.h"
static RpmOstreeCommand commands[] = {
#ifdef HAVE_COMPOSE_TOOLING
{ "compose", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD |
RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT,
"Commands to compose a tree",
rpmostree_builtin_compose },
#endif
{ "cleanup", 0,
"Clear cached/pending data",
rpmostree_builtin_cleanup },
{ "db", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
"Commands to query the RPM database",
rpmostree_builtin_db },
{ "deploy", RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS,
"Deploy a specific commit",
rpmostree_builtin_deploy },
{ "rebase", RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS,
"Switch to a different tree",
rpmostree_builtin_rebase },
{ "rollback", 0,
"Revert to the previously booted tree",
rpmostree_builtin_rollback },
{ "status", 0,
"Get the version of the booted system",
rpmostree_builtin_status },
{ "upgrade", RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS,
"Perform a system upgrade",
rpmostree_builtin_upgrade },
{ "update", RPM_OSTREE_BUILTIN_FLAG_SUPPORTS_PKG_INSTALLS | RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
"Alias for upgrade",
rpmostree_builtin_upgrade },
{ "reload", 0,
"Reload configuration",
rpmostree_builtin_reload },
{ "usroverlay", 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", RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL,
rpmostree_builtin_usroverlay },
{ "cancel", 0,
"Cancel an active transaction",
rpmostree_builtin_cancel },
{ "initramfs", 0,
"Enable or disable local initramfs regeneration",
rpmostree_builtin_initramfs },
{ "install", 0,
"Overlay additional packages",
rpmostree_builtin_install },
{ "uninstall", 0,
"Remove overlayed additional packages",
rpmostree_builtin_uninstall },
{ "override", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
"Manage base package overrides", rpmostree_builtin_override },
{ "refresh-md", 0,
"Generate rpm repo metadata",
rpmostree_builtin_refresh_md },
{ "kargs", 0,
"Query or modify kernel arguments",
rpmostree_builtin_kargs },
/* Legacy aliases */
{ "pkg-add", RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_install },
{ "pkg-remove", RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_uninstall },
{ "rpm", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD |
RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_db },
{ "makecache", RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_refresh_md },
/* Hidden */
{ "ex", 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 },
{ "start-daemon", RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD |
RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT |
RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_start_daemon },
{ 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 g_steal_pointer (&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;
}
const char *
rpmostree_subcommand_parse (int *inout_argc,
char **inout_argv,
RpmOstreeCommandInvocation *invocation)
{
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, invocation);
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, .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;
}
int
main (int argc,
char **argv)
{
GCancellable *cancellable = g_cancellable_new ();
RpmOstreeCommand *command;
const char *command_name = NULL;
g_autofree char *prgname = NULL;
GError *local_error = NULL;
/* 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, "");
/*
* 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, NULL);
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);
RpmOstreeCommandInvocation invocation = { .command = command,
.exit_code = -1 };
exit_statusp = &(invocation.exit_code);
if (!command->fn (argc, argv, &invocation, cancellable, &local_error))
{
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;
}