2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <net/if.h>
|
2015-10-24 22:58:24 +02:00
|
|
|
#include <sys/types.h>
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
#include "sd-device.h"
|
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2015-04-01 13:50:31 +02:00
|
|
|
#include "device-internal.h"
|
|
|
|
#include "device-private.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "device-util.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "fd-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "fileio.h"
|
2015-10-26 21:16:26 +01:00
|
|
|
#include "fs-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "hashmap.h"
|
|
|
|
#include "macro.h"
|
|
|
|
#include "mkdir.h"
|
2019-03-14 13:14:33 +01:00
|
|
|
#include "nulstr-util.h"
|
2015-10-26 16:18:16 +01:00
|
|
|
#include "parse-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "path-util.h"
|
|
|
|
#include "set.h"
|
2015-10-26 22:31:05 +01:00
|
|
|
#include "string-table.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "string-util.h"
|
|
|
|
#include "strv.h"
|
|
|
|
#include "strxcpyx.h"
|
2018-11-30 21:05:27 +01:00
|
|
|
#include "tmpfile-util.h"
|
2015-10-27 00:42:07 +01:00
|
|
|
#include "user-util.h"
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
int device_add_property(sd_device *device, const char *key, const char *value) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(key);
|
|
|
|
|
|
|
|
r = device_add_property_aux(device, key, value, false);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (key[0] != '.') {
|
|
|
|
r = device_add_property_aux(device, key, value, true);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void device_set_devlink_priority(sd_device *device, int priority) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
device->devlink_priority = priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
void device_set_is_initialized(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
device->is_initialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) {
|
2018-12-14 11:33:17 +01:00
|
|
|
usec_t when;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
if (device_old && device_old->usec_initialized > 0)
|
2018-12-14 11:33:17 +01:00
|
|
|
when = device_old->usec_initialized;
|
2015-04-01 13:50:31 +02:00
|
|
|
else
|
2018-12-14 11:33:17 +01:00
|
|
|
when = now(CLOCK_MONOTONIC);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2018-12-14 11:33:17 +01:00
|
|
|
return device_set_usec_initialized(device, when);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t device_get_properties_generation(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
return device->properties_generation;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t device_get_tags_generation(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
return device->tags_generation;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t device_get_devlinks_generation(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
return device->devlinks_generation;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_devnode_mode(sd_device *device, mode_t *mode) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
r = device_read_db(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-09-01 11:05:27 +02:00
|
|
|
if (device->devmode == (mode_t) -1)
|
|
|
|
return -ENOENT;
|
|
|
|
|
2018-10-29 09:17:57 +01:00
|
|
|
if (mode)
|
|
|
|
*mode = device->devmode;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_devnode_uid(sd_device *device, uid_t *uid) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
r = device_read_db(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-09-01 11:05:27 +02:00
|
|
|
if (device->devuid == (uid_t) -1)
|
|
|
|
return -ENOENT;
|
|
|
|
|
2018-10-29 09:17:57 +01:00
|
|
|
if (uid)
|
|
|
|
*uid = device->devuid;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int device_set_devuid(sd_device *device, const char *uid) {
|
|
|
|
unsigned u;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(uid);
|
|
|
|
|
|
|
|
r = safe_atou(uid, &u);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = device_add_property_internal(device, "DEVUID", uid);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
device->devuid = u;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_devnode_gid(sd_device *device, gid_t *gid) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
r = device_read_db(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-09-01 11:05:27 +02:00
|
|
|
if (device->devgid == (gid_t) -1)
|
|
|
|
return -ENOENT;
|
|
|
|
|
2018-10-29 09:17:57 +01:00
|
|
|
if (gid)
|
|
|
|
*gid = device->devgid;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int device_set_devgid(sd_device *device, const char *gid) {
|
|
|
|
unsigned g;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(gid);
|
|
|
|
|
|
|
|
r = safe_atou(gid, &g);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = device_add_property_internal(device, "DEVGID", gid);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
device->devgid = g;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
int device_get_action(sd_device *device, DeviceAction *action) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
if (device->action < 0)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
if (action)
|
|
|
|
*action = device->action;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int device_set_action(sd_device *device, const char *action) {
|
|
|
|
DeviceAction a;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(action);
|
|
|
|
|
|
|
|
a = device_action_from_string(action);
|
|
|
|
if (a < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
r = device_add_property_internal(device, "ACTION", action);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
device->action = a;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_seqnum(sd_device *device, uint64_t *seqnum) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
if (device->seqnum == 0)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
if (seqnum)
|
|
|
|
*seqnum = device->seqnum;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int device_set_seqnum(sd_device *device, const char *str) {
|
|
|
|
uint64_t seqnum;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(str);
|
|
|
|
|
|
|
|
r = safe_atou64(str, &seqnum);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
if (seqnum == 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
r = device_add_property_internal(device, "SEQNUM", str);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
device->seqnum = seqnum;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-04-03 22:51:09 +02:00
|
|
|
static int device_amend(sd_device *device, const char *key, const char *value) {
|
2015-04-01 13:50:31 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(key);
|
|
|
|
assert(value);
|
|
|
|
|
|
|
|
if (streq(key, "DEVPATH")) {
|
|
|
|
char *path;
|
|
|
|
|
|
|
|
path = strjoina("/sys", value);
|
|
|
|
|
|
|
|
/* the caller must verify or trust this data (e.g., if it comes from the kernel) */
|
|
|
|
r = device_set_syspath(device, path, false);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set syspath to '%s': %m", path);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "SUBSYSTEM")) {
|
|
|
|
r = device_set_subsystem(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DEVTYPE")) {
|
|
|
|
r = device_set_devtype(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devtype to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DEVNAME")) {
|
|
|
|
r = device_set_devname(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devname to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "USEC_INITIALIZED")) {
|
2018-12-14 11:33:17 +01:00
|
|
|
usec_t t;
|
|
|
|
|
|
|
|
r = safe_atou64(value, &t);
|
|
|
|
if (r < 0)
|
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to parse timestamp '%s': %m", value);
|
|
|
|
|
|
|
|
r = device_set_usec_initialized(device, t);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set usec-initialized to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DRIVER")) {
|
|
|
|
r = device_set_driver(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set driver to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "IFINDEX")) {
|
|
|
|
r = device_set_ifindex(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set ifindex to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DEVMODE")) {
|
|
|
|
r = device_set_devmode(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devmode to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DEVUID")) {
|
|
|
|
r = device_set_devuid(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devuid to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DEVGID")) {
|
|
|
|
r = device_set_devgid(device, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devgid to '%s': %m", value);
|
2019-03-08 09:25:00 +01:00
|
|
|
} else if (streq(key, "ACTION")) {
|
|
|
|
r = device_set_action(device, value);
|
|
|
|
if (r < 0)
|
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set action to '%s': %m", value);
|
|
|
|
} else if (streq(key, "SEQNUM")) {
|
|
|
|
r = device_set_seqnum(device, value);
|
|
|
|
if (r < 0)
|
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set SEQNUM to '%s': %m", value);
|
2015-04-01 13:50:31 +02:00
|
|
|
} else if (streq(key, "DEVLINKS")) {
|
2020-07-29 12:09:08 +02:00
|
|
|
for (const char *p = value;;) {
|
|
|
|
_cleanup_free_ char *word = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2020-07-29 12:09:08 +02:00
|
|
|
r = extract_first_word(&p, &word, NULL, 0);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
if (r == 0)
|
|
|
|
break;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2020-07-29 12:09:08 +02:00
|
|
|
r = device_add_devlink(device, word);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
2020-07-29 12:09:08 +02:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to add devlink '%s': %m", word);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
udev: make tags "sticky"
This tries to address the "bind"/"unbind" uevent kernel API breakage, by
changing the semantics of device tags.
Previously, tags would be applied on uevents (and the database entries
they result in) only depending on the immediate context. This means that
if one uevent causes the tag to be set and the next to be unset, this
would immediately effect what apps would see and the database entries
would contain each time. This is problematic however, as tags are a
filtering concept, and if tags vanish then clients won't hence notice
when a device stops being relevant to them since not only the tags
disappear but immediately also the uevents for it are filtered including
the one necessary for the app to notice that the device lost its tag and
hence relevance.
With this change tags become "sticky". If a tag is applied is once
applied to a device it will stay in place forever, until the device is
removed. Tags can never be removed again. This means that an app
watching a specific set of devices by filtering for a tag is guaranteed
to not only see the events where the tag is set but also all follow-up
events where the tags might be removed again.
This change of behaviour is unfortunate, but is required due to the
kernel introducing new "bind" and "unbind" uevents that generally have
the effect that tags and properties disappear and apps hence don't
notice when a device looses relevance to it. "bind"/"unbind" events were
introduced in kernel 4.12, and are now used in more and more subsystems.
The introduction broke userspace widely, and this commit is an attempt
to provide a way for apps to deal with it.
While tags are now "sticky" a new automatic device property
CURRENT_TAGS is introduced (matching the existing TAGS property) that
always reflects the precise set of tags applied on the most recent
events. Thus, when subscribing to devices through tags, all devices that
ever had the tag put on them will be be seen, and by CURRENT_TAGS it may
be checked whether the device right at the moment matches the tag
requirements.
See: #7587 #7018 #8221
2018-12-13 17:55:14 +01:00
|
|
|
} else if (STR_IN_SET(key, "TAGS", "CURRENT_TAGS")) {
|
2020-07-29 12:09:08 +02:00
|
|
|
for (const char *p = value;;) {
|
|
|
|
_cleanup_free_ char *word = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2020-07-29 12:09:08 +02:00
|
|
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
if (r == 0)
|
|
|
|
break;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2020-07-29 12:09:08 +02:00
|
|
|
r = device_add_tag(device, word, streq(key, "CURRENT_TAGS"));
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
2020-07-29 12:09:08 +02:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to add tag '%s': %m", word);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r = device_add_property_internal(device, key, value);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to add property '%s=%s': %m", key, value);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
static int device_append(sd_device *device, char *key, const char **_major, const char **_minor) {
|
2015-04-01 13:50:31 +02:00
|
|
|
const char *major = NULL, *minor = NULL;
|
|
|
|
char *value;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(key);
|
|
|
|
assert(_major);
|
|
|
|
assert(_minor);
|
|
|
|
|
|
|
|
value = strchr(key, '=');
|
2020-07-16 18:04:45 +02:00
|
|
|
if (!value)
|
|
|
|
return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
|
|
|
|
"sd-device: Not a key-value pair: '%s'", key);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
*value = '\0';
|
|
|
|
|
|
|
|
value++;
|
|
|
|
|
|
|
|
if (streq(key, "MAJOR"))
|
|
|
|
major = value;
|
|
|
|
else if (streq(key, "MINOR"))
|
|
|
|
minor = value;
|
|
|
|
else {
|
2015-04-03 22:51:09 +02:00
|
|
|
r = device_amend(device, key, value);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (major != 0)
|
|
|
|
*_major = major;
|
|
|
|
|
|
|
|
if (minor != 0)
|
|
|
|
*_minor = minor;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void device_seal(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
device->sealed = true;
|
|
|
|
}
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
static int device_verify(sd_device *device) {
|
2015-04-01 13:50:31 +02:00
|
|
|
assert(device);
|
|
|
|
|
2020-07-16 18:04:45 +02:00
|
|
|
if (!device->devpath || !device->subsystem || device->action < 0 || device->seqnum == 0)
|
|
|
|
return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
|
|
|
|
"sd-device: Device created from strv or nulstr lacks devpath, subsystem, action or seqnum.");
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
device->sealed = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_new_from_strv(sd_device **ret, char **strv) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
char **key;
|
|
|
|
const char *major = NULL, *minor = NULL;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(ret);
|
|
|
|
assert(strv);
|
|
|
|
|
|
|
|
r = device_new_aux(&device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
STRV_FOREACH(key, strv) {
|
2019-03-08 09:25:00 +01:00
|
|
|
r = device_append(device, *key, &major, &minor);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (major) {
|
|
|
|
r = device_set_devnum(device, major, minor);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
r = device_verify(device);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
*ret = TAKE_PTR(device);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
const char *major = NULL, *minor = NULL;
|
|
|
|
unsigned i = 0;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(ret);
|
|
|
|
assert(nulstr);
|
|
|
|
assert(len);
|
|
|
|
|
|
|
|
r = device_new_aux(&device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
while (i < len) {
|
|
|
|
char *key;
|
|
|
|
const char *end;
|
|
|
|
|
|
|
|
key = (char*)&nulstr[i];
|
|
|
|
end = memchr(key, '\0', len - i);
|
2020-07-16 18:04:45 +02:00
|
|
|
if (!end)
|
|
|
|
return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL),
|
|
|
|
"sd-device: Failed to parse nulstr");
|
|
|
|
|
2015-04-01 13:50:31 +02:00
|
|
|
i += end - key + 1;
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
r = device_append(device, key, &major, &minor);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (major) {
|
|
|
|
r = device_set_devnum(device, major, minor);
|
|
|
|
if (r < 0)
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to set devnum %s:%s: %m", major, minor);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
r = device_verify(device);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
*ret = TAKE_PTR(device);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int device_update_properties_bufs(sd_device *device) {
|
|
|
|
const char *val, *prop;
|
2015-06-01 16:28:58 +02:00
|
|
|
_cleanup_free_ char **buf_strv = NULL;
|
|
|
|
_cleanup_free_ uint8_t *buf_nulstr = NULL;
|
2015-06-01 11:32:39 +02:00
|
|
|
size_t allocated_nulstr = 0;
|
2015-06-01 16:28:58 +02:00
|
|
|
size_t nulstr_len = 0, num = 0, i = 0;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
2015-04-03 21:05:42 +02:00
|
|
|
if (!device->properties_buf_outdated)
|
|
|
|
return 0;
|
|
|
|
|
2015-04-01 13:50:31 +02:00
|
|
|
FOREACH_DEVICE_PROPERTY(device, prop, val) {
|
|
|
|
size_t len = 0;
|
|
|
|
|
|
|
|
len = strlen(prop) + 1 + strlen(val);
|
|
|
|
|
|
|
|
buf_nulstr = GREEDY_REALLOC0(buf_nulstr, allocated_nulstr, nulstr_len + len + 2);
|
|
|
|
if (!buf_nulstr)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL);
|
|
|
|
nulstr_len += len + 1;
|
2015-06-01 11:32:39 +02:00
|
|
|
++num;
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
2015-06-01 16:28:58 +02:00
|
|
|
/* build buf_strv from buf_nulstr */
|
|
|
|
buf_strv = new0(char *, num + 1);
|
|
|
|
if (!buf_strv)
|
|
|
|
return -ENOMEM;
|
2015-06-01 11:32:39 +02:00
|
|
|
|
|
|
|
NULSTR_FOREACH(val, (char*) buf_nulstr) {
|
2015-06-01 16:28:58 +02:00
|
|
|
buf_strv[i] = (char *) val;
|
2015-06-01 11:32:39 +02:00
|
|
|
assert(i < num);
|
|
|
|
i++;
|
|
|
|
}
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2017-11-24 11:33:41 +01:00
|
|
|
free_and_replace(device->properties_nulstr, buf_nulstr);
|
2015-06-01 16:28:58 +02:00
|
|
|
device->properties_nulstr_len = nulstr_len;
|
2017-11-24 11:33:41 +01:00
|
|
|
free_and_replace(device->properties_strv, buf_strv);
|
2015-06-01 16:28:58 +02:00
|
|
|
|
2015-04-01 13:50:31 +02:00
|
|
|
device->properties_buf_outdated = false;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(nulstr);
|
|
|
|
assert(len);
|
|
|
|
|
2015-04-03 21:05:42 +02:00
|
|
|
r = device_update_properties_bufs(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
*nulstr = device->properties_nulstr;
|
|
|
|
*len = device->properties_nulstr_len;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_properties_strv(sd_device *device, char ***strv) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(strv);
|
|
|
|
|
|
|
|
r = device_update_properties_bufs(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
*strv = device->properties_strv;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_devlink_priority(sd_device *device, int *priority) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(priority);
|
|
|
|
|
|
|
|
r = device_read_db(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
*priority = device->devlink_priority;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_get_watch_handle(sd_device *device, int *handle) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
r = device_read_db(device);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-09-01 11:05:27 +02:00
|
|
|
if (device->watch_handle < 0)
|
|
|
|
return -ENOENT;
|
|
|
|
|
2018-10-29 09:17:57 +01:00
|
|
|
if (handle)
|
|
|
|
*handle = device->watch_handle;
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void device_set_watch_handle(sd_device *device, int handle) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
device->watch_handle = handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_rename(sd_device *device, const char *name) {
|
|
|
|
_cleanup_free_ char *dirname = NULL;
|
2019-06-24 17:24:23 +02:00
|
|
|
const char *new_syspath, *interface;
|
2015-04-01 13:50:31 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(name);
|
|
|
|
|
|
|
|
dirname = dirname_malloc(device->syspath);
|
|
|
|
if (!dirname)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2019-06-24 17:24:23 +02:00
|
|
|
new_syspath = prefix_roota(dirname, name);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
/* the user must trust that the new name is correct */
|
|
|
|
r = device_set_syspath(device, new_syspath, false);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = sd_device_get_property_value(device, "INTERFACE", &interface);
|
|
|
|
if (r >= 0) {
|
2017-06-18 11:31:30 +02:00
|
|
|
/* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */
|
|
|
|
r = device_add_property_internal(device, "INTERFACE_OLD", interface);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2017-06-18 11:31:30 +02:00
|
|
|
r = device_add_property_internal(device, "INTERFACE", name);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
} else if (r != -ENOENT)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_shallow_clone(sd_device *old_device, sd_device **new_device) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_device_unrefp) sd_device *ret = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(old_device);
|
|
|
|
assert(new_device);
|
|
|
|
|
|
|
|
r = device_new_aux(&ret);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = device_set_syspath(ret, old_device->syspath, false);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = device_set_subsystem(ret, old_device->subsystem);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
ret->devnum = old_device->devnum;
|
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
*new_device = TAKE_PTR(ret);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_clone_with_db(sd_device *old_device, sd_device **new_device) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_device_unrefp) sd_device *ret = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(old_device);
|
|
|
|
assert(new_device);
|
|
|
|
|
|
|
|
r = device_shallow_clone(old_device, &ret);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = device_read_db(ret);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
ret->sealed = true;
|
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
*new_device = TAKE_PTR(ret);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_device_unrefp) sd_device *ret = NULL;
|
2015-04-01 13:50:31 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(new_device);
|
|
|
|
assert(syspath);
|
|
|
|
assert(action);
|
|
|
|
|
|
|
|
r = sd_device_new_from_syspath(&ret, syspath);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = device_read_uevent_file(ret);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2019-03-08 09:25:00 +01:00
|
|
|
r = device_set_action(ret, action);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
*new_device = TAKE_PTR(ret);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-22 07:00:53 +02:00
|
|
|
int device_new_from_stat_rdev(sd_device **ret, const struct stat *st) {
|
|
|
|
char type;
|
|
|
|
|
|
|
|
assert(ret);
|
|
|
|
assert(st);
|
|
|
|
|
|
|
|
if (S_ISBLK(st->st_mode))
|
|
|
|
type = 'b';
|
|
|
|
else if (S_ISCHR(st->st_mode))
|
|
|
|
type = 'c';
|
|
|
|
else
|
|
|
|
return -ENOTTY;
|
|
|
|
|
|
|
|
return sd_device_new_from_devnum(ret, type, st->st_rdev);
|
|
|
|
}
|
|
|
|
|
2015-04-01 13:50:31 +02:00
|
|
|
int device_copy_properties(sd_device *device_dst, sd_device *device_src) {
|
|
|
|
const char *property, *value;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device_dst);
|
|
|
|
assert(device_src);
|
|
|
|
|
2019-01-22 03:45:40 +01:00
|
|
|
r = device_properties_prepare(device_src);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2020-09-08 11:58:29 +02:00
|
|
|
ORDERED_HASHMAP_FOREACH_KEY(value, property, device_src->properties_db) {
|
2019-01-22 03:45:40 +01:00
|
|
|
r = device_add_property_aux(device_dst, property, value, true);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2020-09-08 11:58:29 +02:00
|
|
|
ORDERED_HASHMAP_FOREACH_KEY(value, property, device_src->properties) {
|
2019-01-22 03:45:40 +01:00
|
|
|
r = device_add_property_aux(device_dst, property, value, false);
|
2015-04-01 13:50:31 +02:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void device_cleanup_tags(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
udev: make tags "sticky"
This tries to address the "bind"/"unbind" uevent kernel API breakage, by
changing the semantics of device tags.
Previously, tags would be applied on uevents (and the database entries
they result in) only depending on the immediate context. This means that
if one uevent causes the tag to be set and the next to be unset, this
would immediately effect what apps would see and the database entries
would contain each time. This is problematic however, as tags are a
filtering concept, and if tags vanish then clients won't hence notice
when a device stops being relevant to them since not only the tags
disappear but immediately also the uevents for it are filtered including
the one necessary for the app to notice that the device lost its tag and
hence relevance.
With this change tags become "sticky". If a tag is applied is once
applied to a device it will stay in place forever, until the device is
removed. Tags can never be removed again. This means that an app
watching a specific set of devices by filtering for a tag is guaranteed
to not only see the events where the tag is set but also all follow-up
events where the tags might be removed again.
This change of behaviour is unfortunate, but is required due to the
kernel introducing new "bind" and "unbind" uevents that generally have
the effect that tags and properties disappear and apps hence don't
notice when a device looses relevance to it. "bind"/"unbind" events were
introduced in kernel 4.12, and are now used in more and more subsystems.
The introduction broke userspace widely, and this commit is an attempt
to provide a way for apps to deal with it.
While tags are now "sticky" a new automatic device property
CURRENT_TAGS is introduced (matching the existing TAGS property) that
always reflects the precise set of tags applied on the most recent
events. Thus, when subscribing to devices through tags, all devices that
ever had the tag put on them will be be seen, and by CURRENT_TAGS it may
be checked whether the device right at the moment matches the tag
requirements.
See: #7587 #7018 #8221
2018-12-13 17:55:14 +01:00
|
|
|
device->all_tags = set_free_free(device->all_tags);
|
|
|
|
device->current_tags = set_free_free(device->current_tags);
|
2015-04-01 13:50:31 +02:00
|
|
|
device->property_tags_outdated = true;
|
2016-02-23 05:32:04 +01:00
|
|
|
device->tags_generation++;
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void device_cleanup_devlinks(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
set_free_free(device->devlinks);
|
|
|
|
device->devlinks = NULL;
|
|
|
|
device->property_devlinks_outdated = true;
|
2016-02-23 05:32:04 +01:00
|
|
|
device->devlinks_generation++;
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void device_remove_tag(sd_device *device, const char *tag) {
|
|
|
|
assert(device);
|
|
|
|
assert(tag);
|
|
|
|
|
udev: make tags "sticky"
This tries to address the "bind"/"unbind" uevent kernel API breakage, by
changing the semantics of device tags.
Previously, tags would be applied on uevents (and the database entries
they result in) only depending on the immediate context. This means that
if one uevent causes the tag to be set and the next to be unset, this
would immediately effect what apps would see and the database entries
would contain each time. This is problematic however, as tags are a
filtering concept, and if tags vanish then clients won't hence notice
when a device stops being relevant to them since not only the tags
disappear but immediately also the uevents for it are filtered including
the one necessary for the app to notice that the device lost its tag and
hence relevance.
With this change tags become "sticky". If a tag is applied is once
applied to a device it will stay in place forever, until the device is
removed. Tags can never be removed again. This means that an app
watching a specific set of devices by filtering for a tag is guaranteed
to not only see the events where the tag is set but also all follow-up
events where the tags might be removed again.
This change of behaviour is unfortunate, but is required due to the
kernel introducing new "bind" and "unbind" uevents that generally have
the effect that tags and properties disappear and apps hence don't
notice when a device looses relevance to it. "bind"/"unbind" events were
introduced in kernel 4.12, and are now used in more and more subsystems.
The introduction broke userspace widely, and this commit is an attempt
to provide a way for apps to deal with it.
While tags are now "sticky" a new automatic device property
CURRENT_TAGS is introduced (matching the existing TAGS property) that
always reflects the precise set of tags applied on the most recent
events. Thus, when subscribing to devices through tags, all devices that
ever had the tag put on them will be be seen, and by CURRENT_TAGS it may
be checked whether the device right at the moment matches the tag
requirements.
See: #7587 #7018 #8221
2018-12-13 17:55:14 +01:00
|
|
|
free(set_remove(device->current_tags, tag));
|
2015-04-01 13:50:31 +02:00
|
|
|
device->property_tags_outdated = true;
|
2016-02-23 05:32:04 +01:00
|
|
|
device->tags_generation++;
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int device_tag(sd_device *device, const char *tag, bool add) {
|
|
|
|
const char *id;
|
|
|
|
char *path;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
assert(tag);
|
|
|
|
|
|
|
|
r = device_get_id_filename(device, &id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
path = strjoina("/run/udev/tags/", tag, "/", id);
|
|
|
|
|
|
|
|
if (add) {
|
|
|
|
r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
} else {
|
|
|
|
r = unlink(path);
|
|
|
|
if (r < 0 && errno != ENOENT)
|
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_tag_index(sd_device *device, sd_device *device_old, bool add) {
|
|
|
|
const char *tag;
|
|
|
|
int r = 0, k;
|
|
|
|
|
|
|
|
if (add && device_old) {
|
|
|
|
/* delete possible left-over tags */
|
|
|
|
FOREACH_DEVICE_TAG(device_old, tag) {
|
|
|
|
if (!sd_device_has_tag(device, tag)) {
|
|
|
|
k = device_tag(device_old, tag, false);
|
|
|
|
if (r >= 0 && k < 0)
|
|
|
|
r = k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FOREACH_DEVICE_TAG(device, tag) {
|
|
|
|
k = device_tag(device, tag, add);
|
|
|
|
if (r >= 0 && k < 0)
|
|
|
|
r = k;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool device_has_info(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
if (!set_isempty(device->devlinks))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (device->devlink_priority != 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!ordered_hashmap_isempty(device->properties_db))
|
|
|
|
return true;
|
|
|
|
|
udev: make tags "sticky"
This tries to address the "bind"/"unbind" uevent kernel API breakage, by
changing the semantics of device tags.
Previously, tags would be applied on uevents (and the database entries
they result in) only depending on the immediate context. This means that
if one uevent causes the tag to be set and the next to be unset, this
would immediately effect what apps would see and the database entries
would contain each time. This is problematic however, as tags are a
filtering concept, and if tags vanish then clients won't hence notice
when a device stops being relevant to them since not only the tags
disappear but immediately also the uevents for it are filtered including
the one necessary for the app to notice that the device lost its tag and
hence relevance.
With this change tags become "sticky". If a tag is applied is once
applied to a device it will stay in place forever, until the device is
removed. Tags can never be removed again. This means that an app
watching a specific set of devices by filtering for a tag is guaranteed
to not only see the events where the tag is set but also all follow-up
events where the tags might be removed again.
This change of behaviour is unfortunate, but is required due to the
kernel introducing new "bind" and "unbind" uevents that generally have
the effect that tags and properties disappear and apps hence don't
notice when a device looses relevance to it. "bind"/"unbind" events were
introduced in kernel 4.12, and are now used in more and more subsystems.
The introduction broke userspace widely, and this commit is an attempt
to provide a way for apps to deal with it.
While tags are now "sticky" a new automatic device property
CURRENT_TAGS is introduced (matching the existing TAGS property) that
always reflects the precise set of tags applied on the most recent
events. Thus, when subscribing to devices through tags, all devices that
ever had the tag put on them will be be seen, and by CURRENT_TAGS it may
be checked whether the device right at the moment matches the tag
requirements.
See: #7587 #7018 #8221
2018-12-13 17:55:14 +01:00
|
|
|
if (!set_isempty(device->all_tags))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!set_isempty(device->current_tags))
|
2015-04-01 13:50:31 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
if (device->watch_handle >= 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void device_set_db_persist(sd_device *device) {
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
device->db_persist = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_update_db(sd_device *device) {
|
|
|
|
const char *id;
|
|
|
|
char *path;
|
|
|
|
_cleanup_fclose_ FILE *f = NULL;
|
|
|
|
_cleanup_free_ char *path_tmp = NULL;
|
|
|
|
bool has_info;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
has_info = device_has_info(device);
|
|
|
|
|
|
|
|
r = device_get_id_filename(device, &id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
path = strjoina("/run/udev/data/", id);
|
|
|
|
|
|
|
|
/* do not store anything for otherwise empty devices */
|
|
|
|
if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) {
|
|
|
|
r = unlink(path);
|
|
|
|
if (r < 0 && errno != ENOENT)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write a database file */
|
|
|
|
r = mkdir_parents(path, 0755);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = fopen_temporary(path, &f, &path_tmp);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set 'sticky' bit to indicate that we should not clean the
|
|
|
|
* database when we transition from initramfs to the real root
|
|
|
|
*/
|
|
|
|
if (device->db_persist) {
|
|
|
|
r = fchmod(fileno(f), 01644);
|
|
|
|
if (r < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r = fchmod(fileno(f), 0644);
|
|
|
|
if (r < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_info) {
|
|
|
|
const char *property, *value, *tag;
|
|
|
|
|
|
|
|
if (major(device->devnum) > 0) {
|
|
|
|
const char *devlink;
|
|
|
|
|
|
|
|
FOREACH_DEVICE_DEVLINK(device, devlink)
|
2017-12-14 19:02:29 +01:00
|
|
|
fprintf(f, "S:%s\n", devlink + STRLEN("/dev/"));
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
if (device->devlink_priority != 0)
|
|
|
|
fprintf(f, "L:%i\n", device->devlink_priority);
|
|
|
|
|
|
|
|
if (device->watch_handle >= 0)
|
|
|
|
fprintf(f, "W:%i\n", device->watch_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (device->usec_initialized > 0)
|
|
|
|
fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized);
|
|
|
|
|
2020-09-08 11:58:29 +02:00
|
|
|
ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db)
|
2015-04-01 13:50:31 +02:00
|
|
|
fprintf(f, "E:%s=%s\n", property, value);
|
|
|
|
|
|
|
|
FOREACH_DEVICE_TAG(device, tag)
|
udev: make tags "sticky"
This tries to address the "bind"/"unbind" uevent kernel API breakage, by
changing the semantics of device tags.
Previously, tags would be applied on uevents (and the database entries
they result in) only depending on the immediate context. This means that
if one uevent causes the tag to be set and the next to be unset, this
would immediately effect what apps would see and the database entries
would contain each time. This is problematic however, as tags are a
filtering concept, and if tags vanish then clients won't hence notice
when a device stops being relevant to them since not only the tags
disappear but immediately also the uevents for it are filtered including
the one necessary for the app to notice that the device lost its tag and
hence relevance.
With this change tags become "sticky". If a tag is applied is once
applied to a device it will stay in place forever, until the device is
removed. Tags can never be removed again. This means that an app
watching a specific set of devices by filtering for a tag is guaranteed
to not only see the events where the tag is set but also all follow-up
events where the tags might be removed again.
This change of behaviour is unfortunate, but is required due to the
kernel introducing new "bind" and "unbind" uevents that generally have
the effect that tags and properties disappear and apps hence don't
notice when a device looses relevance to it. "bind"/"unbind" events were
introduced in kernel 4.12, and are now used in more and more subsystems.
The introduction broke userspace widely, and this commit is an attempt
to provide a way for apps to deal with it.
While tags are now "sticky" a new automatic device property
CURRENT_TAGS is introduced (matching the existing TAGS property) that
always reflects the precise set of tags applied on the most recent
events. Thus, when subscribing to devices through tags, all devices that
ever had the tag put on them will be be seen, and by CURRENT_TAGS it may
be checked whether the device right at the moment matches the tag
requirements.
See: #7587 #7018 #8221
2018-12-13 17:55:14 +01:00
|
|
|
fprintf(f, "G:%s\n", tag); /* Any tag */
|
|
|
|
|
2020-09-08 11:58:29 +02:00
|
|
|
SET_FOREACH(tag, device->current_tags)
|
udev: make tags "sticky"
This tries to address the "bind"/"unbind" uevent kernel API breakage, by
changing the semantics of device tags.
Previously, tags would be applied on uevents (and the database entries
they result in) only depending on the immediate context. This means that
if one uevent causes the tag to be set and the next to be unset, this
would immediately effect what apps would see and the database entries
would contain each time. This is problematic however, as tags are a
filtering concept, and if tags vanish then clients won't hence notice
when a device stops being relevant to them since not only the tags
disappear but immediately also the uevents for it are filtered including
the one necessary for the app to notice that the device lost its tag and
hence relevance.
With this change tags become "sticky". If a tag is applied is once
applied to a device it will stay in place forever, until the device is
removed. Tags can never be removed again. This means that an app
watching a specific set of devices by filtering for a tag is guaranteed
to not only see the events where the tag is set but also all follow-up
events where the tags might be removed again.
This change of behaviour is unfortunate, but is required due to the
kernel introducing new "bind" and "unbind" uevents that generally have
the effect that tags and properties disappear and apps hence don't
notice when a device looses relevance to it. "bind"/"unbind" events were
introduced in kernel 4.12, and are now used in more and more subsystems.
The introduction broke userspace widely, and this commit is an attempt
to provide a way for apps to deal with it.
While tags are now "sticky" a new automatic device property
CURRENT_TAGS is introduced (matching the existing TAGS property) that
always reflects the precise set of tags applied on the most recent
events. Thus, when subscribing to devices through tags, all devices that
ever had the tag put on them will be be seen, and by CURRENT_TAGS it may
be checked whether the device right at the moment matches the tag
requirements.
See: #7587 #7018 #8221
2018-12-13 17:55:14 +01:00
|
|
|
fprintf(f, "Q:%s\n", tag); /* Current tag */
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
r = fflush_and_check(f);
|
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
r = rename(path_tmp, path);
|
|
|
|
if (r < 0) {
|
|
|
|
r = -errno;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2018-11-01 08:15:50 +01:00
|
|
|
log_device_debug(device, "sd-device: Created %s file '%s' for '%s'", has_info ? "db" : "empty",
|
|
|
|
path, device->devpath);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail:
|
2015-07-29 20:31:07 +02:00
|
|
|
(void) unlink(path);
|
|
|
|
(void) unlink(path_tmp);
|
2015-04-01 13:50:31 +02:00
|
|
|
|
2018-11-01 08:15:50 +01:00
|
|
|
return log_device_debug_errno(device, r, "sd-device: Failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath);
|
2015-04-01 13:50:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int device_delete_db(sd_device *device) {
|
|
|
|
const char *id;
|
|
|
|
char *path;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
r = device_get_id_filename(device, &id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
path = strjoina("/run/udev/data/", id);
|
|
|
|
|
|
|
|
r = unlink(path);
|
|
|
|
if (r < 0 && errno != ENOENT)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2019-03-08 09:25:00 +01:00
|
|
|
|
|
|
|
static const char* const device_action_table[_DEVICE_ACTION_MAX] = {
|
|
|
|
[DEVICE_ACTION_ADD] = "add",
|
|
|
|
[DEVICE_ACTION_REMOVE] = "remove",
|
|
|
|
[DEVICE_ACTION_CHANGE] = "change",
|
|
|
|
[DEVICE_ACTION_MOVE] = "move",
|
|
|
|
[DEVICE_ACTION_ONLINE] = "online",
|
|
|
|
[DEVICE_ACTION_OFFLINE] = "offline",
|
|
|
|
[DEVICE_ACTION_BIND] = "bind",
|
|
|
|
[DEVICE_ACTION_UNBIND] = "unbind",
|
|
|
|
};
|
|
|
|
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(device_action, DeviceAction);
|
2019-06-28 20:49:20 +02:00
|
|
|
|
|
|
|
void dump_device_action_table(void) {
|
|
|
|
DUMP_STRING_TABLE(device_action, DeviceAction, _DEVICE_ACTION_MAX);
|
|
|
|
}
|