chiark / gitweb /
udevd: clarify worker exit status
[elogind.git] / libudev / libudev-device.c
index e5f8cc3e46b765d2af84ebcfed0123c33db689d5..0ef7260de223a78615b3831cd83906953e090a6e 100644 (file)
 #include <dirent.h>
 #include <fcntl.h>
 #include <ctype.h>
+#include <net/if.h>
 #include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <linux/sockios.h>
 
 #include "libudev.h"
 #include "libudev-private.h"
@@ -54,15 +58,17 @@ struct udev_device {
        char *devpath_old;
        char *sysname_old;
        char *knodename;
+       char *id_filename;
        char **envp;
        char *monitor_buf;
        size_t monitor_buf_len;
        struct udev_list_node devlinks_list;
        struct udev_list_node properties_list;
+       struct udev_list_node sysattr_value_list;
        struct udev_list_node sysattr_list;
        struct udev_list_node tags_list;
        unsigned long long int seqnum;
-       int event_timeout;
+       unsigned long long int usec_initialized;
        int timeout;
        int devlink_priority;
        int refcount;
@@ -80,6 +86,9 @@ struct udev_device {
        bool info_loaded;
        bool db_loaded;
        bool uevent_loaded;
+       bool is_initialized;
+       bool sysattr_list_read;
+       bool db_persist;
 };
 
 struct udev_list_entry *udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value)
@@ -228,70 +237,38 @@ const char *udev_device_get_property_value(struct udev_device *udev_device, cons
        return udev_list_entry_get_value(list_entry);
 }
 
-int udev_device_read_db(struct udev_device *udev_device)
+int udev_device_read_db(struct udev_device *udev_device, const char *dbfile)
 {
-       struct stat stats;
        char filename[UTIL_PATH_SIZE];
        char line[UTIL_LINE_SIZE];
        FILE *f;
 
-       if (udev_device->db_loaded)
-               return 0;
-
-       util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/.udev/db/",
-                     udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname(udev_device), NULL);
+       /* providing a database file will always force-load it */
+       if (dbfile == NULL) {
+               const char *id;
 
-       if (lstat(filename, &stats) != 0) {
-               dbg(udev_device->udev, "no db file to read %s: %m\n", filename);
-               return -1;
-       }
-       if ((stats.st_mode & S_IFMT) == S_IFLNK) {
-               char target[UTIL_PATH_SIZE];
-               char devnode[UTIL_PATH_SIZE];
-               int target_len;
-               char *next;
+               if (udev_device->db_loaded)
+                       return 0;
+               udev_device->db_loaded = true;
 
-               target_len = readlink(filename, target, sizeof(target));
-               if (target_len <= 0 || target_len == sizeof(target)) {
-                       info(udev_device->udev, "error reading db link %s: %m\n", filename);
+               id = udev_device_get_id_filename(udev_device);
+               if (id == NULL)
                        return -1;
-               }
-               target[target_len] = '\0';
-
-               next = strchr(target, ' ');
-               if (next != NULL) {
-                       next[0] = '\0';
-                       next = &next[1];
-               }
-               util_strscpyl(devnode, sizeof(devnode), udev_get_dev_path(udev_device->udev), "/", target, NULL);
-               udev_device_set_devnode(udev_device, devnode);
-               while (next != NULL) {
-                       char devlink[UTIL_PATH_SIZE];
-                       const char *lnk;
-
-                       lnk = next;
-                       next = strchr(next, ' ');
-                       if (next != NULL) {
-                               next[0] = '\0';
-                               next = &next[1];
-                       }
-                       util_strscpyl(devlink, sizeof(devlink), udev_get_dev_path(udev_device->udev), "/", lnk, NULL);
-                       udev_device_add_devlink(udev_device, devlink, 0);
-               }
-               info(udev_device->udev, "device %p filled with db symlink data '%s'\n", udev_device, udev_device->devnode);
-               return 0;
+               util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_device->udev), "/data/", id, NULL);
+               dbfile = filename;
        }
 
-       f = fopen(filename, "re");
+       f = fopen(dbfile, "re");
        if (f == NULL) {
-               dbg(udev_device->udev, "error reading db file %s: %m\n", filename);
+               info(udev_device->udev, "no db file to read %s: %m\n", dbfile);
                return -1;
        }
