2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 1994 Christopher G. Demetriou
5 * Copyright (c) 1994 Simon J. Gerraty
6 * Copyright (c) 2012 Ed Schouten <ed@FreeBSD.org>
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
30 * $FreeBSD: head/usr.sbin/ac/ac.c 326276 2017-11-27 15:37:16Z pfg $
33 #include <sys/queue.h>
48 * this is for our list of currently logged in sessions
51 SLIST_ENTRY(utmpx_entry) next;
52 char user[sizeof(((struct utmpx *)0)->ut_user)];
53 char id[sizeof(((struct utmpx *)0)->ut_id)];
55 char line[sizeof(((struct utmpx *)0)->ut_line)];
61 * this is for our list of users that are accumulating time.
64 SLIST_ENTRY(user_entry) next;
65 char user[sizeof(((struct utmpx *)0)->ut_user)];
70 * this is for chosing whether to ignore a login
73 SLIST_ENTRY(tty_entry) next;
74 char line[sizeof(((struct utmpx *)0)->ut_line) + 2];
83 static const char *Console = CONSOLE_TTY;
85 static struct timeval Total = { 0, 0 };
86 static struct timeval FirstTime = { 0, 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);
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 */
98 static void ac(const char *);
99 static void usage(void) __dead2;
102 add_tty(const char *line)
104 struct tty_entry *tp;
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 */
117 strlcpy(tp->line, line, sizeof(tp->line));
119 if ((rcp = strchr(tp->line, '*')) != NULL) {
121 /* Match len bytes only. */
122 tp->len = strlen(tp->line);
124 SLIST_INSERT_HEAD(&Ttys, tp, next);
128 * should we process the named tty?
131 do_tty(const char *line)
133 struct tty_entry *tp;
136 SLIST_FOREACH(tp, &Ttys, next) {
137 if (tp->ret == 0) /* specific don't */
138 def_ret = 1; /* default do */
140 if (strncmp(line, tp->line, tp->len) == 0)
143 if (strncmp(line, tp->line, sizeof(tp->line)) == 0)
152 * is someone logged in on Console?
157 struct utmpx_entry *up;
159 SLIST_FOREACH(up, &CurUtmpx, next)
160 if (strcmp(up->line, Console) == 0)
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.
172 update_user(const char *user, struct timeval secs)
174 struct user_entry *up, *aup;
178 SLIST_FOREACH(up, &Users, next) {
179 c = strcmp(up->user, user);
181 timeradd(&up->time, &secs, &up->time);
182 timeradd(&Total, &secs, &Total);
189 * not found so add new user unless specified users only
194 if ((up = malloc(sizeof(*up))) == NULL)
195 errx(1, "malloc failed");
197 SLIST_INSERT_HEAD(&Users, up, next);
199 SLIST_INSERT_AFTER(aup, up, next);
200 strlcpy(up->user, user, sizeof(up->user));
202 timeradd(&Total, &secs, &Total);
206 main(int argc, char *argv[])
208 const char *wtmpf = NULL;
211 setlocale(LC_TIME, "");
213 while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) {
228 case 't': /* only do specified ttys */
243 * initialize user list
245 for (; optind < argc; optind++) {
246 update_user(argv[optind], (struct timeval){ 0, 0 });
248 Flags |= AC_U; /* freeze user list */
258 * print login time in decimal hours
261 show(const char *user, struct timeval secs)
263 printf("\t%-*s %8.2f\n",
264 (int)sizeof(((struct user_entry *)0)->user), user,
265 (double)secs.tv_sec / 3600);
271 struct user_entry *lp;
273 SLIST_FOREACH(lp, &Users, next)
274 show(lp->user, lp->time);
278 * print total login time for 24hr period in decimal hours
281 show_today(struct timeval today)
283 struct user_entry *up;
284 struct utmpx_entry *lp;
286 struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday;
287 static int d_first = -1;
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));
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. */
302 SLIST_FOREACH(up, &Users, next) {
303 timeradd(&total, &up->time, &total);
305 timerclear(&up->time);
307 if (timerisset(&total))
308 printf("%s %11.2f\n", date, (double)total.tv_sec / 3600);
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.
317 log_out(const struct utmpx *up)
319 struct utmpx_entry *lp, *lp2, *tlp;
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);
332 lp = SLIST_NEXT(lp, next);
334 SLIST_REMOVE_HEAD(&CurUtmpx, next);
336 SLIST_REMOVE_AFTER(lp2, next);
340 lp = SLIST_NEXT(lp, next);
345 * if do_tty says ok, login a user
348 log_in(struct utmpx *up)
350 struct utmpx_entry *lp;
353 * this could be a login. if we're not dealing with
354 * the console name, say it is.
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.
364 if (up->ut_host[0] == ':') {
366 * SunOS 4.0.2 does not treat ":0.0" as special but we
372 * ok, no recorded login, so they were here when wtmpx
373 * started! Adjust ut_time!
375 up->ut_tv = FirstTime;
377 * this allows us to pick the right logout
379 strlcpy(up->ut_line, Console, sizeof(up->ut_line));
383 * If we are doing specified ttys only, we ignore
386 if (Flags & AC_T && !do_tty(up->ut_line))
390 * go ahead and log them in
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));
398 memcpy(lp->line, up->ut_line, sizeof(lp->line));
400 lp->time = up->ut_tv;
406 struct utmpx_entry *lp;
407 struct utmpx *usr, usht;
409 struct timeval prev_secs, ut_timecopy, secs, clock_shift, now;
413 timerclear(&prev_secs); /* Minimum acceptable date == 1970. */
415 timerclear(&clock_shift);
417 if (setutxdb(UTX_DB_WTMPX, file) != 0)
419 while ((usr = getutxent()) != NULL) {
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;
427 if (!timerisset(&FirstTime))
428 FirstTime = ut_timecopy;
430 ltm = localtime(&ut_timecopy.tv_sec);
431 if (day >= 0 && day != ltm->tm_yday) {
434 * print yesterday's total
437 secs.tv_sec -= ltm->tm_sec;
438 secs.tv_sec -= 60 * ltm->tm_min;
439 secs.tv_sec -= 3600 * ltm->tm_hour;
445 switch(usr->ut_type) {
447 clock_shift = ut_timecopy;
450 timersub(&clock_shift, &ut_timecopy, &clock_shift);
452 * adjust time for those logged in
454 SLIST_FOREACH(lp, &CurUtmpx, next)
455 timersub(&lp->time, &clock_shift, &lp->time);
460 FirstTime = ut_timecopy; /* shouldn't be needed */
464 * If they came in on pts/..., then it is only
465 * a login session if the ut_host field is non-empty.
467 if (strncmp(usr->ut_line, "pts/", 4) != 0 ||
468 *usr->ut_host != '\0')
477 gettimeofday(&now, NULL);
479 usht.ut_tv = ut_timecopy;
482 usht.ut_type = INIT_PROCESS;
485 ltm = localtime(&ut_timecopy.tv_sec);
486 if (day >= 0 && day != ltm->tm_yday) {
488 * print yesterday's total
491 secs.tv_sec -= ltm->tm_sec;
492 secs.tv_sec -= 60 * ltm->tm_min;
493 secs.tv_sec -= 3600 * ltm->tm_hour;
499 * anyone still logged in gets time up to now
508 show("total", Total);
517 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n");
519 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n");