chiark / gitweb /
libudev: introduce clone_with_db()
[elogind.git] / src / libudev / libudev-device.c
index 05c5fbce9858ac38b8b669a65adb650fcf505a29..c0a061af6c808d6d505fc13f0182fcf41d4145c9 100644 (file)
 #include "libudev.h"
 #include "libudev-private.h"
 
+static int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode);
+static struct udev_list_entry *udev_device_add_property_internal(struct udev_device *udev_device, const char *key, const char *value);
+
 /**
  * SECTION:libudev-device
  * @short_description: kernel sys devices
  *
  * Representation of kernel sys devices. Devices are uniquely identified
  * by their syspath, every device has exactly one path in the kernel sys
- * filesystem. Devices usually belong to a kernel subsystem, and and have
+ * filesystem. Devices usually belong to a kernel subsystem, and have
  * a unique name inside that subsystem.
  */
 
@@ -121,7 +124,7 @@ static int udev_device_set_seqnum(struct udev_device *udev_device, unsigned long
 
         udev_device->seqnum = seqnum;
         snprintf(num, sizeof(num), "%llu", seqnum);
-        udev_device_add_property(udev_device, "SEQNUM", num);
+        udev_device_add_property_internal(udev_device, "SEQNUM", num);
         return 0;
 }
 
@@ -137,8 +140,8 @@ static int udev_device_set_ifindex(struct udev_device *udev_device, int ifindex)
         char num[32];
 
         udev_device->ifindex = ifindex;
-        snprintf(num, sizeof(num), "%u", ifindex);
-        udev_device_add_property(udev_device, "IFINDEX", num);
+        snprintf(num, sizeof(num), "%d", ifindex);
+        udev_device_add_property_internal(udev_device, "IFINDEX", num);
         return 0;
 }
 
@@ -166,9 +169,9 @@ static int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum)
         udev_device->devnum = devnum;
 
         snprintf(num, sizeof(num), "%u", major(devnum));
-        udev_device_add_property(udev_device, "MAJOR", num);
+        udev_device_add_property_internal(udev_device, "MAJOR", num);
         snprintf(num, sizeof(num), "%u", minor(devnum));
-        udev_device_add_property(udev_device, "MINOR", num);
+        udev_device_add_property_internal(udev_device, "MINOR", num);
         return 0;
 }
 
@@ -185,7 +188,7 @@ static int udev_device_set_devpath_old(struct udev_device *udev_device, const ch
         udev_device->devpath_old = strdup(devpath_old);
         if (udev_device->devpath_old == NULL)
                 return -ENOMEM;
-        udev_device_add_property(udev_device, "DEVPATH_OLD", udev_device->devpath_old);
+        udev_device_add_property_internal(udev_device, "DEVPATH_OLD", udev_device->devpath_old);
 
         pos = strrchr(udev_device->devpath_old, '/');
         if (pos == NULL)
@@ -222,7 +225,7 @@ static int udev_device_set_driver(struct udev_device *udev_device, const char *d
         if (udev_device->driver == NULL)
                 return -ENOMEM;
         udev_device->driver_set = true;
-        udev_device_add_property(udev_device, "DRIVER", udev_device->driver);
+        udev_device_add_property_internal(udev_device, "DRIVER", udev_device->driver);
         return 0;
 }
 
@@ -252,18 +255,18 @@ static int udev_device_set_devtype(struct udev_device *udev_device, const char *
         if (udev_device->devtype == NULL)
                 return -ENOMEM;
         udev_device->devtype_set = true;
-        udev_device_add_property(udev_device, "DEVTYPE", udev_device->devtype);
+        udev_device_add_property_internal(udev_device, "DEVTYPE", udev_device->devtype);
         return 0;
 }
 
-int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsystem)
+static int udev_device_set_subsystem(struct udev_device *udev_device, const char *subsystem)
 {
         free(udev_device->subsystem);
         udev_device->subsystem = strdup(subsystem);
         if (udev_device->subsystem == NULL)
                 return -ENOMEM;
         udev_device->subsystem_set = true;
-        udev_device_add_property(udev_device, "SUBSYSTEM", udev_device->subsystem);
+        udev_device_add_property_internal(udev_device, "SUBSYSTEM", udev_device->subsystem);
         return 0;
 }
 
