+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
-
- systemd 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.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <linux/rfkill.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",
device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
if (!device)
- return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
+ 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) {
struct udev_device *device,
struct udev_device **ret) {
- _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL;
+ _cleanup_(udev_monitor_unrefp) struct udev_monitor *monitor = NULL;
struct udev_device *d;
const char *sysname;
int watch_fd, r;
/* Check again, maybe things changed */
d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
if (!d)
- return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
+ 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;
}
for (;;) {
- _cleanup_udev_device_unref_ struct udev_device *t = NULL;
+ _cleanup_(udev_device_unrefp) struct udev_device *t = NULL;
- r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+ 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(device), sysname)) {
+ 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,
- struct udev_device *d,
char **ret) {
- _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ _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(d);
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;
if (!escaped_path_id)
return log_oom();
- state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL);
+ state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type);
} else
- state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL);
+ state_file = strjoin("/var/lib/systemd/rfkill/", type);
if (!state_file)
return log_oom();
struct udev *udev,
const struct rfkill_event *event) {
- _cleanup_udev_device_unref_ struct udev_device *device = NULL;
_cleanup_free_ char *state_file = NULL, *value = NULL;
struct rfkill_event we;
ssize_t l;
if (shall_restore_state() == 0)
return 0;
- r = find_device(udev, event, &device);
- if (r < 0)
- return r;
-
- r = determine_state_file(udev, event, device, &state_file);
+ r = determine_state_file(udev, event, &state_file);
if (r < 0)
return r;
return 0;
}
-static int save_state(
+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_udev_device_unref_ struct udev_device *device = NULL;
_cleanup_free_ char *state_file = NULL;
+ struct write_queue_item *item;
int r;
assert(rfkill_fd >= 0);
assert(udev);
assert(event);
- r = find_device(udev, event, &device);
+ r = determine_state_file(udev, event, &state_file);
if (r < 0)
return r;
- r = determine_state_file(udev, event, device, &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;
- r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
+ 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 log_error_errno(r, "Failed to write state file %s: %m", state_file);
+ return r;
- log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
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[]) {
- _cleanup_udev_unref_ struct udev *udev = NULL;
+ 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;
return EXIT_FAILURE;
}
+ LIST_HEAD_INIT(write_queue);
+
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
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(rfkill_fd, udev, &event);
+ (void) save_state_queue(&write_queue, rfkill_fd, udev, &event);
break;
default:
r = 0;
finish:
+ (void) save_state_write(&write_queue);
+
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}