BSD 4_4_Lite2 release
[unix-history] / usr / src / sbin / init / init.c
index 37b1f38..316aa93 100644 (file)
@@ -1,37 +1,64 @@
 /*-
 /*-
- * Copyright (c) 1991 The Regents of the University of California.
- * All rights reserved.
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
  * This code is derived from software contributed to Berkeley by
  *
  * This code is derived from software contributed to Berkeley by
- * Donn Seeley at UUNET Technologies, Inc.
+ * Donn Seeley at Berkeley Software Design, Inc.
  *
  *
- * %sccs.include.redist.c%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-char copyright[] =
-"@(#) Copyright (c) 1991 The Regents of the University of California.\n\
- All rights reserved.\n";
+static char copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
      The Regents of the University of California.  All rights reserved.\n";
 #endif /* not lint */
 
 #ifndef lint
 #endif /* not lint */
 
 #ifndef lint
-static char sccsid[] = "@(#)init.c     6.13 (Berkeley) %G%";
+static char sccsid[] = "@(#)init.c     8.2 (Berkeley) 4/28/95";
 #endif /* not lint */
 
 #endif /* not lint */
 
-#include <sys/types.h>
+#include <sys/param.h>
 #include <sys/sysctl.h>
 #include <sys/wait.h>
 #include <sys/sysctl.h>
 #include <sys/wait.h>
+
 #include <db.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <db.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <syslog.h>
 #include <time.h>
 #include <ttyent.h>
 #include <unistd.h>
 #include <syslog.h>
 #include <time.h>
 #include <ttyent.h>
 #include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 
 #ifdef __STDC__
 #include <stdarg.h>
 
 #ifdef __STDC__
 #include <stdarg.h>
@@ -55,7 +82,8 @@ extern void logwtmp __P((const char *, const char *, const char *));
 /*
  * Sleep times; used to prevent thrashing.
  */
 /*
  * Sleep times; used to prevent thrashing.
  */
-#define        GETTY_SPACING           10      /* fork getty on a port every N secs */
+#define        GETTY_SPACING            5      /* N secs minimum getty spacing */
+#define        GETTY_SLEEP             30      /* sleep N secs after spacing problem */
 #define        WINDOW_WAIT              3      /* wait N secs after starting window */
 #define        STALL_TIMEOUT           30      /* wait N secs after warning */
 #define        DEATH_WATCH             10      /* wait N secs for procs to die */
 #define        WINDOW_WAIT              3      /* wait N secs after starting window */
 #define        STALL_TIMEOUT           30      /* wait N secs after warning */
 #define        DEATH_WATCH             10      /* wait N secs for procs to die */
@@ -92,7 +120,7 @@ state_t requested_transition = runcom;
 
 void setctty __P((char *));
 
 
 void setctty __P((char *));
 