@@ -321,7 +324,7 @@ static int udev_device_set_devnode_mode(struct udev_device *udev_device, mode_t
 
         udev_device->devnode_mode = mode;
         snprintf(num, sizeof(num), "%#o", mode);
-        udev_device_add_property(udev_device, "DEVMODE", num);
+        udev_device_add_property_internal(udev_device, "DEVMODE", num);
         return 0;
 }
 
@@ -338,7 +341,7 @@ static int udev_device_set_devnode_uid(struct udev_device *udev_device, uid_t ui
 
         udev_device->devnode_uid = uid;
         snprintf(num, sizeof(num), "%u", uid);
-        udev_device_add_property(udev_device, "DEVUID", num);
+        udev_device_add_property_internal(udev_device, "DEVUID", num);
         return 0;
 }
 
@@ -355,11 +358,11 @@ static int udev_device_set_devnode_gid(struct udev_device *udev_device, gid_t gi
 
         udev_device->devnode_gid = gid;
         snprintf(num, sizeof(num), "%u", gid);
-        udev_device_add_property(udev_device, "DEVGID", num);
+        udev_device_add_property_internal(udev_device, "DEVGID", num);
         return 0;
 }
 
-struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value)
+static struct udev_list_entry *udev_device_add_property_internal(struct udev_device *udev_device, const char *key, const char *value)
 {
         udev_device->envp_uptodate = false;
         if (value == NULL) {
@@ -374,6 +377,20 @@ struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device
         return udev_list_entry_add(&udev_device->properties_list, key, value);
 }
 
+
+int udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value)
+{
+        struct udev_list_entry *property;
+
+        property = udev_device_add_property_internal(udev_device, key, value);
+
+        /* store in db, skip private keys */
+        if (key[0] != '.')
+                udev_list_entry_set_num(property, true);
+
+        return 0;
+}
+
 static struct udev_list_entry *udev_device_add_property_from_string(struct udev_device *udev_device, const char *property)
 {
         char name[UTIL_LINE_SIZE];
@@ -387,7 +404,45 @@ static struct udev_list_entry *udev_device_add_property_from_string(struct udev_
         val = &val[1];
         if (val[0] == '\0')
                 val = NULL;
-        return udev_device_add_property(udev_device, name, val);
+        return udev_device_add_property_internal(udev_device, name, val);
+}
+
+static int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath)
+{
+        const char *pos;
+        size_t len;
+
+        free(udev_device->syspath);
+        udev_device->syspath = strdup(syspath);
+        if (udev_device->syspath ==  NULL)
+                return -ENOMEM;
+        udev_device->devpath = udev_device->syspath + strlen("/sys");
+        udev_device_add_property_internal(udev_device, "DEVPATH", udev_device->devpath);
+
+        pos = strrchr(udev_device->syspath, '/');
+        if (pos == NULL)
+                return -EINVAL;
+        udev_device->sysname = strdup(&pos[1]);
+        if (udev_device->sysname == NULL)
+                return -ENOMEM;
+
+        /* some devices have '!' in their name, change that to '/' */
+        len = 0;
+        while (udev_device->sysname[len] != '\0') {
+                if (udev_device->sysname[len] == '!')
+                        udev_device->sysname[len] = '/';
+                len++;
+        }
+
+        /* trailing number */
+        while (len > 0 && isdigit(udev_device->sysname[--len]))
+                udev_device->sysnum = &udev_device->sysname[len];
+
+        /* sysname is completely numeric */
+        if (len == 0)
+                udev_device->sysnum = NULL;
+
+        return 0;
 }
 
 /*
@@ -399,7 +454,7 @@ static struct udev_list_entry *udev_device_add_property_from_string(struct udev_
  * udev_device_set_info_loaded() needs to be set, to avoid trying
  * to use a device without a DEVPATH set
  */
