app: Add a new alias command

This command lets you create aliases for running apps so that you can
pass "flatpak run" a short string you specify rather than the full app
ID, as discussed here: https://github.com/flatpak/flatpak/pull/4848
This commit is contained in:
Phaedrus Leeds 2022-07-03 21:44:08 -07:00
parent bf37034663
commit 2c704ef21d
22 changed files with 935 additions and 3 deletions

View File

@ -110,6 +110,7 @@ flatpak_SOURCES = \
app/flatpak-builtins-create-usb.c \
app/flatpak-builtins-kill.c \
app/flatpak-builtins-history.c \
app/flatpak-builtins-alias.c \
app/flatpak-complete.c \
app/flatpak-complete.h \
app/flatpak-cli-transaction.c \

View File

@ -0,0 +1,233 @@
/*
* Copyright © 2022 Matthew Leeds
*
* 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:
* Matthew Leeds <mwleeds@protonmail.com>
*/
#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <glib/gi18n.h>
#include "libglnx.h"
#include "flatpak-builtins.h"
#include "flatpak-builtins-utils.h"
#include "flatpak-cli-transaction.h"
#include "flatpak-quiet-transaction.h"
#include "flatpak-utils-private.h"
#include "flatpak-error.h"
#include "flatpak-table-printer.h"
static gboolean opt_remove;
static GOptionEntry options[] = {
{ "remove", 0, 0, G_OPTION_ARG_NONE, &opt_remove, N_("Remove the specified alias"), NULL },
{ NULL }
};
gboolean
flatpak_builtin_alias (int argc, char **argv, GCancellable *cancellable, GError **error)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GPtrArray) dirs = NULL;
int i;
context = g_option_context_new (_("ALIAS [APP] - Add an alias for running the app APP"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!flatpak_option_context_parse (context, options, &argc, &argv,
FLATPAK_BUILTIN_FLAG_ALL_DIRS | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO,
&dirs, cancellable, error))
return FALSE;
/* Move the user dir to the front so it "wins" in case an app is in more than
* one installation */
if (dirs->len > 1)
{
/* Walk through the array backwards so we can safely remove */
for (i = dirs->len; i > 0; i--)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i - 1);
if (flatpak_dir_is_user (dir))
{
g_ptr_array_insert (dirs, 0, g_object_ref (dir));
g_ptr_array_remove_index (dirs, i);
break;
}
}
}
if (argc == 1)
{
g_autoptr(FlatpakTablePrinter) printer = NULL;
printer = flatpak_table_printer_new ();
flatpak_table_printer_set_column_title (printer, 0, _("Alias"));
flatpak_table_printer_set_column_title (printer, 1, _("App"));
flatpak_table_printer_set_column_title (printer, 2, _("Installation"));
for (i = 0; i < dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i);
g_autoptr(GHashTable) aliases = NULL; /* alias → app-id */
aliases = flatpak_dir_get_aliases (dir);
GLNX_HASH_TABLE_FOREACH_KV (aliases, const char *, alias, const char *, app_id)
{
flatpak_table_printer_add_column (printer, alias);
flatpak_table_printer_add_column (printer, app_id);
flatpak_table_printer_add_column (printer, flatpak_dir_get_name_cached (dir));
flatpak_table_printer_finish_row (printer);
}
}
if (flatpak_table_printer_get_current_row (printer) > 0)
flatpak_table_printer_print (printer);
else if (flatpak_fancy_output ())
g_print (_("No aliases\n"));
}
else if (opt_remove && argc == 2)
{
const char *alias = argv[1];
g_autoptr(GError) saved_error = NULL;
for (i = 0; i < dirs->len; i++)
{
g_autoptr(GError) local_error = NULL;
FlatpakDir *dir = g_ptr_array_index (dirs, i);
if (flatpak_dir_remove_alias (dir, alias, &local_error))
return TRUE;
else
{
if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALIAS_NOT_FOUND))
{
if (saved_error == NULL)
g_propagate_error (&saved_error, g_steal_pointer (&local_error));
continue;
}
else
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
}
}
g_propagate_error (error, g_steal_pointer (&saved_error));
return FALSE;
}
else if (!opt_remove && argc == 3)
{
const char *alias = argv[1];
const char *app = argv[2];
FlatpakDir *dir_to_use = NULL;
g_autoptr(FlatpakDecomposed) current = NULL;
/* Check if the named app is deployed */
for (i = 0; i < dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i);
g_autoptr(GFile) deploy = NULL;
g_clear_pointer (&current, flatpak_decomposed_unref);
current = flatpak_dir_current_ref (dir, app, cancellable);
if (current)
deploy = flatpak_dir_get_if_deployed (dir, current, NULL, cancellable);
if (deploy != NULL)
{
dir_to_use = dir;
break;
}
}
if (dir_to_use == NULL)
return flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED,
_("App %s not installed"), app);
if (!flatpak_dir_make_alias (dir_to_use, current, alias, error))
return FALSE;
}
else
return usage_error (context, _("Wrong number of arguments"), error);
return TRUE;
}
gboolean
flatpak_complete_alias (FlatpakCompletion *completion)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GPtrArray) dirs = NULL;
g_autoptr(GError) error = NULL;
int i;
context = g_option_context_new ("");
if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv,
FLATPAK_BUILTIN_FLAG_ALL_DIRS | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO,
&dirs, NULL, NULL))
return FALSE;
switch (completion->argc)
{
case 0:
case 1: /* ALIAS */
flatpak_complete_options (completion, global_entries);
flatpak_complete_options (completion, options);
flatpak_complete_options (completion, user_entries);
if (opt_remove)
{
for (i = 0; i < dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i);
g_autoptr(GHashTable) aliases = NULL; /* alias → app-id */
aliases = flatpak_dir_get_aliases (dir);
GLNX_HASH_TABLE_FOREACH (aliases, const char *, alias)
flatpak_complete_word (completion, "%s", alias);
}
}
break;
case 2: /* APP */
if (!opt_remove)
{
for (i = 0; i < dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i);
g_autoptr(GPtrArray) refs = NULL;
refs = flatpak_dir_find_installed_refs (dir, NULL, NULL, NULL,
FLATPAK_KINDS_APP,
FIND_MATCHING_REFS_FLAGS_NONE,
&error);
if (refs == NULL)
flatpak_completion_debug ("find installed refs error: %s", error->message);
flatpak_complete_ref_id (completion, refs);
}
}
break;
}
return TRUE;
}

