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"
48 #include "timeutils.h"
50 static time_t strtotime(const char *s_time
)
54 memset(&tm
, '\0', sizeof(struct tm
));
56 if (s_time
[0] == ' ' || s_time
[0] == '\0')
59 if (isdigit(s_time
[0])) {
60 /* [1998-09-01T01:00:00,000000+00:00]
61 * Subseconds are parsed with strtousec(). Timezone is
63 strptime(s_time
, "%Y-%m-%dT%H:%M:%S", &tm
);
65 /* [Tue Sep 01 00:00:00 1998 GMT] */
66 strptime(s_time
, "%a %b %d %T %Y", &tm
);
67 /* Cheesy way of checking for DST. This could be needed
68 * with legacy dumps that used localtime(3). */
69 if (s_time
[26] == 'D')
75 static suseconds_t
strtousec(const char *s_time
)
77 const char *s
= strchr(s_time
, ',');
79 return (suseconds_t
) atoi(s
+ 1);
83 #define cleanse(x) xcleanse(x, sizeof(x))
84 static void xcleanse(char *s
, int len
)
86 for ( ; *s
&& len
-- > 0; s
++)
87 if (!isprint(*s
) || *s
== '[' || *s
== ']')
91 static void print_utline(struct utmpx
*ut
, FILE *out
)
93 const char *addr_string
;
94 char buffer
[INET6_ADDRSTRLEN
];
98 if (ut
->ut_addr_v6
[1] || ut
->ut_addr_v6
[2] || ut
->ut_addr_v6
[3])
99 addr_string
= inet_ntop(AF_INET6
, &(ut
->ut_addr_v6
), buffer
, sizeof(buffer
));
101 addr_string
= inet_ntop(AF_INET
, &(ut
->ut_addr_v6
), buffer
, sizeof(buffer
));
103 tv
.tv_sec
= ut
->ut_tv
.tv_sec
;
104 tv
.tv_usec
= ut
->ut_tv
.tv_usec
;
106 if (strtimeval_iso(&tv
,
107 ISO_8601_DATE
| ISO_8601_TIME
| ISO_8601_COMMAUSEC
|
108 ISO_8601_TIMEZONE
| ISO_8601_GMTIME
, time_string
,
109 sizeof(time_string
)) != 0)
112 cleanse(ut
->ut_user
);
113 cleanse(ut
->ut_line
);
114 cleanse(ut
->ut_host
);
116 /* type pid id user line host addr time */
117 fprintf(out
, "[%d] [%05d] [%-4.4s] [%-*.*s] [%-*.*s] [%-*.*s] [%-15s] [%s]\n",
118 ut
->ut_type
, ut
->ut_pid
, ut
->ut_id
,
119 8, (int)sizeof(ut
->ut_user
), ut
->ut_user
,
120 12, (int)sizeof(ut
->ut_line
), ut
->ut_line
,
121 20, (int)sizeof(ut
->ut_host
), ut
->ut_host
,
122 addr_string
, time_string
);
125 #ifdef HAVE_INOTIFY_INIT
126 #define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)
129 static void roll_file(const char *filename
, off_t
*size
, FILE *out
)
136 if (!(in
= fopen(filename
, "r")))
137 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
139 if (fstat(fileno(in
), &st
) == -1)
140 err(EXIT_FAILURE
, _("stat of %s failed"), filename
);
142 if (st
.st_size
== *size
)
145 if (fseek(in
, *size
, SEEK_SET
) != (off_t
) -1) {
146 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
147 print_utline(&ut
, out
);
151 /* If we've successfully read something, use the file position, this
152 * avoids data duplication. If we read nothing or hit an error,
153 * reset to the reported size, this handles truncated files.
155 *size
= (pos
!= -1 && pos
!= *size
) ? pos
: st
.st_size
;
161 static int follow_by_inotify(FILE *in
, const char *filename
, FILE *out
)
163 char buf
[NEVENTS
* sizeof(struct inotify_event
)];
170 return -1; /* probably reached any limit ... */
175 wd
= inotify_add_watch(fd
, filename
, EVENTS
);
177 err(EXIT_FAILURE
, _("%s: cannot add inotify watch."), filename
);
181 length
= read(fd
, buf
, sizeof(buf
));
183 if (length
< 0 && (errno
== EINTR
|| errno
== EAGAIN
))
186 err(EXIT_FAILURE
, _("%s: cannot read inotify events"),
189 for (event
= 0; event
< length
;) {
190 struct inotify_event
*ev
=
191 (struct inotify_event
*) &buf
[event
];
193 if (ev
->mask
& IN_MODIFY
)
194 roll_file(filename
, &size
, out
);
200 event
+= sizeof(struct inotify_event
) + ev
->len
;
207 #endif /* HAVE_INOTIFY_INIT */
209 static FILE *dump(FILE *in
, const char *filename
, int follow
, FILE *out
)
214 ignore_result( fseek(in
, -10 * sizeof(ut
), SEEK_END
) );
216 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
217 print_utline(&ut
, out
);
222 #ifdef HAVE_INOTIFY_INIT
223 if (follow_by_inotify(in
, filename
, out
) == 0)
224 return NULL
; /* file already closed */
227 /* fallback for systems without inotify or with non-free
228 * inotify instances */
230 while (fread(&ut
, sizeof(ut
), 1, in
) == 1)
231 print_utline(&ut
, out
);
239 /* This function won't work properly if there's a ']' or a ' ' in the real
240 * token. Thankfully, this should never happen. */
241 static int gettok(char *line
, char *dest
, int size
, int eatspace
)
243 int bpos
, epos
, eaten
;
245 bpos
= strchr(line
, '[') - line
;
247 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
250 epos
= strchr(line
, ']') - line
;
252 errx(EXIT_FAILURE
, _("Extraneous newline in file. Exiting."));
255 eaten
= bpos
+ epos
+ 1;
259 if ((t
= strchr(line
, ' ')))
262 strncpy(dest
, line
, size
);
267 static void undump(FILE *in
, FILE *out
)
270 char s_addr
[INET6_ADDRSTRLEN
+ 1], s_time
[29], *linestart
, *line
;
273 linestart
= xmalloc(1024 * sizeof(*linestart
));
276 while (fgets(linestart
, 1023, in
)) {
278 memset(&ut
, '\0', sizeof(ut
));
279 sscanf(line
, "[%hd] [%d] [%4c] ", &ut
.ut_type
, &ut
.ut_pid
, ut
.ut_id
);
282 line
+= gettok(line
, ut
.ut_user
, sizeof(ut
.ut_user
), 1);
283 line
+= gettok(line
, ut
.ut_line
, sizeof(ut
.ut_line
), 1);
284 line
+= gettok(line
, ut
.ut_host
, sizeof(ut
.ut_host
), 1);
285 line
+= gettok(line
, s_addr
, sizeof(s_addr
) - 1, 1);
286 gettok(line
, s_time
, sizeof(s_time
) - 1, 0);
287 if (strchr(s_addr
, '.'))
288 inet_pton(AF_INET
, s_addr
, &(ut
.ut_addr_v6
));
290 inet_pton(AF_INET6
, s_addr
, &(ut
.ut_addr_v6
));
292 ut
.ut_tv
.tv_sec
= strtotime(s_time
);
293 ut
.ut_tv
.tv_usec
= strtousec(s_time
);
295 ignore_result( fwrite(&ut
, sizeof(ut
), 1, out
) );
303 static void __attribute__((__noreturn__
)) usage(FILE *out
)
305 fputs(USAGE_HEADER
, out
);
308 _(" %s [options] [filename]\n"), program_invocation_short_name
);
310 fputs(USAGE_SEPARATOR
, out
);
311 fputs(_("Dump UTMP and WTMP files in raw format.\n"), out
);
313 fputs(USAGE_OPTIONS
, out
);
314 fputs(_(" -f, --follow output appended data as the file grows\n"), out
);
315 fputs(_(" -r, --reverse write back dumped data into utmp file\n"), out
);
316 fputs(_(" -o, --output <file> write to file instead of standard output\n"), out
);
317 fputs(USAGE_HELP
, out
);
318 fputs(USAGE_VERSION
, out
);
320 fprintf(out
, USAGE_MAN_TAIL("utmpdump(1)"));
321 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
324 int main(int argc
, char **argv
)
327 FILE *in
= NULL
, *out
= NULL
;
328 int reverse
= 0, follow
= 0;
329 const char *filename
= NULL
;
331 static const struct option longopts
[] = {
332 { "follow", no_argument
, NULL
, 'f' },
333 { "reverse", no_argument
, NULL
, 'r' },
334 { "output", required_argument
, NULL
, 'o' },
335 { "help", no_argument
, NULL
, 'h' },
336 { "version", no_argument
, NULL
, 'V' },
340 setlocale(LC_ALL
, "");
341 bindtextdomain(PACKAGE
, LOCALEDIR
);
343 atexit(close_stdout
);
345 while ((c
= getopt_long(argc
, argv
, "fro:hV", longopts
, NULL
)) != -1) {
356 out
= fopen(optarg
, "w");
358 err(EXIT_FAILURE
, _("cannot open %s"),
366 printf(UTIL_LINUX_VERSION
);
369 errtryhelp(EXIT_FAILURE
);
377 filename
= argv
[optind
];
378 in
= fopen(filename
, "r");
380 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
383 errx(EXIT_FAILURE
, _("following standard input is unsupported"));
384 filename
= "/dev/stdin";
389 fprintf(stderr
, _("Utmp undump of %s\n"), filename
);
392 fprintf(stderr
, _("Utmp dump of %s\n"), filename
);
393 in
= dump(in
, filename
, follow
, out
);
396 if (out
!= stdout
&& close_stream(out
))
397 err(EXIT_FAILURE
, _("write failed"));
399 if (in
&& in
!= stdin
)