-void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property)
+static void udev_device_add_property_from_string_parse(struct udev_device *udev_device, const char *property)
 {
         if (startswith(property, "DEVPATH=")) {
                 char path[UTIL_PATH_SIZE];
@@ -475,7 +530,7 @@ void udev_device_add_property_from_string_parse(struct udev_device *udev_device,
         }
 }
 
-int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device)
+static int udev_device_add_property_from_string_parse_finish(struct udev_device *udev_device)
 {
         if (udev_device->maj > 0)
                 udev_device_set_devnum(udev_device, makedev(udev_device->maj, udev_device->min));
@@ -510,32 +565,29 @@ _public_ const char *udev_device_get_property_value(struct udev_device *udev_dev
         return udev_list_entry_get_value(list_entry);
 }
 
-int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
+int udev_device_read_db(struct udev_device *udev_device)
 {
         char filename[UTIL_PATH_SIZE];
         char line[UTIL_LINE_SIZE];
+        const char *id;
         FILE *f;
 
-        /* providing a database file will always force-load it */
-        if (dbfile == NULL) {
-                const char *id;
-
-                if (udev_device->db_loaded)
-                        return 0;
-                udev_device->db_loaded = true;
+        if (udev_device->db_loaded)
+                return 0;
 
-                id = udev_device_get_id_filename(udev_device);
-                if (id == NULL)
-                        return -1;
-                strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL);
-                dbfile = filename;
-        }
+        udev_device->db_loaded = true;
 
-        f = fopen(dbfile, "re");
-        if (f == NULL) {
-                udev_dbg(udev_device->udev, "no db file to read %s: %m\n", dbfile);
+        id = udev_device_get_id_filename(udev_device);
+        if (id == NULL)
                 return -1;
-        }
+
+        strscpyl(filename, sizeof(filename), "/run/udev/data/", id, NULL);
+
+        f = fopen(filename, "re");
+        if (f == NULL)
+                return log_debug_errno(errno, "no db file to read %s: %m", filename);
+
+        /* devices with a database entry are initialized */
         udev_device->is_initialized = true;
 
         while (fgets(line, sizeof(line), f)) {
@@ -573,7 +625,7 @@ int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
         }
         fclose(f);
 
-        udev_dbg(udev_device->udev, "device %p filled with db file data\n", udev_device);
+        log_debug("device %p filled with db file data", udev_device);
         return 0;
 }
 
@@ -591,7 +643,7 @@ int udev_device_read_uevent_file(struct udev_device *udev_device)
         strscpyl(filename, sizeof(filename), udev_device->syspath, "/uevent", NULL);
         f = fopen(filename, "re");
         if (f == NULL)
-                return -1;
+                return -errno;
         udev_device->uevent_loaded = true;
 
         while (fgets(line, sizeof(line), f)) {
@@ -635,17 +687,20 @@ void udev_device_set_info_loaded(struct udev_device *device)
         device->info_loaded = true;
 }
 
-struct udev_device *udev_device_new(struct udev *udev)
+static struct udev_device *udev_device_new(struct udev *udev)
 {
         struct udev_device *udev_device;
-        struct udev_list_entry *list_entry;
 
-        if (udev == NULL)
+        if (udev == NULL) {
+                errno = EINVAL;
                 return NULL;
+        }
 
-        udev_device = calloc(1, sizeof(struct udev_device));
-        if (udev_device == NULL)
+        udev_device = new0(struct udev_device, 1);
+        if (udev_device == NULL) {
+                errno = ENOMEM;
                 return NULL;
+        }
         udev_device->refcount = 1;
         udev_device->udev = udev;
         udev_list_init(udev, &udev_device->devlinks_list, true);
@@ -654,11 +709,7 @@ struct udev_device *udev_device_new(struct udev *udev)
         udev_list_init(udev, &udev_device->sysattr_list, false);
         udev_list_init(udev, &udev_device->tags_list, true);
         udev_device->watch_handle = -1;
-        /* copy global properties */
-        udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev))
-                udev_device_add_property(udev_device,
-                                         udev_list_entry_get_name(list_entry),
-                                         udev_list_entry_get_value(list_entry));
+
         return udev_device;
 }
 
@@ -684,22 +735,30 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con
         struct stat statbuf;
         struct udev_device *udev_device;
 
-        if (udev == NULL)
+        if (udev == NULL) {
+                errno = EINVAL;
                 return NULL;
-        if (syspath == NULL)
+        }
+
+        if (syspath == NULL) {
+                errno = EINVAL;
                 return NULL;
+        }
 
         /* path starts in sys */
         if (!startswith(syspath, "/sys")) {
-                udev_dbg(udev, "not in sys :%s\n", syspath);
+                log_debug("not in sys :%s", syspath);
+                errno = EINVAL;
                 return NULL;
         }
 
         /* path is not a root directory */
         subdir = syspath + strlen("/sys");
         pos = strrchr(subdir, '/');
