From: Lennart Poettering Date: Tue, 10 Aug 2010 23:43:23 +0000 (+0200) Subject: audit,utmp: implement audit logic and rip utmp stuff out of the main daemon and into... X-Git-Tag: v8~131 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=4927fcae48de061393b3ce9c12d49f80d73fbf1d audit,utmp: implement audit logic and rip utmp stuff out of the main daemon and into a helper binary --- diff --git a/.gitignore b/.gitignore index 6ff851980..b143ad13d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +systemd-update-utmp test-env-replace systemd-cgls systemd.pc diff --git a/Makefile.am b/Makefile.am index b6041b5d1..cb2318406 100644 --- a/Makefile.am +++ b/Makefile.am @@ -68,7 +68,8 @@ endif rootlibexec_PROGRAMS = \ systemd-logger \ systemd-cgroups-agent \ - systemd-initctl + systemd-initctl \ + systemd-update-utmp noinst_PROGRAMS = \ test-engine \ @@ -243,6 +244,13 @@ libsystemd_basic_la_SOURCES = \ src/log.c \ src/ratelimit.c +libsystemd_basic_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SELINUX_CFLAGS) + +libsystemd_basic_la_LIBADD = \ + $(SELINUX_LIBS) + libsystemd_core_la_SOURCES = \ src/unit.c \ src/job.c \ @@ -282,7 +290,6 @@ libsystemd_core_la_SOURCES = \ src/loopback-setup.c \ src/kmod-setup.c \ src/modprobe-setup.c \ - src/utmp-wtmp.c \ src/specifier.c \ src/unit-name.c \ src/fdset.c \ @@ -293,7 +300,10 @@ libsystemd_core_la_SOURCES = \ libsystemd_core_la_CFLAGS = \ $(AM_CFLAGS) \ $(DBUS_CFLAGS) \ - $(UDEV_CFLAGS) + $(UDEV_CFLAGS) \ + $(LIBWRAP_CFLAGS) \ + $(PAM_CFLAGS) \ + $(AUDIT_CFLAGS) libsystemd_core_la_LIBADD = \ libsystemd-basic.la \ @@ -301,7 +311,7 @@ libsystemd_core_la_LIBADD = \ $(UDEV_LIBS) \ $(LIBWRAP_LIBS) \ $(PAM_LIBS) \ - $(SELINUX_LIBS) + $(AUDIT_LIBS) # This is needed because automake is buggy in how it generates the # rules for C programs, but not Vala programs. We therefore can't @@ -480,6 +490,21 @@ systemd_initctl_LDADD = \ libsystemd-basic.la \ $(DBUS_LIBS) +systemd_update_utmp_SOURCES = \ + src/update-utmp.c \ + src/dbus-common.c \ + src/utmp-wtmp.c + +systemd_update_utmp_CFLAGS = \ + $(AM_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(AUDIT_CFLAGS) + +systemd_update_utmp_LDADD = \ + libsystemd-basic.la \ + $(DBUS_LIBS) \ + $(AUDIT_LIBS) + systemd_cgroups_agent_SOURCES = \ src/cgroups-agent.c \ src/dbus-common.c diff --git a/configure.ac b/configure.ac index f5dba3a86..72218e4df 100644 --- a/configure.ac +++ b/configure.ac @@ -186,6 +186,42 @@ fi AC_SUBST(PAM_LIBS) AM_CONDITIONAL([HAVE_PAM], [test "x$have_pam" != xno]) +AC_ARG_ENABLE([audit], + AS_HELP_STRING([--disable-audit],[Disable optional AUDIT support]), + [case "${enableval}" in + yes) have_audit=yes ;; + no) have_audit=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-audit) ;; + esac], + [have_audit=auto]) + +if test "x${have_audit}" != xno ; then + AC_CHECK_HEADERS( + [libaudit.h], + [have_audit=yes], + [if test "x$have_audit" = xyes ; then + AC_MSG_ERROR([*** AUDIT headers not found.]) + fi]) + + AC_CHECK_LIB( + [audit], + [audit_open], + [have_audit=yes], + [if test "x$have_audit" = xyes ; then + AC_MSG_ERROR([*** libaudit not found.]) + fi]) + + if test "x$have_audit" = xyes ; then + AUDIT_LIBS="-laudit" + AC_DEFINE(HAVE_AUDIT, 1, [AUDIT available]) + else + have_audit=no + fi +else + AUDIT_LIBS= +fi +AC_SUBST(AUDIT_LIBS) + have_gtk=no AC_ARG_ENABLE(gtk, AS_HELP_STRING([--disable-gtk], [disable GTK tools])) if test "x$enable_gtk" != "xno"; then diff --git a/fixme b/fixme index 3614c118d..be771ab02 100644 --- a/fixme +++ b/fixme @@ -79,9 +79,9 @@ * don't show file not found msgs for irrelevant units -* audit +* j->installed issue -* env vars must be replaced by "" +* plymouth boot.log External: diff --git a/src/manager.c b/src/manager.c index ddb253ae4..25eb4e70b 100644 --- a/src/manager.c +++ b/src/manager.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "manager.h" #include "hashmap.h" @@ -202,6 +203,10 @@ int manager_new(ManagerRunningAs running_as, Manager **_m) { m->exit_code = _MANAGER_EXIT_CODE_INVALID; m->pin_cgroupfs_fd = -1; +#ifdef HAVE_AUDIT + m->audit_fd = -1; +#endif + 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 */ @@ -245,6 +250,9 @@ int manager_new(ManagerRunningAs running_as, Manager **_m) { if ((r = bus_init(m)) < 0) goto fail; + if ((m->audit_fd = audit_open()) < 0) + log_error("Failed to connect to audit log: %m"); + *_m = m; return 0; @@ -429,6 +437,11 @@ void manager_free(Manager *m) { if (m->notify_watch.fd >= 0) close_nointr_nofail(m->notify_watch.fd); +#ifdef HAVE_AUDIT + if (m->audit_fd >= 0) + audit_close(m->audit_fd); +#endif + free(m->notify_socket); lookup_paths_free(&m->lookup_paths); @@ -567,10 +580,6 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { m->n_deserializing --; } - /* Now that the initial devices are available, let's see if we - * can write the utmp file */ - manager_write_utmp_reboot(m); - return r; } @@ -2234,70 +2243,25 @@ int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { return 0; } -static bool manager_utmp_good(Manager *m) { - int r; - - assert(m); - - if ((r = mount_path_is_mounted(m, _PATH_UTMPX)) <= 0) { - - if (r < 0) - log_warning("Failed to determine whether " _PATH_UTMPX " is mounted: %s", strerror(-r)); - - return false; - } - - return true; -} - -void manager_write_utmp_reboot(Manager *m) { - int r; - - assert(m); - - if (m->utmp_reboot_written) - return; +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { - if (m->running_as != MANAGER_SYSTEM) - return; +#ifdef HAVE_AUDIT + char *p; - if (!manager_utmp_good(m)) + if (m->audit_fd < 0) return; - if ((r = utmp_put_reboot(m->startup_timestamp.realtime)) < 0) { - - if (r != -ENOENT && r != -EROFS) - log_warning("Failed to write utmp/wtmp: %s", strerror(-r)); - + if (!(p = unit_name_to_prefix_and_instance(u->meta.id))) { + log_error("Failed to allocate unit name for audit message: %s", strerror(ENOMEM)); return; } - m->utmp_reboot_written = true; -} - -void manager_write_utmp_runlevel(Manager *m, Unit *u) { - int runlevel, r; - - assert(m); - assert(u); - - if (u->meta.type != UNIT_TARGET) - return; + if (audit_log_user_comm_message(m->audit_fd, type, "", p, NULL, NULL, NULL, success) < 0) + log_error("Failed to send audit message: %m"); - if (m->running_as != MANAGER_SYSTEM) - return; - - if (!manager_utmp_good(m)) - return; + free(p); +#endif - if ((runlevel = target_get_runlevel(TARGET(u))) <= 0) - return; - - if ((r = utmp_put_runlevel(0, runlevel, 0)) < 0) { - - if (r != -ENOENT && r != -EROFS) - log_warning("Failed to write utmp/wtmp: %s", strerror(-r)); - } } void manager_dispatch_bus_name_owner_changed( diff --git a/src/manager.h b/src/manager.h index a762dbc7c..c1f787f85 100644 --- a/src/manager.h +++ b/src/manager.h @@ -185,6 +185,11 @@ struct Manager { * file system */ int pin_cgroupfs_fd; + /* Audit fd */ +#ifdef HAVE_AUDIT + int audit_fd; +#endif + /* Flags */ ManagerRunningAs running_as; ManagerExitCode exit_code:4; @@ -193,8 +198,6 @@ struct Manager { bool dispatching_run_queue:1; bool dispatching_dbus_queue:1; - bool utmp_reboot_written:1; - int n_deserializing; bool show_status; @@ -234,9 +237,6 @@ unsigned manager_dispatch_dbus_queue(Manager *m); int manager_loop(Manager *m); -void manager_write_utmp_reboot(Manager *m); -void manager_write_utmp_runlevel(Manager *m, Unit *t); - void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner); void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid); @@ -251,6 +251,8 @@ bool manager_is_booting_or_shutting_down(Manager *m); void manager_reset_maintenance(Manager *m); +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); + const char *manager_running_as_to_string(ManagerRunningAs i); ManagerRunningAs manager_running_as_from_string(const char *s); diff --git a/src/missing.h b/src/missing.h index 5914b01a8..d6114cc2b 100644 --- a/src/missing.h +++ b/src/missing.h @@ -63,4 +63,12 @@ static inline int pivot_root(const char *new_root, const char *put_old) { /* static void nss_disable_nscd(void) _weakref_(__nss_disable_nscd); */ +#ifndef AUDIT_SERVICE_START +#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#endif + +#ifndef AUDIT_SERVICE_STOP +#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#endif + #endif diff --git a/src/unit.c b/src/unit.c index 9ccf7a465..3c4bdec98 100644 --- a/src/unit.c +++ b/src/unit.c @@ -41,6 +41,7 @@ #include "dbus-unit.h" #include "special.h" #include "cgroup-util.h" +#include "missing.h" const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = &service_vtable, @@ -1094,12 +1095,11 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) { /* Some names are special */ if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { - if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) { + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) /* The bus just might have become available, * hence try to connect to it, if we aren't * yet connected. */ bus_init(u->meta.manager); - } if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) /* The syslog daemon just might have become @@ -1107,17 +1107,12 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) { * we aren't yet connected. */ log_open(); - if (u->meta.type == UNIT_MOUNT) - /* Another directory became available, let's - * check if that is enough to write our utmp - * entry. */ - manager_write_utmp_reboot(u->meta.manager); - - if (u->meta.type == UNIT_TARGET) - /* A target got activated, maybe this is a runlevel? */ - manager_write_utmp_runlevel(u->meta.manager, u); + if (u->meta.type == UNIT_SERVICE && + !UNIT_IS_ACTIVE_OR_RELOADING(os)) + /* Write audit record if we have just finished starting up */ + manager_send_unit_audit(u->meta.manager, u, AUDIT_SERVICE_START, 1); - } else if (!UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + } else { if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) /* The syslog daemon might just have @@ -1127,6 +1122,13 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) { /* We don't care about D-Bus here, since we'll get an * asynchronous notification for it anyway. */ + + if (u->meta.type == UNIT_SERVICE && + UNIT_IS_INACTIVE_OR_MAINTENANCE(ns) && + !UNIT_IS_INACTIVE_OR_MAINTENANCE(os)) + + /* Write audit record if we have just finished shutting down */ + manager_send_unit_audit(u->meta.manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); } /* Maybe we finished startup and are now ready for being diff --git a/src/update-utmp.c b/src/update-utmp.c new file mode 100644 index 000000000..e64a819aa --- /dev/null +++ b/src/update-utmp.c @@ -0,0 +1,410 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" +#include "macro.h" +#include "util.h" +#include "special.h" +#include "utmp-wtmp.h" +#include "dbus-common.h" + +typedef struct Context { + DBusConnection *bus; +#ifdef HAVE_AUDIT + int audit_fd; +#endif +} Context; + +static usec_t get_startup_time(Context *c) { + const char + *interface = "org.freedesktop.systemd1.Manager", + *property = "StartupTimestamp"; + + DBusError error; + usec_t t = 0; + DBusMessage *m = NULL, *reply = NULL; + DBusMessageIter iter, sub; + + dbus_error_init(&error); + + assert(c); + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) { + log_error("Failed to send command: %s", error.message); + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) { + log_error("Failed to parse reply."); + goto finish; + } + + dbus_message_iter_get_basic(&sub, &t); + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return t; +} + +static int get_current_runlevel(Context *c) { + static const struct { + const int runlevel; + const char *special; + } table[] = { + /* The first target of this list that is active or has + * a job scheduled wins */ + { '0', SPECIAL_POWEROFF_TARGET }, + { '6', SPECIAL_REBOOT_TARGET }, + { '5', SPECIAL_RUNLEVEL5_TARGET }, + { '4', SPECIAL_RUNLEVEL4_TARGET }, + { '3', SPECIAL_RUNLEVEL3_TARGET }, + { '2', SPECIAL_RUNLEVEL2_TARGET }, + { '1', SPECIAL_RESCUE_TARGET }, + }; + const char + *interface = "org.freedesktop.systemd1.Unit", + *property = "ActiveState"; + + DBusMessage *m = NULL, *reply = NULL; + int r = 0; + unsigned i; + DBusError error; + + assert(c); + + dbus_error_init(&error); + + for (i = 0; i < ELEMENTSOF(table); i++) { + const char *path = NULL, *state; + DBusMessageIter iter, sub; + + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &table[i].special, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) { + dbus_error_free(&error); + continue; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", error.message); + r = -EIO; + goto finish; + } + + dbus_message_unref(m); + if (!(m = dbus_message_new_method_call( + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get"))) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + dbus_message_unref(reply); + if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) { + log_error("Failed to send command: %s", error.message); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_get_basic(&sub, &state); + + if (streq(state, "active") || streq(state, "reloading")) + r = table[i].runlevel; + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + + if (r) + break; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int on_reboot(Context *c) { + int r = 0, q; + usec_t t; + + assert(c); + + /* We finished start-up, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", NULL, NULL, NULL, 1) < 0) { + log_error("Failed to send audit message: %m"); + r = -errno; + } +#endif + + /* If this call fails it will return 0, which + * utmp_put_reboot() will then fix to the current time */ + t = get_startup_time(c); + + if ((q = utmp_put_reboot(t)) < 0) { + log_error("Failed to write utmp record: %s", strerror(-q)); + r = q; + } + + return r; +} + +static int on_shutdown(Context *c) { + int r = 0, q; + + assert(c); + + /* We started shut-down, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", NULL, NULL, NULL, 1) < 0) { + log_error("Failed to send audit message: %m"); + r = -errno; + } +#endif + + if ((q = utmp_put_shutdown(0)) < 0) { + log_error("Failed to write utmp record: %s", strerror(-q)); + r = q; + } + + return r; +} + +static int on_runlevel(Context *c) { + int r = 0, q, previous, runlevel; + + assert(c); + + /* We finished changing runlevel, so let's write the + * utmp record and send the audit msg */ + + /* First, get last runlevel */ + if ((q = utmp_get_runlevel(&previous, NULL)) < 0) { + + if (q != -ESRCH && q != -ENOENT) { + log_error("Failed to get current runlevel: %s", strerror(-q)); + return q; + } + + /* Hmm, we didn't find any runlevel, that means we + * have been rebooted */ + r = on_reboot(c); + previous = 0; + } + + /* Second get new runlevel */ + if ((runlevel = get_current_runlevel(c)) < 0) + return runlevel; + + if (previous == runlevel) + return 0; + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) { + char *s = NULL; + + if (asprintf(&s, "old-level=%c new-level=%c", previous, runlevel) < 0) + return -ENOMEM; + + if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0) { + log_error("Failed to send audit message: %m"); + r = -errno; + } + + free(s); + } +#endif + + if ((q = utmp_put_runlevel(0, runlevel, previous)) < 0) { + log_error("Failed to write utmp record: %s", strerror(-q)); + r = q; + } + + return r; +} + +int main(int argc, char *argv[]) { + int r; + DBusError error; + Context c; + + dbus_error_init(&error); + + zero(c); +#ifdef HAVE_AUDIT + c.audit_fd = -1; +#endif + + /* if (getppid() != 1) { */ + /* log_error("This program should be invoked by init only."); */ + /* return 1; */ + /* } */ + + if (argc != 2) { + log_error("This program requires one argument."); + return 1; + } + + log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); + log_parse_environment(); + +#ifdef HAVE_AUDIT + if ((c.audit_fd = audit_open()) < 0) + log_error("Failed to connect to audit log: %m"); +#endif + + if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) { + log_error("Failed to get D-Bus connection: %s", error.message); + r = -EIO; + goto finish; + } + + log_info("systemd-update-utmp running as pid %lu", (unsigned long) getpid()); + + if (streq(argv[1], "reboot")) + r = on_reboot(&c); + else if (streq(argv[1], "shutdown")) + r = on_shutdown(&c); + else if (streq(argv[1], "runlevel")) + r = on_runlevel(&c); + else { + log_error("Unknown command %s", argv[1]); + r = -EINVAL; + } + + log_info("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid()); +finish: + +#ifdef HAVE_AUDIT + if (c.audit_fd >= 0) + audit_close(c.audit_fd); +#endif + + if (c.bus) { + dbus_connection_close(c.bus); + dbus_connection_unref(c.bus); + } + + dbus_error_free(&error); + dbus_shutdown(); + + return r < 0 ? 1 : 0; +} diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c index 45da79c68..46dfba336 100644 --- a/src/utmp-wtmp.c +++ b/src/utmp-wtmp.c @@ -202,11 +202,11 @@ int utmp_put_runlevel(usec_t t, int runlevel, int previous) { previous = 0; } - - if (previous == runlevel) - return 0; } + if (previous == runlevel) + return 0; + init_entry(&store, t); store.ut_type = RUN_LVL;