[systemd-devel] [PATCH 4/7] Session mode

Hristo Venev hristo at venev.name
Sun Dec 1 11:25:55 PST 2013


systemctl --session restart gnome-settings-daemon
Add a new environment variable:
    XDG_SESSION_DIR=/run/session/$XDG_SESSION_ID

The session instance runs in session-*.scope and is started as a normal
process inside a session. The socket is stored in
$XDG_SESSION_DIR/systemd/private

It would be a good idea to implement DBus activation for most present
user services like the 47 I have in /usr/share/dbus-1/services. My
xsession also puts the DBus socket in $XDG_SESSION_DIR/dbus/session_bus_socket.
Good enough to become default? That's another patch series.
---
 Makefile.am                           |  33 +++++
 src/core/dbus.c                       |  17 +++
 src/core/main.c                       |  18 ++-
 src/core/service.c                    |   1 +
 src/core/unit-printf.c                |  56 +++++++++
 src/core/unit.c                       |  24 ++++
 src/libsystemd-bus/bus-util.c         |  46 +++++++
 src/libsystemd-bus/bus-util.h         |   1 +
 src/libsystemd-bus/sd-bus.c           |  55 +++++++++
 src/login/logind-dbus.c               |   5 +-
 src/login/logind-session-dbus.c       |   6 +-
 src/login/logind-session.c            |  36 +++++-
 src/login/logind-session.h            |   1 +
 src/login/pam-module.c                |  11 +-
 src/run/run.c                         |   7 ++
 src/shared/install.c                  |  32 ++++-
 src/shared/install.h                  |   2 +
 src/shared/path-lookup.c              | 225 +++++++++++++++++++++++++++++++++-
 src/shared/path-lookup.h              |   2 +
 src/systemctl/systemctl.c             |  14 +++
 src/systemd/sd-bus.h                  |   2 +
 units/session/.gitignore              |   1 +
 units/session/Makefile                |   1 +
 units/session/default.target          |  11 ++
 units/session/exit.target             |  17 +++
 units/session/systemd-exit.service.in |  17 +++
 26 files changed, 629 insertions(+), 12 deletions(-)
 create mode 100644 units/session/.gitignore
 create mode 120000 units/session/Makefile
 create mode 100644 units/session/default.target
 create mode 100644 units/session/exit.target
 create mode 100644 units/session/systemd-exit.service.in

diff --git a/Makefile.am b/Makefile.am
index 7a45029..985a7f7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -84,6 +84,7 @@ catalogstatedir=$(systemdstatedir)/catalog
 # Our own, non-special dirs
 pkgsysconfdir=$(sysconfdir)/systemd
 userunitdir=$(prefix)/lib/systemd/user
+sessionunitdir=$(prefix)/lib/systemd/session
 userpresetdir=$(prefix)/lib/systemd/user-preset
 tmpfilesdir=$(prefix)/lib/tmpfiles.d
 sysctldir=$(prefix)/lib/sysctl.d
@@ -91,6 +92,7 @@ networkdir=$(prefix)/lib/systemd/network
 pkgincludedir=$(includedir)/systemd
 systemgeneratordir=$(rootlibexecdir)/system-generators
 usergeneratordir=$(prefix)/lib/systemd/user-generators
+sessiongeneratordir=$(prefix)/lib/systemd/session-generators
 systemshutdowndir=$(rootlibexecdir)/system-shutdown
 systemsleepdir=$(rootlibexecdir)/system-sleep
 systemunitdir=$(rootprefix)/lib/systemd/system
@@ -154,6 +156,8 @@ AM_CPPFLAGS = \
 	-DSYSTEM_SYSVRCND_PATH=\"$(SYSTEM_SYSVRCND_PATH)\" \
 	-DUSER_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/user\" \
 	-DUSER_DATA_UNIT_PATH=\"$(userunitdir)\" \
+	-DSESSION_CONFIG_UNIT_PATH=\"$(pkgsysconfdir)/session\" \
+	-DSESSION_DATA_UNIT_PATH=\"$(sessionunitdir)\" \
 	-DCATALOG_DATABASE=\"$(catalogstatedir)/database\" \
 	-DSYSTEMD_CGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \
 	-DSYSTEMD_BINARY_PATH=\"$(rootlibexecdir)/systemd\" \
@@ -168,6 +172,7 @@ AM_CPPFLAGS = \
 	-DSYSTEMD_CRYPTSETUP_PATH=\"$(rootlibexecdir)/systemd-cryptsetup\" \
 	-DSYSTEM_GENERATOR_PATH=\"$(systemgeneratordir)\" \
 	-DUSER_GENERATOR_PATH=\"$(usergeneratordir)\" \
+	-DSESSION_GENERATOR_PATH=\"$(sessiongeneratordir)\" \
 	-DSYSTEM_SHUTDOWN_PATH=\"$(systemshutdowndir)\" \
 	-DSYSTEM_SLEEP_PATH=\"$(systemsleepdir)\" \
 	-DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" \
@@ -223,6 +228,7 @@ TIMERS_TARGET_WANTS =
 
 SYSTEM_UNIT_ALIASES =
 USER_UNIT_ALIASES =
+SESSION_UNIT_ALIASES =
 
 GENERAL_ALIASES =
 
@@ -257,6 +263,8 @@ install-aliases-hook:
 		dir=$(systemunitdir) && $(install-aliases)
 	set -- $(USER_UNIT_ALIASES) && \
 		dir=$(userunitdir) && $(install-aliases)
+	set -- $(SESSION_UNIT_ALIASES) && \
+		dir=$(sessionunitdir) && $(install-aliases)
 	set -- $(GENERAL_ALIASES) && \
 		dir= && $(install-aliases)
 
@@ -460,9 +468,16 @@ dist_userunit_DATA = \
 	units/user/default.target \
 	units/user/exit.target
 
