deploy: support devicetree directory
authorStefan Agner <stefan.agner@toradex.com>
Tue, 4 Feb 2020 12:39:27 +0000 (13:39 +0100)
committerStefan Agner <stefan.agner@toradex.com>
Fri, 29 May 2020 17:56:11 +0000 (19:56 +0200)
Add support for a devicetree directory at /usr/lib/modules/$kver/dtb/.
In ARM world a general purpose distribution often suppports multiple
boards with a single operating system. However, OSTree currently only
supports a single device tree, which does not allow to use the same
OSTree on different ARM machines. In this scenario typically the boot
loader selects the effective device tree.

This adds device tree directory support for the new boot artefact
location under /usr/lib/modules. If the file `devicetree` does not
exist, then the folder dtb will be checked. All devicetrees are hashed
into the deployment hash. This makes sure that even a single devicetree
change leads to a new deployment and hence can be rolled back.

The loader configuration has a new key "devicetreepath" which contains
the path where devicetrees are stored. This is also written to the
U-Boot variable "fdtdir". The boot loader is expected to use this path
to load a particular machines device tree from.

Closes: #1900
Signed-off-by: Stefan Agner <stefan.agner@toradex.com>
src/libostree/ostree-bootloader-uboot.c
src/libostree/ostree-sysroot-deploy.c
tests/test-admin-deploy-uboot.sh

index 4cd955d5f4270c2afa788edc3a2b8388a914c49e..1e1f0371d7de04b39bff272683a705b26d5c28da 100644 (file)
@@ -144,6 +144,10 @@ create_config_from_boot_loader_entries (OstreeBootloaderUboot     *self,
       if (val)
         g_ptr_array_add (new_lines, g_strdup_printf ("fdt_file%s=%s", index_suffix, val));
 
+      val = ostree_bootconfig_parser_get (config, "fdtdir");
+      if (val)
+        g_ptr_array_add (new_lines, g_strdup_printf ("fdtdir%s=%s", index_suffix, val));
+
       val = ostree_bootconfig_parser_get (config, "options");
       if (val)
         {
index ee00c02c5e5ee17e1d92ff2876ef9914f9ed3a5e..cb593020876c7bc5515880f59136761de9c2fc67 100644 (file)
@@ -217,6 +217,79 @@ dirfd_copy_attributes_and_xattrs (int            src_parent_dfd,
   return TRUE;
 }
 
+static gint
+str_sort_cb (gconstpointer name_ptr_a, gconstpointer name_ptr_b)
+{
+  const gchar *name_a = *((const gchar **) name_ptr_a);
+  const gchar *name_b = *((const gchar **) name_ptr_b);
+
+  return g_strcmp0 (name_a, name_b);
+}
+
+static gboolean
+checksum_dir_recurse (int          dfd,
+                  const char      *path,
+                  OtChecksum      *checksum,
+                  GCancellable    *cancellable,
+                  GError         **error)
+{
+  g_auto(GLnxDirFdIterator) dfditer = { 0, };
+  g_autoptr (GPtrArray) d_entries = g_ptr_array_new_with_free_func (g_free);
+
+  if (!glnx_dirfd_iterator_init_at (dfd, path, TRUE, &dfditer, error))
+    return FALSE;
+
+  while (TRUE)
+    {
+      struct dirent *dent;
+
+      if (!glnx_dirfd_iterator_next_dent (&dfditer, &dent, cancellable, error))
+        return FALSE;
+
+      if (dent == NULL)
+        break;
+
+      g_ptr_array_add (d_entries, g_strdup (dent->d_name));
+    }
+
+  /* File systems do not guarantee dir entry order, make sure this is
+   * reproducable
+   */
+  g_ptr_array_sort(d_entries, str_sort_cb);
+
+  for (gint i=0; i < d_entries->len; i++)
+    {
+      const gchar *d_name = (gchar *)g_ptr_array_index (d_entries, i);
+      struct stat stbuf;
+
+      if (!glnx_fstatat (dfditer.fd, d_name, &stbuf,
+                         AT_SYMLINK_NOFOLLOW, error))
+        return FALSE;
+
+      if (S_ISDIR (stbuf.st_mode))
+        {
+          if (!checksum_dir_recurse(dfditer.fd, d_name, checksum, cancellable, error))
+            return FALSE;
+        }
+      else
+        {
+          int fd;
+
+          if (!ot_openat_ignore_enoent (dfditer.fd, d_name, &fd, error))
+            return FALSE;
+          if (fd != -1)
+            {
+              g_autoptr(GInputStream) in = g_unix_input_stream_new (fd, FALSE);
+              if (!ot_gio_splice_update_checksum (NULL, in, checksum, cancellable, error))
+                return FALSE;
+            }
+        }
+
+    }
+
+  return TRUE;
+}
+
 static gboolean
 copy_dir_recurse (int              src_parent_dfd,
                   int              dest_parent_dfd,
@@ -1065,6 +1138,9 @@ get_kernel_from_tree_usrlib_modules (int                  deployment_dfd,
   g_clear_object (&in);
   glnx_close_fd (&fd);
 
+  /* Check for /usr/lib/modules/$kver/devicetree first, if it does not
+   * exist check for /usr/lib/modules/$kver/dtb/ directory.
+   */
   if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "devicetree", &fd, error))
     return FALSE;
   if (fd != -1)
@@ -1075,6 +1151,23 @@ get_kernel_from_tree_usrlib_modules (int                  deployment_dfd,
       if (!ot_gio_splice_update_checksum (NULL, in, &checksum, cancellable, error))
         return FALSE;
     }
+  else
+    {
+      struct stat stbuf;
+      /* Check for dtb directory */
+      if (!glnx_fstatat_allow_noent (ret_layout->boot_dfd, "dtb", &stbuf, 0, error))
+        return FALSE;
+
+      if (errno == 0 && S_ISDIR (stbuf.st_mode))
+        {
+          /* devicetree_namever set to NULL indicates a complete directory */
+          ret_layout->devicetree_srcpath = g_strdup ("dtb");
+          ret_layout->devicetree_namever = NULL;
+
+          if (!checksum_dir_recurse(ret_layout->boot_dfd, "dtb", &checksum, cancellable, error))
+            return FALSE;
+        }
+    }
 
   g_clear_object (&in);
   glnx_close_fd (&fd);
@@ -1730,15 +1823,24 @@ install_deployment_kernel (OstreeSysroot   *sysroot,
 
   if (kernel_layout->devicetree_srcpath)
     {
-      g_assert (kernel_layout->devicetree_namever);
-      if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->devicetree_namever, &stbuf, 0, error))
-        return FALSE;
-      if (errno == ENOENT)
+      /* If devicetree_namever is set a single device tree is deployed */
+      if (kernel_layout->devicetree_namever)
         {
-          if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
-                                  bootcsum_dfd, kernel_layout->devicetree_namever,
-                                  sysroot->debug_flags,
-                                  cancellable, error))
+          if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->devicetree_namever, &stbuf, 0, error))
+            return FALSE;
+          if (errno == ENOENT)
+            {
+              if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
+                                      bootcsum_dfd, kernel_layout->devicetree_namever,
+                                      sysroot->debug_flags,
+                                      cancellable, error))
+                return FALSE;
+            }
+        }
+      else
+        {
+          if (!copy_dir_recurse(kernel_layout->boot_dfd, bootcsum_dfd, kernel_layout->devicetree_srcpath,
+                                sysroot->debug_flags, cancellable, error))
             return FALSE;
         }
     }
