1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
23 /* All Rights Reserved */
24
25
26 /*
27 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
28 * Use is subject to license terms.
29 */
30
31 /*
32 * acctcon1 [-p] [-t] [-l file] [-o file] <wtmpx-file >ctmp-file
33 * -p print input only, no processing
34 * -t test mode: use latest time found in input, rather than
35 * current time when computing times of lines still on
36 * (only way to get repeatable data from old files)
37 * -l file causes output of line usage summary
38 * -o file causes first/last/reboots report to be written to file
39 * reads input (normally /var/adm/wtmpx), produces
40 * list of sessions, sorted by ending time in ctmp.h/ascii format
41 * A_TSIZE is max # distinct ttys
42 */
43
44 #include <sys/types.h>
45 #include "acctdef.h"
46 #include <stdio.h>
47 #include <ctype.h>
48 #include <time.h>
49 #include <utmpx.h>
50 #include <locale.h>
51 #include <stdlib.h>
52
53 int a_tsize = A_TSIZE;
54 int tsize = -1; /* used slots in tbuf table */
55 struct utmpx wb; /* record structure read into */
56 struct ctmp cb; /* record structure written out of */
57
58 struct tbuf {
59 char tline[LSZ]; /* /dev/... */
60 char tname[NSZ]; /* user name */
61 time_t ttime; /* start time */
62 dev_t tdev; /* device */
63 int tlsess; /* # complete sessions */
64 int tlon; /* # times on (ut_type of 7) */
65 int tloff; /* # times off (ut_type != 7) */
66 long ttotal; /* total time used on this line */
67 } * tbuf;
68
69 #define DATE_FMT "%a %b %e %H:%M:%S %Y\n"
70 int nsys;
71 struct sys {
72 char sname[LSZ]; /* reasons for ACCOUNTING records */
73 char snum; /* number of times encountered */
74 } sy[NSYS];
75
76 time_t datetime; /* old time if date changed, otherwise 0 */
77 time_t firstime;
78 time_t lastime;
79 int ndates; /* number of times date changed */
80 int exitcode;
81 char *report = NULL;
82 char *replin = NULL;
83 int printonly;
84 int tflag;
85
86 static char time_buf[50];
87 uid_t namtouid();
88 dev_t lintodev();
89 static size_t wread(void);
90 static int valid(void);
91 static void fixup(FILE *);
92 static void loop(void);
93 static void bootshut(void);
94 static int iline(void);
95 static void upall(void);
96 static void update(struct tbuf *);
97 static void printrep(void);
98 static void printlin(void);
99 static void prctmp(struct ctmp *);
100
101 int
main(int argc,char ** argv)102 main(int argc, char **argv)
103 {
104 char *prog = argv[0];
105
106 (void)setlocale(LC_ALL, "");
107 while (--argc > 0 && **++argv == '-')
108 switch(*++*argv) {
109 case 'l':
110 if (--argc > 0)
111 replin = *++argv;
112 continue;
113 case 'o':
114 if (--argc > 0)
115 report = *++argv;
116 continue;
117 case 'p':
118 printonly++;
119 continue;
120 case 't':
121 tflag++;
122 continue;
123 default:
124 fprintf(stderr, "usage: %s [-p] [-t] [-l lineuse] [-o reboot]\n", prog);
125 exit(1);
126
127 }
128
129 if ((tbuf = (struct tbuf *) calloc(a_tsize,
130 sizeof (struct tbuf))) == NULL) {
131 fprintf(stderr, "acctcon1: Cannot allocate memory\n");
132 exit(3);
133 }
134
135 if (printonly) {
136 while (wread()) {
137 if (valid()) {
138 printf("%.*s\t%.*s\t%lu",
139 sizeof (wb.ut_line),
140 wb.ut_line,
141 sizeof (wb.ut_name),
142 wb.ut_name,
143 wb.ut_xtime);
144 cftime(time_buf, DATE_FMT, &wb.ut_xtime);
145 printf("\t%s", time_buf);
146 } else
147 fixup(stdout);
148
149 }
150 exit(exitcode);
151 }
152
153 while (wread()) {
154 if (firstime == 0)
155 firstime = wb.ut_xtime;
156 if (valid())
157 loop();
158 else
159 fixup(stderr);
160 }
161 wb.ut_name[0] = '\0';
162 strcpy(wb.ut_line, "acctcon1");
163 wb.ut_type = ACCOUNTING;
164 if (tflag)
165 wb.ut_xtime = lastime;
166 else
167 time(&wb.ut_xtime);
168 loop();
169 if (report != NULL)
170 printrep();
171 if (replin != NULL)
172 printlin();
173 exit(exitcode);
174 }
175
176 static size_t
wread()177 wread()
178 {
179 return (fread(&wb, sizeof(wb), 1, stdin) == 1);
180
181 }
182
183 /*
184 * valid: check input wtmp record, return 1 if looks OK
185 */
186 static int
valid()187 valid()
188 {
189 int i, c;
190
191 /* XPG say that user names should not start with a "-". */
192 if ((c = wb.ut_name[0]) == '-')
193 return(0);
194
195 for (i = 0; i < NSZ; i++) {
196 c = wb.ut_name[i];
197 if (isalnum(c) || c == '$' || c == ' ' || c == '_' || c == '-')
198 continue;
199 else if (c == '\0')
200 break;
201 else
202 return(0);
203 }
204
205 if((wb.ut_type >= EMPTY) && (wb.ut_type <= UTMAXTYPE))
206 return(1);
207
208 return(0);
209 }
210
211 /*
212 * fixup assumes that V6 wtmp (16 bytes long) is mixed in with
213 * V7 records (20 bytes each)
214 *
215 * Starting with Release 5.0 of UNIX, this routine will no
216 * longer reset the read pointer. This has a snowball effect
217 * On the following records until the offset corrects itself.
218 * If a message is printed from here, it should be regarded as
219 * a bad record and not as a V6 record.
220 */
221 static void
fixup(FILE * stream)222 fixup(FILE *stream)
223 {
224 fprintf(stream, "bad wtmpx: offset %lu.\n", ftell(stdin)-sizeof(wb));
225 fprintf(stream, "bad record is: %.*s\t%.*s\t%lu",
226 sizeof (wb.ut_line),
227 wb.ut_line,
228 sizeof (wb.ut_name),
229 wb.ut_name,
230 wb.ut_xtime);
231 cftime(time_buf, DATE_FMT, &wb.ut_xtime);
232 fprintf(stream, "\t%s", time_buf);
233 #ifdef V6
234 fseek(stdin, (long)-4, 1);
235 #endif
236 exitcode = 1;
237 }
238
239 static void
loop()240 loop()
241 {
242 int timediff;
243 struct tbuf *tp;
244
245 if(wb.ut_line[0] == '\0' ) /* It's an init admin process */
246 return; /* no connect accounting data here */
247 switch(wb.ut_type) {
248 case OLD_TIME:
249 datetime = wb.ut_xtime;
250 return;
251 case NEW_TIME:
252 if(datetime == 0)
253 return;
254 timediff = wb.ut_xtime - datetime;
255 for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
256 tp->ttime += timediff;
257 datetime = 0;
258 ndates++;
259 return;
260 case BOOT_TIME:
261 upall();
262 /* FALLTHROUGH */
263 case ACCOUNTING:
264 case RUN_LVL:
265 lastime = wb.ut_xtime;
266 bootshut();
267 return;
268 case USER_PROCESS:
269 case LOGIN_PROCESS:
270 case INIT_PROCESS:
271 case DEAD_PROCESS:
272 update(&tbuf[iline()]);
273 return;
274 case EMPTY:
275 return;
276 default:
277 cftime(time_buf, DATE_FMT, &wb.ut_xtime);
278 fprintf(stderr, "acctcon1: invalid type %d for %s %s %s",
279 wb.ut_type,
280 wb.ut_name,
281 wb.ut_line,
282 time_buf);
283 }
284 }
285
286 /*
287 * bootshut: record reboot (or shutdown)
288 * bump count, looking up wb.ut_line in sy table
289 */
290 static void
bootshut()291 bootshut()
292 {
293 int i;
294
295 for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++)
296 ;
297 if (i >= nsys) {
298 if (++nsys > NSYS) {
299 fprintf(stderr,
300 "acctcon1: recompile with larger NSYS\n");
301 nsys = NSYS;
302 return;
303 }
304 CPYN(sy[i].sname, wb.ut_line);
305 }
306 sy[i].snum++;
307 }
308
309 /*
310 * iline: look up/enter current line name in tbuf, return index
311 * (used to avoid system dependencies on naming)
312 */
313 static int
iline()314 iline()
315 {
316 int i;
317
318 for (i = 0; i <= tsize; i++)
319 if (EQN(wb.ut_line, tbuf[i].tline))
320 return(i);
321 if (++tsize >= a_tsize) {
322 a_tsize = a_tsize + A_TSIZE;
323 if ((tbuf = (struct tbuf *) realloc(tbuf, a_tsize *
324 sizeof (struct tbuf))) == NULL) {
325 fprintf(stderr, "acctcon1: Cannot reallocate memory\n");
326 exit(2);
327 }
328 }
329
330 CPYN(tbuf[tsize].tline, wb.ut_line);
331 tbuf[tsize].tdev = lintodev(wb.ut_line);
332 return(tsize);
333 }
334
335 static void
upall()336 upall()
337 {
338 struct tbuf *tp;
339
340 wb.ut_type = INIT_PROCESS; /* fudge a logoff for reboot record */
341 for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
342 update(tp);
343 }
344
345 /*
346 * update tbuf with new time, write ctmp record for end of session
347 */
348 static void
update(struct tbuf * tp)349 update(struct tbuf *tp)
350 {
351 time_t told, /* last time for tbuf record */
352 tnew; /* time of this record */
353 /* Difference is connect time */
354
355 told = tp->ttime;
356 tnew = wb.ut_xtime;
357 cftime(time_buf, DATE_FMT, &told);
358 fprintf(stderr, "The old time is: %s", time_buf);
359 cftime(time_buf, DATE_FMT, &tnew);
360 fprintf(stderr, "the new time is: %s", time_buf);
361 if (told > tnew) {
362 cftime(time_buf, DATE_FMT, &told);
363 fprintf(stderr, "acctcon1: bad times: old: %s", time_buf);
364 cftime(time_buf, DATE_FMT, &tnew);
365 fprintf(stderr, "new: %s", time_buf);
366 exitcode = 1;
367 tp->ttime = tnew;
368 return;
369 }
370 tp->ttime = tnew;
371 switch(wb.ut_type) {
372 case USER_PROCESS:
373 tp->tlsess++;
374 if(tp->tname[0] != '\0') { /* Someone logged in without */
375 /* logging off. Put out record. */
376 cb.ct_tty = tp->tdev;
377 CPYN(cb.ct_name, tp->tname);
378 cb.ct_uid = namtouid(cb.ct_name);
379 cb.ct_start = told;
380 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
381 cb.ct_con) == 0) {
382 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n");
383
384 exit(1);
385 }
386 prctmp(&cb);
387 tp->ttotal += tnew-told;
388 }
389 else /* Someone just logged in */
390 tp->tlon++;
391 CPYN(tp->tname, wb.ut_name);
392 break;
393 case INIT_PROCESS:
394 case LOGIN_PROCESS:
395 case DEAD_PROCESS:
396 tp->tloff++;
397 if(tp->tname[0] != '\0') { /* Someone logged off */
398 /* Set up and print ctmp record */
399 cb.ct_tty = tp->tdev;
400 CPYN(cb.ct_name, tp->tname);
401 cb.ct_uid = namtouid(cb.ct_name);
402 cb.ct_start = told;
403 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
404 cb.ct_con) == 0) {
405 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n");
406 exit(1);
407 }
408 prctmp(&cb);
409 tp->ttotal += tnew-told;
410 tp->tname[0] = '\0';
411 }
412 }
413 }
414
415 static void
printrep()416 printrep()
417 {
418 int i;
419
420 freopen(report, "w", stdout);
421 cftime(time_buf, DATE_FMT, &firstime);
422 printf("from %s", time_buf);
423 cftime(time_buf, DATE_FMT, &lastime);
424 printf("to %s", time_buf);
425 if (ndates)
426 printf("%d\tdate change%c\n",ndates,(ndates>1 ? 's' : '\0'));
427 for (i = 0; i < nsys; i++)
428 printf("%d\t%.*s\n", sy[i].snum,
429 sizeof (sy[i].sname), sy[i].sname);
430 }
431
432 /*
433 * print summary of line usage
434 * accuracy only guaranteed for wtmpx file started fresh
435 */
436 static void
printlin()437 printlin()
438 {
439 struct tbuf *tp;
440 double timet, timei;
441 double ttime;
442 int tsess, ton, toff;
443
444 freopen(replin, "w", stdout);
445 ttime = 0.0;
446 tsess = ton = toff = 0;
447 timet = MINS(lastime-firstime);
448 printf("TOTAL DURATION IS %.0f MINUTES\n", timet);
449 printf("LINE MINUTES PERCENT # SESS # ON # OFF\n");
450 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) {
451 timei = MINS(tp->ttotal);
452 ttime += timei;
453 tsess += tp->tlsess;
454 ton += tp->tlon;
455 toff += tp->tloff;
456 printf("%-*.*s %-7.0f %-7.0f %-6d %-4d %-5d\n",
457 OUTPUT_LSZ,
458 OUTPUT_LSZ,
459 tp->tline,
460 timei,
461 (timet > 0.)? 100*timei/timet : 0.,
462 tp->tlsess,
463 tp->tlon,
464 tp->tloff);
465 }
466 printf("TOTALS %-7.0f -- %-6d %-4d %-5d\n",
467 ttime, tsess, ton, toff);
468 }
469
470 static void
prctmp(struct ctmp * t)471 prctmp(struct ctmp *t)
472 {
473
474 printf("%u\t%ld\t%.*s\t%lu\t%lu\t%lu",
475 t->ct_tty,
476 t->ct_uid,
477 OUTPUT_NSZ,
478 t->ct_name,
479 t->ct_con[0],
480 t->ct_con[1],
481 t->ct_start);
482 cftime(time_buf, DATE_FMT, &t->ct_start);
483 printf("\t%s", time_buf);
484 }
485