/* upstart * * utmp.c - utmp and wtmp handling * * Copyright © 2009 Canonical Ltd. * Author: Scott James Remnant . * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, as * published by the Free Software Foundation. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include #include #include #include "utmp.h" /* Prototypes for static functions */ static void utmp_entry (struct utmpx *utmp, short type, pid_t pid, const char *line, const char *id, const char *user); static int utmp_write (const char *utmp_file, const struct utmpx *utmp) __attribute__ ((warn_unused_result)); static void wtmp_write (const char *wtmp_file, const struct utmpx *utmp); /** * SHUTDOWN_TIME: * * The sysvinit last utility expects a special "shutdown" RUN_LVL entry, * and abuses the type to distinguish that. We'll do the same. **/ #define SHUTDOWN_TIME 254 /** * utmp_read_runlevel: * @utmp_file: utmp or wtmp file to read from, * @prevlevel: pointer to store previous runlevel in. * * Reads the the most recent runlevel entry from @utmp_file, returning * the runlevel from it. If @prevlevel is not NULL, the previous runlevel * will be stored in that variable. * * @utmp_file may be either a utmp or wtmp file, if NULL the default * /var/run/utmp is used. * * Returns: runlevel on success, negative value on raised error. **/ int utmp_read_runlevel (const char *utmp_file, int * prevlevel) { struct utmpx utmp; struct utmpx *lvl; int runlevel; memset (&utmp, 0, sizeof utmp); utmp.ut_type = RUN_LVL; if (utmp_file) utmpxname (utmp_file); setutxent (); lvl = getutxid (&utmp); if (! lvl) { nih_error_raise_system (); endutxent (); return -1; } runlevel = lvl->ut_pid % 256 ?: 'N'; if (runlevel < 0) runlevel = 'N'; if (prevlevel) { *prevlevel = lvl->ut_pid / 256 ?: 'N'; if (*prevlevel < 0) *prevlevel = 'N'; } endutxent (); return runlevel; } /** * utmp_get_runlevel: * @utmp_file: utmp or wtmp file to read from, * @prevlevel: pointer to store previous runlevel in. * * If the RUNLEVEL and PREVLEVEL environment variables are set, returns * the current and previous runlevels from those otherwise calls * utmp_read_runlevel() to read the most recent runlevel entry from * @utmp_file. * * Returns: runlevel on success, negative value on raised error. **/ int utmp_get_runlevel (const char *utmp_file, int * prevlevel) { const char *renv; const char *penv; renv = getenv ("RUNLEVEL"); penv = getenv ("PREVLEVEL"); if (renv) { if (prevlevel) *prevlevel = penv && penv[0] ? penv[0] : 'N'; return renv[0] ?: 'N'; } return utmp_read_runlevel (utmp_file, prevlevel); } /** * utmp_write_runlevel: * @utmp_file: utmp file, * @wtmp_file: wtmp file, * @runlevel: new runlevel, * @prevlevel: previous runlevel. * * Write a runlevel change record from @prevlevel to @runlevel to @utmp_file, * or /var/run/utmp if @utmp_file is NULL, and to @wtmp_file, or /var/log/wtmp * if @wtmp_file is NULL. * * Errors writing to the wtmp file are ignored. * * Returns: zero on success, negative value on raised error. **/ int utmp_write_runlevel (const char *utmp_file, const char *wtmp_file, int runlevel, int prevlevel) { struct utmpx reboot; struct utmpx utmp; int savedlevel; int ret; nih_assert (runlevel > 0); nih_assert (prevlevel >= 0); if (prevlevel == 'N') prevlevel = 0; utmp_entry (&reboot, BOOT_TIME, 0, NULL, NULL, NULL); /* Check for the previous runlevel entry in utmp, if it doesn't * match then we assume a missed reboot so write the boot time * record out first. */ savedlevel = utmp_read_runlevel (utmp_file, NULL); if (savedlevel != prevlevel) { if (savedlevel < 0) nih_free (nih_error_get ()); if (utmp_write (utmp_file, &reboot) < 0) nih_free (nih_error_get ()); } /* Check for the previous runlevel entry in wtmp, if it doesn't * match then we assume a missed reboot so write the boot time * record out first. */ savedlevel = utmp_read_runlevel (wtmp_file, NULL); if (savedlevel != prevlevel) { if (savedlevel < 0) nih_free (nih_error_get ()); wtmp_write (wtmp_file, &reboot); } /* Write the runlevel change record */ utmp_entry (&utmp, RUN_LVL, runlevel + prevlevel * 256, NULL, NULL, NULL); ret = utmp_write (utmp_file, &utmp); wtmp_write (wtmp_file, &utmp); return ret; } /** * utmp_write_shutdown: * @utmp_file: utmp file to write to, * @wtmp_file: wtmp file to write to. * * Write a shutdown utmp record to @utmp_file, or /var/run/utmp if * @utmp_file is NULL, and to @wtmp_file, or /var/log/wtmp if @wtmp_file * is NULL. * * Errors writing to the wtmp file are ignored. * * Returns: zero on success, negative value on raised error. **/ int utmp_write_shutdown (const char *utmp_file, const char *wtmp_file) { struct utmpx utmp; int ret; utmp_entry (&utmp, SHUTDOWN_TIME, 0, NULL, NULL, NULL); ret = utmp_write (utmp_file, &utmp); wtmp_write (wtmp_file, &utmp); return ret; } /** * utmp_entry: * @utmp: utmp entry to fill, * @type: type of record, * @pid: process id of login process, * @line: device name of tty, * @id: terminal name suffix, * @user: username. * * Fill the utmp entry @utmp with the details passed, setting the auxiliary * information such as host and time to sensible defaults. Depending on * @type, the other arguments may be ignored. * * When @type is BOOT_TIME, or the special SHUTDOWN_TIME, all arguments * are ignored. When @type is RUN_LVL, the @line, @id and @user lines are * ignored. * * Any existing values in @utmp before this call will be lost. **/ static void utmp_entry (struct utmpx *utmp, short type, pid_t pid, const char * line, const char * id, const char * user) { struct utsname uts; struct timeval tv; nih_assert (utmp != NULL); nih_assert (type != EMPTY); switch (type) { case BOOT_TIME: pid = 0; line = "~"; id = "~~"; user = "reboot"; break; case SHUTDOWN_TIME: type = RUN_LVL; pid = 0; line = "~"; id = "~~"; user = "shutdown"; break; case RUN_LVL: nih_assert (pid != 0); line = "~"; id = "~~"; user = "runlevel"; break; default: nih_assert (line != NULL); nih_assert (id != NULL); nih_assert (user != NULL); } memset (utmp, 0, sizeof (struct utmpx)); utmp->ut_type = type; utmp->ut_pid = pid; strncpy (utmp->ut_line, line, sizeof utmp->ut_line); strncpy (utmp->ut_id, id, sizeof utmp->ut_id); strncpy (utmp->ut_user, user, sizeof utmp->ut_user); if (uname (&uts) == 0) strncpy (utmp->ut_host, uts.release, sizeof utmp->ut_host); gettimeofday (&tv, NULL); utmp->ut_tv.tv_sec = tv.tv_sec; utmp->ut_tv.tv_usec = tv.tv_usec; } /** * utmp_write: * @utmp_file: utmp file to write to, * @utmp: utmp entry to write. * * Write the utmp entry @utmp to @utmp_file, or /var/run/utmp if @utmp_file * is NULL. * * Returns: zero on success, negative value on raised error. **/ static int utmp_write (const char * utmp_file, const struct utmpx *utmp) { nih_assert (utmp != NULL); utmpxname (utmp_file ?: _PATH_UTMPX); setutxent (); if (! pututxline (utmp)) { nih_error_raise_system (); endutxent (); return -1; } endutxent (); return 0; } /** * wtmp_write: * @wtmp_file: wtmp file to write to, * @utmp: utmp entry to write. * * Write the utmp entry @utmp to @wtmp_file, or /var/log/wtmp if @utmp_file * is NULL. **/ static void wtmp_write (const char * wtmp_file, const struct utmpx *utmp) { nih_assert (utmp != NULL); updwtmpx (wtmp_file ?: _PATH_WTMPX, utmp); }