/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include #include "sd-daemon.h" #include "sd-device.h" #include "alloc-util.h" #include "device-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" #include "io-util.h" #include "list.h" #include "main-func.h" #include "mkdir.h" #include "parse-util.h" #include "reboot-util.h" #include "string-table.h" #include "string-util.h" #include "udev-util.h" /* Note that any write is delayed until exit and the rfkill state will not be * stored for rfkill indices that disappear after a change. */ #define EXIT_USEC (5 * USEC_PER_SEC) typedef struct write_queue_item { LIST_FIELDS(struct write_queue_item, queue); int rfkill_idx; char *file; int state; } write_queue_item; typedef struct Context { LIST_HEAD(write_queue_item, write_queue); int rfkill_fd; } Context; static struct write_queue_item* write_queue_item_free(struct write_queue_item *item) { if (!item) return NULL; free(item->file); return mfree(item); } static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = { [RFKILL_TYPE_ALL] = "all", [RFKILL_TYPE_WLAN] = "wlan", [RFKILL_TYPE_BLUETOOTH] = "bluetooth", [RFKILL_TYPE_UWB] = "uwb", [RFKILL_TYPE_WIMAX] = "wimax", [RFKILL_TYPE_WWAN] = "wwan", [RFKILL_TYPE_GPS] = "gps", [RFKILL_TYPE_FM] = "fm", [RFKILL_TYPE_NFC] = "nfc", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int); static int find_device( const struct rfkill_event *event, sd_device **ret) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; _cleanup_free_ char *sysname = NULL; const char *name; int r; assert(event); assert(ret); if (asprintf(&sysname, "rfkill%u", event->idx) < 0) return log_oom(); r = sd_device_new_from_subsystem_sysname(&device, "rfkill", sysname); if (r < 0) return log_full_errno(ERRNO_IS_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to open device '%s': %m", sysname); r = sd_device_get_sysattr_value(device, "name", &name); if (r < 0) return log_device_debug_errno(device, r, "Device has no name, ignoring: %m"); log_device_debug(device, "Operating on rfkill device '%s'.", name); *ret = TAKE_PTR(device); return 0; } static int determine_state_file( const struct rfkill_event *event, char **ret) { _cleanup_(sd_device_unrefp) sd_device *d = NULL, *device = NULL; const char *path_id, *type; char *state_file; int r; assert(event); assert(ret); r = find_device(event, &d); if (r < 0) return r; r = device_wait_for_initialization(d, "rfkill", USEC_INFINITY, &device); if (r < 0) return r; assert_se(type = rfkill_type_to_string(event->type)); if (sd_device_get_property_value(device, "ID_PATH", &path_id) >= 0) { _cleanup_free_ char *escaped_path_id = NULL; escaped_path_id = cescape(path_id); if (!escaped_path_id) return log_oom(); state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type); } else state_file = strjoin("/var/lib/systemd/rfkill/", type); if (!state_file) return log_oom(); *ret = state_file; return 0; } static int load_state(Context *c, const struct rfkill_event *event) { _cleanup_free_ char *state_file = NULL, *value = NULL; int b, r; assert(c); assert(c->rfkill_fd >= 0); assert(event); if (shall_restore_state() == 0) return 0; r = determine_state_file(event, &state_file); if (r < 0) return r; r = read_one_line_file(state_file, &value); if (IN_SET(r, -ENOENT, 0)) { /* No state file or it's truncated? Then save the current state */ r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); if (r < 0) return log_error_errno(r, "Failed to write state file %s: %m", state_file); log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); return 0; } if (r < 0) return log_error_errno(r, "Failed to read state file %s: %m", state_file); b = parse_boolean(value); if (b < 0) return log_error_errno(b, "Failed to parse state file %s: %m", state_file); struct rfkill_event we = { .idx = event->idx, .op = RFKILL_OP_CHANGE, .soft = b, }; assert_cc(offsetof(struct rfkill_event, op) < RFKILL_EVENT_SIZE_V1); assert_cc(offsetof(struct rfkill_event, soft) < RFKILL_EVENT_SIZE_V1); ssize_t l = write(c->rfkill_fd, &we, sizeof we); if (l < 0) return log_error_errno(errno, "Failed to restore rfkill state for %u: %m", event->idx); if ((size_t)l < RFKILL_EVENT_SIZE_V1) /* l cannot be < 0 here. Cast to fix -Werror=sign-compare */ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Couldn't write rfkill event structure, too short (wrote %zd of %zu bytes).", l, sizeof we); log_debug("Writing struct rfkill_event successful (%zd of %zu bytes).", l, sizeof we); log_debug("Loaded state '%s' from %s.", one_zero(b), state_file); return 0; } static void save_state_queue_remove(Context *c, int idx, const char *state_file) { assert(c); LIST_FOREACH(queue, item, c->write_queue) if ((state_file && streq(item->file, state_file)) || idx == item->rfkill_idx) { log_debug("Canceled previous save state of '%s' to %s.", one_zero(item->state), item->file); LIST_REMOVE(queue, c->write_queue, item); write_queue_item_free(item); } } static int save_state_queue(Context *c, const struct rfkill_event *event) { _cleanup_free_ char *state_file = NULL; struct write_queue_item *item; int r; assert(c); assert(c->rfkill_fd >= 0); assert(event); r = determine_state_file(event, &state_file); if (r < 0) return r; save_state_queue_remove(c, event->idx, state_file); item = new0(struct write_queue_item, 1); if (!item) return -ENOMEM; item->file = TAKE_PTR(state_file); item->rfkill_idx = event->idx; item->state = event->soft; LIST_APPEND(queue, c->write_queue, item); return 0; } static int save_state_cancel(Context *c, const struct rfkill_event *event) { _cleanup_free_ char *state_file = NULL; int r; assert(c); assert(c->rfkill_fd >= 0); assert(event); r = determine_state_file(event, &state_file); save_state_queue_remove(c, event->idx, state_file); if (r < 0) return r; return 0; } static int save_state_write_one(struct write_queue_item *item) { int r; r = write_string_file(item->file, one_zero(item->state), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); if (r < 0) return log_error_errno(r, "Failed to write state file %s: %m", item->file); log_debug("Saved state '%s' to %s.", one_zero(item->state), item->file); return 0; } static void context_save_and_clear(Context *c) { struct write_queue_item *i; assert(c); while ((i = c->write_queue)) { LIST_REMOVE(queue, c->write_queue, i); (void) save_state_write_one(i); write_queue_item_free(i); } safe_close(c->rfkill_fd); } static int run(int argc, char *argv[]) { _cleanup_(context_save_and_clear) Context c = { .rfkill_fd = -EBADF }; bool ready = false; int r, n; if (argc > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires no arguments."); log_setup(); umask(0022); n = sd_listen_fds(false); if (n < 0) return log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m"); if (n > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got too many file descriptors."); if (n == 0) { c.rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (c.rfkill_fd < 0) { if (errno == ENOENT) { log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting."); return 0; } return log_error_errno(errno, "Failed to open /dev/rfkill: %m"); } } else { c.rfkill_fd = SD_LISTEN_FDS_START; r = fd_nonblock(c.rfkill_fd, 1); if (r < 0) return log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m"); } for (;;) { struct rfkill_event event = {}; ssize_t l = read(c.rfkill_fd, &event, sizeof event); if (l < 0) { if (errno != EAGAIN) return log_error_errno(errno, "Failed to read from /dev/rfkill: %m"); if (!ready) { /* Notify manager that we are now finished with processing whatever was * queued */ r = sd_notify(false, "READY=1"); if (r < 0) log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); ready = true; } /* Hang around for a bit, maybe there's more coming */ r = fd_wait_for_event(c.rfkill_fd, POLLIN, EXIT_USEC); if (r == -EINTR) continue; if (r < 0) return log_error_errno(r, "Failed to poll() on device: %m"); if (r > 0) continue; log_debug("All events read and idle, exiting."); break; } if ((size_t)l < RFKILL_EVENT_SIZE_V1) /* l cannot be < 0 here. Cast to fix -Werror=sign-compare */ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read of struct rfkill_event: (%zd < %zu)", l, (size_t) RFKILL_EVENT_SIZE_V1); /* Casting necessary to make compiling with different kernel versions happy */ log_debug("Reading struct rfkill_event: got %zd bytes.", l); /* The event structure has more fields. We only care about the first few, so it's OK if we * don't read the full structure. */ assert_cc(offsetof(struct rfkill_event, op) < RFKILL_EVENT_SIZE_V1); assert_cc(offsetof(struct rfkill_event, type) < RFKILL_EVENT_SIZE_V1); const char *type = rfkill_type_to_string(event.type); if (!type) { log_debug("An rfkill device of unknown type %u discovered, ignoring.", event.type); continue; } switch (event.op) { case RFKILL_OP_ADD: log_debug("A new rfkill device has been added with index %u and type %s.", event.idx, type); (void) load_state(&c, &event); break; case RFKILL_OP_DEL: log_debug("An rfkill device has been removed with index %u and type %s", event.idx, type); (void) save_state_cancel(&c, &event); break; case RFKILL_OP_CHANGE: log_debug("An rfkill device has changed state with index %u and type %s", event.idx, type); (void) save_state_queue(&c, &event); break; default: log_debug("Unknown event %u from /dev/rfkill for index %u and type %s, ignoring.", event.op, event.idx, type); break; } } return 0; } DEFINE_MAIN_FUNCTION(run);