View File

@ -124,6 +124,7 @@ BUILTINPROTO (repair)
BUILTINPROTO (create_usb)
BUILTINPROTO (kill)
BUILTINPROTO (history)
BUILTINPROTO (alias)
#undef BUILTINPROTO

View File

@ -89,6 +89,7 @@ static FlatpakCommand commands[] = {
{ "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 },
{ "alias", N_("Manage short aliases for running applications"), flatpak_builtin_alias, flatpak_complete_alias },
/* translators: please keep the leading newline and space */
{ N_("\n Find applications and runtimes") },

View File

@ -257,6 +257,15 @@ typedef enum {
#define FLATPAK_HELPER_CONFIGURE_FLAGS_ALL (FLATPAK_HELPER_CONFIGURE_FLAGS_UNSET | \
FLATPAK_HELPER_CONFIGURE_FLAGS_NO_INTERACTION)
typedef enum {
FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_NONE = 0,
FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_NO_INTERACTION = 1 << 0,
FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_REMOVE = 1 << 0,
} FlatpakHelperConfigureAliasesFlags;
#define FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_ALL (FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_NO_INTERACTION | \
FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_REMOVE)
typedef enum {
FLATPAK_HELPER_UPDATE_REMOTE_FLAGS_NONE = 0,
FLATPAK_HELPER_UPDATE_REMOTE_FLAGS_NO_INTERACTION = 1 << 0,
@ -495,6 +504,7 @@ GFile * flatpak_dir_get_exports_dir (Fla
GFile * flatpak_dir_get_removed_dir (FlatpakDir *self);
GFile * flatpak_dir_get_sideload_repos_dir (FlatpakDir *self);
GFile * flatpak_dir_get_runtime_sideload_repos_dir (FlatpakDir *self);
GFile * flatpak_dir_get_aliases_dir (FlatpakDir *self);
GFile * flatpak_dir_get_if_deployed (FlatpakDir *self,
FlatpakDecomposed *ref,
const char *checksum,
@ -596,6 +606,14 @@ gboolean flatpak_dir_config_remove_pattern (Fla
const char *key,
const char *pattern,
GError **error);
GHashTable * flatpak_dir_get_aliases (FlatpakDir *self);
gboolean flatpak_dir_make_alias (FlatpakDir *self,
FlatpakDecomposed *current_ref,
const char *alias,
GError **error);
gboolean flatpak_dir_remove_alias (FlatpakDir *self,
const char *alias,
GError **error);
gboolean flatpak_dir_mark_changed (FlatpakDir *self,
GError **error);
gboolean flatpak_dir_remove_appstream (FlatpakDir *self,

View File

@ -79,6 +79,7 @@
#define SYSCONF_REMOTES_FILE_EXT ".flatpakrepo"
#define SIDELOAD_REPOS_DIR_NAME "sideload-repos"
#define ALIASES_DIR_NAME "aliases"
#ifdef USE_SYSTEM_HELPER
/* This uses a weird Auto prefix to avoid conflicts with later added polkit types.
@ -2356,6 +2357,30 @@ flatpak_dir_system_helper_call_configure (FlatpakDir *self,
return ret != NULL;
}
static gboolean
flatpak_dir_system_helper_call_configure_aliases (FlatpakDir *self,
guint arg_flags,
const gchar *arg_ref,
const gchar *arg_alias,
const gchar *arg_installation,
GCancellable *cancellable,
GError **error)
{
if (flatpak_dir_get_no_interaction (self))
arg_flags |= FLATPAK_HELPER_CONFIGURE_FLAGS_NO_INTERACTION;
g_autoptr(GVariant) ret =
flatpak_dir_system_helper_call (self, "ConfigureAliases",
g_variant_new ("(usss)",
arg_flags,
arg_ref,
arg_alias,
arg_installation),
G_VARIANT_TYPE ("()"), NULL,
cancellable, error);
return ret != NULL;
}
static gboolean
flatpak_dir_system_helper_call_update_remote (FlatpakDir *self,
guint arg_flags,
@ -3086,6 +3111,12 @@ flatpak_dir_get_runtime_sideload_repos_dir (FlatpakDir *self)
return g_file_get_child (base, SIDELOAD_REPOS_DIR_NAME);
}
GFile *
flatpak_dir_get_aliases_dir (FlatpakDir *self)
{
return g_file_get_child (self->basedir, ALIASES_DIR_NAME);
}
OstreeRepo *
flatpak_dir_get_repo (FlatpakDir *self)
{
@ -4445,6 +4476,194 @@ flatpak_dir_config_remove_pattern (FlatpakDir *self,
return flatpak_dir_set_config (self, key, merged_patterns, error);
}
GHashTable *
flatpak_dir_get_aliases (FlatpakDir *self)
{
g_autoptr(GHashTable) aliases = NULL; /* alias → app-id */
g_autoptr(GFileEnumerator) dir_enum = NULL;
g_autoptr(GFile) parent = NULL;
aliases = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
if (!flatpak_dir_maybe_ensure_repo (self, NULL, NULL))
return g_steal_pointer (&aliases);
parent = flatpak_dir_get_aliases_dir (self);
dir_enum = g_file_enumerate_children (parent,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL, NULL);
if (dir_enum == NULL)
return g_steal_pointer (&aliases);
while (TRUE)
{
GFileInfo *info;
GFile *path;
g_autofree char *path_basename = NULL;
const char *target;
g_autoptr(GFile) target_file = NULL;
g_autofree char *target_basename = NULL;
if (!g_file_enumerator_iterate (dir_enum, &info, &path, NULL, NULL) ||
info == NULL)
break;
/* We expect symlinks to files in $FLATPAK_DIR/exports/bin/ */
if (g_file_info_get_file_type (info) != G_FILE_TYPE_SYMBOLIC_LINK)
continue;
path_basename = g_file_get_basename (path);
if (path_basename == NULL)
continue;
target = g_file_info_get_symlink_target (info);
if (target == NULL)
continue;
target_file = g_file_new_for_path (target);
target_basename = g_file_get_basename (target_file);
if (target_basename == NULL || !flatpak_is_valid_name (target_basename, -1, NULL))
continue;
g_hash_table_insert (aliases,
g_steal_pointer (&path_basename),
g_steal_pointer (&target_basename));
}
return g_steal_pointer (&aliases);
}
gboolean
flatpak_dir_remove_alias (FlatpakDir *self,
const char *alias,
GError **error)
{
g_autoptr(GFile) exports = NULL;
g_autoptr(GFile) bindir = NULL;
g_autoptr(GFile) runner = NULL;
g_autoptr(GFile) aliases = NULL;
g_autoptr(GFile) alias_file = NULL;
g_autofree char *runner_relpath = NULL;
g_autofree char *symlink_target = NULL;
g_autofree char *app_id = NULL;
g_autoptr(GError) local_error = NULL;
if (!flatpak_is_valid_alias (alias, error))
return FALSE;
if (flatpak_dir_use_system_helper (self, NULL))
{
FlatpakHelperConfigureAliasesFlags flags = FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_REMOVE;
const char *installation = flatpak_dir_get_id (self);
if (!flatpak_dir_system_helper_call_configure_aliases (self,
flags,
"", alias,
installation ? installation : "",
NULL, error))
return FALSE;
return TRUE;
}
aliases = flatpak_dir_get_aliases_dir (self);
alias_file = g_file_get_child (aliases, alias);
/* The caller is responsible for handling FLATPAK_ERROR_ALIAS_NOT_FOUND as needed */
if (!g_file_delete (alias_file, NULL, &local_error))
{
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
flatpak_fail_error (error, FLATPAK_ERROR_ALIAS_NOT_FOUND,
_("Error removing alias '%s': it does not exist"),
alias);
else
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
return TRUE;
}
gboolean
flatpak_dir_make_alias (FlatpakDir *self,
FlatpakDecomposed *current_ref,
const char *alias,
GError **error)
{
g_autoptr(GFile) exports = NULL;
g_autoptr(GFile) bindir = NULL;
g_autoptr(GFile) runner = NULL;
g_autoptr(GFile) aliases = NULL;
g_autoptr(GFile) alias_file = NULL;
g_autofree char *runner_relpath = NULL;
g_autofree char *symlink_target = NULL;
g_autofree char *app_id = NULL;
g_autoptr(GError) local_error = NULL;
/* If the alias is valid and the app is installed, we create a symlink to the
* wrapper script created by flatpak_dir_deploy(). This will be used by the
* flatpak run command.
*/
if (!flatpak_is_valid_alias (alias, error))
return FALSE;
if (flatpak_dir_use_system_helper (self, NULL))
{
FlatpakHelperConfigureAliasesFlags flags = 0;
const char *installation = flatpak_dir_get_id (self);
const char *current_ref_str;
current_ref_str = flatpak_decomposed_get_ref (current_ref);
if (!flatpak_dir_system_helper_call_configure_aliases (self,
flags,
current_ref_str, alias,
installation ? installation : "",
NULL, error))
return FALSE;
return TRUE;
}
app_id = flatpak_decomposed_dup_id (current_ref);
exports = flatpak_dir_get_exports_dir (self);
bindir = g_file_get_child (exports, "bin");
runner = g_file_get_child (bindir, app_id);
if (!g_file_query_exists (runner, NULL))
return flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED,
_("No wrapper script found for %s"), app_id);
aliases = flatpak_dir_get_aliases_dir (self);
alias_file = g_file_get_child (aliases, alias);
if (g_mkdir_with_parents (flatpak_file_get_path_cached (aliases), 0755) != 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
runner_relpath = g_file_get_relative_path (self->basedir, runner);
symlink_target = g_build_filename ("..", runner_relpath, NULL);
if (!g_file_make_symbolic_link (alias_file, symlink_target, NULL, &local_error))
{
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
flatpak_fail_error (error, FLATPAK_ERROR_ALIAS_ALREADY_EXISTS,
_("Error making alias '%s': %s"),
alias, local_error->message);
else
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
return TRUE;
}
gboolean
flatpak_dir_mark_changed (FlatpakDir *self,
GError **error)

View File

@ -52,7 +52,7 @@ G_BEGIN_DECLS
* @FLATPAK_ERROR_EXPORT_FAILED: Exporting data failed. (Since: 1.0.3)
* @FLATPAK_ERROR_REMOTE_USED: Remote can't be uninstalled. (Since: 1.0.3)
* @FLATPAK_ERROR_RUNTIME_USED: Runtime can't be uninstalled. (Since: 1.0.3)
* @FLATPAK_ERROR_INVALID_NAME: Application, runtime or remote name is invalid. (Since: 1.0.3)
* @FLATPAK_ERROR_INVALID_NAME: Application, runtime, remote, or alias name is invalid. (Since: 1.0.3)
* @FLATPAK_ERROR_OUT_OF_SPACE: More disk space needed. (Since: 1.2.0)
* @FLATPAK_ERROR_WRONG_USER: An operation is being attempted by the wrong user (such as
* root operating on a user installation). (Since: 1.2.0)
@ -66,6 +66,8 @@ G_BEGIN_DECLS
* @FLATPAK_ERROR_NOT_AUTHORIZED: An operation tried to access a ref, or information about it that it
* was not authorized. For example, when succesfully authenticating with a
* server but the user doesn't have permissions for a private ref. (Since: 1.7.3)
* @FLATPAK_ERROR_ALIAS_NOT_FOUND: The specified alias was not found. (Since: 1.13.4)
* @FLATPAK_ERROR_ALIAS_ALREADY_EXISTS: The specified alias was already exists. (Since: 1.13.4)
*
* Error codes for library functions.
*/
@ -95,6 +97,8 @@ typedef enum {
FLATPAK_ERROR_PERMISSION_DENIED,
FLATPAK_ERROR_AUTHENTICATION_FAILED,
FLATPAK_ERROR_NOT_AUTHORIZED,
FLATPAK_ERROR_ALIAS_NOT_FOUND,
FLATPAK_ERROR_ALIAS_ALREADY_EXISTS,
} FlatpakError;
/**

View File

@ -169,5 +169,7 @@ char * flatpak_build_app_ref (const char *app,
const char *branch,
const char *arch);
gboolean flatpak_is_valid_alias (const char *string,
GError **error);
#endif /* __FLATPAK_REF_UTILS_H__ */

View File

@ -1687,3 +1687,66 @@ flatpak_build_app_ref (const char *app,
return g_build_filename ("app", app, arch, branch, NULL);
}
/**
* flatpak_is_valid_alias:
* @string: The string to check
* @error: Return location for an error
*
* Checks if @string is a valid alias for use with the "flatpak alias" and
* "flatpak run" commands.
*
* Per flatpak-alias(1), valid characters are "[A-Z][a-z][0-9]_-" and "-" is
* not valid as the first character (to avoid confusion with CLI options).
*
* Aliases must not exceed 255 characters in length.
*
* Returns: %TRUE if valid, %FALSE otherwise.
*/
gboolean
flatpak_is_valid_alias (const char *string,
GError **error)
{
const gchar *s;
gssize len;
g_return_val_if_fail (string != NULL, FALSE);
len = strlen (string);
if (G_UNLIKELY (len == 0))
{
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
_("Alias can't be empty"));
return FALSE;
}
if (G_UNLIKELY (len > 255))
{
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
_("Alias can't be longer than 255 characters"));
return FALSE;
}
s = string;
if (G_UNLIKELY (!is_valid_name_character (*s, FALSE)))
{
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
_("Alias can't start with %c"), *s);
return FALSE;
}
s += 1;
while (*s != '\0')
{
/* Disallowing '.' means that aliases are never valid app IDs which seems
* desirable
*/
if (G_UNLIKELY (!is_valid_name_character (*s, TRUE)))
{
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
_("Alias can't contain %c"), *s);
return FALSE;
}
s += 1;
}
return TRUE;
}

View File

@ -81,6 +81,8 @@ static const GDBusErrorEntry flatpak_error_entries[] = {
{FLATPAK_ERROR_PERMISSION_DENIED, "org.freedesktop.Flatpak.Error.PermissionDenied"}, /* Since: 1.5.1 */
{FLATPAK_ERROR_AUTHENTICATION_FAILED, "org.freedesktop.Flatpak.Error.AuthenticationFailed"}, /* Since: 1.7.3 */
{FLATPAK_ERROR_NOT_AUTHORIZED, "org.freedesktop.Flatpak.Error.NotAuthorized"}, /* Since: 1.7.3 */
{FLATPAK_ERROR_ALIAS_NOT_FOUND, "org.freedesktop.Flatpak.Error.AliasNotFound"}, /* Since: 1.13.4 */
{FLATPAK_ERROR_ALIAS_ALREADY_EXISTS, "org.freedesktop.Flatpak.Error.AliasAlreadyExists"}, /* Since: 1.13.4 */
};
typedef struct archive FlatpakAutoArchiveRead;

View File

@ -269,6 +269,13 @@
<arg type='s' name='installation' direction='in'/>
</method>
<method name="ConfigureAliases">
<arg type='u' name='flags' direction='in'/>
<arg type='s' name='ref' direction='in'/>
<arg type='s' name='alias' direction='in'/>
<arg type='s' name='installation' direction='in'/>
</method>
<method name="UpdateRemote">
<arg type='u' name='flags' direction='in'/>
<arg type='s' name='remote' direction='in'/>

View File

@ -63,6 +63,7 @@ man1 = \
flatpak-kill.1 \
flatpak-history.1 \
flatpak-spawn.1 \
flatpak-alias.1 \
$(NULL)
man5 = \

176
doc/flatpak-alias.xml Normal file
View File

@ -0,0 +1,176 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<refentry id="flatpak-alias">
<refentryinfo>
<title>flatpak alias</title>
<productname>flatpak</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Matthew</firstname>
<surname>Leeds</surname>
<email>mwleeds@protonmail.com</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>flatpak alias</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>flatpak-alias</refname>
<refpurpose>Manage short aliases for running applications</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>flatpak alias</command>
<arg choice="opt" rep="repeat">OPTION</arg>
<arg choice="plain">ALIAS</arg>
<arg choice="plain">APP</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>flatpak alias</command>
<arg choice="opt" rep="repeat">OPTION</arg>
<arg choice="opt">--remove</arg>
<arg choice="plain">ALIAS</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
Normally to run a Flatpak app from the command line you specify the full
App ID (and optionally the arch and branch) to the
<command>flatpak run</command> command. This can be tedious, so the
<command>alias</command> command allows you to set up aliases that can
be passed to <command>flatpak run</command> instead. Once set up, no
additional confirmation prompts are needed each time the alias is used.
</para>
<para>
The <arg choice="plain">APP</arg> argument is the application ID of an
installed application. Only the current branch will be used; see
<citerefentry><refentrytitle>flatpak-make-current</refentrytitle><manvolnum>1</manvolnum></citerefentry>
As with the <command>flatpak run</command> command, if an app is installed
both system-wide and per-user, the per-user one will be used unless
otherwise specified with <option>--system</option> or <option>--installation</option>.
</para>
<para>
The <arg choice="plain">ALIAS</arg> argument is a string which can only
contain the ASCII characters "[A-Z][a-z][0-9]_-" and cannot start with a "-".
</para>
<para>
To list the current set of aliases, run this command without any arguments.
</para>
<para>
Aliases are created as files in an <filename>aliases</filename>
subdirectory of the relevant installation directory. However, it is not
recommended to add this directory to your <envar>PATH</envar> since
Flatpaks cannot handle file arguments like OS packages. For example, a
command such as <command>media-player ~/some-file.ogg</command> would not
work if the relevant Flatpak doesn't have home directory access. To
provide access to particular files specified on the command line, use the
<option>--file-forwarding</option> option documented in
<citerefentry><refentrytitle>flatpak-run</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>-h</option></term>
<term><option>--help</option></term>
<listitem><para>
Show help options and exit.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--remove</option></term>
<listitem><para>
Instead of adding the specified alias, remove it. In this case
there's no need to specify the <arg choice="plain">APP</arg>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--user</option></term>
<listitem><para>
Operate on the per-user installation.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--system</option></term>
<listitem><para>
Operate on the default system-wide installation.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--installation=NAME</option></term>
<listitem><para>
Operate on the system-wide installation
specified by <arg choice="plain">NAME</arg> among those defined in
<filename>/etc/flatpak/installations.d/</filename>. Using
<option>--installation=default</option> is equivalent to using
<option>--system</option>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-v</option></term>
<term><option>--verbose</option></term>
<listitem><para>
Print debug information during command processing.
</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
<command>$ flatpak alias</command>
</para>
<para>
<command>$ flatpak alias app-bin org.example.App</command>
</para>
<para>
<command>$ flatpak alias --system org.example.App app-bin</command>
</para>
<para>
<command>$ flatpak alias --remove app-bin</command>
</para>
</refsect1>
<refsect1>
<title>See also</title>
<para>
<citerefentry><refentrytitle>flatpak</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>flatpak-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
</para>
</refsect1>
</refentry>

View File

@ -287,6 +287,13 @@
Copy apps and/or runtimes onto removable media.
</para></listitem>
</varlistentry>
<varlistentry>
<term><citerefentry><refentrytitle>flatpak-alias</refentrytitle><manvolnum>1</manvolnum></citerefentry></term>
<listitem><para>
Manage short aliases for running applications.
</para></listitem>
</varlistentry>
</variablelist>

View File

@ -1,5 +1,6 @@
# List of source files containing translatable strings.
# Please keep this file sorted alphabetically.
app/flatpak-builtins-alias.c
app/flatpak-builtins-build-bundle.c
app/flatpak-builtins-build.c
app/flatpak-builtins-build-commit-from.c

View File

@ -1177,6 +1177,95 @@ handle_configure (FlatpakSystemHelper *object,
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
/* Note: While this currently only supports creating aliases for apps, the
* argument being a full ref rather than an app ID means we can change it to
* support runtimes in the future if needed.
*/
static gboolean
handle_configure_aliases (FlatpakSystemHelper *object,
GDBusMethodInvocation *invocation,
guint arg_flags,
const gchar *arg_ref,
const gchar *arg_alias,
const gchar *arg_installation)
{
g_autoptr(FlatpakDir) system = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(FlatpakDecomposed) ref = NULL;
g_autoptr(GFile) deploy = NULL;
gboolean remove_alias = (arg_flags & FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_REMOVE) != 0;
g_debug ("ConfigureAliases %u %s %s %s", arg_flags, arg_ref, arg_alias, arg_installation);
system = dir_get_system (arg_installation, get_sender_pid (invocation), (arg_flags & FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_NO_INTERACTION) != 0, &error);
if (system == NULL)
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if ((arg_flags & ~FLATPAK_HELPER_CONFIGURE_ALIASES_FLAGS_ALL) != 0)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Unsupported flags enabled: 0x%x", (arg_flags & ~FLATPAK_HELPER_CONFIGURE_FLAGS_ALL));
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (!remove_alias)
{
ref = flatpak_decomposed_new_from_ref (arg_ref, &error);
if (ref == NULL)
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (!flatpak_decomposed_is_app (ref))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Invalid ref %s: must be an app", arg_ref);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
deploy = flatpak_dir_get_if_deployed (system, ref, NULL, NULL);
if (deploy == NULL)
{
flatpak_invocation_return_error (invocation, error, "App %s not installed", arg_ref);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
}
if (!flatpak_dir_ensure_repo (system, NULL, &error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
if (!remove_alias)
{
if (!flatpak_dir_make_alias (system, ref, arg_alias, &error))
{
flatpak_invocation_return_error (invocation, error, "Error making alias");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
}
else
{
/* Here we consider it an error if the alias does not exist */
if (!flatpak_dir_remove_alias (system, arg_alias, &error))
{
flatpak_invocation_return_error (invocation, error, "Error removing alias");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
g_clear_error (&error);
}
flatpak_system_helper_complete_configure_aliases (object, invocation);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
handle_update_remote (FlatpakSystemHelper *object,
GDBusMethodInvocation *invocation,
@ -2121,6 +2210,15 @@ flatpak_authorize_method_handler (GDBusInterfaceSkeleton *interface,
polkit_details_insert (details, "key", key);
}
else if (g_strcmp0 (method_name, "ConfigureAliases") == 0)
{
guint32 flags;
g_variant_get_child (parameters, 0, "u", &flags);
action = "org.freedesktop.Flatpak.configure-aliases";
no_interaction = (flags & FLATPAK_HELPER_CONFIGURE_FLAGS_NO_INTERACTION) != 0;
}
else if (g_strcmp0 (method_name, "UpdateRemote") == 0)
{
const char *remote;
@ -2223,6 +2321,7 @@ on_bus_acquired (GDBusConnection *connection,
g_signal_connect (helper, "handle-install-bundle", G_CALLBACK (handle_install_bundle), NULL);
g_signal_connect (helper, "handle-configure-remote", G_CALLBACK (handle_configure_remote), NULL);
g_signal_connect (helper, "handle-configure", G_CALLBACK (handle_configure), NULL);
g_signal_connect (helper, "handle-configure-aliases", G_CALLBACK (handle_configure_aliases), NULL);
g_signal_connect (helper, "handle-update-remote", G_CALLBACK (handle_update_remote), NULL);
g_signal_connect (helper, "handle-remove-local-ref", G_CALLBACK (handle_remove_local_ref), NULL);
g_signal_connect (helper, "handle-prune-local-repo", G_CALLBACK (handle_prune_local_repo), NULL);

View File

@ -199,6 +199,21 @@
</defaults>
</action>
<action id="org.freedesktop.Flatpak.configure-aliases">
<!-- SECURITY:
- Normal users need admin authentication to configure aliases in the
system-wide Flatpak installation.
-->
<description>Configure</description>
<message>Authentication is required to configure software installation</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.freedesktop.Flatpak.appstream-update">
<!-- SECURITY:
- Normal users do not require admin authentication to update

View File

@ -28,6 +28,8 @@ TEST_MATRIX= \
tests/test-summaries@system.wrap \
tests/test-subset@user.wrap \
tests/test-subset@system.wrap \
tests/test-alias@user.wrap \
tests/test-alias@system.wrap \
$(NULL)
TEST_MATRIX_DIST= \
tests/test-basic.sh \
@ -58,4 +60,5 @@ TEST_MATRIX_EXTRA_DIST= \
tests/test-update-portal.sh \
tests/test-summaries.sh \
tests/test-subset.sh \
tests/test-alias.sh \
$(NULL)

77
tests/test-alias.sh Executable file
View File

@ -0,0 +1,77 @@
#!/bin/bash
#
# Copyright (C) 2022 Matthew Leeds <mwleeds@protonmail.com>
#
# SPDX-License-Identifier: LGPL-2.0-or-later
set -euo pipefail
. $(dirname $0)/libtest.sh
echo "1..3"
setup_repo
${FLATPAK} ${U} install -y test-repo org.test.Hello >&2
# List aliases (none)
${FLATPAK} ${U} alias > aliases
assert_file_empty aliases
# Make an alias
${FLATPAK} ${U} alias hello org.test.Hello
${FLATPAK} ${U} alias > aliases
if [ x${USE_SYSTEMDIR-} == xyes ]; then
assert_file_has_content aliases "hello org\.test\.Hello system"
else
assert_file_has_content aliases "hello org\.test\.Hello user"
fi
# Shouldn't be able to make an alias that already exists
if ${FLATPAK} ${U} alias hello org.test.Hello &> alias-error-log; then
assert_not_reached "Should not be able to create an alias that already exists"
fi
assert_file_has_content alias-error-log "error: Error making alias 'hello': Error making symbolic link .*: File exists"
# Shouldn't be able to make an alias that for an app that's not installed
if ${FLATPAK} ${U} alias hello org.test.Bonjour &> alias-error-log; then
assert_not_reached "Should not be able to create an alias for an uninstalled app"
fi
assert_file_has_content alias-error-log "error: App org.test.Bonjour not installed"
# Remove an alias
${FLATPAK} ${U} alias --remove hello
${FLATPAK} ${U} alias > aliases
assert_file_empty aliases
ok "alias command works"
if ${FLATPAK} ${U} alias .hello org.test.Hello &> alias-error-log; then
assert_not_reached "Should not be able to create an alias starting with a period"
fi
assert_file_has_content alias-error-log "error: Alias can't start with \."
if ${FLATPAK} ${U} alias org.test.Goodbye org.test.Hello &> alias-error-log; then
assert_not_reached "Should not be able to create an alias which contains a period"
fi
assert_file_has_content alias-error-log "error: Alias can't contain \."
if ${FLATPAK} ${U} alias 🦋 org.test.Hello &> alias-error-log; then
assert_not_reached "Should not be able to create an alias which is a butterfly"
fi
# In flatpak_is_valid_alias() we look at the string byte by byte so multi-byte
# characters don't get printed properly in the error message.
assert_file_has_content alias-error-log "error: Alias can't start with "
ok "alias command rejects invalid alias characters"
if ${FLATPAK} ${U} alias --remove notarealalias &> alias-error-log; then
assert_not_reached "Should not be able to remove an alias that doesn't exist"
fi
assert_file_has_content alias-error-log "error: Error removing alias 'notarealalias': it does not exist"
# clean up
rm aliases alias-error-log
${FLATPAK} ${U} uninstall -y org.test.Platform org.test.Hello >&2
${FLATPAK} ${U} remote-delete test-repo >&2
ok "alias --remove command rejects non-existent alias"

View File

@ -71,7 +71,7 @@ for cmd in install update uninstall list info config repair create-usb \
remote-modify remote-delete remote-ls remote-info build-init \
build build-finish build-export build-bundle build-import-bundle \
build-sign build-update-repo build-commit-from repo kill history \
mask;
mask alias;
do
${FLATPAK} $cmd --help > help_out
head -2 help_out > help_out2

View File

@ -34,6 +34,7 @@ install_repo
${FLATPAK} complete "flatpak a" 9 "a" | sort > complete_out
(diff -u complete_out - || exit 1) <<EOF
alias
EOF
ok "complete a commands"
@ -167,7 +168,7 @@ ok "complete NO_DIR commands"
for cmd in history info list run update mask \
config install make-current override remote-add repair \
create-usb remote-delete remote-info remote-list remote-ls \
remote-modify search uninstall update; do
remote-modify search uninstall update alias; do
len=$(awk '{ print length($0) }' <<< "flatpak $cmd --")
${FLATPAK} complete "flatpak $cmd --" $len "--" > complete_out
assert_file_has_content complete_out "^--system "

View File

@ -34,6 +34,7 @@ TEST_MATRIX_SOURCE=(
'tests/test-prune.sh' \
'tests/test-seccomp.sh' \
'tests/test-repair.sh' \
'tests/test-alias.sh{user+system}' \
)
"${tests_srcdir}/expand-test-matrix.sh" --automake "${TEST_MATRIX_SOURCE[*]}" > "${tests_srcdir}/Makefile-test-matrix.am.inc"