chiark / gitweb /
automount: implement automount unit type
authorLennart Poettering <lennart@poettering.net>
Fri, 16 Apr 2010 21:24:39 +0000 (23:24 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 16 Apr 2010 21:24:39 +0000 (23:24 +0200)
17 files changed:
automount.c
automount.h
execute.c
execute.h
linux/auto_dev-ioctl.h [new file with mode: 0644]
load-fragment.c
manager.c
manager.h
mount-setup.c
mount.c
service.c
socket.c
target.c
unit-name.c
unit.h
util.c
util.h

index 1192949d837f0996af79e134bb8db2875cfbf3f1..5d79131afcbcd814ee4eb0951c59e6dca0272640 100644 (file)
 ***/
 
 #include <errno.h>
+#include <limits.h>
+#include <sys/mount.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <linux/auto_fs4.h>
+#include <linux/auto_dev-ioctl.h>
 
 #include "unit.h"
 #include "automount.h"
 #include "load-fragment.h"
 #include "load-dropin.h"
+#include "unit-name.h"
 
 static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = {
         [AUTOMOUNT_DEAD] = UNIT_INACTIVE,
@@ -40,11 +49,95 @@ static const char* const state_string_table[_AUTOMOUNT_STATE_MAX] = {
         [AUTOMOUNT_MAINTAINANCE] = "maintainance"
 };
 
+static char *automount_name_from_where(const char *where) {
+        assert(where);
+
+        if (streq(where, "/"))
+                return strdup("-.automount");
+
+        return unit_name_build_escape(where+1, NULL, ".automount");
+}
+
 static void automount_init(Unit *u) {
         Automount *a = AUTOMOUNT(u);
 
-        a->state = 0;
+        assert(u);
+        assert(u->meta.load_state == UNIT_STUB);
+
+        a->pipe_watch.fd = a->pipe_fd = -1;
+}
+
+static void repeat_unmout(const char *path) {
+        assert(path);
+
+        for (;;) {
+
+                if (umount2(path, MNT_DETACH) >= 0)
+                        continue;
+
+                if (errno != EINVAL)
+                        log_error("Failed to unmount: %m");
+
+                break;
+        }
+}
+
+static void unmount_autofs(Automount *a) {
+        assert(a);
+
+        if (a->pipe_fd < 0)
+                return;
+
+        automount_send_ready(a, -EHOSTDOWN);
+
+        unit_unwatch_fd(UNIT(a), &a->pipe_watch);
+        close_nointr_nofail(a->pipe_fd);
+        a->pipe_fd = -1;
+
+        repeat_unmout(a->where);
+}
+
+static void automount_done(Unit *u) {
+        Automount *a = AUTOMOUNT(u);
+
+        assert(a);
+
+        unmount_autofs(a);
         a->mount = NULL;
+
+        if (a->tokens) {
+                set_free(a->tokens);
+                a->tokens = NULL;
+        }
+}
+
+static int automount_verify(Automount *a) {
+        bool b;
+        char *e;
+        assert(a);
+
+        if (UNIT(a)->meta.load_state != UNIT_LOADED)
+                return 0;
+
+        if (!a->where) {
+                log_error("%s lacks Where setting. Refusing.", UNIT(a)->meta.id);
+                return -EINVAL;
+        }
+
+        path_kill_slashes(a->where);
+
+        if (!(e = automount_name_from_where(a->where)))
+                return -ENOMEM;
+
+        b = unit_has_name(UNIT(a), e);
+        free(e);
+
+        if (!b) {
+                log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(a)->meta.id);
+                return -EINVAL;
+        }
+
+        return 0;
 }
 
 static int automount_load(Unit *u) {
@@ -67,15 +160,24 @@ static int automount_load(Unit *u) {
                         return r;
         }
 
-        return 0;
+        return automount_verify(a);
 }
 
-static void automount_done(Unit *u) {
-        Automount *a = AUTOMOUNT(u);
-
+static void automount_set_state(Automount *a, AutomountState state) {
+        AutomountState old_state;
         assert(a);
 
-        a->mount = NULL;
+        old_state = a->state;
+        a->state = state;
+
+        if (state != AUTOMOUNT_WAITING &&
+            state != AUTOMOUNT_RUNNING)
+                unmount_autofs(a);
+
+        if (state != old_state)
+                log_debug("%s changed %s → %s", UNIT(a)->meta.id, state_string_table[old_state], state_string_table[state]);
+
+        unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state]);
 }
 
 static void automount_dump(Unit *u, FILE *f, const char *prefix) {
@@ -88,6 +190,336 @@ static void automount_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, state_string_table[s->state]);
 }
 
