diff --git a/data/etc/rc.d/init.d/uevent-mounts b/data/etc/rc.d/init.d/uevent-mounts index eb5a27be..b6f31608 100755 --- a/data/etc/rc.d/init.d/uevent-mounts +++ b/data/etc/rc.d/init.d/uevent-mounts @@ -15,6 +15,8 @@ PIDFILE=/var/run/$NAME.pid ARGS="--lockfile $LOCKFILE --pidfile $PIDFILE --displayname $NAME --name $NAME" start() { + export filterdir + mkdir -p -- "$filterdir/mount/.tmp" start_daemon --background --make-pidfile $ARGS -- "$NAME" diff --git a/data/sbin/ueventd-mounts b/data/sbin/ueventd-mounts deleted file mode 100755 index c523c1e6..00000000 --- a/data/sbin/ueventd-mounts +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -efu -# SPDX-License-Identifier: GPL-3.0-or-later - -. /.initrd/initenv - -[ "${RDLOG-}" != 'console' ] && - logfile=/var/log/mounts.log || - logfile=/dev/console - -exec >>"$logfile" 2>&1 - -. shell-error -. uevent-sh-functions - -message_time=1 - -with_shell= -! findmnt --help | grep -qse --shell || with_shell=1 - -findmnt --poll --pairs ${with_shell:+--shell} | -while read -r ev; do - { - ACTION="" TARGET="" SOURCE="" FSTYPE="" OPTIONS="" - eval "$ev" - - event="$(make_event mount)" - printf '%s\n' "$ev" > "$event" - release_event "mount.${TARGET//[\/.]/_}" "$event" - } & -done diff --git a/datasrc/libinitramfs/logging.c b/datasrc/libinitramfs/logging.c index 39219d4a..22a46d19 100644 --- a/datasrc/libinitramfs/logging.c +++ b/datasrc/libinitramfs/logging.c @@ -132,3 +132,21 @@ void rd_log_message(int priority, const char *fmt, ...) } va_end(ap); } + +void rd_log_setup_stderr(const char *logfile) +{ + if (getenv("RDLOG_STDERR")) + return; + + char *rdlog = getenv("RDLOG"); + + if (rdlog && !strcasecmp(rdlog, "console")) + logfile = "/dev/console"; + + FILE *cons = fopen(logfile, "w+"); + if (!cons) + rd_fatal("open(%s): %m", logfile); + + fclose(stderr); + stderr = cons; +} diff --git a/datasrc/libinitramfs/memory.c b/datasrc/libinitramfs/memory.c index 11340f33..5f59eb38 100644 --- a/datasrc/libinitramfs/memory.c +++ b/datasrc/libinitramfs/memory.c @@ -11,6 +11,14 @@ #include "rd/memory.h" #include "rd/logging.h" +char *rd_strdup_or_die(const char *s) +{ + char *r = strdup(s); + if (!r) + rd_fatal("vasprintf: %m"); + return r; +} + void *rd_calloc_or_die(size_t nmemb, size_t size) { void *r = calloc(nmemb, size); diff --git a/datasrc/libinitramfs/rd/list.h b/datasrc/libinitramfs/rd/list.h new file mode 100644 index 00000000..9d2ec696 --- /dev/null +++ b/datasrc/libinitramfs/rd/list.h @@ -0,0 +1,242 @@ +/*- + * Copyright (c) 2011 Felix Fietkau + * Copyright (c) 2010 Isilon Systems, Inc. + * Copyright (c) 2010 iX Systems, Inc. + * Copyright (c) 2010 Panasas, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _LINUX_LIST_H_ +#define _LINUX_LIST_H_ + +#include +#include + +#define prefetch(x) + +#ifndef container_of +#define container_of(ptr, type, member) \ + ({ \ + const __typeof__(((type *) NULL)->member) *__mptr = (ptr); \ + (type *) ((char *) __mptr - offsetof(type, member)); \ + }) +#endif + +#ifndef container_of_safe +#define container_of_safe(ptr, type, member) \ + ({ \ + const __typeof__(((type *) NULL)->member) *__mptr = (ptr); \ + __mptr ? (type *)((char *) __mptr - offsetof(type, member)) : NULL; \ + }) +#endif + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } +#undef LIST_HEAD +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) + +static inline void +INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list->prev = list; +} + +static inline bool +list_empty(const struct list_head *head) +{ + return (head->next == head); +} + +static inline bool +list_is_first(const struct list_head *list, + const struct list_head *head) +{ + return list->prev == head; +} + +static inline bool +list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +static inline void +_list_del(struct list_head *entry) +{ + entry->next->prev = entry->prev; + entry->prev->next = entry->next; +} + +static inline void +list_del(struct list_head *entry) +{ + _list_del(entry); + entry->next = entry->prev = NULL; +} + +static inline void +_list_add(struct list_head *_new, struct list_head *prev, + struct list_head *next) +{ + + next->prev = _new; + _new->next = next; + _new->prev = prev; + prev->next = _new; +} + +static inline void +list_del_init(struct list_head *entry) +{ + _list_del(entry); + INIT_LIST_HEAD(entry); +} + +#define list_entry(ptr, type, field) container_of(ptr, type, field) +#define list_first_entry(ptr, type, field) list_entry((ptr)->next, type, field) +#define list_last_entry(ptr, type, field) list_entry((ptr)->prev, type, field) +#define list_next_entry(pos, member) list_entry((pos)->member.next, typeof(*(pos)), member) +#define list_entry_is_h(p, h, field) (&p->field == (h)) + +/** + * list_prev_entry - get the prev element in list + * @pos: the type * to cursor + * @member: the name of the list_head within the struct. + */ +#define list_prev_entry(pos, member) \ + list_entry((pos)->member.prev, typeof(*(pos)), member) + +#define list_for_each(p, head) \ + for (p = (head)->next; p != (head); p = p->next) + +#define list_for_each_safe(p, n, head) \ + for (p = (head)->next, n = p->next; p != (head); p = n, n = p->next) + +#define list_for_each_entry(p, h, field) \ + for (p = list_first_entry(h, __typeof__(*p), field); &p->field != (h); \ + p = list_entry(p->field.next, __typeof__(*p), field)) + +#define list_for_each_entry_continue(p, h, field) \ + for (p = list_next_entry(p, field); \ + !list_entry_is_h(p, h, field); \ + p = list_next_entry(p, field)) + +#define list_for_each_entry_continue_reverse(p, h, field) \ + for (p = list_prev_entry(p, field); \ + !list_entry_is_h(p, h, field); \ + p = list_prev_entry(p, field)) + +#define list_for_each_entry_safe(p, n, h, field) \ + for (p = list_first_entry(h, __typeof__(*p), field), \ + n = list_entry(p->field.next, __typeof__(*p), field); &p->field != (h);\ + p = n, n = list_entry(n->field.next, __typeof__(*n), field)) + +#define list_for_each_entry_reverse(p, h, field) \ + for (p = list_last_entry(h, __typeof__(*p), field); &p->field != (h); \ + p = list_entry(p->field.prev, __typeof__(*p), field)) + +#define list_for_each_prev(p, h) for (p = (h)->prev; p != (h); p = p->prev) +#define list_for_each_prev_safe(p, n, h) for (p = (h)->prev, n = p->prev; p != (h); p = n, n = p->prev) + +static inline void +list_add(struct list_head *_new, struct list_head *head) +{ + _list_add(_new, head, head->next); +} + +static inline void +list_add_tail(struct list_head *_new, struct list_head *head) +{ + _list_add(_new, head->prev, head); +} + +static inline void +list_move(struct list_head *list, struct list_head *head) +{ + _list_del(list); + list_add(list, head); +} + +static inline void +list_move_tail(struct list_head *entry, struct list_head *head) +{ + _list_del(entry); + list_add_tail(entry, head); +} + +static inline void +_list_splice(const struct list_head *list, struct list_head *prev, + struct list_head *next) +{ + struct list_head *first; + struct list_head *last; + + if (list_empty(list)) + return; + + first = list->next; + last = list->prev; + first->prev = prev; + prev->next = first; + last->next = next; + next->prev = last; +} + +static inline void +list_splice(const struct list_head *list, struct list_head *head) +{ + _list_splice(list, head, head->next); +} + +static inline void +list_splice_tail(struct list_head *list, struct list_head *head) +{ + _list_splice(list, head->prev, head); +} + +static inline void +list_splice_init(struct list_head *list, struct list_head *head) +{ + _list_splice(list, head, head->next); + INIT_LIST_HEAD(list); +} + +static inline void +list_splice_tail_init(struct list_head *list, struct list_head *head) +{ + _list_splice(list, head->prev, head); + INIT_LIST_HEAD(list); +} + +typedef int __attribute__((nonnull(2,3))) (*list_cmp_func_t)(void *, + const struct list_head *, const struct list_head *); + +__attribute__((nonnull(2,3))) +void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp); + +#endif /* _LINUX_LIST_H_ */ diff --git a/datasrc/libinitramfs/rd/logging.h b/datasrc/libinitramfs/rd/logging.h index 84f887c3..47270bd7 100644 --- a/datasrc/libinitramfs/rd/logging.h +++ b/datasrc/libinitramfs/rd/logging.h @@ -14,6 +14,7 @@ int rd_logging_level(const char *lvl) __attribute__((n void rd_vmessage(const char *fmt, va_list ap) __attribute__((format(printf, 1, 0))); void rd_log_vmessage(int priority, const char *fmt, va_list ap) __attribute__((format(printf, 2, 0))); void rd_log_message(int priority, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +void rd_log_setup_stderr(const char *logfile) __attribute__((nonnull(1))); #define rd_fatal(format, arg...) \ do { \ diff --git a/datasrc/libinitramfs/rd/memory.h b/datasrc/libinitramfs/rd/memory.h index e2c91b72..ac49a3b4 100644 --- a/datasrc/libinitramfs/rd/memory.h +++ b/datasrc/libinitramfs/rd/memory.h @@ -5,6 +5,7 @@ #include +char *rd_strdup_or_die(const char *s) __attribute__((nonnull(1))); void *rd_calloc_or_die(size_t nmemb, size_t size) __attribute__((alloc_size(1, 2),returns_nonnull,warn_unused_result)); void *rd_malloc_or_die(size_t size) __attribute__((alloc_size(1),returns_nonnull,warn_unused_result)); char *rd_asprintf_or_die(const char *fmt, ...) __attribute__((nonnull(1),format(printf, 1, 2),returns_nonnull, diff --git a/datasrc/ueventd-mounts/Makefile.mk b/datasrc/ueventd-mounts/Makefile.mk new file mode 100644 index 00000000..88a9c03d --- /dev/null +++ b/datasrc/ueventd-mounts/Makefile.mk @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +ueventd_mounts_DEST = $(dest_data_sbindir)/ueventd-mounts +ueventd_mounts_SRCDIR = datasrc/ueventd-mounts + +ueventd_mounts_SRCS = $(ueventd_mounts_SRCDIR)/ueventd-mounts.c + +ueventd_mounts_CFLAGS = -D_GNU_SOURCE -Idatasrc/libinitramfs +ueventd_mounts_LIBS = -L$(dest_data_libdir) -linitramfs + +PROGS += ueventd_mounts diff --git a/datasrc/ueventd-mounts/ueventd-mounts.c b/datasrc/ueventd-mounts/ueventd-mounts.c new file mode 100644 index 00000000..a89b1bf6 --- /dev/null +++ b/datasrc/ueventd-mounts/ueventd-mounts.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rd/memory.h" +#include "rd/logging.h" +#include "rd/list.h" + +#define MOUNT_PROC "/proc/self/mounts" +#define DEFAULT_LOGFILE "/var/log/ueventd-mounts.log" + +#define QUEUE_TMPNAME ".tmp" +#define QUEUE_NAME "mount" +#define EVENT_TEMP_NAME "." QUEUE_NAME ".event.new" + +enum mount_entry_t { + MOUNT_ENTRY_OLD, + MOUNT_ENTRY_NEW, + MOUNT_ENTRY_CUR, +}; + +struct mounts { + struct list_head list; + enum mount_entry_t ent_flag; + char *ent_name; + char *ent_path; + char *ent_type; + char *ent_opts; +}; + +static void reset_state(void); +static void read_mounts(void); +static void diff_mounts(void); +static struct mounts *find_mounts(const struct mntent *mnt) __attribute__((nonnull(1))); +static void create_event(const char *action, struct mounts *m) __attribute__((nonnull(1,2))); +static inline char *get_param_dir(const char *name) __attribute__((nonnull(1))); + +static char *filter_dir; +static int queue_dirfd; +static int temp_dirfd; + +LIST_HEAD(mounts); + +void reset_state(void) +{ + struct mounts *mount; + list_for_each_entry(mount, &mounts, list) { + mount->ent_flag = MOUNT_ENTRY_OLD; + } +} + +struct mounts *find_mounts(const struct mntent *mnt) +{ + struct mounts *mount; + list_for_each_entry(mount, &mounts, list) { + if (!strcmp(mount->ent_path, mnt->mnt_dir) && + !strcmp(mount->ent_name, mnt->mnt_fsname) && + !strcmp(mount->ent_type, mnt->mnt_type)) + return mount; + } + return NULL; +} + +void read_mounts(void) +{ + struct mntent *mnt; + FILE *fp; + + if (!(fp = fopen(MOUNT_PROC, "r"))) + rd_fatal("fopen: %s: %m", MOUNT_PROC); + + while ((mnt = getmntent(fp))) { + struct mounts *mount = find_mounts(mnt); + if (mount) { + mount->ent_flag = MOUNT_ENTRY_CUR; + continue; + } + + mount = rd_calloc_or_die(1, sizeof(*mount)); + + mount->ent_name = rd_strdup_or_die(mnt->mnt_fsname); + mount->ent_path = rd_strdup_or_die(mnt->mnt_dir); + mount->ent_type = rd_strdup_or_die(mnt->mnt_type); + mount->ent_opts = rd_strdup_or_die(mnt->mnt_opts); + mount->ent_flag = MOUNT_ENTRY_NEW; + + list_add_tail(&mount->list, &mounts); + } + + fclose(fp); +} + +void create_event(const char *action, struct mounts *m) +{ + int fd; + char *p, *fname; + struct timespec tp = { 0 }; + + clock_gettime(CLOCK_BOOTTIME, &tp); + + fd = openat(temp_dirfd, EVENT_TEMP_NAME, + O_WRONLY | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) + rd_fatal("open: %s/" QUEUE_TMPNAME "/%s: %m", filter_dir, EVENT_TEMP_NAME); + + dprintf(fd, + "ACTION='%s'\n" "SOURCE='%s'\n" "TARGET='%s'\n" "FSTYPE='%s'\n" "OPTIONS='%s'\n", + action, m->ent_name, m->ent_path, m->ent_type, m->ent_opts); + + close(fd); + + fname = rd_asprintf_or_die(QUEUE_NAME ".%s.%06lu.%012lu", m->ent_path, + (unsigned long) tp.tv_sec, tp.tv_nsec); + + for (p = fname; *p != '\0'; p++) { + if (*p == '/') + *p = '_'; + } + + if (renameat(temp_dirfd, EVENT_TEMP_NAME, queue_dirfd, fname) < 0) + rd_fatal("rename `%s/" QUEUE_TMPNAME "/%s' -> `%s/%s': %m", + filter_dir, EVENT_TEMP_NAME, + filter_dir, fname); + + free(fname); +} + +void diff_mounts(void) +{ + struct mounts *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &mounts, list) { + switch (pos->ent_flag) { + case MOUNT_ENTRY_NEW: + rd_info("mount: %s", pos->ent_path); + create_event("mount", pos); + break; + case MOUNT_ENTRY_OLD: + rd_info("umount: %s", pos->ent_path); + create_event("umount", pos); + + list_del(&pos->list); + + free(pos->ent_name); + free(pos->ent_path); + free(pos->ent_type); + free(pos->ent_opts); + free(pos); + break; + default: + } + } +} + +char *get_param_dir(const char *name) +{ + char *value = getenv(name); + if (!value) + rd_fatal("please set `%s' env variable", name); + return value; +} + +int main(int argc, char **argv) +{ + int c, fd; + struct pollfd fds[1]; + int loglevel = LOG_INFO; + + while ((c = getopt(argc, argv, "hl:")) != -1) { + switch (c) { + case 'h': + fprintf(stderr, "Usage: %s [-l loglevel]\n", + program_invocation_short_name); + exit(EXIT_SUCCESS); + break; + case 'l': + loglevel = rd_logging_level(optarg); + break; + default: + exit(EXIT_FAILURE); + break; + } + } + + rd_log_setup_stderr(DEFAULT_LOGFILE); + rd_logging_init(fileno(stderr), loglevel, program_invocation_short_name); + + rd_info("starting server ..."); + + filter_dir = get_param_dir("filterdir"); + + if (chdir(filter_dir) < 0) + rd_fatal("chdir: %s: %m", filter_dir); + + if (chdir(QUEUE_NAME) < 0) + rd_fatal("chdir: %s/" QUEUE_NAME ": %m", filter_dir); + + if ((queue_dirfd = open(".", O_PATH | O_DIRECTORY | O_CLOEXEC)) < 0) + rd_fatal("open: %s/" QUEUE_NAME ": %m", filter_dir); + + if ((temp_dirfd = openat(queue_dirfd, QUEUE_TMPNAME, O_PATH | O_DIRECTORY | O_CLOEXEC)) < 0) + rd_fatal("open: %s/" QUEUE_TMPNAME ": %m", filter_dir); + + read_mounts(); + + if ((fd = open(MOUNT_PROC, O_RDONLY | O_LARGEFILE | O_CLOEXEC)) < 0) + rd_fatal("open: %s: %m", MOUNT_PROC); + + fds[0].fd = fd; + fds[0].events = POLLERR | POLLPRI; + + while (1) { + int ready; + + errno = 0; + if ((ready = poll(fds, 1, -1)) < 0) { + if (errno == EINTR) + continue; + rd_fatal("poll: %m"); + } + + if (fds[0].revents & POLLERR) { + reset_state(); + read_mounts(); + diff_mounts(); + } + } + + close(fd); + + return EXIT_SUCCESS; +} diff --git a/datasrc/ueventd/ueventd.c b/datasrc/ueventd/ueventd.c index bd82a8fd..15d289f7 100644 --- a/datasrc/ueventd/ueventd.c +++ b/datasrc/ueventd/ueventd.c @@ -23,7 +23,7 @@ #include "rd/logging.h" #include "ueventd.h" -#define default_logfile "/var/log/ueventd.log" +#define DEFAULT_LOGFILE "/var/log/ueventd.log" char *uevent_confdb; char *filter_dir; @@ -482,21 +482,7 @@ int main(int argc, char **argv) if (optind == argc) rd_fatal("specify handler program"); - if (!getenv("UEVENTD_USE_STDERR")) { - char *rdlog = getenv("RDLOG"); - const char *logfile = default_logfile; - - if (rdlog && !strcasecmp(rdlog, "console")) - logfile = "/dev/console"; - - FILE *cons = fopen(logfile, "w+"); - if (!cons) - rd_fatal("open(%s): %m", logfile); - - fclose(stderr); - stderr = cons; - } - + rd_log_setup_stderr(DEFAULT_LOGFILE); rd_logging_init(fileno(stderr), loglevel, program_invocation_short_name); rd_info("starting server ...");