ac, by Simon J. Gerraty and myself

This commit is contained in:
cgd 1994-03-29 12:40:06 +00:00
parent abc6222e32
commit 95768aa451
3 changed files with 736 additions and 0 deletions

18
usr.sbin/ac/Makefile Normal file
View File

@ -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 <bsd.prog.mk>

165
usr.sbin/ac/ac.8 Normal file
View File

@ -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.

553
usr.sbin/ac/ac.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/file.h>
#include <sys/time.h>
#include <err.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utmp.h>
#include <unistd.h>
/*
* 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);
}