+dist_sessionunit_DATA = \
+	units/session/default.target \
+	units/session/exit.target
+
 nodist_userunit_DATA = \
 	units/user/systemd-exit.service
 
+nodist_sessionunit_DATA = \
+	units/session/systemd-exit.service
+
 EXTRA_DIST += \
 	units/getty at .service.m4 \
 	units/serial-getty at .service.m4 \
@@ -4354,9 +4369,11 @@ substitutions = \
        '|pkgsysconfdir=$(pkgsysconfdir)|' \
        '|SYSTEM_CONFIG_UNIT_PATH=$(pkgsysconfdir)/system|' \
        '|USER_CONFIG_UNIT_PATH=$(pkgsysconfdir)/user|' \
+       '|SESSION_CONFIG_UNIT_PATH=$(pkgsysconfdir)/session|' \
        '|pkgdatadir=$(pkgdatadir)|' \
        '|systemunitdir=$(systemunitdir)|' \
        '|userunitdir=$(userunitdir)|' \
+       '|sessionunitdir=$(sessionunitdir)|' \
        '|systempresetdir=$(systempresetdir)|' \
        '|userpresetdir=$(userpresetdir)|' \
        '|udevhwdbdir=$(udevhwdbdir)|' \
@@ -4366,6 +4383,7 @@ substitutions = \
        '|sysctldir=$(sysctldir)|' \
        '|systemgeneratordir=$(systemgeneratordir)|' \
        '|usergeneratordir=$(usergeneratordir)|' \
+       '|sessiongeneratordir=$(sessiongeneratordir)|' \
        '|PACKAGE_VERSION=$(PACKAGE_VERSION)|' \
        '|PACKAGE_NAME=$(PACKAGE_NAME)|' \
        '|PACKAGE_URL=$(PACKAGE_URL)|' \
@@ -4451,6 +4469,7 @@ EXTRA_DIST += \
 CLEANFILES += \
 	$(nodist_systemunit_DATA) \
 	$(nodist_userunit_DATA) \
+	$(nodist_sessionunit_DATA) \
 	$(pkgconfigdata_DATA) \
 	$(pkgconfiglib_DATA) \
 	$(nodist_polkitpolicy_DATA)
@@ -4591,10 +4610,21 @@ USER_UNIT_ALIASES += \
 	$(systemunitdir)/sound.target sound.target \
 	$(systemunitdir)/smartcard.target smartcard.target
 
+SESSION_UNIT_ALIASES += \
+	$(systemunitdir)/shutdown.target shutdown.target \
+	$(systemunitdir)/sockets.target sockets.target \
+	$(systemunitdir)/timers.target timers.target \
+	$(systemunitdir)/paths.target paths.target \
+	$(systemunitdir)/bluetooth.target bluetooth.target \
+	$(systemunitdir)/printer.target printer.target \
+	$(systemunitdir)/sound.target sound.target \
+	$(systemunitdir)/smartcard.target smartcard.target
+
 GENERAL_ALIASES += \
 	$(systemunitdir)/remote-fs.target $(pkgsysconfdir)/system/multi-user.target.wants/remote-fs.target \
 	$(systemunitdir)/getty at .service $(pkgsysconfdir)/system/getty.target.wants/getty at tty1.service \
 	$(pkgsysconfdir)/user $(sysconfdir)/xdg/systemd/user \
+	$(pkgsysconfdir)/session $(sysconfdir)/xdg/systemd/session \
 	../system-services/org.freedesktop.systemd1.service $(dbussessionservicedir)/org.freedesktop.systemd1.service
 
 if HAVE_SYSV_COMPAT
@@ -4619,12 +4649,15 @@ INSTALL_DIRS += \
 	$(systemsleepdir) \
 	$(systemgeneratordir) \
 	$(usergeneratordir) \
+	$(sessiongeneratordir) \
 	\
 	$(userunitdir) \
+	$(sessionunitdir) \
 	$(pkgsysconfdir)/system \
 	$(pkgsysconfdir)/system/multi-user.target.wants \
 	$(pkgsysconfdir)/system/getty.target.wants \
 	$(pkgsysconfdir)/user \
+	$(pkgsysconfdir)/session \
 	$(dbussessionservicedir) \
 	$(sysconfdir)/xdg/systemd
 
diff --git a/src/core/dbus.c b/src/core/dbus.c
index ef9a64b..024f5d3 100644
--- a/src/core/dbus.c
+++ b/src/core/dbus.c
@@ -986,6 +986,23 @@ static int bus_init_private(Manager *m) {
                 salen = sizeof(sa.un) - left;
 
                 mkdir_parents_label(sa.un.sun_path, 0755);
+        } else {
+                size_t left = sizeof(sa.un.sun_path);
+                char *p = sa.un.sun_path;
+                const char *e;
+
+                e = secure_getenv("XDG_SESSION_DIR");
+                if (!e) {
+                        log_error("Failed to determine XDG_SESSION_DIR");
+                        return -EHOSTDOWN;
+                }
+
+                left = strpcpy(&p, left, e);
+                left = strpcpy(&p, left, "/systemd/private");
+
+                salen = sizeof(sa.un) - left;
+
+                mkdir_parents_label(sa.un.sun_path, 0755);
         }
 
         unlink(sa.un.sun_path);
diff --git a/src/core/main.c b/src/core/main.c
index ce5b64c..3c6a8aa 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -739,6 +739,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_UNIT,
                 ARG_SYSTEM,
                 ARG_USER,
+                ARG_SESSION,
                 ARG_TEST,
                 ARG_VERSION,
                 ARG_DUMP_CONFIGURATION_ITEMS,