-        if (pos == NULL || pos[1] == '\0' || pos < &subdir[2])
+        if (pos == NULL || pos[1] == '\0' || pos < &subdir[2]) {
+                errno = EINVAL;
                 return NULL;
+        }
 
         /* resolve possible symlink to real path */
         strscpy(path, sizeof(path), syspath);
@@ -714,8 +773,13 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con
                         return NULL;
         } else {
                 /* everything else just needs to be a directory */
-                if (stat(path, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
+                if (stat(path, &statbuf) != 0)
                         return NULL;
+
+                if (!S_ISDIR(statbuf.st_mode)) {
+                        errno = EISDIR;
+                        return NULL;
+                }
         }
 
         udev_device = udev_device_new(udev);
@@ -723,7 +787,7 @@ _public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, con
                 return NULL;
 
         udev_device_set_syspath(udev_device, path);
-        udev_dbg(udev, "device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device));
+        log_debug("device %p has devpath '%s'", udev_device, udev_device_get_devpath(udev_device));
 
         return udev_device;
 }
@@ -753,8 +817,10 @@ _public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char
                 type_str = "block";
         else if (type == 'c')
                 type_str = "char";
-        else
+        else {
+                errno = EINVAL;
                 return NULL;
+        }
 
         /* use /sys/dev/{block,char}/<maj>:<min> link */
         snprintf(path, sizeof(path), "/sys/dev/%s/%u:%u",
@@ -780,7 +846,7 @@ _public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char
  *
  * Returns: a new udev device, or #NULL, if it does not exist
  **/
-_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, char *id)
+_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id)
 {
         char type;
         int maj, min;
@@ -800,13 +866,15 @@ _public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, c
                 int ifindex;
 
                 ifindex = strtoul(&id[1], NULL, 10);
-                if (ifindex <= 0)
+                if (ifindex <= 0) {
+                        errno = EINVAL;
                         return NULL;
+                }
 
                 sk = socket(PF_INET, SOCK_DGRAM, 0);
                 if (sk < 0)
                         return NULL;
-                memset(&ifr, 0x00, sizeof(struct ifreq));
+                memzero(&ifr, sizeof(struct ifreq));
                 ifr.ifr_ifindex = ifindex;
                 if (ioctl(sk, SIOCGIFNAME, &ifr) != 0) {
                         close(sk);
@@ -819,18 +887,24 @@ _public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, c
                         return NULL;
                 if (udev_device_get_ifindex(dev) == ifindex)
                         return dev;
+
+                /* this is racy, so we may end up with the wrong device */
                 udev_device_unref(dev);
+                errno = ENODEV;
                 return NULL;
         }
         case '+':
                 strscpy(subsys, sizeof(subsys), &id[1]);
                 sysname = strchr(subsys, ':');
-                if (sysname == NULL)
+                if (sysname == NULL) {
+                        errno = EINVAL;
                         return NULL;
+                }
                 sysname[0] = '\0';
                 sysname = &sysname[1];
                 return udev_device_new_from_subsystem_sysname(udev, subsys, sysname);
         default:
+                errno = EINVAL;
                 return NULL;
         }
 }
@@ -894,7 +968,9 @@ _public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev
                         strscpyl(path, sizeof(path), "/sys/bus/", subsys, "/drivers/", driver, NULL);
                         if (stat(path, &statbuf) == 0)
                                 goto found;
-                }
+                } else
+                        errno = EINVAL;
+
                 goto out;
         }
 
@@ -943,7 +1019,7 @@ _public_ struct udev_device *udev_device_new_from_environment(struct udev *udev)
                 udev_device_add_property_from_string_parse(udev_device, environ[i]);
 
         if (udev_device_add_property_from_string_parse_finish(udev_device) < 0) {
-                udev_dbg(udev, "missing values, invalid device\n");
+                log_debug("missing values, invalid device");
                 udev_device_unref(udev_device);
                 udev_device = NULL;
         }
@@ -970,6 +1046,8 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic
                 if (udev_device_parent != NULL)
                         return udev_device_parent;
         }