-       udev_device->db_loaded = true;
+       udev_device->is_initialized = true;
 
        while (fgets(line, sizeof(line), f)) {
                ssize_t len;
                const char *val;
+               struct udev_list_entry *entry;
 
                len = strlen(line);
                if (len < 4)
@@ -310,11 +287,9 @@ int udev_device_read_db(struct udev_device *udev_device)
                case 'L':
                        udev_device_set_devlink_priority(udev_device, atoi(val));
                        break;
-               case 'T':
-                       udev_device_set_event_timeout(udev_device, atoi(val));
-                       break;
                case 'E':
-                       udev_device_add_property_from_string(udev_device, val);
+                       entry = udev_device_add_property_from_string(udev_device, val);
+                       udev_list_entry_set_flags(entry, 1);
                        break;
                case 'G':
                        udev_device_add_tag(udev_device, val);
@@ -322,6 +297,9 @@ int udev_device_read_db(struct udev_device *udev_device)
                case 'W':
                        udev_device_set_watch_handle(udev_device, atoi(val));
                        break;
+               case 'I':
+                       udev_device_set_usec_initialized(udev_device, strtoull(val, NULL, 10));
+                       break;
                }
        }
        fclose(f);
@@ -394,9 +372,10 @@ struct udev_device *udev_device_new(struct udev *udev)
        udev_device->udev = udev;
        udev_list_init(&udev_device->devlinks_list);
        udev_list_init(&udev_device->properties_list);
+       udev_list_init(&udev_device->sysattr_value_list);
        udev_list_init(&udev_device->sysattr_list);
        udev_list_init(&udev_device->tags_list);
-       udev_device->event_timeout = -1;
+       udev_device->timeout = -1;
        udev_device->watch_handle = -1;
        /* copy global properties */
        udev_list_entry_foreach(list_entry, udev_get_properties_list_entry(udev))
@@ -515,6 +494,61 @@ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, de
        return udev_device_new_from_syspath(udev, path);
 }
 
+struct udev_device *udev_device_new_from_id_filename(struct udev *udev, char *id)
+{
+       char type;
+       int maj, min;
+       char subsys[UTIL_PATH_SIZE];
+       char *sysname;
+
+       switch(id[0]) {
+       case 'b':
+       case 'c':
+               if (sscanf(id, "%c%i:%i", &type, &maj, &min) != 3)
+                       return NULL;
+               return udev_device_new_from_devnum(udev, type, makedev(maj, min));
+       case 'n': {
+               int sk;
+               struct ifreq ifr;
+               struct udev_device *dev;
+               int ifindex;
+
+               ifindex = strtoul(&id[1], NULL, 10);
+               if (ifindex <= 0)
+                       return NULL;
+
+               sk = socket(PF_INET, SOCK_DGRAM, 0);
+               if (sk < 0)
+                       return NULL;
+               memset(&ifr, 0x00, sizeof(struct ifreq));
+               ifr.ifr_ifindex = ifindex;
+               if (ioctl(sk, SIOCGIFNAME, &ifr) != 0) {
+                       close(sk);
+                       return NULL;
+               }
+               close(sk);
+
+               dev = udev_device_new_from_subsystem_sysname(udev, "net", ifr.ifr_name);
+               if (dev == NULL)
+                       return NULL;
+               if (udev_device_get_ifindex(dev) == ifindex)
+                       return dev;
+               udev_device_unref(dev);
+               return NULL;
+       }
+       case '+':
+               util_strscpy(subsys, sizeof(subsys), &id[1]);
+               sysname = strchr(subsys, ':');
+               if (sysname == NULL)
+                       return NULL;
+               sysname[0] = '\0';
+               sysname = &sysname[1];
+               return udev_device_new_from_subsystem_sysname(udev, subsys, sysname);
+       default:
+               return NULL;
+       }
+}
+
 /**
  * udev_device_new_from_subsystem_sysname:
  * @udev: udev library context
@@ -792,6 +826,7 @@ void udev_device_unref(struct udev_device *udev_device)
        free(udev_device->devtype);
        udev_list_cleanup_entries(udev_device->udev, &udev_device->devlinks_list);
        udev_list_cleanup_entries(udev_device->udev, &udev_device->properties_list);
+       udev_list_cleanup_entries(udev_device->udev, &udev_device->sysattr_value_list);
        udev_list_cleanup_entries(udev_device->udev, &udev_device->sysattr_list);
        udev_list_cleanup_entries(udev_device->udev, &udev_device->tags_list);
        free(udev_device->action);
@@ -799,6 +834,7 @@ void udev_device_unref(struct udev_device *udev_device)
        free(udev_device->devpath_old);
        free(udev_device->sysname_old);
        free(udev_device->knodename);
+       free(udev_device->id_filename);
        free(udev_device->envp);
        free(udev_device->monitor_buf);
        dbg(udev_device->udev, "udev_device: %p released\n", udev_device);
@@ -876,8 +912,21 @@ const char *udev_device_get_devnode(struct udev_device *udev_device)
 {
        if (udev_device == NULL)
                return NULL;
-       if (!udev_device->info_loaded)
-               udev_device_read_db(udev_device);
+       if (!udev_device->info_loaded) {
+               udev_device_read_uevent_file(udev_device);
+               udev_device_read_db(udev_device, NULL);
+       }
+
+       /* we might get called before we handled an event and have a db, use the kernel-provided name */
+       if (udev_device->devnode == NULL && udev_device_get_knodename(udev_device) != NULL) {
+               char filename[UTIL_NAME_SIZE];
+
+               util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_device->udev), "/",
+                             udev_device_get_knodename(udev_device), NULL);
+               udev_device_set_devnode(udev_device, filename);
+               return udev_device->devnode;
+       }
+
        return udev_device->devnode;
 }
 