@@ -760,6 +761,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "unit",                     required_argument, NULL, ARG_UNIT                     },
                 { "system",                   no_argument,       NULL, ARG_SYSTEM                   },
                 { "user",                     no_argument,       NULL, ARG_USER                     },
+                { "session",                  no_argument,       NULL, ARG_SESSION                  },
                 { "test",                     no_argument,       NULL, ARG_TEST                     },
                 { "help",                     no_argument,       NULL, 'h'                          },
                 { "version",                  no_argument,       NULL, ARG_VERSION                  },
@@ -863,6 +865,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_running_as = SYSTEMD_USER;
                         break;
 
+                case ARG_SESSION:
+                        arg_running_as = SYSTEMD_SESSION;
+                        break;
+
                 case ARG_TEST:
                         arg_action = ACTION_TEST;
                         break;
@@ -1008,6 +1014,7 @@ static int help(void) {
                "     --unit=UNIT                 Set default unit\n"
                "     --system                    Run a system instance, even if PID != 1\n"
                "     --user                      Run a user instance\n"
+               "     --session                   Run a session instance\n"
                "     --dump-core[=0|1]           Dump core on crash\n"
                "     --crash-shell[=0|1]         Run shell on crash\n"
                "     --confirm-spawn[=0|1]       Ask for confirmation when spawning processes\n"
@@ -1395,7 +1402,7 @@ int main(int argc, char *argv[]) {
         if (arg_running_as != SYSTEMD_SYSTEM &&
             arg_action == ACTION_RUN &&
             sd_booted() <= 0) {
-                log_error("Trying to run as user instance, but the system has not been booted with systemd.");
+                log_error("Trying to run as user or session instance, but the system has not been booted with systemd.");
                 goto finish;
         }
 
@@ -1427,6 +1434,13 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
+
+        if (arg_running_as == SYSTEMD_SESSION &&
+            !getenv("XDG_SESSION_DIR")) {
+                log_error("Trying to run as session instance, but $XDG_SESSION_DIR is not set.");
+                goto finish;
+        }
+
         assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST);
 
         /* Close logging fds, in order not to confuse fdset below */
@@ -1774,7 +1788,7 @@ finish:
                         args[i++] = SYSTEMD_BINARY_PATH;
                         if (switch_root_dir)
                                 args[i++] = "--switched-root";
-                        args[i++] = arg_running_as == SYSTEMD_SYSTEM ? "--system" : "--user";
+                        args[i++] = arg_running_as == SYSTEMD_SYSTEM ? "--system" : arg_running_as == SYSTEMD_USER ? "--user" : "--session";
                         args[i++] = "--deserialize";
                         args[i++] = sfd;
                         args[i++] = NULL;
diff --git a/src/core/service.c b/src/core/service.c
index 76de567..676ec86 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1160,6 +1160,7 @@ static int service_add_default_dependencies(Service *s) {
                                                       SPECIAL_PATHS_TARGET, NULL, true);
                 if (r < 0)
                         return r;
+
         }
 
         /* Second, activate normal shutdown */
diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c
index 1a29a98..910a908 100644
--- a/src/core/unit-printf.c
+++ b/src/core/unit-printf.c
@@ -154,6 +154,60 @@ static int specifier_cgroup_root(char specifier, void *data, void *userdata, cha
         return 0;
 }
 
