blob: c1dd225ab82a580aeb5ffa3d81824d0444c988e7 [file] [log] [blame]
/* upstart
*
* utmp.c - utmp and wtmp handling
*
* Copyright © 2009 Canonical Ltd.
* Author: Scott James Remnant <scott@netsplit.com>.
*
* 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 <config.h>
#endif /* HAVE_CONFIG_H */
#include <sys/time.h>
#include <sys/utsname.h>
#include <utmpx.h>
#include <stdlib.h>
#include <string.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/logging.h>
#include <nih/error.h>
#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);
}