@@ -959,7 +1008,7 @@ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *
        if (udev_device == NULL)
                return NULL;
        if (!udev_device->info_loaded)
-               udev_device_read_db(udev_device);
+               udev_device_read_db(udev_device, NULL);
        return udev_list_get_entry(&udev_device->devlinks_list);
 }
 
@@ -987,7 +1036,7 @@ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device
                return NULL;
        if (!udev_device->info_loaded) {
                udev_device_read_uevent_file(udev_device);
-               udev_device_read_db(udev_device);
+               udev_device_read_db(udev_device, NULL);
        }
        if (!udev_device->devlinks_uptodate) {
                char symlinks[UTIL_PATH_SIZE];
@@ -1092,6 +1141,44 @@ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device)
        return udev_device->seqnum;
 }
 
+/**
+ * udev_device_get_usec_since_initialized:
+ * @udev_device: udev device
+ *
+ * Return the number of microseconds passed since udev set up the
+ * device for the first time.
+ *
+ * This is only implemented for devices with need to store properties
+ * in the udev database. All other devices return 0 here.
+ *
+ * Returns: the number of microseconds since the device was first seen.
+ **/
+unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device)
+{
+       unsigned long long now;
+
+       if (udev_device == NULL)
+               return 0;
+       if (!udev_device->info_loaded)
+               udev_device_read_db(udev_device, NULL);
+       if (udev_device->usec_initialized == 0)
+               return 0;
+       now = now_usec();
+       if (now == 0)
+               return 0;
+       return now - udev_device->usec_initialized;
+}
+
+unsigned long long udev_device_get_usec_initialized(struct udev_device *udev_device)
+{
+       return udev_device->usec_initialized;
+}
+
+void udev_device_set_usec_initialized(struct udev_device *udev_device, unsigned long long usec_initialized)
+{
+       udev_device->usec_initialized = usec_initialized;
+}
+
 /**
  * udev_device_get_sysattr_value:
  * @udev_device: udev device
@@ -1118,7 +1205,7 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const
                return NULL;
 
        /* look for possibly already cached result */
-       udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_device->sysattr_list)) {
+       udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_device->sysattr_value_list)) {
                if (strcmp(udev_list_entry_get_name(list_entry), sysattr) == 0) {
                        dbg(udev_device->udev, "got '%s' (%s) from cache\n",
                            sysattr, udev_list_entry_get_value(list_entry));
@@ -1129,7 +1216,7 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const
        util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", sysattr, NULL);
        if (lstat(path, &statbuf) != 0) {
                dbg(udev_device->udev, "no attribute '%s', keep negative entry\n", path);
-               udev_list_entry_add(udev_device->udev, &udev_device->sysattr_list, sysattr, NULL, 0, 0);
+               udev_list_entry_add(udev_device->udev, &udev_device->sysattr_value_list, sysattr, NULL, 0, 0);
                goto out;
        }
 
@@ -1153,7 +1240,7 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const
                if (pos != NULL) {
                        pos = &pos[1];
                        dbg(udev_device->udev, "cache '%s' with link value '%s'\n", sysattr, pos);
-                       list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_list, sysattr, pos, 0, 0);
+                       list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_value_list, sysattr, pos, 0, 0);
                        val = udev_list_entry_get_value(list_entry);
                }
 
