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>
45 #include "timeutils.h"
47 #include "closestream.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
, ',');
78 return (suseconds_t
) atoi(s
+ 1);
82 #define cleanse(x) xcleanse(x, sizeof(x))
83 static void xcleanse(char *s
, int len
)
85 for ( ; *s
&& len
-- > 0; s
++)
86 if (!isprint(*s
) || *s
== '[' || *s
== ']')
90 static void print_utline(struct utmpx
*ut
, FILE *out
)
92 const char *addr_string
;
93 char buffer
[INET6_ADDRSTRLEN
];
97 if (ut
->ut_addr_v6
[1] || ut
->ut_addr_v6
[2] || ut
->ut_addr_v6
[3])
98 addr_string
= inet_ntop(AF_INET6
, &(ut
->ut_addr_v6
), buffer
, sizeof(buffer
));
100 addr_string
= inet_ntop(AF_INET
, &(ut
->ut_addr_v6
), buffer
, sizeof(buffer
));
102 tv
.tv_sec
= ut
->ut_tv
.tv_sec
;
103 tv
.tv_usec
= ut
->ut_tv
.tv_usec
;
105 if (strtimeval_iso(&tv
,
106 ISO_8601_DATE
| ISO_8601_TIME
| ISO_8601_COMMAUSEC
|
107 ISO_8601_TIMEZONE
| ISO_8601_GMTIME
, time_string
,
108 sizeof(time_string
)) != 0)
111 cleanse(ut
->ut_user
);
112 cleanse(ut
->ut_line
);
113 cleanse(ut
->ut_host
);
115 /* type pid id user line host addr time */
116 fprintf(out
, "[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15s] [%s]\n",
117 ut
->ut_type
, ut
->ut_pid
, ut
->ut_id
,
118 8, (int)sizeof(ut
->ut_user
), ut
->ut_user
,
119 12, (int)sizeof(ut
->ut_line
), ut
->ut_line
,
120 20, (int)sizeof(ut
->ut_host
), ut
->ut_host
,
121 addr_string
, time_string
);
124 #ifdef HAVE_INOTIFY_INIT
125 #define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)
128 static void roll_file(const char *filename
, off_t
*size
, FILE *out
)
135 if (!(in
= fopen(filename
, "r")))
136 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
138 if (fstat(fileno(in
), &st
) == -1)
139 err(EXIT_FAILURE
, _("stat of %s failed"), filename
);
141 if (st
.st_size
== *size
)
144 if (fseek(in
, *size
, SEEK_SET
) != (off_t
) -1) {
145 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
146 print_utline(&ut
, out
);
150 /* If we've successfully read something, use the file position, this
151 * avoids data duplication. If we read nothing or hit an error,
152 * reset to the reported size, this handles truncated files.
154 *size
= (pos
!= -1 && pos
!= *size
) ? pos
: st
.st_size
;
160 static int follow_by_inotify(FILE *in
, const char *filename
, FILE *out
)
162 char buf
[NEVENTS
* sizeof(struct inotify_event
)];
169 return -1; /* probably reached any limit ... */
174 wd
= inotify_add_watch(fd
, filename
, EVENTS
);
176 err(EXIT_FAILURE
, _("%s: cannot add inotify watch."), filename
);
180 length
= read(fd
, buf
, sizeof(buf
));
182 if (length
< 0 && (errno
== EINTR
|| errno
== EAGAIN
))
185 err(EXIT_FAILURE
, _("%s: cannot read inotify events"),
188 for (event
= 0; event
< length
;) {
189 struct inotify_event
*ev
=
190 (struct inotify_event
*) &buf
[event
];
192 if (ev
->mask
& IN_MODIFY
)
193 roll_file(filename
, &size
, out
);
199 event
+= sizeof(struct inotify_event
) + ev
->len
;
206 #endif /* HAVE_INOTIFY_INIT */
208 static FILE *dump(FILE *in
, const char *filename
, int follow
, FILE *out
)
213 ignore_result( fseek(in
, -10 * sizeof(ut
), SEEK_END
) );
215 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
216 print_utline(&ut
, out
);
221 #ifdef HAVE_INOTIFY_INIT
222 if (follow_by_inotify(in
, filename
, out
) == 0)
223 return NULL
; /* file already closed */
226 /* fallback for systems without inotify or with non-free
227 * inotify instances */
229 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
230 print_utline(&ut
, out
);
238 /* This function won't work properly if there's a ']' or a ' ' in the real
239 * token. Thankfully, this should never happen. */
240 static int gettok(char *line
, char *dest
, int size
, int eatspace
)
242 int bpos
, epos
, eaten
;
244 bpos
= strchr(line
, '[') - line
;
246 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
249 epos
= strchr(line
, ']') - line
;
251 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
254 eaten
= bpos
+ epos
+ 1;
258 if ((t
= strchr(line
, ' ')))
261 strncpy(dest
, line
, size
);
266 static void undump(FILE *in
, FILE *out
)
269 char s_addr
[INET6_ADDRSTRLEN
+ 1], s_time
[29], *linestart
, *line
;
272 linestart
= xmalloc(1024 * sizeof(*linestart
));
275 while (fgets(linestart
, 1023, in
)) {
277 memset(&ut
, '\0', sizeof(ut
));
278 sscanf(line
, "[%hd] [%d] [%4c] ", &ut
.ut_type
, &ut
.ut_pid
, ut
.ut_id
);
281 line
+= gettok(line
, ut
.ut_user
, sizeof(ut
.ut_user
), 1);
282 line
+= gettok(line
, ut
.ut_line
, sizeof(ut
.ut_line
), 1);
283 line
+= gettok(line
, ut
.ut_host
, sizeof(ut
.ut_host
), 1);
284 line
+= gettok(line
, s_addr
, sizeof(s_addr
) - 1, 1);
285 gettok(line
, s_time
, sizeof(s_time
) - 1, 0);
286 if (strchr(s_addr
, '.'))
287 inet_pton(AF_INET
, s_addr
, &(ut
.ut_addr_v6
));
289 inet_pton(AF_INET6
, s_addr
, &(ut
.ut_addr_v6
));
291 ut
.ut_tv
.tv_sec
= strtotime(s_time
);
292 ut
.ut_tv
.tv_usec
= strtousec(s_time
);
294 ignore_result( fwrite(&ut
, sizeof(ut
), 1, out
) );
302 static void __attribute__((__noreturn__
)) usage(FILE *out
)
304 fputs(USAGE_HEADER
, out
);
307 _(" %s [options] [filename]\n"), program_invocation_short_name
);
309 fputs(USAGE_SEPARATOR
, out
);
310 fputs(_("Dump UTMP and WTMP files in raw format.\n"), out
);
312 fputs(USAGE_OPTIONS
, out
);
313 fputs(_(" -f, --follow output appended data as the file grows\n"), out
);
314 fputs(_(" -r, --reverse write back dumped data into utmp file\n"), out
);
315 fputs(_(" -o, --output <file> write to file instead of standard output\n"), out
);
316 fputs(USAGE_HELP
, out
);
317 fputs(USAGE_VERSION
, out
);
319 fprintf(out
, USAGE_MAN_TAIL("utmpdump(1)"));
320 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
323 int main(int argc
, char **argv
)
326 FILE *in
= NULL
, *out
= NULL
;
327 int reverse
= 0, follow
= 0;
328 const char *filename
= NULL
;
330 static const struct option longopts
[] = {
331 { "follow", 0, 0, 'f' },
332 { "reverse", 0, 0, 'r' },
333 { "output", required_argument
, 0, 'o' },
334 { "help", 0, 0, 'h' },
335 { "version", 0, 0, 'V' },
339 setlocale(LC_ALL
, "");
340 bindtextdomain(PACKAGE
, LOCALEDIR
);
342 atexit(close_stdout
);
344 while ((c
= getopt_long(argc
, argv
, "fro:hV", longopts
, NULL
)) != -1) {
355 out
= fopen(optarg
, "w");
357 err(EXIT_FAILURE
, _("cannot open %s"),
365 printf(UTIL_LINUX_VERSION
);
368 errtryhelp(EXIT_FAILURE
);
376 filename
= argv
[optind
];
377 in
= fopen(filename
, "r");
379 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
382 errx(EXIT_FAILURE
, _("following standard input is unsupported"));
383 filename
= "/dev/stdin";
388 fprintf(stderr
, _("Utmp undump of %s\n"), filename
);
391 fprintf(stderr
, _("Utmp dump of %s\n"), filename
);
392 in
= dump(in
, filename
, follow
, out
);
395 if (out
!= stdout
&& close_stream(out
))
396 err(EXIT_FAILURE
, _("write failed"));
398 if (in
&& in
!= stdin
)