+
+        errno = ENOENT;
         return NULL;
 }
 
@@ -980,9 +1058,8 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic
  * Find the next parent device, and fill in information from the sys
  * device and the udev database entry.
  *
- * The returned the device is not referenced. It is attached to the
- * child device, and will be cleaned up when the child device
- * is cleaned up.
+ * Returned device is not referenced. It is attached to the child
+ * device, and will be cleaned up when the child device is cleaned up.
  *
  * It is not necessarily just the upper level directory, empty or not
  * recognized sys directories are ignored.
@@ -994,8 +1071,10 @@ static struct udev_device *device_new_from_parent(struct udev_device *udev_devic
  **/
 _public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device)
 {
-        if (udev_device == NULL)
+        if (udev_device == NULL) {
+                errno = EINVAL;
                 return NULL;
+        }
         if (!udev_device->parent_set) {
                 udev_device->parent_set = true;
                 udev_device->parent_device = device_new_from_parent(udev_device);
@@ -1016,9 +1095,8 @@ _public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_dev
  * If devtype is #NULL, only subsystem is checked, and any devtype will
  * match.
  *
- * The returned the device is not referenced. It is attached to the
- * child device, and will be cleaned up when the child device
- * is cleaned up.
+ * Returned device is not referenced. It is attached to the child
+ * device, and will be cleaned up when the child device is cleaned up.
  *
  * It can be called as many times as needed, without caring about
  * references.
@@ -1029,8 +1107,10 @@ _public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struc
 {
         struct udev_device *parent;
 
-        if (subsystem == NULL)
+        if (subsystem == NULL) {
+                errno = EINVAL;
                 return NULL;
+        }
 
         parent = udev_device_get_parent(udev_device);
         while (parent != NULL) {
@@ -1047,6 +1127,10 @@ _public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struc
                 }
                 parent = udev_device_get_parent(parent);
         }
+
+        if (!parent)
+                errno = ENOENT;
+
         return parent;
 }
 
@@ -1088,7 +1172,7 @@ _public_ struct udev_device *udev_device_ref(struct udev_device *udev_device)
  * Drop a reference of a udev device. If the refcount reaches zero,
  * the resources of the device will be released.
  *
- * Returns: the passed udev device if it has still an active reference, or #NULL otherwise.
+ * Returns: #NULL
  **/
 _public_ struct udev_device *udev_device_unref(struct udev_device *udev_device)
 {
@@ -1096,7 +1180,7 @@ _public_ struct udev_device *udev_device_unref(struct udev_device *udev_device)
                 return NULL;
         udev_device->refcount--;
         if (udev_device->refcount > 0)
-                return udev_device;
+                return NULL;
         if (udev_device->parent_device != NULL)
                 udev_device_unref(udev_device->parent_device);
         free(udev_device->syspath);
@@ -1219,7 +1303,7 @@ _public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev
         if (udev_device == NULL)
                 return NULL;
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         return udev_list_get_entry(&udev_device->devlinks_list);
 }
 
@@ -1247,7 +1331,7 @@ _public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct ud
                 return NULL;
         if (!udev_device->info_loaded) {
                 udev_device_read_uevent_file(udev_device);
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         }
         if (!udev_device->devlinks_uptodate) {
                 char symlinks[UTIL_PATH_SIZE];
@@ -1263,7 +1347,7 @@ _public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct ud
                         l = strpcpyl(&s, sizeof(symlinks), udev_list_entry_get_name(list_entry), NULL);
                         udev_list_entry_foreach(list_entry, udev_list_entry_get_next(list_entry))
                                 l = strpcpyl(&s, l, " ", udev_list_entry_get_name(list_entry), NULL);
-                        udev_device_add_property(udev_device, "DEVLINKS", symlinks);
+                        udev_device_add_property_internal(udev_device, "DEVLINKS", symlinks);
                 }
         }
         if (!udev_device->tags_uptodate) {
@@ -1278,7 +1362,7 @@ _public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct ud
                         l = strpcpyl(&s, sizeof(tags), ":", NULL);
                         udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device))
                                 l = strpcpyl(&s, l, udev_list_entry_get_name(list_entry), ":", NULL);