+static int specifier_session_id(char specifier, void *data, void *userdata, char **ret) {
+        Unit *u = userdata;
+        char *n = NULL;
+
+        assert(u);
+
+        if (u->manager->running_as == SYSTEMD_SESSION) {
+                const char *e;
+
+                e = getenv("XDG_SESSION_ID");
+                if (e) {
+                        n = strdup(e);
+                        if (!n)
+                                return -ENOMEM;
+                }
+        }
+
+        if (!n) {
+                n = strdup("");
+                if (!n)
+                        return -ENOMEM;
+        }
+
+        *ret = n;
+        return 0;
+}
+
+static int specifier_session_dir(char specifier, void *data, void *userdata, char **ret) {
+        Unit *u = userdata;
+        char *n = NULL;
+
+        assert(u);
+
+        if (u->manager->running_as == SYSTEMD_SESSION) {
+                const char *e;
+
+                e = getenv("XDG_SESSION_DIR");
+                if (e) {
+                        n = strdup(e);
+                        if (!n)
+                                return -ENOMEM;
+                }
+        }
+
+        if (!n) {
+                n = strdup("");
+                if (!n)
+                        return -ENOMEM;
+        }
+
+        *ret = n;
+        return 0;
+}
+
 static int specifier_runtime(char specifier, void *data, void *userdata, char **ret) {
         Unit *u = userdata;
         char *n = NULL;
@@ -351,6 +405,8 @@ int unit_full_printf(Unit *u, const char *format, char **ret) {
                 { 'u', specifier_user_name,           NULL },
                 { 'h', specifier_user_home,           NULL },
                 { 's', specifier_user_shell,          NULL },
+                { 'E', specifier_session_id,          NULL },
+                { 'e', specifier_session_dir,         NULL },
 
                 { 'm', specifier_machine_id,          NULL },
                 { 'H', specifier_host_name,           NULL },
diff --git a/src/core/unit.c b/src/core/unit.c
index 69e701c..c963870 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -2734,6 +2734,16 @@ static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, c
                         return -ENOENT;
 
                 p = strjoin(c, "/", u->id, ".d", NULL);
+        } else if (u->manager->running_as == SYSTEMD_SESSION) {
+                _cleanup_free_ char *c = NULL;
+
+                r = session_config_home(&c);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -ENOENT;
+
+                p = strjoin(c, "/", u->id, ".d", NULL);
         } else if (mode & UNIT_PERSISTENT)
                 p = strjoin("/etc/systemd/system/", u->id, ".d", NULL);
         else
@@ -2883,6 +2893,20 @@ int unit_make_transient(Unit *u) {
                         return -ENOMEM;
 
                 mkdir_p(c, 0755);
+        } else if (u->manager->running_as == SYSTEMD_SESSION) {
+                _cleanup_free_ char *c = NULL;
+
+                r = session_config_home(&c);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -ENOENT;
+
+                u->fragment_path = strjoin(c, "/", u->id, NULL);
+                if (!u->fragment_path)
+                        return -ENOMEM;
+
+                mkdir_p(c, 0755);
         } else {
                 u->fragment_path = strappend("/run/systemd/system/", u->id);
                 if (!u->fragment_path)
diff --git a/src/libsystemd-bus/bus-util.c b/src/libsystemd-bus/bus-util.c
index 9459e6f..0c51eae 100644
--- a/src/libsystemd-bus/bus-util.c
+++ b/src/libsystemd-bus/bus-util.c
@@ -504,6 +504,46 @@ int bus_open_user_systemd(sd_bus **_bus) {
         return 0;
 }
 
+int bus_open_session_systemd(sd_bus **_bus) {
+        _cleanup_bus_unref_ sd_bus *bus = NULL;
+        _cleanup_free_ char *p = NULL;
+        const char *e;
+        int r;
+
+        /* If we are supposed to talk to the instance, try via
+         * XDG_SESSION_DIR first, then fallback to normal bus
+         * access */
+
+        assert(_bus);
+
+        e = secure_getenv("XDG_SESSION_DIR");
+        if (e) {
+                if (asprintf(&p, "unix:path=%s/systemd/private", e) < 0)
+                        return -ENOMEM;
+        }
+
+        r = sd_bus_new(&bus);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_set_address(bus, p);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_start(bus);
+        if (r < 0)
+                return r;
+
+        r = bus_check_peercred(bus);
+        if (r < 0)
+                return r;
+
+        *_bus = bus;
+        bus = NULL;
+
+        return 0;
+}
+
 int bus_print_property(const char *name, sd_bus_message *property, bool all) {
         char type;
         const char *contents;
@@ -973,6 +1013,9 @@ int bus_open_transport(BusTransport transport, const char *host, SystemdRunningA
                         case SYSTEMD_USER:
                                 r = sd_bus_default_user(bus);
                                 break;
+                        case SYSTEMD_SESSION:
+                                r = sd_bus_default_session(bus);
+                                break;
                         default:
                                 assert_not_reached("Unknown running_as.");
                 }
@@ -1012,6 +1055,9 @@ int bus_open_transport_systemd(BusTransport transport, const char *host, Systemd
                         case SYSTEMD_USER:
                                 r = bus_open_user_systemd(bus);
                                 break;
+                        case SYSTEMD_SESSION:
+                                r = bus_open_session_systemd(bus);
+                                break;
                         default:
                                 assert_not_reached("Unknown running_as.");
                 }
diff --git a/src/libsystemd-bus/bus-util.h b/src/libsystemd-bus/bus-util.h
index 32ad8f9..a23ff73 100644
--- a/src/libsystemd-bus/bus-util.h
+++ b/src/libsystemd-bus/bus-util.h
@@ -68,6 +68,7 @@ void bus_verify_polkit_async_registry_free(sd_bus *bus, Hashmap *registry);
 
 int bus_open_system_systemd(sd_bus **_bus);
 int bus_open_user_systemd(sd_bus **_bus);
+int bus_open_session_systemd(sd_bus **_bus);
 
 int bus_open_transport(BusTransport transport, const char *host, SystemdRunningAs running_as, sd_bus **bus);
 int bus_open_transport_systemd(BusTransport transport, const char *host, SystemdRunningAs running_as, sd_bus **bus);
diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c
index 1ed08c0..9140a3f 100644
--- a/src/libsystemd-bus/sd-bus.c
+++ b/src/libsystemd-bus/sd-bus.c
@@ -1076,6 +1076,55 @@ fail:
         return r;
 }
 
+_public_ int sd_bus_open_session(sd_bus **ret) {
+        const char *e;
+        sd_bus *b;
+        size_t l;
+        int r;
+
+        assert_return(ret, -EINVAL);
+
+        r = sd_bus_new(&b);
+        if (r < 0)
+                return r;
+
+        e = secure_getenv("DBUS_SESSION_BUS_ADDRESS");
+        if (e) {
+                r = sd_bus_set_address(b, e);
+                if (r < 0)
+                        goto fail;
+        } else {
+                e = secure_getenv("XDG_SESSION_DIR");
+                if (!e) {
+                        r = -ENOENT;
+                        goto fail;
+                }
+
+                l = strlen(e);
+                if (l + 4 > sizeof(b->sockaddr.un.sun_path)) {
+                        r = -E2BIG;
+                        goto fail;
+                }
+
+                b->sockaddr.un.sun_family = AF_UNIX;
+                memcpy(mempcpy(b->sockaddr.un.sun_path, e, l), "/bus", 4);
+                b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l + 4;
+        }
+
+        b->bus_client = true;
+
+        r = sd_bus_start(b);
+        if (r < 0)
+                goto fail;
+
+        *ret = b;
+        return 0;
+
+fail:
+        bus_free(b);
+        return r;
+}
+
 _public_ int sd_bus_open_system_remote(const char *host, sd_bus **ret) {
         _cleanup_free_ char *e = NULL;
         char *p = NULL;
@@ -2703,6 +2752,12 @@ _public_ int sd_bus_default_user(sd_bus **ret) {
         return bus_default(sd_bus_open_user, &default_user_bus, ret);
 }
 
+_public_ int sd_bus_default_session(sd_bus **ret) {
+        static __thread sd_bus *default_session_bus = NULL;
+
+        return bus_default(sd_bus_open_session, &default_session_bus, ret);
+}
+
 _public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) {
         assert_return(b, -EINVAL);
         assert_return(tid, -EINVAL);
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index c3518f6..4708820 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -586,10 +586,11 @@ static int method_create_session(sd_bus *bus, sd_bus_message *message, void *use
                         return -ENOMEM;
 
                 return sd_bus_reply_method_return(
-                                message, "soshusub",
+                                message, "sosshusub",
                                 session->id,
                                 path,
                                 session->user->runtime_path,
+                                session->session_path,
                                 fifo_fd,
                                 (uint32_t) session->user->uid,
                                 session->seat ? session->seat->id : "",
@@ -1888,7 +1889,7 @@ const sd_bus_vtable manager_vtable[] = {
         SD_BUS_METHOD("ListUsers", NULL, "a(uso)", method_list_users, 0),
         SD_BUS_METHOD("ListSeats", NULL, "a(so)", method_list_seats, 0),
         SD_BUS_METHOD("ListInhibitors", NULL, "a(ssssuu)", method_list_inhibitors, 0),
-        SD_BUS_METHOD("CreateSession", "uussssussbssa(sv)", "soshusub", method_create_session, 0),
+        SD_BUS_METHOD("CreateSession", "uussssussbssa(sv)", "sosshusub", method_create_session, 0),
         SD_BUS_METHOD("ReleaseSession", "s", NULL, method_release_session, 0),
         SD_BUS_METHOD("ActivateSession", "s", NULL, method_activate_session, 0),
         SD_BUS_METHOD("ActivateSessionOnSeat", "ss", NULL, method_activate_session_on_seat, 0),
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index 4bbe75e..bdb0639 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -673,19 +673,21 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
                 return -ENOMEM;
 
         log_debug("Sending reply about created session: "
-                  "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
+                  "id=%s object_path=%s runtime_path=%s session_path=%s session_fd=%d seat=%s vtnr=%u",
                   s->id,
                   p,
                   s->user->runtime_path,
+                  s->session_path,
                   fifo_fd,
                   s->seat ? s->seat->id : "",
                   (uint32_t) s->vtnr);
 
         return sd_bus_reply_method_return(
-                        c, "soshusub",
+                        c, "sosshusub",
                         s->id,
                         p,
                         s->user->runtime_path,
+                        s->session_path,
                         fifo_fd,
                         (uint32_t) s->user->uid,
                         s->seat ? s->seat->id : "",
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 66292ef..c5a3878 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -88,6 +88,8 @@ Session* session_new(Manager *m, const char *id) {
                 return NULL;
         }
 
+        s->session_path = strappend("/run/session/", id);
+
         s->manager = m;
         s->fifo_fd = -1;
         s->vtfd = -1;
@@ -107,6 +109,8 @@ void session_free(Session *s) {
 
         session_drop_controller(s);
 
+        free(s->session_path);
+
         while ((sd = hashmap_first(s->devices)))
                 session_device_free(sd);
 
@@ -193,7 +197,8 @@ int session_save(Session *s) {
                 s->user->name,
                 session_is_active(s),
                 session_state_to_string(session_get_state(s)),
-                s->remote);
+                s->remote,
+                s->session_path);
 
         if (s->type >= 0)
                 fprintf(f, "TYPE=%s\n", session_type_to_string(s->type));
@@ -568,6 +573,14 @@ int session_start(Session *s) {
         if (r < 0)
                 return r;
 
+        r = mkdir_safe_label("/run/session", 0755, 0, 0);
+        if (r < 0)
+                return r;
+
+        r = mkdir_safe_label(s->session_path, 0700, s->user->uid, s->user->gid);
+        if (r < 0)
+                return r;
+
         /* Create cgroup */
         r = session_start_scope(s);
         if (r < 0)
@@ -671,6 +684,24 @@ int session_stop(Session *s) {
         return r;
 }
 
+static int session_remove_session_path(Session *s) {
+        int r;
+
+        assert(s);
+
+        if (!s->session_path)
+                return 0;
+
+        r = rm_rf(s->session_path, false, true, false);
+        if (r < 0)
+                log_error("Failed to remove session directory %s: %s", s->session_path, strerror(-r));
+
+        free(s->session_path);
+        s->session_path = NULL;
+
+        return r;
+}
+
 int session_finalize(Session *s) {
         int r = 0;
         SessionDevice *sd;
@@ -696,6 +727,9 @@ int session_finalize(Session *s) {
         /* Remove X11 symlink */
         session_unlink_x11_socket(s);
 
+        /* Kill XDG_SESSION_DIR */
+        session_remove_session_path(s);
+
         unlink(s->state_file);
         session_add_to_gc_queue(s);
         user_add_to_gc_queue(s->user);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index ee93101..fd74816 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -69,6 +69,7 @@ struct Session {
         Manager *manager;
 
         char *id;
+        char *session_path;
         SessionType type;
         SessionClass class;
 
diff --git a/src/login/pam-module.c b/src/login/pam-module.c
index 45428a0..c9f9f48 100644
--- a/src/login/pam-module.c
+++ b/src/login/pam-module.c
@@ -174,7 +174,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
         const char
-                *username, *id, *object_path, *runtime_path,
+                *username, *id, *object_path, *runtime_path, *session_path,
                 *service = NULL,
                 *tty = NULL, *display = NULL,
                 *remote_user = NULL, *remote_host = NULL,
@@ -351,10 +351,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         }
 
         r = sd_bus_message_read(reply,
-                                "soshusub",
+                                "sosshusub",
                                 &id,
                                 &object_path,
                                 &runtime_path,
+                                &session_path,
                                 &session_fd,
                                 &original_uid,
                                 &seat,
@@ -383,6 +384,12 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                  * in privileged apps clobbering the runtime directory
                  * unnecessarily. */
 
+                r = pam_misc_setenv(handle, "XDG_SESSION_DIR", session_path, 0);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to set session dir.");
+                        return r;
+                }
+
                 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
                 if (r != PAM_SUCCESS) {
                         pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
diff --git a/src/run/run.c b/src/run/run.c
index ad23e83..537b725 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -47,6 +47,7 @@ static int help(void) {
                "  -h --help               Show this help\n"
                "     --version            Show package version\n"
                "     --user               Run as user unit\n"
+               "     --session            Run as session unit\n"
                "  -H --host=[USER@]HOST   Operate on remote host\n"
                "  -M --machine=CONTAINER  Operate on local container\n"
                "     --scope              Run this as scope rather than service\n"
@@ -65,6 +66,7 @@ static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_VERSION = 0x100,
                 ARG_USER,
+                ARG_SESSION,
                 ARG_SYSTEM,
                 ARG_SCOPE,
                 ARG_UNIT,
@@ -77,6 +79,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "help",              no_argument,       NULL, 'h'             },
                 { "version",           no_argument,       NULL, ARG_VERSION     },
                 { "user",              no_argument,       NULL, ARG_USER        },
+                { "session",           no_argument,       NULL, ARG_SESSION     },
                 { "system",            no_argument,       NULL, ARG_SYSTEM      },
                 { "scope",             no_argument,       NULL, ARG_SCOPE       },
                 { "unit",              required_argument, NULL, ARG_UNIT        },
@@ -106,6 +109,10 @@ static int parse_argv(int argc, char *argv[]) {
                         puts(SYSTEMD_FEATURES);
                         return 0;
 
+                case ARG_SESSION:
+                        arg_as = SYSTEMD_SESSION;
+                        break;
+
                 case ARG_USER:
                         arg_as = SYSTEMD_USER;
                         break;
diff --git a/src/shared/install.c b/src/shared/install.c
index 100ed69..8199bdb 100644
--- a/src/shared/install.c
+++ b/src/shared/install.c
@@ -55,7 +55,7 @@ static int lookup_paths_init_from_scope(LookupPaths *paths, UnitFileScope scope)
         zero(*paths);
 
         return lookup_paths_init(paths,
-                                 scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : SYSTEMD_USER,
+                                 scope == UNIT_FILE_SYSTEM ? SYSTEMD_SYSTEM : scope == UNIT_FILE_USER || scope == UNIT_FILE_GLOBAL ? SYSTEMD_USER : SYSTEMD_SESSION,
                                  scope == UNIT_FILE_USER,
                                  NULL, NULL, NULL);
 }
@@ -105,6 +105,28 @@ static int get_config_path(UnitFileScope scope, bool runtime, const char *root_d
 
                 break;
 
+        case UNIT_FILE_GLOBAL_SESSION:
+
+                if (root_dir)
+                        return -EINVAL;
+
+                if (runtime)
+                        p = strdup("/run/systemd/session");
+                else
+                        p = strdup(SESSION_CONFIG_UNIT_PATH);
+                break;
+
+        case UNIT_FILE_SESSION:
+
+                if (root_dir || runtime)
+                        return -EINVAL;
+
+                r = session_config_home(&p);
+                if (r <= 0)
+                        return r < 0 ? r : -ENOENT;
+
+                break;
+
         default:
                 assert_not_reached("Bad scope");
         }
@@ -511,7 +533,7 @@ static int find_symlinks_in_scope(
         assert(scope < _UNIT_FILE_SCOPE_MAX);
         assert(name);
 
-        if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL) {
+        if (scope == UNIT_FILE_SYSTEM || scope == UNIT_FILE_GLOBAL || scope == UNIT_FILE_GLOBAL_SESSION) {
                 _cleanup_free_ char *path = NULL;
 
                 /* First look in runtime config path */
@@ -1765,6 +1787,12 @@ int unit_file_query_preset(UnitFileScope scope, const char *name) {
                                     "/usr/local/lib/systemd/user-preset",
                                     "/usr/lib/systemd/user-preset",
                                     NULL);
+        else if (scope == UNIT_FILE_GLOBAL_SESSION)
+                r = conf_files_list(&files, ".preset", NULL,
+                                    "/etc/systemd/session-preset",
+                                    "/usr/local/lib/systemd/session-preset",
+                                    "/usr/lib/systemd/session-preset",
+                                    NULL);
         else
                 return 1;
 
diff --git a/src/shared/install.h b/src/shared/install.h
index e87c57e..38905c2 100644
--- a/src/shared/install.h
+++ b/src/shared/install.h
@@ -27,6 +27,8 @@ typedef enum UnitFileScope {
         UNIT_FILE_SYSTEM,
         UNIT_FILE_GLOBAL,
         UNIT_FILE_USER,
+        UNIT_FILE_SESSION,
+        UNIT_FILE_GLOBAL_SESSION,
         _UNIT_FILE_SCOPE_MAX,
         _UNIT_FILE_SCOPE_INVALID = -1
 } UnitFileScope;
diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c
index be605ca..58b1315 100644
--- a/src/shared/path-lookup.c
+++ b/src/shared/path-lookup.c
@@ -34,7 +34,8 @@
 
 static const char* const systemd_running_as_table[_SYSTEMD_RUNNING_AS_MAX] = {
         [SYSTEMD_SYSTEM] = "system",
-        [SYSTEMD_USER] = "user"
+        [SYSTEMD_USER] = "user",
+        [SYSTEMD_SESSION] = "session",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs);
@@ -68,6 +69,35 @@ int user_config_home(char **config_home) {
         return 0;
 }
 
+int session_config_home(char **config_home) {
+        const char *e;
+        char *r;
+
+        e = getenv("XDG_CONFIG_HOME");
+        if (e) {
+                r = strappend(e, "/systemd/session");
+                if (!r)
+                        return -ENOMEM;
+
+                *config_home = r;
+                return 1;
+        } else {
+                const char *home;
+
+                home = getenv("HOME");
+                if (home) {
+                        r = strappend(home, "/.config/systemd/session");
+                        if (!r)
+                                return -ENOMEM;
+
+                        *config_home = r;
+                        return 1;
+                }
+        }
+
+        return 0;
+}
+
 static char** user_dirs(
                 const char *generator,
                 const char *generator_early,
@@ -235,6 +265,173 @@ fail:
         goto finish;
 }
 
+static char** session_dirs(
+                const char *generator,
+                const char *generator_early,
+                const char *generator_late) {
+
+        const char * const config_unit_paths[] = {
+                SESSION_CONFIG_UNIT_PATH,
+                "/etc/systemd/session",
+                "/run/systemd/session",
+                NULL
+        };
+
+        const char * const data_unit_paths[] = {
+                "/usr/local/lib/systemd/session",
+                "/usr/local/share/systemd/session",
+                SESSION_DATA_UNIT_PATH,
+                "/usr/lib/systemd/session",
+                "/usr/share/systemd/session",
+                NULL
+        };
+
+        const char *home, *e;
+        char *config_home = NULL, *data_home = NULL;
+        char **config_dirs = NULL, **data_dirs = NULL;
+        char **r = NULL, **t;
+
+        /* Implement the mechanisms defined in
+         *
+         * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+         *
+         * We look in both the config and the data dirs because we
+         * want to encourage that distributors ship their unit files
+         * as data, and allow overriding as configuration.
+         */
+
+        if (session_config_home(&config_home) < 0)
+                goto fail;
+
+        home = getenv("HOME");
+
+        e = getenv("XDG_CONFIG_DIRS");
+        if (e) {
+                config_dirs = strv_split(e, ":");
+                if (!config_dirs)
+                        goto fail;
+        }
+
+        /* We don't treat /etc/xdg/systemd here as the spec
+         * suggests because we assume that that is a link to
+         * /etc/systemd/ anyway. */
+
+        e = getenv("XDG_DATA_HOME");
+        if (e) {
+                if (asprintf(&data_home, "%s/systemd/session", e) < 0)
+                        goto fail;
+
+        } else if (home) {
+                if (asprintf(&data_home, "%s/.local/share/systemd/session", home) < 0)
+                        goto fail;
+
+                /* There is really no need for two unit dirs in $HOME,
+                 * except to be fully compliant with the XDG spec. We
+                 * now try to link the two dirs, so that we can
+                 * minimize disk seeks a little. Further down we'll
+                 * then filter out this link, if it is actually is
+                 * one. */
+
+                mkdir_parents_label(data_home, 0777);
+                (void) symlink("../../../.config/systemd/session", data_home);
+        }
+
+        e = getenv("XDG_DATA_DIRS");
+        if (e)
+                data_dirs = strv_split(e, ":");
+        else
+                data_dirs = strv_new("/usr/local/share",
+                                     "/usr/share",
+                                     NULL);
+        if (!data_dirs)
+                goto fail;
+
+        /* Now merge everything we found. */
+        if (generator_early) {
+                t = strv_append(r, generator_early);
+                if (!t)
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (config_home) {
+                t = strv_append(r, config_home);
+                if (!t)
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!strv_isempty(config_dirs)) {
+                t = strv_merge_concat(r, config_dirs, "/systemd/session");
+                if (!t)
+                        goto finish;
+                strv_free(r);
+                r = t;
+        }
+
+        t = strv_merge(r, (char**) config_unit_paths);
+        if (!t)
+                goto fail;
+        strv_free(r);
+        r = t;
+
+        if (generator) {
+                t = strv_append(r, generator);
+                if (!t)
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (data_home) {
+                t = strv_append(r, data_home);
+                if (!t)
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!strv_isempty(data_dirs)) {
+                t = strv_merge_concat(r, data_dirs, "/systemd/session");
+                if (!t)
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        t = strv_merge(r, (char**) data_unit_paths);
+        if (!t)
+                goto fail;
+        strv_free(r);
+        r = t;
+
+        if (generator_late) {
+                t = strv_append(r, generator_late);
+                if (!t)
+                        goto fail;
+                strv_free(r);
+                r = t;
+        }
+
+        if (!path_strv_make_absolute_cwd(r))
+                goto fail;
+
+finish:
+        free(config_home);
+        strv_free(config_dirs);
+        free(data_home);
+        strv_free(data_dirs);
+
+        return r;
+
+fail:
+        strv_free(r);
+        r = NULL;
+        goto finish;
+}
+
 int lookup_paths_init(
                 LookupPaths *p,
                 SystemdRunningAs running_as,
@@ -313,7 +510,33 @@ int lookup_paths_init(
 
                         if (!p->unit_path)
                                 return -ENOMEM;
+
+                } else if (running_as == SYSTEMD_SESSION) {
+                        if (personal)
+                                p->unit_path = session_dirs(generator, generator_early, generator_late);
+                        else
+                                p->unit_path = strv_new(
+                                                /* If you modify this you also want to modify
+                                                 * systemduserunitpath= in systemd.pc.in, and
+                                                 * the arrays in user_dirs() above! */
+                                                STRV_IFNOTNULL(generator_early),
+                                                SESSION_CONFIG_UNIT_PATH,
+                                                "/etc/systemd/session",
+                                                "/run/systemd/session",
+                                                STRV_IFNOTNULL(generator),
+                                                "/usr/local/lib/systemd/session",
+                                                "/usr/local/share/systemd/session",
+                                                SESSION_DATA_UNIT_PATH,
+                                                "/usr/lib/systemd/session",
+                                                "/usr/share/systemd/session",
+                                                STRV_IFNOTNULL(generator_late),
+                                                NULL);
+
+                        if (!p->unit_path)
+                                return -ENOMEM;
+
                 }
+
         }
 
         if (!path_strv_canonicalize(p->unit_path))
diff --git a/src/shared/path-lookup.h b/src/shared/path-lookup.h
index a3ef824..b1341aa 100644
--- a/src/shared/path-lookup.h
+++ b/src/shared/path-lookup.h
@@ -32,6 +32,7 @@ typedef struct LookupPaths {
 typedef enum SystemdRunningAs {
         SYSTEMD_SYSTEM,
         SYSTEMD_USER,
+        SYSTEMD_SESSION,
         _SYSTEMD_RUNNING_AS_MAX,
         _SYSTEMD_RUNNING_AS_INVALID = -1
 } SystemdRunningAs;
@@ -42,6 +43,7 @@ const char* systemd_running_as_to_string(SystemdRunningAs i) _const_;
 SystemdRunningAs systemd_running_as_from_string(const char *s) _pure_;
 
 int user_config_home(char **config_home);
+int session_config_home(char **config_home);
 
 int lookup_paths_init(LookupPaths *p, SystemdRunningAs running_as, bool personal, const char *generator, const char *generator_early, const char *generator_late);
 void lookup_paths_free(LookupPaths *p);
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index edc3cb6..3436784 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -4938,8 +4938,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 ARG_IGNORE_DEPENDENCIES,
                 ARG_VERSION,
                 ARG_USER,
+                ARG_SESSION,
                 ARG_SYSTEM,
                 ARG_GLOBAL,
+                ARG_GLOBAL_SESSION,
                 ARG_NO_BLOCK,
                 ARG_NO_LEGEND,
                 ARG_NO_PAGER,
@@ -4974,8 +4976,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "ignore-dependencies", no_argument,       NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */
                 { "ignore-inhibitors",   no_argument,       NULL, 'i'                     },
                 { "user",                no_argument,       NULL, ARG_USER                },
+                { "session",             no_argument,       NULL, ARG_SESSION             },
                 { "system",              no_argument,       NULL, ARG_SYSTEM              },
                 { "global",              no_argument,       NULL, ARG_GLOBAL              },
+                { "global-session",      no_argument,       NULL, ARG_GLOBAL_SESSION      },
                 { "no-block",            no_argument,       NULL, ARG_NO_BLOCK            },
                 { "no-legend",           no_argument,       NULL, ARG_NO_LEGEND           },
                 { "no-pager",            no_argument,       NULL, ARG_NO_PAGER            },
@@ -5130,6 +5134,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_as = SYSTEMD_USER;
                         break;
 
+                case ARG_SESSION:
+                        arg_scope = UNIT_FILE_SESSION;
+                        arg_as = SYSTEMD_SESSION;
+                        break;
+
                 case ARG_SYSTEM:
                         arg_scope = UNIT_FILE_SYSTEM;
                         arg_as = SYSTEMD_SYSTEM;
@@ -5140,6 +5149,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_as = SYSTEMD_SYSTEM;
                         break;
 
+                case ARG_GLOBAL_SESSION:
+                        arg_scope = UNIT_FILE_GLOBAL_SESSION;
+                        arg_as = SYSTEMD_SYSTEM;
+                        break;
+
                 case ARG_NO_BLOCK:
                         arg_no_block = true;
                         break;
diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h
index 1cce9c5..16465ba 100644
--- a/src/systemd/sd-bus.h
+++ b/src/systemd/sd-bus.h
@@ -89,9 +89,11 @@ typedef int (*sd_bus_node_enumerator_t) (sd_bus *bus, const char *path, void *us
 /* Connections */
 
 int sd_bus_default_user(sd_bus **ret);
+int sd_bus_default_session(sd_bus **ret);
 int sd_bus_default_system(sd_bus **ret);
 
 int sd_bus_open_user(sd_bus **ret);
+int sd_bus_open_session(sd_bus **ret);
 int sd_bus_open_system(sd_bus **ret);
 int sd_bus_open_system_remote(const char *host, sd_bus **ret);
 int sd_bus_open_system_container(const char *machine, sd_bus **ret);
diff --git a/units/session/.gitignore b/units/session/.gitignore
new file mode 100644
index 0000000..41a74f5
--- /dev/null
+++ b/units/session/.gitignore
@@ -0,0 +1 @@
+/systemd-exit.service
diff --git a/units/session/Makefile b/units/session/Makefile
new file mode 120000
index 0000000..50be211
--- /dev/null
+++ b/units/session/Makefile
@@ -0,0 +1 @@
+../../src/Makefile
\ No newline at end of file
diff --git a/units/session/default.target b/units/session/default.target
new file mode 100644
index 0000000..71eed51
--- /dev/null
+++ b/units/session/default.target
@@ -0,0 +1,11 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Default
+Documentation=man:systemd.special(7)
+AllowIsolate=yes
diff --git a/units/session/exit.target b/units/session/exit.target
new file mode 100644
index 0000000..b0ad24c
--- /dev/null
+++ b/units/session/exit.target
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Exit the Session
+Documentation=man:systemd.special(7)
+DefaultDependencies=no
+Requires=systemd-exit.service
+After=systemd-exit.service
+AllowIsolate=yes
+
+[Install]
+Alias=ctrl-alt-del.target
diff --git a/units/session/systemd-exit.service.in b/units/session/systemd-exit.service.in
new file mode 100644
index 0000000..987fab8
--- /dev/null
+++ b/units/session/systemd-exit.service.in
@@ -0,0 +1,17 @@
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Exit the Session
+Documentation=man:systemd.special(7)
+DefaultDependencies=no
+Requires=shutdown.target
+After=shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=@KILL@ -s 58 $MANAGERPID
-- 
1.8.4.4



More information about the systemd-devel mailing list