/* * libudev - interface to udev device information * * Copyright (C) 2008 Kay Sievers * Copyright (C) 2009 Alan Jenkins * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include "libudev.h" #include "libudev-private.h" /** * SECTION:libudev-queue * @short_description: access to currently active events * * The udev daemon processes events asynchronously. All events which do not have * interdependencies run in parallel. This exports the current state of the * event processing queue, and the current event sequence numbers from the kernel * and the udev daemon. */ /** * udev_queue: * * Opaque object representing the current event queue in the udev daemon. */ struct udev_queue { struct udev *udev; int refcount; struct udev_list_node queue_list; struct udev_list_node failed_list; }; /** * udev_queue_new: * @udev: udev library context * * The initial refcount is 1, and needs to be decremented to * release the resources of the udev queue context. * * Returns: the udev queue context, or #NULL on error. **/ struct udev_queue *udev_queue_new(struct udev *udev) { struct udev_queue *udev_queue; if (udev == NULL) return NULL; udev_queue = calloc(1, sizeof(struct udev_queue)); if (udev_queue == NULL) return NULL; udev_queue->refcount = 1; udev_queue->udev = udev; udev_list_init(&udev_queue->queue_list); udev_list_init(&udev_queue->failed_list); return udev_queue; } /** * udev_queue_ref: * @udev_queue: udev queue context * * Take a reference of a udev queue context. * * Returns: the same udev queue context. **/ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) { if (udev_queue == NULL) return NULL; udev_queue->refcount++; return udev_queue; } /** * udev_queue_unref: * @udev_queue: udev queue context * * Drop a reference of a udev queue context. If the refcount reaches zero, * the resources of the queue context will be released. **/ void udev_queue_unref(struct udev_queue *udev_queue) { if (udev_queue == NULL) return; udev_queue->refcount--; if (udev_queue->refcount > 0) return; udev_list_cleanup_entries(udev_queue->udev, &udev_queue->queue_list); udev_list_cleanup_entries(udev_queue->udev, &udev_queue->failed_list); free(udev_queue); } /** * udev_queue_get_udev: * @udev_queue: udev queue context * * Retrieve the udev library context the queue context was created with. * * Returns: the udev library context. **/ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) { if (udev_queue == NULL) return NULL; return udev_queue->udev; } unsigned long long int udev_get_kernel_seqnum(struct udev *udev) { char filename[UTIL_PATH_SIZE]; unsigned long long int seqnum; int fd; char buf[32]; ssize_t len; util_strscpyl(filename, sizeof(filename), udev_get_sys_path(udev), "/kernel/uevent_seqnum", NULL); fd = open(filename, O_RDONLY|O_CLOEXEC); if (fd < 0) return 0; len = read(fd, buf, sizeof(buf)); close(fd); if (len <= 2) return 0; buf[len-1] = '\0'; seqnum = strtoull(buf, NULL, 10); return seqnum; } /** * udev_queue_get_kernel_seqnum: * @udev_queue: udev queue context * * Returns: the current kernel event sequence number. **/ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) { unsigned long long int seqnum; if (udev_queue == NULL) return -EINVAL; seqnum = udev_get_kernel_seqnum(udev_queue->udev); dbg(udev_queue->udev, "seqnum=%llu\n", seqnum); return seqnum; } int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum) { if (fread(seqnum, sizeof(unsigned long long int), 1, queue_file) != 1) return -1; return 0; } ssize_t udev_queue_skip_devpath(FILE *queue_file) { unsigned short int len; if (fread(&len, sizeof(unsigned short int), 1, queue_file) == 1) { char devpath[len]; /* use fread to skip, fseek might drop buffered data */ if (fread(devpath, 1, len, queue_file) == len) return len; } return -1; } ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size) { unsigned short int read_bytes = 0; unsigned short int len; if (fread(&len, sizeof(unsigned short int), 1, queue_file) != 1) return -1; read_bytes = (len < size - 1) ? len : size - 1; if (fread(devpath, 1, read_bytes, queue_file) != read_bytes) return -1; devpath[read_bytes] = '\0'; /* if devpath was too long, skip unread characters */ if (read_bytes != len) { unsigned short int skip_bytes = len - read_bytes; char buf[skip_bytes]; if (fread(buf, 1, skip_bytes, queue_file) != skip_bytes) return -1; } return read_bytes; } static FILE *open_queue_file(struct udev_queue *udev_queue, unsigned long long int *seqnum_start) { char filename[UTIL_PATH_SIZE]; FILE *queue_file; util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev_queue->udev), "/queue.bin", NULL); queue_file = fopen(filename, "re"); if (queue_file == NULL) return NULL; if (udev_queue_read_seqnum(queue_file, seqnum_start) < 0) { err(udev_queue->udev, "corrupt queue file\n"); fclose(queue_file); return NULL; } return queue_file; } /** * udev_queue_get_udev_seqnum: * @udev_queue: udev queue context * * Returns: the last known udev event sequence number. **/ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) { unsigned long long int seqnum_udev; FILE *queue_file; queue_file = open_queue_file(udev_queue, &seqnum_udev); if (queue_file == NULL) return 0; for (;;) { unsigned long long int seqnum; ssize_t devpath_len; if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) break; devpath_len = udev_queue_skip_devpath(queue_file); if (devpath_len < 0) break; if (devpath_len > 0) seqnum_udev = seqnum; } fclose(queue_file); return seqnum_udev; } /** * udev_queue_get_udev_is_active: * @udev_queue: udev queue context * * Returns: a flag indicating if udev is active. **/ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) { unsigned long long int seqnum_start; FILE *queue_file; queue_file = open_queue_file(udev_queue, &seqnum_start); if (queue_file == NULL) return 0; fclose(queue_file); return 1; } /** * udev_queue_get_queue_is_empty: * @udev_queue: udev queue context * * Returns: a flag indicating if udev is currently handling events. **/ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) { unsigned long long int seqnum_kernel; unsigned long long int seqnum_udev = 0; int queued = 0; int is_empty = 0; FILE *queue_file; if (udev_queue == NULL) return -EINVAL; queue_file = open_queue_file(udev_queue, &seqnum_udev); if (queue_file == NULL) return 1; for (;;) { unsigned long long int seqnum; ssize_t devpath_len; if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) break; devpath_len = udev_queue_skip_devpath(queue_file); if (devpath_len < 0) break; if (devpath_len > 0) { queued++; seqnum_udev = seqnum; } else { queued--; } } if (queued > 0) { dbg(udev_queue->udev, "queue is not empty\n"); goto out; } seqnum_kernel = udev_queue_get_kernel_seqnum(udev_queue); if (seqnum_udev < seqnum_kernel) { dbg(udev_queue->udev, "queue is empty but kernel events still pending [%llu]<->[%llu]\n", seqnum_kernel, seqnum_udev); goto out; } dbg(udev_queue->udev, "queue is empty\n"); is_empty = 1; out: fclose(queue_file); return is_empty; } /** * udev_queue_get_seqnum_sequence_is_finished: * @udev_queue: udev queue context * @start: first event sequence number * @end: last event sequence number * * Returns: a flag indicating if any of the sequence numbers in the given range is currently active. **/ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, unsigned long long int start, unsigned long long int end) { unsigned long long int seqnum; ssize_t devpath_len; int unfinished; FILE *queue_file; if (udev_queue == NULL) return -EINVAL; queue_file = open_queue_file(udev_queue, &seqnum); if (queue_file == NULL) return 1; if (start < seqnum) start = seqnum; if (start > end) { fclose(queue_file); return 1; } if (end - start > INT_MAX - 1) { fclose(queue_file); return -EOVERFLOW; } /* * we might start with 0, and handle the initial seqnum * only when we find an entry in the queue file **/ unfinished = end - start; do { if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) break; devpath_len = udev_queue_skip_devpath(queue_file); if (devpath_len < 0) break; /* * we might start with an empty or re-build queue file, where * the initial seqnum is not recorded as finished */ if (start == seqnum && devpath_len > 0) unfinished++; if (devpath_len == 0) { if (seqnum >= start && seqnum <= end) unfinished--; } } while (unfinished > 0); fclose(queue_file); return (unfinished == 0); } /** * udev_queue_get_seqnum_is_finished: * @udev_queue: udev queue context * @seqnum: sequence number * * Returns: a flag indicating if the given sequence number is currently active. **/ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) { if (!udev_queue_get_seqnum_sequence_is_finished(udev_queue, seqnum, seqnum)) return 0; dbg(udev_queue->udev, "seqnum: %llu finished\n", seqnum); return 1; } /** * udev_queue_get_queued_list_entry: * @udev_queue: udev queue context * * Returns: the first entry of the list of queued events. **/ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) { unsigned long long int seqnum; FILE *queue_file; if (udev_queue == NULL) return NULL; udev_list_cleanup_entries(udev_queue->udev, &udev_queue->queue_list); queue_file = open_queue_file(udev_queue, &seqnum); if (queue_file == NULL) return NULL; for (;;) { char syspath[UTIL_PATH_SIZE]; char *s; size_t l; ssize_t len; char seqnum_str[32]; struct udev_list_entry *list_entry; if (udev_queue_read_seqnum(queue_file, &seqnum) < 0) break; snprintf(seqnum_str, sizeof(seqnum_str), "%llu", seqnum); s = syspath; l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL); len = udev_queue_read_devpath(queue_file, s, l); if (len < 0) break; if (len > 0) { udev_list_entry_add(udev_queue->udev, &udev_queue->queue_list, syspath, seqnum_str, 0); } else { udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_queue->queue_list)) { if (strcmp(seqnum_str, udev_list_entry_get_value(list_entry)) == 0) { udev_list_entry_delete(list_entry); break; } } } } fclose(queue_file); return udev_list_get_entry(&udev_queue->queue_list); } /** * udev_queue_get_failed_list_entry: * @udev_queue: udev queue context * * Returns: the first entry of the list of recorded failed events. **/ struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue) { char path[UTIL_PATH_SIZE]; DIR *dir; struct dirent *dent; if (udev_queue == NULL) return NULL; udev_list_cleanup_entries(udev_queue->udev, &udev_queue->failed_list); util_strscpyl(path, sizeof(path), udev_get_run_path(udev_queue->udev), "/failed", NULL); dir = opendir(path); if (dir == NULL) return NULL; for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { char filename[UTIL_PATH_SIZE]; char syspath[UTIL_PATH_SIZE]; char *s; size_t l; ssize_t len; struct stat statbuf; if (dent->d_name[0] == '.') continue; s = syspath; l = util_strpcpyl(&s, sizeof(syspath), udev_get_sys_path(udev_queue->udev), NULL); len = readlinkat(dirfd(dir), dent->d_name, s, l); if (len <= 0 || (size_t)len == l) continue; s[len] = '\0'; dbg(udev_queue->udev, "found '%s' [%s]\n", syspath, dent->d_name); util_strscpyl(filename, sizeof(filename), syspath, "/uevent", NULL); if (stat(filename, &statbuf) != 0) continue; udev_list_entry_add(udev_queue->udev, &udev_queue->failed_list, syspath, NULL, 0); } closedir(dir); return udev_list_get_entry(&udev_queue->failed_list); }