summaryrefslogtreecommitdiffstats
path: root/doc/ota.qdoc
blob: 0f2fb587f3ab2d37b0443ec6f8955cd714d97293 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt OTA Update module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*!
    \page qtota-index.html
    \title Over-The-Air Update

    Over-The-Air (OTA) update is a mechanism of distributing software updates
    over a wireless network without requiring physical access to a device.
    The target device needs to have a support for the OTA to be able to update
    wirelessly.

    The \l {http://code.qt.io/cgit/qt/qtotaupdate.git/} {Qt OTA Update} module provides tools
    that assist in enabling OTA update functionality in an embedded linux images build with
    \l {http://code.qt.io/cgit/yocto/meta-boot2qt.git/} {meta-boot2qt}. Generating new updates
    for OTA enabled devices is completely automated, given an ordinary linux sysroot as an input.
    This includes OTA updates for linux kernel, system libraries, user space applications,
    translation fixes, anything that is part of the sysroot. The offering includes
    \l {Qt OTA Update C++ Classes} {C++} and \l {Qt OTA Update QML Types} {QML} APIs to make
    integration with your Qt-based application a breeze.

    The OTA solution is based on \l {https://ostree.readthedocs.org/en/latest/} {OSTree}. If you
    would like to learn more about OSTree workings refer to the OSTree Documentation. There you
    can read about the anatomy of an OSTree repository and the deployment system, booting, and
    other internals of the project, as well as how OSTree compares to other update solutions.

    The following blog post series contain additional details on the Qt OTA Update module:

    \l {https://blog.qt.io/blog/2016/05/31/over-the-air-updates-part-1-introduction/}
       {Over-the-Air Updates, Part 1: Introduction}\br
    \l {https://blog.qt.io/blog/2016/06/28/over-the-air-updates-part-2-device-integration-api-and-creating-updates/}
       {Over-the-Air Updates, Part 2: Device Integration, API and Creating Updates}\br
    \l {https://blog.qt.io/blog/2016/11/11/air-updates-part-3-repository-configuration-handling/}
       {Over-the-Air Updates, Part 3: Repository Configuration and Handling}

    \section1 Features of the Update System

    \list
    \li Atomic Upgrades (all or nothing) - if an update did not fully complete,
        for example due to a power failure, the system will boot into an unmodified
        tree. The currently running tree is never modified, the update will become
        active on a system reboot.
    \li Secure - GPG signing and pinned TLS with client and server side authentication
        support.
    \li Efficient Handling of Disk Space - see the \c {/ostree} and the \c {/var}
        in \l {Layout of an OTA Enabled Sysroot}.
    \li Snapshot-based - traditional package managers (dpkg/rpm) build filesystem
        trees on the client side. In contrast, the primary focus of OSTree is on
        \e {replicating trees} composed on a server.
    \li Bandwidth Optimized - only the new files and the files that have changed are
        downloaded. When resuming from an interrupted download, only the missing files
        are fetched.
    \li Configuration Management - see the \c {/etc} in \l {Layout of an OTA
        Enabled Sysroot}.
    \li Rollback Support - atomically rollback to the previous version (tree) if
        something goes wrong.
    \li Updates Processing in Background - no unnecessary downtime for a user.
    \li OS updates via OTA, with support for agnostic application delivery mechanism on top.
    \endlist

    \section1 Requirements

    \list 1

      \li Filesystem.

      OSTree operates in userspace, and will work on top of any Linux filesystem
      that supports hard and symbolic links. For OSTree to function reliably, the
      filesystem needs to be able to recover to a consistent state after an unclean
      shutdown. Any journaling or log-structured filesystem, when configured properly,
      is capable of such recovery.

      \li Boot Loader.

      Supported boot loaders are: U-Boot, GRUB 2. \br

    \endlist

    \section1 Quick Start Guide

    This guide will lead you through the full workflow of how to use the provided
    OTA tools.
    \list
      \li Adding the OTA capability to a device before shipping it to a customer.
      \li Generating an update from the new version of your product's sysroot.
      \li Delivering this update to a customer device via OTA.
      \li Securing a delivery of an update.
      \li Support for custom update delivery mechanisms.
    \endlist

    \section2 Installation

    OTA package is distributed with \l {Qt for Device Creation}. The OTA-related files are
    installed under \c Tools/ota directory in the main SDK install location, referred to as
    \c SDK_INSTALL_DIR in this guide.

    When executing scripts, we will refer to the current working directory as
    WORKDIR. We will be using the \c qt-ostree tool from the installation.
    To see a full list of available command line arguments run the following
    command:

    \badcode
    ./qt-ostree --help
    \endcode

    Instead of providing a full path to \c qt-ostree each time we refer to it in
    the guide, we will assume to be already in the
    \c SDK_INSTALL_DIR/Tools/ota/qt-ostree directory.

    \section2 Work on Your Product

    Build your product on top of the \B2Q stack, or build your own custom embedded
    linux image. When the image is ready for the \e {first release}, continue to the
    \l {Enabling OTA Functionality on a Device}.

    \section2 Enabling OTA Functionality on a Device

    When preparing a device for shipping and subsequent updates are to be delivered
    via OTA, you first need to enable this feature in the \e sysroot:

    \list 1

      \li Generate OSTree boot compatible initramfs image (skip this step if not using
          initramfs for booting).

          To generate the initramfs image, run:

          \badcode
          cd SDK_INSTALL_DIR/Tools/ota/dracut/
          ./generate-initramfs
          \endcode

          \b {Important:} The device should be powered on, booted into your current
          product (the sysroot to be released), and connected to a machine from
          which you will run the \c generate-initramfs tool. \l {https://en.wikipedia.org/wiki/Dracut_(software)}
          {Dracut} generates the initramfs image based on the currently running kernel.
          You can, of course, provide your own (not necessarily dracut based) initramfs,
          as long as you include the required OSTree logic from the provided dracut module
          (installed in \c {/usr/lib/dracut/modules.d/}). The generated image uses
          systemd as an init system.

          This will produce an \c initramfs-${device}-${release} file in the
          working directory. The initramfs file will be needed in the later steps.

       \target Boot loader integration.
       \li Boot loader integration.

          OSTree maintains bootloader-independent drop-in configuration files in a format
          as defined by \l {https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/}
          {The Boot Loader Specification}. Not all boot loaders support The Boot Loader
          Specification, so OSTree contains code to generate native configurations files
          from the bootloader-independent configurations.

          The boot script used by your device has to be changed to use the configurations that are
          managed by OSTree. This will ensure that, after OTA updates or rollbacks, the correct
          kernel version (and corresponding boot files) will be selected at boot time.

          \list
            \li \b {U-Boot}

              U-Boot tools package is required. In Ubuntu, this can be installed with the following
              command:

              \badcode
              sudo apt-get install u-boot-tools
              \endcode

              OSTree maintains the \c uEnv.txt file, which the U-Boot environment should
              import. If custom changes to \c uEnv.txt are required, use the \c --uboot-env-file
              argument from the \c {qt-ostree} tool. The provided file will be appended to OSTree's
              managed \c uEnv.txt.

              OSTree maintains the following fields in \c uEnv.txt:

              \list
                \li \c ${kernel_image}: Path to the Linux kernel image.
                \li \c ${ramdisk_image}: Path to the initramfs image (optional).
                \li \c ${bootargs}: Parameters passed to the kernel command line.
                \li \c ${bootdir}: Path to other files that belong to the same release
                       and should be accessible from U-Boot (DTBs, boot scripts).
              \endlist

              An example \c uEnv.txt when booting with initramfs:

              \badcode
              kernel_image=/ostree/qt-os-590db09c66551670019a487992f4dae9cb2067e241f7c7fefd6b3d35af55895b/vmlinuz
              bootdir=/ostree/qt-os-590db09c66551670019a487992f4dae9cb2067e241f7c7fefd6b3d35af55895b/
              ramdisk_image=/ostree/qt-os-590db09c66551670019a487992f4dae9cb2067e241f7c7fefd6b3d35af55895b/initramfs
              bootargs=ostree=/ostree/boot.1/qt-os/590db09c66551670019a487992f4dae9cb2067e241f7c7fefd6b3d35af55895b/0
              \endcode

              A sample U-Boot logic that uses the imported OSTree's environment variables:

              \raw HTML
  <pre class="cpp">
  if ${fs}load ${dtype} ${disk}:${part} ${script} uEnv.txt ; then
    env import -t ${script} ${filesize}
  else
    echo "Error loading uEnv.txt"
    exit
  fi

  fdt_file=&lt;device_tree_filename&gt;

  ${fs}load ${dtype} ${disk}:${part} ${kernel_addr} <b>${kernel_image}</b>
  ${fs}load ${dtype} ${disk}:${part} ${fdt_addr} <b>${bootdir}</b>/${fdt_file}
  ${fs}load ${dtype} ${disk}:${part} ${initramfs_addr} <b>${ramdisk_image}</b>

  # Don't overwrite bootargs set by OSTree in uEnv.txt.
  setenv bootargs <b>${bootargs}</b> &lt;additional_bootargs&gt;

  bootz ${kernel_addr} ${initramfs_addr} ${fdt_addr}
  </pre>
              \endraw

              Enabling OSTree support requires minimal effort when using a default boot script
              as the base. A default boot script here means whatever the device is currently using
              for booting. The \c {qt-ostree} tool does not change the kernel image format, only
              the path and the file name changes. If the original script uses the \c bootm command for
              loading the kernel image, then the OSTree-enabled script should use \c bootm too.

              \note You should expect to find all the files that are required for the
              boot process under the \c ${bootdir} path. Before starting to write
              U-Boot integration code, you can run the \c qt-ostree tool without
              providing the \c --uboot-env-file argument and \l {The generated sysroot} {examine}
              the generated sysroot (see step 3).

            \li \b {GRUB 2}

              Whenever the boot loader configuration files need to be updated on a GRUB 2 based system,
              OSTree executes \c ostree-grub-generator to convert bootloader-independent configuration
              files into native grub.cfg format. A default script, used by the \c qt-ostree tool is
              \c SDK_INSTALL_DIR/Tools/ota/qt-ostree/ostree-grub-generator.
              You can customize this script to match your requirements and provide it to \c qt-ostree
              via \c --grub2-cfg-generator. The \c ostree-grub-generator file contains additional
              details, the script itself is about 40 lines long.

          \endlist

          For more examples refer to \l {Device Integration Examples}.

          \b {The Booting Process}

          OSTree includes a special \c ostree= kernel argument that points to the corresponding tree
          (see the \c {/ostree} in \l {Layout of an OTA Enabled Sysroot}). When not using initramfs,
          the kernel command will also contain the \c init= argument, pointing to the \c ostree-prepare-root
          binary. The same binary is used from initramfs context. The \c ostree-prepare-root binary
          parses the \c ostree= kernel command line argument to find the correct versioned tree. It
          sets up the necessary mounts, notably the read-only mount on the \c {/usr} path, and makes
          the versioned tree to appear as a real \c {"/"} root directory in the booted system.

          After \c ostree-prepare-root (run as PID 1) completes, it passes control to the real init
          process. In initramfs context, once \c ostree-prepare-root is done, systemd's
          \c initrd-switch-root.target will take over. In initramfs, \c ostree-prepare-root is
          used as a user space utility (as opposed to PID 1, when booting without initramfs).

      \li Convert your sysroot into an OTA enabled sysroot.

          The conversion is done using the \c qt-ostree tool.

          \badcode
          sudo ./qt-ostree \
          --sysroot-image-path ${PATH_TO_SYSROOT} \
          --create-ota-sysroot \
          --ota-json ${OTA_METADATA} \
          --initramfs ../dracut/initramfs-${device}-${release} \
          --uboot-env-file ../examples/device-integration/nitrogen6x/6x_bootscript
          \endcode

          \target {The generated sysroot}
          The generated sysroot can be examined by mounting the \c {boot.${BOOTFS_TYPE}} and
          the \c {rootfs.${ROOTFS_TYPE}} filesystem images found in \c {WORKDIR}.

          In this guide we assume that the system is based on U-Boot boot loader.

          Notes on the arguments passed to \c {qt-ostree}:

          \list
              \li \b {\c --sysroot-image-path}
                  \list
                      \li A path to your sysroot. Binary image (\c {*.img})
                          and archive image (\c {*.tar.gz}) is accepted as
                          well as a path to an extracted sysroot.
                  \endlist

              \li \b {\c --create-ota-sysroot}
                  \list
                      \li This option tells \c qt-ostree to create a binary image
                          that contains a bootable OTA enabled sysroot. You will have to
                          deploy the generated image to a device; in this guide, we use
                          an SD card as memory media (see step 4).
                  \endlist

              \li \b {\c --ota-json}
                  \list
                      \li A JSON file containing arbitrary metadata about the system.
                          The following top-level fields have convenience methods in
                          the Qt/QML OTA API: \c version and \c description. Use
                          OtaClient::remoteInfo to fetch the entire JSON file for
                          manual parsing.
                  \endlist

              \li \b {\c --initramfs}
                  \list
                      \li The initramfs image that we generated in the step 1. If initramfs is
                          not used for booting, it may be necessary to provide additional
                          kernel command line arguments (for example, \c {--kernel-args "rootwait root=/dev/sda2"}).
                          The kernel arguments set with \b {\c --kernel-args} are passed to
                          the bootloader integration code. If additional kernel arguments are
                          resolved directly from boot scripts, then \c {--kernel-args} can be omitted.
                  \endlist

              \li \b {\c --uboot-env-file}
                  \list
                      \li A custom U-Boot boot script or \c uEnv.txt file, see \l {Boot loader
                          integration}. This argument is optional as U-Boot environment can be
                          stored directly on the board's persistent storage dedicated for U-boot
                          environment, or defined when building the U-Boot binary.
                  \endlist
          \endlist

      \li Deploy the generated OTA image to an SD card.

          Plug in an SD card or a reader to the development host, and use the
          following command to find out its device name.

          \badcode
          lsblk -d
          \endcode

          Make sure to unmount all partitions on a device.

          \badcode
          sudo umount /dev/<device_name>?*
          \endcode

          And then deploy the image.

          \badcode
          sudo dd bs=4M if=<image> of=/dev/<device_name> && sync
          \endcode

      \li Test that everything went according to the plan.

          Boot from the SD card and run the following command \e {from the device}:

          \badcode
          ostree admin status
          \endcode

          The output should be something similar to:

          \badcode
          * qt-os 36524faa47e33da9dbded2ff99d1df47b3734427b94c8a11e062314ed31442a7.0
              origin refspec: qt-os:linux/qt
          \endcode

          This indicates that the deployment was successful.

          \note You should also verify that application(s) are working as expected
          and do not write outside the \l {Layout of an OTA Enabled Sysroot}
          {permitted paths}.

        \endlist

    \section2 Preparing a New Update for an OTA Enabled Device

    When preparing a new update for a device that already has OTA enabled, the
    workflow is as follows:

    \list 1

      \li Work on your sysroot as you normally would. When the product is ready
          for a release, continue to the next step.

      \li Generate an update.

          This is done by using the \c qt-ostree tool.

          \badcode
          sudo ./qt-ostree \
          --sysroot-image-path ${PATH_TO_SYSROOT} \
          --ota-json ${OTA_METADATA} \
          --initramfs ../dracut/initramfs-${device}-${release} \
          --start-trivial-httpd
          \endcode

        The above command will create a new commit in the OSTree repository at \c
        {WORKDIR/ostree-repo/}, or create a new repository if one does not exist.
        Use the \c --ostree-repo argument to provide a custom path. This repository
        is the OTA update and can be copied to a production server at any time. OSTree
        repositories can be served via a static HTTP server.

        Notes on the arguments passed to \c {qt-ostree}:

        \list
            \li \b {\c --initramfs}
                \list
                    \li When doing \e {minor releases} that do not update the
                        kernel:

                        Use the same initramfs that you already have generated
                        for this kernel version in the earlier steps.

                    \li When doing a \e {major release} that updates a kernel:

                        It is advised to regenerate initramfs for each new
                        kernel release, so that the kernel and initramfs
                        versions \e match. On U-Boot systems: If the new kernel/initramfs
                        is \b {not compatible} with the boot script on a device,
                        it \b must be updated as well (see the \c --uboot-env-file
                        notes below).
                \endlist

                As before, if not using initramfs, it may be necessary to provide
                additional kernel command line arguments via \b {\c --kernel-args}.

            \li \b {\c --sysroot-image-path}
                \list
                    \li Provide a path to the \e {new version} of your sysroot.
                \endlist
            \li \b {\c --uboot-env-file}
                \list
                    \li Updating u-boot environment file is supported only for
                        major releases - when kernel/initramfs versions change.

                        The kernel/initramfs version is considered to change when
                        \c bootcsum changes in the following expression:
                        \badcode
                        bootcsum=$(cat vmlinuz initramfs | sha256sum | cut -f 1 -d ' ')
                        \endcode
                \endlist
            \li \b {\c --start-trivial-httpd}
                \list
                    \li Starts a simple web server which you can access on the
                        local host at address specified in \c WORKDIR/httpd/httpd-address
                        file. Entering this address into a web browser lists the
                        contents of your OSTree repository. This command line
                        argument is useful for quick testing purposes, in production
                        with more advanced requirements (TLS authentication) you
                        will need to use a different web server solution, like
                        Apache.
                \endlist
        \endlist

      \li Use Qt OTA APIs to update devices.

      \li Go back to step 1.

    \endlist

    \section2 Securing a Delivery of an Update

    OTA is a component of a system and not a security framework. If handled
    correctly (GPG and TLS are used properly, the keys are generated and handled
    properly and the servers in question are secure to known vulnerabilies and
    exploits) OTA is considered secure against realistic attacks.

    \list
    \li \b {GPG Signing}

    GPG signing helps to ensure that the data was transmitted in-full, without
    damage or file corruption and that the data was sent by a trusted party. A
    set of trusted keys is stored as keyring files on a device. Look for \c {--gpg-*}
    command line arguments in the output of \c {./qt-ostree --help}.

    In Ubuntu, the required packages can be installed with the following command:

    \badcode
    sudo apt-get install gnupg2
    \endcode

    \li \b {TLS Authentication}

    TLS protects data from tampering and eavesdropping. System administrators use
    this to restrict the access to the server (client authentication) and client
    devices use this to verify the identitiy of an update server (server authentication).
    Look for \c {--tls-*} command line arguments in the output of \c {./qt-ostree --help}.
    \endlist

    It is advised to use both GPG and TLS in hostile environments. To learn more about the
    security topics from the above list, consult dedicated resources. For the corresponding
    client side API see OtaRepositoryConfig.

    \section2 Offline Updates and Custom Delivery Mechanisms

    Updating devices via OtaClient::update() requires a target device to be connected to the
    Internet and this mechanism is limited to HTTP(S) only (OtaRepositoryConfig::url). An
    alternative approach is to generate a self-contained update package. A self-contained
    package support can be enabled by passing \c {--create-self-contained-package} to the
    \c {qt-ostree} tool. This will generate a \c {WORKDIR/superblock} binary file. Generating
    a self-contained update package is required when:
    \list
      \li A device has no network connection and is intended to be
          updated via external media such as a USB drive, or
      \li Some other protocol or proprietary mechanism is used to
          deliver software to a device.
    \endlist
    As all APIs in the Qt OTA Update module, applying a self-contained update package is an
    atomic process, and is done via OtaClient::updateOffline().

    \section1 Layout of an OTA Enabled Sysroot

    There are only two directories on a device for a safe storage of local files:
    \c {/var} and \c {/etc}.
    The sysroot generated by OTA tools adds convenience symbolic links in the
    \c {/} root directory, including symbolic links pointing to the \c {/var}.

    \b {Important:}
    \list
    \li Do not create or modify files in other locations, these files will be
        garbage collected on the next upgrade.
    \li Do not directly modify the contents of the \c {/ostree} and the \c {/boot}
        directory. This can result in a system that fails to boot.
    \endlist

    \table
    \header \li Directory         \li Description

    \row \li \c {/usr}\unicode {0xA0}[read\unicode {0x2011}only]

         \li Everything that is part of the OS - mounted read-only to prevent
             inadvertent corruption. It's recommended that an operating system
             ships all of its content in /usr. Contents of this directory are
             updated via OTA.

    \row \li \c {/etc}

         \li Host-specific system-wide configuration files. OTA \e preserves all
             local changes by doing a 3-way merge. \br \br

             \b {How a 3-way merge works:}

             First OSTree checks on the \e {currently booted tree} which configuration
             files have been changed, removed or added by a user by comparing the
             /etc with the read-only /usr/etc/. The /usr/etc is where OSTree stores
             default configurations of the tree as composed on the server (each
             tree has its own read-only copy of the /usr/etc). The advantage of
             having read-only /usr/etc is that you always have access to system
             defaults.

             Then OSTree takes /etc of the \e {OTA update}, which is a separate
             copy from your running /etc (each tree has its own writable copy of
             the /etc) as a base and applies your local changes on top. It doesn’t
             attempt to understand the contents of files – if a configuration file
             on a device has been modified in any way, that wins.
    \row \li \c {/var}

         \li The only directory that is \e preserved and \e shared across upgrades.
             This is where user and application data should be stored.
             \note OSTree does not update the contents of /var, it is the
             responsibility of the OS to manage and upgrade /var if required.

    \row \li \c /ostree

         \li The location of the OSTree repository on a device and where the
             bootable versioned filesystem trees are installed. These trees \e
             share all common files via hard links into the OSTree repository.
             This means each version is deduplicated; an upgrade process only
             costs disk space proportional to the new files, plus some constant
             overhead.
             \note /ostree is a symbolic link to /sysroot/ostree.

    \row \li \c /boot

         \li Contains the boot loader configuration files, kernel, initramfs, and
             other files that are required for the boot process.

    \row \li \c /sysroot

         \li Physical / root directory.

    \row \li \c /

         \li Versioned filesystem tree's root.

    \endtable

    \section1 Device Integration Examples

    Find examples for real embedded devices in the \c
    SDK_INSTALL_DIR/Tools/ota/examples/device-integration/ directory.
*/