-typedef struct session {
+typedef struct init_session {
        int     se_index;               /* index of entry in ttys file */
        pid_t   se_process;             /* controlling process */
        time_t  se_started;             /* used to avoid thrashing */
        int     se_index;               /* index of entry in ttys file */
        pid_t   se_process;             /* controlling process */
        time_t  se_started;             /* used to avoid thrashing */
@@ -103,8 +131,8 @@ typedef struct session {
        char    **se_getty_argv;        /* pre-parsed argument array */
        char    *se_window;             /* window system (started only once) */
        char    **se_window_argv;       /* pre-parsed argument array */
        char    **se_getty_argv;        /* pre-parsed argument array */
        char    *se_window;             /* window system (started only once) */
        char    **se_window_argv;       /* pre-parsed argument array */
-       struct  session *se_prev;
-       struct  session *se_next;
+       struct  init_session *se_prev;
+       struct  init_session *se_next;
 } session_t;
 
 void free_session __P((session_t *));
 } session_t;
 
 void free_session __P((session_t *));
@@ -113,15 +141,16 @@ session_t *sessions;
 
 char **construct_argv __P((char *));
 void start_window_system __P((session_t *));
 
 char **construct_argv __P((char *));
 void start_window_system __P((session_t *));
-void collect_child __P((int));
+void collect_child __P((pid_t));
 pid_t start_getty __P((session_t *));
 void transition_handler __P((int));
 void alrm_handler __P((int));
 pid_t start_getty __P((session_t *));
 void transition_handler __P((int));
 void alrm_handler __P((int));
+void setsecuritylevel __P((int));
+int getsecuritylevel __P((void));
+int setupargv __P((session_t *, struct ttyent *));
 int clang;
 
 int clang;
 
-int start_logger __P((void));
 void clear_session_logs __P((session_t *));
 void clear_session_logs __P((session_t *));
-int logger_enable;
 
 int start_session_db __P((void));
 void add_session __P((session_t *));
 
 int start_session_db __P((void));
 void add_session __P((session_t *));
@@ -164,7 +193,14 @@ main(argc, argv)
         * Create an initial session.
         */
        if (setsid() < 0)
         * Create an initial session.
         */
        if (setsid() < 0)
-               syslog(LOG_ERR, "setsid failed (initial) %m");
+               warning("initial setsid() failed: %m");
+
+       /*
+        * Establish an initial user so that programs running
+        * single user do not freak out and die (like passwd).
+        */
+       if (setlogin("root") < 0)
+               warning("setlogin() failed: %m");
 
        /*
         * This code assumes that we always get arguments through flags,
 
        /*
         * This code assumes that we always get arguments through flags,
@@ -256,6 +292,7 @@ handle(va_alist)
                sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0;
                sigaction(sig, &sa, (struct sigaction *) 0);
        }
                sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0;
                sigaction(sig, &sa, (struct sigaction *) 0);
        }
+       va_end(ap);
 }
 
 /*
 }
 
 /*
@@ -282,6 +319,7 @@ delset(va_alist)
 
        while (sig = va_arg(ap, int))
                sigdelset(maskp, sig);
 
        while (sig = va_arg(ap, int))
                sigdelset(maskp, sig);
+       va_end(ap);
 }
 
 /*
 }
 
 /*
@@ -297,7 +335,6 @@ stall(va_alist)
        va_dcl
 #endif
 {
        va_dcl
 #endif
 {
-       pid_t pid;
        va_list ap;
 #ifndef __STDC__
        char *message;
        va_list ap;
 #ifndef __STDC__
        char *message;
@@ -403,7 +440,9 @@ disaster(sig)
 int
 getsecuritylevel()
 {
 int
 getsecuritylevel()
 {
-       int name[2], len, curlevel;
+#ifdef KERN_SECURELVL
+       int name[2], curlevel;
+       size_t len;
        extern int errno;
 
        name[0] = CTL_KERN;
        extern int errno;
 
        name[0] = CTL_KERN;
@@ -415,15 +454,20 @@ getsecuritylevel()
                return (-1);
        }
        return (curlevel);
                return (-1);
        }
        return (curlevel);
+#else
+       return (-1);
+#endif
 }
 
 /*
  * Set the security level of the kernel.
  */
 }
 
 /*
  * Set the security level of the kernel.
  */
