]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/rfkill/rfkill.c
util-lib: split string parsing related calls from util.[ch] into parse-util.[ch]
[thirdparty/systemd.git] / src / rfkill / rfkill.c
index 812cf408fb2aacbe20f890f3e153e470b8f58b07..559a17653531c84aa6df86408f55b1cb6ceeea51 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include "util.h"
-#include "mkdir.h"
-#include "fileio.h"
+#include <linux/rfkill.h>
+#include <poll.h>
+
 #include "libudev.h"
+#include "sd-daemon.h"
+
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "io-util.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "string-util.h"
 #include "udev-util.h"
+#include "util.h"
 
-int main(int argc, char *argv[]) {
-        _cleanup_udev_unref_ struct udev *udev = NULL;
-        _cleanup_udev_device_unref_ struct udev_device *device = NULL;
-        _cleanup_free_ char *saved = NULL, *escaped_name = NULL, *escaped_path_id = NULL;
-        const char *name, *path_id;
-        int r;
+#define EXIT_USEC (5 * USEC_PER_SEC)
 
-        if (argc != 3) {
-                log_error("This program requires two arguments.");
-                return EXIT_FAILURE;
-        }
+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",
+};
 
-        log_set_target(LOG_TARGET_AUTO);
-        log_parse_environment();
-        log_open();
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int);
 
-        umask(0022);
+static int find_device(
+                struct udev *udev,
+                const struct rfkill_event *event,
+                struct udev_device **ret) {
 
-        r = mkdir_p("/var/lib/systemd/rfkill", 0755);
-        if (r < 0) {
-                log_error("Failed to create rfkill directory: %s", strerror(-r));
-                return EXIT_FAILURE;
-        }
+        _cleanup_free_ char *sysname = NULL;
+        struct udev_device *device;
+        const char *name;
 
-        udev = udev_new();
-        if (!udev) {
-                log_oom();
-                return EXIT_FAILURE;
-        }
+        assert(udev);
+        assert(event);
+        assert(ret);
 
-        errno = 0;
-        device = udev_device_new_from_subsystem_sysname(udev, "rfkill", argv[2]);
-        if (!device) {
-                if (errno != 0)
-                        log_error("Failed to get rfkill device '%s': %m", argv[2]);
-                else
-                        log_oom();
+        if (asprintf(&sysname, "rfkill%i", event->idx) < 0)
+                return log_oom();
 
-                return EXIT_FAILURE;
-        }
+        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");
 
         name = udev_device_get_sysattr_value(device, "name");
         if (!name) {
-                log_error("rfkill device has no name?");
-                return EXIT_FAILURE;
+                log_debug("Device has no name, ignoring.");
+                udev_device_unref(device);
+                return -ENOENT;
         }
 
-        escaped_name = cescape(name);
-        if (!escaped_name) {
-                log_oom();
-                return EXIT_FAILURE;
+        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_unref_ 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(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
+
+        if (udev_device_get_is_initialized(d) != 0) {
+                *ret = d;
+                return 0;
+        }
+
+        for (;;) {
+                _cleanup_udev_device_unref_ struct udev_device *t = NULL;
+
+                r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+                if (r == -EINTR)
+                        continue;
+                if (r < 0)
+                        return log_error_errno(r, "Failed to watch udev monitor: %m");
+
+                t = udev_monitor_receive_device(monitor);
+                if (!t)
+                        continue;
+
+                if (streq_ptr(udev_device_get_sysname(device), 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;
+        const char *path_id, *type;
+        char *state_file;
+        int r;
+
+        assert(event);
+        assert(d);
+        assert(ret);
+
+        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) {
-                        log_oom();
-                        return EXIT_FAILURE;
-                }
+                if (!escaped_path_id)
+                        return log_oom();
 
-                saved = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", escaped_name, NULL);
+                state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL);
         } else
-                saved = strjoin("/var/lib/systemd/rfkill/", escaped_name, NULL);
+                state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL);
+
+        if (!state_file)
+                return log_oom();
+
+        *ret = state_file;
+        return 0;
+}
 
-        if (!saved) {
-                log_oom();
+static int load_state(
+                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, *value = NULL;
+        struct rfkill_event we;
+        ssize_t l;
+        int b, r;
+
+        assert(rfkill_fd >= 0);
+        assert(udev);
+        assert(event);
+
+        if (!shall_restore_state())
+                return 0;
+
+        r = find_device(udev, event, &device);
+        if (r < 0)
+                return r;
+
+        r = determine_state_file(udev, event, device, &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 int save_state(
+                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;
+        int r;
+
+        assert(rfkill_fd >= 0);
+        assert(udev);
+        assert(event);
+
+        r = find_device(udev, event, &device);
+        if (r < 0)
+                return r;
+
+        r = determine_state_file(udev, event, device, &state_file);
+        if (r < 0)
+                return r;
+
+        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;
+}
+
+int main(int argc, char *argv[]) {
+        _cleanup_udev_unref_ 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;
         }
 
-        if (streq(argv[1], "load")) {
-                _cleanup_free_ char *value = NULL;
+        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;
+        }
 
-                r = read_one_line_file(saved, &value);
-                if (r < 0) {
+        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 (r == -ENOENT)
-                                return EXIT_SUCCESS;
+        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;
+                        }
 
-                        log_error("Failed to read %s: %s", saved, strerror(-r));
-                        return EXIT_FAILURE;
+                        r = log_error_errno(errno, "Failed to open /dev/rfkill: %m");
+                        goto finish;
                 }
+        } else {
+                rfkill_fd = SD_LISTEN_FDS_START;
 
-                r = udev_device_set_sysattr_value(device, "soft", value);
+                r = fd_nonblock(rfkill_fd, 1);
                 if (r < 0) {
-                        log_error("Failed to write system attribute: %s", strerror(-r));
-                        return EXIT_FAILURE;
+                        log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m");
+                        goto finish;
                 }
+        }
 
-        } else if (streq(argv[1], "save")) {
-                const char *value;
+        for (;;) {
+                struct rfkill_event event;
+                const char *type;
+                ssize_t l;
 
-                value = udev_device_get_sysattr_value(device, "soft");
-                if (!value) {
-                        log_error("Failed to read system attribute: %s", strerror(-r));
-                        return EXIT_FAILURE;
+                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");
                 }
 
-                r = write_string_file(saved, value);
-                if (r < 0) {
-                        log_error("Failed to write %s: %s", saved, strerror(-r));
-                        return EXIT_FAILURE;
+                if (l != RFKILL_EVENT_SIZE_V1) {
+                        log_error("Read event structure of invalid size.");
+                        r = -EIO;
+                        goto finish;
                 }
 
-        } else {
-                log_error("Unknown verb %s.", argv[1]);
-                return EXIT_FAILURE;
+                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);
+                        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);
+                        break;
+
+                default:
+                        log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type);
+                        break;
+                }
         }
 
-        return EXIT_SUCCESS;
+        r = 0;
+
+finish:
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }