sysinstall: optionally, pick target block device automatically (#43582)
This commit is contained in:
@@ -204,12 +204,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
|||||||
|
|
||||||
- portabled driving by system credential
|
- portabled driving by system credential
|
||||||
|
|
||||||
- sysinstall: add fully automatic mode that automatically picks target disk,
|
|
||||||
non-interactively. Should wait to ensure system is up for a certain amount of
|
|
||||||
minimal time (alternatively: certain amount of time since the last disk
|
|
||||||
showed up), to ensure disks have shown up before making the decision. Usecase
|
|
||||||
for this: redfish style server provisioning.
|
|
||||||
|
|
||||||
- nspawn: optionally provide a /dev/tpm0 + /dev/tpmrm0 that is backed by swtpm,
|
- nspawn: optionally provide a /dev/tpm0 + /dev/tpmrm0 that is backed by swtpm,
|
||||||
much like we do in vmspawn. let's us minimize differences between
|
much like we do in vmspawn. let's us minimize differences between
|
||||||
environments systemd runs in.
|
environments systemd runs in.
|
||||||
@@ -338,7 +332,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
|||||||
partition with a portable service partition in one.
|
partition with a portable service partition in one.
|
||||||
|
|
||||||
- **systemd-sysinstall:**
|
- **systemd-sysinstall:**
|
||||||
- make systemd-sysinstall itself a varlink service
|
|
||||||
- read installation definition from json file
|
- read installation definition from json file
|
||||||
- polkit support in sysinstall
|
- polkit support in sysinstall
|
||||||
- sysinstall: permit driving installer via credentials
|
- sysinstall: permit driving installer via credentials
|
||||||
|
|||||||
@@ -735,6 +735,17 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \
|
|||||||
to override the filesystem type for all partitions with `Type=root`, you can
|
to override the filesystem type for all partitions with `Type=root`, you can
|
||||||
set `SYSTEMD_REPART_OVERRIDE_FSTYPE_ROOT=ext4`.
|
set `SYSTEMD_REPART_OVERRIDE_FSTYPE_ROOT=ext4`.
|
||||||
|
|
||||||
|
`systemd-sysinstall`:
|
||||||
|
|
||||||
|
* `$SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP` – takes a boolean. If true,
|
||||||
|
loopback block devices are considered as candidates when the installation
|
||||||
|
target device is picked automatically (i.e. when `--device-auto` is used).
|
||||||
|
By default, loopback devices are never picked automatically, since they are
|
||||||
|
hardly ever the disk an unattended installation is supposed to end up on.
|
||||||
|
This is primarily useful for testing purposes, where loopback devices are
|
||||||
|
used as stand-ins for real disks. Only honoured if `systemd-sysinstall` is
|
||||||
|
not invoked with elevated privileges (i.e. via `secure_getenv()`).
|
||||||
|
|
||||||
`systemd-nspawn`, `systemd-networkd`:
|
`systemd-nspawn`, `systemd-networkd`:
|
||||||
|
|
||||||
* `$SYSTEMD_FIREWALL_BACKEND` – takes a string, either `iptables` or
|
* `$SYSTEMD_FIREWALL_BACKEND` – takes a string, either `iptables` or
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
|
|
||||||
<orderedlist>
|
<orderedlist>
|
||||||
<listitem><para>It prompts the user for the target disk to install the OS on. (Unless the block device
|
<listitem><para>It prompts the user for the target disk to install the OS on. (Unless the block device
|
||||||
is already specified on the command line.)</para></listitem>
|
is already specified on the command line, or is picked automatically via
|
||||||
|
<option>--device-auto</option>.)</para></listitem>
|
||||||
|
|
||||||
<listitem><para>It validates whether the disk is suitable (i.e. large enough, and with enough
|
<listitem><para>It validates whether the disk is suitable (i.e. large enough, and with enough
|
||||||
free/unpartitioned space) for an OS installation. If it is generally suitable the user is prompted if they
|
free/unpartitioned space) for an OS installation. If it is generally suitable the user is prompted if they
|
||||||
@@ -110,6 +111,39 @@
|
|||||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--device-auto</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Automatically pick the target disk instead of prompting for it or taking it from the
|
||||||
|
command line. The tool watches the block devices of the system (via udev) and waits for the list of
|
||||||
|
candidate disks to settle: the timeout configured with <option>--device-auto-timeout=</option>
|
||||||
|
starts at invocation and is restarted whenever a block device appears, disappears or changes. Once
|
||||||
|
it expires without further changes, the single candidate disk present at that moment is selected as
|
||||||
|
installation target. If no candidate disk is present when the timeout expires, or if at any time
|
||||||
|
more than one candidate disk is present, the installation fails. Candidate disks are those
|
||||||
|
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||||
|
would offer in the interactive prompt (i.e. that support partition scanning, are writable, not the
|
||||||
|
disk the running system is booted from, and contain a medium). If the disk the running system is
|
||||||
|
booted from cannot be determined – and hence cannot reliably be excluded from the candidate set –
|
||||||
|
the automatic selection is refused. Cannot be combined with an explicitly
|
||||||
|
specified block device. This is primarily useful together with <option>--confirm=no</option>,
|
||||||
|
<option>--erase=</option> and <option>--variables=</option>, for fully unattended
|
||||||
|
installations.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--device-auto-timeout=</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Configures the settle timeout for <option>--device-auto</option>, i.e. for how long
|
||||||
|
the list of candidate disks must remain unchanged before a disk is selected. Takes a time span, see
|
||||||
|
<citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
|
||||||
|
Defaults to 30s.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--welcome=</option></term>
|
<term><option>--welcome=</option></term>
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ _systemd_sysinstall() {
|
|||||||
local comps
|
local comps
|
||||||
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
|
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
|
||||||
local -A OPTS=(
|
local -A OPTS=(
|
||||||
[STANDALONE]='-h --help --version'
|
[STANDALONE]='-h --help --version --device-auto'
|
||||||
[ARG]='--welcome
|
[ARG]='--device-auto-timeout
|
||||||
|
--welcome
|
||||||
--chrome
|
--chrome
|
||||||
--erase
|
--erase
|
||||||
--confirm
|
--confirm
|
||||||
@@ -68,7 +69,7 @@ _systemd_sysinstall() {
|
|||||||
comps=$(compgen -A file -- "$cur")
|
comps=$(compgen -A file -- "$cur")
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
--set-credential)
|
--set-credential|--device-auto-timeout)
|
||||||
comps=''
|
comps=''
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ _systemd-sysinstall_devices() {
|
|||||||
_arguments \
|
_arguments \
|
||||||
'(- *)'{-h,--help}'[Show help text]' \
|
'(- *)'{-h,--help}'[Show help text]' \
|
||||||
'(- *)--version[Show package version]' \
|
'(- *)--version[Show package version]' \
|
||||||
|
'--device-auto[Automatically pick the target disk once the device list settled]' \
|
||||||
|
'--device-auto-timeout=[Settle timeout for --device-auto]:time span' \
|
||||||
'--welcome=[Show welcome text]:boolean:(yes no)' \
|
'--welcome=[Show welcome text]:boolean:(yes no)' \
|
||||||
'--chrome=[Show colored bars at top and bottom of the terminal]:boolean:(yes no)' \
|
'--chrome=[Show colored bars at top and bottom of the terminal]:boolean:(yes no)' \
|
||||||
'--erase=[Erase target disk before installation]:boolean:(yes no auto)' \
|
'--erase=[Erase target disk before installation]:boolean:(yes no auto)' \
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ int vl_method_list_candidate_devices(
|
|||||||
if (r != 0)
|
if (r != 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
/* The filtering below should probably be kept in sync with the one done in src/sysinstall/sysinstall-auto-device.c! */
|
||||||
|
|
||||||
const BlockDevListFlags listflags =
|
const BlockDevListFlags listflags =
|
||||||
BLOCKDEV_LIST_SHOW_SYMLINKS|
|
BLOCKDEV_LIST_SHOW_SYMLINKS|
|
||||||
BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|
|
BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ static int disk_io_generate(const MetricFamily mf[static 2], sd_varlink *link, v
|
|||||||
size_t n_devices = 0;
|
size_t n_devices = 0;
|
||||||
CLEANUP_ARRAY(devices, n_devices, block_device_array_free);
|
CLEANUP_ARRAY(devices, n_devices, block_device_array_free);
|
||||||
|
|
||||||
r = blockdev_list(BLOCKDEV_LIST_IGNORE_VIRTUAL | BLOCKDEV_LIST_IGNORE_PARTITIONS | BLOCKDEV_LIST_RW_STATS,
|
r = blockdev_list(BLOCKDEV_LIST_IGNORE_VIRTUAL | BLOCKDEV_LIST_IGNORE_LOOP | BLOCKDEV_LIST_IGNORE_PARTITIONS | BLOCKDEV_LIST_RW_STATS,
|
||||||
&devices, &n_devices);
|
&devices, &n_devices);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|||||||
@@ -167,7 +167,22 @@ int blockdev_list_one(
|
|||||||
goto no_match;
|
goto no_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_VIRTUAL)) {
|
bool is_loop = false;
|
||||||
|
if (flags & (BLOCKDEV_LIST_IGNORE_LOOP|BLOCKDEV_LIST_IGNORE_VIRTUAL)) {
|
||||||
|
r = device_sysname_startswith(dev, "loop");
|
||||||
|
if (r < 0) {
|
||||||
|
log_device_warning_errno(dev, r, "Failed to check device name of discovered block device '%s', ignoring: %m", node);
|
||||||
|
goto skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_loop = r > 0;
|
||||||
|
if (is_loop && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_LOOP))
|
||||||
|
goto no_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loopback devices are virtual devices too, but they are covered by BLOCKDEV_LIST_IGNORE_LOOP above,
|
||||||
|
* hence exclude them here. */
|
||||||
|
if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_VIRTUAL) && !is_loop) {
|
||||||
const char *devpath;
|
const char *devpath;
|
||||||
|
|
||||||
r = sd_device_get_devpath(dev, &devpath);
|
r = sd_device_get_devpath(dev, &devpath);
|
||||||
@@ -340,7 +355,13 @@ skipped:
|
|||||||
return BLOCKDEV_LIST_MATCH_SKIPPED;
|
return BLOCKDEV_LIST_MATCH_SKIPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices) {
|
int blockdev_list_full(
|
||||||
|
BlockDevListFlags flags,
|
||||||
|
dev_t root_devno,
|
||||||
|
dev_t whole_root_devno,
|
||||||
|
BlockDevice **ret_devices,
|
||||||
|
size_t *ret_n_devices) {
|
||||||
|
|
||||||
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
@@ -353,8 +374,8 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re
|
|||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
CLEANUP_ARRAY(l, n, block_device_array_free);
|
CLEANUP_ARRAY(l, n, block_device_array_free);
|
||||||
|
|
||||||
dev_t root_devno = 0, whole_root_devno = 0;
|
if (root_devno == 0 && whole_root_devno == 0)
|
||||||
(void) blockdev_list_get_root_devnos(flags, &root_devno, &whole_root_devno);
|
(void) blockdev_list_get_root_devnos(flags, &root_devno, &whole_root_devno);
|
||||||
|
|
||||||
if (sd_device_enumerator_new(&e) < 0)
|
if (sd_device_enumerator_new(&e) < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|||||||
+21
-11
@@ -6,16 +6,17 @@
|
|||||||
typedef enum BlockDevListFlags {
|
typedef enum BlockDevListFlags {
|
||||||
BLOCKDEV_LIST_SHOW_SYMLINKS = 1 << 0, /* Pick up symlinks to block devices too */
|
BLOCKDEV_LIST_SHOW_SYMLINKS = 1 << 0, /* Pick up symlinks to block devices too */
|
||||||
BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING = 1 << 1, /* Only consider block devices with partition scanning */
|
BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING = 1 << 1, /* Only consider block devices with partition scanning */
|
||||||
BLOCKDEV_LIST_IGNORE_VIRTUAL = 1 << 2, /* Ignore virtual devices (devices under /sys/devices/virtual/) */
|
BLOCKDEV_LIST_IGNORE_VIRTUAL = 1 << 2, /* Ignore virtual devices (devices under /sys/devices/virtual/), except loopback devices */
|
||||||
BLOCKDEV_LIST_IGNORE_PARTITIONS = 1 << 3, /* Ignore partitions */
|
BLOCKDEV_LIST_IGNORE_LOOP = 1 << 3, /* Ignore loopback devices */
|
||||||
BLOCKDEV_LIST_IGNORE_ZRAM = 1 << 4, /* Ignore ZRAM */
|
BLOCKDEV_LIST_IGNORE_PARTITIONS = 1 << 4, /* Ignore partitions */
|
||||||
BLOCKDEV_LIST_IGNORE_ROOT = 1 << 5, /* Ignore the block device we are currently booted from */
|
BLOCKDEV_LIST_IGNORE_ZRAM = 1 << 5, /* Ignore ZRAM */
|
||||||
BLOCKDEV_LIST_IGNORE_EMPTY = 1 << 6, /* Ignore disks of zero size (usually drives without a medium) */
|
BLOCKDEV_LIST_IGNORE_ROOT = 1 << 6, /* Ignore the block device we are currently booted from */
|
||||||
BLOCKDEV_LIST_IGNORE_READ_ONLY = 1 << 7, /* Ignore read-only block devices */
|
BLOCKDEV_LIST_IGNORE_EMPTY = 1 << 7, /* Ignore disks of zero size (usually drives without a medium) */
|
||||||
BLOCKDEV_LIST_REQUIRE_LUKS = 1 << 8, /* Only consider block devices with LUKS superblocks */
|
BLOCKDEV_LIST_IGNORE_READ_ONLY = 1 << 8, /* Ignore read-only block devices */
|
||||||
BLOCKDEV_LIST_METADATA = 1 << 9, /* Fill in model, vendor, subsystem, read_only */
|
BLOCKDEV_LIST_REQUIRE_LUKS = 1 << 9, /* Only consider block devices with LUKS superblocks */
|
||||||
BLOCKDEV_LIST_DISKSEQ = 1 << 10, /* Fill in the diskseq field */
|
BLOCKDEV_LIST_METADATA = 1 << 10, /* Fill in model, vendor, subsystem, read_only */
|
||||||
BLOCKDEV_LIST_RW_STATS = 1 << 11, /* Fill in read and write measurements */
|
BLOCKDEV_LIST_DISKSEQ = 1 << 11, /* Fill in the diskseq field */
|
||||||
|
BLOCKDEV_LIST_RW_STATS = 1 << 12, /* Fill in read and write measurements */
|
||||||
} BlockDevListFlags;
|
} BlockDevListFlags;
|
||||||
|
|
||||||
/* The "dynamic" filters – ones the kernel can flip at runtime via sysattrs on a live device.
|
/* The "dynamic" filters – ones the kernel can flip at runtime via sysattrs on a live device.
|
||||||
@@ -70,4 +71,13 @@ int blockdev_list_one(
|
|||||||
dev_t whole_root_devno,
|
dev_t whole_root_devno,
|
||||||
BlockDevice *ret);
|
BlockDevice *ret);
|
||||||
|
|
||||||
int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices);
|
int blockdev_list_full(
|
||||||
|
BlockDevListFlags flags,
|
||||||
|
dev_t root_devno,
|
||||||
|
dev_t whole_root_devno,
|
||||||
|
BlockDevice **ret_devices,
|
||||||
|
size_t *ret_n_devices);
|
||||||
|
|
||||||
|
static inline int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices) {
|
||||||
|
return blockdev_list_full(flags, 0, 0, ret_devices, ret_n_devices);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ executables += [
|
|||||||
'name' : 'systemd-sysinstall',
|
'name' : 'systemd-sysinstall',
|
||||||
'public' : true,
|
'public' : true,
|
||||||
'conditions' : ['ENABLE_SYSINSTALL'],
|
'conditions' : ['ENABLE_SYSINSTALL'],
|
||||||
'sources' : files('sysinstall.c'),
|
'sources' : files(
|
||||||
|
'sysinstall.c',
|
||||||
|
'sysinstall-auto-device.c',
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,452 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "sd-daemon.h"
|
||||||
|
#include "sd-device.h"
|
||||||
|
#include "sd-event.h"
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "blockdev-list.h"
|
||||||
|
#include "blockdev-util.h"
|
||||||
|
#include "device-util.h"
|
||||||
|
#include "env-util.h"
|
||||||
|
#include "event-util.h"
|
||||||
|
#include "format-util.h"
|
||||||
|
#include "glyph-util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "path-util.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "sysinstall-auto-device.h"
|
||||||
|
#include "time-util.h"
|
||||||
|
|
||||||
|
/* Keep in sync with the filter set io.systemd.Repart.ListCandidateDevices applies (see
|
||||||
|
* src/repart/repart-list-candidate-devices.c), which is what the interactive device prompt is populated
|
||||||
|
* from, so that the automatic pick never picks a device the prompt wouldn't offer. On top of that we
|
||||||
|
* ignore devices without a medium (i.e. of zero size) – a drive without a disk in it is not something we
|
||||||
|
* can install to, and should not be counted as a candidate. BLOCKDEV_LIST_METADATA is only there so the
|
||||||
|
* log messages can mention vendor/model. Loopback devices are ignored on top of that (unless the
|
||||||
|
* $SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP env var says otherwise, see auto_device_list_flags()). */
|
||||||
|
#define AUTO_DEVICE_LIST_FLAGS \
|
||||||
|
(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING | \
|
||||||
|
BLOCKDEV_LIST_IGNORE_ZRAM | \
|
||||||
|
BLOCKDEV_LIST_IGNORE_READ_ONLY | \
|
||||||
|
BLOCKDEV_LIST_IGNORE_ROOT | \
|
||||||
|
BLOCKDEV_LIST_IGNORE_EMPTY | \
|
||||||
|
BLOCKDEV_LIST_DISKSEQ | \
|
||||||
|
BLOCKDEV_LIST_METADATA)
|
||||||
|
|
||||||
|
/* How often to log about the remaining time while waiting for the device list to settle */
|
||||||
|
#define COUNTDOWN_INTERVAL_USEC (5 * USEC_PER_SEC)
|
||||||
|
|
||||||
|
typedef struct AutoDeviceContext {
|
||||||
|
sd_event *event;
|
||||||
|
sd_device_monitor *monitor;
|
||||||
|
sd_event_source *settle_timer;
|
||||||
|
sd_event_source *countdown_timer;
|
||||||
|
char *candidate; /* Device node of the single candidate seen so far, or NULL if none */
|
||||||
|
uint64_t candidate_diskseq; /* Diskseq of the device */
|
||||||
|
dev_t root_devno, whole_root_devno;
|
||||||
|
usec_t settle_timeout;
|
||||||
|
BlockDevListFlags flags;
|
||||||
|
} AutoDeviceContext;
|
||||||
|
|
||||||
|
static BlockDevListFlags auto_device_list_flags(void) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* A loopback device is hardly ever the disk an unattended installation is supposed to end up on,
|
||||||
|
* but it's what tests use as stand-in for a real disk. Hence ignore loopback devices by default,
|
||||||
|
* but allow explicitly permitting them via an environment variable. */
|
||||||
|
r = secure_getenv_bool("SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP");
|
||||||
|
if (r < 0 && r != -ENXIO)
|
||||||
|
log_warning_errno(r, "Failed to parse $SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP, ignoring: %m");
|
||||||
|
|
||||||
|
return AUTO_DEVICE_LIST_FLAGS | (r > 0 ? 0 : BLOCKDEV_LIST_IGNORE_LOOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void auto_device_context_done(AutoDeviceContext *c) {
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
c->settle_timer = sd_event_source_disable_unref(c->settle_timer);
|
||||||
|
c->countdown_timer = sd_event_source_disable_unref(c->countdown_timer);
|
||||||
|
c->monitor = sd_device_monitor_unref(c->monitor);
|
||||||
|
c->event = sd_event_unref(c->event);
|
||||||
|
c->candidate = mfree(c->candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_on_settle(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||||
|
AutoDeviceContext *c = ASSERT_PTR(userdata);
|
||||||
|
|
||||||
|
/* The candidate list has been stable for the settle timeout. If we have exactly one candidate at
|
||||||
|
* this point, it's the one. (Two candidates can never be pending here, since auto_device_add()
|
||||||
|
* terminates the event loop the moment a second one shows up.) */
|
||||||
|
|
||||||
|
if (!c->candidate) {
|
||||||
|
log_error("No suitable block device showed up within %s, cannot continue.",
|
||||||
|
FORMAT_TIMESPAN(c->settle_timeout, 0));
|
||||||
|
return sd_event_exit(c->event, -ENODEV);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sd_event_exit(c->event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_on_countdown(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||||
|
AutoDeviceContext *c = ASSERT_PTR(userdata);
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(s);
|
||||||
|
assert(c->settle_timer);
|
||||||
|
|
||||||
|
/* Logs how much time is left until the settle timer fires, and reschedules itself, unless the
|
||||||
|
* settle timer fires before the next message would be due anyway. */
|
||||||
|
|
||||||
|
usec_t deadline;
|
||||||
|
r = sd_event_source_get_time(c->settle_timer, &deadline);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to query settle timer: %m");
|
||||||
|
|
||||||
|
usec_t n;
|
||||||
|
r = sd_event_now(c->event, CLOCK_MONOTONIC, &n);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to query event loop time: %m");
|
||||||
|
|
||||||
|
usec_t remaining = usec_sub_unsigned(deadline, n);
|
||||||
|
if (remaining <= COUNTDOWN_INTERVAL_USEC) /* The settle timer fires soon enough anyway */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
log_info("Still waiting for the list of candidate devices to settle, %s left.",
|
||||||
|
FORMAT_TIMESPAN(remaining, USEC_PER_SEC));
|
||||||
|
|
||||||
|
r = sd_event_source_set_time_relative(s, COUNTDOWN_INTERVAL_USEC);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to re-arm countdown timer: %m");
|
||||||
|
|
||||||
|
r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to enable countdown timer: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_arm_countdown(AutoDeviceContext *c) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
/* Schedules the first countdown message, unless the settle timer fires before that anyway. (The
|
||||||
|
* subsequent messages are scheduled by auto_device_on_countdown() itself.) */
|
||||||
|
|
||||||
|
if (c->settle_timeout <= COUNTDOWN_INTERVAL_USEC) {
|
||||||
|
(void) event_source_disable(c->countdown_timer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = event_reset_time_relative(
|
||||||
|
c->event,
|
||||||
|
&c->countdown_timer,
|
||||||
|
CLOCK_MONOTONIC,
|
||||||
|
COUNTDOWN_INTERVAL_USEC,
|
||||||
|
/* accuracy= */ 0,
|
||||||
|
auto_device_on_countdown,
|
||||||
|
c,
|
||||||
|
SD_EVENT_PRIORITY_NORMAL + 1,
|
||||||
|
"sysinstall-countdown-timer",
|
||||||
|
/* force_reset= */ true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to arm countdown timer: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_arm_timer(AutoDeviceContext *c) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
/* (Re)starts the settle clock. Use a priority below the device monitor's, so that uevents that are
|
||||||
|
* already queued are always dispatched before the timer fires. */
|
||||||
|
|
||||||
|
bool initial = !c->settle_timer;
|
||||||
|
|
||||||
|
r = event_reset_time_relative(
|
||||||
|
c->event,
|
||||||
|
&c->settle_timer,
|
||||||
|
CLOCK_MONOTONIC,
|
||||||
|
c->settle_timeout,
|
||||||
|
/* accuracy= */ 0,
|
||||||
|
auto_device_on_settle,
|
||||||
|
c,
|
||||||
|
SD_EVENT_PRIORITY_NORMAL + 1,
|
||||||
|
"sysinstall-settle-timer",
|
||||||
|
/* force_reset= */ true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to arm settle timer: %m");
|
||||||
|
|
||||||
|
/* Log the wait at INFO only when we start waiting; re-arms happen once per change to the candidate
|
||||||
|
* set, and are already accompanied by a message about that change. */
|
||||||
|
log_full(initial ? LOG_INFO : LOG_DEBUG,
|
||||||
|
"Waiting %s for the list of candidate devices to settle...",
|
||||||
|
FORMAT_TIMESPAN(c->settle_timeout, 0));
|
||||||
|
|
||||||
|
return auto_device_arm_countdown(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_get_node(AutoDeviceContext *c, char **ret) {
|
||||||
|
assert(c);
|
||||||
|
assert(c->candidate);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
/* Return a by-diskseq path for the disk, if we have it */
|
||||||
|
|
||||||
|
if (c->candidate_diskseq != UINT64_MAX) {
|
||||||
|
if (asprintf(ret, "/dev/disk/by-diskseq/%" PRIu64, c->candidate_diskseq) < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strdup_to(ret, c->candidate) < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_add(AutoDeviceContext *c, const BlockDevice *d) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
assert(d);
|
||||||
|
assert(d->node);
|
||||||
|
|
||||||
|
if (c->candidate) {
|
||||||
|
if (!path_equal(c->candidate, d->node)) {
|
||||||
|
log_error("Multiple candidate block devices found (%s, %s), refusing to pick one "
|
||||||
|
"automatically. Please specify the target device explicitly.",
|
||||||
|
c->candidate, d->node);
|
||||||
|
return sd_event_exit(c->event, -ENOTUNIQ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Already known, e.g. a "change" uevent for our candidate */
|
||||||
|
if (c->candidate_diskseq == d->diskseq)
|
||||||
|
return 0; /* no change */
|
||||||
|
|
||||||
|
/* Same node, but a different diskseq: the medium behind the node is not the one we saw
|
||||||
|
* before (e.g. medium swapped, or a "remove" uevent got lost). We are going to be
|
||||||
|
* destructive on this device, hence don't silently switch the target, but treat this like a
|
||||||
|
* new candidate: announce it below and restart the settle clock. */
|
||||||
|
log_info("Medium of candidate device %s changed (diskseq %" PRIu64 " %s %" PRIu64 ").",
|
||||||
|
c->candidate, c->candidate_diskseq, glyph(GLYPH_ARROW_RIGHT), d->diskseq);
|
||||||
|
} else {
|
||||||
|
c->candidate = strdup(d->node);
|
||||||
|
if (!c->candidate)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
|
c->candidate_diskseq = d->diskseq;
|
||||||
|
|
||||||
|
_cleanup_free_ char *description = NULL;
|
||||||
|
if (!isempty(d->vendor) && !strextend_with_separator(&description, " ", d->vendor))
|
||||||
|
return log_oom();
|
||||||
|
if (!isempty(d->model) && !strextend_with_separator(&description, " ", d->model))
|
||||||
|
return log_oom();
|
||||||
|
if (d->size != UINT64_MAX && !strextend_with_separator(&description, ", ", FORMAT_BYTES(d->size)))
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
_cleanup_free_ char *n = NULL;
|
||||||
|
r = auto_device_get_node(c, &n);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
log_info("Found candidate device %s%s%s%s.",
|
||||||
|
n,
|
||||||
|
description ? " (" : "", strempty(description), description ? ")" : "");
|
||||||
|
|
||||||
|
return 1; /* positive change */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_remove(AutoDeviceContext *c, sd_device *dev) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
assert(dev);
|
||||||
|
|
||||||
|
if (!c->candidate)
|
||||||
|
return 0; /* no change */
|
||||||
|
|
||||||
|
const char *node;
|
||||||
|
r = sd_device_get_devname(dev, &node);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_error_errno(dev, r, "Failed to acquire device node of block device: %m");
|
||||||
|
|
||||||
|
if (!path_equal(c->candidate, node))
|
||||||
|
return 0; /* no change */
|
||||||
|
|
||||||
|
log_info("Candidate device %s disappeared.", node);
|
||||||
|
c->candidate = mfree(c->candidate);
|
||||||
|
c->candidate_diskseq = UINT64_MAX;
|
||||||
|
|
||||||
|
return 1; /* negative change */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int auto_device_on_uevent(sd_device_monitor *monitor, sd_device *dev, void *userdata) {
|
||||||
|
AutoDeviceContext *c = ASSERT_PTR(userdata);
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(dev);
|
||||||
|
|
||||||
|
/* Note that any failure returned from this callback terminates the event loop (and hence the
|
||||||
|
* automatic pick) with that error, see the sd_event_source_set_exit_on_failure() call in
|
||||||
|
* sysinstall_auto_pick_block_device(). We must never continue with uevent processing disabled: the
|
||||||
|
* settle timer would keep ticking and commit to the current candidate, while a second disk showing
|
||||||
|
* up – i.e. the very thing that must make us refuse – would go unnoticed. */
|
||||||
|
|
||||||
|
sd_device_action_t action;
|
||||||
|
r = sd_device_get_action(dev, &action);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_error_errno(dev, r, "Failed to acquire uevent action of block device: %m");
|
||||||
|
|
||||||
|
if (action == SD_DEVICE_REMOVE)
|
||||||
|
r = auto_device_remove(c, dev);
|
||||||
|
else {
|
||||||
|
/* All non-REMOVE actions mean "device exists afterwards". Re-run the full filter set, the
|
||||||
|
* tri-state distinguishes "never interesting" from "was/is a candidate, but currently
|
||||||
|
* fails a dynamic filter (e.g. medium ejected, read-only flipped on)". */
|
||||||
|
_cleanup_(block_device_done) BlockDevice d = BLOCK_DEVICE_NULL;
|
||||||
|
r = blockdev_list_one(dev, c->flags, c->root_devno, c->whole_root_devno, &d);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
|
||||||
|
case BLOCKDEV_LIST_MATCH_NO:
|
||||||
|
case BLOCKDEV_LIST_MATCH_SKIPPED:
|
||||||
|
case BLOCKDEV_LIST_MATCH_FILTERED:
|
||||||
|
r = auto_device_remove(c, dev);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLOCKDEV_LIST_MATCH_YES:
|
||||||
|
r = auto_device_add(c, &d);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert_not_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Restart the timer if auto_device_add()/auto_device_remove() indicated a relevant change by
|
||||||
|
* returning > 0. */
|
||||||
|
return auto_device_arm_timer(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sysinstall_auto_pick_block_device(usec_t settle_timeout, char **ret_node) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(ret_node);
|
||||||
|
|
||||||
|
/* Watches the block device tree until it has been quiet for 'settle_timeout' (measured from
|
||||||
|
* invocation or from the last block device uevent, whichever is later), and then returns the one
|
||||||
|
* candidate device present. Fails immediately if more than one candidate is ever present, and
|
||||||
|
* fails after the timeout if none is. */
|
||||||
|
|
||||||
|
_cleanup_(auto_device_context_done) AutoDeviceContext c = {
|
||||||
|
.settle_timeout = settle_timeout,
|
||||||
|
.candidate_diskseq = UINT64_MAX,
|
||||||
|
.flags = auto_device_list_flags(),
|
||||||
|
};
|
||||||
|
|
||||||
|
r = sd_event_new(&c.event);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to allocate event loop: %m");
|
||||||
|
|
||||||
|
/* Make SIGINT/SIGTERM terminate the loop cleanly, with an error code we recognize below */
|
||||||
|
r = sd_event_add_signal(c.event, /* ret= */ NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK,
|
||||||
|
/* callback= */ NULL, INT_TO_PTR(-ECANCELED));
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to add SIGINT handler: %m");
|
||||||
|
r = sd_event_add_signal(c.event, /* ret= */ NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK,
|
||||||
|
/* callback= */ NULL, INT_TO_PTR(-ECANCELED));
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to add SIGTERM handler: %m");
|
||||||
|
|
||||||
|
/* Determine the disk the running system is booted from, so that we never pick it as installation
|
||||||
|
* target. In the interactive flow a failure to determine it merely changes which devices a human is
|
||||||
|
* offered, but here it decides what may be erased unattended, hence refuse operation if the booted
|
||||||
|
* disk cannot be determined (e.g. NFS or multi-device root file systems). */
|
||||||
|
r = blockdev_get_root(LOG_ERR, &c.root_devno);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
|
||||||
|
"Unable to determine the disk the running system is booted from, "
|
||||||
|
"cannot reliably exclude it from the candidate set, refusing.");
|
||||||
|
|
||||||
|
r = block_get_whole_disk(c.root_devno, &c.whole_root_devno);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to determine whole disk of the root block device: %m");
|
||||||
|
|
||||||
|
/* Start the monitor *before* the initial enumeration so we don't lose events that fire during the
|
||||||
|
* enumeration window. Seeing the same device twice is harmless, auto_device_add() is idempotent. */
|
||||||
|
r = sd_device_monitor_new(&c.monitor);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to allocate block device monitor: %m");
|
||||||
|
|
||||||
|
(void) sd_device_monitor_set_description(c.monitor, "sysinstall-auto-device");
|
||||||
|
|
||||||
|
r = sd_device_monitor_filter_add_match_subsystem_devtype(c.monitor, "block", /* devtype= */ NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to configure block device monitor filter: %m");
|
||||||
|
|
||||||
|
r = sd_device_monitor_attach_event(c.monitor, c.event);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to attach block device monitor to event loop: %m");
|
||||||
|
|
||||||
|
r = sd_device_monitor_start(c.monitor, auto_device_on_uevent, &c);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to start block device monitor: %m");
|
||||||
|
|
||||||
|
/* By default sd-event just disables an event source whose callback fails. That would leave us
|
||||||
|
* running blind: the settle timer would keep going and eventually commit to whatever candidate we
|
||||||
|
* had, while further devices – in particular a second candidate – could no longer be observed.
|
||||||
|
* Hence make any failure in the uevent callback terminate the event loop (and thus the automatic
|
||||||
|
* pick) instead. */
|
||||||
|
r = sd_event_source_set_exit_on_failure(sd_device_monitor_get_event_source(c.monitor), true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to enable exit-on-failure for block device monitor: %m");
|
||||||
|
|
||||||
|
BlockDevice *l = NULL;
|
||||||
|
size_t n = 0;
|
||||||
|
CLEANUP_ARRAY(l, n, block_device_array_free);
|
||||||
|
|
||||||
|
r = blockdev_list_full(c.flags, c.root_devno, c.whole_root_devno, &l, &n);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
FOREACH_ARRAY(d, l, n) {
|
||||||
|
r = auto_device_add(&c, d);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The settle clock starts ticking now, regardless of whether we found something already */
|
||||||
|
r = auto_device_arm_timer(&c);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Tell the service manager (if any) that we are now watching for devices, so that whoever runs us
|
||||||
|
* knows it's safe to plug in the target disk now. */
|
||||||
|
(void) sd_notify(/* unset_environment= */ false, "READY=1");
|
||||||
|
|
||||||
|
r = sd_event_loop(c.event);
|
||||||
|
if (r == -ECANCELED)
|
||||||
|
return log_error_errno(r, "Installation cancelled.");
|
||||||
|
if (r < 0)
|
||||||
|
return r; /* Already logged about */
|
||||||
|
|
||||||
|
r = auto_device_get_node(&c, ret_node);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
log_info("Selected '%s' as installation target.", *ret_node);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "forward.h"
|
||||||
|
|
||||||
|
int sysinstall_auto_pick_block_device(usec_t settle_timeout, char **ret_node);
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "sd-daemon.h"
|
||||||
#include "sd-json.h"
|
#include "sd-json.h"
|
||||||
#include "sd-varlink.h"
|
#include "sd-varlink.h"
|
||||||
|
|
||||||
@@ -43,12 +44,16 @@
|
|||||||
#include "prompt-util.h"
|
#include "prompt-util.h"
|
||||||
#include "string-table.h"
|
#include "string-table.h"
|
||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
|
#include "sysinstall-auto-device.h"
|
||||||
#include "terminal-util.h"
|
#include "terminal-util.h"
|
||||||
|
#include "time-util.h"
|
||||||
#include "varlink-io.systemd.SysInstall.h"
|
#include "varlink-io.systemd.SysInstall.h"
|
||||||
#include "varlink-util.h"
|
#include "varlink-util.h"
|
||||||
#include "verbs.h"
|
#include "verbs.h"
|
||||||
|
|
||||||
static char *arg_node = NULL;
|
static char *arg_node = NULL;
|
||||||
|
static bool arg_device_auto = false;
|
||||||
|
static usec_t arg_device_auto_timeout = 30 * USEC_PER_SEC;
|
||||||
static bool arg_welcome = true;
|
static bool arg_welcome = true;
|
||||||
static int arg_erase = -1; /* tri-state */
|
static int arg_erase = -1; /* tri-state */
|
||||||
static bool arg_confirm = true;
|
static bool arg_confirm = true;
|
||||||
@@ -179,6 +184,18 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
OPTION_COMMON_VERSION:
|
OPTION_COMMON_VERSION:
|
||||||
return version();
|
return version();
|
||||||
|
|
||||||
|
OPTION_LONG("device-auto", NULL, "Automatically pick the target disk, once exactly one candidate is present and the device list settled"):
|
||||||
|
arg_device_auto = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
OPTION_LONG("device-auto-timeout", "SECS", "How long the list of candidate disks must remain unchanged before a disk is picked"):
|
||||||
|
r = parse_sec(opts.arg, &arg_device_auto_timeout);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse --device-auto-timeout= parameter: %s", opts.arg);
|
||||||
|
if (arg_device_auto_timeout == USEC_INFINITY)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--device-auto-timeout= cannot be infinite, refusing.");
|
||||||
|
break;
|
||||||
|
|
||||||
OPTION_LONG("welcome", "no", "Disable the welcome text"):
|
OPTION_LONG("welcome", "no", "Disable the welcome text"):
|
||||||
r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome);
|
r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
@@ -285,6 +302,9 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
if (strv_length(args) > 1)
|
if (strv_length(args) > 1)
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
|
||||||
if (!strv_isempty(args)) {
|
if (!strv_isempty(args)) {
|
||||||
|
if (arg_device_auto)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --device-auto with an explicitly specified device, refusing.");
|
||||||
|
|
||||||
arg_node = strdup(args[0]);
|
arg_node = strdup(args[0]);
|
||||||
if (!arg_node)
|
if (!arg_node)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
@@ -2078,7 +2098,7 @@ static void end_marker(void) {
|
|||||||
if (!arg_welcome)
|
if (!arg_welcome)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal());
|
printf("\n%sExiting system installer tool.%s\n\n", ansi_grey(), ansi_normal());
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2133,6 +2153,18 @@ static int run(int argc, char *argv[]) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
r = validate_run(&repart_link, arg_node);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
} else if (arg_device_auto) {
|
||||||
|
r = print_welcome(&mute_console_link);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = sysinstall_auto_pick_block_device(arg_device_auto_timeout, &arg_node);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
r = validate_run(&repart_link, arg_node);
|
r = validate_run(&repart_link, arg_node);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
@@ -2208,6 +2240,10 @@ static int run(int argc, char *argv[]) {
|
|||||||
|
|
||||||
putchar('\n');
|
putchar('\n');
|
||||||
|
|
||||||
|
/* All questions asked, we are about to begin the installation. Let the service manager know (it's
|
||||||
|
* fine if we sent READY=1 before already, e.g. from the device auto-pick logic). */
|
||||||
|
(void) sd_notify(/* unset_environment= */ false, "READY=1");
|
||||||
|
|
||||||
r = sysinstall_context_run(&context);
|
r = sysinstall_context_run(&context);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ CRED_VALUE_BASE64=$(echo -n "$CRED_VALUE" | base64 -w0)
|
|||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
set +e
|
set +e
|
||||||
|
# Stop the installer first: it might still be writing to the devices and files removed below.
|
||||||
|
if [[ -n "${UNIT:-}" ]]; then
|
||||||
|
systemctl stop "$UNIT"
|
||||||
|
systemctl reset-failed "$UNIT"
|
||||||
|
UNIT=""
|
||||||
|
fi
|
||||||
if [[ "$MOUNTED" -eq 1 ]]; then
|
if [[ "$MOUNTED" -eq 1 ]]; then
|
||||||
umount -R "$WORKDIR/mnt"
|
umount -R "$WORKDIR/mnt"
|
||||||
MOUNTED=0
|
MOUNTED=0
|
||||||
@@ -54,9 +60,92 @@ cleanup() {
|
|||||||
systemd-dissect --detach "$LOOPDEV"
|
systemd-dissect --detach "$LOOPDEV"
|
||||||
LOOPDEV=""
|
LOOPDEV=""
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "${TARGETDEV:-}" ]]; then
|
||||||
|
systemd-dissect --detach "$TARGETDEV"
|
||||||
|
TARGETDEV=""
|
||||||
|
fi
|
||||||
|
if [[ -n "${TARGETDEV2:-}" ]]; then
|
||||||
|
systemd-dissect --detach "$TARGETDEV2"
|
||||||
|
TARGETDEV2=""
|
||||||
|
fi
|
||||||
rm -rf "$WORKDIR"
|
rm -rf "$WORKDIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_device_auto_environment() {
|
||||||
|
# Safety check for the --device-auto testcases: systemd-sysinstall must be able to determine the disk
|
||||||
|
# the running system is booted from (it refuses the automatic pick otherwise), and no candidate disk
|
||||||
|
# may be around initially, since the testcases rely on the disks they attach themselves being the only
|
||||||
|
# candidates — otherwise the automatic pick would either fail or – worse – install to some unexpected
|
||||||
|
# disk. Query the candidate list the same way systemd-sysinstall does (i.e. with the very same filter
|
||||||
|
# set, except that systemd-sysinstall additionally ignores loopback devices unless
|
||||||
|
# $SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP is set, which the testcases do), instead of approximating
|
||||||
|
# the filters here.
|
||||||
|
#
|
||||||
|
# Returns non-zero (and the testcase is to be skipped) only if the booted disk cannot be determined,
|
||||||
|
# i.e. if the feature is genuinely unusable in this environment. Stray candidate disks on the other
|
||||||
|
# hand mean the test environment is broken (e.g. a loopback device leaked by an earlier testcase),
|
||||||
|
# which must not silently turn the --device-auto testcases into no-ops, hence fail hard in that case.
|
||||||
|
local out
|
||||||
|
|
||||||
|
if ! bootctl -RR >/dev/null; then
|
||||||
|
echo "Cannot determine the disk the system is booted from." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if out="$(varlinkctl call --more "exec:$(command -v systemd-repart)" io.systemd.Repart.ListCandidateDevices '{"ignoreRoot":true,"ignoreEmpty":true}' 2>&1)"; then
|
||||||
|
echo "Unexpected candidate disks present, test environment is broken: $out" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$out" != *"io.systemd.Repart.NoCandidateDevices"* ]]; then
|
||||||
|
echo "Failed to enumerate candidate disks: $out" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_device_auto_installer() {
|
||||||
|
# Starts the installer as a notify service with --device-auto and the specified settle timeout (plus
|
||||||
|
# any further arguments). systemd-sysinstall sends READY=1 once its device monitor is set up, hence
|
||||||
|
# systemd-run only returns once we can be sure that a disk appearing from now on will be noticed.
|
||||||
|
# Loopback devices are not considered as candidates by default, hence explicitly permit them, the
|
||||||
|
# target disks of the testcases are loopback devices.
|
||||||
|
local settle_timeout="${1:?}"
|
||||||
|
shift
|
||||||
|
|
||||||
|
UNIT="sysinstall-device-auto-$RANDOM.service"
|
||||||
|
systemd-run \
|
||||||
|
--unit="$UNIT" \
|
||||||
|
--service-type=notify \
|
||||||
|
--property=RemainAfterExit=yes \
|
||||||
|
--setenv=SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP=1 \
|
||||||
|
--quiet \
|
||||||
|
-- \
|
||||||
|
systemd-sysinstall \
|
||||||
|
--welcome=no \
|
||||||
|
--chrome=no \
|
||||||
|
--confirm=no \
|
||||||
|
--summary=no \
|
||||||
|
--erase=yes \
|
||||||
|
--variables=no \
|
||||||
|
--reboot=no \
|
||||||
|
--mute-console=no \
|
||||||
|
--copy-locale=no \
|
||||||
|
--copy-keymap=no \
|
||||||
|
--copy-timezone=no \
|
||||||
|
--device-auto \
|
||||||
|
--device-auto-timeout="$settle_timeout" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_device_auto_installer() {
|
||||||
|
# Waits for the installer started by start_device_auto_installer() to finish, and dumps its output, so
|
||||||
|
# that it ends up in the test log whatever happened — hence don't abort right away if the wait times
|
||||||
|
# out, the assertions of the caller will catch that case after the output has been dumped.
|
||||||
|
timeout "${1:?}" bash -c "while [[ \"\$(systemctl show -P SubState '$UNIT')\" == running ]]; do sleep 1; done" || :
|
||||||
|
journalctl --sync
|
||||||
|
journalctl --no-pager -u "$UNIT"
|
||||||
|
}
|
||||||
|
|
||||||
create_fake_os_source_tree() {
|
create_fake_os_source_tree() {
|
||||||
# 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via
|
# 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via
|
||||||
# the repart.sysinstall.d definitions: CopyFiles= seeds the new root
|
# the repart.sysinstall.d definitions: CopyFiles= seeds the new root
|
||||||
@@ -222,4 +311,306 @@ testcase_sysinstall_varlink_basic() {
|
|||||||
validate_image
|
validate_image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto() {
|
||||||
|
WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)"
|
||||||
|
LOOPDEV=""
|
||||||
|
TARGETDEV=""
|
||||||
|
UNIT=""
|
||||||
|
MOUNTED=0
|
||||||
|
|
||||||
|
echo "WORKDIR=$WORKDIR"
|
||||||
|
|
||||||
|
trap cleanup RETURN
|
||||||
|
|
||||||
|
if ! check_device_auto_environment; then
|
||||||
|
echo "Environment not suitable for --device-auto tests, skipping." >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_fake_os_source_tree
|
||||||
|
|
||||||
|
# Put a bare file system on the target image, so that systemd-dissect --attach accepts it as an
|
||||||
|
# image later on (an all-zero file is not a valid image). Don't put a partition table on it though:
|
||||||
|
# the resulting loopback device shall have no partition block devices, so that nothing is in the
|
||||||
|
# way when systemd-repart adds the new ones. systemd-sysinstall erases the file system anyway.
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target.img"
|
||||||
|
|
||||||
|
# Start the installer while no candidate disk exists yet.
|
||||||
|
start_device_auto_installer 30s \
|
||||||
|
--set-credential="marker:$CRED_VALUE" \
|
||||||
|
--kernel="$WORKDIR/testuki.efi" \
|
||||||
|
--definitions="$DEFS"
|
||||||
|
|
||||||
|
# Only now, 5s later, make the target disk appear: a partition-scanning loopback device backed by
|
||||||
|
# the target image. It's the sole candidate disk in the VM, hence it must be picked.
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# The installer must still be waiting at this point, there was nothing to install to so far.
|
||||||
|
assert_eq "$(systemctl show -P SubState "$UNIT")" "running"
|
||||||
|
|
||||||
|
TARGETDEV="$(systemd-dissect --attach "$WORKDIR/target.img")"
|
||||||
|
udevadm wait --settle "$TARGETDEV"
|
||||||
|
|
||||||
|
# Wait for the installation to finish, and check that it succeeded.
|
||||||
|
wait_for_device_auto_installer 180
|
||||||
|
assert_eq "$(systemctl show -P SubState "$UNIT")" "exited"
|
||||||
|
assert_eq "$(systemctl show -P Result "$UNIT")" "success"
|
||||||
|
|
||||||
|
# Release our loopback device again, validate_image attaches the image on its own.
|
||||||
|
systemd-dissect --detach "$TARGETDEV"
|
||||||
|
TARGETDEV=""
|
||||||
|
|
||||||
|
validate_image
|
||||||
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto_two_candidates() {
|
||||||
|
WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)"
|
||||||
|
LOOPDEV=""
|
||||||
|
TARGETDEV=""
|
||||||
|
TARGETDEV2=""
|
||||||
|
UNIT=""
|
||||||
|
MOUNTED=0
|
||||||
|
|
||||||
|
echo "WORKDIR=$WORKDIR"
|
||||||
|
|
||||||
|
trap cleanup RETURN
|
||||||
|
|
||||||
|
if ! check_device_auto_environment; then
|
||||||
|
echo "Environment not suitable for --device-auto tests, skipping." >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
create_fake_os_source_tree
|
||||||
|
|
||||||
|
# Attach two candidate disks, both carrying a bare file system (see testcase_sysinstall_device_auto
|
||||||
|
# for why), so that we can verify below that neither of them was touched.
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target.img"
|
||||||
|
truncate -s 512M "$WORKDIR/target2.img"
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target2.img"
|
||||||
|
|
||||||
|
TARGETDEV="$(systemd-dissect --attach "$WORKDIR/target.img")"
|
||||||
|
TARGETDEV2="$(systemd-dissect --attach "$WORKDIR/target2.img")"
|
||||||
|
udevadm wait --settle "$TARGETDEV" "$TARGETDEV2"
|
||||||
|
|
||||||
|
# With two candidate disks present the automatic pick must refuse right away, i.e. well before the
|
||||||
|
# settle timeout expires, and without installing anywhere.
|
||||||
|
local start="$SECONDS" rc=0 out
|
||||||
|
out="$(SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP=1 timeout 60 systemd-sysinstall \
|
||||||
|
--welcome=no \
|
||||||
|
--chrome=no \
|
||||||
|
--confirm=no \
|
||||||
|
--summary=no \
|
||||||
|
--erase=yes \
|
||||||
|
--variables=no \
|
||||||
|
--reboot=no \
|
||||||
|
--mute-console=no \
|
||||||
|
--copy-locale=no \
|
||||||
|
--copy-keymap=no \
|
||||||
|
--copy-timezone=no \
|
||||||
|
--set-credential="marker:$CRED_VALUE" \
|
||||||
|
--kernel="$WORKDIR/testuki.efi" \
|
||||||
|
--definitions="$DEFS" \
|
||||||
|
--device-auto \
|
||||||
|
--device-auto-timeout=30s 2>&1)" || rc=$?
|
||||||
|
echo "$out"
|
||||||
|
assert_neq "$rc" 0
|
||||||
|
assert_neq "$rc" 124
|
||||||
|
assert_in "Multiple candidate block devices found" "$out"
|
||||||
|
assert_le "$((SECONDS - start))" 10
|
||||||
|
|
||||||
|
# Neither disk may have been touched: the bare file systems must have survived unmodified, in
|
||||||
|
# particular no partition table may have been written.
|
||||||
|
assert_eq "$(blkid -p -o value -s TYPE "$TARGETDEV")" "ext4"
|
||||||
|
assert_eq "$(blkid -p -o value -s TYPE "$TARGETDEV2")" "ext4"
|
||||||
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto_late_second_candidate() {
|
||||||
|
WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)"
|
||||||
|
LOOPDEV=""
|
||||||
|
TARGETDEV=""
|
||||||
|
TARGETDEV2=""
|
||||||
|
UNIT=""
|
||||||
|
MOUNTED=0
|
||||||
|
|
||||||
|
echo "WORKDIR=$WORKDIR"
|
||||||
|
|
||||||
|
trap cleanup RETURN
|
||||||
|
|
||||||
|
if ! check_device_auto_environment; then
|
||||||
|
echo "Environment not suitable for --device-auto tests, skipping." >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Same as testcase_sysinstall_device_auto_two_candidates, but the second disk shows up only while the
|
||||||
|
# installer is already waiting for the settle timeout, i.e. the refusal must come via the uevent path
|
||||||
|
# rather than the initial enumeration.
|
||||||
|
truncate -s 512M "$WORKDIR/target.img"
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target.img"
|
||||||
|
truncate -s 512M "$WORKDIR/target2.img"
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target2.img"
|
||||||
|
|
||||||
|
start_device_auto_installer 30s
|
||||||
|
|
||||||
|
TARGETDEV="$(systemd-dissect --attach "$WORKDIR/target.img")"
|
||||||
|
udevadm wait --settle "$TARGETDEV"
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# A single candidate must not have been picked yet, the settle timeout is far from expired.
|
||||||
|
assert_eq "$(systemctl show -P SubState "$UNIT")" "running"
|
||||||
|
|
||||||
|
# Now make the second disk appear. This must make the installer refuse right away.
|
||||||
|
local start="$SECONDS" out
|
||||||
|
TARGETDEV2="$(systemd-dissect --attach "$WORKDIR/target2.img")"
|
||||||
|
udevadm wait --settle "$TARGETDEV2"
|
||||||
|
|
||||||
|
wait_for_device_auto_installer 60
|
||||||
|
assert_le "$((SECONDS - start))" 10
|
||||||
|
assert_neq "$(systemctl show -P SubState "$UNIT")" "running"
|
||||||
|
assert_neq "$(systemctl show -P Result "$UNIT")" "success"
|
||||||
|
out="$(journalctl --no-pager -o cat -u "$UNIT")"
|
||||||
|
assert_in "Multiple candidate block devices found" "$out"
|
||||||
|
|
||||||
|
# Neither disk may have been touched.
|
||||||
|
assert_eq "$(blkid -p -o value -s TYPE "$TARGETDEV")" "ext4"
|
||||||
|
assert_eq "$(blkid -p -o value -s TYPE "$TARGETDEV2")" "ext4"
|
||||||
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto_candidate_disappears() {
|
||||||
|
WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)"
|
||||||
|
LOOPDEV=""
|
||||||
|
TARGETDEV=""
|
||||||
|
UNIT=""
|
||||||
|
MOUNTED=0
|
||||||
|
|
||||||
|
echo "WORKDIR=$WORKDIR"
|
||||||
|
|
||||||
|
trap cleanup RETURN
|
||||||
|
|
||||||
|
if ! check_device_auto_environment; then
|
||||||
|
echo "Environment not suitable for --device-auto tests, skipping." >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A candidate that appears and disappears again while the installer is waiting must be forgotten
|
||||||
|
# about: the installer must not pick the stale device node, but restart the settle clock and
|
||||||
|
# eventually fail for lack of a candidate.
|
||||||
|
truncate -s 512M "$WORKDIR/target.img"
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target.img"
|
||||||
|
|
||||||
|
start_device_auto_installer 10s
|
||||||
|
|
||||||
|
TARGETDEV="$(systemd-dissect --attach "$WORKDIR/target.img")"
|
||||||
|
udevadm wait --settle "$TARGETDEV"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
assert_eq "$(systemctl show -P SubState "$UNIT")" "running"
|
||||||
|
|
||||||
|
local start="$SECONDS" out
|
||||||
|
systemd-dissect --detach "$TARGETDEV"
|
||||||
|
TARGETDEV=""
|
||||||
|
|
||||||
|
# The removal restarts the settle clock, hence the failure may come no earlier than a full settle
|
||||||
|
# timeout after the disk disappeared.
|
||||||
|
wait_for_device_auto_installer 60
|
||||||
|
assert_ge "$((SECONDS - start))" 10
|
||||||
|
assert_neq "$(systemctl show -P SubState "$UNIT")" "running"
|
||||||
|
assert_neq "$(systemctl show -P Result "$UNIT")" "success"
|
||||||
|
out="$(journalctl --no-pager -o cat -u "$UNIT")"
|
||||||
|
assert_in "disappeared" "$out"
|
||||||
|
assert_in "No suitable block device" "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto_no_candidate() {
|
||||||
|
if ! check_device_auto_environment; then
|
||||||
|
echo "Environment not suitable for --device-auto tests, skipping." >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# With no candidate disk around the automatic pick must fail once the settle timeout expires — not
|
||||||
|
# before it expires, and not by hanging around forever either.
|
||||||
|
local start="$SECONDS" rc=0 out
|
||||||
|
out="$(timeout 60 systemd-sysinstall \
|
||||||
|
--welcome=no \
|
||||||
|
--chrome=no \
|
||||||
|
--confirm=no \
|
||||||
|
--summary=no \
|
||||||
|
--erase=yes \
|
||||||
|
--variables=no \
|
||||||
|
--reboot=no \
|
||||||
|
--mute-console=no \
|
||||||
|
--device-auto \
|
||||||
|
--device-auto-timeout=5s 2>&1)" || rc=$?
|
||||||
|
echo "$out"
|
||||||
|
assert_neq "$rc" 0
|
||||||
|
assert_neq "$rc" 124
|
||||||
|
assert_in "No suitable block device" "$out"
|
||||||
|
assert_ge "$((SECONDS - start))" 5
|
||||||
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto_loop_ignored() {
|
||||||
|
WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)"
|
||||||
|
LOOPDEV=""
|
||||||
|
TARGETDEV=""
|
||||||
|
UNIT=""
|
||||||
|
MOUNTED=0
|
||||||
|
|
||||||
|
echo "WORKDIR=$WORKDIR"
|
||||||
|
|
||||||
|
trap cleanup RETURN
|
||||||
|
|
||||||
|
if ! check_device_auto_environment; then
|
||||||
|
echo "Environment not suitable for --device-auto tests, skipping." >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unless $SYSTEMD_SYSINSTALL_PERMIT_AUTO_TARGET_LOOP is set, loopback devices must not be considered as
|
||||||
|
# candidates, i.e. a lone loopback disk must not be picked, and the automatic pick must fail once the
|
||||||
|
# settle timeout expires, exactly as if no disk was around.
|
||||||
|
truncate -s 512M "$WORKDIR/target.img"
|
||||||
|
mkfs.ext4 -q -F "$WORKDIR/target.img"
|
||||||
|
TARGETDEV="$(systemd-dissect --attach "$WORKDIR/target.img")"
|
||||||
|
udevadm wait --settle "$TARGETDEV"
|
||||||
|
|
||||||
|
local start="$SECONDS" rc=0 out
|
||||||
|
out="$(timeout 60 systemd-sysinstall \
|
||||||
|
--welcome=no \
|
||||||
|
--chrome=no \
|
||||||
|
--confirm=no \
|
||||||
|
--summary=no \
|
||||||
|
--erase=yes \
|
||||||
|
--variables=no \
|
||||||
|
--reboot=no \
|
||||||
|
--mute-console=no \
|
||||||
|
--device-auto \
|
||||||
|
--device-auto-timeout=5s 2>&1)" || rc=$?
|
||||||
|
echo "$out"
|
||||||
|
assert_neq "$rc" 0
|
||||||
|
assert_neq "$rc" 124
|
||||||
|
assert_in "No suitable block device" "$out"
|
||||||
|
assert_ge "$((SECONDS - start))" 5
|
||||||
|
|
||||||
|
# The loopback disk must not have been touched.
|
||||||
|
assert_eq "$(blkid -p -o value -s TYPE "$TARGETDEV")" "ext4"
|
||||||
|
}
|
||||||
|
|
||||||
|
testcase_sysinstall_device_auto_argument_errors() {
|
||||||
|
local rc out
|
||||||
|
|
||||||
|
# --device-auto cannot be combined with an explicitly specified target device...
|
||||||
|
rc=0
|
||||||
|
out="$(timeout 30 systemd-sysinstall --device-auto /dev/null 2>&1)" || rc=$?
|
||||||
|
echo "$out"
|
||||||
|
assert_neq "$rc" 0
|
||||||
|
assert_neq "$rc" 124
|
||||||
|
assert_in "Cannot combine --device-auto with an explicitly specified device" "$out"
|
||||||
|
|
||||||
|
# ...and the settle timeout cannot be infinite.
|
||||||
|
rc=0
|
||||||
|
out="$(timeout 30 systemd-sysinstall --device-auto --device-auto-timeout=infinity 2>&1)" || rc=$?
|
||||||
|
echo "$out"
|
||||||
|
assert_neq "$rc" 0
|
||||||
|
assert_neq "$rc" 124
|
||||||
|
assert_in "--device-auto-timeout= cannot be infinite" "$out"
|
||||||
|
}
|
||||||
|
|
||||||
run_testcases
|
run_testcases
|
||||||
|
|||||||
Reference in New Issue
Block a user