+void
 setsecuritylevel(newlevel)
        int newlevel;
 {
 setsecuritylevel(newlevel)
        int newlevel;
 {
-       int name[2], len, curlevel;
+#ifdef KERN_SECURELVL
+       int name[2], curlevel;
        extern int errno;
 
        curlevel = getsecuritylevel();
        extern int errno;
 
        curlevel = getsecuritylevel();
@@ -441,6 +485,7 @@ setsecuritylevel(newlevel)
        warning("kernel security level changed from %d to %d",
            curlevel, newlevel);
 #endif
        warning("kernel security level changed from %d to %d",
            curlevel, newlevel);
 #endif
+#endif
 }
 
 /*
 }
 
 /*
@@ -455,65 +500,6 @@ transition(s)
                s = (state_t) (*s)();
 }
 
                s = (state_t) (*s)();
 }
 
-/*
- * We send requests for session logging to another process for two reasons.
- * First, we don't want to block if the log files go away (e.g. because
- * one or more are on hard-mounted NFS systems whose server crashes).
- * Second, despite all the crud already contained in init, it still isn't
- * right that init should care about session logging record formats and files.
- * We could use explicit 'Unix' IPC for this, but let's try to be POSIX...
- */
-int
-start_logger()
-{
-       static char *argv[] = { _PATH_SLOGGER, 0 };
-       int fd, pfd[2];
-       pid_t pid;
-       sigset_t mask;
-
-       if (pipe(pfd) == -1) {
-               warning("session logging disabled: can't make pipe to %s: %m",
-                         argv[0]);
-               return -1;
-       }
-       if ((pid = fork()) == -1) {
-               emergency("session logging disabled: can't fork for %s: %m",
-                         argv[0]);
-               return -1;
-       }
-
-       if (pid == 0) {
-               close(pfd[1]);
-               if (pfd[0] != 0) {
-                       dup2(pfd[0], 0);
-                       close(pfd[0]);
-               }
-               if ((fd = open(_PATH_DEVNULL, O_WRONLY)) != -1) {
-                       if (fd != 1)
-                               dup2(fd, 1);
-                       if (fd != 2)
-                               dup2(fd, 2);
-                       if (fd != 1 && fd != 2)
-                               close(fd);
-               } else {
-                       /* paranoid */
-                       close(1);
-                       close(2);
-               }
-               sigemptyset(&mask);
-               sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
-               execv(argv[0], argv);
-               stall("can't exec %s: %m", argv[0]);
-               _exit(1);
-       }
-
-       close(pfd[0]);
-       fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
-       fcntl(pfd[1], F_SETFL, O_NONBLOCK);
-
-       return pfd[1];
-}
-
 /*
  * Close out the accounting files for a login session.
  * NB: should send a message to the session logger to avoid blocking.
 /*
  * Close out the accounting files for a login session.
  * NB: should send a message to the session logger to avoid blocking.
@@ -539,6 +525,7 @@ setctty(name)
        int fd;
 
        (void) revoke(name);
        int fd;
 
        (void) revoke(name);
+       sleep (2);                      /* leave DTR low */
        if ((fd = open(name, O_RDWR)) == -1) {
                stall("can't open %s: %m", name);
                _exit(1);
        if ((fd = open(name, O_RDWR)) == -1) {
                stall("can't open %s: %m", name);
                _exit(1);
@@ -565,7 +552,7 @@ single_user()
        struct passwd *pp;
        static const char banner[] =
                "Enter root password, or ^D to go multi-user\n";
        struct passwd *pp;
        static const char banner[] =
                "Enter root password, or ^D to go multi-user\n";
-       char *password;
+       char *clear, *password;
 #endif
 
        /*
 #endif
 
        /*
@@ -580,33 +567,6 @@ single_user()
                 */
                setctty(_PATH_CONSOLE);
 
                 */
                setctty(_PATH_CONSOLE);
 
-#ifdef KTRACEHACK
-               {
-                       static int tracing = 0;
-                       int foo, pid;
-
-                       if (tracing) goto skip;
-                       foo = open("/traceinit", O_RDONLY, 0);
-                       if (foo != -1) {
-                               close(foo);
-                               if ((pid = fork()) == 0) {
-                                       execl("/sbin/mount", "mount", "-uw",
-                                                "/", 0);
-                                       _exit(-1);
-                               }
-                               waitpid(pid, 0, 0);
-                               if (fork() == 0) {
-                                       execl("/ktrace", "ktrace", "-aip",
-                                                "1", 0);
-                                       _exit(-1);
-                               }
-                               waitpid(pid, 0, 0);
-                               tracing = 1;
-                       }
-                       skip: ;
-               }
-#endif /* KTRACEHACK */
-                               
 #ifdef SECURE
                /*
                 * Check the root password.
 #ifdef SECURE
                /*
                 * Check the root password.
@@ -618,10 +578,11 @@ single_user()
                if (typ && (typ->ty_status & TTY_SECURE) == 0 && pp) {
                        write(2, banner, sizeof banner - 1);
                        for (;;) {
                if (typ && (typ->ty_status & TTY_SECURE) == 0 && pp) {
                        write(2, banner, sizeof banner - 1);
                        for (;;) {
-                               password = getpass("Password:");
-                               if (password == 0 || *password == '\0')
+                               clear = getpass("Password:");
+                               if (clear == 0 || *clear == '\0')
                                        _exit(0);
                                        _exit(0);
-                               password = crypt(password, pp->pw_passwd);
+                               password = crypt(clear, pp->pw_passwd);
+                               memset(clear, 0, _PASSWORD_LEN);
                                if (strcmp(password, pp->pw_passwd) == 0)
                                        break;
                                warning("single-user login failed\n");
                                if (strcmp(password, pp->pw_passwd) == 0)
                                        break;
                                warning("single-user login failed\n");
@@ -781,6 +742,16 @@ runcom()
                }
        } while (wpid != pid);
 
                }
        } while (wpid != pid);
 
+       if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM &&
+           requested_transition == catatonia) {
+               /* /etc/rc executed /sbin/reboot; wait for the end quietly */
+               sigset_t s;
+
+               sigfillset(&s);
+               for (;;)
+                       sigsuspend(&s);
+       }
+
        if (!WIFEXITED(status)) {
                warning("%s on %s terminated abnormally, going to single user mode",
                        _PATH_BSHELL, _PATH_RUNCOM);
        if (!WIFEXITED(status)) {
                warning("%s on %s terminated abnormally, going to single user mode",
                        _PATH_BSHELL, _PATH_RUNCOM);
@@ -868,7 +839,7 @@ find_session(pid)
        key.size = sizeof pid;
        if ((*session_db->get)(session_db, &key, &data, 0) != 0)
                return 0;
        key.size = sizeof pid;
        if ((*session_db->get)(session_db, &key, &data, 0) != 0)
                return 0;
-       bcopy(data.data, (char *)&ret, sizeof(ret));
+       memmove(&ret, data.data, sizeof(ret));
        return ret;
 }
 
        return ret;
 }
 
@@ -899,8 +870,10 @@ free_session(sp)
        register session_t *sp;
 {
        free(sp->se_device);
        register session_t *sp;
 {
        free(sp->se_device);
-       free(sp->se_getty);
-       free(sp->se_getty_argv);
+       if (sp->se_getty) {
+               free(sp->se_getty);
+               free(sp->se_getty_argv);
+       }
        if (sp->se_window) {
                free(sp->se_window);
                free(sp->se_window_argv);
        if (sp->se_window) {
                free(sp->se_window);
                free(sp->se_window_argv);
@@ -925,33 +898,16 @@ new_session(sprev, session_index, typ)
                return 0;
 
        sp = (session_t *) malloc(sizeof (session_t));
                return 0;
 
        sp = (session_t *) malloc(sizeof (session_t));
+       memset(sp, 0, sizeof *sp);
 
        sp->se_index = session_index;
 
        sp->se_index = session_index;
-       sp->se_process = 0;
-       sp->se_started = 0;
-       sp->se_flags = 0;
-       sp->se_window = 0;
 
        sp->se_device = malloc(sizeof(_PATH_DEV) + strlen(typ->ty_name));
        (void) sprintf(sp->se_device, "%s%s", _PATH_DEV, typ->ty_name);
 
 
        sp->se_device = malloc(sizeof(_PATH_DEV) + strlen(typ->ty_name));
        (void) sprintf(sp->se_device, "%s%s", _PATH_DEV, typ->ty_name);
 
-       sp->se_getty = strdup(typ->ty_getty);
-       sp->se_getty_argv = construct_argv(sp->se_getty);
-       if (sp->se_getty_argv == 0) {
-               warning("can't parse getty for port %s",
-                       sp->se_device);
+       if (setupargv(sp, typ) == 0) {
                free_session(sp);
                free_session(sp);
-               return 0;
-       }
-       if (typ->ty_window) {
-               sp->se_window = strdup(typ->ty_window);
-               sp->se_window_argv = construct_argv(sp->se_window);
-               if (sp->se_window_argv == 0) {
-                       warning("can't parse window for port %s",
-                               sp->se_device);
-                       free_session(sp);
-                       return 0;
-               }
+               return (0);
        }
 
        sp->se_next = 0;
        }
 
        sp->se_next = 0;
@@ -966,6 +922,44 @@ new_session(sprev, session_index, typ)
        return sp;
 }
 
        return sp;
 }
 
+/*
+ * Calculate getty and if useful window argv vectors.
+ */
+int
+setupargv(sp, typ)
+       session_t *sp;
+       struct ttyent *typ;
+{
+
+       if (sp->se_getty) {
+               free(sp->se_getty);
+               free(sp->se_getty_argv);
+       }
+       sp->se_getty = malloc(strlen(typ->ty_getty) + strlen(typ->ty_name) + 2);
+       (void) sprintf(sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name);
+       sp->se_getty_argv = construct_argv(sp->se_getty);
+       if (sp->se_getty_argv == 0) {
+               warning("can't parse getty for port %s", sp->se_device);
+               free(sp->se_getty);
+               sp->se_getty = 0;
+               return (0);
+       }
+       if (typ->ty_window) {
+               if (sp->se_window)
+                       free(sp->se_window);
+               sp->se_window = strdup(typ->ty_window);
+               sp->se_window_argv = construct_argv(sp->se_window);
+               if (sp->se_window_argv == 0) {
+                       warning("can't parse window for port %s",
+                               sp->se_device);
+                       free(sp->se_window);
+                       sp->se_window = 0;
+                       return (0);
+               }
+       }
+       return (1);
+}
+
 /*
  * Walk the list of ttys and create sessions for each active line.
  */
 /*
  * Walk the list of ttys and create sessions for each active line.
  */
@@ -1000,7 +994,6 @@ read_ttys()
 
        endttyent();
 
 
        endttyent();
 
-       logger_enable = 1;
        return (state_func_t) multi_user;
 }
 
        return (state_func_t) multi_user;
 }
 
@@ -1062,8 +1055,7 @@ start_getty(sp)
            current_time - sp->se_started < GETTY_SPACING) {
                warning("getty repeating too quickly on port %s, sleeping",
                        sp->se_device);
            current_time - sp->se_started < GETTY_SPACING) {
                warning("getty repeating too quickly on port %s, sleeping",
                        sp->se_device);
-               sleep((unsigned) GETTY_SPACING -
-                     (current_time - sp->se_started));
+               sleep((unsigned) GETTY_SLEEP);
        }
 
        if (sp->se_window) {
        }
 
        if (sp->se_window) {
@@ -1071,8 +1063,6 @@ start_getty(sp)
                sleep(WINDOW_WAIT);
        }
 
                sleep(WINDOW_WAIT);
        }
 
