mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-31 02:51:22 -05:00
Any log output interferes with comletion. We already refrain from installing our verbose log handlers in this case, but we are still vulnerable to GLib default message handling, e.g. via the G_MESSAGES_DEBUG environment variable. To avoid that, explicitly install a message handler that suppresses all output. Closes: https://github.com/flatpak/flatpak/issues/2860 Closes: #2862 Approved by: alexlarsson
840 lines
29 KiB
C
840 lines
29 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"
|
|
|
|
#ifdef USE_SYSTEM_HELPER
|
|
#include <polkit/polkit.h>
|
|
#include "flatpak-polkit-agent-text-listener.h"
|
|
|
|
/* Work with polkit before and after autoptr support was added */
|
|
typedef PolkitSubject AutoPolkitSubject;
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitSubject, g_object_unref)
|
|
#endif
|
|
|
|
#include "flatpak-builtins.h"
|
|
#include "flatpak-builtins-utils.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_list_installations;
|
|
static gboolean opt_user;
|
|
static gboolean opt_system;
|
|
static char **opt_installations;
|
|
static gboolean opt_help;
|
|
|
|
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 applications 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 },
|
|
{ "history", N_("Show history"), flatpak_builtin_history, flatpak_complete_history },
|
|
{ "config", N_("Configure flatpak"), flatpak_builtin_config, flatpak_complete_config },
|
|
{ "repair", N_("Repair flatpak installation"), flatpak_builtin_repair, flatpak_complete_repair },
|
|
{ "create-usb", N_("Put applications or runtimes onto removable media"), flatpak_builtin_create_usb, flatpak_complete_create_usb },
|
|
|
|
/* 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 },
|
|
{ "ps", N_("Enumerate running applications"), flatpak_builtin_ps, flatpak_complete_ps },
|
|
{ "kill", N_("Stop a running application"), flatpak_builtin_kill, flatpak_complete_kill },
|
|
|
|
/* translators: please keep the leading newline and space */
|
|
{ N_("\n Manage file access") },
|
|
{ "documents", N_("List exported files"), flatpak_builtin_document_list, flatpak_complete_document_list },
|
|
{ "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", NULL, flatpak_builtin_document_list, flatpak_complete_document_list, TRUE },
|
|
|
|
/* translators: please keep the leading newline and space */
|
|
{ N_("\n Manage dynamic permissions") },
|
|
{ "permissions", N_("List permissions"), flatpak_builtin_permission_list, flatpak_complete_permission_list },
|
|
{ "permission-remove", N_("Remove item from permission store"), flatpak_builtin_permission_remove, flatpak_complete_permission_remove },
|
|
{ "permission-list", NULL, flatpak_builtin_permission_list, flatpak_complete_permission_list, TRUE },
|
|
{ "permission-show", N_("Show app permissions"), flatpak_builtin_permission_show, flatpak_complete_permission_show },
|
|
{ "permission-reset", N_("Reset app permissions"), flatpak_builtin_permission_reset, flatpak_complete_permission_reset },
|
|
|
|
/* translators: please keep the leading newline and space */
|
|
{ N_("\n Manage remote repositories") },
|
|
{ "remotes", N_("List all configured remotes"), flatpak_builtin_remote_list, flatpak_complete_remote_list },
|
|
{ "remote-add", N_("Add a new remote repository (by URL)"), flatpak_builtin_remote_add, flatpak_complete_remote_add },
|
|
{ "remote-modify", N_("Modify properties of a configured remote"), flatpak_builtin_remote_modify, flatpak_complete_remote_modify },
|
|
{ "remote-delete", N_("Delete a configured remote"), flatpak_builtin_remote_delete, flatpak_complete_remote_delete },
|
|
{ "remote-list", NULL, flatpak_builtin_remote_list, flatpak_complete_remote_list, TRUE },
|
|
{ "remote-ls", N_("List contents of a configured remote"), flatpak_builtin_remote_ls, flatpak_complete_remote_ls },
|
|
{ "remote-info", N_("Show information about a remote app or runtime"), flatpak_builtin_remote_info, flatpak_complete_remote_info },
|
|
|
|
/* 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_("Show 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_("Show debug information, -vv for more detail"), NULL },
|
|
{ "ostree-verbose", 0, 0, G_OPTION_ARG_NONE, &opt_ostree_verbose, N_("Show OSTree debug information"), NULL },
|
|
{ "help", '?', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_help, 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 },
|
|
{ "installations", 0, 0, G_OPTION_ARG_NONE, &opt_list_installations, N_("Print paths for system installations and exit"), NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
GOptionEntry user_entries[] = {
|
|
{ "user", 0, 0, G_OPTION_ARG_NONE, &opt_user, N_("Work on the user installation"), NULL },
|
|
{ "system", 0, 0, G_OPTION_ARG_NONE, &opt_system, N_("Work on the system-wide installation (default)"), NULL },
|
|
{ "installation", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_installations, N_("Work on a non-default system-wide installation"), N_("NAME") },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
message_handler (const gchar *log_domain,
|
|
GLogLevelFlags log_level,
|
|
const gchar *message,
|
|
gpointer user_data)
|
|
{
|
|
g_printerr ("F: %s\n", message);
|
|
}
|
|
|
|
static void
|
|
no_message_handler (const char *log_domain,
|
|
GLogLevelFlags log_level,
|
|
const char *message,
|
|
gpointer user_data)
|
|
{
|
|
}
|
|
|
|
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);
|
|
/* Note: the 23 is there to align command descriptions with
|
|
* the option descriptions produced by GOptionContext.
|
|
*/
|
|
if (commands->description)
|
|
g_string_append_printf (summary, "%*s%s", (int) (23 - 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 void
|
|
check_environment (void)
|
|
{
|
|
const char * const *dirs;
|
|
gboolean has_system = FALSE;
|
|
gboolean has_user = FALSE;
|
|
g_autofree char *system_exports = NULL;
|
|
g_autofree char *user_exports = NULL;
|
|
int i;
|
|
int rows, cols;
|
|
|
|
/* Don't recommend restarting the session when we're not in one */
|
|
if (!g_getenv ("DBUS_SESSION_BUS_ADDRESS"))
|
|
return;
|
|
|
|
/* Avoid interfering with tests */
|
|
if (g_getenv ("FLATPAK_SYSTEM_DIR") || g_getenv ("FLATPAK_USER_DIR"))
|
|
return;
|
|
|
|
system_exports = g_build_filename (FLATPAK_SYSTEMDIR, "exports/share", NULL);
|
|
user_exports = g_build_filename (g_get_user_data_dir (), "flatpak/exports/share", NULL);
|
|
|
|
dirs = g_get_system_data_dirs ();
|
|
for (i = 0; dirs[i]; i++)
|
|
{
|
|
if (g_str_has_prefix (dirs[i], system_exports))
|
|
has_system = TRUE;
|
|
if (g_str_has_prefix (dirs[i], user_exports))
|
|
has_user = TRUE;
|
|
}
|
|
|
|
flatpak_get_window_size (&rows, &cols);
|
|
if (cols > 80)
|
|
cols = 80;
|
|
|
|
if (!has_system && !has_user)
|
|
{
|
|
g_autofree char *missing = NULL;
|
|
missing = g_strdup_printf ("\n\n '%s'\n '%s'\n\n", system_exports, user_exports);
|
|
g_print ("\n");
|
|
/* Translators: this text is automatically wrapped, don't insert line breaks */
|
|
print_wrapped (cols,
|
|
_("Note that the directories %s are not in the search path "
|
|
"set by the XDG_DATA_DIRS environment variable, so applications "
|
|
"installed by Flatpak may not appear on your desktop until the "
|
|
"session is restarted."),
|
|
missing);
|
|
g_print ("\n");
|
|
}
|
|
else if (!has_system || !has_user)
|
|
{
|
|
g_autofree char *missing = NULL;
|
|
missing = g_strdup_printf ("\n\n '%s'\n\n", !has_system ? system_exports : user_exports);
|
|
g_print ("\n");
|
|
/* Translators: this text is automatically wrapped, don't insert line breaks */
|
|
print_wrapped (cols,
|
|
_("Note that the directory %s is not in the search path "
|
|
"set by the XDG_DATA_DIRS environment variable, so applications "
|
|
"installed by Flatpak may not appear on your desktop until the "
|
|
"session is restarted."),
|
|
missing);
|
|
g_print ("\n");
|
|
}
|
|
}
|
|
|
|
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 (__builtin_popcount (flags & (FLATPAK_BUILTIN_FLAG_NO_DIR |
|
|
FLATPAK_BUILTIN_FLAG_ONE_DIR |
|
|
FLATPAK_BUILTIN_FLAG_STANDARD_DIRS |
|
|
FLATPAK_BUILTIN_FLAG_ALL_DIRS)) != 1)
|
|
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);
|
|
|
|
/* We never want help output to interrupt completion */
|
|
if (is_in_complete)
|
|
g_option_context_set_help_enabled (context, FALSE);
|
|
|
|
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)
|
|
{
|
|
g_log_set_default_handler (no_message_handler, NULL);
|
|
}
|
|
else
|
|
{
|
|
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_verbose > 0 || opt_ostree_verbose)
|
|
flatpak_disable_fancy_output ();
|
|
}
|
|
|
|
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 nothing is set, then we put the system dir first, which can be used as the default */
|
|
if (opt_system || (!opt_user && opt_installations == NULL))
|
|
g_ptr_array_add (dirs, flatpak_dir_get_system_default ());
|
|
|
|
if (opt_user || (!opt_system && opt_installations == NULL))
|
|
g_ptr_array_add (dirs, flatpak_dir_get_user ());
|
|
|
|
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);
|
|
/* The first dir should be the default */
|
|
g_ptr_array_add (dirs, flatpak_dir_get_system_default ());
|
|
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);
|
|
const char *id = flatpak_dir_get_id (dir);
|
|
if (g_strcmp0 (id, "default") != 0)
|
|
g_ptr_array_add (dirs, g_object_ref (dir));
|
|
}
|
|
}
|
|
}
|
|
else /* FLATPAK_BUILTIN_FLAG_ONE_DIR */
|
|
{
|
|
FlatpakDir *dir;
|
|
|
|
if ((opt_system && opt_user) ||
|
|
(opt_system && opt_installations != NULL) ||
|
|
(opt_user && opt_installations != NULL) ||
|
|
(opt_installations != NULL && opt_installations[1] != NULL))
|
|
return usage_error (context, _("Multiple installations specified for a command "
|
|
"that works on one installation"), error);
|
|
|
|
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)
|
|
{
|
|
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 char *hint = NULL;
|
|
|
|
hint = g_strdup_printf (_("See '%s --help'"), g_get_prgname ());
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s\n\n%s", message, hint);
|
|
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 const char *
|
|
find_similar_command (const char *word)
|
|
{
|
|
int i, d, best;
|
|
|
|
d = G_MAXINT;
|
|
best = 0;
|
|
|
|
for (i = 0; commands[i].name; i++)
|
|
{
|
|
int d1 = flatpak_levenshtein_distance (word, commands[i].name);
|
|
if (d1 < d)
|
|
{
|
|
d = d1;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
return commands[best].name;
|
|
}
|
|
|
|
static gpointer
|
|
install_polkit_agent (void)
|
|
{
|
|
gpointer agent = NULL;
|
|
|
|
#ifdef USE_SYSTEM_HELPER
|
|
PolkitAgentListener *listener = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
/* Install a polkit agent as fallback, in case we're running on a console */
|
|
listener = flatpak_polkit_agent_text_listener_new (NULL, &local_error);
|
|
if (listener == NULL)
|
|
{
|
|
g_debug ("Failed to create polkit agent listener: %s", local_error->message);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr(AutoPolkitSubject) subject = NULL;
|
|
GVariantBuilder opt_builder;
|
|
g_autoptr(GVariant) options = NULL;
|
|
|
|
subject = polkit_unix_process_new_for_owner (getpid (), 0, getuid ());
|
|
|
|
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
|
|
if (g_strcmp0 (g_getenv ("FLATPAK_FORCE_TEXT_AUTH"), "1") != 0)
|
|
g_variant_builder_add (&opt_builder, "{sv}", "fallback", g_variant_new_boolean (TRUE));
|
|
options = g_variant_ref_sink (g_variant_builder_end (&opt_builder));
|
|
|
|
agent = polkit_agent_listener_register_with_options (listener,
|
|
POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD,
|
|
subject,
|
|
NULL,
|
|
options,
|
|
NULL,
|
|
&local_error);
|
|
if (agent == NULL)
|
|
{
|
|
g_debug ("Failed to register polkit agent listener: %s", local_error->message);
|
|
}
|
|
g_object_unref (listener);
|
|
}
|
|
#endif
|
|
|
|
return agent;
|
|
}
|
|
|
|
static void
|
|
uninstall_polkit_agent (gpointer *agent)
|
|
{
|
|
#ifdef USE_SYSTEM_HELPER
|
|
if (*agent)
|
|
polkit_agent_listener_unregister (*agent);
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
|
|
__attribute__((cleanup (uninstall_polkit_agent))) gpointer polkit_agent = NULL;
|
|
|
|
command = extract_command (&argc, argv, &command_name);
|
|
|
|
if (!command->fn)
|
|
{
|
|
GOptionContext *context;
|
|
g_autofree char *hint = NULL;
|
|
g_autofree char *msg = NULL;
|
|
|
|
context = flatpak_option_context_new_with_commands (commands);
|
|
|
|
hint = g_strdup_printf (_("See '%s --help'"), g_get_prgname ());
|
|
|
|
if (command_name != NULL)
|
|
{
|
|
const char *similar;
|
|
|
|
similar = find_similar_command (command_name);
|
|
if (similar)
|
|
msg = g_strdup_printf (_("'%s' is not a flatpak command. Did you mean '%s'?"),
|
|
command_name, similar);
|
|
else
|
|
msg = g_strdup_printf (_("'%s' is not a flatpak command"),
|
|
command_name);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
g_option_context_add_main_entries (context, empty_entries, NULL);
|
|
g_option_context_add_main_entries (context, global_entries, NULL);
|
|
if (g_option_context_parse (context, &argc, &argv, &local_error))
|
|
{
|
|
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 (opt_list_installations)
|
|
{
|
|
GPtrArray *paths;
|
|
|
|
paths = flatpak_get_system_base_dir_locations (NULL, &local_error);
|
|
if (paths)
|
|
{
|
|
guint i;
|
|
for (i = 0; i < paths->len; i++)
|
|
{
|
|
GFile *file = paths->pdata[i];
|
|
g_print ("%s\n", flatpak_file_get_path_cached (file));
|
|
}
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (local_error)
|
|
msg = g_strdup (local_error->message);
|
|
else
|
|
msg = g_strdup (_("No command specified"));
|
|
}
|
|
|
|
g_option_context_free (context);
|
|
|
|
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s\n\n%s", msg, hint);
|
|
|
|
goto out;
|
|
}
|
|
|
|
prgname = g_strdup_printf ("%s %s", g_get_prgname (), command_name);
|
|
g_set_prgname (prgname);
|
|
|
|
check_environment ();
|
|
|
|
polkit_agent = install_polkit_agent ();
|
|
|
|
if (!command->fn (argc, argv, cancellable, &error))
|
|
goto out;
|
|
|
|
success = TRUE;
|
|
out:
|
|
/* Note: We allow failures with NULL error (it means don't print anything), useful when e.g. the user aborted */
|
|
g_assert (!success || error == NULL);
|
|
|
|
if (error)
|
|
{
|
|
g_propagate_error (res_error, error);
|
|
}
|
|
|
|
return success ? 0 : 1;
|
|
}
|
|
|
|
static int
|
|
complete (int argc,
|
|
char **argv)
|
|
{
|
|
g_autoptr(FlatpakCompletion) completion = NULL;
|
|
FlatpakCommand *command;
|
|
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;
|
|
}
|
|
|
|
static void
|
|
handle_sigterm (int signum)
|
|
{
|
|
flatpak_disable_raw_mode ();
|
|
flatpak_show_cursor ();
|
|
_exit (1);
|
|
}
|
|
|
|
int
|
|
main (int argc,
|
|
char **argv)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
g_autofree const char *old_env = NULL;
|
|
int ret;
|
|
struct sigaction action;
|
|
|
|
/* The child repo shared between the client process and the
|
|
system-helper really needs to support creating files that
|
|
are readable by others, so override the umask to 022.
|
|
Ideally this should be set when needed, but umask is thread-unsafe
|
|
so there is really no local way to fix this.
|
|
*/
|
|
umask(022);
|
|
|
|
memset (&action, 0, sizeof (struct sigaction));
|
|
action.sa_handler = handle_sigterm;
|
|
sigaction (SIGTERM, &action, NULL);
|
|
sigaction (SIGHUP, &action, NULL);
|
|
sigaction (SIGINT, &action, NULL);
|
|
|
|
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 weird recursive type initialization deadlocks from libsoup */
|
|
g_type_ensure (G_TYPE_SOCKET);
|
|
|
|
/* 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);
|
|
|
|
ret = flatpak_run (argc, argv, &error);
|
|
|
|
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_dbus_error_strip_remote_error (error);
|
|
g_printerr ("%s%s %s%s\n", prefix, _("error:"), suffix, error->message);
|
|
}
|
|
|
|
return ret;
|
|
}
|