mirror of
https://github.com/systemd/systemd
synced 2025-11-19 08:44:44 +01:00
Compare commits
15 Commits
d62ab43fd0
...
cc7ccd3c6a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7ccd3c6a | ||
|
|
a5962d3327 | ||
|
|
80297f75e1 | ||
|
|
e582484789 | ||
|
|
46da450f13 | ||
|
|
78c017a8ed | ||
|
|
aa47d8ade1 | ||
|
|
0ef4118c78 | ||
|
|
757887d01d | ||
|
|
524ebfe28a | ||
|
|
bb45a893c2 | ||
|
|
a251345cf5 | ||
|
|
b09ea23978 | ||
|
|
7d4e8f920b | ||
|
|
fbc9f0dd4e |
@ -223,11 +223,12 @@ int main(int argc, char **argv) {
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to take an exclusive and nonblocking BSD lock
|
// try to take an exclusive and nonblocking BSD lock (use O_WRONLY mode to ensure udev
|
||||||
|
// rescans the device once the lock is closed)
|
||||||
__attribute__((cleanup(closep))) int fd =
|
__attribute__((cleanup(closep))) int fd =
|
||||||
lock_whole_disk_from_devname(
|
lock_whole_disk_from_devname(
|
||||||
argv[1],
|
argv[1],
|
||||||
O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY,
|
O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY,
|
||||||
LOCK_EX|LOCK_NB);
|
LOCK_EX|LOCK_NB);
|
||||||
|
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
|
|||||||
@ -215,7 +215,8 @@
|
|||||||
<term><varname>Multicast=</varname></term>
|
<term><varname>Multicast=</varname></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>Takes a boolean. If set to true, the multicast flag on the device is enabled. Defaults
|
<para>Takes a boolean. If set to true, the multicast flag on the device is enabled. Defaults
|
||||||
to unset.</para>
|
to unset and the flag is unchanged. When disabled, <varname>IPv6AcceptRA=</varname> and
|
||||||
|
<varname>IPv6SendRA=</varname> cannot be enabled.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v239"/>
|
<xi:include href="version-info.xml" xpointer="v239"/>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -942,10 +943,19 @@ DuplicateAddressDetection=none</programlisting></para>
|
|||||||
<para>Takes a boolean. Controls IPv6 Router Advertisement (RA) reception support for the interface.
|
<para>Takes a boolean. Controls IPv6 Router Advertisement (RA) reception support for the interface.
|
||||||
If true, RAs are accepted; if false, RAs are ignored. When RAs are accepted, they may trigger the
|
If true, RAs are accepted; if false, RAs are ignored. When RAs are accepted, they may trigger the
|
||||||
start of the DHCPv6 client if the relevant flags are set in the RA data, or if no routers are found
|
start of the DHCPv6 client if the relevant flags are set in the RA data, or if no routers are found
|
||||||
on the link. Defaults to false for bridge devices, when <varname>IPv6Forwarding=</varname>,
|
on the link.</para>
|
||||||
<varname>IPv6SendRA=</varname>, or <varname>KeepMaster=</varname> is enabled. Otherwise, enabled by
|
|
||||||
default. Cannot be enabled on devices aggregated in a bond device or when link-local addressing is
|
<para>This cannot be enabled on devices aggregated in a bond device, or when IPv6 link-local
|
||||||
disabled.</para>
|
addressing (see <varname>LinkLocalAddressing=</varname>) or multicasting (see
|
||||||
|
<varname>Multicast=</varname>) is disabled. Note, multicasting is disabled by default for some
|
||||||
|
configurations, e.g. bridge ports. Hence, in that case <varname>Multicast=</varname> needs to be
|
||||||
|
explicitly enabled to make this feature usable. When <varname>IPv6SendRA=</varname>,
|
||||||
|
<varname>IPv6Forwarding=</varname>, or <varname>IPMasquerade=</varname> is enabled, this feature is
|
||||||
|
disabled by default, but can be overridden by explicitly enabling this setting. Note,
|
||||||
|
<varname>IPv6Forwarding=</varname> may be indirectly enabled when the global setting with the same
|
||||||
|
name is enabled, or when <varname>IPMasquerade=</varname> is enabled on <emphasis>any other
|
||||||
|
interfaces</emphasis>. See also <varname>IPv6Forwarding=</varname> and
|
||||||
|
<varname>IPMasquerade=</varname> for more details. Enabled by default otherwise.</para>
|
||||||
|
|
||||||
<para>Further settings for the IPv6 RA support may be configured in the [IPv6AcceptRA]
|
<para>Further settings for the IPv6 RA support may be configured in the [IPv6AcceptRA]
|
||||||
section, see below.</para>
|
section, see below.</para>
|
||||||
|
|||||||
@ -41,7 +41,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
if (S_ISBLK(st.st_mode)) {
|
if (S_ISBLK(st.st_mode)) {
|
||||||
/* Lock the device so that udev doesn't interfere with our work */
|
/* Lock the device so that udev doesn't interfere with our work */
|
||||||
|
|
||||||
lock_fd = lock_whole_block_device(st.st_rdev, LOCK_EX);
|
lock_fd = lock_whole_block_device(st.st_rdev, O_WRONLY, LOCK_EX);
|
||||||
if (lock_fd < 0)
|
if (lock_fd < 0)
|
||||||
return log_error_errno(lock_fd, "Failed to lock whole block device of \"%s\": %m", device);
|
return log_error_errno(lock_fd, "Failed to lock whole block device of \"%s\": %m", device);
|
||||||
} else
|
} else
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
#include "path-util.h"
|
#include "path-util.h"
|
||||||
#include "process-util.h"
|
#include "process-util.h"
|
||||||
#include "random-util.h"
|
#include "random-util.h"
|
||||||
|
#include "reread-partition-table.h"
|
||||||
#include "resize-fs.h"
|
#include "resize-fs.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
@ -2497,7 +2498,7 @@ int home_create_luks(
|
|||||||
|
|
||||||
if (disk_uuid_path)
|
if (disk_uuid_path)
|
||||||
/* Reread partition table if this is a block device */
|
/* Reread partition table if this is a block device */
|
||||||
(void) ioctl(setup->image_fd, BLKRRPART, 0);
|
(void) reread_partition_table_fd(setup->image_fd, /* flags= */ 0);
|
||||||
else {
|
else {
|
||||||
assert(setup->temporary_image_path);
|
assert(setup->temporary_image_path);
|
||||||
|
|
||||||
@ -3469,8 +3470,8 @@ int home_resize_luks(
|
|||||||
if (r > 0)
|
if (r > 0)
|
||||||
log_info("Growing of partition completed.");
|
log_info("Growing of partition completed.");
|
||||||
|
|
||||||
if (S_ISBLK(st.st_mode) && ioctl(image_fd, BLKRRPART, 0) < 0)
|
if (S_ISBLK(st.st_mode))
|
||||||
log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m");
|
(void) reread_partition_table_fd(image_fd, /* flags= */ 0);
|
||||||
|
|
||||||
/* Tell LUKS about the new bigger size too */
|
/* Tell LUKS about the new bigger size too */
|
||||||
r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U);
|
r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U);
|
||||||
@ -3569,8 +3570,8 @@ int home_resize_luks(
|
|||||||
if (r > 0)
|
if (r > 0)
|
||||||
log_info("Shrinking of partition completed.");
|
log_info("Shrinking of partition completed.");
|
||||||
|
|
||||||
if (S_ISBLK(st.st_mode) && ioctl(image_fd, BLKRRPART, 0) < 0)
|
if (S_ISBLK(st.st_mode))
|
||||||
log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m");
|
(void) reread_partition_table_fd(image_fd, /* flags= */ 0);
|
||||||
|
|
||||||
} else { /* → Grow */
|
} else { /* → Grow */
|
||||||
if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
|
if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) {
|
||||||
|
|||||||
@ -713,7 +713,7 @@ int sd_lldp_tx_describe(sd_lldp_tx *lldp_tx, sd_json_variant **ret) {
|
|||||||
|
|
||||||
return sd_json_buildo(
|
return sd_json_buildo(
|
||||||
ret,
|
ret,
|
||||||
SD_JSON_BUILD_PAIR_STRING("ChassisID", SD_ID128_TO_STRING(machine_id)),
|
SD_JSON_BUILD_PAIR_ID128("ChassisID", machine_id),
|
||||||
SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", chassis_id, chassis_id_len),
|
SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", chassis_id, chassis_id_len),
|
||||||
SD_JSON_BUILD_PAIR_STRING("PortID", lldp_tx->ifname),
|
SD_JSON_BUILD_PAIR_STRING("PortID", lldp_tx->ifname),
|
||||||
SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", port_id, port_id_len),
|
SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", port_id, port_id_len),
|
||||||
|
|||||||
@ -14,6 +14,7 @@ int device_opendir(sd_device *device, const char *subdir, DIR **ret);
|
|||||||
int device_get_sysnum_unsigned(sd_device *device, unsigned *ret);
|
int device_get_sysnum_unsigned(sd_device *device, unsigned *ret);
|
||||||
int device_get_property_bool(sd_device *device, const char *key);
|
int device_get_property_bool(sd_device *device, const char *key);
|
||||||
int device_get_property_int(sd_device *device, const char *key, int *ret);
|
int device_get_property_int(sd_device *device, const char *key, int *ret);
|
||||||
|
int device_get_property_uint(sd_device *device, const char *key, unsigned *ret);
|
||||||
int device_get_ifname(sd_device *device, const char **ret);
|
int device_get_ifname(sd_device *device, const char **ret);
|
||||||
int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value);
|
int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value);
|
||||||
int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value);
|
int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value);
|
||||||
|
|||||||
@ -2319,6 +2319,27 @@ int device_get_property_int(sd_device *device, const char *key, int *ret) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int device_get_property_uint(sd_device *device, const char *key, unsigned *ret) {
|
||||||
|
const char *value;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(device);
|
||||||
|
assert(key);
|
||||||
|
|
||||||
|
r = sd_device_get_property_value(device, key, &value);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
unsigned v;
|
||||||
|
r = safe_atou(value, &v);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
*ret = v;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
_public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) {
|
_public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) {
|
||||||
const char *s;
|
const char *s;
|
||||||
sd_id128_t id;
|
sd_id128_t id;
|
||||||
|
|||||||
@ -65,6 +65,7 @@
|
|||||||
#include "process-util.h"
|
#include "process-util.h"
|
||||||
#include "random-util.h"
|
#include "random-util.h"
|
||||||
#include "ratelimit.h"
|
#include "ratelimit.h"
|
||||||
|
#include "reread-partition-table.h"
|
||||||
#include "resize-fs.h"
|
#include "resize-fs.h"
|
||||||
#include "rm-rf.h"
|
#include "rm-rf.h"
|
||||||
#include "set.h"
|
#include "set.h"
|
||||||
@ -855,6 +856,16 @@ static Context* context_free(Context *context) {
|
|||||||
|
|
||||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
|
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
|
||||||
|
|
||||||
|
static void context_disarm_auto_removal(Context *context) {
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
/* Make sure automatic removal of half-written artifacts is disarmed */
|
||||||
|
context->node = mfree(context->node);
|
||||||
|
|
||||||
|
LIST_FOREACH(partitions, p, context->partitions)
|
||||||
|
p->split_path = mfree(p->split_path);
|
||||||
|
}
|
||||||
|
|
||||||
static int context_add_free_area(
|
static int context_add_free_area(
|
||||||
Context *context,
|
Context *context,
|
||||||
uint64_t size,
|
uint64_t size,
|
||||||
@ -7260,12 +7271,8 @@ static int context_write_partition_table(Context *context) {
|
|||||||
else if (capable < 0)
|
else if (capable < 0)
|
||||||
return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m");
|
return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m");
|
||||||
else if (capable > 0) {
|
else if (capable > 0) {
|
||||||
log_info("Telling kernel to reread partition table.");
|
log_info("Informing kernel about changed partitions...");
|
||||||
|
r = reread_partition_table_fd(fdisk_get_devfd(context->fdisk_context), /* flags= */ 0);
|
||||||
if (context->from_scratch)
|
|
||||||
r = fdisk_reread_partition_table(context->fdisk_context);
|
|
||||||
else
|
|
||||||
r = fdisk_reread_changes(context->fdisk_context, original_table);
|
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to reread partition table: %m");
|
return log_error_errno(r, "Failed to reread partition table: %m");
|
||||||
} else
|
} else
|
||||||
@ -10177,10 +10184,7 @@ static int run(int argc, char *argv[]) {
|
|||||||
|
|
||||||
(void) context_dump(context, /*late=*/ true);
|
(void) context_dump(context, /*late=*/ true);
|
||||||
|
|
||||||
context->node = mfree(context->node);
|
context_disarm_auto_removal(context);
|
||||||
|
|
||||||
LIST_FOREACH(partitions, p, context->partitions)
|
|
||||||
p->split_path = mfree(p->split_path);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
#include "path-util.h"
|
#include "path-util.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
|
|
||||||
static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) {
|
static int fd_get_devnum(int fd, BlockDeviceLookupFlags flags, dev_t *ret) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
dev_t devnum;
|
dev_t devnum;
|
||||||
int r;
|
int r;
|
||||||
@ -148,7 +148,7 @@ int block_device_get_originating(sd_device *dev, sd_device **ret) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flags, sd_device **ret) {
|
int block_device_new_from_fd(int fd, BlockDeviceLookupFlags flags, sd_device **ret) {
|
||||||
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
||||||
dev_t devnum;
|
dev_t devnum;
|
||||||
int r;
|
int r;
|
||||||
@ -194,7 +194,7 @@ int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flags, sd_device **re
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flags, sd_device **ret) {
|
int block_device_new_from_path(const char *path, BlockDeviceLookupFlags flags, sd_device **ret) {
|
||||||
_cleanup_close_ int fd = -EBADF;
|
_cleanup_close_ int fd = -EBADF;
|
||||||
|
|
||||||
assert(path);
|
assert(path);
|
||||||
@ -340,20 +340,24 @@ int get_block_device_harder(const char *path, dev_t *ret) {
|
|||||||
return get_block_device_harder_fd(fd, ret);
|
return get_block_device_harder_fd(fd, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
int lock_whole_block_device(dev_t devt, int operation) {
|
int lock_whole_block_device(dev_t devt, int open_flags, int operation) {
|
||||||
_cleanup_close_ int lock_fd = -EBADF;
|
_cleanup_close_ int lock_fd = -EBADF;
|
||||||
dev_t whole_devt;
|
dev_t whole_devt;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
/* Let's get a BSD file lock on the whole block device, as per: https://systemd.io/BLOCK_DEVICE_LOCKING */
|
/* Let's get a BSD file lock on the whole block device, as per: https://systemd.io/BLOCK_DEVICE_LOCKING
|
||||||
|
*
|
||||||
|
* NB: it matters whether open_flags indicates open for write: only then will the eventual closing of
|
||||||
|
* the fd trigger udev's partitioning rescanning of the device (as it watches for IN_CLOSE_WRITE),
|
||||||
|
* hence make sure to pass the right value there. */
|
||||||
|
|
||||||
r = block_get_whole_disk(devt, &whole_devt);
|
r = block_get_whole_disk(devt, &whole_devt);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
lock_fd = r = device_open_from_devnum(S_IFBLK, whole_devt, O_RDONLY|O_CLOEXEC|O_NONBLOCK, NULL);
|
lock_fd = device_open_from_devnum(S_IFBLK, whole_devt, open_flags|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, NULL);
|
||||||
if (r < 0)
|
if (lock_fd < 0)
|
||||||
return r;
|
return lock_fd;
|
||||||
|
|
||||||
if (flock(lock_fd, operation) < 0)
|
if (flock(lock_fd, operation) < 0)
|
||||||
return -errno;
|
return -errno;
|
||||||
@ -414,6 +418,12 @@ int blockdev_partscan_enabled(sd_device *dev) {
|
|||||||
|
|
||||||
assert(dev);
|
assert(dev);
|
||||||
|
|
||||||
|
r = device_in_subsystem(dev, "block");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
return -ENOTBLK;
|
||||||
|
|
||||||
/* For v6.10 or newer. */
|
/* For v6.10 or newer. */
|
||||||
r = device_get_sysattr_bool(dev, "partscan");
|
r = device_get_sysattr_bool(dev, "partscan");
|
||||||
if (r != -ENOENT)
|
if (r != -ENOENT)
|
||||||
@ -798,27 +808,6 @@ int block_device_remove_all_partitions(sd_device *dev, int fd) {
|
|||||||
return k < 0 ? k : has_partitions;
|
return k < 0 ? k : has_partitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int blockdev_reread_partition_table(sd_device *dev) {
|
|
||||||
_cleanup_close_ int fd = -EBADF;
|
|
||||||
|
|
||||||
assert(dev);
|
|
||||||
|
|
||||||
/* Try to re-read the partition table. This only succeeds if none of the devices is busy. */
|
|
||||||
|
|
||||||
fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
|
||||||
if (fd < 0)
|
|
||||||
return fd;
|
|
||||||
|
|
||||||
if (flock(fd, LOCK_EX|LOCK_NB) < 0)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
if (ioctl(fd, BLKRRPART, 0) < 0)
|
|
||||||
return -errno;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int blockdev_get_sector_size(int fd, uint32_t *ret) {
|
int blockdev_get_sector_size(int fd, uint32_t *ret) {
|
||||||
int ssz = 0;
|
int ssz = 0;
|
||||||
|
|
||||||
@ -898,3 +887,46 @@ int blockdev_get_root(int level, dev_t *ret) {
|
|||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int partition_node_of(const char *node, unsigned nr, char **ret) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(node);
|
||||||
|
assert(nr > 0);
|
||||||
|
assert(ret);
|
||||||
|
|
||||||
|
/* Given a device node path to a block device returns the device node path to the partition block
|
||||||
|
* device of the specified partition */
|
||||||
|
|
||||||
|
_cleanup_free_ char *fn = NULL;
|
||||||
|
r = path_extract_filename(node, &fn);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == O_DIRECTORY)
|
||||||
|
return -EISDIR;
|
||||||
|
|
||||||
|
_cleanup_free_ char *dn = NULL;
|
||||||
|
r = path_extract_directory(node, &dn);
|
||||||
|
if (r < 0 && r != -EDESTADDRREQ) /* allow if only filename is specified */
|
||||||
|
return r;
|
||||||
|
|
||||||
|
size_t l = strlen(fn);
|
||||||
|
assert(l > 0); /* underflow check for the subtraction below */
|
||||||
|
|
||||||
|
bool need_p = ascii_isdigit(fn[l-1]); /* Last char a digit? */
|
||||||
|
|
||||||
|
_cleanup_free_ char *subnode = NULL;
|
||||||
|
if (asprintf(&subnode, "%s%s%u", fn, need_p ? "p" : "", nr) < 0)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (dn) {
|
||||||
|
_cleanup_free_ char *j = path_join(dn, subnode);
|
||||||
|
if (!j)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
*ret = TAKE_PTR(j);
|
||||||
|
} else
|
||||||
|
*ret = TAKE_PTR(subnode);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -11,16 +11,16 @@
|
|||||||
#define xsprintf_sys_block_path(buf, suffix, devno) \
|
#define xsprintf_sys_block_path(buf, suffix, devno) \
|
||||||
xsprintf(buf, "/sys/dev/block/%u:%u%s", major(devno), minor(devno), suffix ?: "")
|
xsprintf(buf, "/sys/dev/block/%u:%u%s", major(devno), minor(devno), suffix ?: "")
|
||||||
|
|
||||||
typedef enum BlockDeviceLookupFlag {
|
typedef enum BlockDeviceLookupFlags {
|
||||||
BLOCK_DEVICE_LOOKUP_WHOLE_DISK = 1 << 0, /* whole block device, e.g. sda, nvme0n1, or loop0. */
|
BLOCK_DEVICE_LOOKUP_WHOLE_DISK = 1 << 0, /* whole block device, e.g. sda, nvme0n1, or loop0. */
|
||||||
BLOCK_DEVICE_LOOKUP_BACKING = 1 << 1, /* fd may be regular file or directory on file system, in
|
BLOCK_DEVICE_LOOKUP_BACKING = 1 << 1, /* fd may be regular file or directory on file system, in
|
||||||
* which case backing block device is determined. */
|
* which case backing block device is determined. */
|
||||||
BLOCK_DEVICE_LOOKUP_ORIGINATING = 1 << 2, /* Try to find the underlying layer device for stacked
|
BLOCK_DEVICE_LOOKUP_ORIGINATING = 1 << 2, /* Try to find the underlying layer device for stacked
|
||||||
* block device, e.g. LUKS-style DM. */
|
* block device, e.g. LUKS-style DM. */
|
||||||
} BlockDeviceLookupFlag;
|
} BlockDeviceLookupFlags;
|
||||||
|
|
||||||
int block_device_new_from_fd(int fd, BlockDeviceLookupFlag flag, sd_device **ret);
|
int block_device_new_from_fd(int fd, BlockDeviceLookupFlags flags, sd_device **ret);
|
||||||
int block_device_new_from_path(const char *path, BlockDeviceLookupFlag flag, sd_device **ret);
|
int block_device_new_from_path(const char *path, BlockDeviceLookupFlags flags, sd_device **ret);
|
||||||
|
|
||||||
int block_device_is_whole_disk(sd_device *dev);
|
int block_device_is_whole_disk(sd_device *dev);
|
||||||
int block_device_get_whole_disk(sd_device *dev, sd_device **ret);
|
int block_device_get_whole_disk(sd_device *dev, sd_device **ret);
|
||||||
@ -35,7 +35,7 @@ int get_block_device(const char *path, dev_t *dev);
|
|||||||
int get_block_device_harder_fd(int fd, dev_t *dev);
|
int get_block_device_harder_fd(int fd, dev_t *dev);
|
||||||
int get_block_device_harder(const char *path, dev_t *dev);
|
int get_block_device_harder(const char *path, dev_t *dev);
|
||||||
|
|
||||||
int lock_whole_block_device(dev_t devt, int operation);
|
int lock_whole_block_device(dev_t devt, int open_flags, int operation);
|
||||||
|
|
||||||
int blockdev_partscan_enabled(sd_device *d);
|
int blockdev_partscan_enabled(sd_device *d);
|
||||||
int blockdev_partscan_enabled_fd(int fd);
|
int blockdev_partscan_enabled_fd(int fd);
|
||||||
@ -51,9 +51,10 @@ int block_device_remove_partition(int fd, const char *name, int nr);
|
|||||||
int block_device_resize_partition(int fd, int nr, uint64_t start, uint64_t size);
|
int block_device_resize_partition(int fd, int nr, uint64_t start, uint64_t size);
|
||||||
int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret);
|
int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret);
|
||||||
int block_device_remove_all_partitions(sd_device *dev, int fd);
|
int block_device_remove_all_partitions(sd_device *dev, int fd);
|
||||||
int blockdev_reread_partition_table(sd_device *dev);
|
|
||||||
|
|
||||||
int blockdev_get_sector_size(int fd, uint32_t *ret);
|
int blockdev_get_sector_size(int fd, uint32_t *ret);
|
||||||
int blockdev_get_device_size(int fd, uint64_t *ret);
|
int blockdev_get_device_size(int fd, uint64_t *ret);
|
||||||
|
|
||||||
int blockdev_get_root(int level, dev_t *ret);
|
int blockdev_get_root(int level, dev_t *ret);
|
||||||
|
|
||||||
|
int partition_node_of(const char *node, unsigned nr, char **ret);
|
||||||
|
|||||||
@ -70,12 +70,12 @@ int bus_property_get_id128(
|
|||||||
void *userdata,
|
void *userdata,
|
||||||
sd_bus_error *reterr_error) {
|
sd_bus_error *reterr_error) {
|
||||||
|
|
||||||
sd_id128_t *id = userdata;
|
sd_id128_t *id = ASSERT_PTR(userdata);
|
||||||
|
|
||||||
if (sd_id128_is_null(*id)) /* Add an empty array if the ID is zero */
|
if (sd_id128_is_null(*id)) /* Add an empty array if the ID is zero */
|
||||||
return sd_bus_message_append(reply, "ay", 0);
|
return sd_bus_message_append(reply, "ay", 0);
|
||||||
else
|
|
||||||
return sd_bus_message_append_array(reply, 'y', id->bytes, 16);
|
return sd_bus_message_append_array(reply, 'y', id->bytes, sizeof(sd_id128_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __SIZEOF_SIZE_T__ != 8
|
#if __SIZEOF_SIZE_T__ != 8
|
||||||
|
|||||||
@ -627,14 +627,9 @@ static int make_partition_devname(
|
|||||||
if (!s)
|
if (!s)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
} else {
|
} else {
|
||||||
size_t l = strlen(whole_devname);
|
r = partition_node_of(whole_devname, nr, &s);
|
||||||
if (l < 1) /* underflow check for the subtraction below */
|
if (r < 0)
|
||||||
return -EINVAL;
|
return r;
|
||||||
|
|
||||||
bool need_p = ascii_isdigit(whole_devname[l-1]); /* Last char a digit? */
|
|
||||||
|
|
||||||
if (asprintf(&s, "%s%s%i", whole_devname, need_p ? "p" : "", nr) < 0)
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (nr < 0) /* whole disk? */
|
if (nr < 0) /* whole disk? */
|
||||||
|
|||||||
@ -167,6 +167,7 @@ shared_sources = files(
|
|||||||
'quota-util.c',
|
'quota-util.c',
|
||||||
'reboot-util.c',
|
'reboot-util.c',
|
||||||
'recovery-key.c',
|
'recovery-key.c',
|
||||||
|
'reread-partition-table.c',
|
||||||
'resize-fs.c',
|
'resize-fs.c',
|
||||||
'resolve-util.c',
|
'resolve-util.c',
|
||||||
'rm-rf.c',
|
'rm-rf.c',
|
||||||
|
|||||||
396
src/shared/reread-partition-table.c
Normal file
396
src/shared/reread-partition-table.c
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#include "sd-device.h"
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "blkid-util.h"
|
||||||
|
#include "blockdev-util.h"
|
||||||
|
#include "device-private.h"
|
||||||
|
#include "device-util.h"
|
||||||
|
#include "errno-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "parse-util.h"
|
||||||
|
#include "reread-partition-table.h"
|
||||||
|
#include "set.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
|
||||||
|
static int trigger_partitions(sd_device *dev, bool blkrrpart_success) {
|
||||||
|
int ret = 0, r;
|
||||||
|
|
||||||
|
assert(dev);
|
||||||
|
|
||||||
|
/* search for partitions */
|
||||||
|
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||||
|
r = partition_enumerator_new(dev, &e);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator: %m");
|
||||||
|
|
||||||
|
/* We have partitions and re-read the table, the kernel already sent out a "change"
|
||||||
|
* event for the disk, and "remove/add" for all partitions. */
|
||||||
|
if (blkrrpart_success && sd_device_enumerator_get_device_first(e))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* We have partitions but re-reading the partition table did not work, synthesize
|
||||||
|
* "change" for the disk and all partitions. */
|
||||||
|
r = sd_device_trigger(dev, SD_DEVICE_CHANGE);
|
||||||
|
if (r < 0)
|
||||||
|
RET_GATHER(ret, log_device_debug_errno(dev, r, "Failed to trigger 'change' uevent, proceeding: %m"));
|
||||||
|
|
||||||
|
FOREACH_DEVICE(e, d) {
|
||||||
|
r = sd_device_trigger(d, SD_DEVICE_CHANGE);
|
||||||
|
if (r < 0)
|
||||||
|
RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to trigger 'change' uevent, proceeding: %m"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fallback_ioctl(sd_device *d, int fd, RereadPartitionTableFlags flags) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(d);
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
r = RET_NERRNO(ioctl(fd, BLKRRPART, 0));
|
||||||
|
if (r < 0)
|
||||||
|
log_device_debug_errno(d, r, "Failed to reread partition table via BLKRRPART: %m");
|
||||||
|
else
|
||||||
|
log_device_debug(d, "Successfully reread partition table via BLKRRPART.");
|
||||||
|
|
||||||
|
if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT))
|
||||||
|
RET_GATHER(r, trigger_partitions(d, r >= 0));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HAVE_BLKID
|
||||||
|
static int process_partition(
|
||||||
|
sd_device *d,
|
||||||
|
int fd,
|
||||||
|
blkid_partition pp,
|
||||||
|
sd_device_enumerator *e,
|
||||||
|
Set **partnos,
|
||||||
|
RereadPartitionTableFlags flags,
|
||||||
|
bool *changed) {
|
||||||
|
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(d);
|
||||||
|
assert(fd >= 0);
|
||||||
|
assert(pp);
|
||||||
|
assert(e);
|
||||||
|
assert(partnos);
|
||||||
|
assert(changed);
|
||||||
|
|
||||||
|
const char *node;
|
||||||
|
r = sd_device_get_devname(d, &node);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(d, r, "Failed to acquire device node path: %m");
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
int nr = sym_blkid_partition_get_partno(pp);
|
||||||
|
if (nr < 0)
|
||||||
|
return log_debug_errno(errno_or_else(EIO), "Failed to read partition number of partition: %m");
|
||||||
|
|
||||||
|
log_device_debug(d, "Processing partition %i...", nr);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
blkid_loff_t start = sym_blkid_partition_get_start(pp);
|
||||||
|
if (start < 0)
|
||||||
|
return log_debug_errno(errno_or_else(EIO), "Failed to read partition start offset of partition %i: %m", nr);
|
||||||
|
assert((uint64_t) start < UINT64_MAX / 512U);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
blkid_loff_t size = sym_blkid_partition_get_size(pp);
|
||||||
|
if (size < 0)
|
||||||
|
return log_debug_errno(errno_or_else(EIO), "Failed to read partition size of partition %i: %m", nr);
|
||||||
|
assert((uint64_t) size < UINT64_MAX / 512U);
|
||||||
|
|
||||||
|
if (set_ensure_put(partnos, /* hash_ops= */ NULL, UINT_TO_PTR(nr)) < 0)
|
||||||
|
return log_oom_debug();
|
||||||
|
|
||||||
|
_cleanup_free_ char *subnode = NULL;
|
||||||
|
r = partition_node_of(node, nr, &subnode);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(d, r, "Failed to determine partition node %i for '%s': %m", nr, node);
|
||||||
|
|
||||||
|
_cleanup_(sd_device_unrefp) sd_device *partition = NULL;
|
||||||
|
r = sd_device_new_from_devname(&partition, subnode);
|
||||||
|
if (r < 0) {
|
||||||
|
if (r != -ENODEV)
|
||||||
|
return log_device_debug_errno(d, r, "Failed to acquire device '%s': %m", subnode);
|
||||||
|
} else {
|
||||||
|
uint64_t start_kernel;
|
||||||
|
r = device_get_sysattr_u64(partition, "start", &start_kernel);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(partition, r, "Failed to get start of kernel partition device '%s': %m", subnode);
|
||||||
|
|
||||||
|
uint64_t size_kernel;
|
||||||
|
r = device_get_sysattr_u64(partition, "size", &size_kernel);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(partition, r, "Failed to get size of kernel partition device '%s': %m", subnode);
|
||||||
|
|
||||||
|
if (start_kernel == (uint64_t) start && size_kernel == (uint64_t) size) {
|
||||||
|
log_device_debug(partition, "Kernel partition device '%s' already matches partition table, not modifying.", subnode);
|
||||||
|
|
||||||
|
if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) {
|
||||||
|
if (!*changed) {
|
||||||
|
/* Make sure to synthesize a change event on the main device, before we issue the first one on a partition device */
|
||||||
|
r = sd_device_trigger(d, SD_DEVICE_CHANGE);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(d, r, "Failed to issue 'change' uevent on device '%s': %m", node);
|
||||||
|
|
||||||
|
log_device_debug(d, "Successfully issued 'change' uevent on device '%s'.", node);
|
||||||
|
*changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = sd_device_trigger(partition, SD_DEVICE_CHANGE);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(partition, r, "Failed to issue 'change' uevent on partition '%s': %m", subnode);
|
||||||
|
|
||||||
|
log_device_debug(partition, "Successfully issued 'change' uevent on partition '%s'.", subnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_kernel == (uint64_t) start) {
|
||||||
|
/* If the start offsize doesn't change we can just resize the partition */
|
||||||
|
log_device_debug(partition, "Resizing partition %i...", nr);
|
||||||
|
|
||||||
|
r = block_device_resize_partition(fd, nr, (uint64_t) start * 512U, (uint64_t) size * 512U);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(partition, r, "Failed to resize kernel partition device '%s' to partition table values: %m", subnode);
|
||||||
|
|
||||||
|
log_device_debug(partition, "Successfully resized kernel partition device '%s' to match partition table.", subnode);
|
||||||
|
*changed = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the start offset changed we need to remove and recreate the partition */
|
||||||
|
log_device_debug(partition, "Removing and recreating partition %i...", nr);
|
||||||
|
|
||||||
|
/* NB: when logging below we use the parent device now, after all the partition device ceased
|
||||||
|
* existing by now, most likely. Let's explicitly get rid of the obsolete device object now,
|
||||||
|
* just to make a point. */
|
||||||
|
partition = sd_device_unref(partition);
|
||||||
|
|
||||||
|
r = block_device_remove_partition(fd, subnode, (int) nr);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' in order to recreate it: %m", subnode);
|
||||||
|
|
||||||
|
/* And now add it the partition anew*/
|
||||||
|
log_device_debug(d, "Successfully removed kernel partition device '%s' in order to recreate it.", subnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_device_debug(d, "Adding partition %i...", nr);
|
||||||
|
|
||||||
|
r = block_device_add_partition(fd, subnode, nr, (uint64_t) start * 512U, (uint64_t) size * 512U);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(d, r, "Failed to add kernel partition device %i to partition table values: %m", nr);
|
||||||
|
|
||||||
|
log_device_debug(d, "Successfully added kernel partition device %i to match partition table.", nr);
|
||||||
|
*changed = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int remove_partitions(sd_device *d, int fd, sd_device_enumerator *e, Set *partnos, bool *changed) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(d);
|
||||||
|
assert(fd >= 0);
|
||||||
|
assert(e);
|
||||||
|
assert(changed);
|
||||||
|
|
||||||
|
/* Removes all partitions of the specified device that we didn't find in the partition table (as
|
||||||
|
* listed in the specified Set object) */
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
FOREACH_DEVICE(e, partition) {
|
||||||
|
const char *devname;
|
||||||
|
|
||||||
|
r = sd_device_get_devname(partition, &devname);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(partition, r, "Failed to get name of partition: %m");
|
||||||
|
|
||||||
|
unsigned nr;
|
||||||
|
r = device_get_property_uint(partition, "PARTN", &nr);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(partition, r, "Failed to read partition number property: %m");
|
||||||
|
if (set_contains(partnos, UINT_TO_PTR(nr))) {
|
||||||
|
log_device_debug(partition, "Found kernel partition device %u in partition table, leaving around.", nr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_device_debug(partition, "Kernel knows partition %u which we didn't find, removing.", nr);
|
||||||
|
|
||||||
|
r = block_device_remove_partition(fd, devname, (int) nr);
|
||||||
|
if (r < 0) /* NB: when logging we use the parent device below, after all the partition device ceased existing by now, most likely */
|
||||||
|
RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' that vanished from partition table: %m", devname));
|
||||||
|
else {
|
||||||
|
log_device_debug(d, "Removed partition %u from kernel.", nr);
|
||||||
|
*changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int reread_partition_table_full(sd_device *dev, int fd, RereadPartitionTableFlags flags) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(dev);
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
const char *p;
|
||||||
|
r = sd_device_get_devname(dev, &p);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to get block device name: %m");
|
||||||
|
|
||||||
|
_cleanup_close_ int lock_fd = -EBADF;
|
||||||
|
if (FLAGS_SET(flags, REREADPT_BSD_LOCK)) {
|
||||||
|
lock_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||||
|
if (lock_fd < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed top open lock fd for block device '%s': %m", p);
|
||||||
|
|
||||||
|
if (flock(lock_fd, LOCK_SH|LOCK_NB) < 0)
|
||||||
|
return log_device_debug_errno(dev, errno, "Failed to take BSD lock on block device '%s': %m", p);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = blockdev_partscan_enabled(dev);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to test if block device '%s' knows partition scanning: %m", p);
|
||||||
|
if (r == 0) {
|
||||||
|
/* No partition scanning? Generate a uevent at least, if that's requested */
|
||||||
|
if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) {
|
||||||
|
r = sd_device_trigger(dev, SD_DEVICE_CHANGE);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to trigger 'change' uevent, proceeding: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOTTY), "Block device '%s' does not support partition scanning.", p);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HAVE_BLKID
|
||||||
|
r = dlopen_libblkid();
|
||||||
|
if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
|
||||||
|
log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p);
|
||||||
|
return fallback_ioctl(dev, fd, flags);
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to load libblkid: %m");
|
||||||
|
|
||||||
|
_cleanup_(blkid_free_probep) blkid_probe b = sym_blkid_new_probe();
|
||||||
|
if (!b)
|
||||||
|
return log_oom_debug();
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
r = sym_blkid_probe_set_device(b, fd, /* off= */ 0, /* size= */ 0);
|
||||||
|
if (r != 0)
|
||||||
|
return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to open block device '%s': %m", p);
|
||||||
|
|
||||||
|
(void) sym_blkid_probe_enable_partitions(b, 1);
|
||||||
|
(void) sym_blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
r = sym_blkid_do_safeprobe(b);
|
||||||
|
if (r == _BLKID_SAFEPROBE_ERROR)
|
||||||
|
return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p);
|
||||||
|
if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) {
|
||||||
|
log_device_debug(dev, "Didn't find partition table on block device '%s', falling back to BLKRRPART.", p);
|
||||||
|
return fallback_ioctl(dev, fd, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(r == _BLKID_SAFEPROBE_FOUND);
|
||||||
|
|
||||||
|
const char *pttype = NULL;
|
||||||
|
(void) sym_blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
|
||||||
|
if (!streq_ptr(pttype, "gpt")) {
|
||||||
|
log_device_debug(dev, "Didn't find a GPT partition table on '%s', falling back to BLKRRPART.", p);
|
||||||
|
return fallback_ioctl(dev, fd, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
blkid_partlist pl = sym_blkid_probe_get_partitions(b);
|
||||||
|
if (!pl)
|
||||||
|
return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to read partition table of '%s': %m", p);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
int n_partitions = sym_blkid_partlist_numof_partitions(pl);
|
||||||
|
if (n_partitions < 0)
|
||||||
|
return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to acquire number of entries in partition table of '%s': %m", p);
|
||||||
|
|
||||||
|
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
||||||
|
r = partition_enumerator_new(dev, &e);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to enumerate kernel partition devices: %m");
|
||||||
|
|
||||||
|
log_device_debug(dev, "Updating/adding kernel partition devices...");
|
||||||
|
|
||||||
|
_cleanup_(set_freep) Set *found_partnos = NULL;
|
||||||
|
bool changed = false;
|
||||||
|
int ret = 0;
|
||||||
|
for (int i = 0; i < n_partitions; i++) {
|
||||||
|
errno = 0;
|
||||||
|
blkid_partition pp = sym_blkid_partlist_get_partition(pl, i);
|
||||||
|
if (!pp)
|
||||||
|
return log_device_debug_errno(dev, errno_or_else(EIO), "Unable to get partition data of partition %i of partition table of '%s': %m", i, p);
|
||||||
|
|
||||||
|
RET_GATHER(ret, process_partition(dev, fd, pp, e, &found_partnos, flags, &changed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only delete unrecognized partitions if everything else worked */
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
log_device_debug(dev, "Removing old kernel partition devices...");
|
||||||
|
|
||||||
|
r = remove_partitions(dev, fd, e, found_partnos, &changed);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (FLAGS_SET(flags, REREADPT_FORCE_UEVENT)) {
|
||||||
|
/* No change? Then trigger an event manually if we were told to */
|
||||||
|
r = sd_device_trigger(dev, SD_DEVICE_CHANGE);
|
||||||
|
if (r < 0)
|
||||||
|
return log_device_debug_errno(dev, r, "Failed to issue 'change' uevent on device '%s': %m", p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
log_device_debug(dev, "We don't have libblkid, falling back to BLKRRPART on '%s'.", p);
|
||||||
|
return fallback_ioctl(dev, fd, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int reread_partition_table(sd_device *dev, RereadPartitionTableFlags flags) {
|
||||||
|
assert(dev);
|
||||||
|
|
||||||
|
_cleanup_close_ int fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_debug_errno(fd, "Failed to open block device: %m");
|
||||||
|
|
||||||
|
return reread_partition_table_full(dev, fd, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
int reread_partition_table_fd(int fd, RereadPartitionTableFlags flags) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
_cleanup_(sd_device_unrefp) sd_device *dev = NULL;
|
||||||
|
r = block_device_new_from_fd(fd, /* flags= */ 0, &dev);
|
||||||
|
if (r < 0)
|
||||||
|
return log_debug_errno(r, "Failed to get block device object: %m");
|
||||||
|
|
||||||
|
return reread_partition_table_full(dev, fd, flags);
|
||||||
|
}
|
||||||
14
src/shared/reread-partition-table.h
Normal file
14
src/shared/reread-partition-table.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sd-forward.h"
|
||||||
|
|
||||||
|
#include "shared-forward.h"
|
||||||
|
|
||||||
|
typedef enum RereadPartitionTableFlags {
|
||||||
|
REREADPT_FORCE_UEVENT = 1 << 0, /* Force a "change" ueven out on partitions we didn't resize/remove/add */
|
||||||
|
REREADPT_BSD_LOCK = 1 << 1, /* Take a BSD lock on the device around the rescan operation */
|
||||||
|
} RereadPartitionTableFlags;
|
||||||
|
|
||||||
|
int reread_partition_table_fd(int fd, RereadPartitionTableFlags flags);
|
||||||
|
int reread_partition_table(sd_device *dev, RereadPartitionTableFlags flags);
|
||||||
@ -390,7 +390,7 @@ static int nvme_subsystem_add(const char *node, int consumed_fd, sd_device *devi
|
|||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
fd = RET_NERRNO(open(node, O_RDONLY|O_CLOEXEC|O_NONBLOCK));
|
fd = RET_NERRNO(open(node, O_RDWR|O_CLOEXEC|O_NONBLOCK));
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return log_error_errno(fd, "Failed to open '%s': %m", node);
|
return log_error_errno(fd, "Failed to open '%s': %m", node);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -432,6 +432,13 @@ executables += [
|
|||||||
'dependencies' : libm,
|
'dependencies' : libm,
|
||||||
'timeout' : 120,
|
'timeout' : 120,
|
||||||
},
|
},
|
||||||
|
test_template + {
|
||||||
|
'sources' : files('test-reread-partition-table.c'),
|
||||||
|
},
|
||||||
|
test_template + {
|
||||||
|
'sources' : files('test-reread-partition-table-manual.c'),
|
||||||
|
'type' : 'manual',
|
||||||
|
},
|
||||||
test_template + {
|
test_template + {
|
||||||
'sources' : files('test-sbat.c'),
|
'sources' : files('test-sbat.c'),
|
||||||
'conditions' : ['ENABLE_BOOTLOADER'],
|
'conditions' : ['ENABLE_BOOTLOADER'],
|
||||||
|
|||||||
@ -77,4 +77,30 @@ TEST(partscan_enabled) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_partition_node_of_one(const char *main, unsigned partition, const char *result, int retval) {
|
||||||
|
_cleanup_free_ char *s = NULL;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = partition_node_of(main, partition, &s);
|
||||||
|
ASSERT_EQ(r, retval);
|
||||||
|
if (r < 0)
|
||||||
|
return;
|
||||||
|
ASSERT_STREQ(s, result);
|
||||||
|
|
||||||
|
log_info("%s with %u → %s", main, partition, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(partition_node_of) {
|
||||||
|
test_partition_node_of_one("/dev/sda", 2, "/dev/sda2", 0);
|
||||||
|
test_partition_node_of_one("sda", 3, "sda3", 0);
|
||||||
|
test_partition_node_of_one("/dev/nvme0n1", 7, "/dev/nvme0n1p7", 0);
|
||||||
|
test_partition_node_of_one("nvme0n1", 8, "nvme0n1p8", 0);
|
||||||
|
test_partition_node_of_one("/dev/loop1", 3, "/dev/loop1p3", 0);
|
||||||
|
test_partition_node_of_one("", 1, NULL, -EINVAL);
|
||||||
|
test_partition_node_of_one("/", 1, NULL, -EADDRNOTAVAIL);
|
||||||
|
test_partition_node_of_one("/dev/", 1, NULL, -EISDIR);
|
||||||
|
test_partition_node_of_one("/sda", 1, "/sda1", 0);
|
||||||
|
test_partition_node_of_one(".", 1, NULL, -EADDRNOTAVAIL);
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_TEST_MAIN(LOG_INFO);
|
DEFINE_TEST_MAIN(LOG_INFO);
|
||||||
|
|||||||
28
src/test/test-reread-partition-table-manual.c
Normal file
28
src/test/test-reread-partition-table-manual.c
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "main-func.h"
|
||||||
|
#include "reread-partition-table.h"
|
||||||
|
|
||||||
|
static int run(int argc, char *argv[]) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
log_set_max_level(LOG_DEBUG);
|
||||||
|
log_setup();
|
||||||
|
|
||||||
|
if (argc != 2)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected single parameter, the device node to open.");
|
||||||
|
|
||||||
|
_cleanup_close_ int fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
|
||||||
|
|
||||||
|
r = reread_partition_table_fd(fd, REREADPT_BSD_LOCK|REREADPT_FORCE_UEVENT);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to reread partition table: %m");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_MAIN_FUNCTION(run);
|
||||||
162
src/test/test-reread-partition-table.c
Normal file
162
src/test/test-reread-partition-table.c
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include <linux/loop.h>
|
||||||
|
|
||||||
|
#include "blockdev-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
|
#include "loop-util.h"
|
||||||
|
#include "memfd-util.h"
|
||||||
|
#include "path-util.h"
|
||||||
|
#include "process-util.h"
|
||||||
|
#include "reread-partition-table.h"
|
||||||
|
#include "tests.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
|
#include "virt.h"
|
||||||
|
|
||||||
|
static void sfdisk(const char *sfdisk_path, LoopDevice *loop, const char *definition) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(sfdisk_path);
|
||||||
|
assert(loop);
|
||||||
|
assert(definition);
|
||||||
|
|
||||||
|
_cleanup_close_ int memfd = memfd_new_and_seal("sfdisk", definition, SIZE_MAX);
|
||||||
|
ASSERT_OK(memfd);
|
||||||
|
|
||||||
|
r = safe_fork_full(
|
||||||
|
"(sfdisk)",
|
||||||
|
(int[]) { memfd, STDOUT_FILENO, STDERR_FILENO },
|
||||||
|
/* except_fds= */ NULL,
|
||||||
|
/* n_except_fds= */ 0,
|
||||||
|
FORK_CLOSE_ALL_FDS|FORK_RESET_SIGNALS|FORK_REARRANGE_STDIO|FORK_LOG|FORK_WAIT,
|
||||||
|
/* ret_pid= */ NULL);
|
||||||
|
if (r == 0) {
|
||||||
|
/* child */
|
||||||
|
execl(sfdisk_path, "fdisk", "--no-tell-kernel", "--no-reread", loop->node, NULL);
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_OK(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(rereadpt) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (detect_container() > 0)
|
||||||
|
return (void) log_tests_skipped("test not available in container");
|
||||||
|
if (running_in_chroot() > 0)
|
||||||
|
return (void) log_tests_skipped("test not available in chroot()");
|
||||||
|
|
||||||
|
_cleanup_free_ char *sfdisk_path = NULL;
|
||||||
|
r = find_executable("sfdisk", &sfdisk_path);
|
||||||
|
if (r == -ENOENT)
|
||||||
|
return (void) log_tests_skipped("sfdisk not found");
|
||||||
|
ASSERT_OK(r);
|
||||||
|
|
||||||
|
_cleanup_close_ int fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR);
|
||||||
|
ASSERT_FD(fd);
|
||||||
|
|
||||||
|
ASSERT_OK_ERRNO(ftruncate(fd, 100 * 1024 * 1024));
|
||||||
|
|
||||||
|
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
|
||||||
|
r = loop_device_make(
|
||||||
|
fd,
|
||||||
|
O_RDWR,
|
||||||
|
/* offset= */ 0,
|
||||||
|
/* size= */ UINT64_MAX,
|
||||||
|
/* sector_size= */ 512U,
|
||||||
|
LO_FLAGS_PARTSCAN,
|
||||||
|
LOCK_EX, &loop);
|
||||||
|
if (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NOT_SUPPORTED(r))
|
||||||
|
return (void) log_tests_skipped("loopback block devices not available");
|
||||||
|
|
||||||
|
_cleanup_free_ char *p = NULL;
|
||||||
|
ASSERT_OK(partition_node_of(loop->node, 1, &p));
|
||||||
|
ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT);
|
||||||
|
|
||||||
|
/* No change */
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT);
|
||||||
|
|
||||||
|
/* Create */
|
||||||
|
log_notice("CREATING 20M");
|
||||||
|
sfdisk(sfdisk_path,
|
||||||
|
loop,
|
||||||
|
"label: gpt\n"
|
||||||
|
"start=, size=20M, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\n");
|
||||||
|
|
||||||
|
ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT);
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
|
||||||
|
_cleanup_close_ int pfd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||||
|
ASSERT_OK_ERRNO(pfd);
|
||||||
|
uint64_t size;
|
||||||
|
ASSERT_OK(blockdev_get_device_size(pfd, &size));
|
||||||
|
ASSERT_EQ(size, 20U*1024U*1024U);
|
||||||
|
|
||||||
|
/* No change */
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
|
||||||
|
/* No change, but synthesize change anyway */
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ REREADPT_FORCE_UEVENT));
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
|
||||||
|
/* Resize */
|
||||||
|
log_notice("RESIZING TO 30M");
|
||||||
|
sfdisk(sfdisk_path,
|
||||||
|
loop,
|
||||||
|
"label: gpt\n"
|
||||||
|
"start=, size=30M, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\n");
|
||||||
|
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
|
||||||
|
ASSERT_OK(blockdev_get_device_size(pfd, &size));
|
||||||
|
ASSERT_EQ(size, 30U*1024U*1024U);
|
||||||
|
|
||||||
|
/* No change */
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
|
||||||
|
/* Move */
|
||||||
|
log_notice("MOVING BY 50M");
|
||||||
|
sfdisk(sfdisk_path,
|
||||||
|
loop,
|
||||||
|
"label: gpt\n"
|
||||||
|
"start=50M, size=15M, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\n");
|
||||||
|
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
ASSERT_ERROR(reread_partition_table_fd(loop->fd, /* flags= */ 0), EBUSY);
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
|
||||||
|
safe_close(pfd);
|
||||||
|
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
|
||||||
|
pfd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||||
|
ASSERT_OK_ERRNO(pfd);
|
||||||
|
ASSERT_OK(blockdev_get_device_size(pfd, &size));
|
||||||
|
ASSERT_EQ(size, 15U*1024U*1024U);
|
||||||
|
|
||||||
|
/* No change */
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
|
||||||
|
/* Remove */
|
||||||
|
log_notice("REMOVING");
|
||||||
|
sfdisk(sfdisk_path,
|
||||||
|
loop,
|
||||||
|
"label: gpt\n");
|
||||||
|
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
ASSERT_ERROR(reread_partition_table_fd(loop->fd, /* flags= */ 0), EBUSY);
|
||||||
|
|
||||||
|
ASSERT_OK_ZERO_ERRNO(access(p, F_OK));
|
||||||
|
pfd = safe_close(pfd);
|
||||||
|
ASSERT_OK(reread_partition_table_fd(loop->fd, /* flags= */ 0));
|
||||||
|
ASSERT_ERROR_ERRNO(access(p, F_OK), ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||||
@ -22,6 +22,7 @@
|
|||||||
#include "parse-util.h"
|
#include "parse-util.h"
|
||||||
#include "pidref.h"
|
#include "pidref.h"
|
||||||
#include "process-util.h"
|
#include "process-util.h"
|
||||||
|
#include "reread-partition-table.h"
|
||||||
#include "rm-rf.h"
|
#include "rm-rf.h"
|
||||||
#include "set.h"
|
#include "set.h"
|
||||||
#include "signal-util.h"
|
#include "signal-util.h"
|
||||||
@ -161,36 +162,6 @@ static int synthesize_change_one(sd_device *dev, sd_device *target) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int synthesize_change_all(sd_device *dev) {
|
|
||||||
int r;
|
|
||||||
|
|
||||||
assert(dev);
|
|
||||||
|
|
||||||
r = blockdev_reread_partition_table(dev);
|
|
||||||
if (r < 0)
|
|
||||||
log_device_debug_errno(dev, r, "Failed to re-read partition table, ignoring: %m");
|
|
||||||
bool part_table_read = r >= 0;
|
|
||||||
|
|
||||||
/* search for partitions */
|
|
||||||
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
|
|
||||||
r = partition_enumerator_new(dev, &e);
|
|
||||||
if (r < 0)
|
|
||||||
return log_device_debug_errno(dev, r, "Failed to initialize partition enumerator, ignoring: %m");
|
|
||||||
|
|
||||||
/* We have partitions and re-read the table, the kernel already sent out a "change"
|
|
||||||
* event for the disk, and "remove/add" for all partitions. */
|
|
||||||
if (part_table_read && sd_device_enumerator_get_device_first(e))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* We have partitions but re-reading the partition table did not work, synthesize
|
|
||||||
* "change" for the disk and all partitions. */
|
|
||||||
r = synthesize_change_one(dev, dev);
|
|
||||||
FOREACH_DEVICE(e, d)
|
|
||||||
RET_GATHER(r, synthesize_change_one(dev, d));
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int synthesize_change_child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
static int synthesize_change_child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||||
Manager *manager = ASSERT_PTR(userdata);
|
Manager *manager = ASSERT_PTR(userdata);
|
||||||
assert(s);
|
assert(s);
|
||||||
@ -226,7 +197,7 @@ static int synthesize_change(Manager *manager, sd_device *dev) {
|
|||||||
return r;
|
return r;
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
/* child */
|
/* child */
|
||||||
(void) synthesize_change_all(dev);
|
(void) reread_partition_table(dev, REREADPT_FORCE_UEVENT|REREADPT_BSD_LOCK);
|
||||||
_exit(EXIT_SUCCESS);
|
_exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -184,7 +184,8 @@ static int lock_device(
|
|||||||
struct stat st;
|
struct stat st;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
/* We open in O_WRONLY mode here, to trigger a rescan in udev once we are done */
|
||||||
|
fd = open(path, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
return log_error_errno(errno, "Failed to open '%s': %m", path);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user