-                        udev_device_add_property(udev_device, "TAGS", tags);
+                        udev_device_add_property_internal(udev_device, "TAGS", tags);
                 }
         }
         return udev_list_get_entry(&udev_device->properties_list);
@@ -1320,7 +1404,7 @@ _public_ unsigned long long int udev_device_get_usec_since_initialized(struct ud
         if (udev_device == NULL)
                 return 0;
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         if (udev_device->usec_initialized == 0)
                 return 0;
         now_ts = now(CLOCK_MONOTONIC);
@@ -1339,8 +1423,8 @@ void udev_device_set_usec_initialized(struct udev_device *udev_device, usec_t us
         char num[32];
 
         udev_device->usec_initialized = usec_initialized;
-        snprintf(num, sizeof(num), "%llu", (unsigned long long)usec_initialized);
-        udev_device_add_property(udev_device, "USEC_INITIALIZED", num);
+        snprintf(num, sizeof(num), USEC_FMT, usec_initialized);
+        udev_device_add_property_internal(udev_device, "USEC_INITIALIZED", num);
 }
 
 /**
@@ -1381,8 +1465,6 @@ _public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_devi
         }
 
         if (S_ISLNK(statbuf.st_mode)) {
-                struct udev_device *dev;
-
                 /*
                  * Some core links return only the last element of the target path,
                  * these are just values, the paths should not be exposed.
@@ -1398,17 +1480,6 @@ _public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_devi
                         goto out;
                 }
 
-                /* resolve custom link to a device and return its syspath */
-                if (!streq(sysattr, "device")) {
-                        strscpyl(path, sizeof(path), udev_device->syspath, "/", sysattr, NULL);
-                        dev = udev_device_new_from_syspath(udev_device->udev, path);
-                        if (dev != NULL) {
-                                list_entry = udev_list_entry_add(&udev_device->sysattr_value_list, sysattr,
-                                                                 udev_device_get_syspath(dev));
-                                val = udev_list_entry_get_value(list_entry);
-                                udev_device_unref(dev);
-                        }
-                }
                 goto out;
         }
 
@@ -1468,7 +1539,7 @@ _public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, cons
                 value_len = 0;
         else
                 value_len = strlen(value);
-restart:
+
         strscpyl(path, sizeof(path), udev_device_get_syspath(dev), "/", sysattr, NULL);
         if (lstat(path, &statbuf) != 0) {
                 udev_list_entry_add(&dev->sysattr_value_list, sysattr, NULL);
@@ -1477,24 +1548,7 @@ restart:
         }
 
         if (S_ISLNK(statbuf.st_mode)) {
-                /*
-                 * Cannot modify core link values
-                 */
-                if (streq(sysattr, "driver") ||
-                    streq(sysattr, "subsystem") ||
-                    streq(sysattr, "module")) {
-                        ret = -EPERM;
-                } else if (!streq(sysattr, "device")) {
-                        /* resolve custom link to a device */
-                        strscpyl(path, sizeof(path), udev_device->syspath, "/", sysattr, NULL);
-                        dev = udev_device_new_from_syspath(udev_device->udev, path);
-                        if (dev != NULL)
-                                goto restart;
-                        ret = -ENXIO;
-                } else {
-                        /* Unhandled, to not try to modify anything */
-                        ret = -EINVAL;
-                }
+                ret = -EINVAL;
                 goto out;
         }
 
@@ -1549,13 +1603,13 @@ static int udev_device_sysattr_list_read(struct udev_device *udev_device)
         int num = 0;
 
         if (udev_device == NULL)
-                return -1;
+                return -EINVAL;
         if (udev_device->sysattr_list_read)
                 return 0;
 
         dir = opendir(udev_device_get_syspath(udev_device));
         if (!dir)
-                return -1;
+                return -errno;
 
         for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
                 char path[UTIL_PATH_SIZE];
@@ -1603,45 +1657,7 @@ _public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_
         return udev_list_get_entry(&udev_device->sysattr_list);
 }
 
