Skip to content

Commit

Permalink
Add utmp format importer
Browse files Browse the repository at this point in the history
  • Loading branch information
andy-bower committed Feb 25, 2025
1 parent c4f445c commit 466dc85
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
4 changes: 4 additions & 0 deletions include/wtmpdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ extern int wtmpdb_read_all_v2 (const char *db_path,
void *userdata, char **error);
extern int wtmpdb_rotate (const char *db_path, const int days, char **error,
char **wtmpdb_name, uint64_t *entries);
extern int wtmpdb_import (const char *db_path,
const /* struct utmp */ void *utmp_data,
int entries,
char **error);

/* Returns last "BOOT_TIME" entry as usec */
extern uint64_t wtmpdb_get_boottime (const char *db_path, char **error);
Expand Down
4 changes: 4 additions & 0 deletions lib/libwtmpdb.map
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ LIBWTMPDB_0.50 {
global:
wtmpdb_read_all_v2;
} LIBWTMPDB_0.8;
LIBWTMPDB_0.80 {
global:
wtmpdb_import;
} LIBWTMPDB_0.50;
119 changes: 119 additions & 0 deletions lib/sqlite.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,30 @@
#include <sys/stat.h>
#include <sqlite3.h>

#include <utmp.h>
enum utmp_type {
UTMP_EMPTY = EMPTY,
UTMP_RUN_LVL = RUN_LVL,
UTMP_BOOT_TIME = BOOT_TIME,
UTMP_USER_PROCESS = USER_PROCESS,
UTMP_DEAD_PROCESS = DEAD_PROCESS,
UTMP_ACCOUNTING = ACCOUNTING,
};
#define UTMP_NUM_TYPES (UTMP_ACCOUNTING + 1)
#undef EMPTY
#undef RUN_LVL
#undef BOOT_TIME
#undef USER_PROCESS

#include "wtmpdb.h"
#include "sqlite.h"
#include "mkdir_p.h"
#include "wtmpdb_internal.h"

#define TIMEOUT 5000 /* 5 sec */

static const int verbose = 0;

static void
strip_extension(char *in_str)
{
Expand Down Expand Up @@ -734,3 +751,105 @@ sqlite_get_boottime (const char *db_path,

return 0;
}

/* Imports utmp entries into a wtmpdb-format database.
Returns 0 on success, -1 on failure. */
int
wtmpdb_import (const char *db_path,
const void *data,
int entries,
char **error)
{
sqlite3 *db;
int row = 0;
int ret = 0;
int discarded[UTMP_NUM_TYPES + 1] = { 0 };
int imported[UTMP_NUM_TYPES + 1] = { 0 };
const struct utmp *utmp_data = (const struct utmp *) data;
int64_t last_reboot_id = -1;
int64_t *id_map;

id_map = calloc(entries, sizeof *id_map);
if (id_map == NULL)
{
*error = strdup("wtmpdb_import: out of memory allocating id map");
return -1;
}

ret = open_database_rw (db_path, &db, error);
if (ret < 0)
{
free(id_map);
return ret;
}

for (row = 0; ret == 0 && row < entries; row++)
{
const struct utmp *u = utmp_data + row;
const struct utmp *v;
int64_t id = -1;
int64_t usecs = USEC_PER_SEC * u->ut_tv.tv_sec + u->ut_tv.tv_usec;

switch (u->ut_type)
{
case UTMP_RUN_LVL:
case UTMP_BOOT_TIME:
if (u->ut_id[0] == '~' &&
u->ut_id[1] == '~' &&
u->ut_id[2] == '\0')
{
if (strcmp (u->ut_user, "reboot") == 0)
{
id = add_entry (db, WTMPDB_BOOT_TIME, "reboot", usecs, "~",
u->ut_host, NULL, error);
ret = id == -1 ? -1 : 0;
last_reboot_id = id;
}
else if (strcmp (u->ut_user, "shutdown") == 0 &&
last_reboot_id != -1)
{
ret = update_logout(db, last_reboot_id, usecs, error);
last_reboot_id = -1;
}
}
break;
case UTMP_USER_PROCESS:
id = add_entry (db, WTMPDB_USER_PROCESS,
u->ut_user, usecs, u->ut_line, u->ut_host, NULL, error);
ret = id == -1 ? -1 : 0;
break;
case UTMP_DEAD_PROCESS:
for (v = u - 1; v >= utmp_data && v->ut_type != UTMP_BOOT_TIME; v--)
{
if (v->ut_type == UTMP_USER_PROCESS &&
((u->ut_pid != 0 && v->ut_pid == u->ut_pid) ||
(u->ut_pid == 0 && strncmp(v->ut_line, u->ut_line, UT_LINESIZE) == 0)))
{
id = id_map[v - utmp_data];
if (id > 0)
ret = update_logout(db, id, usecs, error);
break;
}
}
break;
}

id_map[row] = id;

/* Increment counters */
(id == -1 ? discarded : imported)[u->ut_type >= UTMP_NUM_TYPES ? UTMP_NUM_TYPES : u->ut_type]++;
}

if (verbose)
{
printf("%-5s | %-20s | %-20s\n", "type", "imported", "discarded");
for (int i = 0; i <= UTMP_NUM_TYPES; i++) {
printf("%5d | %.20d | %.20d\n", i, imported[i], discarded[i]);
}
}

sqlite3_close (db);
free(id_map);

return ret;
}
24 changes: 24 additions & 0 deletions man/wtmpdb.8.xml
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,30 @@
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term><command>import</command>
<optional><replaceable>option</replaceable>…</optional>
<replaceable>file</replaceable>…
</term>
<listitem>
<para>
<command>wtmpdb import</command> imports legacy wtmp log
files to the <filename>/var/lib/wtmpdb/wtmp.db</filename>
database.
</para>
<title>import options</title>
<varlistentry>
<term>
<option>-f, --file</option> <replaceable>FILE</replaceable>
</term>
<listitem>
<para>
Use <replaceable>FILE</replaceable> as wtmpdb database.
</para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
<varlistentry>
<term>global options</term>
<title>global options</title>
Expand Down
103 changes: 102 additions & 1 deletion src/wtmpdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
Expand All @@ -39,7 +40,11 @@
#include <netdb.h>
#include <inttypes.h>
#include <arpa/inet.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <utmp.h>

#if HAVE_AUDIT
#include <libaudit.h>
Expand Down Expand Up @@ -603,7 +608,7 @@ usage (int retval)
FILE *output = (retval != EXIT_SUCCESS) ? stderr : stdout;

fprintf (output, "Usage: wtmpdb [command] [options]\n");
fputs ("Commands: last, boot, boottime, rotate, shutdown\n\n", output);
fputs ("Commands: last, boot, boottime, rotate, shutdown, import\n\n", output);
fputs ("Options for last:\n", output);
fputs (" -a, --hostlast Display hostnames as last entry\n", output);
fputs (" -d, --dns Translate IP addresses into a hostname\n", output);
Expand Down Expand Up @@ -644,6 +649,11 @@ usage (int retval)
fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output);
fputs ("\n", output);

