Systemd/src/rfkill/rfkill.c
Lennart Poettering 0c69794138 tree-wide: remove Lennart's copyright lines
These lines are generally out-of-date, incomplete and unnecessary. With
SPDX and git repository much more accurate and fine grained information
about licensing and authorship is available, hence let's drop the
per-file copyright notice. Of course, removing copyright lines of others
is problematic, hence this commit only removes my own lines and leaves
all others untouched. It might be nicer if sooner or later those could
go away too, making git the only and accurate source of authorship
information.
2018-06-14 10:20:20 +02:00

503 lines
16 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <linux/rfkill.h>
#include <poll.h>
#include "libudev.h"
#include "sd-daemon.h"
#include "alloc-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "mkdir.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "string-table.h"
#include "string-util.h"
#include "udev-util.h"
#include "util.h"
#include "list.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;
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(
struct udev *udev,
const struct rfkill_event *event,
struct udev_device **ret) {
_cleanup_free_ char *sysname = NULL;
struct udev_device *device;
const char *name;
assert(udev);
assert(event);
assert(ret);
if (asprintf(&sysname, "rfkill%i", event->idx) < 0)
return log_oom();
device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
if (!device)
return log_full_errno(IN_SET(errno, ENOENT, ENXIO, ENODEV) ? LOG_DEBUG : LOG_ERR, errno,
"Failed to open device '%s': %m", sysname);
name = udev_device_get_sysattr_value(device, "name");
if (!name) {
log_debug("Device has no name, ignoring.");
udev_device_unref(device);
return -ENOENT;
}
log_debug("Operating on rfkill device '%s'.", name);
*ret = device;
return 0;
}
static int wait_for_initialized(
struct udev *udev,
struct udev_device *device,
struct udev_device **ret) {
_cleanup_(udev_monitor_unrefp) struct udev_monitor *monitor = NULL;
struct udev_device *d;
const char *sysname;
int watch_fd, r;
assert(udev);
assert(device);
assert(ret);
if (udev_device_get_is_initialized(device) != 0) {
*ret = udev_device_ref(device);
return 0;
}
assert_se(sysname = udev_device_get_sysname(device));
/* Wait until the device is initialized, so that we can get
* access to the ID_PATH property */
monitor = udev_monitor_new_from_netlink(udev, "udev");
if (!monitor)
return log_error_errno(errno, "Failed to acquire monitor: %m");
r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL);
if (r < 0)
return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m");
r = udev_monitor_enable_receiving(monitor);
if (r < 0)
return log_error_errno(r, "Failed to enable udev receiving: %m");
watch_fd = udev_monitor_get_fd(monitor);
if (watch_fd < 0)
return log_error_errno(watch_fd, "Failed to get watch fd: %m");
/* Check again, maybe things changed */
d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
if (!d)
return log_full_errno(IN_SET(errno, ENOENT, ENXIO, ENODEV) ? LOG_DEBUG : LOG_ERR, errno,
"Failed to open device '%s': %m", sysname);
if (udev_device_get_is_initialized(d) != 0) {
*ret = d;
return 0;
}
for (;;) {
_cleanup_(udev_device_unrefp) struct udev_device *t = NULL;
r = fd_wait_for_event(watch_fd, POLLIN, EXIT_USEC);
if (r == -EINTR)
continue;
if (r < 0)
return log_error_errno(r, "Failed to watch udev monitor: %m");
if (r == 0) {
log_error("Timed out waiting for udev monitor.");
return -ETIMEDOUT;
}
t = udev_monitor_receive_device(monitor);
if (!t)
continue;
if (streq_ptr(udev_device_get_sysname(t), sysname)) {
*ret = udev_device_ref(t);
return 0;
}
}
}
static int determine_state_file(
struct udev *udev,
const struct rfkill_event *event,
char **ret) {
_cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
_cleanup_(udev_device_unrefp) struct udev_device *device = NULL;
const char *path_id, *type;
char *state_file;
int r;
assert(event);
assert(ret);
r = find_device(udev, event, &d);
if (r < 0)
return r;
r = wait_for_initialized(udev, d, &device);
if (r < 0)
return r;
assert_se(type = rfkill_type_to_string(event->type));
path_id = udev_device_get_property_value(device, "ID_PATH");
if (path_id) {
_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(
int rfkill_fd,
struct udev *udev,
const struct rfkill_event *event) {
_cleanup_free_ char *state_file = NULL, *value = NULL;
struct rfkill_event we;
ssize_t l;
int b, r;
assert(rfkill_fd >= 0);
assert(udev);
assert(event);
if (shall_restore_state() == 0)
return 0;
r = determine_state_file(udev, event, &state_file);
if (r < 0)
return r;
r = read_one_line_file(state_file, &value);
if (r == -ENOENT) {
/* No state file? Then save the current state */
r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
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);
we = (struct rfkill_event) {
.op = RFKILL_OP_CHANGE,
.idx = event->idx,
.soft = b,
};
l = write(rfkill_fd, &we, sizeof(we));
if (l < 0)
return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx);
if (l != sizeof(we)) {
log_error("Couldn't write rfkill event structure, too short.");
return -EIO;
}
log_debug("Loaded state '%s' from %s.", one_zero(b), state_file);
return 0;
}
static void save_state_queue_remove(
struct write_queue_item **write_queue,
int idx,
char *state_file) {
struct write_queue_item *item, *tmp;
LIST_FOREACH_SAFE(queue, item, tmp, *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, *write_queue, item);
write_queue_item_free(item);
}
}
}
static int save_state_queue(
struct write_queue_item **write_queue,
int rfkill_fd,
struct udev *udev,
const struct rfkill_event *event) {
_cleanup_free_ char *state_file = NULL;
struct write_queue_item *item;
int r;
assert(rfkill_fd >= 0);
assert(udev);
assert(event);
r = determine_state_file(udev, event, &state_file);
if (r < 0)
return r;
save_state_queue_remove(write_queue, 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, *write_queue, item);
return 0;
}
static int save_state_cancel(
struct write_queue_item **write_queue,
int rfkill_fd,
struct udev *udev,
const struct rfkill_event *event) {
_cleanup_free_ char *state_file = NULL;
int r;
assert(rfkill_fd >= 0);
assert(udev);
assert(event);
r = determine_state_file(udev, event, &state_file);
save_state_queue_remove(write_queue, event->idx, state_file);
if (r < 0)
return r;
return 0;
}
static int save_state_write(struct write_queue_item **write_queue) {
struct write_queue_item *item, *tmp;
int result = 0;
bool error_logged = false;
int r;
LIST_FOREACH_SAFE(queue, item, tmp, *write_queue) {
r = write_string_file(item->file, one_zero(item->state), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
if (r < 0) {
result = r;
if (!error_logged) {
log_error_errno(r, "Failed to write state file %s: %m", item->file);
error_logged = true;
} else
log_warning_errno(r, "Failed to write state file %s: %m", item->file);
} else
log_debug("Saved state '%s' to %s.", one_zero(item->state), item->file);
LIST_REMOVE(queue, *write_queue, item);
write_queue_item_free(item);
}
return result;
}
int main(int argc, char *argv[]) {
LIST_HEAD(write_queue_item, write_queue);
_cleanup_(udev_unrefp) struct udev *udev = NULL;
_cleanup_close_ int rfkill_fd = -1;
bool ready = false;
int r, n;
if (argc > 1) {
log_error("This program requires no arguments.");
return EXIT_FAILURE;
}
LIST_HEAD_INIT(write_queue);
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
umask(0022);
udev = udev_new();
if (!udev) {
r = log_oom();
goto finish;
}
r = mkdir_p("/var/lib/systemd/rfkill", 0755);
if (r < 0) {
log_error_errno(r, "Failed to create rfkill directory: %m");
goto finish;
}
n = sd_listen_fds(false);
if (n < 0) {
r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m");
goto finish;
}
if (n > 1) {
log_error("Got too many file descriptors.");
r = -EINVAL;
goto finish;
}
if (n == 0) {
rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (rfkill_fd < 0) {
if (errno == ENOENT) {
log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting.");
r = 0;
goto finish;
}
r = log_error_errno(errno, "Failed to open /dev/rfkill: %m");
goto finish;
}
} else {
rfkill_fd = SD_LISTEN_FDS_START;
r = fd_nonblock(rfkill_fd, 1);
if (r < 0) {
log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m");
goto finish;
}
}
for (;;) {
struct rfkill_event event;
const char *type;
ssize_t l;
l = read(rfkill_fd, &event, sizeof(event));
if (l < 0) {
if (errno == EAGAIN) {
if (!ready) {
/* Notify manager that we are
* now finished with
* processing whatever was
* queued */
(void) sd_notify(false, "READY=1");
ready = true;
}
/* Hang around for a bit, maybe there's more coming */
r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC);
if (r == -EINTR)
continue;
if (r < 0) {
log_error_errno(r, "Failed to poll() on device: %m");
goto finish;
}
if (r > 0)
continue;
log_debug("All events read and idle, exiting.");
break;
}
log_error_errno(errno, "Failed to read from /dev/rfkill: %m");
}
if (l != RFKILL_EVENT_SIZE_V1) {
log_error("Read event structure of invalid size.");
r = -EIO;
goto finish;
}
type = rfkill_type_to_string(event.type);
if (!type) {
log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type);
continue;
}
switch (event.op) {
case RFKILL_OP_ADD:
log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type);
(void) load_state(rfkill_fd, udev, &event);
break;
case RFKILL_OP_DEL:
log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type);
(void) save_state_cancel(&write_queue, rfkill_fd, udev, &event);
break;
case RFKILL_OP_CHANGE:
log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type);
(void) save_state_queue(&write_queue, rfkill_fd, udev, &event);
break;
default:
log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type);
break;
}
}
r = 0;
finish:
(void) save_state_write(&write_queue);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}