-int udev_device_set_syspath(struct udev_device *udev_device, const char *syspath)
-{
-        const char *pos;
-        size_t len;
-
-        free(udev_device->syspath);
-        udev_device->syspath = strdup(syspath);
-        if (udev_device->syspath ==  NULL)
-                return -ENOMEM;
-        udev_device->devpath = udev_device->syspath + strlen("/sys");
-        udev_device_add_property(udev_device, "DEVPATH", udev_device->devpath);
-
-        pos = strrchr(udev_device->syspath, '/');
-        if (pos == NULL)
-                return -EINVAL;
-        udev_device->sysname = strdup(&pos[1]);
-        if (udev_device->sysname == NULL)
-                return -ENOMEM;
-
-        /* some devices have '!' in their name, change that to '/' */
-        len = 0;
-        while (udev_device->sysname[len] != '\0') {
-                if (udev_device->sysname[len] == '!')
-                        udev_device->sysname[len] = '/';
-                len++;
-        }
-
-        /* trailing number */
-        while (len > 0 && isdigit(udev_device->sysname[--len]))
-                udev_device->sysnum = &udev_device->sysname[len];
-
-        /* sysname is completely numeric */
-        if (len == 0)
-                udev_device->sysnum = NULL;
-
-        return 0;
-}
-
-int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode)
+static int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode)
 {
         free(udev_device->devnode);
         if (devnode[0] != '/') {
@@ -1652,7 +1668,7 @@ int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode
         }
         if (udev_device->devnode == NULL)
                 return -ENOMEM;
-        udev_device_add_property(udev_device, "DEVNAME", udev_device->devnode);
+        udev_device_add_property_internal(udev_device, "DEVNAME", udev_device->devnode);
         return 0;
 }
 
