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