@@ -1185,12 +1272,78 @@ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const
        value[size] = '\0';
        util_remove_trailing_chars(value, '\n');
        dbg(udev_device->udev, "'%s' has attribute value '%s'\n", path, value);
-       list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_list, sysattr, value, 0, 0);
+       list_entry = udev_list_entry_add(udev_device->udev, &udev_device->sysattr_value_list, sysattr, value, 0, 0);
        val = udev_list_entry_get_value(list_entry);
 out:
        return val;
 }
 
+static int udev_device_sysattr_list_read(struct udev_device *udev_device)
+{
+       struct dirent *dent;
+       DIR *dir;
+       int num = 0;
+
+       if (udev_device == NULL)
+               return -1;
+       if (udev_device->sysattr_list_read)
+               return 0;
+
+       dir = opendir(udev_device_get_syspath(udev_device));
+       if (!dir) {
+               dbg(udev_device->udev, "sysfs dir '%s' can not be opened\n",
+                               udev_device_get_syspath(udev_device));
+               return -1;
+       }
+
+       for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+               char path[UTIL_PATH_SIZE];
+               struct stat statbuf;
+
+               /* only handle symlinks and regular files */
+               if (dent->d_type != DT_LNK && dent->d_type != DT_REG)
+                       continue;
+
+               util_strscpyl(path, sizeof(path), udev_device_get_syspath(udev_device), "/", dent->d_name, NULL);
+               if (lstat(path, &statbuf) != 0)
+                       continue;
+               if ((statbuf.st_mode & S_IRUSR) == 0)
+                       continue;
+
+               udev_list_entry_add(udev_device->udev, &udev_device->sysattr_list,
+                                   dent->d_name, NULL, 0, 0);
+               num++;
+       }
+
+       closedir(dir);
+       dbg(udev_device->udev, "found %d sysattrs for '%s'\n", num, udev_device_get_syspath(udev_device));
+       udev_device->sysattr_list_read = true;
+
+       return num;
+}
+
+/**
+ * udev_device_get_sysattr_list_entry:
+ * @udev_device: udev device
+ *
+ * Retrieve the list of available sysattrs, with value being empty;
+ * This just return all available sysfs attributes for a particular
+ * device without reading their values.
+ *
+ * Returns: the first entry of the property list
+ **/
+struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device)
+{
+       if (!udev_device->sysattr_list_read) {
+               int ret;
+               ret = udev_device_sysattr_list_read(udev_device);
+               if (0 > ret)
+                       return NULL;
+       }
+
+       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;
@@ -1255,8 +1408,6 @@ int udev_device_set_devnode(struct udev_device *udev_device, const char *devnode
 {
        free(udev_device->devnode);
        udev_device->devnode = strdup(devnode);
-       if (devnode == NULL)
-               return 0;
        if (udev_device->devnode == NULL)
                return -ENOMEM;
        udev_device_add_property(udev_device, "DEVNAME", udev_device->devnode);
@@ -1276,6 +1427,65 @@ int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink
        return 0;
 }
 
+const char *udev_device_get_id_filename(struct udev_device *udev_device)
+{
+       if (udev_device->id_filename == NULL) {
+               if (udev_device_get_subsystem(udev_device) == NULL)
+                       return NULL;
+
+               if (major(udev_device_get_devnum(udev_device)) > 0) {
+                       /* use dev_t -- b259:131072, c254:0 */
+                       if (asprintf(&udev_device->id_filename, "%c%u:%u",
+                                    strcmp(udev_device_get_subsystem(udev_device), "block") == 0 ? 'b' : 'c',
+                                    major(udev_device_get_devnum(udev_device)),
+                                    minor(udev_device_get_devnum(udev_device))) < 0)
+                               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)
+                               udev_device->id_filename = NULL;
+               } else {
+                       /*
+                        * use $subsys:$syname -- pci:0000:00:1f.2
+                        * sysname() has '!' translated, get it from devpath
+                        */
+                       const char *sysname;
+                       sysname = strrchr(udev_device->devpath, '/');
+                       if (sysname == NULL)
+                               return NULL;
+                       sysname = &sysname[1];
+                       if (asprintf(&udev_device->id_filename, "+%s:%s", udev_device_get_subsystem(udev_device), sysname) < 0)
+                               udev_device->id_filename = NULL;
+               }
+       }
+       return udev_device->id_filename;
+}
+
+/**
+ * udev_device_get_is_initialized:
+ * @udev_device: udev device
+ *
+ * Check if udev has already handled the device and has set up
+ * device node permissions and context, or has renamed a network
+ * device.
+ *
+ * This is only implemented for devices with a device node
+ * or network interfaces. All other devices return 1 here.
+ *
+ * Returns: 1 if the device is set up. 0 otherwise.
+ **/
+int udev_device_get_is_initialized(struct udev_device *udev_device)
+{
+       if (!udev_device->info_loaded)
+               udev_device_read_db(udev_device, NULL);
+       return udev_device->is_initialized;
+}
+
+void udev_device_set_is_initialized(struct udev_device *udev_device)
+{
+       udev_device->is_initialized = true;
+}
+
 int udev_device_add_tag(struct udev_device *udev_device, const char *tag)
 {
        if (strchr(tag, ':') != NULL || strchr(tag, ' ') != NULL)
@@ -1315,7 +1525,7 @@ int udev_device_has_tag(struct udev_device *udev_device, const char *tag)
        struct udev_list_entry *list_entry;
 
        if (!udev_device->info_loaded)
-               udev_device_read_db(udev_device);
+               udev_device_read_db(udev_device, NULL);
        list_entry = udev_device_get_tags_list_entry(udev_device);
        list_entry =  udev_list_entry_get_by_name(list_entry, tag);
        if (list_entry != NULL)
@@ -1466,7 +1676,9 @@ int udev_device_set_knodename(struct udev_device *udev_device, const char *knode
        udev_device->knodename = strdup(knodename);
        if (udev_device->knodename == NULL)
                return -ENOMEM;
-       udev_device_add_property(udev_device, "DEVNAME", udev_device->knodename);
+       /* do not overwrite the udev property with the kernel property */
+       if (udev_device->devnode == NULL)
+               udev_device_add_property(udev_device, "DEVNAME", udev_device->knodename);
        return 0;
 }
 
@@ -1477,19 +1689,11 @@ int udev_device_get_timeout(struct udev_device *udev_device)
 
 int udev_device_set_timeout(struct udev_device *udev_device, int timeout)
 {
-       udev_device->timeout = timeout;
-       return 0;
-}
-int udev_device_get_event_timeout(struct udev_device *udev_device)
-{
-       if (!udev_device->info_loaded)
-               udev_device_read_db(udev_device);
-       return udev_device->event_timeout;
-}
+       char num[32];
 
-int udev_device_set_event_timeout(struct udev_device *udev_device, int event_timeout)
-{
-       udev_device->event_timeout = event_timeout;
+       udev_device->timeout = timeout;
+       snprintf(num, sizeof(num), "%u", timeout);
+       udev_device_add_property(udev_device, "TIMEOUT", num);
        return 0;
 }
 
@@ -1519,7 +1723,7 @@ int udev_device_set_devnum(struct udev_device *udev_device, dev_t devnum)
 int udev_device_get_devlink_priority(struct udev_device *udev_device)
 {
        if (!udev_device->info_loaded)
-               udev_device_read_db(udev_device);
+               udev_device_read_db(udev_device, NULL);
        return udev_device->devlink_priority;
 }
 
@@ -1532,7 +1736,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);
+               udev_device_read_db(udev_device, NULL);
        return udev_device->watch_handle;
 }
 
@@ -1544,11 +1748,27 @@ int udev_device_set_watch_handle(struct udev_device *udev_device, int handle)
 
 int udev_device_get_ifindex(struct udev_device *udev_device)
 {
+       if (!udev_device->info_loaded)
+               udev_device_read_uevent_file(udev_device);
        return udev_device->ifindex;
 }
 
 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);
        return 0;
 }
+
+bool udev_device_get_db_persist(struct udev_device *udev_device)
+{
+       return udev_device->db_persist;
+}
+
+void udev_device_set_db_persist(struct udev_device *udev_device)
+{
+       udev_device->db_persist = true;
+}