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