summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Leeds <matthew.leeds@endlessm.com>2020-07-30 17:05:43 -0700
committerAlexander Larsson <alexander.larsson@gmail.com>2020-08-31 16:29:03 +0200
commitd2d5397cc15fb60faf515e444d9cc34b9e5af16d (patch)
treedb169fdb7a5da5c35d92977e13ea8e58200c626a
parentf24b1fdc1aff9787d219a87a4ca15f0eed4d6420 (diff)
downloadflatpak-d2d5397cc15fb60faf515e444d9cc34b9e5af16d.tar.gz
Add pin command to keep unused runtimes
As discussed here [1], we want a way to mark runtimes to be kept even when they are unused by any apps and we are removing such runtimes. Currently this is a command that can be run manually; a subsequent commit will pin runtimes automatically if they are installed independently of any app. A unit test is included. [1] https://github.com/flatpak/flatpak/issues/2639#issuecomment-662311756
-rw-r--r--app/Makefile.am.inc1
-rw-r--r--app/flatpak-builtins-mask.c4
-rw-r--r--app/flatpak-builtins-pin.c185
-rw-r--r--app/flatpak-builtins.h1
-rw-r--r--app/flatpak-main.c1
-rw-r--r--common/flatpak-dir-private.h2
-rw-r--r--common/flatpak-dir.c66
-rw-r--r--common/flatpak-installation.c7
-rw-r--r--common/flatpak-utils-private.h2
-rw-r--r--common/flatpak-utils.c25
-rw-r--r--doc/Makefile.am1
-rw-r--r--doc/flatpak-pin.xml151
-rw-r--r--doc/flatpak.xml7
-rw-r--r--system-helper/flatpak-system-helper.c3
-rw-r--r--tests/test-repo.sh26
15 files changed, 472 insertions, 10 deletions
diff --git a/app/Makefile.am.inc b/app/Makefile.am.inc
index 0f5868d4..0175b1c6 100644
--- a/app/Makefile.am.inc
+++ b/app/Makefile.am.inc
@@ -81,6 +81,7 @@ flatpak_SOURCES = \
app/flatpak-builtins-update.c \
app/flatpak-builtins-uninstall.c \
app/flatpak-builtins-mask.c \
+ app/flatpak-builtins-pin.c \
app/flatpak-builtins-list.c \
app/flatpak-builtins-info.c \
app/flatpak-builtins-config.c \
diff --git a/app/flatpak-builtins-mask.c b/app/flatpak-builtins-mask.c
index 68b0b0db..401bb1c0 100644
--- a/app/flatpak-builtins-mask.c
+++ b/app/flatpak-builtins-mask.c
@@ -138,7 +138,9 @@ flatpak_builtin_mask (int argc, char **argv, GCancellable *cancellable, GError *
{
g_autofree char *regexp;
- regexp = flatpak_filter_glob_to_regexp (pattern, error);
+ regexp = flatpak_filter_glob_to_regexp (pattern,
+ FALSE, /* match apps or runtimes */
+ error);
if (regexp == NULL)
return FALSE;
diff --git a/app/flatpak-builtins-pin.c b/app/flatpak-builtins-pin.c
new file mode 100644
index 00000000..64da2c18
--- /dev/null
+++ b/app/flatpak-builtins-pin.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright © 2020 Endless OS Foundation LLC
+ *
+ * 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 <matthew.leeds@endlessm.com>
+ */
+
+#include "config.h"
+
+#include <locale.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "libglnx/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"
+
+/* Note: the code here is copied from flatpak-builtins-mask.c */
+
+static gboolean opt_remove;
+
+static GOptionEntry options[] = {
+ { "remove", 0, 0, G_OPTION_ARG_NONE, &opt_remove, N_("Remove matching pins"), NULL },
+ { NULL }
+};
+
+static GPtrArray *
+get_old_patterns (FlatpakDir *dir)
+{
+ g_autoptr(GPtrArray) patterns = NULL;
+ g_autofree char *pinned = NULL;
+ int i;
+
+ patterns = g_ptr_array_new_with_free_func (g_free);
+
+ pinned = flatpak_dir_get_config (dir, "pinned", NULL);
+ if (pinned)
+ {
+ g_auto(GStrv) oldv = g_strsplit (pinned, ";", -1);
+
+ for (i = 0; oldv[i] != NULL; i++)
+ {
+ const char *old = oldv[i];
+
+ if (*old != 0 && !flatpak_g_ptr_array_contains_string (patterns, old))
+ g_ptr_array_add (patterns, g_strdup (old));
+ }
+ }
+
+ return g_steal_pointer (&patterns);
+}
+
+
+gboolean
+flatpak_builtin_pin (int argc, char **argv, GCancellable *cancellable, GError **error)
+{
+ g_autoptr(GOptionContext) context = NULL;
+ g_autoptr(GPtrArray) dirs = NULL;
+ FlatpakDir *dir;
+ g_autofree char *merged_patterns = NULL;
+ g_autoptr(GPtrArray) patterns = NULL;
+ int i;
+
+ context = g_option_context_new (_("[PATTERN…] - disable automatic removal of runtimes matching patterns"));
+ g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
+
+ if (!flatpak_option_context_parse (context, options, &argc, &argv,
+ FLATPAK_BUILTIN_FLAG_ONE_DIR,
+ &dirs, cancellable, error))
+ return FALSE;
+
+ dir = g_ptr_array_index (dirs, 0);
+
+ patterns = get_old_patterns (dir);
+
+ if (argc == 1)
+ {
+ if (patterns->len == 0)
+ {
+ g_print (_("No pinned patterns\n"));
+ }
+ else
+ {
+ g_print (_("Pinned patterns:\n"));
+
+ for (i = 0; i < patterns->len; i++)
+ {
+ const char *old = g_ptr_array_index (patterns, i);
+ g_print (" %s\n", old);
+ }
+ }
+ }
+ else
+ {
+ for (i = 1; i < argc; i++)
+ {
+ const char *pattern = argv[i];
+
+ if (opt_remove)
+ {
+ int j;
+
+ for (j = 0; j < patterns->len; j++)
+ {
+ if (strcmp (g_ptr_array_index (patterns, j), pattern) == 0)
+ break;
+ }
+
+ if (j == patterns->len)
+ return flatpak_fail (error, _("No current pin matching %s"), pattern);
+ else
+ g_ptr_array_remove_index (patterns, j);
+ }
+ else
+ {
+ g_autofree char *regexp;
+
+ regexp = flatpak_filter_glob_to_regexp (pattern,
+ TRUE, /* only match runtimes */
+ error);
+ if (regexp == NULL)
+ return FALSE;
+
+ if (!flatpak_g_ptr_array_contains_string (patterns, pattern))
+ g_ptr_array_add (patterns, g_strdup (pattern));
+ }
+ }
+
+ g_ptr_array_sort (patterns, flatpak_strcmp0_ptr);
+
+ g_ptr_array_add (patterns, NULL);
+ merged_patterns = g_strjoinv (";", (char **)patterns->pdata);
+
+ if (!flatpak_dir_set_config (dir, "pinned", merged_patterns, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+flatpak_complete_pin (FlatpakCompletion *completion)
+{
+ g_autoptr(GOptionContext) context = NULL;
+ g_autoptr(GPtrArray) dirs = NULL;
+
+ context = g_option_context_new ("");
+ if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv,
+ FLATPAK_BUILTIN_FLAG_ONE_DIR | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO,
+ &dirs, NULL, NULL))
+ return FALSE;
+
+ switch (completion->argc)
+ {
+ case 0:
+ case 1: /* PATTERN */
+ flatpak_complete_options (completion, global_entries);
+ flatpak_complete_options (completion, options);
+ flatpak_complete_options (completion, user_entries);
+ break;
+ }
+
+ return TRUE;
+}
diff --git a/app/flatpak-builtins.h b/app/flatpak-builtins.h
index 1fd9c9e5..ec1ac4a4 100644
--- a/app/flatpak-builtins.h
+++ b/app/flatpak-builtins.h
@@ -86,6 +86,7 @@ BUILTINPROTO (remote_info)
BUILTINPROTO (remote_list)
BUILTINPROTO (install)
BUILTINPROTO (mask)
+BUILTINPROTO (pin)
BUILTINPROTO (update)
BUILTINPROTO (make_current_app)
BUILTINPROTO (uninstall)
diff --git a/app/flatpak-main.c b/app/flatpak-main.c
index 0fe1ce7b..62151e02 100644
--- a/app/flatpak-main.c
+++ b/app/flatpak-main.c
@@ -80,6 +80,7 @@ static FlatpakCommand commands[] = {
/* 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 },
diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h
index de8d047b..8f5ec83e 100644
--- a/common/flatpak-dir-private.h
+++ b/common/flatpak-dir-private.h
@@ -474,6 +474,8 @@ char ** flatpak_dir_search_for_dependency (FlatpakDir *self,
GError **error);
gboolean flatpak_dir_ref_is_masked (FlatpakDir *self,
const char *ref);
+gboolean flatpak_dir_ref_is_pinned (FlatpakDir *self,
+ const char *ref);
char * flatpak_dir_find_remote_ref (FlatpakDir *self,
const char *remote,
const char **opt_sideload_repos,
diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c
index 09a390c2..0f325b9c 100644
--- a/common/flatpak-dir.c
+++ b/common/flatpak-dir.c
@@ -214,6 +214,7 @@ struct FlatpakDir
/* Config cache, protected by config_cache lock */
GRegex *masked;
+ GRegex *pinned;
SoupSession *soup_session;
};
@@ -2260,6 +2261,7 @@ flatpak_dir_finalize (GObject *object)
g_clear_pointer (&self->summary_cache, g_hash_table_unref);
g_clear_pointer (&self->remote_filters, g_hash_table_unref);
g_clear_pointer (&self->masked, g_regex_unref);
+ g_clear_pointer (&self->pinned, g_regex_unref);
G_OBJECT_CLASS (flatpak_dir_parent_class)->finalize (object);
}
@@ -3436,6 +3438,7 @@ flatpak_dir_recreate_repo (FlatpakDir *self,
G_LOCK (config_cache);
g_clear_pointer (&self->masked, g_regex_unref);
+ g_clear_pointer (&self->pinned, g_regex_unref);
G_UNLOCK (config_cache);
@@ -3841,6 +3844,7 @@ _flatpak_dir_reload_config (FlatpakDir *self,
G_LOCK (config_cache);
g_clear_pointer (&self->masked, g_regex_unref);
+ g_clear_pointer (&self->pinned, g_regex_unref);
G_UNLOCK (config_cache);
return TRUE;
@@ -13603,7 +13607,7 @@ flatpak_dir_get_mask_regexp (FlatpakDir *self)
{
g_autofree char *regexp = NULL;
- regexp = flatpak_filter_glob_to_regexp (pattern, NULL);
+ regexp = flatpak_filter_glob_to_regexp (pattern, FALSE, NULL);
if (regexp)
{
if (i != 0)
@@ -13635,6 +13639,66 @@ flatpak_dir_ref_is_masked (FlatpakDir *self,
return !flatpak_filters_allow_ref (NULL, masked, ref);
}
+static GRegex *
+flatpak_dir_get_pin_regexp (FlatpakDir *self)
+{
+ GRegex *res = NULL;
+
+ G_LOCK (config_cache);
+
+ if (self->pinned == NULL)
+ {
+ g_autofree char *pinned = NULL;
+
+ pinned = flatpak_dir_get_config (self, "pinned", NULL);
+ if (pinned)
+ {
+ g_auto(GStrv) patterns = g_strsplit (pinned, ";", -1);
+ g_autoptr(GString) deny_regexp = g_string_new ("^(");
+ int i;
+
+ for (i = 0; patterns[i] != NULL; i++)
+ {
+ const char *pattern = patterns[i];
+
+ if (*pattern != 0)
+ {
+ g_autofree char *regexp = NULL;
+
+ regexp = flatpak_filter_glob_to_regexp (pattern,
+ TRUE, /* only match runtimes */
+ NULL);
+ if (regexp)
+ {
+ if (i != 0)
+ g_string_append (deny_regexp, "|");
+ g_string_append (deny_regexp, regexp);
+ }
+ }
+ }
+
+ g_string_append (deny_regexp, ")$");
+ self->pinned = g_regex_new (deny_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, NULL);
+ }
+ }
+
+ if (self->pinned)
+ res = g_regex_ref (self->pinned);
+
+ G_UNLOCK (config_cache);
+
+ return res;
+}
+
+gboolean
+flatpak_dir_ref_is_pinned (FlatpakDir *self,
+ const char *ref)
+{
+ g_autoptr(GRegex) pinned = flatpak_dir_get_pin_regexp (self);
+
+ return !flatpak_filters_allow_ref (NULL, pinned, ref);
+}
+
GPtrArray *
flatpak_dir_find_remote_related_for_metadata (FlatpakDir *self,
FlatpakRemoteState *state,
diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c
index 32cffd62..8b2f9d69 100644
--- a/common/flatpak-installation.c
+++ b/common/flatpak-installation.c
@@ -2926,6 +2926,7 @@ find_used_refs (FlatpakDir *dir,
*
* A reference is used if it is either an application, or an sdk,
* or the runtime of a used ref, or an extension of a used ref.
+ * Pinned runtimes are also considered used; see flatpak-pin(1).
*
* Returns: (transfer container) (element-type FlatpakInstalledRef): a GPtrArray of
* #FlatpakInstalledRef instances
@@ -3030,6 +3031,12 @@ flatpak_installation_list_unused_refs (FlatpakInstallation *self,
if (arch != NULL && strcmp (parts[2], arch) != 0)
continue;
+ if (flatpak_dir_ref_is_pinned (dir, ref))
+ {
+ g_debug ("Ref %s is pinned, considering as used", ref);
+ continue;
+ }
+
if (!g_hash_table_contains (used_refs, ref))
{
if (g_hash_table_add (refs_hash, (gpointer) ref))
diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h
index 40fa57c2..ebbf7759 100644
--- a/common/flatpak-utils-private.h
+++ b/common/flatpak-utils-private.h
@@ -194,7 +194,7 @@ gboolean flatpak_id_has_subref_suffix (const char *id);
char **flatpak_decompose_ref (const char *ref,
GError **error);
-char * flatpak_filter_glob_to_regexp (const char *glob, GError **error);
+char * flatpak_filter_glob_to_regexp (const char *glob, gboolean runtime_only, GError **error);
gboolean flatpak_parse_filters (const char *data,
GRegex **allow_refs_out,
GRegex **deny_refs_out,
diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c
index 16c307e1..4b3079bc 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -1131,7 +1131,9 @@ line_get_word (char **line)
}
char *
-flatpak_filter_glob_to_regexp (const char *glob, GError **error)
+flatpak_filter_glob_to_regexp (const char *glob,
+ gboolean runtime_only,
+ GError **error)
{
g_autoptr(GString) regexp = g_string_new ("");
int parts = 1;
@@ -1139,8 +1141,16 @@ flatpak_filter_glob_to_regexp (const char *glob, GError **error)
if (g_str_has_prefix (glob, "app/"))
{
- glob += strlen ("app/");
- g_string_append (regexp, "app/");
+ if (runtime_only)
+ {
+ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Glob can't match apps"));
+ return NULL;
+ }
+ else
+ {
+ glob += strlen ("app/");
+ g_string_append (regexp, "app/");
+ }
}
else if (g_str_has_prefix (glob, "runtime/"))
{
@@ -1148,7 +1158,12 @@ flatpak_filter_glob_to_regexp (const char *glob, GError **error)
g_string_append (regexp, "runtime/");
}
else
- g_string_append (regexp, "(app|runtime)/");
+ {
+ if (runtime_only)
+ g_string_append (regexp, "runtime/");
+ else
+ g_string_append (regexp, "(app|runtime)/");
+ }
/* We really need an id part, the rest is optional */
if (*glob == 0)
@@ -1253,7 +1268,7 @@ flatpak_parse_filters (const char *data,
if (next != NULL)
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Trailing text on line %d"), i + 1);
- ref_regexp = flatpak_filter_glob_to_regexp (glob, error);
+ ref_regexp = flatpak_filter_glob_to_regexp (glob, FALSE, error);
if (ref_regexp == NULL)
return glnx_prefix_error (error, _("on line %d"), i + 1);
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 36ef2783..aa3d186b 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -30,6 +30,7 @@ man1 = \
flatpak-update.1 \
flatpak-uninstall.1 \
flatpak-mask.1 \
+ flatpak-pin.1 \
flatpak-list.1 \
flatpak-info.1 \
flatpak-make-current.1 \
diff --git a/doc/flatpak-pin.xml b/doc/flatpak-pin.xml
new file mode 100644
index 00000000..b086e9b3
--- /dev/null
+++ b/doc/flatpak-pin.xml
@@ -0,0 +1,151 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="flatpak-pin">
+
+ <refentryinfo>
+ <title>flatpak pin</title>
+ <productname>flatpak</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Matthew</firstname>
+ <surname>Leeds</surname>
+ <email>matthew.leeds@endlessm.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>flatpak pin</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>flatpak-pin</refname>
+ <refpurpose>Pin runtimes to prevent automatic removal</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>flatpak pin</command>
+ <arg choice="opt" rep="repeat">OPTION</arg>
+ <arg choice="plain" rep="repeat">PATTERN</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ Flatpak maintains a list of patterns that define which refs are pinned.
+ A pinned ref will never be automatically uninstalled (as are unused
+ runtimes periodically). This can be useful if for example you are using
+ a runtime for development purposes.
+ </para>
+ <para>
+ The patterns are just a partial ref, with the * character matching anything
+ within that part of the ref. Only runtimes can be pinned, not apps. Here
+ are some example patterns:
+<programlisting>
+org.some.Runtime
+org.some.Runtime//unstable
+runtime/org.domain.*
+org.some.Runtime/arm
+</programlisting>
+ </para>
+ <para>
+ To list the current set of pins, run this command without any patterns.
+ </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 patterns, remove matching patterns.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--user</option></term>
+
+ <listitem><para>
+ Pin refs in a per-user installation.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--system</option></term>
+
+ <listitem><para>
+ Pin refs in the default system-wide installation.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--installation=NAME</option></term>
+
+ <listitem><para>
+ Pin refs in a 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 pin</command>
+ </para>
+ <para>
+ <command>$ flatpak pin org.freedesktop.Platform//19.08</command>
+ </para>
+ <para>
+ <command>$ flatpak pin --remove org.freedesktop.Platform//19.08</command>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>See also</title>
+
+ <para>
+ <citerefentry><refentrytitle>flatpak</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>flatpak-uninstall</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ </para>
+
+ </refsect1>
+
+</refentry>
diff --git a/doc/flatpak.xml b/doc/flatpak.xml
index aefa4629..b07aa31c 100644
--- a/doc/flatpak.xml
+++ b/doc/flatpak.xml
@@ -216,6 +216,13 @@
</para></listitem>
</varlistentry>
<varlistentry>
+ <term><citerefentry><refentrytitle>flatpak-pin</refentrytitle><manvolnum>1</manvolnum></citerefentry></term>
+
+ <listitem><para>
+ Pin runtimes to prevent automatic removal.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
<term><citerefentry><refentrytitle>flatpak-list</refentrytitle><manvolnum>1</manvolnum></citerefentry></term>
<listitem><para>
diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c
index 0aa9f6a0..137d30b2 100644
--- a/system-helper/flatpak-system-helper.c
+++ b/system-helper/flatpak-system-helper.c
@@ -1108,7 +1108,8 @@ handle_configure (FlatpakSystemHelper *object,
if ((strcmp (arg_key, "languages") != 0) &&
(strcmp (arg_key, "extra-languages") != 0) &&
- (strcmp (arg_key, "masked") != 0))
+ (strcmp (arg_key, "masked") != 0) &&
+ (strcmp (arg_key, "pinned") != 0))
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Unsupported key: %s", arg_key);
diff --git a/tests/test-repo.sh b/tests/test-repo.sh
index a367a1b1..5c2db337 100644
--- a/tests/test-repo.sh
+++ b/tests/test-repo.sh
@@ -24,7 +24,7 @@ set -euo pipefail
skip_without_bwrap
skip_revokefs_without_fuse
-echo "1..37"
+echo "1..38"
#Regular repo
setup_repo
@@ -587,6 +587,30 @@ assert_not_file_has_content list-log "org\.test\.Platform"
ok "uninstall --unused"
+${FLATPAK} ${U} install -y test-repo org.test.Platform
+
+${FLATPAK} ${U} list -a --columns=application > list-log
+assert_file_has_content list-log "org\.test\.Platform"
+
+# Check that the runtime won't be removed if it's pinned
+${FLATPAK} ${U} pin org.test.Platform
+${FLATPAK} ${U} pin > pins
+assert_file_has_content pins "org\.test\.Platform"
+rm pins
+
+${FLATPAK} ${U} uninstall -y --unused
+
+${FLATPAK} ${U} list -a --columns=application > list-log
+assert_file_has_content list-log "org\.test\.Platform"
+
+# Remove the pin and try again
+${FLATPAK} ${U} pin --remove "org.test.Platform"
+${FLATPAK} ${U} uninstall -y --unused
+${FLATPAK} ${U} list -a --columns=application > list-log
+assert_not_file_has_content list-log "org\.test\.Platform"
+
+ok "uninstall --unused ignores pinned runtimes"
+
# Test that remote-ls works in all of the following cases:
# * system remote, and --system is used
# * system remote, and --system is omitted