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
, ',');
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
, ISO_TIMESTAMP_COMMA_GT
, time_string
,
106 sizeof(time_string
)) != 0)
109 cleanse(ut
->ut_user
);
110 cleanse(ut
->ut_line
);
111 cleanse(ut
->ut_host
);
113 /* type pid id user line host addr time */
114 fprintf(out
, "[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15s] [%s]\n",
115 ut
->ut_type
, ut
->ut_pid
, ut
->ut_id
,
116 8, (int)sizeof(ut
->ut_user
), ut
->ut_user
,
117 12, (int)sizeof(ut
->ut_line
), ut
->ut_line
,
118 20, (int)sizeof(ut
->ut_host
), ut
->ut_host
,
119 addr_string
, time_string
);
122 #ifdef HAVE_INOTIFY_INIT
123 #define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)
126 static void roll_file(const char *filename
, off_t
*size
, FILE *out
)
133 if (!(in
= fopen(filename
, "r")))
134 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
136 if (fstat(fileno(in
), &st
) == -1)
137 err(EXIT_FAILURE
, _("stat of %s failed"), filename
);
139 if (st
.st_size
== *size
)
142 if (fseek(in
, *size
, SEEK_SET
) != (off_t
) -1) {
143 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
144 print_utline(&ut
, out
);
148 /* If we've successfully read something, use the file position, this
149 * avoids data duplication. If we read nothing or hit an error,
150 * reset to the reported size, this handles truncated files.
152 *size
= (pos
!= -1 && pos
!= *size
) ? pos
: st
.st_size
;
158 static int follow_by_inotify(FILE *in
, const char *filename
, FILE *out
)
160 char buf
[NEVENTS
* sizeof(struct inotify_event
)];
167 return -1; /* probably reached any limit ... */
172 wd
= inotify_add_watch(fd
, filename
, EVENTS
);
174 err(EXIT_FAILURE
, _("%s: cannot add inotify watch."), filename
);
178 length
= read(fd
, buf
, sizeof(buf
));
180 if (length
< 0 && (errno
== EINTR
|| errno
== EAGAIN
))
183 err(EXIT_FAILURE
, _("%s: cannot read inotify events"),
186 for (event
= 0; event
< length
;) {
187 struct inotify_event
*ev
=
188 (struct inotify_event
*) &buf
[event
];
190 if (ev
->mask
& IN_MODIFY
)
191 roll_file(filename
, &size
, out
);
197 event
+= sizeof(struct inotify_event
) + ev
->len
;
204 #endif /* HAVE_INOTIFY_INIT */
206 static FILE *dump(FILE *in
, const char *filename
, int follow
, FILE *out
)
211 ignore_result( fseek(in
, -10 * sizeof(ut
), SEEK_END
) );
213 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
214 print_utline(&ut
, out
);
219 #ifdef HAVE_INOTIFY_INIT
220 if (follow_by_inotify(in
, filename
, out
) == 0)
221 return NULL
; /* file already closed */
224 /* fallback for systems without inotify or with non-free
225 * inotify instances */
227 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
228 print_utline(&ut
, out
);
236 /* This function won't work properly if there's a ']' or a ' ' in the real
237 * token. Thankfully, this should never happen. */
238 static int gettok(char *line
, char *dest
, int size
, int eatspace
)
240 int bpos
, epos
, eaten
;
242 bpos
= strchr(line
, '[') - line
;
244 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
247 epos
= strchr(line
, ']') - line
;
249 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
252 eaten
= bpos
+ epos
+ 1;
256 if ((t
= strchr(line
, ' ')))
259 strncpy(dest
, line
, size
);
264 static void undump(FILE *in
, FILE *out
)
267 char s_addr
[INET6_ADDRSTRLEN
+ 1], s_time
[29], *linestart
, *line
;
269 linestart
= xmalloc(1024 * sizeof(*linestart
));
272 while (fgets(linestart
, 1023, in
)) {
274 memset(&ut
, '\0', sizeof(ut
));
275 sscanf(line
, "[%hd] [%d] [%4c] ", &ut
.ut_type
, &ut
.ut_pid
, ut
.ut_id
);
278 line
+= gettok(line
, ut
.ut_user
, sizeof(ut
.ut_user
), 1);
279 line
+= gettok(line
, ut
.ut_line
, sizeof(ut
.ut_line
), 1);
280 line
+= gettok(line
, ut
.ut_host
, sizeof(ut
.ut_host
), 1);
281 line
+= gettok(line
, s_addr
, sizeof(s_addr
) - 1, 1);
282 gettok(line
, s_time
, sizeof(s_time
) - 1, 0);
283 if (strchr(s_addr
, '.'))
284 inet_pton(AF_INET
, s_addr
, &(ut
.ut_addr_v6
));
286 inet_pton(AF_INET6
, s_addr
, &(ut
.ut_addr_v6
));
288 ut
.ut_tv
.tv_sec
= strtotime(s_time
);
289 ut
.ut_tv
.tv_usec
= strtousec(s_time
);
291 ignore_result( fwrite(&ut
, sizeof(ut
), 1, out
) );
297 static void __attribute__((__noreturn__
)) usage(void)
300 fputs(USAGE_HEADER
, out
);
303 _(" %s [options] [filename]\n"), program_invocation_short_name
);
305 fputs(USAGE_SEPARATOR
, out
);
306 fputs(_("Dump UTMP and WTMP files in raw format.\n"), out
);
308 fputs(USAGE_OPTIONS
, out
);
309 fputs(_(" -f, --follow output appended data as the file grows\n"), out
);
310 fputs(_(" -r, --reverse write back dumped data into utmp file\n"), out
);
311 fputs(_(" -o, --output <file> write to file instead of standard output\n"), out
);
312 printf(USAGE_HELP_OPTIONS(22));
314 printf(USAGE_MAN_TAIL("utmpdump(1)"));
318 int main(int argc
, char **argv
)
321 FILE *in
= NULL
, *out
= NULL
;
322 int reverse
= 0, follow
= 0;
323 const char *filename
= NULL
;
325 static const struct option longopts
[] = {
326 { "follow", no_argument
, NULL
, 'f' },
327 { "reverse", no_argument
, NULL
, 'r' },
328 { "output", required_argument
, NULL
, 'o' },
329 { "help", no_argument
, NULL
, 'h' },
330 { "version", no_argument
, NULL
, 'V' },
334 setlocale(LC_ALL
, "");
335 bindtextdomain(PACKAGE
, LOCALEDIR
);
337 close_stdout_atexit();
339 while ((c
= getopt_long(argc
, argv
, "fro:hV", longopts
, NULL
)) != -1) {
350 out
= fopen(optarg
, "w");
352 err(EXIT_FAILURE
, _("cannot open %s"),
359 print_version(EXIT_SUCCESS
);
361 errtryhelp(EXIT_FAILURE
);
369 filename
= argv
[optind
];
370 in
= fopen(filename
, "r");
372 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
375 errx(EXIT_FAILURE
, _("following standard input is unsupported"));
376 filename
= "/dev/stdin";
381 fprintf(stderr
, _("Utmp undump of %s\n"), filename
);
384 fprintf(stderr
, _("Utmp dump of %s\n"), filename
);
385 in
= dump(in
, filename
, follow
, out
);
388 if (out
!= stdout
&& close_stream(out
))
389 err(EXIT_FAILURE
, _("write failed"));
391 if (in
&& in
!= stdin
)