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