mirror of
https://github.com/flatpak/flatpak.git
synced 2026-03-27 19:33:06 -04:00
It is already the case that when we are using ALL_DIRS, we always combine it with OPTIONAL_REPO, meaning no need to populate empty installations. ALL_DIRS is used for commands that iterate through all known installations to enumerate apps/runtimes, such as `flatpak run` and `flatpak list`; for these commands, it's reasonable to say that if the installation does not have a libostree repository, then that's equivalent to it having a libostree repository with no apps and no runtimes. Make this happen automatically if forgotten. For STANDARD_DIRS, we were inconsistent about this: `flatpak remote-list` had OPTIONAL_REPO, but the other commands did not. STANDARD_DIRS is used for `flatpak create-usb`, and for all the commands that manipulate remotes. For the commands that manipulate remotes, it seems reasonable to say that if an installation has no libostree repository and we are unable to create one, then that's equivalent to an installation with a libostree repository but no remotes. Similarly, for create-usb, an installation where we are unable to create a libostree repository seems like it should be equivalent to an installation whose libostree repository does not contain any of the refs we are interested in. Resolves: https://github.com/flatpak/flatpak/issues/4111 Signed-off-by: Simon McVittie <smcv@collabora.com>
976 lines
36 KiB
C
976 lines
36 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_print_updated_env;
|
|
static gboolean opt_print_system_only;
|
|
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 },
|
|
/* Alias upgrade to update to help users of yum/dnf */
|
|
{ "upgrade", NULL, flatpak_builtin_update, flatpak_complete_update, TRUE },
|
|
{ "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 },
|
|
{ "mask", N_("Mask out updates and automatic installation"), flatpak_builtin_mask, flatpak_complete_mask },
|
|
{ "pin", N_("Pin a runtime to prevent automatic removal"), flatpak_builtin_pin, flatpak_complete_pin },
|
|
{ "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 Find 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 Manage 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-set", N_("Set permissions"), flatpak_builtin_permission_set, flatpak_complete_permission_set },
|
|
{ "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 },
|
|
{ "print-updated-env", 0, 0, G_OPTION_ARG_NONE, &opt_print_updated_env, N_("Print the updated environment needed to run flatpaks"), NULL },
|
|
{ "print-system-only", 0, 0, G_OPTION_ARG_NONE, &opt_print_system_only, N_("Only include the system installation with --print-updated-env"), 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 *f_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 (f_commands->name != NULL)
|
|
{
|
|
if (!f_commands->deprecated)
|
|
{
|
|
if (f_commands->fn != NULL)
|
|
{
|
|
g_string_append_printf (summary, "\n %s", f_commands->name);
|
|
/* Note: the 23 is there to align command descriptions with
|
|
* the option descriptions produced by GOptionContext.
|
|
*/
|
|
if (f_commands->description)
|
|
g_string_append_printf (summary, "%*s%s", (int) (23 - strlen (f_commands->name)), "", _(f_commands->description));
|
|
}
|
|
else
|
|
{
|
|
g_string_append_printf (summary, "\n%s", _(f_commands->name));
|
|
}
|
|
}
|
|
f_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;
|
|
|
|
/* Only print warnings on ttys */
|
|
if (!flatpak_fancy_output ())
|
|
return;
|
|
|
|
/* 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++)
|
|
{
|
|
/* There should never be a relative path but just in case we don't want
|
|
* g_file_new_for_path() to take the current directory into account.
|
|
*/
|
|
if (!g_str_has_prefix (dirs[i], "/"))
|
|
continue;
|
|
|
|
/* Normalize the path using GFile to e.g. replace // with / */
|
|
g_autoptr(GFile) dir_file = g_file_new_for_path (dirs[i]);
|
|
g_autofree char *dir_path = g_file_get_path (dir_file);
|
|
|
|
if (g_str_has_prefix (dir_path, system_exports))
|
|
has_system = TRUE;
|
|
if (g_str_has_prefix (dir_path, 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 ();
|
|
}
|
|
|
|
/* sudo flatpak --user ... would operate on the root user's installation,
|
|
* which is almost certainly not what the user intended so just consider it
|
|
* an error.
|
|
*/
|
|
if (opt_user && running_under_sudo ())
|
|
return flatpak_fail_error (error, FLATPAK_ERROR,
|
|
_("Refusing to operate under sudo with --user. "
|
|
"Omit sudo to operate on the user installation, "
|
|
"or use a root shell to operate on the root user's installation."));
|
|
|
|
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, SYSTEM_DIR_DEFAULT_ID) != 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 |
|
|
FLATPAK_BUILTIN_FLAG_ALL_DIRS |
|
|
FLATPAK_BUILTIN_FLAG_STANDARD_DIRS))
|
|
{
|
|
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,
|
|
gboolean *option)
|
|
{
|
|
int i, d, k;
|
|
const char *suggestion;
|
|
GOptionEntry *entries[3] = { global_entries, empty_entries, user_entries };
|
|
|
|
d = G_MAXINT;
|
|
suggestion = NULL;
|
|
|
|
for (i = 0; commands[i].name; i++)
|
|
{
|
|
if (!commands[i].fn)
|
|
continue;
|
|
|
|
int d1 = flatpak_levenshtein_distance (word, -1, commands[i].name, -1);
|
|
if (d1 < d)
|
|
{
|
|
d = d1;
|
|
suggestion = commands[i].name;
|
|
*option = FALSE;
|
|
}
|
|
}
|
|
|
|
for (k = 0; k < 3; k++)
|
|
{
|
|
for (i = 0; entries[k][i].long_name; i++)
|
|
{
|
|
int d1 = flatpak_levenshtein_distance (word, -1, entries[k][i].long_name, -1);
|
|
if (d1 < d)
|
|
{
|
|
d = d1;
|
|
suggestion = entries[k][i].long_name;
|
|
*option = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return suggestion;
|
|
}
|
|
|
|
static gpointer
|
|
install_polkit_agent (void)
|
|
{
|
|
gpointer agent = NULL;
|
|
|
|
#ifdef USE_SYSTEM_HELPER
|
|
PolkitAgentListener *listener = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
g_autoptr(GDBusConnection) bus = NULL;
|
|
|
|
bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error);
|
|
|
|
if (bus == NULL)
|
|
{
|
|
g_debug ("Unable to connect to system bus: %s", local_error->message);
|
|
return 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;
|
|
gboolean option;
|
|
|
|
similar = find_similar_command (command_name, &option);
|
|
if (similar)
|
|
msg = g_strdup_printf (_("'%s' is not a flatpak command. Did you mean '%s%s'?"),
|
|
command_name, option ? "--" : "", 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);
|
|
}
|
|
}
|
|
|
|
/* systemd environment generator for system and user installations.
|
|
* Intended to be used only from the scripts in env.d/.
|
|
* See `man systemd.environment-generator`. */
|
|
if (opt_print_updated_env)
|
|
{
|
|
g_autoptr(GPtrArray) installations = g_ptr_array_new_with_free_func (g_free); /* (element-type GFile) */
|
|
GPtrArray *system_installation_locations; /* (element-type GFile) */
|
|
const gchar * const *xdg_data_dirs = g_get_system_data_dirs ();
|
|
g_autoptr(GPtrArray) new_dirs = g_ptr_array_new_with_free_func (g_free); /* (element-type filename) */
|
|
g_autofree gchar *new_dirs_joined = NULL;
|
|
|
|
/* Work out the set of installations we want in the environment. */
|
|
if (!opt_print_system_only)
|
|
{
|
|
g_autoptr(GFile) home_installation_location = flatpak_get_user_base_dir_location ();
|
|
g_ptr_array_add (installations, g_file_get_path (home_installation_location));
|
|
}
|
|
|
|
system_installation_locations = flatpak_get_system_base_dir_locations (NULL, &local_error);
|
|
if (local_error != NULL)
|
|
{
|
|
g_printerr ("%s\n", local_error->message);
|
|
exit (1);
|
|
}
|
|
|
|
for (gsize i = 0; i < system_installation_locations->len; i++)
|
|
g_ptr_array_add (installations, g_file_get_path (system_installation_locations->pdata[i]));
|
|
|
|
/* Get the export path for each installation, and filter out
|
|
* ones which are already listed in @xdg_data_dirs. */
|
|
for (gsize i = 0; i < installations->len; i++)
|
|
{
|
|
g_autofree gchar *share_path = g_build_filename (installations->pdata[i], "exports", "share", NULL);
|
|
g_autofree gchar *share_path_with_slash = g_strconcat (share_path, "/", NULL);
|
|
|
|
if (g_strv_contains (xdg_data_dirs, share_path) || g_strv_contains (xdg_data_dirs, share_path_with_slash))
|
|
continue;
|
|
|
|
g_ptr_array_add (new_dirs, g_steal_pointer (&share_path));
|
|
}
|
|
|
|
/* Add the rest of the existing @xdg_data_dirs to the new list. */
|
|
for (gsize i = 0; xdg_data_dirs[i] != NULL; i++)
|
|
g_ptr_array_add (new_dirs, g_strdup (xdg_data_dirs[i]));
|
|
g_ptr_array_add (new_dirs, NULL);
|
|
|
|
/* Print in a format suitable for a system environment generator. */
|
|
new_dirs_joined = g_strjoinv (":", (gchar **) new_dirs->pdata);
|
|
g_print ("XDG_DATA_DIRS=%s\n", new_dirs_joined);
|
|
|
|
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);
|
|
|
|
/* Only print environment warnings in some commonly used interactive operations so we
|
|
avoid messing up output in commands where you might parse the output. */
|
|
if (g_strcmp0 (command->name, "install") == 0 ||
|
|
g_strcmp0 (command->name, "update") == 0 ||
|
|
g_strcmp0 (command->name, "remote-add") == 0 ||
|
|
g_strcmp0 (command->name, "run") == 0)
|
|
check_environment ();
|
|
|
|
/* Don't talk to dbus in enter, as it must be thread-free to setns, also
|
|
skip run/build/history for performance reasons (no need to connect to dbus). */
|
|
if (g_strcmp0 (command->name, "enter") != 0 &&
|
|
g_strcmp0 (command->name, "run") != 0 &&
|
|
g_strcmp0 (command->name, "build") != 0 &&
|
|
g_strcmp0 (command->name, "history") != 0)
|
|
polkit_agent = install_polkit_agent ();
|
|
|
|
/* g_vfs_get_default can spawn threads */
|
|
if (g_strcmp0 (command->name, "enter") != 0)
|
|
{
|
|
g_autofree const char *old_env = NULL;
|
|
|
|
/* 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 (!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 && !c->deprecated)
|
|
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;
|
|
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);
|
|
|
|
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;
|
|
}
|