]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/rfkill/rfkill.c
tree-wide: drop redundant _cleanup_ macros (#8810)
[thirdparty/systemd.git] / src / rfkill / rfkill.c
index 0acdf229ed9ac59b673b4fd1b2b93b85d2d34de0..330c4e8629b1ee2a8a73c3df0a1ae7196f4ddb7e 100644 (file)
@@ -1,20 +1,8 @@
+/* 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",
@@ -70,7 +76,8 @@ static int find_device(
 
         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) {
@@ -90,7 +97,7 @@ static int wait_for_initialized(
                 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;
@@ -128,7 +135,8 @@ static int wait_for_initialized(
         /* 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;
@@ -136,19 +144,23 @@ static int wait_for_initialized(
         }
 
         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;
                 }
@@ -158,18 +170,21 @@ static int wait_for_initialized(
 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;
@@ -184,9 +199,9 @@ static int determine_state_file(
                 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();
@@ -200,7 +215,6 @@ static int load_state(
                 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;
@@ -213,11 +227,7 @@ static int load_state(
         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;
 
@@ -257,37 +267,103 @@ static int load_state(
         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;
@@ -297,6 +373,8 @@ int main(int argc, char *argv[]) {
                 return EXIT_FAILURE;
         }
 
+        LIST_HEAD_INIT(write_queue);
+
         log_set_target(LOG_TARGET_AUTO);
         log_parse_environment();
         log_open();
@@ -406,11 +484,12 @@ int main(int argc, char *argv[]) {
 
                 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:
@@ -422,5 +501,7 @@ int main(int argc, char *argv[]) {
         r = 0;
 
 finish:
+        (void) save_state_write(&write_queue);
+
         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }