From deb0a77cf0b409141c4b116ae30becb3d878e1ad Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 14 Apr 2015 22:01:48 +0200 Subject: [PATCH] automount: add expire support --- man/systemd.automount.xml | 8 ++ man/systemd.mount.xml | 9 ++ src/core/automount.c | 224 ++++++++++++++++++++++++++++++++-- src/core/automount.h | 6 +- src/core/dbus-automount.c | 1 + src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/mount.c | 20 +-- src/fstab-generator/fstab-generator.c | 26 ++++ 8 files changed, 267 insertions(+), 28 deletions(-) diff --git a/man/systemd.automount.xml b/man/systemd.automount.xml index b5b5885..9561590 100644 --- a/man/systemd.automount.xml +++ b/man/systemd.automount.xml @@ -135,6 +135,14 @@ creating these directories. Takes an access mode in octal notation. Defaults to 0755. + + TimeoutIdleSec= + Configures an idleness timeout. Once the mount has been + idle for the specified time, systemd will attempt to unmount. Takes a + unit-less value in seconds, or a time span value such as "5min 20s". + Pass 0 to disable the timeout logic. The timeout is disabled by + default. + diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml index fcb9a44..e102d27 100644 --- a/man/systemd.mount.xml +++ b/man/systemd.mount.xml @@ -148,6 +148,15 @@ + + + Configures the idleness timeout of the + automount unit. See TimeoutIdleSec= in + systemd.automount5 + for details. + + + Configure how long systemd should wait for a diff --git a/src/core/automount.c b/src/core/automount.c index ce484ff..82c3e3d 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -42,6 +42,7 @@ #include "bus-error.h" #include "formats-util.h" #include "process-util.h" +#include "async.h" static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { [AUTOMOUNT_DEAD] = UNIT_INACTIVE, @@ -50,6 +51,22 @@ static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { [AUTOMOUNT_FAILED] = UNIT_FAILED }; +struct expire_data { + int dev_autofs_fd; + int ioctl_fd; +}; + +static inline void expire_data_free(struct expire_data *data) { + if (!data) + return; + + safe_close(data->dev_autofs_fd); + safe_close(data->ioctl_fd); + free(data); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct expire_data*, expire_data_free); + static int open_dev_autofs(Manager *m); static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata); @@ -81,13 +98,16 @@ static void repeat_unmount(const char *path) { } } +static int automount_send_ready(Automount *a, Set *tokens, int status); + static void unmount_autofs(Automount *a) { assert(a); if (a->pipe_fd < 0) return; - automount_send_ready(a, -EHOSTDOWN); + automount_send_ready(a, a->tokens, -EHOSTDOWN); + automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); a->pipe_event_source = sd_event_source_unref(a->pipe_event_source); a->pipe_fd = safe_close(a->pipe_fd); @@ -112,6 +132,10 @@ static void automount_done(Unit *u) { set_free(a->tokens); a->tokens = NULL; + set_free(a->expire_tokens); + a->expire_tokens = NULL; + + a->expire_event_source = sd_event_source_unref(a->expire_event_source); } static int automount_add_mount_links(Automount *a) { @@ -265,6 +289,7 @@ static int automount_coldplug(Unit *u, Hashmap *deferred_work) { } static void automount_dump(Unit *u, FILE *f, const char *prefix) { + char time_string[FORMAT_TIMESPAN_MAX]; Automount *a = AUTOMOUNT(u); assert(a); @@ -273,11 +298,13 @@ static void automount_dump(Unit *u, FILE *f, const char *prefix) { "%sAutomount State: %s\n" "%sResult: %s\n" "%sWhere: %s\n" - "%sDirectoryMode: %04o\n", + "%sDirectoryMode: %04o\n" + "%sTimeoutIdleUSec: %s\n", prefix, automount_state_to_string(a->state), prefix, automount_result_to_string(a->result), prefix, a->where, - prefix, a->directory_mode); + prefix, a->directory_mode, + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, a->timeout_idle_usec, USEC_PER_SEC)); } static void automount_enter_dead(Automount *a, AutomountResult f) { @@ -367,7 +394,7 @@ static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) { return 0; } -static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, time_t sec) { +static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, usec_t usec) { struct autofs_dev_ioctl param; assert(dev_autofs_fd >= 0); @@ -375,7 +402,9 @@ static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, time_t sec) { init_autofs_dev_ioctl(¶m); param.ioctlfd = ioctl_fd; - param.timeout.timeout = sec; + + /* Convert to seconds, rounding up. */ + param.timeout.timeout = (usec + USEC_PER_SEC - 1) / USEC_PER_SEC; if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) return -errno; @@ -404,7 +433,7 @@ static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, in return 0; } -int automount_send_ready(Automount *a, int status) { +static int automount_send_ready(Automount *a, Set *tokens, int status) { _cleanup_close_ int ioctl_fd = -1; unsigned token; int r; @@ -412,7 +441,7 @@ int automount_send_ready(Automount *a, int status) { assert(a); assert(status <= 0); - if (set_isempty(a->tokens)) + if (set_isempty(tokens)) return 0; ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); @@ -427,7 +456,7 @@ int automount_send_ready(Automount *a, int status) { r = 0; /* Autofs thankfully does not hand out 0 as a token */ - while ((token = PTR_TO_UINT(set_steal_first(a->tokens)))) { + while ((token = PTR_TO_UINT(set_steal_first(tokens)))) { int k; /* Autofs fun fact II: @@ -446,6 +475,55 @@ int automount_send_ready(Automount *a, int status) { return r; } +int automount_update_mount(Automount *a, MountState old_state, MountState state) { + _cleanup_close_ int ioctl_fd = -1; + + assert(a); + + switch (state) { + case MOUNT_MOUNTED: + case MOUNT_REMOUNTING: + automount_send_ready(a, a->tokens, 0); + break; + case MOUNT_DEAD: + case MOUNT_UNMOUNTING: + case MOUNT_MOUNTING_SIGTERM: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_FAILED: + if (old_state != state) + automount_send_ready(a, a->tokens, -ENODEV); + break; + default: + break; + } + + switch (state) { + case MOUNT_DEAD: + automount_send_ready(a, a->expire_tokens, 0); + break; + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + case MOUNT_MOUNTING_SIGTERM: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_FAILED: + if (old_state != state) + automount_send_ready(a, a->expire_tokens, -ENODEV); + break; + default: + break; + } + + return 0; +} + static void automount_enter_waiting(Automount *a) { _cleanup_close_ int ioctl_fd = -1; int p[2] = { -1, -1 }; @@ -505,7 +583,7 @@ static void automount_enter_waiting(Automount *a) { if (r < 0) goto fail; - r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, 300); + r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, a->timeout_idle_usec); if (r < 0) goto fail; @@ -537,6 +615,83 @@ fail: automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); } +static void *expire_thread(void *p) { + struct autofs_dev_ioctl param; + _cleanup_(expire_data_freep) struct expire_data *data = (struct expire_data*)p; + int r; + + assert(data->dev_autofs_fd >= 0); + assert(data->ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = data->ioctl_fd; + + do { + r = ioctl(data->dev_autofs_fd, AUTOFS_DEV_IOCTL_EXPIRE, ¶m); + } while (r >= 0); + + if (errno != EAGAIN) + log_warning_errno(errno, "Failed to expire automount, ignoring: %m"); + + return NULL; +} + +static int automount_start_expire(Automount *a); + +static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { + Automount *a = AUTOMOUNT(userdata); + _cleanup_(expire_data_freep) struct expire_data *data = NULL; + int r; + + assert(a); + assert(source == a->expire_event_source); + + data = new0(struct expire_data, 1); + if (!data) + return log_oom(); + + data->ioctl_fd = -1; + + data->dev_autofs_fd = fcntl(UNIT(a)->manager->dev_autofs_fd, F_DUPFD_CLOEXEC, 3); + if (data->dev_autofs_fd < 0) + return log_unit_error_errno(UNIT(a)->id, errno, "Failed to duplicate autofs fd: %m"); + + data->ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); + if (data->ioctl_fd < 0) + return log_unit_error_errno(UNIT(a)->id, data->ioctl_fd, "Couldn't open autofs ioctl fd: %m"); + + r = asynchronous_job(expire_thread, data); + if (r < 0) + return log_unit_error_errno(UNIT(a)->id, r, "Failed to start expire job: %m"); + + data = NULL; + + return automount_start_expire(a); +} + +static int automount_start_expire(Automount *a) { + int r; + usec_t timeout; + + assert(a); + + timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/10, USEC_PER_SEC); + + if (a->expire_event_source) { + r = sd_event_source_set_time(a->expire_event_source, timeout); + if (r < 0) + return r; + + return sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_ONESHOT); + } + + return sd_event_add_time( + UNIT(a)->manager->event, + &a->expire_event_source, + CLOCK_MONOTONIC, timeout, 0, + automount_dispatch_expire, a); +} + static void automount_enter_runnning(Automount *a) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; struct stat st; @@ -549,7 +704,8 @@ static void automount_enter_runnning(Automount *a) { if (unit_stop_pending(UNIT(a))) { log_unit_debug(UNIT(a)->id, "Suppressing automount request on %s since unit stop is scheduled.", UNIT(a)->id); - automount_send_ready(a, -EHOSTDOWN); + automount_send_ready(a, a->tokens, -EHOSTDOWN); + automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); return; } @@ -576,6 +732,10 @@ static void automount_enter_runnning(Automount *a) { } } + r = automount_start_expire(a); + if (r < 0) + log_unit_warning_errno(UNIT(a)->id, r, "Failed to start expiration timer, ignoring: %m"); + automount_set_state(a, AUTOMOUNT_RUNNING); return; @@ -627,6 +787,8 @@ static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { SET_FOREACH(p, a->tokens, i) unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p)); + SET_FOREACH(p, a->expire_tokens, i) + unit_serialize_item_format(u, f, "expire-token", "%u", PTR_TO_UINT(p)); if (a->pipe_fd >= 0) { int copy; @@ -686,6 +848,22 @@ static int automount_deserialize_item(Unit *u, const char *key, const char *valu if (r < 0) return r; } + } else if (streq(key, "expire-token")) { + unsigned token; + + if (safe_atou(value, &token) < 0) + log_unit_debug(u->id, "Failed to parse token value %s", value); + else { + r = set_ensure_allocated(&a->expire_tokens, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = set_put(a->expire_tokens, UINT_TO_PTR(token)); + if (r < 0) + log_unit_error_errno(u->id, r, "Failed to add expire token to set: %m"); + } } else if (streq(key, "pipe-fd")) { int fd; @@ -723,6 +901,7 @@ static bool automount_check_gc(Unit *u) { } static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; union autofs_v5_packet_union packet; Automount *a = AUTOMOUNT(userdata); int r; @@ -771,6 +950,31 @@ static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, vo automount_enter_runnning(a); break; + case autofs_ptype_expire_direct: + log_unit_debug(UNIT(a)->id, "Got direct umount request on %s", a->where); + + (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); + + r = set_ensure_allocated(&a->expire_tokens, NULL); + if (r < 0) { + log_unit_error(UNIT(a)->id, "Failed to allocate token set."); + goto fail; + } + + r = set_put(a->expire_tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); + if (r < 0) { + log_unit_error_errno(UNIT(a)->id, r, "Failed to remember token: %m"); + goto fail; + } + r = manager_add_job(UNIT(a)->manager, JOB_STOP, UNIT_TRIGGER(UNIT(a)), JOB_REPLACE, true, &error, NULL); + if (r < 0) { + log_unit_warning(UNIT(a)->id, + "%s failed to queue umount startup job: %s", + UNIT(a)->id, bus_error_message(&error, r)); + goto fail; + } + break; + default: log_unit_error(UNIT(a)->id, "Received unknown automount request %i", packet.hdr.type); break; diff --git a/src/core/automount.h b/src/core/automount.h index 60f5522..2a50fef 100644 --- a/src/core/automount.h +++ b/src/core/automount.h @@ -47,6 +47,7 @@ struct Automount { AutomountState state, deserialized_state; char *where; + usec_t timeout_idle_usec; int pipe_fd; sd_event_source *pipe_event_source; @@ -54,13 +55,16 @@ struct Automount { dev_t dev_id; Set *tokens; + Set *expire_tokens; + + sd_event_source *expire_event_source; AutomountResult result; }; extern const UnitVTable automount_vtable; -int automount_send_ready(Automount *a, int status); +int automount_update_mount(Automount *a, MountState old_state, MountState state); const char* automount_state_to_string(AutomountState i) _const_; AutomountState automount_state_from_string(const char *s) _pure_; diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c index 38acbd0..5162ce3 100644 --- a/src/core/dbus-automount.c +++ b/src/core/dbus-automount.c @@ -30,5 +30,6 @@ const sd_bus_vtable bus_automount_vtable[] = { SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Automount, where), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Automount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Automount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 5305984..66c9145 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -318,6 +318,7 @@ KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl m4_dnl Automount.Where, config_parse_path, 0, offsetof(Automount, where) Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) +Automount.TimeoutIdleSec, config_parse_sec, 0, offsetof(Automount, timeout_idle_usec) m4_dnl Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what) Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) diff --git a/src/core/mount.c b/src/core/mount.c index 8bfbbed..d3a4098 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -548,7 +548,7 @@ static int mount_load(Unit *u) { return mount_verify(m); } -static int mount_notify_automount(Mount *m, int status) { +static int mount_notify_automount(Mount *m, MountState old_state, MountState state) { Unit *p; int r; Iterator i; @@ -557,7 +557,7 @@ static int mount_notify_automount(Mount *m, int status) { SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i) if (p->type == UNIT_AUTOMOUNT) { - r = automount_send_ready(AUTOMOUNT(p), status); + r = automount_update_mount(AUTOMOUNT(p), old_state, state); if (r < 0) return r; } @@ -588,21 +588,7 @@ static void mount_set_state(Mount *m, MountState state) { m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; } - if (state == MOUNT_MOUNTED || - state == MOUNT_REMOUNTING) - mount_notify_automount(m, 0); - else if (state == MOUNT_DEAD || - state == MOUNT_UNMOUNTING || - state == MOUNT_MOUNTING_SIGTERM || - state == MOUNT_MOUNTING_SIGKILL || - state == MOUNT_REMOUNTING_SIGTERM || - state == MOUNT_REMOUNTING_SIGKILL || - state == MOUNT_UNMOUNTING_SIGTERM || - state == MOUNT_UNMOUNTING_SIGKILL || - state == MOUNT_FAILED) { - if (state != old_state) - mount_notify_automount(m, -ENODEV); - } + mount_notify_automount(m, old_state, state); if (state != old_state) log_unit_debug(UNIT(m)->id, diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 57fb7c3..cb5112e 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -154,6 +154,28 @@ static bool mount_in_initrd(struct mntent *me) { streq(me->mnt_dir, "/usr"); } +static int write_idle_timeout(FILE *f, const char *where, const char *opts, char **filtered) { + _cleanup_free_ char *timeout = NULL; + char timespan[FORMAT_TIMESPAN_MAX]; + usec_t u; + int r; + + r = fstab_filter_options(opts, "x-systemd.idle-timeout\0", NULL, &timeout, filtered); + if (r < 0) + return log_warning_errno(r, "Failed to parse options: %m"); + if (r == 0) + return 0; + + r = parse_sec(timeout, &u); + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + return 0; + } + + fprintf(f, "TimeoutIdleSec=%s\n", format_timespan(timespan, sizeof(timespan), u, 0)); + + return 0; +} static int add_mount( const char *what, const char *where, @@ -293,6 +315,10 @@ static int add_mount( "Where=%s\n", where); + r = write_idle_timeout(f, where, opts, &filtered); + if (r < 0) + return r; + fflush(f); if (ferror(f)) return log_error_errno(errno, "Failed to write unit file %s: %m", automount_unit); -- 2.7.4