fputs ("Options for import (imports legacy wtmp logs):\n", output);
fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output);
fputs (" logs... Legacy log files to import\n", output);
fputs ("\n", output);

fputs ("Generic options:\n", output);
fputs (" -h, --help Display this help message and exit\n", output);
fputs (" -v, --version Print version number and exit\n", output);
Expand Down Expand Up @@ -1180,6 +1190,95 @@ main_shutdown (int argc, char **argv)
return EXIT_SUCCESS;
}

static int
import_utmp (const char *file)
{
int fd;
int rc;
int64_t entries;
void *data;
char *error = NULL;
struct stat statbuf;

fd = open(file, O_RDONLY);
if (fd == -1)
{
fprintf (stderr, "Couldn't open '%s' to import: %s\n",
file, strerror(errno));
return -1;
}

rc = fstat(fd, &statbuf);
if (rc == -1)
{
fprintf (stderr, "Could not stat '%s': %s\n",
file, strerror(errno));
close(fd);
return -1;
}

entries = statbuf.st_size / sizeof(struct utmp);
if (entries * (off_t) sizeof(struct utmp) != statbuf.st_size)
{
fprintf (stderr, "Warning: utmp-format file is not a multiple of "
"sizeof(struct utmp) in length: %zd spare bytes, %s\n",
statbuf.st_size - entries * sizeof(struct utmp), file);
}

data = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
fprintf(stderr, "Could not map file to import: %s\n", strerror(errno));
close(fd);
return -1;
}

rc = wtmpdb_import(wtmpdb_path, data, entries, &error);
if (rc == -1)
{
fprintf(stderr, "Error importing %s: %sn", file, error);
}

munmap(data, statbuf.st_size);
close(fd);
return rc;
}

static int
main_import (int argc, char **argv)
{
struct option const longopts[] = {
{"file", required_argument, NULL, 'f'},
{NULL, 0, NULL, '\0'}
};
int c;

while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1)
{
switch (c)
{
case 'f':
wtmpdb_path = optarg;
break;
default:
usage (EXIT_FAILURE);
break;
}
}

if (argc == optind)
{
fprintf (stderr, "No files specified to import.\n");
usage (EXIT_FAILURE);
}

for (; optind < argc; optind++)
if (import_utmp(argv[optind]) == -1)
return EXIT_FAILURE;

return EXIT_SUCCESS;
}

int
main (int argc, char **argv)
{
Expand All @@ -1204,6 +1303,8 @@ main (int argc, char **argv)
return main_boottime (--argc, ++argv);
else if (strcmp (argv[1], "rotate") == 0)
return main_rotate (--argc, ++argv);
else if (strcmp (argv[1], "import") == 0)
return main_import (--argc, ++argv);

while ((c = getopt_long (argc, argv, "hv", longopts, NULL)) != -1)
{
Expand Down

0 comments on commit 466dc85

Please sign in to comment.