Files
flatpak/app/flatpak-main.c
Alexander Larsson f239a6e6a4 Add flatpak repair command
This is a fsck-like command that tries to automatically repair your
flatpak installations. It works by running an fsck on all the refs in
the local repo, and removing all refs that has an unexpectedly missing
(or deleted) object.

Then it runs a prune, which will remove all the object that were
referenced by these removed refs (that were not references by anything
else), so that any non-detected invalid objects are also removed.

Then it looks at all the deployed refs, and if they lack a ref in
the local repo, we `install --reinstall $origin $ref` them.

Closes: #1739
Approved by: alexlarsson
2018-05-31 15:37:09 +00:00

624 lines
21 KiB
C

/*
* Copyright © 2014 Red Hat, Inc
*
* 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.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, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "libglnx/libglnx.h"
#include "flatpak-builtins.h"
#include "flatpak-utils-private.h"
static int opt_verbose;
static gboolean opt_ostree_verbose;
static gboolean opt_version;
static gboolean opt_default_arch;
static gboolean opt_supported_arches;
static gboolean opt_gl_drivers;
static gboolean opt_user;
static gboolean opt_system;
static char **opt_installations;
static gboolean is_in_complete;
typedef struct
{
const char *name;
const char *description;
gboolean (*fn)(int argc,
char **argv,
GCancellable *cancellable,
GError **error);
gboolean (*complete)(FlatpakCompletion *completion);
gboolean deprecated;
} FlatpakCommand;
static FlatpakCommand commands[] = {
/* translators: please keep the leading space */
{ N_(" Manage installed apps and runtimes") },
{ "install", N_("Install an application or runtime"), flatpak_builtin_install, flatpak_complete_install },
{ "update", N_("Update an installed application or runtime"), flatpak_builtin_update, flatpak_complete_update },
{ "uninstall", N_("Uninstall an installed application or runtime"), flatpak_builtin_uninstall, flatpak_complete_uninstall },
/* Alias remove to uninstall to help users of yum/dnf/apt */
{ "remove", NULL, flatpak_builtin_uninstall, flatpak_complete_uninstall, TRUE },
{ "list", N_("List installed apps and/or runtimes"), flatpak_builtin_list, flatpak_complete_list },
{ "info", N_("Show info for installed app or runtime"), flatpak_builtin_info, flatpak_complete_info },
{ "config", N_("Configure flatpak"), flatpak_builtin_config, flatpak_complete_config },
{ "repair", N_("Repair flatpak installation"), flatpak_builtin_repair, flatpak_complete_repair },
/* translators: please keep the leading newline and space */
{ N_("\n Finding applications and runtimes") },
{ "search", N_("Search for remote apps/runtimes"), flatpak_builtin_search, flatpak_complete_search },
/* translators: please keep the leading newline and space */
{ N_("\n Running applications") },
{ "run", N_("Run an application"), flatpak_builtin_run, flatpak_complete_run },
{ "override", N_("Override permissions for an application"), flatpak_builtin_override, flatpak_complete_override },
{ "make-current", N_("Specify default version to run"), flatpak_builtin_make_current_app, flatpak_complete_make_current_app },
{ "enter", N_("Enter the namespace of a running application"), flatpak_builtin_enter, flatpak_complete_enter },
/* translators: please keep the leading newline and space */
{ N_("\n Manage file access") },
{ "document-export", N_("Grant an application access to a specific file"), flatpak_builtin_document_export, flatpak_complete_document_export },
{ "document-unexport", N_("Revoke access to a specific file"), flatpak_builtin_document_unexport, flatpak_complete_document_unexport },
{ "document-info", N_("Show information about a specific file"), flatpak_builtin_document_info, flatpak_complete_document_info },
{ "document-list", N_("List exported files"), flatpak_builtin_document_list, flatpak_complete_document_list },
/* translators: please keep the leading newline and space */
{ N_("\n Manage remote repositories") },
{ "remotes", N_("List all configured remotes"), flatpak_builtin_list_remotes, flatpak_complete_list_remotes },
{ "remote-add", N_("Add a new remote repository (by URL)"), flatpak_builtin_add_remote, flatpak_complete_add_remote },
{ "remote-modify", N_("Modify properties of a configured remote"), flatpak_builtin_modify_remote, flatpak_complete_modify_remote },
{ "remote-delete", N_("Delete a configured remote"), flatpak_builtin_delete_remote, flatpak_complete_delete_remote },
{ "remote-list", NULL, flatpak_builtin_list_remotes, flatpak_complete_list_remotes, TRUE },
{ "remote-ls", N_("List contents of a configured remote"), flatpak_builtin_ls_remote, flatpak_complete_ls_remote },
{ "remote-info", N_("Show information about a remote app or runtime"), flatpak_builtin_info_remote, flatpak_complete_info_remote },
/* translators: please keep the leading newline and space */
{ N_("\n Build applications") },
{ "build-init", N_("Initialize a directory for building"), flatpak_builtin_build_init, flatpak_complete_build_init },
{ "build", N_("Run a build command inside the build dir"), flatpak_builtin_build, flatpak_complete_build },
{ "build-finish", N_("Finish a build dir for export"), flatpak_builtin_build_finish, flatpak_complete_build_finish },
{ "build-export", N_("Export a build dir to a repository"), flatpak_builtin_build_export, flatpak_complete_build_export },
{ "build-bundle", N_("Create a bundle file from a ref in a local repository"), flatpak_builtin_build_bundle, flatpak_complete_build_bundle },
{ "build-import-bundle", N_("Import a bundle file"), flatpak_builtin_build_import, flatpak_complete_build_import },
{ "build-sign", N_("Sign an application or runtime"), flatpak_builtin_build_sign, flatpak_complete_build_sign },
{ "build-update-repo", N_("Update the summary file in a repository"), flatpak_builtin_build_update_repo, flatpak_complete_build_update_repo },
{ "build-commit-from", N_("Create new commit based on existing ref"), flatpak_builtin_build_commit_from, flatpak_complete_build_commit_from },
{ "repo", N_("Print information about a repo"), flatpak_builtin_repo, flatpak_complete_repo },
{ NULL }
};
static gboolean
opt_verbose_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
opt_verbose++;
return TRUE;
}
GOptionEntry global_entries[] = {
{ "verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &opt_verbose_cb, N_("Print debug information during command processing, -vv for more detail"), NULL },
{ "ostree-verbose", 0, 0, G_OPTION_ARG_NONE, &opt_ostree_verbose, N_("Print OSTree debug information during command processing"), NULL },
{ "help", '?', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, N_("Show help options"), NULL, NULL },
{ NULL }
};
static GOptionEntry empty_entries[] = {
{ "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, N_("Print version information and exit"), NULL },
{ "default-arch", 0, 0, G_OPTION_ARG_NONE, &opt_default_arch, N_("Print default arch and exit"), NULL },
{ "supported-arches", 0, 0, G_OPTION_ARG_NONE, &opt_supported_arches, N_("Print supported arches and exit"), NULL },
{ "gl-drivers", 0, 0, G_OPTION_ARG_NONE, &opt_gl_drivers, N_("Print active gl drivers and exit"), NULL },
{ NULL }
};
GOptionEntry user_entries[] = {
{ "user", 0, 0, G_OPTION_ARG_NONE, &opt_user, N_("Work on user installations"), NULL },
{ "system", 0, 0, G_OPTION_ARG_NONE, &opt_system, N_("Work on system-wide installations (default)"), NULL },
{ "installation", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_installations, N_("Work on specific system-wide installation(s)"), N_("NAME") },
{ NULL }
};
static void
message_handler (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
/* Make this look like normal console output */
if (log_level & G_LOG_LEVEL_DEBUG)
g_printerr ("F: %s\n", message);
else
g_printerr ("%s: %s\n", g_get_prgname (), message);
}
static GOptionContext *
flatpak_option_context_new_with_commands (FlatpakCommand *commands)
{
GOptionContext *context;
GString *summary;
context = g_option_context_new (_("COMMAND"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
summary = g_string_new (_("Builtin Commands:"));
while (commands->name != NULL)
{
if (!commands->deprecated)
{
if (commands->fn != NULL)
{
g_string_append_printf (summary, "\n %s", commands->name);
if (commands->description)
g_string_append_printf (summary, "%*s%s", (int) (20 - strlen (commands->name)), "", _(commands->description));
}
else
{
g_string_append_printf (summary, "\n%s", _(commands->name));
}
}
commands++;
}
g_option_context_set_summary (context, summary->str);
g_string_free (summary, TRUE);
return context;
}
static int
flatpak_usage (FlatpakCommand *commands,
gboolean is_error)
{
GOptionContext *context;
g_autofree char *help = NULL;
context = flatpak_option_context_new_with_commands (commands);
g_option_context_add_main_entries (context, global_entries, NULL);
help = g_option_context_get_help (context, FALSE, NULL);
if (is_error)
g_printerr ("%s", help);
else
g_print ("%s", help);
g_option_context_free (context);
return is_error ? 1 : 0;
}
gboolean
flatpak_option_context_parse (GOptionContext *context,
const GOptionEntry *main_entries,
int *argc,
char ***argv,
FlatpakBuiltinFlags flags,
GPtrArray **out_dirs,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GPtrArray) dirs = NULL;
if (!(flags & FLATPAK_BUILTIN_FLAG_NO_DIR) &&
!(flags & FLATPAK_BUILTIN_FLAG_ONE_DIR) &&
!(flags & FLATPAK_BUILTIN_FLAG_STANDARD_DIRS) &&
!(flags & FLATPAK_BUILTIN_FLAG_ALL_DIRS))
g_assert_not_reached ();
if (flags & FLATPAK_BUILTIN_FLAG_NO_DIR &&
(flags & FLATPAK_BUILTIN_FLAG_ONE_DIR ||
flags & FLATPAK_BUILTIN_FLAG_STANDARD_DIRS ||
flags & FLATPAK_BUILTIN_FLAG_ALL_DIRS))
g_assert_not_reached ();
if (flags & FLATPAK_BUILTIN_FLAG_ONE_DIR &&
(flags & FLATPAK_BUILTIN_FLAG_STANDARD_DIRS ||
flags & FLATPAK_BUILTIN_FLAG_ALL_DIRS))
g_assert_not_reached ();
if (flags & FLATPAK_BUILTIN_FLAG_STANDARD_DIRS &&
flags & FLATPAK_BUILTIN_FLAG_ALL_DIRS)
g_assert_not_reached ();
if (!(flags & FLATPAK_BUILTIN_FLAG_NO_DIR))
g_option_context_add_main_entries (context, user_entries, NULL);
if (main_entries != NULL)
g_option_context_add_main_entries (context, main_entries, NULL);
g_option_context_add_main_entries (context, global_entries, NULL);
if (!g_option_context_parse (context, argc, argv, error))
return FALSE;
/* We never want verbose output in the complete case, that breaks completion */
if (!is_in_complete)
{
if (opt_verbose > 0)
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL);
if (opt_verbose > 1)
g_log_set_handler (G_LOG_DOMAIN "2", G_LOG_LEVEL_DEBUG, message_handler, NULL);
if (opt_ostree_verbose)
g_log_set_handler ("OSTree", G_LOG_LEVEL_DEBUG, message_handler, NULL);
}
if (opt_version)
{
g_print ("%s\n", PACKAGE_STRING);
exit (EXIT_SUCCESS);
}
if (opt_default_arch)
{
g_print ("%s\n", flatpak_get_arch ());
exit (EXIT_SUCCESS);
}
if (opt_supported_arches)
{
const char **arches = flatpak_get_arches ();
int i;
for (i = 0; arches[i] != NULL; i++)
g_print ("%s\n", arches[i]);
exit (EXIT_SUCCESS);
}
if (opt_gl_drivers)
{
const char **drivers = flatpak_get_gl_drivers ();
int i;
for (i = 0; drivers[i] != NULL; i++)
g_print ("%s\n", drivers[i]);
exit (EXIT_SUCCESS);
}
if (!(flags & FLATPAK_BUILTIN_FLAG_NO_DIR))
{
dirs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
int i;
if (!(flags & FLATPAK_BUILTIN_FLAG_ONE_DIR))
{
/*
* FLATPAK_BUILTIN_FLAG_STANDARD_DIRS or FLATPAK_BUILTIN_FLAG_ALL_DIRS
* must be set.
*/
if (opt_user || (!opt_system && opt_installations == NULL))
g_ptr_array_add (dirs, flatpak_dir_get_user ());
if (opt_system || (!opt_user && opt_installations == NULL))
g_ptr_array_add (dirs, flatpak_dir_get_system_default ());
if (opt_installations != NULL)
{
for (i = 0; opt_installations[i] != NULL; i++)
{
FlatpakDir *installation_dir = NULL;
/* Already included the default system installation */
if (opt_system && g_strcmp0 (opt_installations[i], "default") == 0)
continue;
installation_dir = flatpak_dir_get_system_by_id (opt_installations[i], cancellable, error);
if (installation_dir == NULL)
return FALSE;
g_ptr_array_add (dirs, installation_dir);
}
}
if (flags & FLATPAK_BUILTIN_FLAG_ALL_DIRS &&
opt_installations == NULL && !opt_user && !opt_system)
{
g_autoptr(GPtrArray) system_dirs = NULL;
g_ptr_array_set_size (dirs, 0);
g_ptr_array_add (dirs, flatpak_dir_get_user ());
system_dirs = flatpak_dir_get_system_list (cancellable, error);
if (system_dirs == NULL)
return FALSE;
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (system_dirs, i);
g_ptr_array_add (dirs, g_object_ref (dir));
}
}
}
else /* FLATPAK_BUILTIN_FLAG_ONE_DIR */
{
FlatpakDir *dir;
if (opt_system || (!opt_user && opt_installations == NULL))
dir = flatpak_dir_get_system_default ();
else if (opt_user)
dir = flatpak_dir_get_user ();
else if (opt_installations != NULL)
{
if (g_strv_length (opt_installations) > 1)
return usage_error (context, _("The --installation option was used multiple times "
"for a command that works on one installation"), error);
dir = flatpak_dir_get_system_by_id (opt_installations[0], cancellable, error);
if (dir == NULL)
return FALSE;
}
else
g_assert_not_reached ();
g_ptr_array_add (dirs, dir);
}
for (i = 0; i < dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i);
if (flags & FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO)
{
if (!flatpak_dir_maybe_ensure_repo (dir, cancellable, error))
return FALSE;
}
else
{
if (!flatpak_dir_ensure_repo (dir, cancellable, error))
return FALSE;
}
flatpak_log_dir_access (dir);
}
}
if (out_dirs)
*out_dirs = g_steal_pointer (&dirs);
return TRUE;
}
gboolean
usage_error (GOptionContext *context, const char *message, GError **error)
{
g_autofree gchar *help = g_option_context_get_help (context, TRUE, NULL);
g_printerr ("%s", help);
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message);
return FALSE;
}
static FlatpakCommand *
extract_command (int *argc,
char **argv,
const char **command_name_out)
{
FlatpakCommand *command;
const char *command_name = NULL;
int in, out;
/*
* 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.
*/
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 (command_name == NULL)
{
command_name = argv[in];
out--;
continue;
}
}
argv[out] = argv[in];
}
*argc = out;
argv[out] = NULL;
command = commands;
while (command->name)
{
if (command->fn != NULL &&
g_strcmp0 (command_name, command->name) == 0)
break;
command++;
}
*command_name_out = command_name;
return command;
}
static int
flatpak_run (int argc,
char **argv,
GError **res_error)
{
FlatpakCommand *command;
GError *error = NULL;
GCancellable *cancellable = NULL;
g_autofree char *prgname = NULL;
gboolean success = FALSE;
const char *command_name = NULL;
command = extract_command (&argc, argv, &command_name);
if (!command->fn)
{
GOptionContext *context;
g_autofree char *help = NULL;
context = flatpak_option_context_new_with_commands (commands);
if (command_name != NULL)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Unknown command '%s'"), command_name);
}
else
{
/* This will not return for some options (e.g. --version). */
if (flatpak_option_context_parse (context, empty_entries, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, &error))
{
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("No command specified"));
}
}
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 (), command_name);
g_set_prgname (prgname);
if (!command->fn (argc, argv, cancellable, &error))
goto out;
success = TRUE;
out:
g_assert (success || error);
if (error)
{
g_propagate_error (res_error, error);
return 1;
}
return 0;
}
static int
complete (int argc,
char **argv)
{
FlatpakCommand *command;
FlatpakCompletion *completion;
const char *command_name = NULL;
is_in_complete = TRUE;
completion = flatpak_completion_new (argv[2], argv[3], argv[4]);
if (completion == NULL)
return 1;
command = extract_command (&completion->argc, completion->argv, &command_name);
flatpak_completion_debug ("command=%p '%s'", command->fn, command->name);
if (!command->fn)
{
FlatpakCommand *c = commands;
while (c->name)
{
if (c->fn != NULL)
flatpak_complete_word (completion, "%s ", c->name);
c++;
}
flatpak_complete_options (completion, global_entries);
flatpak_complete_options (completion, empty_entries);
flatpak_complete_options (completion, user_entries);
}
else if (command->complete)
{
if (!command->complete (completion))
return 1;
}
else
{
flatpak_complete_options (completion, global_entries);
}
return 0;
}
int
main (int argc,
char **argv)
{
GError *error = NULL;
g_autofree const char *old_env = NULL;
int ret;
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_WARNING, message_handler, NULL);
g_set_prgname (argv[0]);
/* avoid gvfs (http://bugzilla.gnome.org/show_bug.cgi?id=526454) */
old_env = g_strdup (g_getenv ("GIO_USE_VFS"));
g_setenv ("GIO_USE_VFS", "local", TRUE);
g_vfs_get_default ();
if (old_env)
g_setenv ("GIO_USE_VFS", old_env, TRUE);
else
g_unsetenv ("GIO_USE_VFS");
if (argc >= 4 && strcmp (argv[1], "complete") == 0)
return complete (argc, argv);
flatpak_migrate_from_xdg_app ();
ret = flatpak_run (argc, argv, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
flatpak_usage (commands, TRUE);
if (error != NULL)
{
const char *prefix = "";
const char *suffix = "";
if (flatpak_fancy_output ())
{
prefix = FLATPAK_ANSI_RED FLATPAK_ANSI_BOLD_ON;
suffix = FLATPAK_ANSI_BOLD_OFF FLATPAK_ANSI_COLOR_RESET;
}
g_printerr ("%s%s %s%s\n", prefix, _("error:"), suffix, error->message);
g_error_free (error);
}
return ret;
}