"Fossies" - the Fresh Open Source Software Archive

Member "flatpak-1.12.2/common/flatpak-utils.c" (11 Oct 2021, 288662 Bytes) of package /linux/misc/flatpak-1.12.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "flatpak-utils.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.12.1_vs_1.12.2.

    1 /*
    2  * Copyright © 1995-1998 Free Software Foundation, Inc.
    3  * Copyright © 2014-2019 Red Hat, Inc
    4  *
    5  * This program is free software; you can redistribute it and/or
    6  * modify it under the terms of the GNU Lesser General Public
    7  * License as published by the Free Software Foundation; either
    8  * version 2.1 of the License, or (at your option) any later version.
    9  *
   10  * This library is distributed in the hope that it will be useful,
   11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   13  * Lesser General Public License for more details.
   14  *
   15  * You should have received a copy of the GNU Lesser General Public
   16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
   17  *
   18  * Authors:
   19  *       Alexander Larsson <alexl@redhat.com>
   20  */
   21 
   22 #include "config.h"
   23 
   24 #include <glib/gi18n-lib.h>
   25 
   26 #include <string.h>
   27 #include <stdlib.h>
   28 #include <ctype.h>
   29 #include <stdio.h>
   30 #include <errno.h>
   31 #include <unistd.h>
   32 #include <fcntl.h>
   33 #include <string.h>
   34 #include <sys/stat.h>
   35 #include <sys/file.h>
   36 #include <sys/mman.h>
   37 #include <sys/types.h>
   38 #include <sys/utsname.h>
   39 #include <sys/ioctl.h>
   40 #include <termios.h>
   41 
   42 #include <glib.h>
   43 #include <gio/gunixoutputstream.h>
   44 #include <gio/gunixinputstream.h>
   45 
   46 #include "flatpak-dir-private.h"
   47 #include "flatpak-error.h"
   48 #include "flatpak-oci-registry-private.h"
   49 #include "flatpak-progress-private.h"
   50 #include "flatpak-run-private.h"
   51 #include "flatpak-utils-base-private.h"
   52 #include "flatpak-utils-private.h"
   53 #include "flatpak-variant-impl-private.h"
   54 #include "libglnx/libglnx.h"
   55 #include "valgrind-private.h"
   56 
   57 /* This is also here so the common code can report these errors to the lib */
   58 static const GDBusErrorEntry flatpak_error_entries[] = {
   59   {FLATPAK_ERROR_ALREADY_INSTALLED,     "org.freedesktop.Flatpak.Error.AlreadyInstalled"},
   60   {FLATPAK_ERROR_NOT_INSTALLED,         "org.freedesktop.Flatpak.Error.NotInstalled"},
   61   {FLATPAK_ERROR_ONLY_PULLED,           "org.freedesktop.Flatpak.Error.OnlyPulled"}, /* Since: 1.0 */
   62   {FLATPAK_ERROR_DIFFERENT_REMOTE,      "org.freedesktop.Flatpak.Error.DifferentRemote"}, /* Since: 1.0 */
   63   {FLATPAK_ERROR_ABORTED,               "org.freedesktop.Flatpak.Error.Aborted"}, /* Since: 1.0 */
   64   {FLATPAK_ERROR_SKIPPED,               "org.freedesktop.Flatpak.Error.Skipped"}, /* Since: 1.0 */
   65   {FLATPAK_ERROR_NEED_NEW_FLATPAK,      "org.freedesktop.Flatpak.Error.NeedNewFlatpak"}, /* Since: 1.0 */
   66   {FLATPAK_ERROR_REMOTE_NOT_FOUND,      "org.freedesktop.Flatpak.Error.RemoteNotFound"}, /* Since: 1.0 */
   67   {FLATPAK_ERROR_RUNTIME_NOT_FOUND,     "org.freedesktop.Flatpak.Error.RuntimeNotFound"}, /* Since: 1.0 */
   68   {FLATPAK_ERROR_DOWNGRADE,             "org.freedesktop.Flatpak.Error.Downgrade"}, /* Since: 1.0 */
   69   {FLATPAK_ERROR_INVALID_REF,           "org.freedesktop.Flatpak.Error.InvalidRef"}, /* Since: 1.0.3 */
   70   {FLATPAK_ERROR_INVALID_DATA,          "org.freedesktop.Flatpak.Error.InvalidData"}, /* Since: 1.0.3 */
   71   {FLATPAK_ERROR_UNTRUSTED,             "org.freedesktop.Flatpak.Error.Untrusted"}, /* Since: 1.0.3 */
   72   {FLATPAK_ERROR_SETUP_FAILED,          "org.freedesktop.Flatpak.Error.SetupFailed"}, /* Since: 1.0.3 */
   73   {FLATPAK_ERROR_EXPORT_FAILED,         "org.freedesktop.Flatpak.Error.ExportFailed"}, /* Since: 1.0.3 */
   74   {FLATPAK_ERROR_REMOTE_USED,           "org.freedesktop.Flatpak.Error.RemoteUsed"}, /* Since: 1.0.3 */
   75   {FLATPAK_ERROR_RUNTIME_USED,          "org.freedesktop.Flatpak.Error.RuntimeUsed"}, /* Since: 1.0.3 */
   76   {FLATPAK_ERROR_INVALID_NAME,          "org.freedesktop.Flatpak.Error.InvalidName"}, /* Since: 1.0.3 */
   77   {FLATPAK_ERROR_OUT_OF_SPACE,          "org.freedesktop.Flatpak.Error.OutOfSpace"}, /* Since: 1.2.0 */
   78   {FLATPAK_ERROR_WRONG_USER,            "org.freedesktop.Flatpak.Error.WrongUser"}, /* Since: 1.2.0 */
   79   {FLATPAK_ERROR_NOT_CACHED,            "org.freedesktop.Flatpak.Error.NotCached"}, /* Since: 1.3.3 */
   80   {FLATPAK_ERROR_REF_NOT_FOUND,         "org.freedesktop.Flatpak.Error.RefNotFound"}, /* Since: 1.4.0 */
   81   {FLATPAK_ERROR_PERMISSION_DENIED,     "org.freedesktop.Flatpak.Error.PermissionDenied"}, /* Since: 1.5.1 */
   82 };
   83 
   84 typedef struct archive FlatpakAutoArchiveRead;
   85 G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakAutoArchiveRead, archive_read_free)
   86 
   87 static void
   88 propagate_libarchive_error (GError        **error,
   89                             struct archive *a)
   90 {
   91   g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
   92                "%s", archive_error_string (a));
   93 }
   94 
   95 GQuark
   96 flatpak_error_quark (void)
   97 {
   98   static volatile gsize quark_volatile = 0;
   99 
  100   g_dbus_error_register_error_domain ("flatpak-error-quark",
  101                                       &quark_volatile,
  102                                       flatpak_error_entries,
  103                                       G_N_ELEMENTS (flatpak_error_entries));
  104   return (GQuark) quark_volatile;
  105 }
  106 
  107 gboolean
  108 flatpak_fail_error (GError **error, FlatpakError code, const char *fmt, ...)
  109 {
  110   if (error == NULL)
  111     return FALSE;
  112 
  113   va_list args;
  114   va_start (args, fmt);
  115   GError *new = g_error_new_valist (FLATPAK_ERROR, code, fmt, args);
  116   va_end (args);
  117   g_propagate_error (error, g_steal_pointer (&new));
  118   return FALSE;
  119 }
  120 
  121 void
  122 flatpak_debug2 (const char *format, ...)
  123 {
  124   va_list var_args;
  125 
  126   va_start (var_args, format);
  127   g_logv (G_LOG_DOMAIN "2",
  128           G_LOG_LEVEL_DEBUG,
  129           format, var_args);
  130   va_end (var_args);
  131 }
  132 
  133 gboolean
  134 flatpak_write_update_checksum (GOutputStream *out,
  135                                gconstpointer  data,
  136                                gsize          len,
  137                                gsize         *out_bytes_written,
  138                                GChecksum     *checksum,
  139                                GCancellable  *cancellable,
  140                                GError       **error)
  141 {
  142   if (out)
  143     {
  144       if (!g_output_stream_write_all (out, data, len, out_bytes_written,
  145                                       cancellable, error))
  146         return FALSE;
  147     }
  148   else if (out_bytes_written)
  149     {
  150       *out_bytes_written = len;
  151     }
  152 
  153   if (checksum)
  154     g_checksum_update (checksum, data, len);
  155 
  156   return TRUE;
  157 }
  158 
  159 gboolean
  160 flatpak_splice_update_checksum (GOutputStream         *out,
  161                                 GInputStream          *in,
  162                                 GChecksum             *checksum,
  163                                 FlatpakLoadUriProgress progress,
  164                                 gpointer               progress_data,
  165                                 GCancellable          *cancellable,
  166                                 GError               **error)
  167 {
  168   gsize bytes_read, bytes_written;
  169   char buf[32 * 1024];
  170   guint64 downloaded_bytes = 0;
  171   gint64 progress_start;
  172 
  173   progress_start = g_get_monotonic_time ();
  174   do
  175     {
  176       if (!g_input_stream_read_all (in, buf, sizeof buf, &bytes_read, cancellable, error))
  177         return FALSE;
  178 
  179       if (!flatpak_write_update_checksum (out, buf, bytes_read, &bytes_written, checksum,
  180                                           cancellable, error))
  181         return FALSE;
  182 
  183       downloaded_bytes += bytes_read;
  184 
  185       if (progress &&
  186           g_get_monotonic_time () - progress_start >  5 * 1000000)
  187         {
  188           progress (downloaded_bytes, progress_data);
  189           progress_start = g_get_monotonic_time ();
  190         }
  191     }
  192   while (bytes_read > 0);
  193 
  194   if (progress)
  195     progress (downloaded_bytes, progress_data);
  196 
  197   return TRUE;
  198 }
  199 
  200 GBytes *
  201 flatpak_zlib_compress_bytes (GBytes *bytes,
  202                              int level,
  203                              GError **error)
  204 {
  205   g_autoptr(GZlibCompressor) compressor = NULL;
  206   g_autoptr(GOutputStream) out = NULL;
  207   g_autoptr(GOutputStream) mem = NULL;
  208 
  209   mem = g_memory_output_stream_new_resizable ();
  210 
  211   compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, level);
  212   out = g_converter_output_stream_new (mem, G_CONVERTER (compressor));
  213 
  214   if (!g_output_stream_write_all (out, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes),
  215                                   NULL, NULL, error))
  216     return NULL;
  217 
  218   if (!g_output_stream_close (out, NULL, error))
  219     return NULL;
  220 
  221   return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem));
  222 }
  223 
  224 GBytes *
  225 flatpak_zlib_decompress_bytes (GBytes *bytes,
  226                                GError **error)
  227 {
  228   g_autoptr(GZlibDecompressor) decompressor = NULL;
  229   g_autoptr(GOutputStream) out = NULL;
  230   g_autoptr(GOutputStream) mem = NULL;
  231 
  232   mem = g_memory_output_stream_new_resizable ();
  233 
  234   decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
  235   out = g_converter_output_stream_new (mem, G_CONVERTER (decompressor));
  236 
  237   if (!g_output_stream_write_all (out, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes),
  238                                   NULL, NULL, error))
  239     return NULL;
  240 
  241   if (!g_output_stream_close (out, NULL, error))
  242     return NULL;
  243 
  244   return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem));
  245 }
  246 
  247 GBytes *
  248 flatpak_read_stream (GInputStream *in,
  249                      gboolean      null_terminate,
  250                      GError      **error)
  251 {
  252   g_autoptr(GOutputStream) mem_stream = NULL;
  253 
  254   mem_stream = g_memory_output_stream_new_resizable ();
  255   if (g_output_stream_splice (mem_stream, in,
  256                               0, NULL, error) < 0)
  257     return NULL;
  258 
  259   if (null_terminate)
  260     {
  261       if (!g_output_stream_write (G_OUTPUT_STREAM (mem_stream), "\0", 1, NULL, error))
  262         return NULL;
  263     }
  264 
  265   if (!g_output_stream_close (G_OUTPUT_STREAM (mem_stream), NULL, error))
  266     return NULL;
  267 
  268   return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem_stream));
  269 }
  270 
  271 gint
  272 flatpak_strcmp0_ptr (gconstpointer a,
  273                      gconstpointer b)
  274 {
  275   return g_strcmp0 (*(char * const *) a, *(char * const *) b);
  276 }
  277 
  278 /* Sometimes this is /var/run which is a symlink, causing weird issues when we pass
  279  * it as a path into the sandbox */
  280 char *
  281 flatpak_get_real_xdg_runtime_dir (void)
  282 {
  283   return realpath (g_get_user_runtime_dir (), NULL);
  284 }
  285 
  286 /* Compares if str has a specific path prefix. This differs
  287    from a regular prefix in two ways. First of all there may
  288    be multiple slashes separating the path elements, and
  289    secondly, if a prefix is matched that has to be en entire
  290    path element. For instance /a/prefix matches /a/prefix/foo/bar,
  291    but not /a/prefixfoo/bar. */
  292 gboolean
  293 flatpak_has_path_prefix (const char *str,
  294                          const char *prefix)
  295 {
  296   while (TRUE)
  297     {
  298       /* Skip consecutive slashes to reach next path
  299          element */
  300       while (*str == '/')
  301         str++;
  302       while (*prefix == '/')
  303         prefix++;
  304 
  305       /* No more prefix path elements? Done! */
  306       if (*prefix == 0)
  307         return TRUE;
  308 
  309       /* Compare path element */
  310       while (*prefix != 0 && *prefix != '/')
  311         {
  312           if (*str != *prefix)
  313             return FALSE;
  314           str++;
  315           prefix++;
  316         }
  317 
  318       /* Matched prefix path element,
  319          must be entire str path element */
  320       if (*str != '/' && *str != 0)
  321         return FALSE;
  322     }
  323 }
  324 
  325 /* Returns end of matching path prefix, or NULL if no match */
  326 const char *
  327 flatpak_path_match_prefix (const char *pattern,
  328                            const char *string)
  329 {
  330   char c, test;
  331   const char *tmp;
  332 
  333   while (*pattern == '/')
  334     pattern++;
  335 
  336   while (*string == '/')
  337     string++;
  338 
  339   while (TRUE)
  340     {
  341       switch (c = *pattern++)
  342         {
  343         case 0:
  344           if (*string == '/' || *string == 0)
  345             return string;
  346           return NULL;
  347 
  348         case '?':
  349           if (*string == '/' || *string == 0)
  350             return NULL;
  351           string++;
  352           break;
  353 
  354         case '*':
  355           c = *pattern;
  356 
  357           while (c == '*')
  358             c = *++pattern;
  359 
  360           /* special case * at end */
  361           if (c == 0)
  362             {
  363               tmp = strchr (string, '/');
  364               if (tmp != NULL)
  365                 return tmp;
  366               return string + strlen (string);
  367             }
  368           else if (c == '/')
  369             {
  370               string = strchr (string, '/');
  371               if (string == NULL)
  372                 return NULL;
  373               break;
  374             }
  375 
  376           while ((test = *string) != 0)
  377             {
  378               tmp = flatpak_path_match_prefix (pattern, string);
  379               if (tmp != NULL)
  380                 return tmp;
  381               if (test == '/')
  382                 break;
  383               string++;
  384             }
  385           return NULL;
  386 
  387         default:
  388           if (c != *string)
  389             return NULL;
  390           string++;
  391           break;
  392         }
  393     }
  394   return NULL; /* Should not be reached */
  395 }
  396 
  397 static const char *
  398 flatpak_get_kernel_arch (void)
  399 {
  400   static struct utsname buf;
  401   static char *arch = NULL;
  402   char *m;
  403 
  404   if (arch != NULL)
  405     return arch;
  406 
  407   if (uname (&buf))
  408     {
  409       arch = "unknown";
  410       return arch;
  411     }
  412 
  413   /* By default, just pass on machine, good enough for most arches */
  414   arch = buf.machine;
  415 
  416   /* Override for some arches */
  417 
  418   m = buf.machine;
  419   /* i?86 */
  420   if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8'  && m[3] == '6')
  421     {
  422       arch = "i386";
  423     }
  424   else if (g_str_has_prefix (m, "arm"))
  425     {
  426       if (g_str_has_suffix (m, "b"))
  427         arch = "armeb";
  428       else
  429         arch = "arm";
  430     }
  431   else if (strcmp (m, "mips") == 0)
  432     {
  433 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
  434       arch = "mipsel";
  435 #endif
  436     }
  437   else if (strcmp (m, "mips64") == 0)
  438     {
  439 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
  440       arch = "mips64el";
  441 #endif
  442     }
  443 
  444   return arch;
  445 }
  446 
  447 /* This maps the kernel-reported uname to a single string representing
  448  * the cpu family, in the sense that all members of this family would
  449  * be able to understand and link to a binary file with such cpu
  450  * opcodes. That doesn't necessarily mean that all members of the
  451  * family can run all opcodes, for instance for modern 32bit intel we
  452  * report "i386", even though they support instructions that the
  453  * original i386 cpu cannot run. Still, such an executable would
  454  * at least try to execute a 386, whereas an arm binary would not.
  455  */
  456 const char *
  457 flatpak_get_arch (void)
  458 {
  459   /* Avoid using uname on multiarch machines, because uname reports the kernels
  460    * arch, and that may be different from userspace. If e.g. the kernel is 64bit and
  461    * the userspace is 32bit we want to use 32bit by default. So, we take the current build
  462    * arch as the default. */
  463 #if defined(__i386__)
  464   return "i386";
  465 #elif defined(__x86_64__)
  466   return "x86_64";
  467 #elif defined(__aarch64__)
  468   return "aarch64";
  469 #elif defined(__arm__)
  470 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
  471   return "arm";
  472 #else
  473   return "armeb";
  474 #endif
  475 #else
  476   return flatpak_get_kernel_arch ();
  477 #endif
  478 }
  479 
  480 gboolean
  481 flatpak_is_linux32_arch (const char *arch)
  482 {
  483   const char *kernel_arch = flatpak_get_kernel_arch ();
  484 
  485   if (strcmp (kernel_arch, "x86_64") == 0 &&
  486       strcmp (arch, "i386") == 0)
  487     return TRUE;
  488 
  489   if (strcmp (kernel_arch, "aarch64") == 0 &&
  490       strcmp (arch, "arm") == 0)
  491     return TRUE;
  492 
  493   return FALSE;
  494 }
  495 
  496 static struct
  497 {
  498   const char *kernel_arch;
  499   const char *compat_arch;
  500 } compat_arches[] = {
  501   { "x86_64", "i386" },
  502   { "aarch64", "arm" },
  503 };
  504 
  505 static const char *
  506 flatpak_get_compat_arch (const char *kernel_arch)
  507 {
  508   int i;
  509 
  510   /* Also add all other arches that are compatible with the kernel arch */
  511   for (i = 0; i < G_N_ELEMENTS (compat_arches); i++)
  512     {
  513       if (strcmp (compat_arches[i].kernel_arch, kernel_arch) == 0)
  514         return compat_arches[i].compat_arch;
  515     }
  516 
  517   return NULL;
  518 }
  519 
  520 const char *
  521 flatpak_get_compat_arch_reverse (const char *compat_arch)
  522 {
  523   int i;
  524 
  525   /* Also add all other arches that are compatible with the kernel arch */
  526   for (i = 0; i < G_N_ELEMENTS (compat_arches); i++)
  527     {
  528       if (strcmp (compat_arches[i].compat_arch, compat_arch) == 0)
  529         return compat_arches[i].kernel_arch;
  530     }
  531 
  532   return NULL;
  533 }
  534 
  535 /* Get all compatible arches for this host in order of priority */
  536 const char **
  537 flatpak_get_arches (void)
  538 {
  539   static gsize arches = 0;
  540 
  541   if (g_once_init_enter (&arches))
  542     {
  543       gsize new_arches = 0;
  544       const char *main_arch = flatpak_get_arch ();
  545       const char *kernel_arch = flatpak_get_kernel_arch ();
  546       const char *compat_arch;
  547       GPtrArray *array = g_ptr_array_new ();
  548 
  549       /* This is the userspace arch, i.e. the one flatpak itself was
  550          build for. It's always first. */
  551       g_ptr_array_add (array, (char *) main_arch);
  552 
  553       compat_arch = flatpak_get_compat_arch (kernel_arch);
  554       if (g_strcmp0 (compat_arch, main_arch) != 0)
  555         g_ptr_array_add (array, (char *) compat_arch);
  556 
  557       g_ptr_array_add (array, NULL);
  558       new_arches = (gsize) g_ptr_array_free (array, FALSE);
  559 
  560       g_once_init_leave (&arches, new_arches);
  561     }
  562 
  563   return (const char **) arches;
  564 }
  565 
  566 const char **
  567 flatpak_get_gl_drivers (void)
  568 {
  569   static gsize drivers = 0;
  570 
  571   if (g_once_init_enter (&drivers))
  572     {
  573       gsize new_drivers;
  574       char **new_drivers_c = 0;
  575       const char *env = g_getenv ("FLATPAK_GL_DRIVERS");
  576       if (env != NULL && *env != 0)
  577         new_drivers_c = g_strsplit (env, ":", -1);
  578       else
  579         {
  580           g_autofree char *nvidia_version = NULL;
  581           char *dot;
  582           GPtrArray *array = g_ptr_array_new ();
  583 
  584           if (g_file_get_contents ("/sys/module/nvidia/version",
  585                                    &nvidia_version, NULL, NULL))
  586             {
  587               g_strstrip (nvidia_version);
  588               /* Convert dots to dashes */
  589               while ((dot = strchr (nvidia_version, '.')) != NULL)
  590                 *dot = '-';
  591               g_ptr_array_add (array, g_strconcat ("nvidia-", nvidia_version, NULL));
  592             }
  593 
  594           g_ptr_array_add (array, (char *) "default");
  595           g_ptr_array_add (array, (char *) "host");
  596 
  597           g_ptr_array_add (array, NULL);
  598           new_drivers_c = (char **) g_ptr_array_free (array, FALSE);
  599         }
  600 
  601       new_drivers = (gsize) new_drivers_c;
  602       g_once_init_leave (&drivers, new_drivers);
  603     }
  604 
  605   return (const char **) drivers;
  606 }
  607 
  608 static gboolean
  609 flatpak_get_have_intel_gpu (void)
  610 {
  611   static int have_intel = -1;
  612 
  613   if (have_intel == -1)
  614     have_intel = g_file_test ("/sys/module/i915", G_FILE_TEST_EXISTS);
  615 
  616   return have_intel;
  617 }
  618 
  619 static const char *
  620 flatpak_get_gtk_theme (void)
  621 {
  622   static char *gtk_theme;
  623 
  624   if (g_once_init_enter (&gtk_theme))
  625     {
  626       /* The schema may not be installed so check first */
  627       GSettingsSchemaSource *source = g_settings_schema_source_get_default ();
  628       g_autoptr(GSettingsSchema) schema = NULL;
  629 
  630       if (source == NULL)
  631         g_once_init_leave (&gtk_theme, g_strdup (""));
  632       else
  633         {
  634           schema = g_settings_schema_source_lookup (source,
  635                                                     "org.gnome.desktop.interface", FALSE);
  636 
  637           if (schema == NULL)
  638             g_once_init_leave (&gtk_theme, g_strdup (""));
  639           else
  640             {
  641               /* GSettings is used to store the theme if you use Wayland or GNOME.
  642                * TODO: Check XSettings Net/ThemeName for other desktops.
  643                * We don't care about any other method (like settings.ini) because they
  644                *   aren't passed through the sandbox anyway. */
  645               g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
  646               g_once_init_leave (&gtk_theme, g_settings_get_string (settings, "gtk-theme"));
  647             }
  648         }
  649     }
  650 
  651   return (const char *) gtk_theme;
  652 }
  653 
  654 static int fancy_output = -1;
  655 
  656 void
  657 flatpak_disable_fancy_output (void)
  658 {
  659   fancy_output = FALSE;
  660 }
  661 
  662 void
  663 flatpak_enable_fancy_output (void)
  664 {
  665   fancy_output = TRUE;
  666 }
  667 
  668 gboolean
  669 flatpak_fancy_output (void)
  670 {
  671   if (fancy_output != -1)
  672     return fancy_output;
  673 
  674   if (g_strcmp0 (g_getenv ("FLATPAK_FANCY_OUTPUT"), "0") == 0)
  675     return FALSE;
  676 
  677   return isatty (STDOUT_FILENO);
  678 }
  679 
  680 const char *
  681 flatpak_get_bwrap (void)
  682 {
  683   const char *e = g_getenv ("FLATPAK_BWRAP");
  684 
  685   if (e != NULL)
  686     return e;
  687   return HELPER;
  688 }
  689 
  690 gboolean
  691 flatpak_get_allowed_exports (const char     *source_path,
  692                              const char     *app_id,
  693                              FlatpakContext *context,
  694                              char         ***allowed_extensions_out,
  695                              char         ***allowed_prefixes_out,
  696                              gboolean       *require_exact_match_out)
  697 {
  698   g_autoptr(GPtrArray) allowed_extensions = g_ptr_array_new_with_free_func (g_free);
  699   g_autoptr(GPtrArray) allowed_prefixes = g_ptr_array_new_with_free_func (g_free);
  700   gboolean require_exact_match = FALSE;
  701 
  702   g_ptr_array_add (allowed_prefixes, g_strdup_printf ("%s.*", app_id));
  703 
  704   if (strcmp (source_path, "share/applications") == 0)
  705     {
  706       g_ptr_array_add (allowed_extensions, g_strdup (".desktop"));
  707     }
  708   else if (flatpak_has_path_prefix (source_path, "share/icons"))
  709     {
  710       g_ptr_array_add (allowed_extensions, g_strdup (".svgz"));
  711       g_ptr_array_add (allowed_extensions, g_strdup (".png"));
  712       g_ptr_array_add (allowed_extensions, g_strdup (".svg"));
  713       g_ptr_array_add (allowed_extensions, g_strdup (".ico"));
  714     }
  715   else if (strcmp (source_path, "share/dbus-1/services") == 0)
  716     {
  717       g_auto(GStrv) owned_dbus_names =  flatpak_context_get_session_bus_policy_allowed_own_names (context);
  718 
  719       g_ptr_array_add (allowed_extensions, g_strdup (".service"));
  720 
  721       for (GStrv iter = owned_dbus_names; *iter != NULL; ++iter)
  722         g_ptr_array_add (allowed_prefixes, g_strdup (*iter));
  723 
  724       /* We need an exact match with no extra garbage, because the filename refers to busnames
  725        * and we can *only* match exactly these */
  726       require_exact_match = TRUE;
  727     }
  728   else if (strcmp (source_path, "share/gnome-shell/search-providers") == 0)
  729     {
  730       g_ptr_array_add (allowed_extensions, g_strdup (".ini"));
  731     }
  732   else if (strcmp (source_path, "share/mime/packages") == 0)
  733     {
  734       g_ptr_array_add (allowed_extensions, g_strdup (".xml"));
  735     }
  736   else
  737     return FALSE;
  738 
  739   g_ptr_array_add (allowed_extensions, NULL);
  740   g_ptr_array_add (allowed_prefixes, NULL);
  741 
  742   if (allowed_extensions_out)
  743     *allowed_extensions_out = (char **) g_ptr_array_free (g_steal_pointer (&allowed_extensions), FALSE);
  744 
  745   if (allowed_prefixes_out)
  746     *allowed_prefixes_out = (char **) g_ptr_array_free (g_steal_pointer (&allowed_prefixes), FALSE);
  747 
  748   if (require_exact_match_out)
  749     *require_exact_match_out = require_exact_match;
  750 
  751   return TRUE;
  752 }
  753 
  754 
  755 static char *
  756 line_get_word (char **line)
  757 {
  758   char *word = NULL;
  759 
  760   while (g_ascii_isspace (**line))
  761     (*line)++;
  762 
  763   if (**line == 0)
  764     return NULL;
  765 
  766   word = *line;
  767 
  768   while (**line && !g_ascii_isspace (**line))
  769     (*line)++;
  770 
  771   if (**line)
  772     {
  773       **line = 0;
  774       (*line)++;
  775     }
  776 
  777   return word;
  778 }
  779 
  780 char *
  781 flatpak_filter_glob_to_regexp (const char  *glob,
  782                                gboolean     runtime_only,
  783                                GError     **error)
  784 {
  785   g_autoptr(GString) regexp = g_string_new ("");
  786   int parts = 1;
  787   gboolean empty_part;
  788 
  789   if (g_str_has_prefix (glob, "app/"))
  790     {
  791       if (runtime_only)
  792         {
  793           flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Glob can't match apps"));
  794           return NULL;
  795         }
  796       else
  797         {
  798           glob += strlen ("app/");
  799           g_string_append (regexp, "app/");
  800         }
  801     }
  802   else if (g_str_has_prefix (glob, "runtime/"))
  803     {
  804       glob += strlen ("runtime/");
  805       g_string_append (regexp, "runtime/");
  806     }
  807   else
  808     {
  809       if (runtime_only)
  810         g_string_append (regexp, "runtime/");
  811       else
  812         g_string_append (regexp, "(app|runtime)/");
  813     }
  814 
  815   /* We really need an id part, the rest is optional */
  816   if (*glob == 0)
  817     {
  818       flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Empty glob"));
  819       return NULL;
  820     }
  821 
  822   empty_part = TRUE;
  823   while (*glob != 0)
  824     {
  825       char c = *glob;
  826       glob++;
  827 
  828       if (c == '/')
  829         {
  830           if (empty_part)
  831             g_string_append (regexp, "[.\\-_a-zA-Z0-9]*");
  832           empty_part = TRUE;
  833           parts++;
  834           g_string_append (regexp, "/");
  835           if (parts > 3)
  836             {
  837               flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Too many segments in glob"));
  838               return NULL;
  839             }
  840         }
  841       else if (c == '*')
  842         {
  843           empty_part = FALSE;
  844          g_string_append (regexp, "[.\\-_a-zA-Z0-9]*");
  845         }
  846       else if (c == '.')
  847         {
  848           empty_part = FALSE;
  849           g_string_append (regexp, "\\.");
  850         }
  851       else if (g_ascii_isalnum (c) || c == '-' || c == '_')
  852         {
  853           empty_part = FALSE;
  854           g_string_append_c (regexp, c);
  855         }
  856       else
  857         {
  858           flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid glob character '%c'"), c);
  859           return NULL;
  860         }
  861     }
  862 
  863   while (parts < 3)
  864     {
  865       parts++;
  866       g_string_append (regexp, "/[.\\-_a-zA-Z0-9]*");
  867     }
  868 
  869   return g_string_free (g_steal_pointer (&regexp), FALSE);
  870 }
  871 
  872 gboolean
  873 flatpak_parse_filters (const char *data,
  874                        GRegex **allow_refs_out,
  875                        GRegex **deny_refs_out,
  876                        GError **error)
  877 {
  878   g_auto(GStrv) lines = NULL;
  879   int i;
  880   g_autoptr(GString) allow_regexp = g_string_new ("^(");
  881   g_autoptr(GString) deny_regexp = g_string_new ("^(");
  882   gboolean has_allow = FALSE;
  883   gboolean has_deny = FALSE;
  884   g_autoptr(GRegex) allow_refs = NULL;
  885   g_autoptr(GRegex) deny_refs = NULL;
  886 
  887   lines = g_strsplit (data, "\n", -1);
  888   for (i = 0; lines[i] != NULL; i++)
  889     {
  890       char *line = lines[i];
  891       char *comment, *command;
  892 
  893       /* Ignore shell-style comments */
  894       comment = strchr (line, '#');
  895       if (comment != NULL)
  896         *comment = 0;
  897 
  898       command = line_get_word (&line);
  899       /* Ignore empty lines */
  900       if (command == NULL)
  901         continue;
  902 
  903       if (strcmp (command, "allow") == 0 || strcmp (command, "deny") == 0)
  904         {
  905           char *glob, *next;
  906           g_autofree char *ref_regexp = NULL;
  907           GString *command_regexp;
  908           gboolean *has_type = NULL;
  909 
  910           glob = line_get_word (&line);
  911           if (glob == NULL)
  912             return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Missing glob on line %d"), i + 1);
  913 
  914           next = line_get_word (&line);
  915           if (next != NULL)
  916             return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Trailing text on line %d"), i + 1);
  917 
  918           ref_regexp = flatpak_filter_glob_to_regexp (glob, FALSE, error);
  919           if (ref_regexp == NULL)
  920             return glnx_prefix_error (error, _("on line %d"), i + 1);
  921 
  922           if (strcmp (command, "allow") == 0)
  923             {
  924               command_regexp = allow_regexp;
  925               has_type = &has_allow;
  926             }
  927           else
  928             {
  929               command_regexp = deny_regexp;
  930               has_type = &has_deny;
  931             }
  932 
  933           if (*has_type)
  934             g_string_append (command_regexp, "|");
  935           else
  936             *has_type = TRUE;
  937 
  938           g_string_append (command_regexp, ref_regexp);
  939         }
  940       else
  941         {
  942           return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unexpected word '%s' on line %d"), command, i + 1);
  943         }
  944     }
  945 
  946   g_string_append (allow_regexp, ")$");
  947   g_string_append (deny_regexp, ")$");
  948 
  949   if (allow_regexp)
  950     {
  951       allow_refs = g_regex_new (allow_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, error);
  952       if (allow_refs == NULL)
  953         return FALSE;
  954     }
  955 
  956   if (deny_regexp)
  957     {
  958       deny_refs = g_regex_new (deny_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, error);
  959       if (deny_refs == NULL)
  960         return FALSE;
  961     }
  962 
  963   *allow_refs_out = g_steal_pointer (&allow_refs);
  964   *deny_refs_out = g_steal_pointer (&deny_refs);
  965 
  966   return TRUE;
  967 }
  968 
  969 gboolean
  970 flatpak_filters_allow_ref (GRegex *allow_refs,
  971                            GRegex *deny_refs,
  972                            const char *ref)
  973 {
  974   if (deny_refs == NULL)
  975     return TRUE; /* All refs are allowed by default */
  976 
  977   if (!g_regex_match (deny_refs, ref, G_REGEX_MATCH_ANCHORED, NULL))
  978     return TRUE; /* Not denied */
  979 
  980   if (allow_refs &&  g_regex_match (allow_refs, ref, G_REGEX_MATCH_ANCHORED, NULL))
  981     return TRUE; /* Explicitly allowed */
  982 
  983   return FALSE;
  984 }
  985 
  986 char **
  987 flatpak_list_deployed_refs (const char   *type,
  988                             const char   *name_prefix,
  989                             const char   *arch,
  990                             const char   *branch,
  991                             GCancellable *cancellable,
  992                             GError      **error)
  993 {
  994   gchar **ret = NULL;
  995   g_autoptr(GPtrArray) names = NULL;
  996   g_autoptr(GHashTable) hash = NULL;
  997   g_autoptr(FlatpakDir) user_dir = NULL;
  998   g_autoptr(GPtrArray) system_dirs = NULL;
  999   int i;
 1000 
 1001   hash = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
 1002 
 1003   user_dir = flatpak_dir_get_user ();
 1004   system_dirs = flatpak_dir_get_system_list (cancellable, error);
 1005   if (system_dirs == NULL)
 1006     goto out;
 1007 
 1008   if (!flatpak_dir_collect_deployed_refs (user_dir, type, name_prefix,
 1009                                           arch, branch, hash, cancellable,
 1010                                           error))
 1011     goto out;
 1012 
 1013   for (i = 0; i < system_dirs->len; i++)
 1014     {
 1015       FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
 1016       if (!flatpak_dir_collect_deployed_refs (system_dir, type, name_prefix,
 1017                                               arch, branch, hash, cancellable,
 1018                                               error))
 1019         goto out;
 1020     }
 1021 
 1022   names = g_ptr_array_new ();
 1023 
 1024   GLNX_HASH_TABLE_FOREACH (hash, FlatpakDecomposed *, ref)
 1025     {
 1026       g_ptr_array_add (names, flatpak_decomposed_dup_id (ref));
 1027     }
 1028 
 1029   g_ptr_array_sort (names, flatpak_strcmp0_ptr);
 1030   g_ptr_array_add (names, NULL);
 1031 
 1032   ret = (char **) g_ptr_array_free (names, FALSE);
 1033   names = NULL;
 1034 
 1035 out:
 1036   return ret;
 1037 }
 1038 
 1039 char **
 1040 flatpak_list_unmaintained_refs (const char   *name_prefix,
 1041                                 const char   *arch,
 1042                                 const char   *branch,
 1043                                 GCancellable *cancellable,
 1044                                 GError      **error)
 1045 {
 1046   gchar **ret = NULL;
 1047   g_autoptr(GPtrArray) names = NULL;
 1048   g_autoptr(GHashTable) hash = NULL;
 1049   g_autoptr(FlatpakDir) user_dir = NULL;
 1050   const char *key;
 1051   GHashTableIter iter;
 1052   g_autoptr(GPtrArray) system_dirs = NULL;
 1053   int i;
 1054 
 1055   hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 1056 
 1057   user_dir = flatpak_dir_get_user ();
 1058 
 1059   if (!flatpak_dir_collect_unmaintained_refs (user_dir, name_prefix,
 1060                                               arch, branch, hash, cancellable,
 1061                                               error))
 1062     return NULL;
 1063 
 1064   system_dirs = flatpak_dir_get_system_list (cancellable, error);
 1065   if (system_dirs == NULL)
 1066     return NULL;
 1067 
 1068   for (i = 0; i < system_dirs->len; i++)
 1069     {
 1070       FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
 1071 
 1072       if (!flatpak_dir_collect_unmaintained_refs (system_dir, name_prefix,
 1073                                                   arch, branch, hash, cancellable,
 1074                                                   error))
 1075         return NULL;
 1076     }
 1077 
 1078   names = g_ptr_array_new ();
 1079   g_hash_table_iter_init (&iter, hash);
 1080   while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
 1081     g_ptr_array_add (names, g_strdup (key));
 1082 
 1083   g_ptr_array_sort (names, flatpak_strcmp0_ptr);
 1084   g_ptr_array_add (names, NULL);
 1085 
 1086   ret = (char **) g_ptr_array_free (names, FALSE);
 1087   names = NULL;
 1088 
 1089   return ret;
 1090 }
 1091 
 1092 GFile *
 1093 flatpak_find_deploy_dir_for_ref (FlatpakDecomposed *ref,
 1094                                  FlatpakDir       **dir_out,
 1095                                  GCancellable      *cancellable,
 1096                                  GError           **error)
 1097 {
 1098   g_autoptr(FlatpakDir) user_dir = NULL;
 1099   g_autoptr(GPtrArray) system_dirs = NULL;
 1100   FlatpakDir *dir = NULL;
 1101   g_autoptr(GFile) deploy = NULL;
 1102 
 1103   user_dir = flatpak_dir_get_user ();
 1104   system_dirs = flatpak_dir_get_system_list (cancellable, error);
 1105   if (system_dirs == NULL)
 1106     return NULL;
 1107 
 1108   dir = user_dir;
 1109   deploy = flatpak_dir_get_if_deployed (dir, ref, NULL, cancellable);
 1110   if (deploy == NULL)
 1111     {
 1112       int i;
 1113       for (i = 0; deploy == NULL && i < system_dirs->len; i++)
 1114         {
 1115           dir = g_ptr_array_index (system_dirs, i);
 1116           deploy = flatpak_dir_get_if_deployed (dir, ref, NULL, cancellable);
 1117           if (deploy != NULL)
 1118             break;
 1119         }
 1120     }
 1121 
 1122   if (deploy == NULL)
 1123     {
 1124       flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED, _("%s not installed"), flatpak_decomposed_get_ref (ref));
 1125       return NULL;
 1126     }
 1127 
 1128   if (dir_out)
 1129     *dir_out = g_object_ref (dir);
 1130   return g_steal_pointer (&deploy);
 1131 }
 1132 
 1133 GFile *
 1134 flatpak_find_files_dir_for_ref (FlatpakDecomposed *ref,
 1135                                 GCancellable      *cancellable,
 1136                                 GError           **error)
 1137 {
 1138   g_autoptr(GFile) deploy = NULL;
 1139 
 1140   deploy = flatpak_find_deploy_dir_for_ref (ref, NULL, cancellable, error);
 1141   if (deploy == NULL)
 1142     return NULL;
 1143 
 1144   return g_file_get_child (deploy, "files");
 1145 }
 1146 
 1147 GFile *
 1148 flatpak_find_unmaintained_extension_dir_if_exists (const char   *name,
 1149                                                    const char   *arch,
 1150                                                    const char   *branch,
 1151                                                    GCancellable *cancellable)
 1152 {
 1153   g_autoptr(FlatpakDir) user_dir = NULL;
 1154   g_autoptr(GFile) extension_dir = NULL;
 1155   g_autoptr(GError) local_error = NULL;
 1156 
 1157   user_dir = flatpak_dir_get_user ();
 1158 
 1159   extension_dir = flatpak_dir_get_unmaintained_extension_dir_if_exists (user_dir, name, arch, branch, cancellable);
 1160   if (extension_dir == NULL)
 1161     {
 1162       g_autoptr(GPtrArray) system_dirs = NULL;
 1163       int i;
 1164 
 1165       system_dirs = flatpak_dir_get_system_list (cancellable, &local_error);
 1166       if (system_dirs == NULL)
 1167         {
 1168           g_warning ("Could not get the system installations: %s", local_error->message);
 1169           return NULL;
 1170         }
 1171 
 1172       for (i = 0; i < system_dirs->len; i++)
 1173         {
 1174           FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
 1175           extension_dir = flatpak_dir_get_unmaintained_extension_dir_if_exists (system_dir, name, arch, branch, cancellable);
 1176           if (extension_dir != NULL)
 1177             break;
 1178         }
 1179     }
 1180 
 1181   if (extension_dir == NULL)
 1182     return NULL;
 1183 
 1184   return g_steal_pointer (&extension_dir);
 1185 }
 1186 
 1187 FlatpakDecomposed *
 1188 flatpak_find_current_ref (const char   *app_id,
 1189                           GCancellable *cancellable,
 1190                           GError      **error)
 1191 {
 1192   g_autoptr(FlatpakDecomposed) current_ref = NULL;
 1193   g_autoptr(FlatpakDir) user_dir = flatpak_dir_get_user ();
 1194   int i;
 1195 
 1196   current_ref = flatpak_dir_current_ref (user_dir, app_id, NULL);
 1197   if (current_ref == NULL)
 1198     {
 1199       g_autoptr(GPtrArray) system_dirs = NULL;
 1200 
 1201       system_dirs = flatpak_dir_get_system_list (cancellable, error);
 1202       if (system_dirs == NULL)
 1203         return FALSE;
 1204 
 1205       for (i = 0; i < system_dirs->len; i++)
 1206         {
 1207           FlatpakDir *dir = g_ptr_array_index (system_dirs, i);
 1208           current_ref = flatpak_dir_current_ref (dir, app_id, cancellable);
 1209           if (current_ref != NULL)
 1210             break;
 1211         }
 1212     }
 1213 
 1214   if (current_ref)
 1215     return g_steal_pointer (&current_ref);
 1216 
 1217   flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED, _("%s not installed"), app_id);
 1218   return NULL;
 1219 }
 1220 
 1221 FlatpakDeploy *
 1222 flatpak_find_deploy_for_ref_in (GPtrArray    *dirs,
 1223                                 const char   *ref_str,
 1224                                 const char   *commit,
 1225                                 GCancellable *cancellable,
 1226                                 GError      **error)
 1227 {
 1228   FlatpakDeploy *deploy = NULL;
 1229   int i;
 1230   g_autoptr(GError) my_error = NULL;
 1231 
 1232   g_autoptr(FlatpakDecomposed) ref = flatpak_decomposed_new_from_ref (ref_str, error);
 1233   if (ref == NULL)
 1234     return NULL;
 1235 
 1236   for (i = 0; deploy == NULL && i < dirs->len; i++)
 1237     {
 1238       FlatpakDir *dir = g_ptr_array_index (dirs, i);
 1239 
 1240       flatpak_log_dir_access (dir);
 1241       g_clear_error (&my_error);
 1242       deploy = flatpak_dir_load_deployed (dir, ref, commit, cancellable, &my_error);
 1243     }
 1244 
 1245   if (deploy == NULL)
 1246     g_propagate_error (error, g_steal_pointer (&my_error));
 1247 
 1248   return deploy;
 1249 }
 1250 
 1251 FlatpakDeploy *
 1252 flatpak_find_deploy_for_ref (const char   *ref,
 1253                              const char   *commit,
 1254                              FlatpakDir   *opt_user_dir,
 1255                              GCancellable *cancellable,
 1256                              GError      **error)
 1257 {
 1258   g_autoptr(GPtrArray) dirs = NULL;
 1259 
 1260   dirs = flatpak_dir_get_system_list (cancellable, error);
 1261   if (dirs == NULL)
 1262     return NULL;
 1263 
 1264   /* If an custom dir was passed, use that instead of the user dir.
 1265    * This is used when running apply-extra-data where if the target
 1266    * is a custom installation location the regular user one may not
 1267    * have the (possibly just installed in this transaction) runtime.
 1268    */
 1269   if (opt_user_dir)
 1270     g_ptr_array_insert (dirs, 0, g_object_ref (opt_user_dir));
 1271   else
 1272     g_ptr_array_insert (dirs, 0, flatpak_dir_get_user ());
 1273 
 1274   return flatpak_find_deploy_for_ref_in (dirs, ref, commit, cancellable, error);
 1275 }
 1276 
 1277 static gboolean
 1278 remove_dangling_symlinks (int           parent_fd,
 1279                           const char   *name,
 1280                           GCancellable *cancellable,
 1281                           GError      **error)
 1282 {
 1283   gboolean ret = FALSE;
 1284   struct dirent *dent;
 1285   g_auto(GLnxDirFdIterator) iter = { 0 };
 1286 
 1287   if (!glnx_dirfd_iterator_init_at (parent_fd, name, FALSE, &iter, error))
 1288     goto out;
 1289 
 1290   while (TRUE)
 1291     {
 1292       if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, cancellable, error))
 1293         goto out;
 1294 
 1295       if (dent == NULL)
 1296         break;
 1297 
 1298       if (dent->d_type == DT_DIR)
 1299         {
 1300           if (!remove_dangling_symlinks (iter.fd, dent->d_name, cancellable, error))
 1301             goto out;
 1302         }
 1303       else if (dent->d_type == DT_LNK)
 1304         {
 1305           struct stat stbuf;
 1306           if (fstatat (iter.fd, dent->d_name, &stbuf, 0) != 0 && errno == ENOENT)
 1307             {
 1308               if (unlinkat (iter.fd, dent->d_name, 0) != 0)
 1309                 {
 1310                   glnx_set_error_from_errno (error);
 1311                   goto out;
 1312                 }
 1313             }
 1314         }
 1315     }
 1316 
 1317   ret = TRUE;
 1318 out:
 1319 
 1320   return ret;
 1321 }
 1322 
 1323 gboolean
 1324 flatpak_remove_dangling_symlinks (GFile        *dir,
 1325                                   GCancellable *cancellable,
 1326                                   GError      **error)
 1327 {
 1328   gboolean ret = FALSE;
 1329 
 1330   /* The fd is closed by this call */
 1331   if (!remove_dangling_symlinks (AT_FDCWD, flatpak_file_get_path_cached (dir),
 1332                                  cancellable, error))
 1333     goto out;
 1334 
 1335   ret = TRUE;
 1336 
 1337 out:
 1338   return ret;
 1339 }
 1340 
 1341 /* This atomically replaces a symlink with a new value, removing the
 1342  * existing symlink target, if it exstis and is different from
 1343  * @target. This is atomic in the sense that we're guaranteed to
 1344  * remove any existing symlink target (once), independent of how many
 1345  * processes do the same operation in parallele. However, it is still
 1346  * possible that we remove the old and then fail to create the new
 1347  * symlink for some reason, ending up with neither the old or the new
 1348  * target. That is fine if the reason for the symlink is keeping a
 1349  * cache though.
 1350  */
 1351 gboolean
 1352 flatpak_switch_symlink_and_remove (const char *symlink_path,
 1353                                    const char *target,
 1354                                    GError    **error)
 1355 {
 1356   g_autofree char *symlink_dir = g_path_get_dirname (symlink_path);
 1357   int try;
 1358 
 1359   for (try = 0; try < 100; try++)
 1360     {
 1361       g_autofree char *tmp_path = NULL;
 1362       int fd;
 1363 
 1364       /* Try to atomically create the symlink */
 1365       if (TEMP_FAILURE_RETRY (symlink (target, symlink_path)) == 0)
 1366         return TRUE;
 1367 
 1368       if (errno != EEXIST)
 1369         {
 1370           /* Unexpected failure, bail */
 1371           glnx_set_error_from_errno (error);
 1372           return FALSE;
 1373         }
 1374 
 1375       /* The symlink existed, move it to a temporary name atomically, and remove target
 1376          if that succeeded. */
 1377       tmp_path = g_build_filename (symlink_dir, ".switched-symlink-XXXXXX", NULL);
 1378 
 1379       fd = g_mkstemp_full (tmp_path, O_RDWR, 0644);
 1380       if (fd == -1)
 1381         {
 1382           glnx_set_error_from_errno (error);
 1383           return FALSE;
 1384         }
 1385       close (fd);
 1386 
 1387       if (TEMP_FAILURE_RETRY (rename (symlink_path, tmp_path)) == 0)
 1388         {
 1389           /* The move succeeded, now we can remove the old target */
 1390           g_autofree char *old_target = flatpak_readlink (tmp_path, error);
 1391           if (old_target == NULL)
 1392             return FALSE;
 1393           if (strcmp (old_target, target) != 0) /* Don't remove old file if its the same as the new one */
 1394             {
 1395               g_autofree char *old_target_path = g_build_filename (symlink_dir, old_target, NULL);
 1396               unlink (old_target_path);
 1397             }
 1398         }
 1399       else if (errno != ENOENT)
 1400         {
 1401           glnx_set_error_from_errno (error);
 1402           unlink (tmp_path);
 1403           return -1;
 1404         }
 1405       unlink (tmp_path);
 1406 
 1407       /* An old target was removed, try again */
 1408     }
 1409 
 1410   return flatpak_fail (error, "flatpak_switch_symlink_and_remove looped too many times");
 1411 }
 1412 
 1413 gboolean
 1414 flatpak_argument_needs_quoting (const char *arg)
 1415 {
 1416   if (*arg == '\0')
 1417     return TRUE;
 1418 
 1419   while (*arg != 0)
 1420     {
 1421       char c = *arg;
 1422       if (!g_ascii_isalnum (c) &&
 1423           !(c == '-' || c == '/' || c == '~' ||
 1424             c == ':' || c == '.' || c == '_' ||
 1425             c == '=' || c == '@'))
 1426         return TRUE;
 1427       arg++;
 1428     }
 1429   return FALSE;
 1430 }
 1431 
 1432 char *
 1433 flatpak_quote_argv (const char *argv[],
 1434                     gssize      len)
 1435 {
 1436   GString *res = g_string_new ("");
 1437   int i;
 1438 
 1439   if (len == -1)
 1440     len = g_strv_length ((char **) argv);
 1441 
 1442   for (i = 0; i < len; i++)
 1443     {
 1444       if (i != 0)
 1445         g_string_append_c (res, ' ');
 1446 
 1447       if (flatpak_argument_needs_quoting (argv[i]))
 1448         {
 1449           g_autofree char *quoted = g_shell_quote (argv[i]);
 1450           g_string_append (res, quoted);
 1451         }
 1452       else
 1453         g_string_append (res, argv[i]);
 1454     }
 1455 
 1456   return g_string_free (res, FALSE);
 1457 }
 1458 
 1459 /* This is useful, because it handles escaped characters in uris, and ? arguments at the end of the uri */
 1460 gboolean
 1461 flatpak_file_arg_has_suffix (const char *arg, const char *suffix)
 1462 {
 1463   g_autoptr(GFile) file = g_file_new_for_commandline_arg (arg);
 1464   g_autofree char *basename = g_file_get_basename (file);
 1465 
 1466   return g_str_has_suffix (basename, suffix);
 1467 }
 1468 
 1469 GFile *
 1470 flatpak_build_file_va (GFile  *base,
 1471                        va_list args)
 1472 {
 1473   g_autoptr(GFile) res = g_object_ref (base);
 1474   const gchar *arg;
 1475 
 1476   while ((arg = va_arg (args, const gchar *)))
 1477     {
 1478       g_autoptr(GFile) child = g_file_resolve_relative_path (res, arg);
 1479       g_set_object (&res, child);
 1480     }
 1481 
 1482   return g_steal_pointer (&res);
 1483 }
 1484 
 1485 GFile *
 1486 flatpak_build_file (GFile *base, ...)
 1487 {
 1488   GFile *res;
 1489   va_list args;
 1490 
 1491   va_start (args, base);
 1492   res = flatpak_build_file_va (base, args);
 1493   va_end (args);
 1494 
 1495   return res;
 1496 }
 1497 
 1498 const char *
 1499 flatpak_file_get_path_cached (GFile *file)
 1500 {
 1501   const char *path;
 1502   static GQuark _file_path_quark = 0;
 1503 
 1504   if (G_UNLIKELY (_file_path_quark == 0))
 1505     _file_path_quark = g_quark_from_static_string ("flatpak-file-path");
 1506 
 1507   do
 1508     {
 1509       path = g_object_get_qdata ((GObject *) file, _file_path_quark);
 1510       if (path == NULL)
 1511         {
 1512           g_autofree char *new_path = NULL;
 1513           new_path = g_file_get_path (file);
 1514           if (new_path == NULL)
 1515             return NULL;
 1516 
 1517           if (g_object_replace_qdata ((GObject *) file, _file_path_quark,
 1518                                       NULL, new_path, g_free, NULL))
 1519             path = g_steal_pointer (&new_path);
 1520         }
 1521     }
 1522   while (path == NULL);
 1523 
 1524   return path;
 1525 }
 1526 
 1527 gboolean
 1528 flatpak_openat_noatime (int           dfd,
 1529                         const char   *name,
 1530                         int          *ret_fd,
 1531                         GCancellable *cancellable,
 1532                         GError      **error)
 1533 {
 1534   int fd;
 1535   int flags = O_RDONLY | O_CLOEXEC;
 1536 
 1537 #ifdef O_NOATIME
 1538   do
 1539     fd = openat (dfd, name, flags | O_NOATIME, 0);
 1540   while (G_UNLIKELY (fd == -1 && errno == EINTR));
 1541   /* Only the owner or superuser may use O_NOATIME; so we may get
 1542    * EPERM.  EINVAL may happen if the kernel is really old...
 1543    */
 1544   if (fd == -1 && (errno == EPERM || errno == EINVAL))
 1545 #endif
 1546   do
 1547     fd = openat (dfd, name, flags, 0);
 1548   while (G_UNLIKELY (fd == -1 && errno == EINTR));
 1549 
 1550   if (fd == -1)
 1551     {
 1552       glnx_set_error_from_errno (error);
 1553       return FALSE;
 1554     }
 1555   else
 1556     {
 1557       *ret_fd = fd;
 1558       return TRUE;
 1559     }
 1560 }
 1561 
 1562 gboolean
 1563 flatpak_cp_a (GFile         *src,
 1564               GFile         *dest,
 1565               FlatpakCpFlags flags,
 1566               GCancellable  *cancellable,
 1567               GError       **error)
 1568 {
 1569   gboolean ret = FALSE;
 1570   GFileEnumerator *enumerator = NULL;
 1571   GFileInfo *src_info = NULL;
 1572   GFile *dest_child = NULL;
 1573   int dest_dfd = -1;
 1574   gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0;
 1575   gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0;
 1576   gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0;
 1577   g_autoptr(GFileInfo) child_info = NULL;
 1578   GError *temp_error = NULL;
 1579   int r;
 1580 
 1581   enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
 1582                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
 1583                                           cancellable, error);
 1584   if (!enumerator)
 1585     goto out;
 1586 
 1587   src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
 1588                                      "time::modified,time::modified-usec,time::access,time::access-usec",
 1589                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
 1590                                 cancellable, error);
 1591   if (!src_info)
 1592     goto out;
 1593 
 1594   do
 1595     r = mkdir (flatpak_file_get_path_cached (dest), 0755);
 1596   while (G_UNLIKELY (r == -1 && errno == EINTR));
 1597   if (r == -1 &&
 1598       (!merge || errno != EEXIST))
 1599     {
 1600       glnx_set_error_from_errno (error);
 1601       goto out;
 1602     }
 1603 
 1604   if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dest), TRUE,
 1605                        &dest_dfd, error))
 1606     goto out;
 1607 
 1608   if (!no_chown)
 1609     {
 1610       do
 1611         r = fchown (dest_dfd,
 1612                     g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
 1613                     g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
 1614       while (G_UNLIKELY (r == -1 && errno == EINTR));
 1615       if (r == -1)
 1616         {
 1617           glnx_set_error_from_errno (error);
 1618           goto out;
 1619         }
 1620     }
 1621 
 1622   do
 1623     r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
 1624   while (G_UNLIKELY (r == -1 && errno == EINTR));
 1625 
 1626   if (dest_dfd != -1)
 1627     {
 1628       (void) close (dest_dfd);
 1629       dest_dfd = -1;
 1630     }
 1631 
 1632   while ((child_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error)))
 1633     {
 1634       const char *name = g_file_info_get_name (child_info);
 1635       g_autoptr(GFile) src_child = g_file_get_child (src, name);
 1636 
 1637       if (dest_child)
 1638         g_object_unref (dest_child);
 1639       dest_child = g_file_get_child (dest, name);
 1640 
 1641       if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
 1642         {
 1643           if (!flatpak_cp_a (src_child, dest_child, flags,
 1644                              cancellable, error))
 1645             goto out;
 1646         }
 1647       else
 1648         {
 1649           (void) unlink (flatpak_file_get_path_cached (dest_child));
 1650           GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
 1651           if (!no_chown)
 1652             copyflags |= G_FILE_COPY_ALL_METADATA;
 1653           if (move)
 1654             {
 1655               if (!g_file_move (src_child, dest_child, copyflags,
 1656                                 cancellable, NULL, NULL, error))
 1657                 goto out;
 1658             }
 1659           else
 1660             {
 1661               if (!g_file_copy (src_child, dest_child, copyflags,
 1662                                 cancellable, NULL, NULL, error))
 1663                 goto out;
 1664             }
 1665         }
 1666 
 1667       g_clear_object (&child_info);
 1668     }
 1669 
 1670   if (temp_error != NULL)
 1671     {
 1672       g_propagate_error (error, temp_error);
 1673       goto out;
 1674     }
 1675 
 1676   if (move &&
 1677       !g_file_delete (src, NULL, error))
 1678     goto out;
 1679 
 1680   ret = TRUE;
 1681 out:
 1682   if (dest_dfd != -1)
 1683     (void) close (dest_dfd);
 1684   g_clear_object (&src_info);
 1685   g_clear_object (&enumerator);
 1686   g_clear_object (&dest_child);
 1687   return ret;
 1688 }
 1689 
 1690 static gboolean
 1691 _flatpak_canonicalize_permissions (int         parent_dfd,
 1692                                    const char *rel_path,
 1693                                    gboolean    toplevel,
 1694                                    int         uid,
 1695                                    int         gid,
 1696                                    GError    **error)
 1697 {
 1698   struct stat stbuf;
 1699   gboolean res = TRUE;
 1700 
 1701   /* Note, in order to not leave non-canonical things around in case
 1702    * of error, this continues after errors, but returns the first
 1703    * error. */
 1704 
 1705   if (TEMP_FAILURE_RETRY (fstatat (parent_dfd, rel_path, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
 1706     {
 1707       glnx_set_error_from_errno (error);
 1708       return FALSE;
 1709     }
 1710 
 1711   if ((uid != -1 && uid != stbuf.st_uid) || (gid != -1 && gid != stbuf.st_gid))
 1712     {
 1713       if (TEMP_FAILURE_RETRY (fchownat (parent_dfd, rel_path, uid, gid, AT_SYMLINK_NOFOLLOW)) != 0)
 1714         {
 1715           glnx_set_error_from_errno (error);
 1716           return FALSE;
 1717         }
 1718 
 1719       /* Re-read st_mode for new owner */
 1720       if (TEMP_FAILURE_RETRY (fstatat (parent_dfd, rel_path, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
 1721         {
 1722           glnx_set_error_from_errno (error);
 1723           return FALSE;
 1724         }
 1725     }
 1726 
 1727   if (S_ISDIR (stbuf.st_mode))
 1728     {
 1729       g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
 1730 
 1731       /* For the toplevel we set to 0700 so we can modify it, but not
 1732          expose any non-canonical files to any other user, then we set
 1733          it to 0755 afterwards. */
 1734       if (fchmodat (parent_dfd, rel_path, toplevel ? 0700 : 0755, 0) != 0)
 1735         {
 1736           glnx_set_error_from_errno (error);
 1737           error = NULL;
 1738           res = FALSE;
 1739         }
 1740 
 1741       if (glnx_dirfd_iterator_init_at (parent_dfd, rel_path, FALSE, &dfd_iter, NULL))
 1742         {
 1743           while (TRUE)
 1744             {
 1745               struct dirent *dent;
 1746 
 1747               if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, NULL) || dent == NULL)
 1748                 break;
 1749 
 1750               if (!_flatpak_canonicalize_permissions (dfd_iter.fd, dent->d_name, FALSE, uid, gid, error))
 1751                 {
 1752                   error = NULL;
 1753                   res = FALSE;
 1754                 }
 1755             }
 1756         }
 1757 
 1758       if (toplevel &&
 1759           fchmodat (parent_dfd, rel_path, 0755, 0) != 0)
 1760         {
 1761           glnx_set_error_from_errno (error);
 1762           error = NULL;
 1763           res = FALSE;
 1764         }
 1765 
 1766       return res;
 1767     }
 1768   else if (S_ISREG (stbuf.st_mode))
 1769     {
 1770       mode_t mode;
 1771 
 1772       /* If use can execute, make executable by all */
 1773       if (stbuf.st_mode & S_IXUSR)
 1774         mode = 0755;
 1775       else /* otherwise executable by none */
 1776         mode = 0644;
 1777 
 1778       if (fchmodat (parent_dfd, rel_path, mode, 0) != 0)
 1779         {
 1780           glnx_set_error_from_errno (error);
 1781           res = FALSE;
 1782         }
 1783     }
 1784   else if (S_ISLNK (stbuf.st_mode))
 1785     {
 1786       /* symlinks have no permissions */
 1787     }
 1788   else
 1789     {
 1790       /* some weird non-canonical type, lets delete it */
 1791       if (unlinkat (parent_dfd, rel_path, 0) != 0)
 1792         {
 1793           glnx_set_error_from_errno (error);
 1794           res = FALSE;
 1795         }
 1796     }
 1797 
 1798   return res;
 1799 }
 1800 
 1801 /* Canonicalizes files to the same permissions as bare-user-only checkouts */
 1802 gboolean
 1803 flatpak_canonicalize_permissions (int         parent_dfd,
 1804                                   const char *rel_path,
 1805                                   int         uid,
 1806                                   int         gid,
 1807                                   GError    **error)
 1808 {
 1809   return _flatpak_canonicalize_permissions (parent_dfd, rel_path, TRUE, uid, gid, error);
 1810 }
 1811 
 1812 /* Make a directory, and its parent. Don't error if it already exists.
 1813  * If you want a failure mode with EEXIST, use g_file_make_directory_with_parents. */
 1814 gboolean
 1815 flatpak_mkdir_p (GFile        *dir,
 1816                  GCancellable *cancellable,
 1817                  GError      **error)
 1818 {
 1819   return glnx_shutil_mkdir_p_at (AT_FDCWD,
 1820                                  flatpak_file_get_path_cached (dir),
 1821                                  0777,
 1822                                  cancellable,
 1823                                  error);
 1824 }
 1825 
 1826 gboolean
 1827 flatpak_rm_rf (GFile        *dir,
 1828                GCancellable *cancellable,
 1829                GError      **error)
 1830 {
 1831   return glnx_shutil_rm_rf_at (AT_FDCWD,
 1832                                flatpak_file_get_path_cached (dir),
 1833                                cancellable, error);
 1834 }
 1835 
 1836 gboolean
 1837 flatpak_file_rename (GFile        *from,
 1838                      GFile        *to,
 1839                      GCancellable *cancellable,
 1840                      GError      **error)
 1841 {
 1842   if (g_cancellable_set_error_if_cancelled (cancellable, error))
 1843     return FALSE;
 1844 
 1845   if (rename (flatpak_file_get_path_cached (from),
 1846               flatpak_file_get_path_cached (to)) < 0)
 1847     {
 1848       glnx_set_error_from_errno (error);
 1849       return FALSE;
 1850     }
 1851 
 1852   return TRUE;
 1853 }
 1854 
 1855 /* If memfd_create() is available, generate a sealed memfd with contents of
 1856  * @str. Otherwise use an O_TMPFILE @tmpf in anonymous mode, write @str to
 1857  * @tmpf, and lseek() back to the start. See also similar uses in e.g.
 1858  * rpm-ostree for running dracut.
 1859  */
 1860 gboolean
 1861 flatpak_buffer_to_sealed_memfd_or_tmpfile (GLnxTmpfile *tmpf,
 1862                                            const char  *name,
 1863                                            const char  *str,
 1864                                            size_t       len,
 1865                                            GError     **error)
 1866 {
 1867   if (len == -1)
 1868     len = strlen (str);
 1869   glnx_autofd int memfd = memfd_create (name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
 1870   int fd; /* Unowned */
 1871   if (memfd != -1)
 1872     {
 1873       fd = memfd;
 1874     }
 1875   else
 1876     {
 1877       /* We use an anonymous fd (i.e. O_EXCL) since we don't want
 1878        * the target container to potentially be able to re-link it.
 1879        */
 1880       if (!G_IN_SET (errno, ENOSYS, EOPNOTSUPP))
 1881         return glnx_throw_errno_prefix (error, "memfd_create");
 1882       if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, tmpf, error))
 1883         return FALSE;
 1884       fd = tmpf->fd;
 1885     }
 1886   if (ftruncate (fd, len) < 0)
 1887     return glnx_throw_errno_prefix (error, "ftruncate");
 1888   if (glnx_loop_write (fd, str, len) < 0)
 1889     return glnx_throw_errno_prefix (error, "write");
 1890   if (lseek (fd, 0, SEEK_SET) < 0)
 1891     return glnx_throw_errno_prefix (error, "lseek");
 1892   if (memfd != -1)
 1893     {
 1894       /* Valgrind doesn't currently handle G_ADD_SEALS, so lets not seal when debugging... */
 1895       if ((!RUNNING_ON_VALGRIND) &&
 1896           fcntl (memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) < 0)
 1897         return glnx_throw_errno_prefix (error, "fcntl(F_ADD_SEALS)");
 1898       /* The other values can stay default */
 1899       tmpf->fd = glnx_steal_fd (&memfd);
 1900       tmpf->initialized = TRUE;
 1901     }
 1902   return TRUE;
 1903 }
 1904 
 1905 gboolean
 1906 flatpak_open_in_tmpdir_at (int             tmpdir_fd,
 1907                            int             mode,
 1908                            char           *tmpl,
 1909                            GOutputStream **out_stream,
 1910                            GCancellable   *cancellable,
 1911                            GError        **error)
 1912 {
 1913   const int max_attempts = 128;
 1914   int i;
 1915   int fd;
 1916 
 1917   /* 128 attempts seems reasonable... */
 1918   for (i = 0; i < max_attempts; i++)
 1919     {
 1920       glnx_gen_temp_name (tmpl);
 1921 
 1922       do
 1923         fd = openat (tmpdir_fd, tmpl, O_WRONLY | O_CREAT | O_EXCL, mode);
 1924       while (fd == -1 && errno == EINTR);
 1925       if (fd < 0 && errno != EEXIST)
 1926         {
 1927           glnx_set_error_from_errno (error);
 1928           return FALSE;
 1929         }
 1930       else if (fd != -1)
 1931         break;
 1932     }
 1933   if (i == max_attempts)
 1934     {
 1935       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
 1936                    "Exhausted attempts to open temporary file");
 1937       return FALSE;
 1938     }
 1939 
 1940   if (out_stream)
 1941     *out_stream = g_unix_output_stream_new (fd, TRUE);
 1942   else
 1943     (void) close (fd);
 1944 
 1945   return TRUE;
 1946 }
 1947 
 1948 gboolean
 1949 flatpak_bytes_save (GFile        *dest,
 1950                     GBytes       *bytes,
 1951                     GCancellable *cancellable,
 1952                     GError      **error)
 1953 {
 1954   g_autoptr(GOutputStream) out = NULL;
 1955 
 1956   out = (GOutputStream *) g_file_replace (dest, NULL, FALSE,
 1957                                           G_FILE_CREATE_REPLACE_DESTINATION,
 1958                                           cancellable, error);
 1959   if (out == NULL)
 1960     return FALSE;
 1961 
 1962   if (!g_output_stream_write_all (out,
 1963                                   g_bytes_get_data (bytes, NULL),
 1964                                   g_bytes_get_size (bytes),
 1965                                   NULL,
 1966                                   cancellable,
 1967                                   error))
 1968     return FALSE;
 1969 
 1970   if (!g_output_stream_close (out, cancellable, error))
 1971     return FALSE;
 1972 
 1973   return TRUE;
 1974 }
 1975 
 1976 gboolean
 1977 flatpak_variant_save (GFile        *dest,
 1978                       GVariant     *variant,
 1979                       GCancellable *cancellable,
 1980                       GError      **error)
 1981 {
 1982   g_autoptr(GOutputStream) out = NULL;
 1983   gsize bytes_written;
 1984 
 1985   out = (GOutputStream *) g_file_replace (dest, NULL, FALSE,
 1986                                           G_FILE_CREATE_REPLACE_DESTINATION,
 1987                                           cancellable, error);
 1988   if (out == NULL)
 1989     return FALSE;
 1990 
 1991   if (!g_output_stream_write_all (out,
 1992                                   g_variant_get_data (variant),
 1993                                   g_variant_get_size (variant),
 1994                                   &bytes_written,
 1995                                   cancellable,
 1996                                   error))
 1997     return FALSE;
 1998 
 1999   if (!g_output_stream_close (out, cancellable, error))
 2000     return FALSE;
 2001 
 2002   return TRUE;
 2003 }
 2004 
 2005 /* This special cases the ref lookup which by doing a
 2006    bsearch since the array is sorted */
 2007 gboolean
 2008 flatpak_var_ref_map_lookup_ref (VarRefMapRef   ref_map,
 2009                                 const char    *ref,
 2010                                 VarRefInfoRef *out_info)
 2011 {
 2012   gsize imax, imin;
 2013   gsize imid;
 2014   gsize n;
 2015 
 2016   g_return_val_if_fail (out_info != NULL, FALSE);
 2017 
 2018   n = var_ref_map_get_length (ref_map);
 2019   if (n == 0)
 2020     return FALSE;
 2021 
 2022   imax = n - 1;
 2023   imin = 0;
 2024   while (imax >= imin)
 2025     {
 2026       VarRefMapEntryRef entry;
 2027       const char *cur;
 2028       int cmp;
 2029 
 2030       imid = (imin + imax) / 2;
 2031 
 2032       entry = var_ref_map_get_at (ref_map, imid);
 2033       cur = var_ref_map_entry_get_ref (entry);
 2034 
 2035       cmp = strcmp (cur, ref);
 2036       if (cmp < 0)
 2037         {
 2038           imin = imid + 1;
 2039         }
 2040       else if (cmp > 0)
 2041         {
 2042           if (imid == 0)
 2043             break;
 2044           imax = imid - 1;
 2045         }
 2046       else
 2047         {
 2048           *out_info = var_ref_map_entry_get_info (entry);
 2049           return TRUE;
 2050         }
 2051     }
 2052 
 2053   return FALSE;
 2054 }
 2055 
 2056 /* Find the list of refs which belong to the given @collection_id in @summary.
 2057  * If @collection_id is %NULL, the main refs list from the summary will be
 2058  * returned. If @collection_id doesn’t match any collection IDs in the summary
 2059  * file, %FALSE will be returned. */
 2060 gboolean
 2061 flatpak_summary_find_ref_map (VarSummaryRef summary,
 2062                               const char *collection_id,
 2063                               VarRefMapRef *refs_out)
 2064 {
 2065   VarMetadataRef metadata = var_summary_get_metadata (summary);
 2066   const char *summary_collection_id;
 2067 
 2068   summary_collection_id = var_metadata_lookup_string (metadata, "ostree.summary.collection-id", NULL);
 2069 
 2070   if (collection_id == NULL || g_strcmp0 (collection_id, summary_collection_id) == 0)
 2071     {
 2072       if (refs_out)
 2073         *refs_out = var_summary_get_ref_map (summary);
 2074       return TRUE;
 2075     }
 2076   else if (collection_id != NULL)
 2077     {
 2078       VarVariantRef collection_map_v;
 2079       if (var_metadata_lookup (metadata, "ostree.summary.collection-map", NULL, &collection_map_v))
 2080         {
 2081           VarCollectionMapRef collection_map = var_collection_map_from_variant (collection_map_v);
 2082           return var_collection_map_lookup (collection_map, collection_id, NULL, refs_out);
 2083         }
 2084     }
 2085 
 2086   return FALSE;
 2087 }
 2088 
 2089 /* This matches all refs from @collection_id that have ref, followed by '.'  as prefix */
 2090 GPtrArray *
 2091 flatpak_summary_match_subrefs (GVariant          *summary_v,
 2092                                const char        *collection_id,
 2093                                FlatpakDecomposed *ref)
 2094 {
 2095   GPtrArray *res = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
 2096   gsize n, i;
 2097   g_autofree char *parts_prefix = NULL;
 2098   g_autofree char *ref_prefix = NULL;
 2099   g_autofree char *ref_suffix = NULL;
 2100   VarSummaryRef summary;
 2101   VarRefMapRef ref_map;
 2102 
 2103   summary = var_summary_from_gvariant (summary_v);
 2104 
 2105   /* Work out which refs list to use, based on the @collection_id. */
 2106   if (flatpak_summary_find_ref_map (summary, collection_id, &ref_map))
 2107     {
 2108       /* Match against the refs. */
 2109       g_autofree char *id = flatpak_decomposed_dup_id (ref);
 2110       g_autofree char *arch = flatpak_decomposed_dup_arch (ref);
 2111       g_autofree char *branch = flatpak_decomposed_dup_branch (ref);
 2112       parts_prefix = g_strconcat (id, ".", NULL);
 2113 
 2114       ref_prefix = g_strconcat (flatpak_decomposed_get_kind_str (ref), "/", NULL);
 2115       ref_suffix = g_strconcat ("/", arch, "/", branch, NULL);
 2116 
 2117       n = var_ref_map_get_length (ref_map);
 2118       for (i = 0; i < n; i++)
 2119         {
 2120           VarRefMapEntryRef entry = var_ref_map_get_at (ref_map, i);
 2121           const char *cur;
 2122           const char *id_start;
 2123           const char *id_suffix;
 2124           const char *id_end;
 2125 
 2126           cur = var_ref_map_entry_get_ref (entry);
 2127 
 2128           /* Must match type */
 2129           if (!g_str_has_prefix (cur, ref_prefix))
 2130             continue;
 2131 
 2132           /* Must match arch & branch */
 2133           if (!g_str_has_suffix (cur, ref_suffix))
 2134             continue;
 2135 
 2136           id_start = strchr (cur, '/');
 2137           if (id_start == NULL)
 2138             continue;
 2139           id_start += 1;
 2140 
 2141           id_end = strchr (id_start, '/');
 2142           if (id_end == NULL)
 2143             continue;
 2144 
 2145           /* But only prefix of id */
 2146           if (!g_str_has_prefix (id_start, parts_prefix))
 2147             continue;
 2148 
 2149           /* And no dots (we want to install prefix.$ID, but not prefix.$ID.Sources) */
 2150           id_suffix = id_start + strlen (parts_prefix);
 2151           if (memchr (id_suffix, '.', id_end - id_suffix) != NULL)
 2152             continue;
 2153 
 2154           FlatpakDecomposed *d = flatpak_decomposed_new_from_ref (cur, NULL);
 2155           if (d)
 2156             g_ptr_array_add (res, d);
 2157         }
 2158     }
 2159 
 2160   return g_steal_pointer (&res);
 2161 }
 2162 
 2163 gboolean
 2164 flatpak_summary_lookup_ref (GVariant      *summary_v,
 2165                             const char    *collection_id,
 2166                             const char    *ref,
 2167                             char         **out_checksum,
 2168                             VarRefInfoRef *out_info)
 2169 {
 2170   VarSummaryRef summary;
 2171   VarRefMapRef ref_map;
 2172   VarRefInfoRef info;
 2173   const guchar *checksum_bytes;
 2174   gsize checksum_bytes_len;
 2175 
 2176   summary = var_summary_from_gvariant (summary_v);
 2177 
 2178   /* Work out which refs list to use, based on the @collection_id. */
 2179   if (!flatpak_summary_find_ref_map (summary, collection_id, &ref_map))
 2180     return FALSE;
 2181 
 2182   if (!flatpak_var_ref_map_lookup_ref (ref_map, ref, &info))
 2183     return FALSE;
 2184 
 2185   checksum_bytes = var_ref_info_peek_checksum (info, &checksum_bytes_len);
 2186   if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
 2187     return FALSE;
 2188 
 2189   if (out_checksum)
 2190     *out_checksum = ostree_checksum_from_bytes (checksum_bytes);
 2191 
 2192   if (out_info)
 2193     *out_info = info;
 2194 
 2195   return TRUE;
 2196 }
 2197 
 2198 GKeyFile *
 2199 flatpak_parse_repofile (const char   *remote_name,
 2200                         gboolean      from_ref,
 2201                         GKeyFile     *keyfile,
 2202                         GBytes      **gpg_data_out,
 2203                         GCancellable *cancellable,
 2204                         GError      **error)
 2205 {
 2206   g_autoptr(GBytes) gpg_data = NULL;
 2207   g_autofree char *uri = NULL;
 2208   g_autofree char *title = NULL;
 2209   g_autofree char *gpg_key = NULL;
 2210   g_autofree char *collection_id = NULL;
 2211   g_autofree char *default_branch = NULL;
 2212   g_autofree char *comment = NULL;
 2213   g_autofree char *description = NULL;
 2214   g_autofree char *icon = NULL;
 2215   g_autofree char *homepage = NULL;
 2216   g_autofree char *filter = NULL;
 2217   g_autofree char *subset = NULL;
 2218   g_autofree char *authenticator_name = NULL;
 2219   gboolean nodeps;
 2220   const char *source_group;
 2221   g_autofree char *version = NULL;
 2222 
 2223   if (from_ref)
 2224     source_group = FLATPAK_REF_GROUP;
 2225   else
 2226     source_group = FLATPAK_REPO_GROUP;
 2227 
 2228   GKeyFile *config = g_key_file_new ();
 2229   g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote_name);
 2230 
 2231   if (!g_key_file_has_group (keyfile, source_group))
 2232     {
 2233       flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid %s: Missing group ‘%s’"),
 2234                           from_ref ? ".flatpakref" : ".flatpakrepo", source_group);
 2235       return NULL;
 2236     }
 2237 
 2238   uri = g_key_file_get_string (keyfile, source_group,
 2239                                FLATPAK_REPO_URL_KEY, NULL);
 2240   if (uri == NULL)
 2241     {
 2242       flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid %s: Missing key ‘%s’"),
 2243                           from_ref ? ".flatpakref" : ".flatpakrepo", FLATPAK_REPO_URL_KEY);
 2244       return NULL;
 2245     }
 2246 
 2247   version = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2248                                    FLATPAK_REPO_VERSION_KEY, NULL);
 2249   if (version != NULL && strcmp (version, "1") != 0)
 2250     {
 2251       flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
 2252                           _("Invalid version %s, only 1 supported"), version);
 2253       return NULL;
 2254     }
 2255 
 2256   g_key_file_set_string (config, group, "url", uri);
 2257 
 2258   subset = g_key_file_get_locale_string (keyfile, source_group,
 2259                                          FLATPAK_REPO_SUBSET_KEY, NULL, NULL);
 2260   if (subset != NULL)
 2261     g_key_file_set_string (config, group, "xa.subset", subset);
 2262 
 2263   title = g_key_file_get_locale_string (keyfile, source_group,
 2264                                         FLATPAK_REPO_TITLE_KEY, NULL, NULL);
 2265   if (title != NULL)
 2266     g_key_file_set_string (config, group, "xa.title", title);
 2267 
 2268   default_branch = g_key_file_get_locale_string (keyfile, source_group,
 2269                                                  FLATPAK_REPO_DEFAULT_BRANCH_KEY, NULL, NULL);
 2270   if (default_branch != NULL)
 2271     g_key_file_set_string (config, group, "xa.default-branch", default_branch);
 2272 
 2273   nodeps = g_key_file_get_boolean (keyfile, source_group,
 2274                                    FLATPAK_REPO_NODEPS_KEY, NULL);
 2275   if (nodeps)
 2276     g_key_file_set_boolean (config, group, "xa.nodeps", TRUE);
 2277 
 2278   gpg_key = g_key_file_get_string (keyfile, source_group,
 2279                                    FLATPAK_REPO_GPGKEY_KEY, NULL);
 2280   if (gpg_key != NULL)
 2281     {
 2282       guchar *decoded;
 2283       gsize decoded_len;
 2284 
 2285       gpg_key = g_strstrip (gpg_key);
 2286       decoded = g_base64_decode (gpg_key, &decoded_len);
 2287       if (decoded_len < 10) /* Check some minimal size so we don't get crap */
 2288         {
 2289           g_free (decoded);
 2290           flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid gpg key"));
 2291           return NULL;
 2292         }
 2293 
 2294       gpg_data = g_bytes_new_take (decoded, decoded_len);
 2295       g_key_file_set_boolean (config, group, "gpg-verify", TRUE);
 2296     }
 2297   else
 2298     {
 2299       g_key_file_set_boolean (config, group, "gpg-verify", FALSE);
 2300     }
 2301 
 2302   collection_id = g_key_file_get_string (keyfile, source_group,
 2303                                          FLATPAK_REPO_DEPLOY_COLLECTION_ID_KEY, NULL);
 2304   if (collection_id != NULL && *collection_id == '\0')
 2305     g_clear_pointer (&collection_id, g_free);
 2306   if (collection_id == NULL)
 2307     collection_id = g_key_file_get_string (keyfile, source_group,
 2308                                            FLATPAK_REPO_COLLECTION_ID_KEY, NULL);
 2309   if (collection_id != NULL && *collection_id == '\0')
 2310     g_clear_pointer (&collection_id, g_free);
 2311   if (collection_id != NULL)
 2312     {
 2313       if (gpg_key == NULL)
 2314         {
 2315           flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ID requires GPG key to be provided"));
 2316           return NULL;
 2317         }
 2318 
 2319       g_key_file_set_string (config, group, "collection-id", collection_id);
 2320     }
 2321 
 2322   g_key_file_set_boolean (config, group, "gpg-verify-summary",
 2323                           (gpg_key != NULL));
 2324 
 2325   authenticator_name = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2326                                               FLATPAK_REPO_AUTHENTICATOR_NAME_KEY, NULL);
 2327   if (authenticator_name)
 2328     g_key_file_set_string (config, group, "xa.authenticator-name", authenticator_name);
 2329 
 2330   if (g_key_file_has_key (keyfile, FLATPAK_REPO_GROUP, FLATPAK_REPO_AUTHENTICATOR_INSTALL_KEY, NULL))
 2331     {
 2332       gboolean authenticator_install = g_key_file_get_boolean (keyfile, FLATPAK_REPO_GROUP,
 2333                                                                FLATPAK_REPO_AUTHENTICATOR_INSTALL_KEY, NULL);
 2334       g_key_file_set_boolean (config, group, "xa.authenticator-install", authenticator_install);
 2335     }
 2336 
 2337   comment = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2338                                    FLATPAK_REPO_COMMENT_KEY, NULL);
 2339   if (comment)
 2340     g_key_file_set_string (config, group, "xa.comment", comment);
 2341 
 2342   description = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2343                                        FLATPAK_REPO_DESCRIPTION_KEY, NULL);
 2344   if (description)
 2345     g_key_file_set_string (config, group, "xa.description", description);
 2346 
 2347   icon = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2348                                 FLATPAK_REPO_ICON_KEY, NULL);
 2349   if (icon)
 2350     g_key_file_set_string (config, group, "xa.icon", icon);
 2351 
 2352   homepage  = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2353                                      FLATPAK_REPO_HOMEPAGE_KEY, NULL);
 2354   if (homepage)
 2355     g_key_file_set_string (config, group, "xa.homepage", homepage);
 2356 
 2357   filter = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
 2358                                    FLATPAK_REPO_FILTER_KEY, NULL);
 2359   if (filter)
 2360     g_key_file_set_string (config, group, "xa.filter", filter);
 2361   else
 2362     g_key_file_set_string (config, group, "xa.filter", ""); /* Default to override any pre-existing filters */
 2363 
 2364   *gpg_data_out = g_steal_pointer (&gpg_data);
 2365 
 2366   return g_steal_pointer (&config);
 2367 }
 2368 
 2369 gboolean
 2370 flatpak_repo_set_title (OstreeRepo *repo,
 2371                         const char *title,
 2372                         GError    **error)
 2373 {
 2374   g_autoptr(GKeyFile) config = NULL;
 2375 
 2376   config = ostree_repo_copy_config (repo);
 2377 
 2378   if (title)
 2379     g_key_file_set_string (config, "flatpak", "title", title);
 2380   else
 2381     g_key_file_remove_key (config, "flatpak", "title", NULL);
 2382 
 2383   if (!ostree_repo_write_config (repo, config, error))
 2384     return FALSE;
 2385 
 2386   return TRUE;
 2387 }
 2388 
 2389 gboolean
 2390 flatpak_repo_set_comment (OstreeRepo *repo,
 2391                           const char *comment,
 2392                           GError    **error)
 2393 {
 2394   g_autoptr(GKeyFile) config = NULL;
 2395 
 2396   config = ostree_repo_copy_config (repo);
 2397 
 2398   if (comment)
 2399     g_key_file_set_string (config, "flatpak", "comment", comment);
 2400   else
 2401     g_key_file_remove_key (config, "flatpak", "comment", NULL);
 2402 
 2403   if (!ostree_repo_write_config (repo, config, error))
 2404     return FALSE;
 2405 
 2406   return TRUE;
 2407 }
 2408 
 2409 gboolean
 2410 flatpak_repo_set_description (OstreeRepo *repo,
 2411                               const char *description,
 2412                               GError    **error)
 2413 {
 2414   g_autoptr(GKeyFile) config = NULL;
 2415 
 2416   config = ostree_repo_copy_config (repo);
 2417 
 2418   if (description)
 2419     g_key_file_set_string (config, "flatpak", "description", description);
 2420   else
 2421     g_key_file_remove_key (config, "flatpak", "description", NULL);
 2422 
 2423   if (!ostree_repo_write_config (repo, config, error))
 2424     return FALSE;
 2425 
 2426   return TRUE;
 2427 }
 2428 
 2429 
 2430 gboolean
 2431 flatpak_repo_set_icon (OstreeRepo *repo,
 2432                        const char *icon,
 2433                        GError    **error)
 2434 {
 2435   g_autoptr(GKeyFile) config = NULL;
 2436 
 2437   config = ostree_repo_copy_config (repo);
 2438 
 2439   if (icon)
 2440     g_key_file_set_string (config, "flatpak", "icon", icon);
 2441   else
 2442     g_key_file_remove_key (config, "flatpak", "icon", NULL);
 2443 
 2444   if (!ostree_repo_write_config (repo, config, error))
 2445     return FALSE;
 2446 
 2447   return TRUE;
 2448 }
 2449 
 2450 gboolean
 2451 flatpak_repo_set_homepage (OstreeRepo *repo,
 2452                            const char *homepage,
 2453                            GError    **error)
 2454 {
 2455   g_autoptr(GKeyFile) config = NULL;
 2456 
 2457   config = ostree_repo_copy_config (repo);
 2458 
 2459   if (homepage)
 2460     g_key_file_set_string (config, "flatpak", "homepage", homepage);
 2461   else
 2462     g_key_file_remove_key (config, "flatpak", "homepage", NULL);
 2463 
 2464   if (!ostree_repo_write_config (repo, config, error))
 2465     return FALSE;
 2466 
 2467   return TRUE;
 2468 }
 2469 
 2470 gboolean
 2471 flatpak_repo_set_redirect_url (OstreeRepo *repo,
 2472                                const char *redirect_url,
 2473                                GError    **error)
 2474 {
 2475   g_autoptr(GKeyFile) config = NULL;
 2476 
 2477   config = ostree_repo_copy_config (repo);
 2478 
 2479   if (redirect_url)
 2480     g_key_file_set_string (config, "flatpak", "redirect-url", redirect_url);
 2481   else
 2482     g_key_file_remove_key (config, "flatpak", "redirect-url", NULL);
 2483 
 2484   if (!ostree_repo_write_config (repo, config, error))
 2485     return FALSE;
 2486 
 2487   return TRUE;
 2488 }
 2489 
 2490 gboolean
 2491 flatpak_repo_set_authenticator_name (OstreeRepo *repo,
 2492                                      const char *authenticator_name,
 2493                                      GError    **error)
 2494 {
 2495   g_autoptr(GKeyFile) config = NULL;
 2496 
 2497   config = ostree_repo_copy_config (repo);
 2498 
 2499   if (authenticator_name)
 2500     g_key_file_set_string (config, "flatpak", "authenticator-name", authenticator_name);
 2501   else
 2502     g_key_file_remove_key (config, "flatpak", "authenticator-name", NULL);
 2503 
 2504   if (!ostree_repo_write_config (repo, config, error))
 2505     return FALSE;
 2506 
 2507   return TRUE;
 2508 }
 2509 
 2510 gboolean
 2511 flatpak_repo_set_authenticator_install (OstreeRepo *repo,
 2512                                         gboolean authenticator_install,
 2513                                         GError    **error)
 2514 {
 2515   g_autoptr(GKeyFile) config = NULL;
 2516 
 2517   config = ostree_repo_copy_config (repo);
 2518 
 2519   g_key_file_set_boolean (config, "flatpak", "authenticator-install", authenticator_install);
 2520 
 2521   if (!ostree_repo_write_config (repo, config, error))
 2522     return FALSE;
 2523 
 2524   return TRUE;
 2525 }
 2526 
 2527 gboolean
 2528 flatpak_repo_set_authenticator_option (OstreeRepo *repo,
 2529                                        const char *key,
 2530                                        const char *value,
 2531                                        GError    **error)
 2532 {
 2533   g_autoptr(GKeyFile) config = NULL;
 2534   g_autofree char *full_key = g_strdup_printf ("authenticator-options.%s", key);
 2535 
 2536   config = ostree_repo_copy_config (repo);
 2537 
 2538   if (value)
 2539     g_key_file_set_string (config, "flatpak", full_key, value);
 2540   else
 2541     g_key_file_remove_key (config, "flatpak", full_key, NULL);
 2542 
 2543   if (!ostree_repo_write_config (repo, config, error))
 2544     return FALSE;
 2545 
 2546   return TRUE;
 2547 }
 2548 
 2549 gboolean
 2550 flatpak_repo_set_deploy_collection_id (OstreeRepo *repo,
 2551                                        gboolean    deploy_collection_id,
 2552                                        GError    **error)
 2553 {
 2554   g_autoptr(GKeyFile) config = NULL;
 2555 
 2556   config = ostree_repo_copy_config (repo);
 2557   g_key_file_set_boolean (config, "flatpak", "deploy-collection-id", deploy_collection_id);
 2558   return ostree_repo_write_config (repo, config, error);
 2559 }
 2560 
 2561 gboolean
 2562 flatpak_repo_set_deploy_sideload_collection_id (OstreeRepo *repo,
 2563                                            gboolean    deploy_collection_id,
 2564                                            GError    **error)
 2565 {
 2566   g_autoptr(GKeyFile) config = NULL;
 2567 
 2568   config = ostree_repo_copy_config (repo);
 2569   g_key_file_set_boolean (config, "flatpak", "deploy-sideload-collection-id", deploy_collection_id);
 2570   return ostree_repo_write_config (repo, config, error);
 2571 }
 2572 
 2573 gboolean
 2574 flatpak_repo_set_gpg_keys (OstreeRepo *repo,
 2575                            GBytes     *bytes,
 2576                            GError    **error)
 2577 {
 2578   g_autoptr(GKeyFile) config = NULL;
 2579   g_autofree char *value_base64 = NULL;
 2580 
 2581   config = ostree_repo_copy_config (repo);
 2582 
 2583   value_base64 = g_base64_encode (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
 2584 
 2585   g_key_file_set_string (config, "flatpak", "gpg-keys", value_base64);
 2586 
 2587   if (!ostree_repo_write_config (repo, config, error))
 2588     return FALSE;
 2589 
 2590   return TRUE;
 2591 }
 2592 
 2593 gboolean
 2594 flatpak_repo_set_default_branch (OstreeRepo *repo,
 2595                                  const char *branch,
 2596                                  GError    **error)
 2597 {
 2598   g_autoptr(GKeyFile) config = NULL;
 2599 
 2600   config = ostree_repo_copy_config (repo);
 2601 
 2602   if (branch)
 2603     g_key_file_set_string (config, "flatpak", "default-branch", branch);
 2604   else
 2605     g_key_file_remove_key (config, "flatpak", "default-branch", NULL);
 2606 
 2607   if (!ostree_repo_write_config (repo, config, error))
 2608     return FALSE;
 2609 
 2610   return TRUE;
 2611 }
 2612 
 2613 gboolean
 2614 flatpak_repo_set_collection_id (OstreeRepo *repo,
 2615                                 const char *collection_id,
 2616                                 GError    **error)
 2617 {
 2618   g_autoptr(GKeyFile) config = NULL;
 2619 
 2620   if (!ostree_repo_set_collection_id (repo, collection_id, error))
 2621     return FALSE;
 2622 
 2623   config = ostree_repo_copy_config (repo);
 2624   if (!ostree_repo_write_config (repo, config, error))
 2625     return FALSE;
 2626 
 2627   return TRUE;
 2628 }
 2629 
 2630 gboolean
 2631 flatpak_repo_set_summary_history_length (OstreeRepo *repo,
 2632                                          guint       length,
 2633                                          GError    **error)
 2634 {
 2635   g_autoptr(GKeyFile) config = NULL;
 2636 
 2637   config = ostree_repo_copy_config (repo);
 2638 
 2639   if (length)
 2640     g_key_file_set_integer (config, "flatpak", "summary-history-length", length);
 2641   else
 2642     g_key_file_remove_key (config, "flatpak", "summary-history-length", NULL);
 2643 
 2644   if (!ostree_repo_write_config (repo, config, error))
 2645     return FALSE;
 2646 
 2647   return TRUE;
 2648 }
 2649 
 2650 guint
 2651 flatpak_repo_get_summary_history_length (OstreeRepo *repo)
 2652 {
 2653   GKeyFile *config = ostree_repo_get_config (repo);
 2654   int length;
 2655 
 2656   length = g_key_file_get_integer (config, "flatpak", "sumary-history-length", NULL);
 2657 
 2658   if (length <= 0)
 2659     return FLATPAK_SUMMARY_HISTORY_LENGTH_DEFAULT;
 2660 
 2661   return length;
 2662 }
 2663 
 2664 GVariant *
 2665 flatpak_commit_get_extra_data_sources (GVariant *commitv,
 2666                                        GError  **error)
 2667 {
 2668   g_autoptr(GVariant) commit_metadata = NULL;
 2669   g_autoptr(GVariant) extra_data_sources = NULL;
 2670 
 2671   commit_metadata = g_variant_get_child_value (commitv, 0);
 2672   extra_data_sources = g_variant_lookup_value (commit_metadata,
 2673                                                "xa.extra-data-sources",
 2674                                                G_VARIANT_TYPE ("a(ayttays)"));
 2675 
 2676   if (extra_data_sources == NULL)
 2677     {
 2678       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
 2679                    _("No extra data sources"));
 2680       return NULL;
 2681     }
 2682 
 2683   return g_steal_pointer (&extra_data_sources);
 2684 }
 2685 
 2686 
 2687 GVariant *
 2688 flatpak_repo_get_extra_data_sources (OstreeRepo   *repo,
 2689                                      const char   *rev,
 2690                                      GCancellable *cancellable,
 2691                                      GError      **error)
 2692 {
 2693   g_autoptr(GVariant) commitv = NULL;
 2694 
 2695   if (!ostree_repo_load_variant (repo,
 2696                                  OSTREE_OBJECT_TYPE_COMMIT,
 2697                                  rev, &commitv, error))
 2698     return NULL;
 2699 
 2700   return flatpak_commit_get_extra_data_sources (commitv, error);
 2701 }
 2702 
 2703 void
 2704 flatpak_repo_parse_extra_data_sources (GVariant      *extra_data_sources,
 2705                                        int            index,
 2706                                        const char   **name,
 2707                                        guint64       *download_size,
 2708                                        guint64       *installed_size,
 2709                                        const guchar **sha256,
 2710                                        const char   **uri)
 2711 {
 2712   g_autoptr(GVariant) sha256_v = NULL;
 2713   g_variant_get_child (extra_data_sources, index, "(^aytt@ay&s)",
 2714                        name,
 2715                        download_size,
 2716                        installed_size,
 2717                        &sha256_v,
 2718                        uri);
 2719 
 2720   if (download_size)
 2721     *download_size = GUINT64_FROM_BE (*download_size);
 2722 
 2723   if (installed_size)
 2724     *installed_size = GUINT64_FROM_BE (*installed_size);
 2725 
 2726   if (sha256)
 2727     *sha256 = ostree_checksum_bytes_peek (sha256_v);
 2728 }
 2729 
 2730 #define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \
 2731                                    "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev")
 2732 
 2733 static gboolean
 2734 _flatpak_repo_collect_sizes (OstreeRepo   *repo,
 2735                              GFile        *file,
 2736                              GFileInfo    *file_info,
 2737                              guint64      *installed_size,
 2738                              guint64      *download_size,
 2739                              GCancellable *cancellable,
 2740                              GError      **error)
 2741 {
 2742   g_autoptr(GFileEnumerator) dir_enum = NULL;
 2743   GFileInfo *child_info_tmp;
 2744   g_autoptr(GError) temp_error = NULL;
 2745 
 2746   if (file_info != NULL && g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
 2747     {
 2748       const char *checksum = ostree_repo_file_get_checksum (OSTREE_REPO_FILE (file));
 2749       guint64 obj_size;
 2750       guint64 file_size = g_file_info_get_size (file_info);
 2751 
 2752       if (installed_size)
 2753         *installed_size += ((file_size + 511) / 512) * 512;
 2754 
 2755       if (download_size)
 2756         {
 2757           g_autoptr(GInputStream) input = NULL;
 2758           GInputStream *base_input;
 2759           g_autoptr(GError) local_error = NULL;
 2760 
 2761           if (!ostree_repo_query_object_storage_size (repo,
 2762                                                       OSTREE_OBJECT_TYPE_FILE, checksum,
 2763                                                       &obj_size, cancellable, &local_error))
 2764             {
 2765               int fd;
 2766               struct stat stbuf;
 2767 
 2768               /* Ostree does not look at the staging directory when querying storage
 2769                  size, so may return a NOT_FOUND error here. We work around this
 2770                  by loading the object and walking back until we find the original
 2771                  fd which we can fstat(). */
 2772               if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
 2773                 return FALSE;
 2774 
 2775               if (!ostree_repo_load_file (repo, checksum,  &input, NULL, NULL, NULL, error))
 2776                 return FALSE;
 2777 
 2778               base_input = input;
 2779               while (G_IS_FILTER_INPUT_STREAM (base_input))
 2780                 base_input = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (base_input));
 2781 
 2782               if (!G_IS_UNIX_INPUT_STREAM (base_input))
 2783                 return flatpak_fail (error, "Unable to find size of commit %s, not an unix stream", checksum);
 2784 
 2785               fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (base_input));
 2786 
 2787               if (fstat (fd, &stbuf) != 0)
 2788                 return glnx_throw_errno_prefix (error, "Can't find commit size: ");
 2789 
 2790               obj_size = stbuf.st_size;
 2791             }
 2792 
 2793           *download_size += obj_size;
 2794         }
 2795     }
 2796 
 2797   if (file_info == NULL || g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
 2798     {
 2799       dir_enum = g_file_enumerate_children (file, OSTREE_GIO_FAST_QUERYINFO,
 2800                                             G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
 2801                                             cancellable, error);
 2802       if (!dir_enum)
 2803         return FALSE;
 2804 
 2805 
 2806       while ((child_info_tmp = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
 2807         {
 2808           g_autoptr(GFileInfo) child_info = child_info_tmp;
 2809           const char *name = g_file_info_get_name (child_info);
 2810           g_autoptr(GFile) child = g_file_get_child (file, name);
 2811 
 2812           if (!_flatpak_repo_collect_sizes (repo, child, child_info, installed_size, download_size, cancellable, error))
 2813             return FALSE;
 2814         }
 2815     }
 2816 
 2817   return TRUE;
 2818 }
 2819 
 2820 gboolean
 2821 flatpak_repo_collect_sizes (OstreeRepo   *repo,
 2822                             GFile        *root,
 2823                             guint64      *installed_size,
 2824                             guint64      *download_size,
 2825                             GCancellable *cancellable,
 2826                             GError      **error)
 2827 {
 2828   /* Initialize the sums */
 2829   if (installed_size)
 2830     *installed_size = 0;
 2831   if (download_size)
 2832     *download_size = 0;
 2833   return _flatpak_repo_collect_sizes (repo, root, NULL, installed_size, download_size, cancellable, error);
 2834 }
 2835 
 2836 
 2837 static void
 2838 flatpak_repo_collect_extra_data_sizes (OstreeRepo *repo,
 2839                                        const char *rev,
 2840                                        guint64    *installed_size,
 2841                                        guint64    *download_size)
 2842 {
 2843   g_autoptr(GVariant) extra_data_sources = NULL;
 2844   gsize n_extra_data;
 2845   int i;
 2846 
 2847   extra_data_sources = flatpak_repo_get_extra_data_sources (repo, rev, NULL, NULL);
 2848   if (extra_data_sources == NULL)
 2849     return;
 2850 
 2851   n_extra_data = g_variant_n_children (extra_data_sources);
 2852   if (n_extra_data == 0)
 2853     return;
 2854 
 2855   for (i = 0; i < n_extra_data; i++)
 2856     {
 2857       guint64 extra_download_size;
 2858       guint64 extra_installed_size;
 2859 
 2860       flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
 2861                                              NULL,
 2862                                              &extra_download_size,
 2863                                              &extra_installed_size,
 2864                                              NULL, NULL);
 2865       if (installed_size)
 2866         *installed_size += extra_installed_size;
 2867       if (download_size)
 2868         *download_size += extra_download_size;
 2869     }
 2870 }
 2871 
 2872 /* Loads the old compat summary file from a local repo */
 2873 GVariant *
 2874 flatpak_repo_load_summary (OstreeRepo *repo,
 2875                            GError    **error)
 2876 {
 2877   glnx_autofd int fd = -1;
 2878   g_autoptr(GMappedFile) mfile = NULL;
 2879   g_autoptr(GBytes) bytes = NULL;
 2880 
 2881   fd = openat (ostree_repo_get_dfd (repo), "summary", O_RDONLY | O_CLOEXEC);
 2882   if (fd < 0)
 2883     {
 2884       glnx_set_error_from_errno (error);
 2885       return NULL;
 2886     }
 2887 
 2888   mfile = g_mapped_file_new_from_fd (fd, FALSE, error);
 2889   if (!mfile)
 2890     return NULL;
 2891 
 2892   bytes = g_mapped_file_get_bytes (mfile);
 2893 
 2894   return g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, bytes, TRUE));
 2895 }
 2896 
 2897 GVariant *
 2898 flatpak_repo_load_summary_index (OstreeRepo *repo,
 2899                                  GError    **error)
 2900 {
 2901   glnx_autofd int fd = -1;
 2902   g_autoptr(GMappedFile) mfile = NULL;
 2903   g_autoptr(GBytes) bytes = NULL;
 2904 
 2905   fd = openat (ostree_repo_get_dfd (repo), "summary.idx", O_RDONLY | O_CLOEXEC);
 2906   if (fd < 0)
 2907     {
 2908       glnx_set_error_from_errno (error);
 2909       return NULL;
 2910     }
 2911 
 2912   mfile = g_mapped_file_new_from_fd (fd, FALSE, error);
 2913   if (!mfile)
 2914     return NULL;
 2915 
 2916   bytes = g_mapped_file_get_bytes (mfile);
 2917 
 2918   return g_variant_ref_sink (g_variant_new_from_bytes (FLATPAK_SUMMARY_INDEX_GVARIANT_FORMAT, bytes, TRUE));
 2919 }
 2920 
 2921 static gboolean
 2922 flatpak_repo_save_compat_summary (OstreeRepo   *repo,
 2923                                   GVariant     *summary,
 2924                                   time_t       *out_old_sig_mtime,
 2925                                   GCancellable *cancellable,
 2926                                   GError      **error)
 2927 {
 2928   int repo_dfd = ostree_repo_get_dfd (repo);
 2929   struct stat stbuf;
 2930   time_t old_sig_mtime = 0;
 2931   GLnxFileReplaceFlags flags;
 2932 
 2933   flags = GLNX_FILE_REPLACE_INCREASING_MTIME;
 2934   if (ostree_repo_get_disable_fsync (repo))
 2935     flags |= GLNX_FILE_REPLACE_NODATASYNC;
 2936   else
 2937     flags |= GLNX_FILE_REPLACE_DATASYNC_NEW;
 2938 
 2939   if (!glnx_file_replace_contents_at (repo_dfd, "summary",
 2940                                       g_variant_get_data (summary),
 2941                                       g_variant_get_size (summary),
 2942                                       flags,
 2943                                       cancellable, error))
 2944     return FALSE;
 2945 
 2946   if (fstatat (repo_dfd, "summary.sig", &stbuf, AT_SYMLINK_NOFOLLOW) == 0)
 2947     old_sig_mtime = stbuf.st_mtime;
 2948 
 2949   if (unlinkat (repo_dfd, "summary.sig", 0) != 0 &&
 2950       G_UNLIKELY (errno != ENOENT))
 2951     {
 2952       glnx_set_error_from_errno (error);
 2953       return FALSE;
 2954     }
 2955 
 2956   *out_old_sig_mtime = old_sig_mtime;
 2957   return TRUE;
 2958 }
 2959 
 2960 static gboolean
 2961 flatpak_repo_save_summary_index (OstreeRepo   *repo,
 2962                                  GVariant     *index,
 2963                                  const char   *index_digest,
 2964                                  GBytes       *index_sig,
 2965                                  GCancellable *cancellable,
 2966                                  GError      **error)
 2967 {
 2968   int repo_dfd = ostree_repo_get_dfd (repo);
 2969   GLnxFileReplaceFlags  flags;
 2970 
 2971   if (index == NULL)
 2972     {
 2973       if (unlinkat (repo_dfd, "summary.idx", 0) != 0 &&
 2974           G_UNLIKELY (errno != ENOENT))
 2975         {
 2976           glnx_set_error_from_errno (error);
 2977           return FALSE;
 2978         }
 2979       if (unlinkat (repo_dfd, "summary.idx.sig", 0) != 0 &&
 2980           G_UNLIKELY (errno != ENOENT))
 2981         {
 2982           glnx_set_error_from_errno (error);
 2983           return FALSE;
 2984         }
 2985 
 2986       return TRUE;
 2987     }
 2988 
 2989   flags = GLNX_FILE_REPLACE_INCREASING_MTIME;
 2990   if (ostree_repo_get_disable_fsync (repo))
 2991     flags |= GLNX_FILE_REPLACE_NODATASYNC;
 2992   else
 2993     flags |= GLNX_FILE_REPLACE_DATASYNC_NEW;
 2994 
 2995   if (index_sig)
 2996     {
 2997       g_autofree char *path = g_strconcat ("summaries/", index_digest, ".idx.sig", NULL);
 2998 
 2999       if (!glnx_shutil_mkdir_p_at (repo_dfd, "summaries",
 3000                                    0775, cancellable, error))
 3001         return FALSE;
 3002 
 3003       if (!glnx_file_replace_contents_at (repo_dfd, path,
 3004                                           g_bytes_get_data (index_sig, NULL),
 3005                                           g_bytes_get_size (index_sig),
 3006                                           flags,
 3007                                           cancellable, error))
 3008         return FALSE;
 3009     }
 3010 
 3011   if (!glnx_file_replace_contents_at (repo_dfd, "summary.idx",
 3012                                       g_variant_get_data (index),
 3013                                       g_variant_get_size (index),
 3014                                       flags,
 3015                                       cancellable, error))
 3016     return FALSE;
 3017 
 3018   /* Update the non-indexed summary.idx.sig file that was introduced in 1.9.1 but
 3019    * was made unnecessary in 1.9.3. Lets keep it for a while until everyone updates
 3020    */
 3021   if (index_sig)
 3022     {
 3023       if (!glnx_file_replace_contents_at (repo_dfd, "summary.idx.sig",
 3024                                           g_bytes_get_data (index_sig, NULL),
 3025                                           g_bytes_get_size (index_sig),
 3026                                           flags,
 3027                                           cancellable, error))
 3028         return FALSE;
 3029     }
 3030   else
 3031     {
 3032       if (unlinkat (repo_dfd, "summary.idx.sig", 0) != 0 &&
 3033           G_UNLIKELY (errno != ENOENT))
 3034         {
 3035           glnx_set_error_from_errno (error);
 3036           return FALSE;
 3037         }
 3038     }
 3039 
 3040   return TRUE;
 3041 }
 3042 
 3043 GVariant *
 3044 flatpak_repo_load_digested_summary (OstreeRepo *repo,
 3045                                    const char *digest,
 3046                                    GError    **error)
 3047 {
 3048   glnx_autofd int fd = -1;
 3049   g_autoptr(GMappedFile) mfile = NULL;
 3050   g_autoptr(GBytes) bytes = NULL;
 3051   g_autoptr(GBytes) compressed_bytes = NULL;
 3052   g_autofree char *path = NULL;
 3053   g_autofree char *filename = NULL;
 3054 
 3055   filename = g_strconcat (digest, ".gz", NULL);
 3056   path = g_build_filename ("summaries", filename, NULL);
 3057 
 3058   fd = openat (ostree_repo_get_dfd (repo), path, O_RDONLY | O_CLOEXEC);
 3059   if (fd < 0)
 3060     {
 3061       glnx_set_error_from_errno (error);
 3062       return NULL;
 3063     }
 3064 
 3065   mfile = g_mapped_file_new_from_fd (fd, FALSE, error);
 3066   if (!mfile)
 3067     return NULL;
 3068 
 3069   compressed_bytes = g_mapped_file_get_bytes (mfile);
 3070   bytes = flatpak_zlib_decompress_bytes (compressed_bytes, error);
 3071   if (bytes == NULL)
 3072     return NULL;
 3073 
 3074   return g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, bytes, TRUE));
 3075 }
 3076 
 3077 static char *
 3078 flatpak_repo_save_digested_summary (OstreeRepo   *repo,
 3079                                     const char   *name,
 3080                                     GVariant     *summary,
 3081                                     GCancellable *cancellable,
 3082                                     GError      **error)
 3083 {
 3084   int repo_dfd = ostree_repo_get_dfd (repo);
 3085   g_autofree char *digest = NULL;
 3086   g_autofree char *filename = NULL;
 3087   g_autofree char *path = NULL;
 3088   g_autoptr(GBytes) data = NULL;
 3089   g_autoptr(GBytes) compressed_data = NULL;
 3090   struct stat stbuf;
 3091 
 3092   if (!glnx_shutil_mkdir_p_at (repo_dfd, "summaries",
 3093                                0775,
 3094                                cancellable,
 3095                                error))
 3096     return NULL;
 3097 
 3098   digest = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
 3099                                         g_variant_get_data (summary),
 3100                                         g_variant_get_size (summary));
 3101   filename = g_strconcat (digest, ".gz", NULL);
 3102 
 3103   path = g_build_filename ("summaries", filename, NULL);
 3104 
 3105   /* Check for pre-existing (non-truncated) copy and avoid re-writing it */
 3106   if (fstatat (repo_dfd, path, &stbuf, 0) == 0 &&
 3107       stbuf.st_size != 0)
 3108     {
 3109       g_debug ("Reusing digested summary at %s for %s", path, name);
 3110       return g_steal_pointer (&digest);
 3111     }
 3112 
 3113   data = g_variant_get_data_as_bytes (summary);
 3114   compressed_data = flatpak_zlib_compress_bytes (data, -1, error);
 3115   if (compressed_data == NULL)
 3116     return NULL;
 3117 
 3118   if (!glnx_file_replace_contents_at (repo_dfd, path,
 3119                                       g_bytes_get_data (compressed_data, NULL),
 3120                                       g_bytes_get_size (compressed_data),
 3121                                       ostree_repo_get_disable_fsync (repo) ? GLNX_FILE_REPLACE_NODATASYNC : GLNX_FILE_REPLACE_DATASYNC_NEW,
 3122                                       cancellable, error))
 3123     return NULL;
 3124 
 3125   g_debug ("Wrote digested summary at %s for %s", path, name);
 3126   return g_steal_pointer (&digest);
 3127 }
 3128 
 3129 static gboolean
 3130 flatpak_repo_save_digested_summary_delta (OstreeRepo   *repo,
 3131                                           const char   *from_digest,
 3132                                           const char   *to_digest,
 3133                                           GBytes       *delta,
 3134                                           GCancellable *cancellable,
 3135                                           GError      **error)
 3136 {
 3137   int repo_dfd = ostree_repo_get_dfd (repo);
 3138   g_autofree char *path = NULL;
 3139   g_autofree char *filename = g_strconcat (from_digest, "-", to_digest, ".delta", NULL);
 3140   struct stat stbuf;
 3141 
 3142   if (!glnx_shutil_mkdir_p_at (repo_dfd, "summaries",
 3143                                0775,
 3144                                cancellable,
 3145                                error))
 3146     return FALSE;
 3147 
 3148   path = g_build_filename ("summaries", filename, NULL);
 3149 
 3150   /* Check for pre-existing copy of same size and avoid re-writing it */
 3151   if (fstatat (repo_dfd, path, &stbuf, 0) == 0 &&
 3152       stbuf.st_size == g_bytes_get_size (delta))
 3153     {
 3154       g_debug ("Reusing digested summary-diff for %s", filename);
 3155       return TRUE;
 3156     }
 3157 
 3158   if (!glnx_file_replace_contents_at (repo_dfd, path,
 3159                                       g_bytes_get_data (delta, NULL),
 3160                                       g_bytes_get_size (delta),
 3161                                       ostree_repo_get_disable_fsync (repo) ? GLNX_FILE_REPLACE_NODATASYNC : GLNX_FILE_REPLACE_DATASYNC_NEW,
 3162                                       cancellable, error))
 3163     return FALSE;
 3164 
 3165   g_debug ("Wrote digested summary delta at %s", path);
 3166   return TRUE;
 3167 }
 3168 
 3169 
 3170 static gboolean
 3171 is_flatpak_ref (const char *ref)
 3172 {
 3173   return
 3174     g_str_has_prefix (ref, "appstream/") ||
 3175     g_str_has_prefix (ref, "appstream2/") ||
 3176     g_str_has_prefix (ref, "app/") ||
 3177     g_str_has_prefix (ref, "runtime/");
 3178 }
 3179 
 3180 typedef struct
 3181 {
 3182   guint64    installed_size;
 3183   guint64    download_size;
 3184   char      *metadata_contents;
 3185   GPtrArray *subsets;
 3186   GVariant  *sparse_data;
 3187   gsize      commit_size;
 3188   guint64    commit_timestamp;
 3189 } CommitData;
 3190 
 3191 static void
 3192 commit_data_free (gpointer data)
 3193 {
 3194   CommitData *rev_data = data;
 3195 
 3196   if (rev_data->subsets)
 3197     g_ptr_array_unref (rev_data->subsets);
 3198   g_free (rev_data->metadata_contents);
 3199   if (rev_data->sparse_data)
 3200     g_variant_unref (rev_data->sparse_data);
 3201   g_free (rev_data);
 3202 }
 3203 
 3204 G_DEFINE_AUTOPTR_CLEANUP_FUNC (CommitData, commit_data_free);
 3205 
 3206 static GHashTable *
 3207 commit_data_cache_new (void)
 3208 {
 3209   return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, commit_data_free);
 3210 }
 3211 
 3212 static GHashTable *
 3213 populate_commit_data_cache (OstreeRepo *repo,
 3214                             GVariant *index_v)
 3215 {
 3216 
 3217   VarSummaryIndexRef index = var_summary_index_from_gvariant (index_v);
 3218   VarMetadataRef index_metadata = var_summary_index_get_metadata (index);
 3219   VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index);
 3220   gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);
 3221   guint32 cache_version;
 3222   g_autoptr(GHashTable) commit_data_cache = commit_data_cache_new ();
 3223 
 3224   cache_version = GUINT32_FROM_LE (var_metadata_lookup_uint32 (index_metadata, "xa.cache-version", 0));
 3225   if (cache_version < FLATPAK_XA_CACHE_VERSION)
 3226     {
 3227       /* Need to re-index to get all data */
 3228       g_debug ("Old summary cache version %d, not using cache", cache_version);
 3229       return NULL;
 3230     }
 3231 
 3232   for (gsize i = 0; i < n_subsummaries; i++)
 3233     {
 3234       VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
 3235       const char *name = var_summary_index_subsummaries_entry_get_key (entry);
 3236       const char *s;
 3237       g_autofree char *subset = NULL;
 3238       VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry);
 3239       gsize checksum_bytes_len;
 3240       const guchar *checksum_bytes;
 3241       g_autofree char *digest = NULL;
 3242       g_autoptr(GVariant) summary_v = NULL;
 3243       VarSummaryRef summary;
 3244       VarRefMapRef ref_map;
 3245       gsize n_refs;
 3246 
 3247       checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len);
 3248       if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
 3249         {
 3250           g_debug ("Invalid checksum for digested summary, not using cache");
 3251           return NULL;
 3252         }
 3253       digest = ostree_checksum_from_bytes (checksum_bytes);
 3254 
 3255       s = strrchr (name, '-');
 3256       if (s != NULL)
 3257         subset = g_strndup (name, s - name);
 3258       else
 3259         subset = g_strdup ("");
 3260 
 3261       summary_v = flatpak_repo_load_digested_summary (repo, digest, NULL);
 3262       if (summary_v == NULL)
 3263         {
 3264           g_debug ("Failed to load digested summary %s, not using cache", digest);
 3265           return NULL;
 3266         }
 3267 
 3268       /* Note that all summaries refered to by the index is in new format */
 3269       summary = var_summary_from_gvariant (summary_v);
 3270       ref_map = var_summary_get_ref_map (summary);
 3271       n_refs = var_ref_map_get_length (ref_map);
 3272       for (gsize j = 0; j < n_refs; j++)
 3273         {
 3274           VarRefMapEntryRef e = var_ref_map_get_at (ref_map, j);
 3275           const char *ref = var_ref_map_entry_get_ref (e);
 3276           VarRefInfoRef info = var_ref_map_entry_get_info (e);
 3277           VarMetadataRef commit_metadata = var_ref_info_get_metadata (info);
 3278           guint64 commit_size = var_ref_info_get_commit_size (info);
 3279           const guchar *commit_bytes;
 3280           gsize commit_bytes_len;
 3281           g_autofree char *rev = NULL;
 3282           CommitData *rev_data;
 3283           VarVariantRef xa_data_v;
 3284           VarCacheDataRef xa_data;
 3285 
 3286           if (!is_flatpak_ref (ref))
 3287             continue;
 3288 
 3289           commit_bytes = var_ref_info_peek_checksum (info, &commit_bytes_len);
 3290           if (G_UNLIKELY (commit_bytes_len != OSTREE_SHA256_DIGEST_LEN))
 3291             continue;
 3292 
 3293           if (!var_metadata_lookup (commit_metadata, "xa.data", NULL, &xa_data_v) ||
 3294               !var_variant_is_type (xa_data_v, G_VARIANT_TYPE ("(tts)")))
 3295             {
 3296               g_debug ("Missing xa.data for ref %s, not using cache", ref);
 3297               return NULL;
 3298             }
 3299 
 3300           xa_data = var_cache_data_from_variant (xa_data_v);
 3301 
 3302           rev = ostree_checksum_from_bytes (commit_bytes);
 3303           rev_data = g_hash_table_lookup (commit_data_cache, rev);
 3304           if (rev_data == NULL)
 3305             {
 3306               g_auto(GVariantBuilder) sparse_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
 3307               g_variant_builder_init (&sparse_builder, G_VARIANT_TYPE_VARDICT);
 3308               gboolean has_sparse = FALSE;
 3309 
 3310               rev_data = g_new0 (CommitData, 1);
 3311               rev_data->installed_size = var_cache_data_get_installed_size (xa_data);
 3312               rev_data->download_size = var_cache_data_get_download_size (xa_data);
 3313               rev_data->metadata_contents = g_strdup (var_cache_data_get_metadata (xa_data));
 3314               rev_data->commit_size = commit_size;
 3315               rev_data->commit_timestamp = GUINT64_FROM_BE (var_metadata_lookup_uint64 (commit_metadata, OSTREE_COMMIT_TIMESTAMP2, 0));
 3316 
 3317               /* Get sparse data */
 3318               gsize len = var_metadata_get_length (commit_metadata);
 3319               for (gsize k = 0; k < len; k++)
 3320                 {
 3321                   VarMetadataEntryRef m = var_metadata_get_at (commit_metadata, k);
 3322                   const char *m_key = var_metadata_entry_get_key (m);
 3323                   if (!g_str_has_prefix (m_key, "ot.") &&
 3324                       !g_str_has_prefix (m_key, "ostree.") &&
 3325                       strcmp (m_key, "xa.data") != 0)
 3326                     {
 3327                       VarVariantRef v = var_metadata_entry_get_value (m);
 3328                       GVariant *vv = var_variant_dup_to_gvariant (v);
 3329                       g_variant_builder_add (&sparse_builder, "{sv}", m_key, g_variant_get_child_value (vv, 0));
 3330                       has_sparse = TRUE;
 3331                     }
 3332                 }
 3333 
 3334               if (has_sparse)
 3335                 rev_data->sparse_data = g_variant_ref_sink (g_variant_builder_end (&sparse_builder));
 3336 
 3337               g_hash_table_insert (commit_data_cache, g_strdup (rev), (CommitData *)rev_data);
 3338             }
 3339 
 3340           if (*subset != 0)
 3341             {
 3342               if (rev_data->subsets == NULL)
 3343                 rev_data->subsets = g_ptr_array_new_with_free_func (g_free);
 3344 
 3345               if (!flatpak_g_ptr_array_contains_string (rev_data->subsets, subset))
 3346                 g_ptr_array_add (rev_data->subsets, g_strdup (subset));
 3347             }
 3348         }
 3349     }
 3350 
 3351   return g_steal_pointer (&commit_data_cache);
 3352 }
 3353 
 3354 static CommitData *
 3355 read_commit_data (OstreeRepo   *repo,
 3356                   const char   *ref,
 3357                   const char   *rev,
 3358                   GCancellable *cancellable,
 3359                   GError      **error)
 3360 {
 3361   g_autoptr(GFile) root = NULL;
 3362   g_autoptr(GFile) metadata = NULL;
 3363   guint64 installed_size = 0;
 3364   guint64 download_size = 0;
 3365   g_autofree char *metadata_contents = NULL;
 3366   g_autofree char *commit = NULL;
 3367   g_autoptr(GVariant) commit_v = NULL;
 3368   g_autoptr(GVariant) commit_metadata = NULL;
 3369   g_autoptr(GPtrArray) subsets = NULL;
 3370   CommitData *rev_data;
 3371   const char *eol = NULL;
 3372   const char *eol_rebase = NULL;
 3373   int token_type = -1;
 3374   g_autoptr(GVariant) extra_data_sources = NULL;
 3375   guint32 n_extra_data = 0;
 3376   guint64 total_extra_data_download_size = 0;
 3377   g_autoptr(GVariantIter) subsets_iter = NULL;
 3378 
 3379   if (!ostree_repo_read_commit (repo, rev, &root, &commit, NULL, error))
 3380     return NULL;
 3381 
 3382   if (!ostree_repo_load_commit (repo, commit, &commit_v, NULL, error))
 3383     return NULL;
 3384 
 3385   commit_metadata = g_variant_get_child_value (commit_v, 0);
 3386   if (!g_variant_lookup (commit_metadata, "xa.metadata", "s", &metadata_contents))
 3387     {
 3388       metadata = g_file_get_child (root, "metadata");
 3389       if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL))
 3390         metadata_contents = g_strdup ("");
 3391     }
 3392 
 3393   if (g_variant_lookup (commit_metadata, "xa.installed-size", "t", &installed_size) &&
 3394       g_variant_lookup (commit_metadata, "xa.download-size", "t", &download_size))
 3395     {
 3396       installed_size = GUINT64_FROM_BE (installed_size);
 3397       download_size = GUINT64_FROM_BE (download_size);
 3398     }
 3399   else
 3400     {
 3401       if (!flatpak_repo_collect_sizes (repo, root, &installed_size, &download_size, cancellable, error))
 3402         return NULL;
 3403     }
 3404 
 3405   if (g_variant_lookup (commit_metadata, "xa.subsets", "as", &subsets_iter))
 3406     {
 3407       const char *subset;
 3408       subsets = g_ptr_array_new_with_free_func (g_free);
 3409       while (g_variant_iter_next (subsets_iter, "&s", &subset))
 3410         g_ptr_array_add (subsets, g_strdup (subset));
 3411     }
 3412 
 3413   flatpak_repo_collect_extra_data_sizes (repo, rev, &installed_size, &download_size);
 3414 
 3415   rev_data = g_new0 (CommitData, 1);
 3416   rev_data->installed_size = installed_size;
 3417   rev_data->download_size = download_size;
 3418   rev_data->metadata_contents = g_steal_pointer (&metadata_contents);
 3419   rev_data->subsets = g_steal_pointer (&subsets);
 3420   rev_data->commit_size = g_variant_get_size (commit_v);
 3421   rev_data->commit_timestamp = ostree_commit_get_timestamp (commit_v);
 3422 
 3423   g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "&s", &eol);
 3424   g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "&s", &eol_rebase);
 3425   if (g_variant_lookup (commit_metadata, "xa.token-type", "i", &token_type))
 3426     token_type = GINT32_FROM_LE(token_type);
 3427 
 3428   extra_data_sources = flatpak_commit_get_extra_data_sources (commit_v, NULL);
 3429   if (extra_data_sources)
 3430     {
 3431       n_extra_data = g_variant_n_children (extra_data_sources);
 3432       for (int i = 0; i < n_extra_data; i++)
 3433         {
 3434           guint64 extra_download_size;
 3435           flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
 3436                                                  NULL,
 3437                                                  &extra_download_size,
 3438                                                  NULL,
 3439                                                  NULL,
 3440                                                  NULL);
 3441           total_extra_data_download_size += extra_download_size;
 3442         }
 3443     }
 3444 
 3445   if (eol || eol_rebase || token_type >= 0 || n_extra_data > 0)
 3446     {
 3447       g_auto(GVariantBuilder) sparse_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
 3448       g_variant_builder_init (&sparse_builder, G_VARIANT_TYPE_VARDICT);
 3449       if (eol)
 3450         g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, g_variant_new_string (eol));
 3451       if (eol_rebase)
 3452         g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, g_variant_new_string (eol_rebase));
 3453       if (token_type >= 0)
 3454         g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_TOKEN_TYPE, g_variant_new_int32 (GINT32_TO_LE(token_type)));
 3455       if (n_extra_data > 0)
 3456         g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_EXTRA_DATA_SIZE,
 3457                                g_variant_new ("(ut)", GUINT32_TO_LE(n_extra_data), GUINT64_TO_LE(total_extra_data_download_size)));
 3458 
 3459       rev_data->sparse_data = g_variant_ref_sink (g_variant_builder_end (&sparse_builder));
 3460     }
 3461 
 3462   return rev_data;
 3463 }
 3464 
 3465 static void
 3466 _ostree_parse_delta_name (const char *delta_name,
 3467                           char      **out_from,
 3468                           char      **out_to)
 3469 {
 3470   g_auto(GStrv) parts = g_strsplit (delta_name, "-", 2);
 3471 
 3472   if (parts[0] && parts[1])
 3473     {
 3474       *out_from = g_steal_pointer (&parts[0]);
 3475       *out_to = g_steal_pointer (&parts[1]);
 3476     }
 3477   else
 3478     {
 3479       *out_from = NULL;
 3480       *out_to = g_steal_pointer (&parts[0]);
 3481     }
 3482 }
 3483 
 3484 static GString *
 3485 static_delta_path_base (const char *dir,
 3486                         const char *from,
 3487                         const char *to)
 3488 {
 3489   guint8 csum_to[OSTREE_SHA256_DIGEST_LEN];
 3490   char to_b64[44];
 3491   guint8 csum_to_copy[OSTREE_SHA256_DIGEST_LEN];
 3492   GString *ret = g_string_new (dir);
 3493 
 3494   ostree_checksum_inplace_to_bytes (to, csum_to);
 3495   ostree_checksum_b64_inplace_from_bytes (csum_to, to_b64);
 3496   ostree_checksum_b64_inplace_to_bytes (to_b64, csum_to_copy);
 3497 
 3498   g_assert (memcmp (csum_to, csum_to_copy, OSTREE_SHA256_DIGEST_LEN) == 0);
 3499 
 3500   if (from != NULL)
 3501     {
 3502       guint8 csum_from[OSTREE_SHA256_DIGEST_LEN];
 3503       char from_b64[44];
 3504 
 3505       ostree_checksum_inplace_to_bytes (from, csum_from);
 3506       ostree_checksum_b64_inplace_from_bytes (csum_from, from_b64);
 3507 
 3508       g_string_append_c (ret, from_b64[0]);
 3509       g_string_append_c (ret, from_b64[1]);
 3510       g_string_append_c (ret, '/');
 3511       g_string_append (ret, from_b64 + 2);
 3512       g_string_append_c (ret, '-');
 3513     }
 3514 
 3515   g_string_append_c (ret, to_b64[0]);
 3516   g_string_append_c (ret, to_b64[1]);
 3517   if (from == NULL)
 3518     g_string_append_c (ret, '/');
 3519   g_string_append (ret, to_b64 + 2);
 3520 
 3521   return ret;
 3522 }
 3523 
 3524 static char *
 3525 _ostree_get_relative_static_delta_path (const char *from,
 3526                                         const char *to,
 3527                                         const char *target)
 3528 {
 3529   GString *ret = static_delta_path_base ("deltas/", from, to);
 3530 
 3531   if (target != NULL)
 3532     {
 3533       g_string_append_c (ret, '/');
 3534       g_string_append (ret, target);
 3535     }
 3536 
 3537   return g_string_free (ret, FALSE);
 3538 }
 3539 
 3540 static char *
 3541 _ostree_get_relative_static_delta_superblock_path (const char        *from,
 3542                                                    const char        *to)
 3543 {
 3544   return _ostree_get_relative_static_delta_path (from, to, "superblock");
 3545 }
 3546 
 3547 static GVariant *
 3548 _ostree_repo_static_delta_superblock_digest (OstreeRepo    *repo,
 3549                                              const char    *from,
 3550                                              const char    *to,
 3551                                              GCancellable  *cancellable,
 3552                                              GError       **error)
 3553 {
 3554   g_autofree char *superblock = _ostree_get_relative_static_delta_superblock_path ((from && from[0]) ? from : NULL, to);
 3555   glnx_autofd int fd = -1;
 3556   guint8 digest[OSTREE_SHA256_DIGEST_LEN];
 3557   gsize len;
 3558 
 3559   if (!glnx_openat_rdonly (ostree_repo_get_dfd (repo), superblock, TRUE, &fd, error))
 3560     return NULL;
 3561 
 3562   g_autoptr(GBytes) superblock_content = glnx_fd_readall_bytes (fd, cancellable, error);
 3563   if (!superblock_content)
 3564     return NULL;
 3565 
 3566   g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256);
 3567   g_checksum_update (checksum, g_bytes_get_data (superblock_content, NULL), g_bytes_get_size (superblock_content));
 3568   len = sizeof digest;
 3569   g_checksum_get_digest (checksum, digest, &len);
 3570 
 3571   return g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
 3572                                   g_memdup (digest, len), len,
 3573                                   FALSE, g_free, FALSE);
 3574 }
 3575 
 3576 static char *
 3577 appstream_ref_get_subset (const char *ref)
 3578 {
 3579   if (!g_str_has_prefix (ref, "appstream2/"))
 3580     return NULL;
 3581 
 3582   const char *rest = ref + strlen ("appstream2/");
 3583   const char *dash = strrchr (rest, '-');
 3584   if (dash == NULL)
 3585     return NULL;
 3586 
 3587   return g_strndup (rest, dash - rest);
 3588 }
 3589 
 3590 char *
 3591 flatpak_get_arch_for_ref (const char *ref)
 3592 {
 3593   if (g_str_has_prefix (ref, "appstream/") ||
 3594       g_str_has_prefix (ref, "appstream2/"))
 3595     {
 3596       const char *rest = strchr (ref, '/') + 1; /* Guaranteed to exist per above check */
 3597       const char *dash = strrchr (rest, '-'); /*  Subset appstream refs are appstream2/$subset-$arch */
 3598       if (dash != NULL)
 3599         rest = dash + 1;
 3600       return g_strdup (rest);
 3601     }
 3602     else if (g_str_has_prefix (ref, "app/") ||
 3603       g_str_has_prefix (ref, "runtime/"))
 3604     {
 3605       const char *slash;
 3606       const char *arch;
 3607 
 3608       slash = strchr (ref, '/') + 1; /* Guaranteed to exist per above check */
 3609       slash = strchr (slash, '/'); /* Skip id */
 3610       if (slash == NULL)
 3611         return NULL;
 3612       arch = slash + 1;
 3613 
 3614       slash = strchr (arch, '/'); /* skip to end arch */
 3615       if (slash == NULL)
 3616         return NULL;
 3617 
 3618       return g_strndup (arch, slash - arch);
 3619     }
 3620 
 3621   return NULL;
 3622 }
 3623 
 3624 typedef enum {
 3625   DIFF_OP_KIND_RESUSE_OLD,
 3626   DIFF_OP_KIND_SKIP_OLD,
 3627   DIFF_OP_KIND_DATA,
 3628 } DiffOpKind;
 3629 
 3630 typedef struct {
 3631   DiffOpKind kind;
 3632   gsize size;
 3633 } DiffOp;
 3634 
 3635 typedef struct {
 3636   const guchar *old_data;
 3637   const guchar *new_data;
 3638 
 3639   GArray *ops;
 3640   GArray *data;
 3641 
 3642   gsize last_old_offset;
 3643   gsize last_new_offset;
 3644 } DiffData;
 3645 
 3646 static gsize
 3647 match_bytes_at_start (const guchar *data1,
 3648                       gsize data1_len,
 3649                       const guchar *data2,
 3650                       gsize data2_len)
 3651 {
 3652   gsize len = 0;
 3653   gsize max_len = MIN (data1_len, data2_len);
 3654 
 3655   while (len < max_len)
 3656     {
 3657       if (*data1 != *data2)
 3658         break;
 3659       data1++;
 3660       data2++;
 3661       len++;
 3662     }
 3663   return len;
 3664 }
 3665 
 3666 static gsize
 3667 match_bytes_at_end (const guchar *data1,
 3668                     gsize data1_len,
 3669                     const guchar *data2,
 3670                     gsize data2_len)
 3671 {
 3672   gsize len = 0;
 3673   gsize max_len = MIN (data1_len, data2_len);
 3674 
 3675   data1 += data1_len - 1;
 3676   data2 += data2_len - 1;
 3677 
 3678   while (len < max_len)
 3679     {
 3680       if (*data1 != *data2)
 3681         break;
 3682       data1--;
 3683       data2--;
 3684       len++;
 3685     }
 3686   return len;
 3687 }
 3688 
 3689 static DiffOp *
 3690 diff_ensure_op (DiffData *data,
 3691                 DiffOpKind kind)
 3692 {
 3693   if (data->ops->len == 0 ||
 3694       g_array_index (data->ops, DiffOp, data->ops->len-1).kind != kind)
 3695     {
 3696       DiffOp op = {kind, 0};
 3697       g_array_append_val (data->ops, op);
 3698     }
 3699 
 3700   return &g_array_index (data->ops, DiffOp, data->ops->len-1);
 3701 }
 3702 
 3703 static void
 3704 diff_emit_reuse (DiffData *data,
 3705                  gsize size)
 3706 {
 3707   DiffOp *op;
 3708 
 3709   if (size == 0)
 3710     return;
 3711 
 3712   op = diff_ensure_op (data, DIFF_OP_KIND_RESUSE_OLD);
 3713   op->size += size;
 3714 }
 3715 
 3716 static void
 3717 diff_emit_skip (DiffData *data,
 3718                 gsize size)
 3719 {
 3720   DiffOp *op;
 3721 
 3722   if (size == 0)
 3723     return;
 3724 
 3725   op = diff_ensure_op (data, DIFF_OP_KIND_SKIP_OLD);
 3726   op->size += size;
 3727 }
 3728 
 3729 static void
 3730 diff_emit_data (DiffData *data,
 3731                 gsize size,
 3732                 const guchar *new_data)
 3733 {
 3734   DiffOp *op;
 3735 
 3736   if (size == 0)
 3737     return;
 3738 
 3739   op = diff_ensure_op (data, DIFF_OP_KIND_DATA);
 3740   op->size += size;
 3741 
 3742   g_array_append_vals (data->data, new_data, size);
 3743 }
 3744 
 3745 static GBytes *
 3746 diff_encode (DiffData *data, GError **error)
 3747 {
 3748   g_autoptr(GOutputStream) mem = g_memory_output_stream_new_resizable ();
 3749   g_autoptr(GDataOutputStream) out = g_data_output_stream_new (mem);
 3750   gsize ops_count = 0;
 3751 
 3752   g_data_output_stream_set_byte_order (out, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
 3753 
 3754   /* Header */
 3755   if (!g_output_stream_write_all (G_OUTPUT_STREAM (out),
 3756                                   FLATPAK_SUMMARY_DIFF_HEADER, 4,
 3757                                   NULL, NULL, error))
 3758     return NULL;
 3759 
 3760   /* Write the ops count placeholder */
 3761   if (!g_data_output_stream_put_uint32 (out, 0, NULL, error))
 3762     return NULL;
 3763 
 3764   for (gsize i = 0; i < data->ops->len; i++)
 3765     {
 3766       DiffOp *op = &g_array_index (data->ops, DiffOp, i);
 3767       gsize size = op->size;
 3768 
 3769       while (size > 0)
 3770         {
 3771           /* We leave a nibble at the top for the op */
 3772           guint32 opdata = (guint64)size & 0x0fffffff;
 3773           size -= opdata;
 3774 
 3775           opdata = opdata | ((0xf & op->kind) << 28);
 3776 
 3777           if (!g_data_output_stream_put_uint32 (out, opdata, NULL, error))
 3778             return NULL;
 3779           ops_count++;
 3780         }
 3781     }
 3782 
 3783   /* Then add the data */
 3784   if (data->data->len > 0 &&
 3785       !g_output_stream_write_all (G_OUTPUT_STREAM (out),
 3786                                   data->data->data, data->data->len,
 3787                                   NULL, NULL, error))
 3788     return NULL;
 3789 
 3790   /* Back-patch in the ops count */
 3791   if (!g_seekable_seek (G_SEEKABLE(out), 4, G_SEEK_SET, NULL, error))
 3792     return NULL;
 3793 
 3794   if (!g_data_output_stream_put_uint32 (out, ops_count, NULL, error))
 3795     return NULL;
 3796 
 3797   if (!g_output_stream_close (G_OUTPUT_STREAM (out), NULL, error))
 3798     return NULL;
 3799 
 3800   return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem));
 3801 }
 3802 
 3803 static void
 3804 diff_consume_block2 (DiffData *data,
 3805                      gsize consume_old_offset,
 3806                      gsize consume_old_size,
 3807                      gsize produce_new_offset,
 3808                      gsize produce_new_size)
 3809 {
 3810   /* We consumed $consume_old_size bytes from $consume_old_offset to
 3811      produce $produce_new_size bytes at $produce_new_size */
 3812 
 3813   /* First we copy old data for any matching prefix of the block */
 3814 
 3815   gsize prefix_len = match_bytes_at_start (data->old_data + consume_old_offset, consume_old_size,
 3816                                            data->new_data + produce_new_offset, produce_new_size);
 3817   diff_emit_reuse (data, prefix_len);
 3818 
 3819   consume_old_size -= prefix_len;
 3820   consume_old_offset += prefix_len;
 3821 
 3822   produce_new_size -= prefix_len;
 3823   produce_new_offset += prefix_len;
 3824 
 3825   /* Then we find the matching suffix for the rest */
 3826   gsize suffix_len = match_bytes_at_end (data->old_data + consume_old_offset, consume_old_size,
 3827                                          data->new_data + produce_new_offset, produce_new_size);
 3828 
 3829   /* Skip source data until suffix match */
 3830   diff_emit_skip (data, consume_old_size - suffix_len);
 3831 
 3832   /* Copy new data until suffix match */
 3833   diff_emit_data (data, produce_new_size - suffix_len, data->new_data + produce_new_offset);
 3834 
 3835   diff_emit_reuse (data, suffix_len);
 3836 }
 3837 
 3838 static void
 3839 diff_consume_block (DiffData *data,
 3840                     gssize consume_old_offset,
 3841                     gsize consume_old_size,
 3842                     gssize produce_new_offset,
 3843                     gsize produce_new_size)
 3844 {
 3845   if (consume_old_offset == -1)
 3846     consume_old_offset = data->last_old_offset;
 3847   if (produce_new_offset == -1)
 3848     produce_new_offset = data->last_new_offset;
 3849 
 3850   /* We consumed $consume_old_size bytes from $consume_old_offset to
 3851    * produce $produce_new_size bytes at $produce_new_size, however
 3852    * while the emitted blocks are in order they may not cover the
 3853    * every byte, so we emit the inbetwen blocks separately. */
 3854 
 3855   if (consume_old_offset != data->last_old_offset ||
 3856       produce_new_offset != data->last_new_offset)
 3857     diff_consume_block2 (data,
 3858                          data->last_old_offset, consume_old_offset - data->last_old_offset ,
 3859                          data->last_new_offset, produce_new_offset - data->last_new_offset);
 3860 
 3861   diff_consume_block2 (data,
 3862                        consume_old_offset, consume_old_size,
 3863                        produce_new_offset, produce_new_size);
 3864 
 3865   data->last_old_offset = consume_old_offset + consume_old_size;
 3866   data->last_new_offset = produce_new_offset + produce_new_size;
 3867 }
 3868 
 3869 GBytes *
 3870 flatpak_summary_apply_diff (GBytes *old,
 3871                             GBytes *diff,
 3872                             GError **error)
 3873 {
 3874   g_autoptr(GBytes) uncompressed = NULL;
 3875   const guchar *diffdata;
 3876   gsize diff_size;
 3877   guint32 *ops;
 3878   guint32 n_ops;
 3879   gsize data_offset;
 3880   gsize data_size;
 3881   const guchar *data;
 3882   const guchar *old_data = g_bytes_get_data (old, NULL);
 3883   gsize old_size = g_bytes_get_size (old);
 3884   g_autoptr(GByteArray) res = g_byte_array_new ();
 3885 
 3886   uncompressed = flatpak_zlib_decompress_bytes (diff, error);
 3887   if (uncompressed == NULL)
 3888     {
 3889       g_prefix_error (error, "Invalid summary diff: ");
 3890       return NULL;
 3891     }
 3892 
 3893   diffdata = g_bytes_get_data (uncompressed, NULL);
 3894   diff_size = g_bytes_get_size (uncompressed);
 3895 
 3896   if (diff_size < 8 ||
 3897       memcmp (diffdata, FLATPAK_SUMMARY_DIFF_HEADER, 4) != 0)
 3898     {
 3899       flatpak_fail (error, "Invalid summary diff");
 3900       return NULL;
 3901     }
 3902 
 3903   n_ops = GUINT32_FROM_LE (*(guint32 *)(diffdata+4));
 3904   ops = (guint32 *)(diffdata+8);
 3905 
 3906   data_offset = 4 + 4 + 4 * n_ops;
 3907 
 3908   /* All ops must fit in diff, and avoid wrapping the multiply */
 3909   if (data_offset > diff_size ||
 3910       (data_offset - 4 - 4) / 4 != n_ops)
 3911     {
 3912       flatpak_fail (error, "Invalid summary diff");
 3913       return NULL;
 3914     }
 3915 
 3916   data = diffdata + data_offset;
 3917   data_size = diff_size - data_offset;
 3918 
 3919   for (gsize i = 0; i < n_ops; i++)
 3920     {
 3921       guint32 opdata = GUINT32_FROM_LE (ops[i]);
 3922       guint32 kind = (opdata & 0xf0000000) >> 28;
 3923       guint32 size = opdata & 0x0fffffff;
 3924 
 3925       switch (kind)
 3926         {
 3927         case DIFF_OP_KIND_RESUSE_OLD:
 3928           if (size > old_size)
 3929             {
 3930               flatpak_fail (error, "Invalid summary diff");
 3931               return NULL;
 3932             }
 3933           g_byte_array_append (res, old_data, size);
 3934           old_data += size;
 3935           old_size -= size;
 3936           break;
 3937         case DIFF_OP_KIND_SKIP_OLD:
 3938           if (size > old_size)
 3939             {
 3940               flatpak_fail (error, "Invalid summary diff");
 3941               return NULL;
 3942             }
 3943           old_data += size;
 3944           old_size -= size;
 3945           break;
 3946         case DIFF_OP_KIND_DATA:
 3947           if (size > data_size)
 3948             {
 3949               flatpak_fail (error, "Invalid summary diff");
 3950               return NULL;
 3951             }
 3952           g_byte_array_append (res, data, size);
 3953           data += size;
 3954           data_size -= size;
 3955           break;
 3956         default:
 3957           flatpak_fail (error, "Invalid summary diff");
 3958           return NULL;
 3959         }
 3960     }
 3961 
 3962   return g_byte_array_free_to_bytes (g_steal_pointer (&res));
 3963 }
 3964 
 3965 
 3966 static GBytes *
 3967 flatpak_summary_generate_diff (GVariant *old_v,
 3968                                GVariant *new_v,
 3969                                GError **error)
 3970 {
 3971   VarSummaryRef new, old;
 3972   VarRefMapRef new_refs, old_refs;
 3973   VarRefMapEntryRef new_entry, old_entry;
 3974   gsize new_len, old_len;
 3975   int new_i, old_i;
 3976   const char *old_ref, *new_ref;
 3977   g_autoptr(GArray) ops = g_array_new (FALSE, TRUE, sizeof (DiffOp));
 3978   g_autoptr(GArray) data_bytes = g_array_new (FALSE, TRUE, 1);
 3979   g_autoptr(GBytes) diff_uncompressed = NULL;
 3980   g_autoptr(GBytes) diff_compressed = NULL;
 3981   DiffData data = {
 3982     g_variant_get_data (old_v),
 3983     g_variant_get_data (new_v),
 3984     ops,
 3985     data_bytes,
 3986   };
 3987 
 3988   new = var_summary_from_gvariant (new_v);
 3989   old = var_summary_from_gvariant (old_v);
 3990 
 3991   new_refs = var_summary_get_ref_map (new);
 3992   old_refs = var_summary_get_ref_map (old);
 3993 
 3994   new_len = var_ref_map_get_length (new_refs);
 3995   old_len = var_ref_map_get_length (old_refs);
 3996 
 3997   new_i = old_i = 0;
 3998   while (new_i < new_len && old_i < old_len)
 3999     {
 4000       if (new_i == new_len)
 4001         {
 4002           /* Just old left */
 4003           old_entry = var_ref_map_get_at (old_refs, old_i);
 4004           old_ref = var_ref_map_entry_get_ref (old_entry);
 4005           old_i++;
 4006           diff_consume_block (&data,
 4007                               -1, 0,
 4008                               (const guchar *)new_entry.base - (const guchar *)new.base, new_entry.size);
 4009         }
 4010       else if (old_i == old_len)
 4011         {
 4012           /* Just new left */
 4013           new_entry = var_ref_map_get_at (new_refs, new_i);
 4014           new_ref = var_ref_map_entry_get_ref (new_entry);
 4015           diff_consume_block (&data,
 4016                               (const guchar *)old_entry.base - (const guchar *)old.base, old_entry.size,
 4017                               -1, 0);
 4018 
 4019           new_i++;
 4020         }
 4021       else
 4022         {
 4023           new_entry = var_ref_map_get_at (new_refs, new_i);
 4024           new_ref = var_ref_map_entry_get_ref (new_entry);
 4025 
 4026           old_entry = var_ref_map_get_at (old_refs, old_i);
 4027           old_ref = var_ref_map_entry_get_ref (old_entry);
 4028 
 4029           int cmp = strcmp (new_ref, old_ref);
 4030           if (cmp == 0)
 4031             {
 4032               /* same ref */
 4033               diff_consume_block (&data,
 4034                                   (const guchar *)old_entry.base - (const guchar *)old.base, old_entry.size,
 4035                                   (const guchar *)new_entry.base - (const guchar *)new.base, new_entry.size);
 4036               old_i++;
 4037               new_i++;
 4038             }
 4039           else if (cmp < 0)
 4040             {
 4041               /* new added */
 4042               diff_consume_block (&data,
 4043                                   -1, 0,
 4044                                   (const guchar *)new_entry.base - (const guchar *)new.base, new_entry.size);
 4045               new_i++;
 4046             }
 4047           else
 4048             {
 4049               /* old removed */
 4050               diff_consume_block (&data,
 4051                                   (const guchar *)old_entry.base - (const guchar *)old.base, old_entry.size,
 4052                                   -1, 0);
 4053               old_i++;
 4054             }
 4055         }
 4056     }
 4057 
 4058   /* Flush till the end */
 4059   diff_consume_block2 (&data,
 4060                        data.last_old_offset, old.size - data.last_old_offset,
 4061                        data.last_new_offset, new.size - data.last_new_offset);
 4062 
 4063   diff_uncompressed = diff_encode (&data, error);
 4064   if (diff_uncompressed == NULL)
 4065     return NULL;
 4066 
 4067   diff_compressed = flatpak_zlib_compress_bytes (diff_uncompressed, 9, error);
 4068   if (diff_compressed == NULL)
 4069     return NULL;
 4070 
 4071 #ifdef VALIDATE_DIFF
 4072   {
 4073     g_autoptr(GError) apply_error = NULL;
 4074     g_autoptr(GBytes) old_bytes = g_variant_get_data_as_bytes (old_v);
 4075     g_autoptr(GBytes) new_bytes = g_variant_get_data_as_bytes (new_v);
 4076     g_autoptr(GBytes) applied = flatpak_summary_apply_diff (old_bytes, diff_compressed, &apply_error);
 4077     g_assert (applied != NULL);
 4078     g_assert (g_bytes_equal (applied, new_bytes));
 4079   }
 4080 #endif
 4081 
 4082   return g_steal_pointer (&diff_compressed);
 4083 }
 4084 
 4085 static void
 4086 variant_dict_merge (GVariantDict *dict,
 4087                     GVariant *to_merge)
 4088 {
 4089   GVariantIter iter;
 4090   gchar *key;
 4091   GVariant *value;
 4092 
 4093   if (to_merge)
 4094     {
 4095       g_variant_iter_init (&iter, to_merge);
 4096       while (g_variant_iter_next (&iter, "{sv}", &key, &value))
 4097         {
 4098           g_variant_dict_insert_value (dict, key, value);
 4099           g_variant_unref (value);
 4100           g_free (key);
 4101         }
 4102     }
 4103 }
 4104 
 4105 static void
 4106 add_summary_metadata (OstreeRepo   *repo,
 4107                       GVariantBuilder *metadata_builder)
 4108 {
 4109   GKeyFile *config;
 4110   g_autofree char *title = NULL;
 4111   g_autofree char *comment = NULL;
 4112   g_autofree char *description = NULL;
 4113   g_autofree char *homepage = NULL;
 4114   g_autofree char *icon = NULL;
 4115   g_autofree char *redirect_url = NULL;
 4116   g_autofree char *default_branch = NULL;
 4117   g_autofree char *remote_mode_str = NULL;
 4118   g_autofree char *authenticator_name = NULL;
 4119   g_autofree char *gpg_keys = NULL;
 4120   g_auto(GStrv) config_keys = NULL;
 4121   int authenticator_install = -1;
 4122   const char *collection_id;
 4123   gboolean deploy_collection_id = FALSE;
 4124   gboolean deploy_sideload_collection_id = FALSE;
 4125   gboolean tombstone_commits = FALSE;
 4126 
 4127   config = ostree_repo_get_config (repo);
 4128 
 4129   if (config)
 4130     {
 4131       remote_mode_str = g_key_file_get_string (config, "core", "mode", NULL);
 4132       tombstone_commits = g_key_file_get_boolean (config, "core", "tombstone-commits", NULL);
 4133 
 4134       title = g_key_file_get_string (config, "flatpak", "title", NULL);
 4135       comment = g_key_file_get_string (config, "flatpak", "comment", NULL);
 4136       description = g_key_file_get_string (config, "flatpak", "description", NULL);
 4137       homepage = g_key_file_get_string (config, "flatpak", "homepage", NULL);
 4138       icon = g_key_file_get_string (config, "flatpak", "icon", NULL);
 4139       default_branch = g_key_file_get_string (config, "flatpak", "default-branch", NULL);
 4140       gpg_keys = g_key_file_get_string (config, "flatpak", "gpg-keys", NULL);
 4141       redirect_url = g_key_file_get_string (config, "flatpak", "redirect-url", NULL);
 4142       deploy_sideload_collection_id = g_key_file_get_boolean (config, "flatpak", "deploy-sideload-collection-id", NULL);
 4143       deploy_collection_id = g_key_file_get_boolean (config, "flatpak", "deploy-collection-id", NULL);
 4144       authenticator_name = g_key_file_get_string (config, "flatpak", "authenticator-name", NULL);
 4145       if (g_key_file_has_key (config, "flatpak", "authenticator-install", NULL))
 4146         authenticator_install = g_key_file_get_boolean (config, "flatpak", "authenticator-install", NULL);
 4147 
 4148       config_keys = g_key_file_get_keys (config, "flatpak", NULL, NULL);
 4149     }
 4150 
 4151   collection_id = ostree_repo_get_collection_id (repo);
 4152 
 4153   g_variant_builder_add (metadata_builder, "{sv}", "ostree.summary.mode",
 4154                          g_variant_new_string (remote_mode_str ? remote_mode_str : "bare"));
 4155   g_variant_builder_add (metadata_builder, "{sv}", "ostree.summary.tombstone-commits",
 4156                          g_variant_new_boolean (tombstone_commits));
 4157   g_variant_builder_add (metadata_builder, "{sv}", "ostree.summary.indexed-deltas",
 4158                          g_variant_new_boolean (TRUE));
 4159   g_variant_builder_add (metadata_builder, "{sv}", "ostree.summary.last-modified",
 4160                          g_variant_new_uint64 (GUINT64_TO_BE (g_get_real_time () / G_USEC_PER_SEC)));
 4161 
 4162   if (collection_id)
 4163     g_variant_builder_add (metadata_builder, "{sv}", "ostree.summary.collection-id",
 4164                            g_variant_new_string (collection_id));
 4165 
 4166   if (title)
 4167     g_variant_builder_add (metadata_builder, "{sv}", "xa.title",
 4168                            g_variant_new_string (title));
 4169 
 4170   if (comment)
 4171     g_variant_builder_add (metadata_builder, "{sv}", "xa.comment",
 4172                            g_variant_new_string (comment));
 4173 
 4174   if (description)
 4175     g_variant_builder_add (metadata_builder, "{sv}", "xa.description",
 4176                            g_variant_new_string (description));
 4177 
 4178   if (homepage)
 4179     g_variant_builder_add (metadata_builder, "{sv}", "xa.homepage",
 4180                            g_variant_new_string (homepage));
 4181 
 4182   if (icon)
 4183     g_variant_builder_add (metadata_builder, "{sv}", "xa.icon",
 4184                            g_variant_new_string (icon));
 4185 
 4186   if (redirect_url)
 4187     g_variant_builder_add (metadata_builder, "{sv}", "xa.redirect-url",
 4188                            g_variant_new_string (redirect_url));
 4189 
 4190   if (default_branch)
 4191     g_variant_builder_add (metadata_builder, "{sv}", "xa.default-branch",
 4192                            g_variant_new_string (default_branch));
 4193 
 4194   if (deploy_collection_id && collection_id != NULL)
 4195     g_variant_builder_add (metadata_builder, "{sv}", OSTREE_META_KEY_DEPLOY_COLLECTION_ID,
 4196                            g_variant_new_string (collection_id));
 4197   else if (deploy_sideload_collection_id && collection_id != NULL)
 4198     g_variant_builder_add (metadata_builder, "{sv}", "xa.deploy-collection-id",
 4199                            g_variant_new_string (collection_id));
 4200   else if (deploy_collection_id)
 4201     g_debug ("Ignoring deploy-collection-id=true because no collection ID is set.");
 4202 
 4203   if (authenticator_name)
 4204     g_variant_builder_add (metadata_builder, "{sv}", "xa.authenticator-name",
 4205                            g_variant_new_string (authenticator_name));
 4206 
 4207   if (authenticator_install != -1)
 4208     g_variant_builder_add (metadata_builder, "{sv}", "xa.authenticator-install",
 4209                            g_variant_new_boolean (authenticator_install));
 4210 
 4211   g_variant_builder_add (metadata_builder, "{sv}", "xa.cache-version",
 4212                          g_variant_new_uint32 (GUINT32_TO_LE (FLATPAK_XA_CACHE_VERSION)));
 4213 
 4214   if (config_keys != NULL)
 4215     {
 4216       for (int i = 0; config_keys[i] != NULL; i++)
 4217         {
 4218           const char *key = config_keys[i];
 4219           g_autofree char *xa_key = NULL;
 4220           g_autofree char *value = NULL;
 4221 
 4222           if (!g_str_has_prefix (key, "authenticator-options."))
 4223             continue;
 4224 
 4225           value = g_key_file_get_string (config, "flatpak", key, NULL);
 4226           if (value == NULL)
 4227             continue;
 4228 
 4229           xa_key = g_strconcat ("xa.", key, NULL);
 4230           g_variant_builder_add (metadata_builder, "{sv}", xa_key,
 4231                                  g_variant_new_string (value));
 4232         }
 4233     }
 4234 
 4235   if (gpg_keys)
 4236     {
 4237       guchar *decoded;
 4238       gsize decoded_len;
 4239 
 4240       gpg_keys = g_strstrip (gpg_keys);
 4241       decoded = g_base64_decode (gpg_keys, &decoded_len);
 4242 
 4243       g_variant_builder_add (metadata_builder, "{sv}", "xa.gpg-keys",
 4244                              g_variant_new_from_data (G_VARIANT_TYPE ("ay"), decoded, decoded_len,
 4245                                                       TRUE, (GDestroyNotify) g_free, decoded));
 4246     }
 4247 }
 4248 
 4249 static GVariant *
 4250 generate_summary (OstreeRepo   *repo,
 4251                   gboolean      compat_format,
 4252                   GHashTable   *refs,
 4253                   GHashTable   *commit_data_cache,
 4254                   GPtrArray    *delta_names,
 4255                   const char   *subset,
 4256                   const char  **summary_arches,
 4257                   GCancellable *cancellable,
 4258                   GError      **error)
 4259 {
 4260   g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
 4261   g_autoptr(GVariantBuilder) ref_data_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{s(tts)}"));
 4262   g_autoptr(GVariantBuilder) ref_sparse_data_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sa{sv}}"));
 4263   g_autoptr(GVariantBuilder) refs_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(s(taya{sv}))"));
 4264   g_autoptr(GVariantBuilder) summary_builder = g_variant_builder_new (OSTREE_SUMMARY_GVARIANT_FORMAT);
 4265   g_autoptr(GHashTable) summary_arches_ht = NULL;
 4266   g_autoptr(GHashTable) commits = NULL;
 4267   g_autoptr(GList) ordered_keys = NULL;
 4268   GList *l = NULL;
 4269 
 4270   /* In the new format this goes in the summary index instead */
 4271   if (compat_format)
 4272     add_summary_metadata (repo, metadata_builder);
 4273 
 4274   ordered_keys = g_hash_table_get_keys (refs);
 4275   ordered_keys = g_list_sort (ordered_keys, (GCompareFunc) strcmp);
 4276 
 4277   if (summary_arches)
 4278     {
 4279       summary_arches_ht = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
 4280       for (int i = 0; summary_arches[i] != NULL; i++)
 4281         {
 4282           const char *arch = summary_arches[i];
 4283           const char *compat_arch = flatpak_get_compat_arch (arch);
 4284 
 4285           g_hash_table_add (summary_arches_ht, (char *)arch);
 4286           if (compat_arch)
 4287             g_hash_table_add (summary_arches_ht, (char *)compat_arch);
 4288         }
 4289     }
 4290 
 4291   /* Compute which commits to keep */
 4292   commits = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); /* strings owned by ref */
 4293   for (l = ordered_keys; l; l = l->next)
 4294     {
 4295       const char *ref = l->data;
 4296       const char *rev = g_hash_table_lookup (refs, ref);
 4297       g_autofree char *arch = NULL;
 4298       const CommitData *rev_data = NULL;
 4299 
 4300       if (summary_arches)
 4301         {
 4302           /* NOTE: Non-arched (unknown) refs get into all summary versions */
 4303           arch = flatpak_get_arch_for_ref (ref);
 4304           if (arch != NULL && !g_hash_table_contains (summary_arches_ht, arch))
 4305             continue; /* Filter this ref by arch */
 4306         }
 4307 
 4308       rev_data = g_hash_table_lookup (commit_data_cache, rev);
 4309       if (*subset != 0)
 4310         {
 4311           /* Subset summaries keep the appstream2/$subset-$arch, and have no appstream/ compat branch */
 4312 
 4313           if (g_str_has_prefix (ref, "appstream/"))
 4314             {
 4315               continue; /* No compat branch in subsets */
 4316             }
 4317           else if (g_str_has_prefix (ref, "appstream2/"))
 4318             {
 4319               g_autofree char *ref_subset = appstream_ref_get_subset (ref);
 4320               if (ref_subset == NULL)
 4321                 continue; /* Non-subset, ignore */
 4322 
 4323               if (strcmp (subset, ref_subset) != 0)
 4324                 continue; /* Different subset, ignore */
 4325 
 4326               /* Otherwise, keep */
 4327             }
 4328           else if (rev_data)
 4329             {
 4330               if (rev_data->subsets == NULL ||
 4331                   !flatpak_g_ptr_array_contains_string (rev_data->subsets, subset))
 4332                 continue; /* Ref is not in this subset */
 4333             }
 4334         }
 4335       else
 4336         {
 4337           /* non-subset, keep everything but subset appstream refs */
 4338 
 4339           g_autofree char *ref_subset = appstream_ref_get_subset (ref);
 4340           if (ref_subset != NULL)
 4341             continue; /* Subset appstream ref, ignore */
 4342         }
 4343 
 4344       g_hash_table_add (commits, (char *)rev);
 4345     }
 4346 
 4347   /* Create refs list, metadata and sparse_data */
 4348   for (l = ordered_keys; l; l = l->next)
 4349     {
 4350       const char *ref = l->data;
 4351       const char *rev = g_hash_table_lookup (refs, ref);
 4352       const CommitData *rev_data = NULL;
 4353       g_auto(GVariantDict) commit_metadata_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
 4354       guint64 commit_size;
 4355       guint64 commit_timestamp;
 4356 
 4357       if (!g_hash_table_contains (commits, rev))
 4358         continue; /* Filter out commit (by arch & subset) */
 4359 
 4360       if (is_flatpak_ref (ref))
 4361         rev_data = g_hash_table_lookup (commit_data_cache, rev);
 4362 
 4363       if (rev_data != NULL)
 4364         {
 4365           commit_size = rev_data->commit_size;
 4366           commit_timestamp = rev_data->commit_timestamp;
 4367         }
 4368       else
 4369         {
 4370           g_autoptr(GVariant) commit_obj = NULL;
 4371           if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, rev, &commit_obj, error))
 4372             return NULL;
 4373           commit_size = g_variant_get_size (commit_obj);
 4374           commit_timestamp = ostree_commit_get_timestamp (commit_obj);
 4375         }
 4376 
 4377       g_variant_dict_init (&commit_metadata_builder, NULL);
 4378       if (!compat_format && rev_data)
 4379         {
 4380           g_variant_dict_insert (&commit_metadata_builder, "xa.data", "(tts)",
 4381                                  GUINT64_TO_BE (rev_data->installed_size),
 4382                                  GUINT64_TO_BE (rev_data->download_size),
 4383                                  rev_data->metadata_contents);
 4384           variant_dict_merge (&commit_metadata_builder, rev_data->sparse_data);
 4385         }
 4386 
 4387       /* For the new format summary we use a shorter name for the timestamp to save space */
 4388       g_variant_dict_insert_value (&commit_metadata_builder,
 4389                                    compat_format ? OSTREE_COMMIT_TIMESTAMP  : OSTREE_COMMIT_TIMESTAMP2,
 4390                                    g_variant_new_uint64 (GUINT64_TO_BE (commit_timestamp)));
 4391 
 4392       g_variant_builder_add_value (refs_builder,
 4393                                    g_variant_new ("(s(t@ay@a{sv}))", ref,
 4394                                                   commit_size,
 4395                                                   ostree_checksum_to_bytes_v (rev),
 4396                                                   g_variant_dict_end (&commit_metadata_builder)));
 4397 
 4398       if (compat_format && rev_data)
 4399         {
 4400           g_variant_builder_add (ref_data_builder, "{s(tts)}",
 4401                                  ref,
 4402                                  GUINT64_TO_BE (rev_data->installed_size),
 4403                                  GUINT64_TO_BE (rev_data->download_size),
 4404                                  rev_data->metadata_contents);
 4405           if (rev_data->sparse_data)
 4406             g_variant_builder_add (ref_sparse_data_builder, "{s@a{sv}}",
 4407                                    ref, rev_data->sparse_data);
 4408         }
 4409     }
 4410 
 4411   if (delta_names)
 4412     {
 4413       g_auto(GVariantDict) deltas_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
 4414 
 4415       g_variant_dict_init (&deltas_builder, NULL);
 4416       for (guint i = 0; i < delta_names->len; i++)
 4417         {
 4418           g_autofree char *from = NULL;
 4419           g_autofree char *to = NULL;
 4420           GVariant *digest;
 4421 
 4422           _ostree_parse_delta_name (delta_names->pdata[i], &from, &to);
 4423 
 4424           /* Only keep deltas going to a ref that is in the summary
 4425            * (i.e. not arch filtered or random) */
 4426           if (!g_hash_table_contains (commits, to))
 4427             continue;
 4428 
 4429           digest = _ostree_repo_static_delta_superblock_digest (repo,
 4430                                                                 (from && from[0]) ? from : NULL,
 4431                                                                 to, cancellable, error);
 4432           if (digest == NULL)
 4433             return FALSE;
 4434 
 4435           g_variant_dict_insert_value (&deltas_builder, delta_names->pdata[i], digest);
 4436         }
 4437 
 4438       if (delta_names->len > 0)
 4439         g_variant_builder_add (metadata_builder, "{sv}", "ostree.static-deltas", g_variant_dict_end (&deltas_builder));
 4440     }
 4441 
 4442   if (compat_format)
 4443     {
 4444       /* Note: xa.cache doesn’t need to support collection IDs for the refs listed
 4445        * in it, because the xa.cache metadata is stored on the ostree-metadata ref,
 4446        * which is itself strongly bound to a collection ID — so that collection ID
 4447        * is bound to all the refs in xa.cache. If a client is using the xa.cache
 4448        * data from a summary file (rather than an ostree-metadata branch), they are
 4449        * too old to care about collection IDs anyway. */
 4450       g_variant_builder_add (metadata_builder, "{sv}", "xa.cache",
 4451                              g_variant_new_variant (g_variant_builder_end (ref_data_builder)));
 4452       g_variant_builder_add (metadata_builder, "{sv}", "xa.sparse-cache",
 4453                              g_variant_builder_end (ref_sparse_data_builder));
 4454     }
 4455   else
 4456     {
 4457       g_variant_builder_add (metadata_builder, "{sv}", "xa.summary-version",
 4458                              g_variant_new_uint32 (GUINT32_TO_LE (FLATPAK_XA_SUMMARY_VERSION)));
 4459     }
 4460 
 4461   g_variant_builder_add_value (summary_builder, g_variant_builder_end (refs_builder));
 4462   g_variant_builder_add_value (summary_builder, g_variant_builder_end (metadata_builder));
 4463 
 4464   return g_variant_ref_sink (g_variant_builder_end (summary_builder));
 4465 }
 4466 
 4467 static GVariant *
 4468 read_digested_summary (OstreeRepo   *repo,
 4469                        const char   *digest,
 4470                        GHashTable   *digested_summary_cache,
 4471                        GCancellable *cancellable,
 4472                        GError      **error)
 4473 {
 4474   GVariant *cached;
 4475   g_autoptr(GVariant) loaded = NULL;
 4476 
 4477   cached = g_hash_table_lookup (digested_summary_cache, digest);
 4478   if (cached)
 4479     return g_variant_ref (cached);
 4480 
 4481   loaded = flatpak_repo_load_digested_summary (repo, digest, error);
 4482   if (loaded == NULL)
 4483     return NULL;
 4484 
 4485   g_hash_table_insert (digested_summary_cache, g_strdup (digest), g_variant_ref (loaded));
 4486 
 4487   return g_steal_pointer (&loaded);
 4488 }
 4489 
 4490 static gboolean
 4491 add_to_history (OstreeRepo      *repo,
 4492                 GVariantBuilder *history_builder,
 4493                 VarChecksumRef   old_digest_vv,
 4494                 GVariant        *current_digest_v,
 4495                 GVariant        *current_content,
 4496                 GHashTable      *digested_summary_cache,
 4497                 guint           *history_len,
 4498                 guint            max_history_length,
 4499                 GCancellable    *cancellable,
 4500                 GError         **error)
 4501 {
 4502   g_autoptr(GVariant) old_digest_v = g_variant_ref_sink (var_checksum_dup_to_gvariant (old_digest_vv));
 4503   g_autofree char *old_digest = NULL;
 4504   g_autoptr(GVariant) old_content = NULL;
 4505   g_autofree char *current_digest = NULL;
 4506   g_autoptr(GBytes) subsummary_diff = NULL;
 4507 
 4508   /* Limit history length */
 4509   if (*history_len >= max_history_length)
 4510     return TRUE;
 4511 
 4512   /* Avoid repeats in the history (in case nothing changed in subsummary) */
 4513   if (g_variant_equal (old_digest_v, current_digest_v))
 4514     return TRUE;
 4515 
 4516   old_digest = ostree_checksum_from_bytes_v (old_digest_v);
 4517   old_content = read_digested_summary (repo, old_digest, digested_summary_cache, cancellable, NULL);
 4518   if  (old_content == NULL)
 4519     return TRUE; /* Only add parents that still exist */
 4520 
 4521   subsummary_diff = flatpak_summary_generate_diff (old_content, current_content, error);
 4522   if  (subsummary_diff == NULL)
 4523     return FALSE;
 4524 
 4525   current_digest = ostree_checksum_from_bytes_v (current_digest_v);
 4526 
 4527   if (!flatpak_repo_save_digested_summary_delta (repo, old_digest, current_digest,
 4528                                                  subsummary_diff, cancellable, error))
 4529     return FALSE;
 4530 
 4531   *history_len += 1;
 4532   g_variant_builder_add_value (history_builder, old_digest_v);
 4533 
 4534   return TRUE;
 4535 }
 4536 
 4537 static GVariant *
 4538 generate_summary_index (OstreeRepo   *repo,
 4539                         GVariant     *old_index_v,
 4540                         GHashTable   *summaries,
 4541                         GHashTable   *digested_summaries,
 4542                         GHashTable   *digested_summary_cache,
 4543                         const char  **gpg_key_ids,
 4544                         const char   *gpg_homedir,
 4545                         GCancellable *cancellable,
 4546                         GError      **error)
 4547 {
 4548   g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
 4549   g_autoptr(GVariantBuilder) subsummary_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{s(ayaaya{sv})}"));
 4550   g_autoptr(GVariantBuilder) index_builder = g_variant_builder_new (FLATPAK_SUMMARY_INDEX_GVARIANT_FORMAT);
 4551   g_autoptr(GVariant) index = NULL;
 4552   g_autoptr(GList) ordered_summaries = NULL;
 4553   guint max_history_length = flatpak_repo_get_summary_history_length (repo);
 4554   GList *l;
 4555 
 4556   add_summary_metadata (repo, metadata_builder);
 4557 
 4558   ordered_summaries = g_hash_table_get_keys (summaries);
 4559   ordered_summaries = g_list_sort (ordered_summaries, (GCompareFunc) strcmp);
 4560   for (l = ordered_summaries; l; l = l->next)
 4561     {
 4562       g_auto(GVariantDict) subsummary_metadata_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
 4563       const char *subsummary = l->data;
 4564       const char *digest = g_hash_table_lookup (summaries, subsummary);
 4565       g_autoptr(GVariant) digest_v = g_variant_ref_sink (ostree_checksum_to_bytes_v (digest));
 4566       g_autoptr(GVariantBuilder) history_builder = g_variant_builder_new (G_VARIANT_TYPE ("aay"));
 4567       g_autoptr(GVariant) subsummary_content = NULL;
 4568 
 4569       subsummary_content = read_digested_summary (repo, digest, digested_summary_cache, cancellable, error);
 4570       if  (subsummary_content == NULL)
 4571         return NULL;  /* This really should always be there as we're supposed to index it */
 4572 
 4573       if (old_index_v)
 4574         {
 4575           VarSummaryIndexRef old_index = var_summary_index_from_gvariant (old_index_v);
 4576           VarSummaryIndexSubsummariesRef old_subsummaries = var_summary_index_get_subsummaries (old_index);
 4577           VarSubsummaryRef old_subsummary;
 4578           guint history_len = 0;
 4579 
 4580           if (var_summary_index_subsummaries_lookup (old_subsummaries, subsummary, NULL, &old_subsummary))
 4581             {
 4582               VarChecksumRef parent = var_subsummary_get_checksum (old_subsummary);
 4583 
 4584               /* Add current as first in history */
 4585               if (!add_to_history (repo, history_builder, parent, digest_v, subsummary_content, digested_summary_cache,
 4586                                    &history_len, max_history_length, cancellable, error))
 4587                 return FALSE;
 4588 
 4589               /* Add previous history */
 4590               VarArrayofChecksumRef history = var_subsummary_get_history (old_subsummary);
 4591               gsize len = var_arrayof_checksum_get_length (history);
 4592               for (gsize i = 0; i < len; i++)
 4593                 {
 4594                   VarChecksumRef c = var_arrayof_checksum_get_at (history, i);
 4595                   if (!add_to_history (repo, history_builder, c, digest_v, subsummary_content, digested_summary_cache,
 4596                                        &history_len, max_history_length, cancellable, error))
 4597                     return FALSE;
 4598                 }
 4599             }
 4600         }
 4601 
 4602       g_variant_dict_init (&subsummary_metadata_builder, NULL);
 4603       g_variant_builder_add (subsummary_builder, "{s(@ay@aay@a{sv})}",
 4604                              subsummary,
 4605                              digest_v,
 4606                              g_variant_builder_end (history_builder),
 4607                              g_variant_dict_end (&subsummary_metadata_builder));
 4608     }
 4609 
 4610   g_variant_builder_add_value (index_builder, g_variant_builder_end (subsummary_builder));
 4611   g_variant_builder_add_value (index_builder, g_variant_builder_end (metadata_builder));
 4612 
 4613   index = g_variant_ref_sink (g_variant_builder_end (index_builder));
 4614 
 4615   return g_steal_pointer (&index);
 4616 }
 4617 
 4618 static gboolean
 4619 flatpak_repo_gc_digested_summaries (OstreeRepo *repo,
 4620                                     const char *index_digest,           /* The digest of the current (new) index (if any) */
 4621                                     const char *old_index_digest,       /* The digest of the previous index (if any) */
 4622                                     GHashTable *digested_summaries,     /* generated */
 4623                                     GHashTable *digested_summary_cache, /* generated + referenced */
 4624                                     GCancellable *cancellable,
 4625                                     GError **error)
 4626 {
 4627   g_auto(GLnxDirFdIterator) iter = {0};
 4628   int repo_fd = ostree_repo_get_dfd (repo);
 4629   struct dirent *dent;
 4630   const char *ext;
 4631   g_autoptr(GError) local_error = NULL;
 4632 
 4633   if (!glnx_dirfd_iterator_init_at (repo_fd, "summaries", FALSE, &iter, &local_error))
 4634     {
 4635       if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
 4636         return TRUE;
 4637 
 4638       g_propagate_error (error, g_steal_pointer (&local_error));
 4639       return FALSE;
 4640     }
 4641 
 4642   while (TRUE)
 4643     {
 4644       gboolean remove = FALSE;
 4645 
 4646       if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, cancellable, error))
 4647         return FALSE;
 4648 
 4649       if (dent == NULL)
 4650         break;
 4651 
 4652       if (dent->d_type != DT_REG)
 4653         continue;
 4654 
 4655       /* Keep it if its an unexpected type */
 4656       ext = strchr (dent->d_name, '.');
 4657       if (ext != NULL)
 4658         {
 4659           if (strcmp (ext, ".gz") == 0 && strlen (dent->d_name) == 64 + 3)
 4660             {
 4661               char *sha256 = g_strndup (dent->d_name, 64);
 4662 
 4663               /* Keep all the referenced summaries */
 4664               if (g_hash_table_contains (digested_summary_cache, sha256))
 4665                 {
 4666                   g_debug ("Keeping referenced summary %s", dent->d_name);
 4667                   continue;
 4668                 }
 4669               /* Remove rest */
 4670               remove = TRUE;
 4671             }
 4672           else if (strcmp (ext, ".delta") == 0)
 4673             {
 4674               const char *dash = strchr (dent->d_name, '-');
 4675               if (dash != NULL && dash < ext && (ext - dash) == 1 + 64)
 4676                 {
 4677                   char *to_sha256 = g_strndup (dash + 1, 64);
 4678 
 4679                   /* Only keep deltas going to a generated summary */
 4680                   if (g_hash_table_contains (digested_summaries, to_sha256))
 4681                     {
 4682                       g_debug ("Keeping delta to generated summary %s", dent->d_name);
 4683                       continue;
 4684                     }
 4685                   /* Remove rest */
 4686                   remove = TRUE;
 4687                 }
 4688             }
 4689           else if (strcmp (ext, ".idx.sig") == 0)
 4690             {
 4691               g_autofree char *digest = g_strndup (dent->d_name, strlen (dent->d_name) - strlen (".idx.sig"));
 4692 
 4693               if (g_strcmp0 (digest, index_digest) == 0)
 4694                 continue; /* Always keep current */
 4695 
 4696               if (g_strcmp0 (digest, old_index_digest) == 0)
 4697                 continue; /* Always keep previous one, to avoid some races */
 4698 
 4699               /* Remove the rest */
 4700               remove = TRUE;
 4701             }
 4702         }
 4703 
 4704       if (remove)
 4705         {
 4706           g_debug ("Removing old digested summary file %s", dent->d_name);
 4707           if (unlinkat (iter.fd, dent->d_name, 0) != 0)
 4708             {
 4709               glnx_set_error_from_errno (error);
 4710               return FALSE;
 4711             }
 4712         }
 4713       else
 4714         g_debug ("Keeping unexpected summary file %s", dent->d_name);
 4715     }
 4716 
 4717   return TRUE;
 4718 }
 4719 
 4720 
 4721 /* Update the metadata in the summary file for @repo, and then re-sign the file.
 4722  * If the repo has a collection ID set, additionally store the metadata on a
 4723  * contentless commit in a well-known branch, which is the preferred way of
 4724  * broadcasting per-repo metadata (putting it in the summary file is deprecated,
 4725  * but kept for backwards compatibility).
 4726  *
 4727  * Note that there are two keys for the collection ID: collection-id, and
 4728  * ostree.deploy-collection-id. If a client does not currently have a
 4729  * collection ID configured for this remote, it will *only* update its
 4730  * configuration from ostree.deploy-collection-id.  This allows phased
 4731  * deployment of collection-based repositories. Clients will only update their
 4732  * configuration from an unset to a set collection ID once (otherwise the
 4733  * security properties of collection IDs are broken). */
 4734 gboolean
 4735 flatpak_repo_update (OstreeRepo   *repo,
 4736                      FlatpakRepoUpdateFlags flags,
 4737                      const char  **gpg_key_ids,
 4738                      const char   *gpg_homedir,
 4739                      GCancellable *cancellable,
 4740                      GError      **error)
 4741 {
 4742   g_autoptr(GHashTable) commit_data_cache = NULL;
 4743   g_autoptr(GVariant) compat_summary = NULL;
 4744   g_autoptr(GVariant) summary_index = NULL;
 4745   g_autoptr(GVariant) old_index = NULL;
 4746   g_autoptr(GPtrArray) delta_names = NULL;
 4747   g_auto(GStrv) summary_arches = NULL;
 4748   g_autoptr(GHashTable) refs = NULL;
 4749   g_autoptr(GHashTable) arches = NULL;
 4750   g_autoptr(GHashTable) subsets = NULL;
 4751   g_autoptr(GHashTable) summaries = NULL;
 4752   g_autoptr(GHashTable) digested_summaries = NULL;
 4753   g_autoptr(GHashTable) digested_summary_cache = NULL;
 4754   g_autoptr(GBytes) index_sig = NULL;
 4755   time_t old_compat_sig_mtime;
 4756   GKeyFile *config;
 4757   gboolean disable_index = (flags & FLATPAK_REPO_UPDATE_FLAG_DISABLE_INDEX) != 0;
 4758   g_autofree char *index_digest = NULL;
 4759   g_autofree char *old_index_digest = NULL;
 4760 
 4761   config = ostree_repo_get_config (repo);
 4762 
 4763   if (!ostree_repo_list_refs_ext (repo, NULL, &refs,
 4764                                   OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES | OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_MIRRORS,
 4765                                   cancellable, error))
 4766     return FALSE;
 4767 
 4768   old_index = flatpak_repo_load_summary_index (repo, NULL);
 4769   if (old_index)
 4770     commit_data_cache = populate_commit_data_cache (repo, old_index);
 4771 
 4772   if (commit_data_cache == NULL) /* No index or failed to load it */
 4773     commit_data_cache = commit_data_cache_new ();
 4774 
 4775   if (!ostree_repo_list_static_delta_names (repo, &delta_names, cancellable, error))
 4776     return FALSE;
 4777 
 4778   if (config)
 4779     summary_arches = g_key_file_get_string_list (config, "flatpak", "summary-arches", NULL, NULL);
 4780 
 4781   summaries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 4782   /* These are the ones we generated */
 4783   digested_summaries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
 4784   /* These are the ones generated or references */
 4785   digested_summary_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
 4786 
 4787   arches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 4788   subsets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 4789   g_hash_table_add (subsets, g_strdup ("")); /* Always have everything subset */
 4790 
 4791   GLNX_HASH_TABLE_FOREACH_KV (refs, const char *, ref, const char *, rev)
 4792     {
 4793       g_autofree char *arch = flatpak_get_arch_for_ref (ref);
 4794       CommitData *rev_data = NULL;
 4795 
 4796       if (arch != NULL &&
 4797           !g_hash_table_contains (arches, arch))
 4798         g_hash_table_add (arches, g_steal_pointer (&arch));
 4799 
 4800       /* Add CommitData for flatpak refs that we didn't already pre-populate */
 4801       if (is_flatpak_ref (ref))
 4802         {
 4803           rev_data = g_hash_table_lookup (commit_data_cache, rev);
 4804           if (rev_data == NULL)
 4805             {
 4806               rev_data = read_commit_data (repo, ref, rev, cancellable, error);
 4807               if (rev_data == NULL)
 4808                 return FALSE;
 4809 
 4810               g_hash_table_insert (commit_data_cache, g_strdup (rev), (CommitData *)rev_data);
 4811             }
 4812 
 4813           for (int i = 0; rev_data->subsets != NULL && i < rev_data->subsets->len; i++)
 4814             {
 4815               const char *subset = g_ptr_array_index (rev_data->subsets, i);
 4816               if (!g_hash_table_contains (subsets, subset))
 4817                 g_hash_table_add (subsets, g_strdup (subset));
 4818             }
 4819         }
 4820     }
 4821 
 4822   compat_summary = generate_summary (repo, TRUE, refs, commit_data_cache, delta_names,
 4823                                      "", (const char **)summary_arches,
 4824                                      cancellable, error);
 4825   if (compat_summary == NULL)
 4826     return FALSE;
 4827 
 4828   if (!disable_index)
 4829     {
 4830       GLNX_HASH_TABLE_FOREACH (subsets, const char *, subset)
 4831         {
 4832           GLNX_HASH_TABLE_FOREACH (arches, const char *, arch)
 4833             {
 4834               const char *arch_v[] = { arch, NULL };
 4835               g_autofree char *name = NULL;
 4836               g_autofree char *digest = NULL;
 4837 
 4838               if (*subset == 0)
 4839                 name = g_strdup (arch);
 4840               else
 4841                 name = g_strconcat (subset, "-", arch, NULL);
 4842 
 4843               g_autoptr(GVariant) arch_summary = generate_summary (repo, FALSE, refs, commit_data_cache, NULL, subset, arch_v,
 4844                                                                    cancellable, error);
 4845               if (arch_summary == NULL)
 4846                 return FALSE;
 4847 
 4848               digest = flatpak_repo_save_digested_summary (repo, name, arch_summary, cancellable, error);
 4849               if (digest == NULL)
 4850                 return FALSE;
 4851 
 4852               g_hash_table_insert (digested_summaries, g_strdup (digest), g_variant_ref (arch_summary));
 4853               /* Prime summary cache with generated summaries */
 4854               g_hash_table_insert (digested_summary_cache, g_strdup (digest), g_variant_ref (arch_summary));
 4855               g_hash_table_insert (summaries, g_steal_pointer (&name), g_steal_pointer (&digest));
 4856             }
 4857         }
 4858 
 4859       summary_index = generate_summary_index (repo, old_index, summaries, digested_summaries, digested_summary_cache,
 4860                                               gpg_key_ids, gpg_homedir,
 4861                                               cancellable, error);
 4862       if (summary_index == NULL)
 4863         return FALSE;
 4864     }
 4865 
 4866   if (!ostree_repo_static_delta_reindex (repo, 0, NULL, cancellable, error))
 4867     return FALSE;
 4868 
 4869   if (summary_index && gpg_key_ids)
 4870     {
 4871       g_autoptr(GBytes) index_bytes = g_variant_get_data_as_bytes (summary_index);
 4872 
 4873       if (!ostree_repo_gpg_sign_data (repo, index_bytes,
 4874                                       NULL,
 4875                                       gpg_key_ids,
 4876                                       gpg_homedir,
 4877                                       &index_sig,
 4878                                       cancellable,
 4879                                       error))
 4880         return FALSE;
 4881     }
 4882 
 4883   if (summary_index)
 4884     index_digest = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
 4885                                                 g_variant_get_data (summary_index),
 4886                                                 g_variant_get_size (summary_index));
 4887   if (old_index)
 4888     old_index_digest = g_compute_checksum_for_data (G_CHECKSUM_SHA256,
 4889                                                     g_variant_get_data (old_index),
 4890                                                     g_variant_get_size (old_index));
 4891 
 4892   if (!flatpak_repo_save_summary_index (repo, summary_index, index_digest, index_sig, cancellable, error))
 4893     return FALSE;
 4894 
 4895   if (!flatpak_repo_save_compat_summary (repo, compat_summary, &old_compat_sig_mtime, cancellable, error))
 4896     return FALSE;
 4897 
 4898   if (gpg_key_ids)
 4899     {
 4900       if (!ostree_repo_add_gpg_signature_summary (repo,
 4901                                                   gpg_key_ids,
 4902                                                   gpg_homedir,
 4903                                                   cancellable,
 4904                                                   error))
 4905         return FALSE;
 4906 
 4907 
 4908       if (old_compat_sig_mtime != 0)
 4909         {
 4910           int repo_dfd = ostree_repo_get_dfd (repo);
 4911           struct stat stbuf;
 4912 
 4913           /* Ensure we increase (in sec precision) */
 4914           if (fstatat (repo_dfd, "summary.sig", &stbuf, AT_SYMLINK_NOFOLLOW) == 0 &&
 4915               stbuf.st_mtime <= old_compat_sig_mtime)
 4916             {
 4917               struct timespec ts[2] = { {0, UTIME_OMIT}, {old_compat_sig_mtime + 1, 0} };
 4918               (void) utimensat (repo_dfd, "summary.sig", ts, AT_SYMLINK_NOFOLLOW);
 4919             }
 4920         }
 4921     }
 4922 
 4923   if (!disable_index &&
 4924       !flatpak_repo_gc_digested_summaries (repo, index_digest, old_index_digest, digested_summaries, digested_summary_cache, cancellable, error))
 4925     return FALSE;
 4926 
 4927   return TRUE;
 4928 }
 4929 
 4930 gboolean
 4931 flatpak_mtree_create_dir (OstreeRepo         *repo,
 4932                           OstreeMutableTree  *parent,
 4933                           const char         *name,
 4934                           OstreeMutableTree **dir_out,
 4935                           GError            **error)
 4936 {
 4937   g_autoptr(OstreeMutableTree) dir = NULL;
 4938 
 4939   if (!ostree_mutable_tree_ensure_dir (parent, name, &dir, error))
 4940     return FALSE;
 4941 
 4942   if (!flatpak_mtree_ensure_dir_metadata (repo, dir, NULL, error))
 4943     return FALSE;
 4944 
 4945   *dir_out = g_steal_pointer (&dir);
 4946   return TRUE;
 4947 }
 4948 
 4949 gboolean
 4950 flatpak_mtree_create_symlink (OstreeRepo         *repo,
 4951                               OstreeMutableTree  *parent,
 4952                               const char         *filename,
 4953                               const char         *target,
 4954                               GError            **error)
 4955 {
 4956   g_autoptr(GFileInfo) file_info = g_file_info_new ();
 4957   g_autoptr(GInputStream) content_stream = NULL;
 4958   g_autofree guchar *raw_checksum = NULL;
 4959   g_autofree char *checksum = NULL;
 4960   guint64 length;
 4961 
 4962   g_file_info_set_name (file_info, filename);
 4963   g_file_info_set_file_type (file_info, G_FILE_TYPE_SYMBOLIC_LINK);
 4964   g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0);
 4965   g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0);
 4966   g_file_info_set_attribute_uint32 (file_info, "unix::mode", S_IFLNK | 0777);
 4967 
 4968   g_file_info_set_attribute_boolean (file_info, "standard::is-symlink", TRUE);
 4969   g_file_info_set_attribute_byte_string (file_info, "standard::symlink-target", target);
 4970 
 4971   if (!ostree_raw_file_to_content_stream (NULL, file_info, NULL,
 4972                                           &content_stream, &length,
 4973                                           NULL, error))
 4974     return FALSE;
 4975 
 4976   if (!ostree_repo_write_content (repo, NULL, content_stream, length,
 4977                                   &raw_checksum, NULL, error))
 4978     return FALSE;
 4979 
 4980   checksum = ostree_checksum_from_bytes (raw_checksum);
 4981 
 4982   if (!ostree_mutable_tree_replace_file (parent, filename, checksum, error))
 4983     return FALSE;
 4984 
 4985   return TRUE;
 4986 }
 4987 
 4988 gboolean
 4989 flatpak_mtree_add_file_from_bytes (OstreeRepo *repo,
 4990                                    GBytes *bytes,
 4991                                    OstreeMutableTree *parent,
 4992                                    const char *filename,
 4993                                    GCancellable *cancellable,
 4994                                    GError      **error)
 4995 {
 4996   g_autoptr(GFileInfo) info = g_file_info_new ();
 4997   g_autoptr(GInputStream) memstream = NULL;
 4998   g_autoptr(GInputStream) content_stream = NULL;
 4999   g_autofree guchar *raw_checksum = NULL;
 5000   g_autofree char *checksum = NULL;
 5001   guint64 length;
 5002 
 5003   g_file_info_set_attribute_uint32 (info, "standard::type", G_FILE_TYPE_REGULAR);
 5004   g_file_info_set_attribute_uint64 (info, "standard::size", g_bytes_get_size (bytes));
 5005   g_file_info_set_attribute_uint32 (info, "unix::uid", 0);
 5006   g_file_info_set_attribute_uint32 (info, "unix::gid", 0);
 5007   g_file_info_set_attribute_uint32 (info, "unix::mode", S_IFREG | 0644);
 5008 
 5009   memstream = g_memory_input_stream_new_from_bytes (bytes);
 5010 
 5011   if (!ostree_raw_file_to_content_stream (memstream, info, NULL,
 5012                                           &content_stream, &length,
 5013                                           cancellable, error))
 5014     return FALSE;
 5015 
 5016   if (!ostree_repo_write_content (repo, NULL, content_stream, length,
 5017                                   &raw_checksum, cancellable, error))
 5018     return FALSE;
 5019 
 5020   checksum = ostree_checksum_from_bytes (raw_checksum);
 5021 
 5022   if (!ostree_mutable_tree_replace_file (parent, filename, checksum, error))
 5023     return FALSE;
 5024 
 5025   return TRUE;
 5026 }
 5027 
 5028 gboolean
 5029 flatpak_mtree_ensure_dir_metadata (OstreeRepo        *repo,
 5030                                    OstreeMutableTree *mtree,
 5031                                    GCancellable      *cancellable,
 5032                                    GError           **error)
 5033 {
 5034   g_autoptr(GVariant) dirmeta = NULL;
 5035   g_autoptr(GFileInfo) file_info = g_file_info_new ();
 5036   g_autofree guchar *csum = NULL;
 5037   g_autofree char *checksum = NULL;
 5038 
 5039   g_file_info_set_name (file_info, "/");
 5040   g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY);
 5041   g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0);
 5042   g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0);
 5043   g_file_info_set_attribute_uint32 (file_info, "unix::mode", 040755);
 5044 
 5045   dirmeta = ostree_create_directory_metadata (file_info, NULL);
 5046   if (!ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, NULL,
 5047                                    dirmeta, &csum, cancellable, error))
 5048     return FALSE;
 5049 
 5050   checksum = ostree_checksum_from_bytes (csum);
 5051   ostree_mutable_tree_set_metadata_checksum (mtree, checksum);
 5052 
 5053   return TRUE;
 5054 }
 5055 
 5056 static gboolean
 5057 validate_component (FlatpakXml *component,
 5058                     const char *ref,
 5059                     const char *id,
 5060                     char      **tags,
 5061                     const char *runtime,
 5062                     const char *sdk)
 5063 {
 5064   FlatpakXml *bundle, *text, *prev, *id_node, *id_text_node, *metadata, *value;
 5065   g_autofree char *id_text = NULL;
 5066   int i;
 5067 
 5068   if (g_strcmp0 (component->element_name, "component") != 0)
 5069     return FALSE;
 5070 
 5071   id_node = flatpak_xml_find (component, "id", NULL);
 5072   if (id_node == NULL)
 5073     return FALSE;
 5074 
 5075   id_text_node = flatpak_xml_find (id_node, NULL, NULL);
 5076   if (id_text_node == NULL || id_text_node->text == NULL)
 5077     return FALSE;
 5078 
 5079   id_text = g_strstrip (g_strdup (id_text_node->text));
 5080 
 5081   /* Drop .desktop file suffix (unless the actual app id ends with .desktop) */
 5082   if (g_str_has_suffix (id_text, ".desktop") &&
 5083       !g_str_has_suffix (id, ".desktop"))
 5084     id_text[strlen (id_text) - strlen (".desktop")] = 0;
 5085 
 5086   if (!g_str_has_prefix (id_text, id))
 5087     {
 5088       g_warning ("Invalid id %s", id_text);
 5089       return FALSE;
 5090     }
 5091 
 5092   while ((bundle = flatpak_xml_find (component, "bundle", &prev)) != NULL)
 5093     flatpak_xml_free (flatpak_xml_unlink (bundle, prev));
 5094 
 5095   bundle = flatpak_xml_new ("bundle");
 5096   bundle->attribute_names = g_new0 (char *, 2 * 4);
 5097   bundle->attribute_values = g_new0 (char *, 2 * 4);
 5098   bundle->attribute_names[0] = g_strdup ("type");
 5099   bundle->attribute_values[0] = g_strdup ("flatpak");
 5100 
 5101   i = 1;
 5102   if (runtime && !g_str_has_prefix (runtime, "runtime/"))
 5103     {
 5104       bundle->attribute_names[i] = g_strdup ("runtime");
 5105       bundle->attribute_values[i] = g_strdup (runtime);
 5106       i++;
 5107     }
 5108 
 5109   if (sdk)
 5110     {
 5111       bundle->attribute_names[i] = g_strdup ("sdk");
 5112       bundle->attribute_values[i] = g_strdup (sdk);
 5113       i++;
 5114     }
 5115 
 5116   text = flatpak_xml_new (NULL);
 5117   text->text = g_strdup (ref);
 5118   flatpak_xml_add (bundle, text);
 5119 
 5120   flatpak_xml_add (component, flatpak_xml_new_text ("  "));
 5121   flatpak_xml_add (component, bundle);
 5122   flatpak_xml_add (component, flatpak_xml_new_text ("\n  "));
 5123 
 5124   if (tags != NULL && tags[0] != NULL)
 5125     {
 5126       metadata = flatpak_xml_find (component, "metadata", NULL);
 5127       if (metadata == NULL)
 5128         {
 5129           metadata = flatpak_xml_new ("metadata");
 5130           metadata->attribute_names = g_new0 (char *, 1);
 5131           metadata->attribute_values = g_new0 (char *, 1);
 5132 
 5133           flatpak_xml_add (component, flatpak_xml_new_text ("  "));
 5134           flatpak_xml_add (component, metadata);
 5135           flatpak_xml_add (component, flatpak_xml_new_text ("\n  "));
 5136         }
 5137 
 5138       value = flatpak_xml_new ("value");
 5139       value->attribute_names = g_new0 (char *, 2);
 5140       value->attribute_values = g_new0 (char *, 2);
 5141       value->attribute_names[0] = g_strdup ("key");
 5142       value->attribute_values[0] = g_strdup ("X-Flatpak-Tags");
 5143       flatpak_xml_add (metadata, flatpak_xml_new_text ("\n       "));
 5144       flatpak_xml_add (metadata, value);
 5145       flatpak_xml_add (metadata, flatpak_xml_new_text ("\n    "));
 5146 
 5147       text = flatpak_xml_new (NULL);
 5148       text->text = g_strjoinv (",", tags);
 5149       flatpak_xml_add (value, text);
 5150     }
 5151 
 5152   return TRUE;
 5153 }
 5154 
 5155 gboolean
 5156 flatpak_appstream_xml_migrate (FlatpakXml *source,
 5157                                FlatpakXml *dest,
 5158                                const char *ref,
 5159                                const char *id,
 5160                                GKeyFile   *metadata)
 5161 {
 5162   FlatpakXml *source_components;
 5163   FlatpakXml *dest_components;
 5164   FlatpakXml *component;
 5165   FlatpakXml *prev_component;
 5166   gboolean migrated = FALSE;
 5167   g_auto(GStrv) tags = NULL;
 5168   g_autofree const char *runtime = NULL;
 5169   g_autofree const char *sdk = NULL;
 5170   const char *group;
 5171 
 5172   if (source->first_child == NULL ||
 5173       source->first_child->next_sibling != NULL ||
 5174       g_strcmp0 (source->first_child->element_name, "components") != 0)
 5175     return FALSE;
 5176 
 5177   if (g_str_has_prefix (ref, "app/"))
 5178     group = FLATPAK_METADATA_GROUP_APPLICATION;
 5179   else
 5180     group = FLATPAK_METADATA_GROUP_RUNTIME;
 5181 
 5182   tags = g_key_file_get_string_list (metadata, group, FLATPAK_METADATA_KEY_TAGS,
 5183                                      NULL, NULL);
 5184   runtime = g_key_file_get_string (metadata, group,
 5185                                    FLATPAK_METADATA_KEY_RUNTIME, NULL);
 5186   sdk = g_key_file_get_string (metadata, group, FLATPAK_METADATA_KEY_SDK, NULL);
 5187 
 5188   source_components = source->first_child;
 5189   dest_components = dest->first_child;
 5190 
 5191   component = source_components->first_child;
 5192   prev_component = NULL;
 5193   while (component != NULL)
 5194     {
 5195       FlatpakXml *next = component->next_sibling;
 5196 
 5197       if (validate_component (component, ref, id, tags, runtime, sdk))
 5198         {
 5199           flatpak_xml_add (dest_components,
 5200                            flatpak_xml_unlink (component, prev_component));
 5201           migrated = TRUE;
 5202         }
 5203       else
 5204         {
 5205           prev_component = component;
 5206         }
 5207 
 5208       component = next;
 5209     }
 5210 
 5211   return migrated;
 5212 }
 5213 
 5214 static gboolean
 5215 copy_icon (const char        *id,
 5216            GFile             *icons_dir,
 5217            OstreeRepo        *repo,
 5218            OstreeMutableTree *size_mtree,
 5219            const char        *size,
 5220            GError           **error)
 5221 {
 5222   g_autofree char *icon_name = g_strconcat (id, ".png", NULL);
 5223   g_autoptr(GFile) size_dir = g_file_get_child (icons_dir, size);
 5224   g_autoptr(GFile) icon_file = g_file_get_child (size_dir, icon_name);
 5225   const char *checksum;
 5226 
 5227   if (!ostree_repo_file_ensure_resolved (OSTREE_REPO_FILE(icon_file), NULL))
 5228     {
 5229       g_debug ("No icon at size %s for %s", size, id);
 5230       return TRUE;
 5231     }
 5232 
 5233   checksum = ostree_repo_file_get_checksum (OSTREE_REPO_FILE(icon_file));
 5234   if (!ostree_mutable_tree_replace_file (size_mtree, icon_name, checksum, error))
 5235     return FALSE;
 5236 
 5237   return TRUE;
 5238 }
 5239 
 5240 static gboolean
 5241 extract_appstream (OstreeRepo        *repo,
 5242                    FlatpakXml        *appstream_root,
 5243                    FlatpakDecomposed *ref,
 5244                    const char        *id,
 5245                    OstreeMutableTree *size1_mtree,
 5246                    OstreeMutableTree *size2_mtree,
 5247                    GCancellable       *cancellable,
 5248                    GError            **error)
 5249 {
 5250   g_autoptr(GFile) root = NULL;
 5251   g_autoptr(GFile) app_info_dir = NULL;
 5252   g_autoptr(GFile) xmls_dir = NULL;
 5253   g_autoptr(GFile) icons_dir = NULL;
 5254   g_autoptr(GFile) appstream_file = NULL;
 5255   g_autoptr(GFile) metadata = NULL;
 5256   g_autofree char *appstream_basename = NULL;
 5257   g_autoptr(GInputStream) in = NULL;
 5258   g_autoptr(FlatpakXml) xml_root = NULL;
 5259   g_autoptr(GKeyFile) keyfile = NULL;
 5260 
 5261   if (!ostree_repo_read_commit (repo, flatpak_decomposed_get_ref (ref), &root, NULL, NULL, error))
 5262     return FALSE;
 5263 
 5264   keyfile = g_key_file_new ();
 5265   metadata = g_file_get_child (root, "metadata");
 5266   if (g_file_query_exists (metadata, cancellable))
 5267     {
 5268       g_autofree char *content = NULL;
 5269       gsize len;
 5270 
 5271       if (!g_file_load_contents (metadata, cancellable, &content, &len, NULL, error))
 5272         return FALSE;
 5273 
 5274       if (!g_key_file_load_from_data (keyfile, content, len, G_KEY_FILE_NONE, error))
 5275         return FALSE;
 5276     }
 5277 
 5278   app_info_dir = g_file_resolve_relative_path (root, "files/share/app-info");
 5279 
 5280   xmls_dir = g_file_resolve_relative_path (app_info_dir, "xmls");
 5281   icons_dir = g_file_resolve_relative_path (app_info_dir, "icons/flatpak");
 5282 
 5283   appstream_basename = g_strconcat (id, ".xml.gz", NULL);
 5284   appstream_file = g_file_get_child (xmls_dir, appstream_basename);
 5285 
 5286   in = (GInputStream *) g_file_read (appstream_file, cancellable, error);
 5287   if (!in)
 5288     return FALSE;
 5289 
 5290   xml_root = flatpak_xml_parse (in, TRUE, cancellable, error);
 5291   if (xml_root == NULL)
 5292     return FALSE;
 5293 
 5294   if (flatpak_appstream_xml_migrate (xml_root, appstream_root,
 5295                                      flatpak_decomposed_get_ref (ref), id, keyfile))
 5296     {
 5297       g_autoptr(GError) my_error = NULL;
 5298       FlatpakXml *components = appstream_root->first_child;
 5299       FlatpakXml *component = components->first_child;
 5300 
 5301       while (component != NULL)
 5302         {
 5303           FlatpakXml *component_id, *component_id_text_node;
 5304           g_autofree char *component_id_text = NULL;
 5305           char *component_id_suffix;
 5306 
 5307           if (g_strcmp0 (component->element_name, "component") != 0)
 5308             {
 5309               component = component->next_sibling;
 5310               continue;
 5311             }
 5312 
 5313           component_id = flatpak_xml_find (component, "id", NULL);
 5314           component_id_text_node = flatpak_xml_find (component_id, NULL, NULL);
 5315 
 5316           component_id_text = g_strstrip (g_strdup (component_id_text_node->text));
 5317 
 5318           /* We're looking for a component that matches the app-id (id), but it
 5319              may have some further elements (separated by dot) and can also have
 5320              ".desktop" at the end which we need to strip out. Further complicating
 5321              things, some actual app ids ends in .desktop, such as org.telegram.desktop. */
 5322 
 5323           component_id_suffix = component_id_text + strlen (id); /* Don't deref before we check for prefix match! */
 5324           if (!g_str_has_prefix (component_id_text, id) ||
 5325               (component_id_suffix[0] != 0 && component_id_suffix[0] != '.'))
 5326             {
 5327               component = component->next_sibling;
 5328               continue;
 5329             }
 5330 
 5331           if (g_str_has_suffix (component_id_suffix, ".desktop"))
 5332             component_id_suffix[strlen (component_id_suffix) - strlen (".desktop")] = 0;
 5333 
 5334           if (!copy_icon (component_id_text, icons_dir, repo, size1_mtree, "64x64", &my_error))
 5335             {
 5336               g_print (_("Error copying 64x64 icon for component %s: %s\n"), component_id_text, my_error->message);
 5337               g_clear_error (&my_error);
 5338             }
 5339 
 5340           if (!copy_icon (component_id_text, icons_dir, repo, size2_mtree, "128x128", &my_error))
 5341              {
 5342                g_print (_("Error copying 128x128 icon for component %s: %s\n"), component_id_text, my_error->message);
 5343                g_clear_error (&my_error);
 5344              }
 5345 
 5346 
 5347           /* We might match other prefixes, so keep on going */
 5348           component = component->next_sibling;
 5349         }
 5350     }
 5351 
 5352   return TRUE;
 5353 }
 5354 
 5355 FlatpakXml *
 5356 flatpak_appstream_xml_new (void)
 5357 {
 5358   FlatpakXml *appstream_root = NULL;
 5359   FlatpakXml *appstream_components;
 5360 
 5361   appstream_root = flatpak_xml_new ("root");
 5362   appstream_components = flatpak_xml_new ("components");
 5363   flatpak_xml_add (appstream_root, appstream_components);
 5364   flatpak_xml_add (appstream_components, flatpak_xml_new_text ("\n  "));
 5365 
 5366   appstream_components->attribute_names = g_new0 (char *, 3);
 5367   appstream_components->attribute_values = g_new0 (char *, 3);
 5368   appstream_components->attribute_names[0] = g_strdup ("version");
 5369   appstream_components->attribute_values[0] = g_strdup ("0.8");
 5370   appstream_components->attribute_names[1] = g_strdup ("origin");
 5371   appstream_components->attribute_values[1] = g_strdup ("flatpak");
 5372 
 5373   return appstream_root;
 5374 }
 5375 
 5376 gboolean
 5377 flatpak_appstream_xml_root_to_data (FlatpakXml *appstream_root,
 5378                                     GBytes    **uncompressed,
 5379                                     GBytes    **compressed,
 5380                                     GError    **error)
 5381 {
 5382   g_autoptr(GString) xml = NULL;
 5383   g_autoptr(GZlibCompressor) compressor = NULL;
 5384   g_autoptr(GOutputStream) out2 = NULL;
 5385   g_autoptr(GOutputStream) out = NULL;
 5386 
 5387   flatpak_xml_add (appstream_root->first_child, flatpak_xml_new_text ("\n"));
 5388 
 5389   xml = g_string_new ("");
 5390   flatpak_xml_to_string (appstream_root, xml);
 5391 
 5392   if (compressed)
 5393     {
 5394       compressor = g_zlib_compressor_new