@@ -1682,7 +1698,7 @@ const char *udev_device_get_id_filename(struct udev_device *udev_device)
                                 udev_device->id_filename = NULL;
                 } else if (udev_device_get_ifindex(udev_device) > 0) {
                         /* use netdev ifindex -- n3 */
-                        if (asprintf(&udev_device->id_filename, "n%u", udev_device_get_ifindex(udev_device)) < 0)
+                        if (asprintf(&udev_device->id_filename, "n%i", udev_device_get_ifindex(udev_device)) < 0)
                                 udev_device->id_filename = NULL;
                 } else {
                         /*
@@ -1717,7 +1733,7 @@ const char *udev_device_get_id_filename(struct udev_device *udev_device)
 _public_ int udev_device_get_is_initialized(struct udev_device *udev_device)
 {
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         return udev_device->is_initialized;
 }
 
@@ -1726,9 +1742,14 @@ void udev_device_set_is_initialized(struct udev_device *udev_device)
         udev_device->is_initialized = true;
 }
 
+static bool is_valid_tag(const char *tag)
+{
+        return !strchr(tag, ':') && !strchr(tag, ' ');
+}
+
 int udev_device_add_tag(struct udev_device *udev_device, const char *tag)
 {
-        if (strchr(tag, ':') != NULL || strchr(tag, ' ') != NULL)
+        if (!is_valid_tag(tag))
                 return -EINVAL;
         udev_device->tags_uptodate = false;
         if (udev_list_entry_add(&udev_device->tags_list, tag, NULL) != NULL)
@@ -1736,6 +1757,20 @@ int udev_device_add_tag(struct udev_device *udev_device, const char *tag)
         return -ENOMEM;
 }
 
+void udev_device_remove_tag(struct udev_device *udev_device, const char *tag)
+{
+        struct udev_list_entry *e;
+
+        if (!is_valid_tag(tag))
+                return;
+        e = udev_list_get_entry(&udev_device->tags_list);
+        e = udev_list_entry_get_by_name(e, tag);
+        if (e) {
+                udev_device->tags_uptodate = false;
+                udev_list_entry_delete(e);
+        }
+}
+
 void udev_device_cleanup_tags_list(struct udev_device *udev_device)
 {
         udev_device->tags_uptodate = false;
@@ -1758,7 +1793,7 @@ _public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_dev
         if (udev_device == NULL)
                 return NULL;
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         return udev_list_get_entry(&udev_device->tags_list);
 }
 
@@ -1778,7 +1813,7 @@ _public_ int udev_device_has_tag(struct udev_device *udev_device, const char *ta
         if (udev_device == NULL)
                 return false;
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         list_entry = udev_device_get_tags_list_entry(udev_device);
         if (udev_list_entry_get_by_name(list_entry, tag) != NULL)
                 return true;
@@ -1860,14 +1895,14 @@ int udev_device_set_action(struct udev_device *udev_device, const char *action)
         udev_device->action = strdup(action);
         if (udev_device->action == NULL)
                 return -ENOMEM;
-        udev_device_add_property(udev_device, "ACTION", udev_device->action);
+        udev_device_add_property_internal(udev_device, "ACTION", udev_device->action);
         return 0;
 }
 
 int udev_device_get_devlink_priority(struct udev_device *udev_device)
 {
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         return udev_device->devlink_priority;
 }
 
@@ -1880,7 +1915,7 @@ int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio)
 int udev_device_get_watch_handle(struct udev_device *udev_device)
 {
         if (!udev_device->info_loaded)
-                udev_device_read_db(udev_device, NULL);
+                udev_device_read_db(udev_device);
         return udev_device->watch_handle;
 }
 
@@ -1899,3 +1934,128 @@ void udev_device_set_db_persist(struct udev_device *udev_device)
 {
         udev_device->db_persist = true;
 }
+
+int udev_device_rename(struct udev_device *udev_device, const char *name)
+{
+        _cleanup_free_ char *dirname = NULL;
+        const char *interface;
+        char *new_syspath;
+        int r;
+
+        if (udev_device == NULL || name == NULL)
+                return -EINVAL;
+
+        dirname = dirname_malloc(udev_device->syspath);
+        if (!dirname)
+                return -ENOMEM;
+
+        new_syspath = strjoina(dirname, "/", name);
+
+        r = udev_device_set_syspath(udev_device, new_syspath);
+        if (r < 0)
+                return r;
+
+        interface = udev_device_get_property_value(udev_device, "INTERFACE");
+        if (interface) {
+                /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */
+                udev_device_add_property_internal(udev_device, "INTERFACE_OLD", interface);
+                udev_device_add_property_internal(udev_device, "INTERFACE", name);
+        }
+
+        return 0;
+}
+
+struct udev_device *udev_device_shallow_clone(struct udev_device *old_device)
+{
+        struct udev_device *device;
+
+        if (old_device == NULL)
+                return NULL;
+
+        device = udev_device_new(old_device->udev);
+        if (!device) {
+                errno = ENOMEM;
+
+                return NULL;
+        }
+
+        udev_device_set_syspath(device, udev_device_get_syspath(old_device));
+        udev_device_set_subsystem(device, udev_device_get_subsystem(old_device));
+        udev_device_set_devnum(device, udev_device_get_devnum(old_device));
+
+        return device;
+}
+
+struct udev_device *udev_device_clone_with_db(struct udev_device *old_device)
+{
+        struct udev_device *device;
+
+        device = udev_device_shallow_clone(old_device);
+        if (!device)
+                return NULL;
+
+        udev_device_read_db(device);
+        udev_device_set_info_loaded(device);
+
+        return device;
+}
+
+struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen) {
+        struct udev_device *device;
+        ssize_t bufpos = 0;
+
+        if (nulstr == NULL || buflen <= 0) {
+                errno = EINVAL;
+
+                return NULL;
+        }
+
+        device = udev_device_new(udev);
+        if (!device) {
+                errno = ENOMEM;
+
+                return NULL;
+        }
+
+        udev_device_set_info_loaded(device);
+
+        while (bufpos < buflen) {
+                char *key;
+                size_t keylen;
+
+                key = nulstr + bufpos;
+                keylen = strlen(key);
+                if (keylen == 0)
+                        break;
+
+                bufpos += keylen + 1;
+                udev_device_add_property_from_string_parse(device, key);
+        }
+
+        if (udev_device_add_property_from_string_parse_finish(device) < 0) {
+                log_debug("missing values, invalid device");
+
+                udev_device_unref(device);
+
+                errno = EINVAL;
+
+                return NULL;
+        }
+
+        return device;
+}
+
+int udev_device_copy_properties(struct udev_device *dst, struct udev_device *src) {
+        struct udev_list_entry *entry;
+
+        for ((entry = udev_device_get_properties_list_entry(src)); entry; entry = udev_list_entry_get_next(entry)) {
+                const char *key, *value;
+
+                key = udev_list_entry_get_name(entry);
+                value = udev_list_entry_get_value(entry);
+
+                udev_device_add_property(dst, key, value);
+        }
+
+        return 0;
+}