-       setctty(sp->se_device);
-
        sigemptyset(&mask);
        sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
 
        sigemptyset(&mask);
        sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
 
@@ -1087,8 +1077,12 @@ start_getty(sp)
  * If an exiting login, start a new login running.
  */
 void
  * If an exiting login, start a new login running.
  */
 void
+#ifdef __STDC__
+collect_child(pid_t pid)
+#else
 collect_child(pid)
        pid_t pid;
 collect_child(pid)
        pid_t pid;
+#endif
 {
        register session_t *sp, *sprev, *snext;
 
 {
        register session_t *sp, *sprev, *snext;
 
@@ -1158,7 +1152,6 @@ multi_user()
        register session_t *sp;
 
        requested_transition = 0;
        register session_t *sp;
 
        requested_transition = 0;
-       logger_enable = 1;
 
        /*
         * If the administrator has not set the security level to -1
 
        /*
         * If the administrator has not set the security level to -1
@@ -1207,7 +1200,7 @@ clean_ttys()
        while (typ = getttyent()) {
                ++session_index;
 
        while (typ = getttyent()) {
                ++session_index;
 
-               for (sp = sessions; sp; sprev = sp, sp = sp->se_next)
+               for (sprev = 0, sp = sessions; sp; sprev = sp, sp = sp->se_next)
                        if (strcmp(typ->ty_name, sp->se_device + devlen) == 0)
                                break;
 
                        if (strcmp(typ->ty_name, sp->se_device + devlen) == 0)
                                break;
 
@@ -1218,9 +1211,16 @@ clean_ttys()
                                       session_index);
                                sp->se_index = session_index;
                        }
                                       session_index);
                                sp->se_index = session_index;
                        }
-                       if (typ->ty_status & TTY_ON)
-                               sp->se_flags &= ~SE_SHUTDOWN;
-                       else {
+                       if ((typ->ty_status & TTY_ON) == 0 ||
+                           typ->ty_getty == 0) {
+                               sp->se_flags |= SE_SHUTDOWN;
+                               kill(sp->se_process, SIGHUP);
+                               continue;
+                       }
+                       sp->se_flags &= ~SE_SHUTDOWN;
+                       if (setupargv(sp, typ) == 0) {
+                               warning("can't parse getty for port %s",
+                                       sp->se_device);
                                sp->se_flags |= SE_SHUTDOWN;
                                kill(sp->se_process, SIGHUP);
                        }
                                sp->se_flags |= SE_SHUTDOWN;
                                kill(sp->se_process, SIGHUP);
                        }
@@ -1275,7 +1275,6 @@ death()
 
        /* NB: should send a message to the session logger to avoid blocking. */
        logwtmp("~", "shutdown", "");
 
        /* NB: should send a message to the session logger to avoid blocking. */
        logwtmp("~", "shutdown", "");
-       logger_enable = 0;
 
        for (i = 0; i < 3; ++i) {
                if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH)
 
        for (i = 0; i < 3; ++i) {
                if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH)
@@ -1292,7 +1291,7 @@ death()
                        return (state_func_t) single_user;
        }
 
                        return (state_func_t) single_user;
        }
 
-       warning("some processes wouldn't die");
+       warning("some processes would not die; ps axl advised");
 
        return (state_func_t) single_user;
 }
 
        return (state_func_t) single_user;
 }