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 #pragma ident "%Z%%M% %I% %E% SMI"
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 case ACCOUNTING:
263 case RUN_LVL:
264 lastime = wb.ut_xtime;
265 bootshut();
266 return;
267 case USER_PROCESS:
268 case LOGIN_PROCESS:
269 case INIT_PROCESS:
270 case DEAD_PROCESS:
271 update(&tbuf[iline()]);
272 return;
273 case EMPTY:
274 return;
275 default:
276 cftime(time_buf, DATE_FMT, &wb.ut_xtime);
277 fprintf(stderr, "acctcon1: invalid type %d for %s %s %s",
278 wb.ut_type,
279 wb.ut_name,
280 wb.ut_line,
281 time_buf);
282 }
283 }
284
285 /*
286 * bootshut: record reboot (or shutdown)
287 * bump count, looking up wb.ut_line in sy table
288 */
289 static void
bootshut()290 bootshut()
291 {
292 int i;
293
294 for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++)
295 ;
296 if (i >= nsys) {
297 if (++nsys > NSYS) {
298 fprintf(stderr,
299 "acctcon1: recompile with larger NSYS\n");
300 nsys = NSYS;
301 return;
302 }
303 CPYN(sy[i].sname, wb.ut_line);
304 }
305 sy[i].snum++;
306 }
307
308 /*
309 * iline: look up/enter current line name in tbuf, return index
310 * (used to avoid system dependencies on naming)
311 */
312 static int
iline()313 iline()
314 {
315 int i;
316
317 for (i = 0; i <= tsize; i++)
318 if (EQN(wb.ut_line, tbuf[i].tline))
319 return(i);
320 if (++tsize >= a_tsize) {
321 a_tsize = a_tsize + A_TSIZE;
322 if ((tbuf = (struct tbuf *) realloc(tbuf, a_tsize *
323 sizeof (struct tbuf))) == NULL) {
324 fprintf(stderr, "acctcon1: Cannot reallocate memory\n");
325 exit(2);
326 }
327 }
328
329 CPYN(tbuf[tsize].tline, wb.ut_line);
330 tbuf[tsize].tdev = lintodev(wb.ut_line);
331 return(tsize);
332 }
333
334 static void
upall()335 upall()
336 {
337 struct tbuf *tp;
338
339 wb.ut_type = INIT_PROCESS; /* fudge a logoff for reboot record */
340 for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
341 update(tp);
342 }
343
344 /*
345 * update tbuf with new time, write ctmp record for end of session
346 */
347 static void
update(struct tbuf * tp)348 update(struct tbuf *tp)
349 {
350 time_t told, /* last time for tbuf record */
351 tnew; /* time of this record */
352 /* Difference is connect time */
353
354 told = tp->ttime;
355 tnew = wb.ut_xtime;
356 cftime(time_buf, DATE_FMT, &told);
357 fprintf(stderr, "The old time is: %s", time_buf);
358 cftime(time_buf, DATE_FMT, &tnew);
359 fprintf(stderr, "the new time is: %s", time_buf);
360 if (told > tnew) {
361 cftime(time_buf, DATE_FMT, &told);
362 fprintf(stderr, "acctcon1: bad times: old: %s", time_buf);
363 cftime(time_buf, DATE_FMT, &tnew);
364 fprintf(stderr, "new: %s", time_buf);
365 exitcode = 1;
366 tp->ttime = tnew;
367 return;
368 }
369 tp->ttime = tnew;
370 switch(wb.ut_type) {
371 case USER_PROCESS:
372 tp->tlsess++;
373 if(tp->tname[0] != '\0') { /* Someone logged in without */
374 /* logging off. Put out record. */
375 cb.ct_tty = tp->tdev;
376 CPYN(cb.ct_name, tp->tname);
377 cb.ct_uid = namtouid(cb.ct_name);
378 cb.ct_start = told;
379 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
380 cb.ct_con) == 0) {
381 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n");
382
383 exit(1);
384 }
385 prctmp(&cb);
386 tp->ttotal += tnew-told;
387 }
388 else /* Someone just logged in */
389 tp->tlon++;
390 CPYN(tp->tname, wb.ut_name);
391 break;
392 case INIT_PROCESS:
393 case LOGIN_PROCESS:
394 case DEAD_PROCESS:
395 tp->tloff++;
396 if(tp->tname[0] != '\0') { /* Someone logged off */
397 /* Set up and print ctmp record */
398 cb.ct_tty = tp->tdev;
399 CPYN(cb.ct_name, tp->tname);
400 cb.ct_uid = namtouid(cb.ct_name);
401 cb.ct_start = told;
402 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
403 cb.ct_con) == 0) {
404 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n");
405 exit(1);
406 }
407 prctmp(&cb);
408 tp->ttotal += tnew-told;
409 tp->tname[0] = '\0';
410 }
411 }
412 }
413
414 static void
printrep()415 printrep()
416 {
417 int i;
418
419 freopen(report, "w", stdout);
420 cftime(time_buf, DATE_FMT, &firstime);
421 printf("from %s", time_buf);
422 cftime(time_buf, DATE_FMT, &lastime);
423 printf("to %s", time_buf);
424 if (ndates)
425 printf("%d\tdate change%c\n",ndates,(ndates>1 ? 's' : '\0'));
426 for (i = 0; i < nsys; i++)
427 printf("%d\t%.*s\n", sy[i].snum,
428 sizeof (sy[i].sname), sy[i].sname);
429 }
430
431 /*
432 * print summary of line usage
433 * accuracy only guaranteed for wtmpx file started fresh
434 */
435 static void
printlin()436 printlin()
437 {
438 struct tbuf *tp;
439 double timet, timei;
440 double ttime;
441 int tsess, ton, toff;
442
443 freopen(replin, "w", stdout);
444 ttime = 0.0;
445 tsess = ton = toff = 0;
446 timet = MINS(lastime-firstime);
447 printf("TOTAL DURATION IS %.0f MINUTES\n", timet);
448 printf("LINE MINUTES PERCENT # SESS # ON # OFF\n");
449 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) {
450 timei = MINS(tp->ttotal);
451 ttime += timei;
452 tsess += tp->tlsess;
453 ton += tp->tlon;
454 toff += tp->tloff;
455 printf("%-*.*s %-7.0f %-7.0f %-6d %-4d %-5d\n",
456 OUTPUT_LSZ,
457 OUTPUT_LSZ,
458 tp->tline,
459 timei,
460 (timet > 0.)? 100*timei/timet : 0.,
461 tp->tlsess,
462 tp->tlon,
463 tp->tloff);
464 }
465 printf("TOTALS %-7.0f -- %-6d %-4d %-5d\n",
466 ttime, tsess, ton, toff);
467 }
468
469 static void
prctmp(struct ctmp * t)470 prctmp(struct ctmp *t)
471 {
472
473 printf("%u\t%ld\t%.*s\t%lu\t%lu\t%lu",
474 t->ct_tty,
475 t->ct_uid,
476 OUTPUT_NSZ,
477 t->ct_name,
478 t->ct_con[0],
479 t->ct_con[1],
480 t->ct_start);
481 cftime(time_buf, DATE_FMT, &t->ct_start);
482 printf("\t%s", time_buf);
483 }
484