4 * Simple program to dump UTMP and WTMP files in raw format, so they can be
7 * Based on utmpdump dump from sysvinit suite.
9 * Copyright (C) 1991-2000 Miquel van Smoorenburg <miquels@cistron.nl>
11 * Copyright (C) 1998 Danek Duvall <duvall@alumni.princeton.edu>
12 * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
39 #ifdef HAVE_INOTIFY_INIT
40 #include <sys/inotify.h>
46 #include "closestream.h"
47 #include "timeutils.h"
49 static time_t strtotime(const char *s_time
)
53 memset(&tm
, '\0', sizeof(struct tm
));
55 if (s_time
[0] == ' ' || s_time
[0] == '\0')
58 if (isdigit(s_time
[0])) {
59 /* [1998-09-01T01:00:00,000000+00:00]
60 * Subseconds are parsed with strtousec(). Timezone is
62 strptime(s_time
, "%Y-%m-%dT%H:%M:%S", &tm
);
64 /* [Tue Sep 01 00:00:00 1998 GMT] */
65 strptime(s_time
, "%a %b %d %T %Y", &tm
);
66 /* Cheesy way of checking for DST. This could be needed
67 * with legacy dumps that used localtime(3). */
68 if (s_time
[26] == 'D')
74 static suseconds_t
strtousec(const char *s_time
)
76 const char *s
= strchr(s_time
, ',');
83 us
= strtol(s
, &end
, 10);
84 if (errno
== 0 && end
&& end
> s
)
90 #define cleanse(x) xcleanse(x, sizeof(x))
91 static void xcleanse(char *s
, int len
)
93 for ( ; *s
&& len
-- > 0; s
++)
94 if (!isprint(*s
) || *s
== '[' || *s
== ']')
98 static void print_utline(struct utmpx
*ut
, FILE *out
)
100 const char *addr_string
;
101 char buffer
[INET6_ADDRSTRLEN
];
102 char time_string
[40];
105 if (ut
->ut_addr_v6
[1] || ut
->ut_addr_v6
[2] || ut
->ut_addr_v6
[3])
106 addr_string
= inet_ntop(AF_INET6
, &(ut
->ut_addr_v6
), buffer
, sizeof(buffer
));
108 addr_string
= inet_ntop(AF_INET
, &(ut
->ut_addr_v6
), buffer
, sizeof(buffer
));
110 tv
.tv_sec
= ut
->ut_tv
.tv_sec
;
111 tv
.tv_usec
= ut
->ut_tv
.tv_usec
;
113 if (strtimeval_iso(&tv
, ISO_TIMESTAMP_COMMA_GT
, time_string
,
114 sizeof(time_string
)) != 0)
117 cleanse(ut
->ut_user
);
118 cleanse(ut
->ut_line
);
119 cleanse(ut
->ut_host
);
121 /* type pid id user line host addr time */
122 fprintf(out
, "[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15s] [%s]\n",
123 ut
->ut_type
, ut
->ut_pid
, ut
->ut_id
,
124 8, (int)sizeof(ut
->ut_user
), ut
->ut_user
,
125 12, (int)sizeof(ut
->ut_line
), ut
->ut_line
,
126 20, (int)sizeof(ut
->ut_host
), ut
->ut_host
,
127 addr_string
, time_string
);
130 #ifdef HAVE_INOTIFY_INIT
131 #define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)
134 static void roll_file(const char *filename
, off_t
*size
, FILE *out
)
141 if (!(in
= fopen(filename
, "r")))
142 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
144 if (fstat(fileno(in
), &st
) == -1)
145 err(EXIT_FAILURE
, _("stat of %s failed"), filename
);
147 if (st
.st_size
== *size
)
150 if (fseek(in
, *size
, SEEK_SET
) != (off_t
) -1) {
151 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
152 print_utline(&ut
, out
);
156 /* If we've successfully read something, use the file position, this
157 * avoids data duplication. If we read nothing or hit an error,
158 * reset to the reported size, this handles truncated files.
160 *size
= (pos
!= -1 && pos
!= *size
) ? pos
: st
.st_size
;
166 static int follow_by_inotify(FILE *in
, const char *filename
, FILE *out
)
168 char buf
[NEVENTS
* sizeof(struct inotify_event
)];
175 return -1; /* probably reached any limit ... */
181 err(EXIT_FAILURE
, _("%s: cannot get file position"), filename
);
183 wd
= inotify_add_watch(fd
, filename
, EVENTS
);
185 err(EXIT_FAILURE
, _("%s: cannot add inotify watch."), filename
);
189 length
= read(fd
, buf
, sizeof(buf
));
191 if (length
< 0 && (errno
== EINTR
|| errno
== EAGAIN
))
194 err(EXIT_FAILURE
, _("%s: cannot read inotify events"),
197 for (event
= 0; event
< length
;) {
198 struct inotify_event
*ev
=
199 (struct inotify_event
*) &buf
[event
];
201 if (ev
->mask
& IN_MODIFY
)
202 roll_file(filename
, &size
, out
);
208 event
+= sizeof(struct inotify_event
) + ev
->len
;
215 #endif /* HAVE_INOTIFY_INIT */
217 static FILE *dump(FILE *in
, const char *filename
, int follow
, FILE *out
)
222 ignore_result( fseek(in
, -10 * sizeof(ut
), SEEK_END
) );
224 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
225 print_utline(&ut
, out
);
230 #ifdef HAVE_INOTIFY_INIT
231 if (follow_by_inotify(in
, filename
, out
) == 0)
232 return NULL
; /* file already closed */
234 /* fallback for systems without inotify or with non-free
235 * inotify instances */
237 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
238 print_utline(&ut
, out
);
246 /* This function won't work properly if there's a ']' or a ' ' in the real
247 * token. Thankfully, this should never happen. */
248 static int gettok(char *line
, char *dest
, int size
, int eatspace
)
250 int bpos
, epos
, eaten
;
252 bpos
= strchr(line
, '[') - line
;
254 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
257 epos
= strchr(line
, ']') - line
;
259 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
262 eaten
= bpos
+ epos
+ 1;
266 if ((t
= strchr(line
, ' ')))
269 strncpy(dest
, line
, size
);
274 static void undump(FILE *in
, FILE *out
)
277 char s_addr
[INET6_ADDRSTRLEN
+ 1], s_time
[29] = {}, *linestart
, *line
;
279 linestart
= xmalloc(1024 * sizeof(*linestart
));
282 while (fgets(linestart
, 1023, in
)) {
284 memset(&ut
, '\0', sizeof(ut
));
286 if (sscanf(line
, "[%hd] [%d] [%4c] ",
287 &ut
.ut_type
, &ut
.ut_pid
, ut
.ut_id
) != 3) {
288 warnx(_("parse error: %s"), line
);
293 line
+= gettok(line
, ut
.ut_user
, sizeof(ut
.ut_user
), 1);
294 line
+= gettok(line
, ut
.ut_line
, sizeof(ut
.ut_line
), 1);
295 line
+= gettok(line
, ut
.ut_host
, sizeof(ut
.ut_host
), 1);
296 line
+= gettok(line
, s_addr
, sizeof(s_addr
) - 1, 1);
297 gettok(line
, s_time
, sizeof(s_time
) - 1, 0);
298 if (strchr(s_addr
, '.'))
299 inet_pton(AF_INET
, s_addr
, &(ut
.ut_addr_v6
));
301 inet_pton(AF_INET6
, s_addr
, &(ut
.ut_addr_v6
));
303 ut
.ut_tv
.tv_sec
= strtotime(s_time
);
304 ut
.ut_tv
.tv_usec
= strtousec(s_time
);
306 ignore_result( fwrite(&ut
, sizeof(ut
), 1, out
) );
312 static void __attribute__((__noreturn__
)) usage(void)
315 fputs(USAGE_HEADER
, out
);
318 _(" %s [options] [filename]\n"), program_invocation_short_name
);
320 fputs(USAGE_SEPARATOR
, out
);
321 fputs(_("Dump UTMP and WTMP files in raw format.\n"), out
);
323 fputs(USAGE_OPTIONS
, out
);
324 fputs(_(" -f, --follow output appended data as the file grows\n"), out
);
325 fputs(_(" -r, --reverse write back dumped data into utmp file\n"), out
);
326 fputs(_(" -o, --output <file> write to file instead of standard output\n"), out
);
327 printf(USAGE_HELP_OPTIONS(22));
329 printf(USAGE_MAN_TAIL("utmpdump(1)"));
333 int main(int argc
, char **argv
)
336 FILE *in
= NULL
, *out
= NULL
;
337 int reverse
= 0, follow
= 0;
338 const char *filename
= NULL
;
340 static const struct option longopts
[] = {
341 { "follow", no_argument
, NULL
, 'f' },
342 { "reverse", no_argument
, NULL
, 'r' },
343 { "output", required_argument
, NULL
, 'o' },
344 { "help", no_argument
, NULL
, 'h' },
345 { "version", no_argument
, NULL
, 'V' },
349 setlocale(LC_ALL
, "");
350 bindtextdomain(PACKAGE
, LOCALEDIR
);
352 close_stdout_atexit();
354 while ((c
= getopt_long(argc
, argv
, "fro:hV", longopts
, NULL
)) != -1) {
365 out
= fopen(optarg
, "w");
367 err(EXIT_FAILURE
, _("cannot open %s"),
374 print_version(EXIT_SUCCESS
);
376 errtryhelp(EXIT_FAILURE
);
383 if (follow
&& (out
!= stdout
|| !isatty(STDOUT_FILENO
))) {
384 setvbuf(out
, NULL
, _IOLBF
, 0);
388 filename
= argv
[optind
];
389 in
= fopen(filename
, "r");
391 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
394 errx(EXIT_FAILURE
, _("following standard input is unsupported"));
395 filename
= "/dev/stdin";
400 fprintf(stderr
, _("Utmp undump of %s\n"), filename
);
403 fprintf(stderr
, _("Utmp dump of %s\n"), filename
);
404 in
= dump(in
, filename
, follow
, out
);
407 if (out
!= stdout
&& close_stream(out
))
408 err(EXIT_FAILURE
, _("write failed"));
410 if (in
&& in
!= stdin
)