| /* |
| * libudev - interface to udev device information |
| * |
| * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org> |
| * Copyright (C) 2009 Alan Jenkins <alan-jenkins@tuffmail.co.uk> |
| * |
| * 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. |
| */ |
| |
| /* |
| * DISCLAIMER - The file format mentioned here is private to udev/libudev, |
| * and may be changed without notice. |
| * |
| * The udev event queue is exported as a binary log file. |
| * Each log record consists of a sequence number followed by the device path. |
| * |
| * When a new event is queued, its details are appended to the log. |
| * When the event finishes, a second record is appended to the log |
| * with the same sequence number but a devpath len of 0. |
| * |
| * Example: |
| * { 0x0000000000000001 } |
| * { 0x0000000000000001, 0x0019, "/devices/virtual/mem/null" }, |
| * { 0x0000000000000002, 0x001b, "/devices/virtual/mem/random" }, |
| * { 0x0000000000000001, 0x0000 }, |
| * { 0x0000000000000003, 0x0019, "/devices/virtual/mem/zero" }, |
| * |
| * Events 2 and 3 are still queued, but event 1 has finished. |
| * |
| * The queue does not grow indefinitely. It is periodically re-created |
| * to remove finished events. Atomic rename() makes this transparent to readers. |
| * |
| * The queue file starts with a single sequence number which specifies the |
| * minimum sequence number in the log that follows. Any events prior to this |
| * sequence number have already finished. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <limits.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include "libudev.h" |
| #include "libudev-private.h" |
| |
| static int rebuild_queue_file(struct udev_queue_export *udev_queue_export); |
| |
| struct udev_queue_export { |
| struct udev *udev; |
| int failed_count; /* number of failed events exported */ |
| int queued_count; /* number of unfinished events exported in queue file */ |
| FILE *queue_file; |
| unsigned long long int seqnum_max; /* earliest sequence number in queue file */ |
| unsigned long long int seqnum_min; /* latest sequence number in queue file */ |
| int waste_bytes; /* queue file bytes wasted on finished events */ |
| }; |
| |
| struct udev_queue_export *udev_queue_export_new(struct udev *udev) |
| { |
| struct udev_queue_export *udev_queue_export; |
| unsigned long long int initial_seqnum; |
| |
| if (udev == NULL) |
| return NULL; |
| |
| udev_queue_export = calloc(1, sizeof(struct udev_queue_export)); |
| if (udev_queue_export == NULL) |
| return NULL; |
| udev_queue_export->udev = udev; |
| |
| initial_seqnum = udev_get_kernel_seqnum(udev); |
| udev_queue_export->seqnum_min = initial_seqnum; |
| udev_queue_export->seqnum_max = initial_seqnum; |
| |
| udev_queue_export_cleanup(udev_queue_export); |
| if (rebuild_queue_file(udev_queue_export) != 0) { |
| free(udev_queue_export); |
| return NULL; |
| } |
| |
| return udev_queue_export; |
| } |
| |
| void udev_queue_export_unref(struct udev_queue_export *udev_queue_export) |
| { |
| if (udev_queue_export == NULL) |
| return; |
| if (udev_queue_export->queue_file != NULL) |
| fclose(udev_queue_export->queue_file); |
| free(udev_queue_export); |
| } |
| |
| void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export) |
| { |
| char filename[UTIL_PATH_SIZE]; |
| |
| util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_queue_export->udev), "/.udev/queue.tmp", NULL); |
| unlink(filename); |
| |
| util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_queue_export->udev), "/.udev/queue.bin", NULL); |
| unlink(filename); |
| } |
| |
| static int skip_to(FILE *file, long offset) |
| { |
| long old_offset; |
| |
| /* fseek may drop buffered data, avoid it for small seeks */ |
| old_offset = ftell(file); |
| if (offset > old_offset && offset - old_offset <= BUFSIZ) { |
| size_t skip_bytes = offset - old_offset; |
| char buf[skip_bytes]; |
| |
| if (fread(buf, skip_bytes, 1, file) != skip_bytes) |
| return -1; |
| } |
| |
| return fseek(file, offset, SEEK_SET); |
| } |
| |
| struct queue_devpaths { |
| unsigned int devpaths_first; /* index of first queued event */ |
| unsigned int devpaths_size; |
| long devpaths[]; /* seqnum -> offset of devpath in queue file (or 0) */ |
| }; |
| |
| /* |
| * Returns a table mapping seqnum to devpath file offset for currently queued events. |
| * devpaths[i] represents the event with seqnum = i + udev_queue_export->seqnum_min. |
| */ |
| static struct queue_devpaths *build_index(struct udev_queue_export *udev_queue_export) |
| { |
| struct queue_devpaths *devpaths; |
| unsigned long long int range; |
| long devpath_offset; |
| ssize_t devpath_len; |
| unsigned long long int seqnum; |
| unsigned long long int n; |
| unsigned int i; |
| |
| /* seek to the first event in the file */ |
| rewind(udev_queue_export->queue_file); |
| udev_queue_read_seqnum(udev_queue_export->queue_file, &seqnum); |
| |
| /* allocate the table */ |
| range = udev_queue_export->seqnum_min - udev_queue_export->seqnum_max; |
| if (range - 1 > INT_MAX) { |
| err(udev_queue_export->udev, "queue file overflow\n"); |
| return NULL; |
| } |
| devpaths = calloc(1, sizeof(struct queue_devpaths) + (range + 1) * sizeof(long)); |
| if (devpaths == NULL) |
| return NULL; |
| devpaths->devpaths_size = range + 1; |
| |
| /* read all records and populate the table */ |
| while(1) { |
| if (udev_queue_read_seqnum(udev_queue_export->queue_file, &seqnum) < 0) |
| break; |
| n = seqnum - udev_queue_export->seqnum_max; |
| if (n >= devpaths->devpaths_size) |
| goto read_error; |
| |
| devpath_offset = ftell(udev_queue_export->queue_file); |
| devpath_len = udev_queue_skip_devpath(udev_queue_export->queue_file); |
| if (devpath_len < 0) |
| goto read_error; |
| |
| if (devpath_len > 0) |
| devpaths->devpaths[n] = devpath_offset; |
| else |
| devpaths->devpaths[n] = 0; |
| } |
| |
| /* find first queued event */ |
| for (i = 0; i < devpaths->devpaths_size; i++) { |
| if (devpaths->devpaths[i] != 0) |
| break; |
| } |
| devpaths->devpaths_first = i; |
| |
| return devpaths; |
| |
| read_error: |
| err(udev_queue_export->udev, "queue file corrupted\n"); |
| free(devpaths); |
| return NULL; |
| } |
| |
| static int rebuild_queue_file(struct udev_queue_export *udev_queue_export) |
| { |
| unsigned long long int seqnum; |
| struct queue_devpaths *devpaths = NULL; |
| char filename[UTIL_PATH_SIZE]; |
| char filename_tmp[UTIL_PATH_SIZE]; |
| FILE *new_queue_file = NULL; |
| unsigned int i; |
| |
| /* read old queue file */ |
| if (udev_queue_export->queue_file != NULL) { |
| dbg(udev_queue_export->udev, "compacting queue file, freeing %d bytes\n", |
| udev_queue_export->waste_bytes); |
| |
| devpaths = build_index(udev_queue_export); |
| if (devpaths != NULL) |
| udev_queue_export->seqnum_max += devpaths->devpaths_first; |
| } |
| if (devpaths == NULL) { |
| dbg(udev_queue_export->udev, "creating empty queue file\n"); |
| udev_queue_export->queued_count = 0; |
| udev_queue_export->seqnum_max = udev_queue_export->seqnum_min; |
| } |
| |
| /* create new queue file */ |
| util_strscpyl(filename_tmp, sizeof(filename_tmp), udev_get_dev_path(udev_queue_export->udev), "/.udev/queue.tmp", NULL); |
| new_queue_file = fopen(filename_tmp, "w+"); |
| if (new_queue_file == NULL) |
| goto error; |
| seqnum = udev_queue_export->seqnum_max; |
| fwrite(&seqnum, 1, sizeof(unsigned long long int), new_queue_file); |
| |
| /* copy unfinished events only to the new file */ |
| if (devpaths != NULL) { |
| for (i = devpaths->devpaths_first; i < devpaths->devpaths_size; i++) { |
| char devpath[UTIL_PATH_SIZE]; |
| int err; |
| unsigned short devpath_len; |
| |
| if (devpaths->devpaths[i] != 0) |
| { |
| skip_to(udev_queue_export->queue_file, devpaths->devpaths[i]); |
| err = udev_queue_read_devpath(udev_queue_export->queue_file, devpath, sizeof(devpath)); |
| devpath_len = err; |
| |
| fwrite(&seqnum, sizeof(unsigned long long int), 1, new_queue_file); |
| fwrite(&devpath_len, sizeof(unsigned short), 1, new_queue_file); |
| fwrite(devpath, 1, devpath_len, new_queue_file); |
| } |
| seqnum++; |
| } |
| free(devpaths); |
| devpaths = NULL; |
| } |
| fflush(new_queue_file); |
| if (ferror(new_queue_file)) |
| goto error; |
| |
| /* rename the new file on top of the old one */ |
| util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev_queue_export->udev), "/.udev/queue.bin", NULL); |
| if (rename(filename_tmp, filename) != 0) |
| goto error; |
| |
| if (udev_queue_export->queue_file != NULL) |
| fclose(udev_queue_export->queue_file); |
| udev_queue_export->queue_file = new_queue_file; |
| udev_queue_export->waste_bytes = 0; |
| |
| return 0; |
| |
| error: |
| err(udev_queue_export->udev, "failed to create queue file: %m\n"); |
| udev_queue_export_cleanup(udev_queue_export); |
| |
| if (udev_queue_export->queue_file != NULL) { |
| fclose(udev_queue_export->queue_file); |
| udev_queue_export->queue_file = NULL; |
| } |
| if (new_queue_file != NULL) |
| fclose(new_queue_file); |
| |
| if (devpaths != NULL) |
| free(devpaths); |
| udev_queue_export->queued_count = 0; |
| udev_queue_export->waste_bytes = 0; |
| udev_queue_export->seqnum_max = udev_queue_export->seqnum_min; |
| |
| return -1; |
| } |
| |
| static int write_queue_record(struct udev_queue_export *udev_queue_export, |
| unsigned long long int seqnum, const char *devpath, size_t devpath_len) |
| { |
| unsigned short len; |
| |
| if (udev_queue_export->queue_file == NULL) { |
| dbg(udev_queue_export->udev, "can't record event: queue file not available\n"); |
| return -1; |
| } |
| |
| if (fwrite(&seqnum, sizeof(unsigned long long int), 1, udev_queue_export->queue_file) != 1) |
| goto write_error; |
| |
| len = (devpath_len < USHRT_MAX) ? devpath_len : USHRT_MAX; |
| if (fwrite(&len, sizeof(unsigned short), 1, udev_queue_export->queue_file) != 1) |
| goto write_error; |
| if (fwrite(devpath, 1, len, udev_queue_export->queue_file) != len) |
| goto write_error; |
| |
| /* *must* flush output; caller may fork */ |
| if (fflush(udev_queue_export->queue_file) != 0) |
| goto write_error; |
| |
| return 0; |
| |
| write_error: |
| /* if we failed half way through writing a record to a file, |
| we should not try to write any further records to it. */ |
| err(udev_queue_export->udev, "error writing to queue file: %m\n"); |
| fclose(udev_queue_export->queue_file); |
| udev_queue_export->queue_file = NULL; |
| |
| return -1; |
| } |
| |
| enum device_state { |
| DEVICE_QUEUED, |
| DEVICE_FINISHED, |
| DEVICE_FAILED, |
| }; |
| |
| static inline size_t queue_record_size(size_t devpath_len) |
| { |
| return sizeof(unsigned long long int) + sizeof(unsigned short int) + devpath_len; |
| } |
| |
| static int update_queue(struct udev_queue_export *udev_queue_export, |
| struct udev_device *udev_device, enum device_state state) |
| { |
| unsigned long long int seqnum = udev_device_get_seqnum(udev_device); |
| const char *devpath = NULL; |
| size_t devpath_len = 0; |
| int bytes; |
| int err; |
| |
| if (state == DEVICE_QUEUED) { |
| devpath = udev_device_get_devpath(udev_device); |
| devpath_len = strlen(devpath); |
| } |
| |
| /* recover from an earlier failed rebuild */ |
| if (udev_queue_export->queue_file == NULL) { |
| if (rebuild_queue_file(udev_queue_export) != 0) |
| return -1; |
| } |
| |
| /* when the queue file grows too large, garbage-collect and rebuild it */ |
| bytes = ftell(udev_queue_export->queue_file) + queue_record_size(devpath_len); |
| |
| /* if we're removing the last event from the queue, that's the best time to rebuild it */ |
| if (state != DEVICE_QUEUED && udev_queue_export->queued_count == 1 && bytes > 2048) { |
| /* because we don't need to read the old queue file */ |
| fclose(udev_queue_export->queue_file); |
| udev_queue_export->queue_file = NULL; |
| rebuild_queue_file(udev_queue_export); |
| return 0; |
| } |
| |
| /* try to rebuild the queue files before they grow larger than one page. */ |
| if ((udev_queue_export->waste_bytes > bytes / 2) && bytes > 4096) |
| rebuild_queue_file(udev_queue_export); |
| |
| /* don't record a finished event, if we already dropped the event in a failed rebuild */ |
| if (seqnum < udev_queue_export->seqnum_max) |
| return 0; |
| |
| /* now write to the queue */ |
| if (state == DEVICE_QUEUED) { |
| udev_queue_export->queued_count++; |
| udev_queue_export->seqnum_min = seqnum; |
| } else { |
| udev_queue_export->waste_bytes += queue_record_size(devpath_len) + queue_record_size(0); |
| udev_queue_export->queued_count--; |
| } |
| err = write_queue_record(udev_queue_export, seqnum, devpath, devpath_len); |
| |
| /* try to handle ENOSPC */ |
| if (err != 0 && udev_queue_export->queued_count == 0) { |
| udev_queue_export_cleanup(udev_queue_export); |
| err = rebuild_queue_file(udev_queue_export); |
| } |
| |
| return err; |
| } |
| |
| static void update_failed(struct udev_queue_export *udev_queue_export, |
| struct udev_device *udev_device, enum device_state state) |
| { |
| struct udev *udev = udev_device_get_udev(udev_device); |
| char filename[UTIL_PATH_SIZE]; |
| |
| if (state != DEVICE_FAILED && udev_queue_export->failed_count == 0) |
| return; |
| |
| /* location of failed file */ |
| util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/failed/", |
| udev_device_get_subsystem(udev_device), ":", udev_device_get_sysname(udev_device), NULL); |
| |
| switch (state) { |
| case DEVICE_FAILED: |
| /* record event in the failed directory */ |
| udev_queue_export->failed_count++; |
| util_create_path(udev, filename); |
| udev_selinux_setfscreatecon(udev, filename, S_IFLNK); |
| symlink(udev_device_get_devpath(udev_device), filename); |
| udev_selinux_resetfscreatecon(udev); |
| break; |
| |
| case DEVICE_QUEUED: |
| /* delete failed file */ |
| if (unlink(filename) == 0) { |
| util_delete_path(udev, filename); |
| udev_queue_export->failed_count--; |
| } |
| break; |
| |
| case DEVICE_FINISHED: |
| break; |
| } |
| |
| return; |
| } |
| |
| static int update(struct udev_queue_export *udev_queue_export, |
| struct udev_device *udev_device, enum device_state state) |
| { |
| update_failed(udev_queue_export, udev_device, state); |
| |
| if (update_queue(udev_queue_export, udev_device, state) != 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device) |
| { |
| return update(udev_queue_export, udev_device, DEVICE_QUEUED); |
| } |
| |
| int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device) |
| { |
| return update(udev_queue_export, udev_device, DEVICE_FINISHED); |
| } |
| |
| int udev_queue_export_device_failed(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device) |
| { |
| return update(udev_queue_export, udev_device, DEVICE_FAILED); |
| } |