From 95768aa45183ea21e7b11108b4209fb915760281 Mon Sep 17 00:00:00 2001 From: cgd Date: Tue, 29 Mar 1994 12:40:06 +0000 Subject: [PATCH] ac, by Simon J. Gerraty and myself --- usr.sbin/ac/Makefile | 18 ++ usr.sbin/ac/ac.8 | 165 +++++++++++++ usr.sbin/ac/ac.c | 553 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 736 insertions(+) create mode 100644 usr.sbin/ac/Makefile create mode 100644 usr.sbin/ac/ac.8 create mode 100644 usr.sbin/ac/ac.c diff --git a/usr.sbin/ac/Makefile b/usr.sbin/ac/Makefile new file mode 100644 index 000000000000..9a8c43b9d3d6 --- /dev/null +++ b/usr.sbin/ac/Makefile @@ -0,0 +1,18 @@ +# $Id: Makefile,v 1.1 1994/03/29 12:40:06 cgd Exp $ + +PROG= ac +MAN8= ac.0 + +# If "CONSOLE_TTY" is not defined, this program is compatible with the +# traditional implementation (using SunOS 4.x as the sample traditional +# implementation). This is the default. +# +# If "CONSOLE_TTY" is defined, it must be defined to the appropriate +# console name, e.g. "vga". Additionally, the various commented-out +# sections of the man page should be uncommented. This is not the +# default because of the inability to detect the proper console name +# easily, especially on m68k systems, which can share binaries. +# +#CFLAGS+=-DCONSOLE_TTY=\"vga\" + +.include diff --git a/usr.sbin/ac/ac.8 b/usr.sbin/ac/ac.8 new file mode 100644 index 000000000000..ee895df627ab --- /dev/null +++ b/usr.sbin/ac/ac.8 @@ -0,0 +1,165 @@ +.\" +.\" Copyright (c) 1994 Simon J. Gerraty +.\" Copyright (c) 1994 Christopher G. Demetriou +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Christopher G. Demetriou. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $Id: ac.8,v 1.1 1994/03/29 12:40:08 cgd Exp $ +.\" +.Dd March 15, 1994 +.Dt AC 8 +.Os NetBSD 0.9a +.Sh NAME +.Nm ac +.Nd connect time accounting +.Sh SYNOPSIS +.Nm ac +.Op Fl dp +.\".Op Fl c Ar console +.Op Fl t Ar tty +.Op Fl w Ar wtmp +.Op Ar users ... +.Sh DESCRIPTION +If the file +.Pa /var/log/wtmp +exists, a record of individual login and logout +times are written to it by +.Xr login 8 +and +.Xr init 8 , +respectively. +.Nm \&Ac +examines these records and writes the accumulated connect time +for all logins to the standard output. +.Pp +The options are as follows: +.Bl -tag -width indentXXX +.It Fl d +Display the connect times in 24 hour chunks. +.\" .It Fl c Ar console +.\" Use +.\" .Ar console +.\" as the name of the device that local X sessions (ut_host of ":0.0") +.\" originate from. If any login has been recorded on +.\" .Ar console +.\" then these X sessions are ignored unless COMPAT_SUNOS was defined at +.\" compile time. +.It Fl p +Print individual users' totals. +.It Fl t Ar tty +Only do accounting logins on certain ttys. The +.Ar tty +specification can start with '!' to indicate not this +.Ar tty +and end with '*' to indicate all similarly named ttys. +Multiple +.Fl t +flags may be specified. +.It Fl w Ar wtmp +Read connect time data from +.Ar wtmp +instead of the default file, +.Pa /var/log/wtmp . +.It Ar users ... +Display totals for the given individuals only. +.El +.Pp +If no arguments are given, +.Nm ac +displays the total connect time for all +accounts with login sessions recorded in +.Pa wtmp . +.Pp +The default +.Pa wtmp +file will increase without bound unless it is truncated. +It is normally truncated by the daily scripts run +by +.Xr cron 8 , +which rename and rotate the +.Pa wtmp +files, keeping a week's worth of data on +hand. No login or connect time accounting is performed if +.Pa /var/log/wtmp +does not exist. +.Pp +For example, +.Bd -literal -offset +ac -p -t "ttyd*" > modems +ac -p -t "!ttyd*" > other +.Ed +.Pp +allows times recorded in +.Pa modems +to be charged out at a different rate than +.Pa other . +.Pp +The +.Nm ac +utility exits 0 on success, and >0 if a fatal error occurs. +.Sh FILES +.Bl -tag -width /var/log/wtmp.[0-7] -compact +.It Pa /var/log/wtmp +connect time accounting file +.It Pa /var/log/wtmp.[0-7] +rotated files +.El +.Sh SEE ALSO +.Xr init 8 , +.Xr sa 8 , +.Xr login 1 , +.Xr utmp +.Sh HISTORY +An +.Nm ac +command appeard in +.At v6 . +This version of +.Nm ac +was written for +.Nx 0.9a +from the specification provided by various systems' manual pages. +.\" .Sh NOTES +.\" If COMPAT_SUNOS is defined +.\" .Nm ac +.\" ignores the fact that entries with ut_host of ":0.0" are not real +.\" login sessions. Normally such entries are ignored except in the case +.\" of a user being logged in when the +.\" .Pa wtmp +.\" file was rotated, in which case a login with ut_host of ":0.0" may +.\" appear without any preceeding console logins. +.\" If no one is logged in on the console, the user is deemed to have +.\" logged in on at the earliest time stamp found in +.\" .Pa wtmp . +.\" Use of +.\" .Pa console +.\" allows +.\" .Nm ac +.\" to identify and correcty process a logout for the user. The default +.\" value for +.\" .Pa console +.\" is usually correct at compile time. diff --git a/usr.sbin/ac/ac.c b/usr.sbin/ac/ac.c new file mode 100644 index 000000000000..a6e52a9d0da8 --- /dev/null +++ b/usr.sbin/ac/ac.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 1994 Christopher G. Demetriou. + * @(#)Copyright (c) 1994, Simon J. Gerraty. + * + * This is free software. It comes with NO WARRANTY. + * Permission to use, modify and distribute this source code + * is granted subject to the following conditions. + * 1/ that the above copyright notice and this notice + * are preserved in all copies and that due credit be given + * to the author. + * 2/ that any changes to this code are clearly commented + * as such so that the author does not get blamed for bugs + * other than his own. + */ + +#ifndef lint +static char rcsid[] = "$Id: ac.c,v 1.1 1994/03/29 12:40:09 cgd Exp $"; +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * this is for our list of currently logged in sessions + */ +struct utmp_list { + struct utmp_list *next; + struct utmp usr; +}; + +/* + * this is for our list of users that are accumulating time. + */ +struct user_list { + struct user_list *next; + char name[UT_NAMESIZE]; + time_t secs; +}; + +/* + * this is for chosing whether to ignore a login + */ +struct tty_list { + struct tty_list *next; + char name[UT_LINESIZE]; + int len; + int ret; +}; + +/* + * globals - yes yuk + */ +#ifdef CONSOLE_TTY +static char *Console = CONSOLE_TTY; +#endif +static time_t Total = 0; +static time_t FirstTime = 0; +static int Flags = 0; +static struct user_list *Users = NULL; +static struct tty_list *Ttys = NULL; + +#define NEW(type) (type *)malloc(sizeof (type)) + +#define AC_W 1 /* not _PATH_WTMP */ +#define AC_D 2 /* daily totals (ignore -p) */ +#define AC_P 4 /* per-user totals */ +#define AC_U 8 /* specified users only */ +#define AC_T 16 /* specified ttys only */ + +#ifdef DEBUG +static int Debug = 0; +#endif + +int main __P((int, char **)); +int ac __P((FILE *)); +struct tty_list *add_tty __P((char *)); +int do_tty __P((char *)); +FILE *file __P((char *)); +struct utmp_list *log_in __P((struct utmp_list *, struct utmp *)); +struct utmp_list *log_out __P((struct utmp_list *, struct utmp *)); +int on_console __P((struct utmp_list *)); +void show __P((char *, time_t)); +void show_today __P((struct user_list *, struct utmp_list *, + time_t)); +void show_users __P((struct user_list *)); +struct user_list *update_user __P((struct user_list *, char *, time_t)); +void usage __P((void)); + +/* + * open wtmp or die + */ +FILE * +file(name) + char *name; +{ + FILE *fp; + + if ((fp = fopen(name, "r")) == NULL) + err(1, "%s", name); + /* in case we want to discriminate */ + if (strcmp(_PATH_WTMP, name)) + Flags |= AC_W; + return fp; +} + +struct tty_list * +add_tty(name) + char *name; +{ + struct tty_list *tp; + register char *rcp; + + Flags |= AC_T; + + if ((tp = NEW(struct tty_list)) == NULL) + err(1, "malloc"); + tp->len = 0; /* full match */ + tp->ret = 1; /* do if match */ + if (*name == '!') { /* don't do if match */ + tp->ret = 0; + name++; + } + (void)strncpy(tp->name, name, sizeof (tp->name)); + tp->name[sizeof (tp->name) - 1] = '\0'; + if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ + *rcp = '\0'; + tp->len = strlen(tp->name); /* match len bytes only */ + } + tp->next = Ttys; + Ttys = tp; + return Ttys; +} + +/* + * should we process the named tty? + */ +int +do_tty(name) + char *name; +{ + struct tty_list *tp; + int def_ret = 0; + + for (tp = Ttys; tp != NULL; tp = tp->next) { + if (tp->ret == 0) /* specific don't */ + def_ret = 1; /* default do */ + if (tp->len != 0) { + if (strncmp(name, tp->name, tp->len) == 0) + return tp->ret; + } else { + if (strcmp(name, tp->name) == 0) + return tp->ret; + } + } + return def_ret; +} + +#ifdef CONSOLE_TTY +/* + * is someone logged in on Console? + */ +int +on_console(head) + struct utmp_list *head; +{ + struct utmp_list *up; + + for (up = head; up; up = up->next) { + if (strcmp(up->usr.ut_line, Console) == 0) + return 1; + } + return 0; +} +#endif + +/* + * update user's login time + */ +struct user_list * +update_user(head, name, secs) + struct user_list *head; + char *name; + time_t secs; +{ + struct user_list *up; + + for (up = head; up != NULL; up = up->next) { + if (strcmp(up->name, name) == 0) { + up->secs += secs; + Total += secs; + return head; + } + } + /* + * not found so add new user unless specified users only + */ + if (Flags & AC_U) + return head; + + if ((up = NEW(struct user_list)) == NULL) + err(1, "malloc"); + up->next = head; + (void)strncpy(up->name, name, sizeof (up->name) - 1); + up->name[sizeof (up->name) - 1] = '\0'; /* paranoid! */ + up->secs = secs; + Total += secs; + return up; +} + +int +main(argc, argv) + int argc; + char **argv; +{ + FILE *fp; + int c; + + fp = NULL; + while ((c = getopt(argc, argv, "Dc:dpt:w:")) != EOF) { + switch (c) { +#ifdef DEBUG + case 'D': + Debug++; + break; +#endif + case 'c': +#ifdef CONSOLE_TTY + Console = optarg; +#else + usage(); /* XXX */ +#endif + break; + case 'd': + Flags |= AC_D; + break; + case 'p': + Flags |= AC_P; + break; + case 't': /* only do specified ttys */ + add_tty(optarg); + break; + case 'w': + fp = file(optarg); + break; + case '?': + default: + usage(); + break; + } + } + if (optind < argc) { + /* + * initialize user list + */ + for (; optind < argc; optind++) { + Users = update_user(Users, argv[optind], 0L); + } + Flags |= AC_U; /* freeze user list */ + } + if (Flags & AC_D) + Flags &= ~AC_P; + if (fp == NULL) { + /* + * if _PATH_WTMP does not exist, exit quietly + */ + if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT) + return 0; + + fp = file(_PATH_WTMP); + } + ac(fp); + + return 0; +} + +/* + * print login time in decimal hours + */ +void +show(name, secs) + char *name; + time_t secs; +{ + (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, + ((double)secs / 3600)); +} + +void +show_users(list) + struct user_list *list; +{ + struct user_list *lp; + + for (lp = list; lp; lp = lp->next) + show(lp->name, lp->secs); +} + +/* + * print total login time for 24hr period in decimal hours + */ +void +show_today(users, logins, secs) + struct user_list *users; + struct utmp_list *logins; + time_t secs; +{ + struct user_list *up; + struct utmp_list *lp; + char date[64]; + time_t yesterday = secs - 1; + + (void)strftime(date, sizeof (date), "%b %e total", + localtime(&yesterday)); + + /* restore the missing second */ + yesterday++; + + for (lp = logins; lp != NULL; lp = lp->next) { + secs = yesterday - lp->usr.ut_time; + Users = update_user(Users, lp->usr.ut_name, secs); + lp->usr.ut_time = yesterday; /* as if they just logged in */ + } + secs = 0; + for (up = users; up != NULL; up = up->next) { + secs += up->secs; + up->secs = 0; /* for next day */ + } + if (secs) + (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); +} + +/* + * log a user out and update their times. + * if ut_line is "~", we log all users out as the system has + * been shut down. + */ +struct utmp_list * +log_out(head, up) + struct utmp_list *head; + struct utmp *up; +{ + struct utmp_list *lp, *lp2, *tlp; + time_t secs; + + for (lp = head, lp2 = NULL; lp != NULL; ) + if (*up->ut_line == '~' || + strcmp(lp->usr.ut_line, up->ut_line) == 0) { + secs = up->ut_time - lp->usr.ut_time; + Users = update_user(Users, lp->usr.ut_name, secs); +#ifdef DEBUG + if (Debug) + printf( + "%-.*s %s: %s logged out (%2d:%02d:%02d)\n", + 19, ctime(&up->ut_time), lp->usr.ut_line, + lp->usr.ut_name, secs / 3600, + (secs % 3600) / 60, secs % 60); +#endif + /* + * now lose it + */ + tlp = lp; + lp = lp->next; + if (tlp == head) + head = lp; + else if (lp2 != NULL) + lp2->next = lp; + free(tlp); + } else { + lp2 = lp; + lp = lp->next; + } + return head; +} + + +/* + * if do_tty says ok, login a user + */ +struct utmp_list * +log_in(head, up) + struct utmp_list *head; + struct utmp *up; +{ + struct utmp_list *lp; + + /* + * this could be a login. if we're not dealing with + * the console name, say it is. + * + * If we are, and if ut_host==":0.0" we know that it + * isn't a real login. _But_ if we have not yet recorded + * someone being logged in on Console - due to the wtmp + * file starting after they logged in, we'll pretend they + * logged in, at the start of the wtmp file. + */ + +#ifdef CONSOLE_TTY + if (up->ut_host[0] == ':') { + /* + * SunOS 4.0.2 does not treat ":0.0" as special but we + * do. + */ + if (on_console(head)) + return head; + /* + * ok, no recorded login, so they were here when wtmp + * started! Adjust ut_time! + */ + up->ut_time = FirstTime; + /* + * this allows us to pick the right logout + */ + (void)strcpy(up->ut_line, Console); + } +#endif + /* + * If we are doing specified ttys only, we ignore + * anything else. + */ + if (Flags & AC_T) + if (!do_tty(up->ut_line)) + return head; + + /* + * go ahead and log them in + */ + if ((lp = NEW(struct utmp_list)) == NULL) + err(1, "malloc"); + lp->next = head; + head = lp; + memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp)); +#ifdef DEBUG + if (Debug) { + printf("%-.*s %s: %s logged in", 19, ctime(&lp->usr.ut_time), + up->ut_line, up->ut_name); + if (*up->ut_host) + printf(" (%s)", up->ut_host); + putchar('\n'); + } +#endif + return head; +} + +int +ac(fp) + FILE *fp; +{ + struct utmp_list *lp, *head = NULL; + struct utmp usr; + struct tm *ltm; + time_t secs; + int day = -1; + + while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) { + if (!FirstTime) + FirstTime = usr.ut_time; + if (Flags & AC_D) { + ltm = localtime(&usr.ut_time); + if (day >= 0 && day != ltm->tm_yday) { + day = ltm->tm_yday; + /* + * print yesterday's total + */ + secs = usr.ut_time; + secs -= ltm->tm_sec; + secs -= 60 * ltm->tm_min; + secs -= 3600 * ltm->tm_hour; + show_today(Users, head, secs); + } else + day = ltm->tm_yday; + } + switch(*usr.ut_line) { + case '|': + secs = usr.ut_time; + break; + case '{': + secs -= usr.ut_time; + /* + * adjust time for those logged in + */ + for (lp = head; lp != NULL; lp = lp->next) + lp->usr.ut_time -= secs; + break; + case '~': /* reboot or shutdown */ + head = log_out(head, &usr); + break; + default: + /* + * if they came in on tty[p-y]*, then it is only + * a login session if the ut_host field is non-empty + */ + if (*usr.ut_name) { + if (strncmp(usr.ut_line, "tty", 3) != 0 || + strchr("pqrstuvwxy", usr.ut_line[3]) == 0 || + *usr.ut_host != '\0') + head = log_in(head, &usr); + } else + head = log_out(head, &usr); + break; + } + } + (void)fclose(fp); + usr.ut_time = time((time_t *)0); + (void)strcpy(usr.ut_line, "~"); + + if (Flags & AC_D) { + ltm = localtime(&usr.ut_time); + if (day >= 0 && day != ltm->tm_yday) { + /* + * print yesterday's total + */ + secs = usr.ut_time; + secs -= ltm->tm_sec; + secs -= 60 * ltm->tm_min; + secs -= 3600 * ltm->tm_hour; + show_today(Users, head, secs); + } + } + /* + * anyone still logged in gets time up to now + */ + head = log_out(head, &usr); + + if (Flags & AC_D) + show_today(Users, head, time((time_t *)0)); + else { + if (Flags & AC_P) + show_users(Users); + show("total", Total); + } + return 0; +} + +void +usage() +{ + (void)fprintf(stderr, +#ifdef CONSOLE_TTY + "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); +#else + "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); +#endif + exit(1); +}