+static void automount_enter_dead(Automount *a, bool success) {
+        assert(a);
+
+        if (!success)
+                a->failure = true;
+
+        automount_set_state(a, a->failure ? AUTOMOUNT_MAINTAINANCE : AUTOMOUNT_DEAD);
+}
+
+static int open_dev_autofs(Manager *m) {
+        struct autofs_dev_ioctl param;
+
+        assert(m);
+
+        if (m->dev_autofs_fd >= 0)
+                return m->dev_autofs_fd;
+
+        if ((m->dev_autofs_fd = open("/dev/autofs", O_RDONLY)) < 0) {
+                log_error("Failed to open /dev/autofs: %s", strerror(errno));
+                return -errno;
+        }
+
+        init_autofs_dev_ioctl(&param);
+        if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, &param) < 0) {
+                close_nointr_nofail(m->dev_autofs_fd);
+                m->dev_autofs_fd = -1;
+                return -errno;
+        }
+
+        log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor);
+
+        return m->dev_autofs_fd;
+}
+
+static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) {
+        struct autofs_dev_ioctl *param;
+        size_t l;
+        int r;
+
+        assert(dev_autofs_fd >= 0);
+        assert(where);
+
+        l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1;
+
+        if (!(param = malloc(l)))
+                return -ENOMEM;
+
+        init_autofs_dev_ioctl(param);
+        param->size = l;
+        param->ioctlfd = -1;
+        param->openmount.devid = devid;
+        strcpy(param->path, where);
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (param->ioctlfd < 0) {
+                r = -EIO;
+                goto finish;
+        }
+
+        r = param->ioctlfd;
+
+finish:
+        free(param);
+        return r;
+}
+
+static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) {
+        uint32_t major, minor;
+        struct autofs_dev_ioctl param;
+
+        assert(dev_autofs_fd >= 0);
+        assert(ioctl_fd >= 0);
+
+        init_autofs_dev_ioctl(&param);
+        param.ioctlfd = ioctl_fd;
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, &param) < 0)
+                return -errno;
+
+        major = param.protover.version;
+
+        init_autofs_dev_ioctl(&param);
+        param.ioctlfd = ioctl_fd;
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, &param) < 0)
+                return -errno;
+
+        minor = param.protosubver.sub_version;
+
+        log_debug("Autofs protocol version %i.%i", major, minor);
+        return 0;
+}
+
+static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, time_t sec) {
+        struct autofs_dev_ioctl param;
+
+        assert(dev_autofs_fd >= 0);
+        assert(ioctl_fd >= 0);
+
+        init_autofs_dev_ioctl(&param);
+        param.ioctlfd = ioctl_fd;
+        param.timeout.timeout = sec;
+
+        if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, &param) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) {
+        struct autofs_dev_ioctl param;
+
+        assert(dev_autofs_fd >= 0);
+        assert(ioctl_fd >= 0);
+
+        init_autofs_dev_ioctl(&param);
+        param.ioctlfd = ioctl_fd;
+
+        if (status) {
+                param.fail.token = token;
+                param.fail.status = status;
+        } else
+                param.ready.token = token;
+
+        if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, &param) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int automount_send_ready(Automount *a, int status) {
+        int ioctl_fd, r;
+        unsigned token;
+
+        assert(a);
+        assert(status <= 0);
+
+        if (set_isempty(a->tokens))
+                return 0;
+
+        if ((ioctl_fd = open_ioctl_fd(UNIT(a)->meta.manager->dev_autofs_fd, a->where, a->dev_id)) < 0) {
+                r = ioctl_fd;
+                goto fail;
+        }
+
+        if (status)
+                log_debug("Sending failure: %s", strerror(-status));
+        else
+                log_debug("Sending success.");
+
+        /* Autofs thankfully does not hand out 0 as a token */
+        while ((token = PTR_TO_UINT(set_steal_first(a->tokens)))) {
+                int k;
+
+                /* Autofs fun fact II:
+                 *
+                 * if you pass a positive status code here, the kernel will
+                 * freeze! Yay! */
+
+                if ((k = autofs_send_ready(UNIT(a)->meta.manager->dev_autofs_fd,
+                                           ioctl_fd,
+                                           token,
+                                           status)) < 0)
+                        r = k;
+        }
+
+        r = 0;
+
+fail:
+        if (ioctl_fd >= 0)
+                close_nointr_nofail(ioctl_fd);
+
+        return r;
+}
+
+static void automount_enter_waiting(Automount *a) {
+        int p[2] = { -1, -1 };
+        char name[32], options[128];
+        bool mounted = false;
+        int r, ioctl_fd = -1, dev_autofs_fd;
+        struct stat st;
+
+        assert(a);
+        assert(a->pipe_fd < 0);
+        assert(a->where);
+
+        if (a->tokens)
+                set_clear(a->tokens);
+        else if (!(a->tokens = set_new(trivial_hash_func, trivial_compare_func))) {
+                r = -ENOMEM;
+                goto fail;
+        }
+
+        if ((dev_autofs_fd = open_dev_autofs(UNIT(a)->meta.manager)) < 0) {
+                r = dev_autofs_fd;
+                goto fail;
+        }
+
+        /* We knowingly ignore the results of this call */
+        mkdir_p(a->where, 0555);
+
+        if (pipe2(p, O_NONBLOCK) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        snprintf(options, sizeof(options), "fd=%i,pgrp=%u,minproto=5,maxproto=5,direct", p[1], (unsigned) getpgrp());
+        char_array_0(options);
+
+        snprintf(name, sizeof(name), "systemd-%u", (unsigned) getpid());
+        char_array_0(name);
+
+        if (mount(name, a->where, "autofs", 0, options) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        mounted = true;
+
+        close_nointr_nofail(p[1]);
+        p[1] = -1;
+
+        if (stat(a->where, &st) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if ((ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev)) < 0) {
+                r = ioctl_fd;
+                goto fail;
+        }
+
+        if ((r = autofs_protocol(dev_autofs_fd, ioctl_fd)) < 0)
+                goto fail;
+
+        if ((r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, 300)) < 0)
+                goto fail;
+
+        /* Autofs fun fact:
+         *
+         * Unless we close the ioctl fd here, for some weird reason
+         * the direct mount will not receive events from the
+         * kernel. */
+
+        close_nointr_nofail(ioctl_fd);
+        ioctl_fd = -1;
+
+        if ((r = unit_watch_fd(UNIT(a), p[0], EPOLLIN, &a->pipe_watch)) < 0)
+                goto fail;
+
+        a->pipe_fd = p[0];
+        a->dev_id = st.st_dev;
+
+        automount_set_state(a, AUTOMOUNT_WAITING);
+
+        return;
+
+fail:
+        assert_se(close_pipe(p) == 0);
+
+        if (ioctl_fd >= 0)
+                close_nointr_nofail(ioctl_fd);
+
+        if (mounted)
+                repeat_unmout(a->where);
+
+        log_error("Failed to initialize automounter: %s", strerror(-r));
+        automount_enter_dead(a, false);
+}
+
+static void automount_enter_runnning(Automount *a) {
+        int r;
+        struct stat st;
+
+        assert(a);
+        assert(a->mount);
+
+        /* Before we do anything, let's see if somebody is playing games with us? */
+
+        if (stat(a->where, &st) < 0) {
+                log_warning("%s failed stat automount point: %m", a->meta.id);
+                goto fail;
+        }
+
+        if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id)
+                log_info("%s's automount point already active?", a->meta.id);
+        else if ((r = manager_add_job(UNIT(a)->meta.manager, JOB_START, UNIT(a->mount), JOB_REPLACE, true, NULL)) < 0) {
+                log_warning("%s failed to queue mount startup job: %s", a->meta.id, strerror(-r));
+                goto fail;
+        }
+
+        automount_set_state(a, AUTOMOUNT_RUNNING);
+        return;
+
+fail:
+        automount_enter_dead(a, false);
+}
+
+static int automount_start(Unit *u) {
+        Automount *a = AUTOMOUNT(u);
+
+        assert(a);
+
+        if (path_is_mount_point(a->where)) {
+                log_error("Path %s is already a mount point, refusing start for %s", a->where, u->meta.id);
+                return -EEXIST;
+        }
+
+        assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_MAINTAINANCE);
+
+        a->failure = false;
+        automount_enter_waiting(a);
+        return 0;
+}
+
+static int automount_stop(Unit *u) {
+        Automount *a = AUTOMOUNT(u);
+
+        assert(a);
+
+        assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING);
+
+        automount_enter_dead(a, true);
+        return 0;
+}
+
 static UnitActiveState automount_active_state(Unit *u) {
 
         return state_translation_table[AUTOMOUNT(u)->state];
@@ -99,10 +531,62 @@ static const char *automount_sub_state_to_string(Unit *u) {
         return state_string_table[AUTOMOUNT(u)->state];
 }
 
+static void automount_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
+        union autofs_v5_packet_union packet;
+        ssize_t l;
+        int r;
+
+        Automount *a = AUTOMOUNT(u);
+
+        assert(a);
+        assert(fd == a->pipe_fd);
+
+        if (events != EPOLLIN) {
+                log_error("Got invalid poll event on pipe.");
+                goto fail;
+        }
+
+        if ((l = loop_read(a->pipe_fd, &packet, sizeof(packet))) != sizeof(packet)) {
+                log_error("Invalid read from pipe: %s", l < 0 ? strerror(-l) : "short read");
+                goto fail;
+        }
+
+        switch (packet.hdr.type) {
+
+        case autofs_ptype_missing_direct:
+                log_debug("Got direct mount request for %s", packet.v5_packet.name);
+
+                if ((r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token))) < 0) {
+                        log_error("Failed to remember token: %s", strerror(-r));
+                        goto fail;
+                }
+
+                automount_enter_runnning(a);
+                break;
+
+        default:
+                log_error("Received unknown automount request %i", packet.hdr.type);
+                break;
+        }
+
+        return;
+
+fail:
+        automount_enter_dead(a, false);
+}
+
+static void automount_shutdown(Manager *m) {
+        assert(m);
+
+        if (m->dev_autofs_fd >= 0)
+                close_nointr_nofail(m->dev_autofs_fd);
+}
+
 const UnitVTable automount_vtable = {
-        .suffix = ".mount",
+        .suffix = ".automount",
 
         .no_alias = true,
+        .no_instances = true,
 
         .init = automount_init,
         .load = automount_load,
@@ -110,6 +594,13 @@ const UnitVTable automount_vtable = {
 
         .dump = automount_dump,
 
+        .start = automount_start,
+        .stop = automount_stop,
+
         .active_state = automount_active_state,
-        .sub_state_to_string = automount_sub_state_to_string
+        .sub_state_to_string = automount_sub_state_to_string,
+
+        .fd_event = automount_fd_event,
+
+        .shutdown = automount_shutdown
 };
index daa3686fcba33e6c5fa08e923a3e0090dbd89365..a8c81e17f2b84a7bdd971c348bee39bf69943937 100644 (file)
@@ -40,9 +40,21 @@ struct Automount {
 
         AutomountState state;
 
+        char *where;
+
         Mount *mount;
+
+        int pipe_fd;
+        Watch pipe_watch;
+        dev_t dev_id;
+
+        Set *tokens;
+
+        bool failure:1;
 };
 
 extern const UnitVTable automount_vtable;
 
+int automount_send_ready(Automount *a, int status);
+
 #endif
index dfedb31a5312f652ac6da60584a4185fa9bb3bd5..357fd5c20874aea7a6c670b89f7e737f64aa3fb1 100644 (file)
--- a/execute.c
+++ b/execute.c
@@ -788,10 +788,11 @@ int exec_spawn(ExecCommand *command,
                         goto fail;
                 }
 
-                if (setsid() < 0) {
-                        r = EXIT_SETSID;
-                        goto fail;
-                }
+                if (!context->no_setsid)
+                        if (setsid() < 0) {
+                                r = EXIT_SETSID;
+                                goto fail;
+                        }
 
                 umask(context->umask);
 
index 135c8bab5fafab64f59b42e9b7ff379d997ce1cd..655f1e4f7b4aec9a66446a5e90c089d863ac7e25 100644 (file)
--- a/execute.h
+++ b/execute.h
@@ -97,6 +97,13 @@ struct ExecContext {
         bool cpu_affinity_set:1;
         bool timer_slack_ns_set:1;
 
+        /* This is not exposed to the user but available
+         * internally. We need it to make sure that whenever we spawn
+         * /bin/mount it is run in the same process group as us so
+         * that the autofs logic detects that it belongs to us and we
+         * don't enter a trigger loop. */
+        bool no_setsid:1;
+
         bool cpu_sched_reset_on_fork;
         bool non_blocking;
 
diff --git a/linux/auto_dev-ioctl.h b/linux/auto_dev-ioctl.h
new file mode 100644 (file)
index 0000000..850f39b
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2008 Red Hat, Inc. All rights reserved.
+ * Copyright 2008 Ian Kent <raven@themaw.net>
+ *
+ * This file is part of the Linux kernel and is made available under
+ * the terms of the GNU General Public License, version 2, or at your
+ * option, any later version, incorporated herein by reference.
+ */
+
+#ifndef _LINUX_AUTO_DEV_IOCTL_H
+#define _LINUX_AUTO_DEV_IOCTL_H
+
+#include <linux/auto_fs.h>
+
+#ifdef __KERNEL__
+#include <linux/string.h>
+#else
+#include <string.h>
+#endif /* __KERNEL__ */
+
+#define AUTOFS_DEVICE_NAME             "autofs"
+
+#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1
+#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0
+
+#define AUTOFS_DEVID_LEN               16
+
+#define AUTOFS_DEV_IOCTL_SIZE          sizeof(struct autofs_dev_ioctl)
+
+/*
+ * An ioctl interface for autofs mount point control.
+ */
+
+struct args_protover {
+       __u32   version;
+};
+
+struct args_protosubver {
+       __u32   sub_version;
+};
+
+struct args_openmount {
+       __u32   devid;
+};
+
+struct args_ready {
+       __u32   token;
+};
+
+struct args_fail {
+       __u32   token;
+       __s32   status;
+};
+
+struct args_setpipefd {
+       __s32   pipefd;
+};
+
+struct args_timeout {
+       __u64   timeout;
+};
+
+struct args_requester {
+       __u32   uid;
+       __u32   gid;
+};
+
+struct args_expire {
+       __u32   how;
+};
+
+struct args_askumount {
+       __u32   may_umount;
+};
+
+struct args_ismountpoint {
+       union {
+               struct args_in {
+                       __u32   type;
+               } in;
+               struct args_out {
+                       __u32   devid;
+                       __u32   magic;
+               } out;
+       };
+};
+
+/*
+ * All the ioctls use this structure.
+ * When sending a path size must account for the total length
+ * of the chunk of memory otherwise is is the size of the
+ * structure.
+ */
+
+struct autofs_dev_ioctl {
+       __u32 ver_major;
+       __u32 ver_minor;
+       __u32 size;             /* total size of data passed in
+                                * including this struct */
+       __s32 ioctlfd;          /* automount command fd */
+
+       /* Command parameters */
+
+       union {
+               struct args_protover            protover;
+               struct args_protosubver         protosubver;
+               struct args_openmount           openmount;
+               struct args_ready               ready;
+               struct args_fail                fail;
+               struct args_setpipefd           setpipefd;
+               struct args_timeout             timeout;
+               struct args_requester           requester;
+               struct args_expire              expire;
+               struct args_askumount           askumount;
+               struct args_ismountpoint        ismountpoint;
+       };
+
+       char path[0];
+};
+
+static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in)
+{
+       memset(in, 0, sizeof(struct autofs_dev_ioctl));
+       in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR;
+       in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR;
+       in->size = sizeof(struct autofs_dev_ioctl);
+       in->ioctlfd = -1;
+       return;
+}
+
+/*
+ * If you change this make sure you make the corresponding change
+ * to autofs-dev-ioctl.c:lookup_ioctl()
+ */
+enum {
+       /* Get various version info */
+       AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71,
+       AUTOFS_DEV_IOCTL_PROTOVER_CMD,
+       AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD,
+
+       /* Open mount ioctl fd */
+       AUTOFS_DEV_IOCTL_OPENMOUNT_CMD,
+
+       /* Close mount ioctl fd */
+       AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD,
+
+       /* Mount/expire status returns */
+       AUTOFS_DEV_IOCTL_READY_CMD,
+       AUTOFS_DEV_IOCTL_FAIL_CMD,
+
+       /* Activate/deactivate autofs mount */
+       AUTOFS_DEV_IOCTL_SETPIPEFD_CMD,
+       AUTOFS_DEV_IOCTL_CATATONIC_CMD,
+
+       /* Expiry timeout */
+       AUTOFS_DEV_IOCTL_TIMEOUT_CMD,
+
+       /* Get mount last requesting uid and gid */
+       AUTOFS_DEV_IOCTL_REQUESTER_CMD,
+
+       /* Check for eligible expire candidates */
+       AUTOFS_DEV_IOCTL_EXPIRE_CMD,
+
+       /* Request busy status */
+       AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD,
+
+       /* Check if path is a mountpoint */
+       AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD,
+};
+
+#define AUTOFS_IOCTL 0x93
+
+#define AUTOFS_DEV_IOCTL_VERSION \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOVER \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_PROTOSUBVER \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_OPENMOUNT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_READY \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_FAIL \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_SETPIPEFD \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_CATATONIC \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_TIMEOUT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_REQUESTER \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_EXPIRE \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ASKUMOUNT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl)
+
+#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \
+       _IOWR(AUTOFS_IOCTL, \
+             AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl)
+
+#endif /* _LINUX_AUTO_DEV_IOCTL_H */
index 89365817992aa1bf2de0113e9696c687142a44ac..f79af635b8db2e751784d1c81d7f1a58f5407ce8 100644 (file)
@@ -1211,6 +1211,8 @@ static int load_from_path(Unit *u, const char *path) {
                 { "KillMode",               config_parse_kill_mode,       &u->mount.kill_mode,                             "Mount"   },
                 EXEC_CONTEXT_CONFIG_ITEMS(u->mount.exec_context, "Mount"),
 
+                { "Where",                  config_parse_path,            &u->automount.where,                             "Automount" },
+
                 { NULL, NULL, NULL, NULL }
         };
 
index e5dc209bf23c08a102f1d7fa01963966cd390276..6f3ff54d6ceaa64307edb54a1bfcacca1836cd70 100644 (file)
--- a/manager.c
+++ b/manager.c
@@ -322,7 +322,7 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
         m->confirm_spawn = confirm_spawn;
         m->name_data_slot = -1;
 
-        m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = -1;
+        m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = -1;
         m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
 
         if (!(m->units = hashmap_new(string_hash_func, string_compare_func)))
index cb4af820d9ce9c35f4c4f8c8668d69c1c983140b..58e709b116725be1683d94943c1eeb40b372cca2 100644 (file)
--- a/manager.h
+++ b/manager.h
@@ -165,6 +165,8 @@ struct Manager {
         char **sysvinit_path;
         char **sysvrcnd_path;
 
+        usec_t boot_timestamp;
+
         /* Data specific to the device subsystem */
         struct udev* udev;
         struct udev_monitor* udev_monitor;
@@ -186,7 +188,8 @@ struct Manager {
         char *cgroup_controller;
         char *cgroup_hierarchy;
 
-        usec_t boot_timestamp;
+        /* Data specific to the Automount subsystem */
+        int dev_autofs_fd;
 };
 
 int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **m);
index 013e6144cfc142532f17358ccea4e0ab83be3959..a804030df00242050ba62cafdef3ba6533a400e2 100644 (file)
@@ -64,37 +64,12 @@ bool mount_point_is_api(const char *path) {
         return false;
 }
 
-static int is_mount_point(const char *t) {
-        struct stat a, b;
-        char *copy;
-
-        if (lstat(t, &a) < 0) {
-
-                if (errno == ENOENT)
-                        return 0;
-
-                return -errno;
-        }
-
-        if (!(copy = strdup(t)))
-                return -ENOMEM;
-
-        if (lstat(dirname(copy), &b) < 0) {
-                free(copy);
-                return -errno;
-        }
-
-        free(copy);
-
-        return a.st_dev != b.st_dev;
-}
-
 static int mount_one(const MountPoint *p) {
         int r;
 
         assert(p);
 
-        if ((r = is_mount_point(p->where)) < 0)
+        if ((r = path_is_mount_point(p->where)) < 0)
                 return r;
 
         if (r > 0)
diff --git a/mount.c b/mount.c
index 597df9e614fe289893a501247da57ad5cc6a3f8d..cc94ca6f2365cb078275ee9ad323efefbe345c83 100644 (file)
--- a/mount.c
+++ b/mount.c
@@ -66,6 +66,15 @@ static const char* const state_string_table[_MOUNT_STATE_MAX] = {
         [MOUNT_MAINTAINANCE] = "maintainance"
 };
 
+static char *mount_name_from_where(const char *where) {
+        assert(where);
+
+        if (streq(where, "/"))
+                return strdup("-.mount");
+
+        return unit_name_build_escape(where+1, NULL, ".mount");
+}
+
 static void service_unwatch_control_pid(Mount *m) {
         assert(m);
 
@@ -113,26 +122,34 @@ static void mount_init(Unit *u) {
         assert(u);
         assert(u->meta.load_state == UNIT_STUB);
 
-        m->state = 0;
-        m->from_etc_fstab = false;
-        m->from_proc_self_mountinfo = false;
-        m->from_fragment = false;
+        m->timeout_usec = DEFAULT_TIMEOUT_USEC;
+        exec_context_init(&m->exec_context);
 
-        m->is_mounted = false;
-        m->just_mounted = false;
-        m->just_changed = false;
+        /* We need to make sure that /bin/mount is always called in
+         * the same process group as us, so that the autofs kernel
+         * side doesn't send us another mount request while we are
+         * already trying to comply its last one. */
+        m->exec_context.no_setsid = true;
 
-        m->timeout_usec = DEFAULT_TIMEOUT_USEC;
+        m->timer_watch.type = WATCH_INVALID;
+}
 
-        zero(m->exec_command);
-        exec_context_init(&m->exec_context);
+static int mount_notify_automount(Mount *m, int status) {
+        Unit *p;
+        char *k;
 
-        m->kill_mode = 0;
+        assert(m);
 
-        m->control_pid = 0;
-        m->failure = false;
+        if (!(k = unit_name_change_suffix(UNIT(m)->meta.id, ".automount")))
+                return -ENOMEM;
 
-        m->timer_watch.type = WATCH_INVALID;
+        p = manager_get_unit(UNIT(m)->meta.manager, k);
+        free(k);
+
+        if (!p)
+                return 0;
+
+        return automount_send_ready(AUTOMOUNT(p), status);
 }
 
 static int mount_add_node_links(Mount *m) {
@@ -279,6 +296,35 @@ static int mount_add_target_links(Mount *m) {
         return unit_add_dependency(UNIT(m), UNIT_BEFORE, u);
 }
 
+static int mount_verify(Mount *m) {
+        bool b;
+        char *e;
+        assert(m);
+
+        if (UNIT(m)->meta.load_state != UNIT_LOADED)
+                return 0;
+
+        if (!m->where) {
+                log_error("%s lacks Where setting. Refusing.", UNIT(m)->meta.id);
+                return -EINVAL;
+        }
+
+        path_kill_slashes(m->where);
+
+        if (!(e = mount_name_from_where(m->where)))
+                return -ENOMEM;
+
+        b = unit_has_name(UNIT(m), e);
+        free(e);
+
+        if (!b) {
+                log_error("%s's Where setting doesn't match unit name. Refusing.", UNIT(m)->meta.id);
+                return -EINVAL;
+        }
+
+        return 0;
+}
+
 static int mount_load(Unit *u) {
         Mount *m = MOUNT(u);
         int r;
@@ -312,7 +358,7 @@ static int mount_load(Unit *u) {
                         return r;
         }
 
-        return 0;
+        return mount_verify(m);
 }
 
 static void mount_set_state(Mount *m, MountState state) {
@@ -337,6 +383,20 @@ static void mount_set_state(Mount *m, MountState state) {
                 m->control_command = NULL;
         }
 
+        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_MAINTAINANCE)
+                mount_notify_automount(m, -ENODEV);
+
         if (state != old_state)
                 log_debug("%s changed %s → %s", UNIT(m)->meta.id, state_string_table[old_state], state_string_table[state]);
 
@@ -534,15 +594,12 @@ fail:
         mount_enter_mounted(m, false);
 }
 
-static void mount_enter_mounting(Mount *m, bool success) {
+static void mount_enter_mounting(Mount *m) {
         ExecCommand *c;
         int r;
 
         assert(m);
 
-        if (!success)
-                m->failure = true;
-
         m->control_command = c = m->exec_command + MOUNT_EXEC_MOUNT;
 
         if (m->from_fragment)
@@ -552,7 +609,7 @@ static void mount_enter_mounting(Mount *m, bool success) {
                                 m->parameters_fragment.what,
                                 m->where,
                                 "-t", m->parameters_fragment.fstype,
-                                "-o", m->parameters_fragment.options,
+                                m->parameters_fragment.options ? "-o" : NULL, m->parameters_fragment.options,
                                 NULL);
         else if (m->from_etc_fstab)
                 r = exec_command_set(
@@ -580,12 +637,9 @@ fail:
         mount_enter_dead(m, false);
 }
 
-static void mount_enter_mounting_done(Mount *m, bool success) {
+static void mount_enter_mounting_done(Mount *m) {
         assert(m);
 
-        if (!success)
-                m->failure = true;
-
         mount_set_state(m, MOUNT_MOUNTING_DONE);
 }
 
@@ -674,7 +728,7 @@ static int mount_start(Unit *u) {
 
         m->failure = false;
 
-        mount_enter_mounting(m, true);
+        mount_enter_mounting(m);
         return 0;
 }
 
@@ -874,14 +928,14 @@ static int mount_add_one(
         if (mount_point_is_api(where))
                 return 0;
 
+        if (streq(fstype, "autofs"))
+                return 0;
+
         /* probably some kind of swap, which we don't cover for now */
         if (where[0] != '/')
                 return 0;
 
-        if (streq(where, "/"))
-                e = strdup("-.mount");
-        else
-                e = unit_name_build_escape(where+1, NULL, ".mount");
+        e = mount_name_from_where(where);
 
         if (!e)
                 return -ENOMEM;
@@ -1209,7 +1263,7 @@ void mount_fd_event(Manager *m, int events) {
                                 break;
 
                         case MOUNT_MOUNTING:
-                                mount_enter_mounting_done(mount, true);
+                                mount_enter_mounting_done(mount);
                                 break;
 
                         default:
@@ -1247,7 +1301,7 @@ int mount_path_is_mounted(Manager *m, const char* path) {
                 char *e, *slash;
                 Unit *u;
 
-                if (!(e = unit_name_build_escape(t+1, NULL, ".mount"))) {
+                if (!(e = mount_name_from_where(t))) {
                         r = -ENOMEM;
                         goto finish;
                 }
index bd248a07881c387f01c93365a2384239965d4f96..c85c6f572a62b109c11974ad8d20a307a0fd8dac 100644 (file)
--- a/service.c
+++ b/service.c
@@ -740,30 +740,13 @@ static void service_init(Unit *u) {
         assert(u);
         assert(u->meta.load_state == UNIT_STUB);
 
-        s->type = 0;
-        s->restart = 0;
-
         s->timeout_usec = DEFAULT_TIMEOUT_USEC;
         s->restart_usec = DEFAULT_RESTART_USEC;
-
-        exec_context_init(&s->exec_context);
-
         s->timer_watch.type = WATCH_INVALID;
-
-        s->state = SERVICE_DEAD;
-
         s->sysv_start_priority = -1;
-        s->permissions_start_only = false;
-        s->root_directory_start_only = false;
-        s->valid_no_process = false;
-        s->kill_mode = 0;
-        s->sysv_has_lsb = false;
-        s->main_pid = s->control_pid = 0;
-        s->main_pid_known = false;
-        s->failure = false;
-
         s->socket_fd = -1;
-        s->bus_name_good = false;
+
+        exec_context_init(&s->exec_context);
 
         RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5);
 }
@@ -987,7 +970,8 @@ static int service_get_sockets(Service *s, Set **_set) {
                 p = manager_get_unit(UNIT(s)->meta.manager, k);
                 free(k);
 
-                if (!p) continue;
+                if (!p)
+                        continue;
 
                 if ((r = set_put(set, p)) < 0)
                         goto fail;
index 91832128f5a9c9f83d8d8d517ac448976e9e488f..88fb2d3869e50231a4cfd4a2b6974c091e352688 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -111,19 +111,12 @@ static void socket_init(Unit *u) {
         assert(u);
         assert(u->meta.load_state == UNIT_STUB);
 
-        s->state = 0;
         s->timer_watch.type = WATCH_INVALID;
-        s->bind_ipv6_only = false;
         s->backlog = SOMAXCONN;
         s->timeout_usec = DEFAULT_TIMEOUT_USEC;
         s->directory_mode = 0755;
         s->socket_mode = 0666;
-        s->kill_mode = 0;
-        s->failure = false;
-        s->control_pid = 0;
-        s->service = NULL;
-        s->accept = false;
-        s->n_accepted = 0;
+
         exec_context_init(&s->exec_context);
 }
 
@@ -899,13 +892,13 @@ static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
         int cfd = -1;
 
         assert(s);
+        assert(fd >= 0);
 
         log_debug("Incoming traffic on %s", u->meta.id);
 
         if (events != EPOLLIN) {
                 log_error("Got invalid poll event on socket.");
-                socket_enter_stop_pre(s, false);
-                return;
+                goto fail;
         }
 
         if (w->data.socket_accept) {
@@ -917,8 +910,7 @@ static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
                                         continue;
 
                                 log_error("Failed to accept socket: %m");
-                                socket_enter_stop_pre(s, false);
-                                return;
+                                goto fail;
                         }
 
                         break;
@@ -926,6 +918,10 @@ static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
         }
 
         socket_enter_running(s, cfd);
+        return;
+
+fail:
+        socket_enter_stop_pre(s, false);
 }
 
 static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
index e6f26f1412ad19ad63117a8603140d1a72b58852..176ab68c42f4d5f7ce2a601c3dffcd867f3d0028 100644 (file)
--- a/target.c
+++ b/target.c
@@ -37,15 +37,6 @@ static const char* const state_string_table[_TARGET_STATE_MAX] = {
         [TARGET_ACTIVE] = "active"
 };
 
-static void target_init(Unit *u) {
-        Target *t = TARGET(u);
-
-        assert(t);
-        assert(u->meta.load_state == UNIT_STUB);
-
-        t->state = 0;
-}
-
 static void target_dump(Unit *u, FILE *f, const char *prefix) {
         Target *t = TARGET(u);
 
@@ -134,7 +125,6 @@ int target_get_runlevel(Target *t) {
 const UnitVTable target_vtable = {
         .suffix = ".target",
 
-        .init = target_init,
         .load = unit_load_fragment_and_dropin,
 
         .dump = target_dump,
index eb2f704e2f7f4d61319d1a1f8de230dc8b3c4515..5053d30f29a4721a6b5b43671a8400cacff93de8 100644 (file)
@@ -207,8 +207,8 @@ static char* do_escape(const char *f, char *t) {
 
         for (; *f; f++) {
                 if (*f == '/')
-                        *(t++) = '.';
-                else if (*f == '.' || *f == '\\' || !strchr(VALID_CHARS, *f)) {
+                        *(t++) = '-';
+                else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) {
                         *(t++) = '\\';
                         *(t++) = 'x';
                         *(t++) = hexchar(*f > 4);
@@ -286,7 +286,7 @@ char *unit_name_unescape(const char *f) {
                 return NULL;
 
         for (t = r; *f; f++) {
-                if (*f == '.')
+                if (*f == '-')
                         *(t++) = '/';
                 else if (*f == '\\') {
                         int a, b;
diff --git a/unit.h b/unit.h
index 338a58b00981c1d79e724fbaa99f6c5a5e677171..c8ebf492ae99d18b4396b2f119ffead75c303173 100644 (file)
--- a/unit.h
+++ b/unit.h
@@ -54,12 +54,12 @@ typedef enum KillMode {
 
 enum UnitType {
         UNIT_SERVICE = 0,
-        UNIT_TIMER,
         UNIT_SOCKET,
         UNIT_TARGET,
         UNIT_DEVICE,
         UNIT_MOUNT,
         UNIT_AUTOMOUNT,
+        UNIT_TIMER,
         UNIT_SNAPSHOT,
         _UNIT_TYPE_MAX,
         _UNIT_TYPE_INVALID = -1
diff --git a/util.c b/util.c
index 5f36819fa31f432181989130822d6f047c013da7..dd4dc097a95aaf2f129cec8076e5bdb14db8c3d7 100644 (file)
--- a/util.c
+++ b/util.c
@@ -41,6 +41,7 @@
 #include <stdarg.h>
 #include <sys/inotify.h>
 #include <sys/poll.h>
+#include <libgen.h>
 
 #include "macro.h"
 #include "util.h"
@@ -1651,6 +1652,97 @@ int ignore_signal(int sig) {
         return sigaction(sig, &sa, NULL);
 }
 
+int close_pipe(int p[]) {
+        int a = 0, b = 0;
+
+        assert(p);
+
+        if (p[0] >= 0) {
+                a = close_nointr(p[0]);
+                p[0] = -1;
+        }
+
+        if (p[1] >= 0) {
+                b = close_nointr(p[1]);
+                p[1] = -1;
+        }
+
+        return a < 0 ? a : b;
+}
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes) {
+        uint8_t *p;
+        ssize_t n = 0;
+
+        assert(fd >= 0);
+        assert(buf);
+
+        p = buf;
+
+        while (nbytes > 0) {
+                ssize_t k;
+
+                if ((k = read(fd, p, nbytes)) <= 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN) {
+                                struct pollfd pollfd;
+
+                                zero(pollfd);
+                                pollfd.fd = fd;
+                                pollfd.events = POLLIN;
+
+                                if (poll(&pollfd, 1, -1) < 0) {
+                                        if (errno == EINTR)
+                                                continue;
+
+                                        return n > 0 ? n : -errno;
+                                }
+
+                                if (pollfd.revents != POLLIN)
+                                        return n > 0 ? n : -EIO;
+
+                                continue;
+                        }
+
+                        return n > 0 ? n : (k < 0 ? -errno : 0);
+                }
+
+                p += k;
+                nbytes -= k;
+                n += k;
+        }
+
+        return n;
+}
+
+int path_is_mount_point(const char *t) {
+        struct stat a, b;
+        char *copy;
+
+        if (lstat(t, &a) < 0) {
+
+                if (errno == ENOENT)
+                        return 0;
+
+                return -errno;
+        }
+
+        if (!(copy = strdup(t)))
+                return -ENOMEM;
+
+        if (lstat(dirname(copy), &b) < 0) {
+                free(copy);
+                return -errno;
+        }
+
+        free(copy);
+
+        return a.st_dev != b.st_dev;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
diff --git a/util.h b/util.h
index 6728eb143a2cc4b2777e846a08cf0123c8f27c88..6f87894d15251e454fc4b4daf945399ac535e6f5 100644 (file)
--- a/util.h
+++ b/util.h
@@ -213,6 +213,12 @@ int flush_fd(int fd);
 
 int ignore_signal(int sig);
 
+int close_pipe(int p[]);
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes);
+
+int path_is_mount_point(const char *path);
+
 extern char * __progname;
 
 const char *ioprio_class_to_string(int i);