]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/ac/ac.c
Use better variable naming.
[FreeBSD/FreeBSD.git] / usr.sbin / ac / ac.c
1 /*
2  *      Copyright (c) 1994 Christopher G. Demetriou.
3  *      @(#)Copyright (c) 1994, Simon J. Gerraty.
4  *
5  *      This is free software.  It comes with NO WARRANTY.
6  *      Permission to use, modify and distribute this source code
7  *      is granted subject to the following conditions.
8  *      1/ that the above copyright notice and this notice
9  *      are preserved in all copies and that due credit be given
10  *      to the author.
11  *      2/ that any changes to this code are clearly commented
12  *      as such so that the author does not get blamed for bugs
13  *      other than his own.
14  */
15
16 #include <sys/cdefs.h>
17 __FBSDID("$FreeBSD$");
18
19 #include <sys/queue.h>
20 #include <sys/time.h>
21
22 #include <err.h>
23 #include <errno.h>
24 #include <langinfo.h>
25 #include <locale.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <timeconv.h>
30 #include <unistd.h>
31 #include <utmpx.h>
32
33 /*
34  * this is for our list of currently logged in sessions
35  */
36 struct utmpx_entry {
37         SLIST_ENTRY(utmpx_entry) next;
38         char            user[sizeof(((struct utmpx *)0)->ut_user)];
39         char            id[sizeof(((struct utmpx *)0)->ut_id)];
40 #ifdef CONSOLE_TTY
41         char            line[sizeof(((struct utmpx *)0)->ut_line)];
42 #endif
43         struct timeval  time;
44 };
45
46 /*
47  * this is for our list of users that are accumulating time.
48  */
49 struct user_entry {
50         SLIST_ENTRY(user_entry) next;
51         char            user[sizeof(((struct utmpx *)0)->ut_user)];
52         struct timeval  time;
53 };
54
55 /*
56  * this is for chosing whether to ignore a login
57  */
58 struct tty_entry {
59         SLIST_ENTRY(tty_entry) next;
60         char            line[sizeof(((struct utmpx *)0)->ut_line) + 2];
61         size_t          len;
62         int             ret;
63 };
64
65 /*
66  * globals - yes yuk
67  */
68 #ifdef CONSOLE_TTY
69 static const char *Console = CONSOLE_TTY;
70 #endif
71 static struct timeval Total = { 0, 0 };
72 static struct timeval FirstTime = { 0, 0 };
73 static int      Flags = 0;
74 static SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx);
75 static SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users);
76 static SLIST_HEAD(, tty_entry) Ttys = SLIST_HEAD_INITIALIZER(Ttys);
77
78 #define AC_W    1                               /* not _PATH_WTMP */
79 #define AC_D    2                               /* daily totals (ignore -p) */
80 #define AC_P    4                               /* per-user totals */
81 #define AC_U    8                               /* specified users only */
82 #define AC_T    16                              /* specified ttys only */
83
84 static void     ac(const char *);
85 static void     usage(void);
86
87 static void
88 add_tty(const char *line)
89 {
90         struct tty_entry *tp;
91         char *rcp;
92
93         Flags |= AC_T;
94
95         if ((tp = malloc(sizeof(*tp))) == NULL)
96                 errx(1, "malloc failed");
97         tp->len = 0;                            /* full match */
98         tp->ret = 1;                            /* do if match */
99         if (*line == '!') {                     /* don't do if match */
100                 tp->ret = 0;
101                 line++;
102         }
103         strlcpy(tp->line, line, sizeof(tp->line));
104         /* Wildcard. */
105         if ((rcp = strchr(tp->line, '*')) != NULL) {
106                 *rcp = '\0';
107                 /* Match len bytes only. */
108                 tp->len = strlen(tp->line);
109         }
110         SLIST_INSERT_HEAD(&Ttys, tp, next);
111 }
112
113 /*
114  * should we process the named tty?
115  */
116 static int
117 do_tty(const char *line)
118 {
119         struct tty_entry *tp;
120         int def_ret = 0;
121
122         SLIST_FOREACH(tp, &Ttys, next) {
123                 if (tp->ret == 0)               /* specific don't */
124                         def_ret = 1;            /* default do */
125                 if (tp->len != 0) {
126                         if (strncmp(line, tp->line, tp->len) == 0)
127                                 return tp->ret;
128                 } else {
129                         if (strncmp(line, tp->line, sizeof(tp->line)) == 0)
130                                 return tp->ret;
131                 }
132         }
133         return (def_ret);
134 }
135
136 #ifdef CONSOLE_TTY
137 /*
138  * is someone logged in on Console?
139  */
140 static int
141 on_console(void)
142 {
143         struct utmpx_entry *up;
144
145         SLIST_FOREACH(up, &CurUtmpx, next)
146                 if (strcmp(up->line, Console) == 0)
147                         return (1);
148         return (0);
149 }
150 #endif
151
152 /*
153  * Update user's login time.
154  * If no entry for this user is found, a new entry is inserted into the
155  * list alphabetically.
156  */
157 static void
158 update_user(const char *user, struct timeval secs)
159 {
160         struct user_entry *up, *aup;
161         int c;
162
163         aup = NULL;
164         SLIST_FOREACH(up, &Users, next) {
165                 c = strcmp(up->user, user);
166                 if (c == 0) {
167                         timeradd(&up->time, &secs, &up->time);
168                         timeradd(&Total, &secs, &Total);
169                         return;
170                 } else if (c > 0)
171                         break;
172                 aup = up;
173         }
174         /*
175          * not found so add new user unless specified users only
176          */
177         if (Flags & AC_U)
178                 return;
179
180         if ((up = malloc(sizeof(*up))) == NULL)
181                 errx(1, "malloc failed");
182         if (aup == NULL)
183                 SLIST_INSERT_HEAD(&Users, up, next);
184         else
185                 SLIST_INSERT_AFTER(aup, up, next);
186         strlcpy(up->user, user, sizeof(up->user));
187         up->time = secs;
188         timeradd(&Total, &secs, &Total);
189 }
190
191 int
192 main(int argc, char *argv[])
193 {
194         const char *wtmpf = NULL;
195         int c;
196
197         (void) setlocale(LC_TIME, "");
198
199         while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) {
200                 switch (c) {
201                 case 'c':
202 #ifdef CONSOLE_TTY
203                         Console = optarg;
204 #else
205                         usage();                /* XXX */
206 #endif
207                         break;
208                 case 'd':
209                         Flags |= AC_D;
210                         break;
211                 case 'p':
212                         Flags |= AC_P;
213                         break;
214                 case 't':                       /* only do specified ttys */
215                         add_tty(optarg);
216                         break;
217                 case 'w':
218                         Flags |= AC_W;
219                         wtmpf = optarg;
220                         break;
221                 case '?':
222                 default:
223                         usage();
224                         break;
225                 }
226         }
227         if (optind < argc) {
228                 /*
229                  * initialize user list
230                  */
231                 for (; optind < argc; optind++) {
232                         update_user(argv[optind], (struct timeval){ 0, 0 });
233                 }
234                 Flags |= AC_U;                  /* freeze user list */
235         }
236         if (Flags & AC_D)
237                 Flags &= ~AC_P;
238         ac(wtmpf);
239
240         return (0);
241 }
242
243 /*
244  * print login time in decimal hours
245  */
246 static void
247 show(const char *user, struct timeval secs)
248 {
249         (void)printf("\t%-*s %8.2f\n",
250             (int)sizeof(((struct user_entry *)0)->user), user,
251             (double)secs.tv_sec / 3600);
252 }
253
254 static void
255 show_users(void)
256 {
257         struct user_entry *lp;
258
259         SLIST_FOREACH(lp, &Users, next)
260                 show(lp->user, lp->time);
261 }
262
263 /*
264  * print total login time for 24hr period in decimal hours
265  */
266 static void
267 show_today(struct timeval today)
268 {
269         struct user_entry *up;
270         struct utmpx_entry *lp;
271         char date[64];
272         struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday;
273         static int d_first = -1;
274
275         if (d_first < 0)
276                 d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
277         timersub(&today, &usec, &yesterday);
278         (void)strftime(date, sizeof(date),
279                        d_first ? "%e %b  total" : "%b %e  total",
280                        localtime(&yesterday.tv_sec));
281
282         SLIST_FOREACH(lp, &CurUtmpx, next) {
283                 timersub(&today, &lp->time, &diff);
284                 update_user(lp->user, diff);
285                 /* As if they just logged in. */
286                 lp->time = today;
287         }
288         SLIST_FOREACH(up, &Users, next) {
289                 timeradd(&total, &up->time, &total);
290                 /* For next day. */
291                 timerclear(&up->time);
292         }
293         if (timerisset(&total))
294                 (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600);
295 }
296
297 /*
298  * Log a user out and update their times.
299  * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the
300  * system has been shut down.
301  */
302 static void
303 log_out(const struct utmpx *up)
304 {
305         struct utmpx_entry *lp, *lp2, *tlp;
306         struct timeval secs;
307
308         for (lp = SLIST_FIRST(&CurUtmpx), lp2 = NULL; lp != NULL;)
309                 if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME ||
310                     (up->ut_type == DEAD_PROCESS &&
311                     memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) {
312                         timersub(&up->ut_tv, &lp->time, &secs);
313                         update_user(lp->user, secs);
314                         /*
315                          * now lose it
316                          */
317                         tlp = lp;
318                         lp = SLIST_NEXT(lp, next);
319                         if (lp2 == NULL)
320                                 SLIST_REMOVE_HEAD(&CurUtmpx, next);
321                         else
322                                 SLIST_REMOVE_AFTER(lp2, next);
323                         free(tlp);
324                 } else {
325                         lp2 = lp;
326                         lp = SLIST_NEXT(lp, next);
327                 }
328 }
329
330 /*
331  * if do_tty says ok, login a user
332  */
333 static void
334 log_in(struct utmpx *up)
335 {
336         struct utmpx_entry *lp;
337
338         /*
339          * this could be a login. if we're not dealing with
340          * the console name, say it is.
341          *
342          * If we are, and if ut_host==":0.0" we know that it
343          * isn't a real login. _But_ if we have not yet recorded
344          * someone being logged in on Console - due to the wtmp
345          * file starting after they logged in, we'll pretend they
346          * logged in, at the start of the wtmp file.
347          */
348
349 #ifdef CONSOLE_TTY
350         if (up->ut_host[0] == ':') {
351                 /*
352                  * SunOS 4.0.2 does not treat ":0.0" as special but we
353                  * do.
354                  */
355                 if (on_console())
356                         return;
357                 /*
358                  * ok, no recorded login, so they were here when wtmp
359                  * started!  Adjust ut_time!
360                  */
361                 up->ut_tv = FirstTime;
362                 /*
363                  * this allows us to pick the right logout
364                  */
365                 strlcpy(up->ut_line, Console, sizeof(up->ut_line));
366         }
367 #endif
368         /*
369          * If we are doing specified ttys only, we ignore
370          * anything else.
371          */
372         if (Flags & AC_T && !do_tty(up->ut_line))
373                 return;
374
375         /*
376          * go ahead and log them in
377          */
378         if ((lp = malloc(sizeof(*lp))) == NULL)
379                 errx(1, "malloc failed");
380         SLIST_INSERT_HEAD(&CurUtmpx, lp, next);
381         strlcpy(lp->user, up->ut_user, sizeof(lp->user));
382         memcpy(lp->id, up->ut_id, sizeof(lp->id));
383 #ifdef CONSOLE_TTY
384         memcpy(lp->line, up->ut_line, sizeof(lp->line));
385 #endif
386         lp->time = up->ut_tv;
387 }
388
389 static void
390 ac(const char *file)
391 {
392         struct utmpx_entry *lp;
393         struct utmpx *usr, usht;
394         struct tm *ltm;
395         struct timeval prev_secs, ut_timecopy, secs, clock_shift, now;
396         int day, rfound;
397
398         day = -1;
399         timerclear(&prev_secs); /* Minimum acceptable date == 1970. */
400         timerclear(&secs);
401         timerclear(&clock_shift);
402         rfound = 0;
403         if (setutxdb(UTXDB_LOG, file) != 0)
404                 err(1, "%s", file);
405         while ((usr = getutxent()) != NULL) {
406                 rfound++;
407                 ut_timecopy = usr->ut_tv;
408                 /* Don't let the time run backwards. */
409                 if (timercmp(&ut_timecopy, &prev_secs, <))
410                         ut_timecopy = prev_secs;
411                 prev_secs = ut_timecopy;
412
413                 if (!timerisset(&FirstTime))
414                         FirstTime = ut_timecopy;
415                 if (Flags & AC_D) {
416                         ltm = localtime(&ut_timecopy.tv_sec);
417                         if (day >= 0 && day != ltm->tm_yday) {
418                                 day = ltm->tm_yday;
419                                 /*
420                                  * print yesterday's total
421                                  */
422                                 secs = ut_timecopy;
423                                 secs.tv_sec -= ltm->tm_sec;
424                                 secs.tv_sec -= 60 * ltm->tm_min;
425                                 secs.tv_sec -= 3600 * ltm->tm_hour;
426                                 secs.tv_usec = 0;
427                                 show_today(secs);
428                         } else
429                                 day = ltm->tm_yday;
430                 }
431                 switch(usr->ut_type) {
432                 case OLD_TIME:
433                         clock_shift = ut_timecopy;
434                         break;
435                 case NEW_TIME:
436                         timersub(&clock_shift, &ut_timecopy, &clock_shift);
437                         /*
438                          * adjust time for those logged in
439                          */
440                         SLIST_FOREACH(lp, &CurUtmpx, next)
441                                 timersub(&lp->time, &clock_shift, &lp->time);
442                         break;
443                 case BOOT_TIME:
444                 case SHUTDOWN_TIME:
445                         log_out(usr);
446                         FirstTime = ut_timecopy; /* shouldn't be needed */
447                         break;
448                 case USER_PROCESS:
449                         /*
450                          * If they came in on pts/..., then it is only
451                          * a login session if the ut_host field is non-empty.
452                          */
453                         if (strncmp(usr->ut_line, "pts/", 4) != 0 ||
454                             *usr->ut_host != '\0')
455                                 log_in(usr);
456                         break;
457                 case DEAD_PROCESS:
458                         log_out(usr);
459                         break;
460                 }
461         }
462         endutxent();
463         (void)gettimeofday(&now, NULL);
464         if (Flags & AC_W)
465                 usht.ut_tv = ut_timecopy;
466         else
467                 usht.ut_tv = now;
468         usht.ut_type = SHUTDOWN_TIME;
469
470         if (Flags & AC_D) {
471                 ltm = localtime(&ut_timecopy.tv_sec);
472                 if (day >= 0 && day != ltm->tm_yday) {
473                         /*
474                          * print yesterday's total
475                          */
476                         secs = ut_timecopy;
477                         secs.tv_sec -= ltm->tm_sec;
478                         secs.tv_sec -= 60 * ltm->tm_min;
479                         secs.tv_sec -= 3600 * ltm->tm_hour;
480                         secs.tv_usec = 0;
481                         show_today(secs);
482                 }
483         }
484         /*
485          * anyone still logged in gets time up to now
486          */
487         log_out(&usht);
488
489         if (Flags & AC_D)
490                 show_today(now);
491         else {
492                 if (Flags & AC_P)
493                         show_users();
494                 show("total", Total);
495         }
496 }
497
498 static void
499 usage(void)
500 {
501         (void)fprintf(stderr,
502 #ifdef CONSOLE_TTY
503             "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n");
504 #else
505             "ac [-dp] [-t tty] [-w wtmp] [users ...]\n");
506 #endif
507         exit(1);
508 }