@@ -1850,6 +1952,15 @@ install_deployment_kernel (OstreeSysroot   *sysroot,
       g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_namever, NULL);
       ostree_bootconfig_parser_set (bootconfig, "devicetree", boot_relpath);
     }
+  else if (kernel_layout->devicetree_srcpath)
+    {
+      /* If devicetree_srcpath is set but devicetree_namever is NULL, then we
+       * want to point to a whole directory of device trees.
+       * See: https://github.com/ostreedev/ostree/issues/1900
+       */
+      g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_srcpath, NULL);
+      ostree_bootconfig_parser_set (bootconfig, "fdtdir", boot_relpath);
+    }
 
   /* Note this is parsed in ostree-impl-system-generator.c */
   g_autofree char *ostree_kernel_arg = g_strdup_printf ("ostree=/ostree/boot.%d/%s/%s/%d",
index 8ea37fe90910e41e6b6532b4648eb5a75e0d3f69..e3163cb0f5b6cd32862f2c665a0e755b779e13bd 100755 (executable)
@@ -25,9 +25,11 @@ set -euo pipefail
 . $(dirname $0)/libtest.sh
 
 # Exports OSTREE_SYSROOT so --sysroot not needed.
-setup_os_repository "archive" "uboot"
+kver="3.6.0"
+modulesdir="usr/lib/modules/${kver}"
+setup_os_repository "archive" "uboot" ${modulesdir}
 
-extra_admin_tests=1
+extra_admin_tests=2
 
 . $(dirname $0)/admin-test.sh
 
@@ -52,3 +54,27 @@ assert_file_has_content sysroot/boot/uEnv.txt "kernel_image2="
 assert_file_has_content sysroot/boot/uEnv.txt "kernel_image3="
 
 echo "ok merging uEnv.txt files"
+
+cd ${test_tmpdir}
+os_repository_new_commit "uboot test" "test with device tree directory"
+
+devicetree_path=osdata/${modulesdir}/dtb/asoc-board.dtb
+devicetree_overlay_path=osdata/${modulesdir}/dtb/overlays/overlay.dtbo
+
+mkdir -p osdata/${modulesdir}/dtb
+echo "a device tree" > ${devicetree_path}
+mkdir -p osdata/${modulesdir}/dtb/overlays
+echo "a device tree overlay" > ${devicetree_overlay_path}
+
+bootcsum=$(
+  (echo "new: a kernel uboot test" && echo "new: an initramfs uboot test" &&
+    cat ${devicetree_path} ${devicetree_overlay_path} ) |
+  sha256sum | cut -f 1 -d ' ')
+
+${CMD_PREFIX} ostree --repo=testos-repo commit --tree=dir=osdata/ -b testos/buildmaster/x86_64-runtime
+${CMD_PREFIX} ostree admin upgrade --os=testos
+assert_file_has_content sysroot/boot/uEnv.txt "fdtdir="
+assert_file_has_content sysroot/boot/ostree/testos-${bootcsum}/dtb/asoc-board.dtb 'a device tree'
+assert_file_has_content sysroot/boot/ostree/testos-${bootcsum}/dtb/overlays/overlay.dtbo 'a device tree overlay'
+
+echo "ok deploying fdtdir"