summaryrefslogtreecommitdiff
path: root/src/login/logind.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/login/logind.c')
-rw-r--r--src/login/logind.c317
1 files changed, 150 insertions, 167 deletions
diff --git a/src/login/logind.c b/src/login/logind.c
index 52fcee933c..8d85de9b43 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -5,25 +5,27 @@
#include <string.h>
#include <unistd.h>
-#include "libudev.h"
#include "sd-daemon.h"
+#include "sd-device.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
#include "cgroup-util.h"
#include "def.h"
+#include "device-util.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
#include "logind.h"
+#include "main-func.h"
#include "parse-util.h"
#include "process-util.h"
#include "selinux-util.h"
#include "signal-util.h"
#include "strv.h"
-#include "udev-util.h"
+#include "terminal-util.h"
static Manager* manager_unref(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
@@ -34,18 +36,21 @@ static int manager_new(Manager **ret) {
assert(ret);
- m = new0(Manager, 1);
+ m = new(Manager, 1);
if (!m)
return -ENOMEM;
- m->console_active_fd = -1;
- m->reserve_vt_fd = -1;
+ *m = (Manager) {
+ .console_active_fd = -1,
+ .reserve_vt_fd = -1,
+ };
m->idle_action_not_before_usec = now(CLOCK_MONOTONIC);
m->devices = hashmap_new(&string_hash_ops);
m->seats = hashmap_new(&string_hash_ops);
m->sessions = hashmap_new(&string_hash_ops);
+ m->sessions_by_leader = hashmap_new(NULL);
m->users = hashmap_new(NULL);
m->inhibitors = hashmap_new(&string_hash_ops);
m->buttons = hashmap_new(&string_hash_ops);
@@ -53,13 +58,9 @@ static int manager_new(Manager **ret) {
m->user_units = hashmap_new(&string_hash_ops);
m->session_units = hashmap_new(&string_hash_ops);
- if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
+ if (!m->devices || !m->seats || !m->sessions || !m->sessions_by_leader || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units)
return -ENOMEM;
- m->udev = udev_new();
- if (!m->udev)
- return -errno;
-
r = sd_event_default(&m->event);
if (r < 0)
return r;
@@ -112,6 +113,7 @@ static Manager* manager_unref(Manager *m) {
hashmap_free(m->devices);
hashmap_free(m->seats);
hashmap_free(m->sessions);
+ hashmap_free(m->sessions_by_leader);
hashmap_free(m->users);
hashmap_free(m->inhibitors);
hashmap_free(m->buttons);
@@ -126,20 +128,18 @@ static Manager* manager_unref(Manager *m) {
sd_event_source_unref(m->wall_message_timeout_source);
sd_event_source_unref(m->console_active_event_source);
- sd_event_source_unref(m->udev_seat_event_source);
- sd_event_source_unref(m->udev_device_event_source);
- sd_event_source_unref(m->udev_vcsa_event_source);
- sd_event_source_unref(m->udev_button_event_source);
sd_event_source_unref(m->lid_switch_ignore_event_source);
- safe_close(m->console_active_fd);
+#if ENABLE_UTMP
+ sd_event_source_unref(m->utmp_event_source);
+#endif
- udev_monitor_unref(m->udev_seat_monitor);
- udev_monitor_unref(m->udev_device_monitor);
- udev_monitor_unref(m->udev_vcsa_monitor);
- udev_monitor_unref(m->udev_button_monitor);
+ safe_close(m->console_active_fd);
- udev_unref(m->udev);
+ sd_device_monitor_unref(m->device_seat_monitor);
+ sd_device_monitor_unref(m->device_monitor);
+ sd_device_monitor_unref(m->device_vcsa_monitor);
+ sd_device_monitor_unref(m->device_button_monitor);
if (m->unlink_nologin)
(void) unlink_or_warn("/run/nologin");
@@ -163,8 +163,8 @@ static Manager* manager_unref(Manager *m) {
}
static int manager_enumerate_devices(Manager *m) {
- struct udev_list_entry *item = NULL, *first = NULL;
- _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL;
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ sd_device *d;
int r;
assert(m);
@@ -172,31 +172,17 @@ static int manager_enumerate_devices(Manager *m) {
/* Loads devices from udev and creates seats for them as
* necessary */
- e = udev_enumerate_new(m->udev);
- if (!e)
- return -ENOMEM;
-
- r = udev_enumerate_add_match_tag(e, "master-of-seat");
- if (r < 0)
- return r;
-
- r = udev_enumerate_add_match_is_initialized(e);
+ r = sd_device_enumerator_new(&e);
if (r < 0)
return r;
- r = udev_enumerate_scan_devices(e);
+ r = sd_device_enumerator_add_match_tag(e, "master-of-seat");
if (r < 0)
return r;
- first = udev_enumerate_get_list_entry(e);
- udev_list_entry_foreach(item, first) {
- _cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
+ FOREACH_DEVICE(e, d) {
int k;
- d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item));
- if (!d)
- return -ENOMEM;
-
k = manager_process_seat_device(m, d);
if (k < 0)
r = k;
@@ -206,8 +192,8 @@ static int manager_enumerate_devices(Manager *m) {
}
static int manager_enumerate_buttons(Manager *m) {
- _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL;
- struct udev_list_entry *item = NULL, *first = NULL;
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ sd_device *d;
int r;
assert(m);
@@ -217,35 +203,21 @@ static int manager_enumerate_buttons(Manager *m) {
if (manager_all_buttons_ignored(m))
return 0;
- e = udev_enumerate_new(m->udev);
- if (!e)
- return -ENOMEM;
-
- r = udev_enumerate_add_match_subsystem(e, "input");
+ r = sd_device_enumerator_new(&e);
if (r < 0)
return r;
- r = udev_enumerate_add_match_tag(e, "power-switch");
+ r = sd_device_enumerator_add_match_subsystem(e, "input", true);
if (r < 0)
return r;
- r = udev_enumerate_add_match_is_initialized(e);
+ r = sd_device_enumerator_add_match_tag(e, "power-switch");
if (r < 0)
return r;
- r = udev_enumerate_scan_devices(e);
- if (r < 0)
- return r;
-
- first = udev_enumerate_get_list_entry(e);
- udev_list_entry_foreach(item, first) {
- _cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
+ FOREACH_DEVICE(e, d) {
int k;
- d = udev_device_new_from_syspath(m->udev, udev_list_entry_get_name(item));
- if (!d)
- return -ENOMEM;
-
k = manager_process_button_device(m, d);
if (k < 0)
r = k;
@@ -373,7 +345,7 @@ static int manager_enumerate_users(Manager *m) {
static int parse_fdname(const char *fdname, char **session_id, dev_t *dev) {
_cleanup_strv_free_ char **parts = NULL;
_cleanup_free_ char *id = NULL;
- unsigned int major, minor;
+ unsigned major, minor;
int r;
parts = strv_split(fdname, "-");
@@ -565,67 +537,52 @@ static int manager_enumerate_inhibitors(Manager *m) {
return r;
}
-static int manager_dispatch_seat_udev(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
+static int manager_dispatch_seat_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
Manager *m = userdata;
assert(m);
+ assert(device);
- d = udev_monitor_receive_device(m->udev_seat_monitor);
- if (!d)
- return -ENOMEM;
-
- manager_process_seat_device(m, d);
+ manager_process_seat_device(m, device);
return 0;
}
-static int manager_dispatch_device_udev(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
+static int manager_dispatch_device_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
Manager *m = userdata;
assert(m);
+ assert(device);
- d = udev_monitor_receive_device(m->udev_device_monitor);
- if (!d)
- return -ENOMEM;
-
- manager_process_seat_device(m, d);
+ manager_process_seat_device(m, device);
return 0;
}
-static int manager_dispatch_vcsa_udev(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
+static int manager_dispatch_vcsa_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
Manager *m = userdata;
- const char *name;
+ const char *name, *action;
assert(m);
-
- d = udev_monitor_receive_device(m->udev_vcsa_monitor);
- if (!d)
- return -ENOMEM;
-
- name = udev_device_get_sysname(d);
+ assert(device);
/* Whenever a VCSA device is removed try to reallocate our
* VTs, to make sure our auto VTs never go away. */
- if (name && startswith(name, "vcsa") && streq_ptr(udev_device_get_action(d), "remove"))
+ if (sd_device_get_sysname(device, &name) >= 0 &&
+ startswith(name, "vcsa") &&
+ sd_device_get_property_value(device, "ACTION", &action) >= 0 &&
+ streq(action, "remove"))
seat_preallocate_vts(m->seat0);
return 0;
}
-static int manager_dispatch_button_udev(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_(udev_device_unrefp) struct udev_device *d = NULL;
+static int manager_dispatch_button_udev(sd_device_monitor *monitor, sd_device *device, void *userdata) {
Manager *m = userdata;
assert(m);
+ assert(device);
- d = udev_monitor_receive_device(m->udev_button_monitor);
- if (!d)
- return -ENOMEM;
-
- manager_process_button_device(m, d);
+ manager_process_button_device(m, device);
return 0;
}
@@ -791,7 +748,29 @@ static int manager_vt_switch(sd_event_source *src, const struct signalfd_siginfo
active = m->seat0->active;
if (!active || active->vtnr < 1) {
- log_warning("Received VT_PROCESS signal without a registered session on that VT.");
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ /* We are requested to acknowledge the VT-switch signal by the kernel but
+ * there's no registered sessions for the current VT. Normally this
+ * shouldn't happen but something wrong might have happened when we tried
+ * to release the VT. Better be safe than sorry, and try to release the VT
+ * one more time otherwise the user will be locked with the current VT. */
+
+ log_warning("Received VT_PROCESS signal without a registered session, restoring VT.");
+
+ /* At this point we only have the kernel mapping for referring to the
+ * current VT. */
+ fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0) {
+ log_warning_errno(fd, "Failed to open, ignoring: %m");
+ return 0;
+ }
+
+ r = vt_release(fd, true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to release VT, ignoring: %m");
+
return 0;
}
@@ -815,28 +794,28 @@ static int manager_connect_console(Manager *m) {
assert(m);
assert(m->console_active_fd < 0);
- /* On certain architectures (S390 and Xen, and containers),
- /dev/tty0 does not exist, so don't fail if we can't open
- it. */
+ /* On certain systems (such as S390, Xen, and containers) /dev/tty0 does not exist (as there is no VC), so
+ * don't fail if we can't open it. */
+
if (access("/dev/tty0", F_OK) < 0)
return 0;
m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (m->console_active_fd < 0) {
- /* On some systems the device node /dev/tty0 may exist
- * even though /sys/class/tty/tty0 does not. */
- if (errno == ENOENT)
+ /* On some systems /dev/tty0 may exist even though /sys/class/tty/tty0 does not. These are broken, but
+ * common. Let's complain but continue anyway. */
+ if (errno == ENOENT) {
+ log_warning_errno(errno, "System has /dev/tty0 but not /sys/class/tty/tty0/active which is broken, ignoring: %m");
return 0;
+ }
return log_error_errno(errno, "Failed to open /sys/class/tty/tty0/active: %m");
}
r = sd_event_add_io(m->event, &m->console_active_event_source, m->console_active_fd, 0, manager_dispatch_console, m);
- if (r < 0) {
- log_error("Failed to watch foreground console");
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch foreground console: %m");
/*
* SIGRTMIN is used as global VT-release signal, SIGRTMIN + 1 is used
@@ -845,17 +824,17 @@ static int manager_connect_console(Manager *m) {
* release events immediately.
*/
- if (SIGRTMIN + 1 > SIGRTMAX) {
- log_error("Not enough real-time signals available: %u-%u", SIGRTMIN, SIGRTMAX);
- return -EINVAL;
- }
+ if (SIGRTMIN + 1 > SIGRTMAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Not enough real-time signals available: %u-%u",
+ SIGRTMIN, SIGRTMAX);
assert_se(ignore_signals(SIGRTMIN + 1, -1) >= 0);
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN, -1) >= 0);
r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to subscribe to signal: %m");
return 0;
}
@@ -864,92 +843,100 @@ static int manager_connect_udev(Manager *m) {
int r;
assert(m);
- assert(!m->udev_seat_monitor);
- assert(!m->udev_device_monitor);
- assert(!m->udev_vcsa_monitor);
- assert(!m->udev_button_monitor);
+ assert(!m->device_seat_monitor);
+ assert(!m->device_monitor);
+ assert(!m->device_vcsa_monitor);
+ assert(!m->device_button_monitor);
- m->udev_seat_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
- if (!m->udev_seat_monitor)
- return -ENOMEM;
+ r = sd_device_monitor_new(&m->device_seat_monitor);
+ if (r < 0)
+ return r;
- r = udev_monitor_filter_add_match_tag(m->udev_seat_monitor, "master-of-seat");
+ r = sd_device_monitor_filter_add_match_tag(m->device_seat_monitor, "master-of-seat");
if (r < 0)
return r;
- r = udev_monitor_enable_receiving(m->udev_seat_monitor);
+ r = sd_device_monitor_attach_event(m->device_seat_monitor, m->event);
if (r < 0)
return r;
- r = sd_event_add_io(m->event, &m->udev_seat_event_source, udev_monitor_get_fd(m->udev_seat_monitor), EPOLLIN, manager_dispatch_seat_udev, m);
+ r = sd_device_monitor_start(m->device_seat_monitor, manager_dispatch_seat_udev, m);
if (r < 0)
return r;
- m->udev_device_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
- if (!m->udev_device_monitor)
- return -ENOMEM;
+ (void) sd_event_source_set_description(sd_device_monitor_get_event_source(m->device_seat_monitor), "logind-seat-monitor");
- r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_device_monitor, "input", NULL);
+ r = sd_device_monitor_new(&m->device_monitor);
if (r < 0)
return r;
- r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_device_monitor, "graphics", NULL);
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "input", NULL);
if (r < 0)
return r;
- r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_device_monitor, "drm", NULL);
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "graphics", NULL);
if (r < 0)
return r;
- r = udev_monitor_enable_receiving(m->udev_device_monitor);
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "drm", NULL);
if (r < 0)
return r;
- r = sd_event_add_io(m->event, &m->udev_device_event_source, udev_monitor_get_fd(m->udev_device_monitor), EPOLLIN, manager_dispatch_device_udev, m);
+ r = sd_device_monitor_attach_event(m->device_monitor, m->event);
if (r < 0)
return r;
+ r = sd_device_monitor_start(m->device_monitor, manager_dispatch_device_udev, m);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(sd_device_monitor_get_event_source(m->device_monitor), "logind-device-monitor");
+
/* Don't watch keys if nobody cares */
if (!manager_all_buttons_ignored(m)) {
- m->udev_button_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
- if (!m->udev_button_monitor)
- return -ENOMEM;
+ r = sd_device_monitor_new(&m->device_button_monitor);
+ if (r < 0)
+ return r;
- r = udev_monitor_filter_add_match_tag(m->udev_button_monitor, "power-switch");
+ r = sd_device_monitor_filter_add_match_tag(m->device_button_monitor, "power-switch");
if (r < 0)
return r;
- r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_button_monitor, "input", NULL);
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_button_monitor, "input", NULL);
if (r < 0)
return r;
- r = udev_monitor_enable_receiving(m->udev_button_monitor);
+ r = sd_device_monitor_attach_event(m->device_button_monitor, m->event);
if (r < 0)
return r;
- r = sd_event_add_io(m->event, &m->udev_button_event_source, udev_monitor_get_fd(m->udev_button_monitor), EPOLLIN, manager_dispatch_button_udev, m);
+ r = sd_device_monitor_start(m->device_button_monitor, manager_dispatch_button_udev, m);
if (r < 0)
return r;
+
+ (void) sd_event_source_set_description(sd_device_monitor_get_event_source(m->device_button_monitor), "logind-button-monitor");
}
/* Don't bother watching VCSA devices, if nobody cares */
if (m->n_autovts > 0 && m->console_active_fd >= 0) {
- m->udev_vcsa_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
- if (!m->udev_vcsa_monitor)
- return -ENOMEM;
+ r = sd_device_monitor_new(&m->device_vcsa_monitor);
+ if (r < 0)
+ return r;
- r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_vcsa_monitor, "vc", NULL);
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_vcsa_monitor, "vc", NULL);
if (r < 0)
return r;
- r = udev_monitor_enable_receiving(m->udev_vcsa_monitor);
+ r = sd_device_monitor_attach_event(m->device_vcsa_monitor, m->event);
if (r < 0)
return r;
- r = sd_event_add_io(m->event, &m->udev_vcsa_event_source, udev_monitor_get_fd(m->udev_vcsa_monitor), EPOLLIN, manager_dispatch_vcsa_udev, m);
+ r = sd_device_monitor_start(m->device_vcsa_monitor, manager_dispatch_vcsa_udev, m);
if (r < 0)
return r;
+
+ (void) sd_event_source_set_description(sd_device_monitor_get_event_source(m->device_vcsa_monitor), "logind-vcsa-monitor");
}
return 0;
@@ -979,13 +966,13 @@ static void manager_gc(Manager *m, bool drop_not_started) {
/* First, if we are not closing yet, initiate stopping */
if (session_may_gc(session, drop_not_started) &&
session_get_state(session) != SESSION_CLOSING)
- session_stop(session, false);
+ (void) session_stop(session, false);
/* Normally, this should make the session referenced
* again, if it doesn't then let's get rid of it
* immediately */
if (session_may_gc(session, drop_not_started)) {
- session_finalize(session);
+ (void) session_finalize(session);
session_free(session);
}
}
@@ -996,11 +983,11 @@ static void manager_gc(Manager *m, bool drop_not_started) {
/* First step: queue stop jobs */
if (user_may_gc(user, drop_not_started))
- user_stop(user, false);
+ (void) user_stop(user, false);
/* Second step: finalize user */
if (user_may_gc(user, drop_not_started)) {
- user_finalize(user);
+ (void) user_finalize(user);
user_free(user);
}
}
@@ -1095,6 +1082,9 @@ static int manager_startup(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to register SIGHUP handler: %m");
+ /* Connect to utmp */
+ manager_connect_utmp(m);
+
/* Connect to console */
r = manager_connect_console(m);
if (r < 0)
@@ -1150,15 +1140,18 @@ static int manager_startup(Manager *m) {
/* Reserve the special reserved VT */
manager_reserve_vt(m);
+ /* Read in utmp if it exists */
+ manager_read_utmp(m);
+
/* And start everything */
HASHMAP_FOREACH(seat, m->seats, i)
- seat_start(seat);
+ (void) seat_start(seat);
HASHMAP_FOREACH(user, m->users, i)
- user_start(user);
+ (void) user_start(user);
HASHMAP_FOREACH(session, m->sessions, i)
- session_start(session, NULL);
+ (void) session_start(session, NULL, NULL);
HASHMAP_FOREACH(inhibitor, m->inhibitors, i)
inhibitor_start(inhibitor);
@@ -1197,28 +1190,23 @@ static int manager_run(Manager *m) {
}
}
-int main(int argc, char *argv[]) {
+static int run(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
- log_set_target(LOG_TARGET_AUTO);
log_set_facility(LOG_AUTH);
- log_parse_environment();
- log_open();
+ log_setup_service();
umask(0022);
if (argc != 1) {
log_error("This program takes no arguments.");
- r = -EINVAL;
- goto finish;
+ return -EINVAL;
}
r = mac_selinux_init();
- if (r < 0) {
- log_error_errno(r, "Could not initialize labelling: %m");
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Could not initialize labelling: %m");
/* Always create the directories people can create inotify watches in. Note that some applications might check
* for the existence of /run/systemd/seats/ to determine whether logind is available, so please always make
@@ -1230,21 +1218,16 @@ int main(int argc, char *argv[]) {
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, -1) >= 0);
r = manager_new(&m);
- if (r < 0) {
- log_error_errno(r, "Failed to allocate manager object: %m");
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate manager object: %m");
(void) manager_parse_config_file(m);
r = manager_startup(m);
- if (r < 0) {
- log_error_errno(r, "Failed to fully start up daemon: %m");
- goto finish;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to fully start up daemon: %m");
log_debug("systemd-logind running as pid "PID_FMT, getpid_cached());
-
(void) sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");
@@ -1252,11 +1235,11 @@ int main(int argc, char *argv[]) {
r = manager_run(m);
log_debug("systemd-logind stopped as pid "PID_FMT, getpid_cached());
-
(void) sd_notify(false,
"STOPPING=1\n"
"STATUS=Shutting down...");
-finish:
- return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+ return r;
}
+
+DEFINE_MAIN_FUNCTION(run);