Newsgroups: comp.sources.unix
From: dws@cs.wisc.edu (DaviD W. Sanderson)
Subject: v25i096: utmp - utility to repair broken /etc/utmp files
Sender: sources-moderator@pa.dec.com
Approved: vixie@pa.dec.com

Submitted-By: dws@cs.wisc.edu (DaviD W. Sanderson)
Posting-Number: Volume 25, Issue 96
Archive-Name: utmp

I wrote this program to help me repair /etc/utmp files left for
whatever reason with invalid entries.  It works for the utmp file
formats on System V, BSD, AIX3, and AIX 2 (though recently I haven't
had access to an AIX 2 system to make sure it still works there).  I
designed the code to make it easy to add or remove fields for a new
utmp format.

	dws@cs.wisc.edu (DaviD W. Sanderson)

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README utmp.1 utmp.c Makefile
# Wrapped by dws@skinner on Sat Dec 21 17:06:59 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(387 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
XI wrote this program to help me repair /etc/utmp files left for
Xwhatever reason with invalid entries.  It works for the utmp file
Xformats on System V, BSD, AIX3, and AIX 2 (though recently I haven't
Xhad access to an AIX 2 system to make sure it still works there).  I
Xdesigned the code to make it easy to add or remove fields for a new
Xutmp format.
X
XDaviD W. Sanderson (dws@cs.wisc.edu)
END_OF_FILE
if test 387 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'utmp.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'utmp.1'\"
else
echo shar: Extracting \"'utmp.1'\" \(1727 characters\)
sed "s/^X//" >'utmp.1' <<'END_OF_FILE'
X.\"-------
X.\" $RCSfile: utmp.1,v $$Revision: 1.2 $$Date: 1991/12/21 23:03:43 $
X.\"
X.\" $Log: utmp.1,v $
X.\" Revision 1.2  1991/12/21  23:03:43  dws
X.\" Added caveats.
X.\"
X.\" Revision 1.1  1991/12/21  21:46:38  dws
X.\" Initial revision
X.\"
X.\"-------
X.TH UTMP 1
X.SH NAME
Xutmp \- translate between binary and textual utmp files
X.SH SYNOPSIS
X.B utmp
X.RB [ \-v ]
X.RI [ filename ]
X.SH DESCRIPTION
X.I utmp
Xis useful for fixing a corrupted /etc/utmp file.
XUse
X.I utmp
Xto obtain a printable representation of the file.
XEdit it, and then use
X.I utmp
Xagain to translate it back into binary form.
X.PP
X.I utmp
Xreads from the given file, or stdin if none is given (/etc/utmp
Xif stdin is a tty), and writes to stdout.
XIt detects automatically which transformation to apply.
X.PP
XThe flag
X.B \-v
Xindicates that times are to be printed in a
Xhuman-comprehensible form.
XNote that this output format is not
Xcurrently inverted, so it is not useful for making repairs.
X.PP
XIf
X.I utmp
Xis working correctly then
X.PP
X.in +0.5i
X$ utmp
X.in
X.PP
Xand
X.PP
X.in +0.5i
X$ utmp | utmp | utmp
X.in
X.PP
Xshould produce the same output.
XIdeally
X.PP
X.in +0.5i
X$ utmp | utmp | cmp \- /etc/utmp
X.in
X.PP
Xshould produce no output, but some systems don't fill the entire
Xunused part of the character fields with NULs.
X.SH CAVEATS
XI encourage system administrators to install this command
Xsomewhere besides /etc.
X.PP
XThere are probably /etc/utmp formats I do not yet accomodate.
X.SH FILES
X/etc/utmp
X.br
X/etc/wtmp
X.SH "SEE ALSO"
X.IR who (1),
X(SysV)
X.IR utmp (4),
X(BSD)
X.IR utmp (5)
X.SH AUTHOR
XDaviD W. Sanderson	(dws\|@cs.wisc.edu)
X.SH COPYRIGHT
X\&
X.br
X.if n (C)
X.if t \s+8\v'+2p'\fB\(co\fR\v'-2p'\s0
X\s+2Copyright 1991 by DaviD W. Sanderson\s0
X(but freely redistributable)
END_OF_FILE
if test 1727 -ne `wc -c <'utmp.1'`; then
    echo shar: \"'utmp.1'\" unpacked with wrong size!
fi
# end of 'utmp.1'
fi
if test -f 'utmp.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'utmp.c'\"
else
echo shar: Extracting \"'utmp.c'\" \(19486 characters\)
sed "s/^X//" >'utmp.c' <<'END_OF_FILE'
X/*
X * $RCSfile: utmp.c,v $$Revision: 1.2 $$Date: 1991/12/21 22:02:57 $
X *
X * utmp - translate between binary and textual utmp files.
X * 
X * This is useful for fixing a corrupted /etc/utmp.  Use utmp to obtain
X * a printable representation of the file.  Edit it, and then use
X * utmp again to translate it back into binary form.
X * 
X * usage: utmp [-v] [infile]
X * 
X * utmp reads from the given file, or stdin if none is given (/etc/utmp
X * if stdin is a tty), and writes to stdout.  It detects which
X * transformation it is to apply automatically.
X * 
X * The flag -v indicates that times are to be printed in a
X * human-comprehensible form.  Note that this output format is not
X * currently inverted, so it is not useful for making repairs.
X */
X/**
X * If utmp is working correctly then
X *
X *	$ utmp
X * and
X *	$ utmp | utmp | utmp
X *
X * should produce the same output.  Ideally
X *
X *	$ utmp | utmp | cmp - /etc/utmp
X *
X * should produce no output, but some systems don't fill the entire
X * unused part of the character fields with NULs.
X */
X/**
X * The device used to detect what sort of struct utmp we have is
X * to see whether USER_PROCESS (a value for ut_type) is defined by
X * the include files.  If it is, then struct utmp probably looks
X * like this:
X *
X *	struct utmp
X *	{
X *		char	ut_user[8];	User login name
X *		char	ut_id[4];	/etc/inittab id
X *		char	ut_line[12];	device name (console, lnxx)
X *		short	ut_pid;		process id
X *		short	ut_type;	type of entry
X *		struct	exit_status
X *		{
X *		short	e_termination;	Process termination status
X *		short	e_exit;		Process exit status
X *		}	ut_exit;	The exit status of a process
X *					marked as DEAD_PROCESS.
X *		time_t	ut_time;	time entry was made
X *	};
X *
X * AIX 3 systems have utmp entries like this:
X *
X *	struct utmp
X *	{
X *		char	ut_user[8];	User login name
X *		char	ut_id[14];	/etc/inittab id
X *		char	ut_line[12];	device name (console, lnxx)
X *		short	ut_type;	type of entry
X *		pid_t	ut_pid;		process id
X *		struct exit_status
X *		{
X *		short	e_termination;	Process termination status
X *		short	e_exit;		Process exit status
X *		}	ut_exit;	The exit status of a process
X *					marked as DEAD_PROCESS.
X *		time_t	ut_time;	time entry was made
X *		char	ut_host[16];	host name
X *	};
X *
X * To use abbreviations of the symbolic ut_type names, compile with
X * SHORT_TYPES defined.
X *
X * If USER_PROCESS is not defined, I assume an archaic struct utmp
X * like this:
X *
X *	struct utmp
X *	{
X *		char	ut_line[8];		tty name
X *		char	ut_name[8];		user id
X *		char	ut_host[16];		host name, if remote
X *		long	ut_time;		time on
X *	};
X *
X * $Log: utmp.c,v $
X * Revision 1.2  1991/12/21  22:02:57  dws
X * Improved the comments.
X *
X * Revision 1.1  1991/12/21  21:44:04  dws
X * Initial revision
X *
X */
X
X#include <sys/types.h>
X#include <assert.h>
X#include <ctype.h>
X#include <time.h>
X#include <stdio.h>
X#include <string.h>
X#include <utmp.h>
X
Xextern void exit();
X
X/*
X * Configuration Section
X * 
X * Configuration is done on a field-by-field basis, for the greatest
X * flexibility (who knows what those vendors will think up next?).
X * 
X * If the symbolic ut_type names are available, assume a SysV-like set
X * of fields.  Otherwise, assume BSD fields.
X * 
X * On some systems, utmp has no ut_id field, and ut_id is #defined to
X * ut_line.
X * 
X * AIX3 systems have the ut_host field.
X */
X
X#ifdef	USER_PROCESS
X# define	HAVE_ut_user
X# ifndef ut_id
X#  define	HAVE_ut_id
X# endif
X# define	HAVE_ut_line
X# define	HAVE_ut_pid
X# define	HAVE_ut_type
X# define	HAVE_ut_exit
X# define	HAVE_ut_time
X# ifdef	_AIX
X#  define	HAVE_ut_host
X# endif
X#else
X# define	HAVE_ut_line
X# define	HAVE_ut_name
X# define	HAVE_ut_host
X# define	HAVE_ut_time
X#endif
X
X#ifndef	UTMP_FILE
X#define	UTMP_FILE	"/etc/utmp"
X#endif
X
X/*
X * getopt() Section
X *
X * The usage() function uses write() instead of printf() so that
X * programs using this template will not necessarily have to use stdio.
X */
X
Xextern int write();
X
X/*
X * swrite() - write() a "string variable" (char *)
X */
X
X#define	swrite(fd, s)	(void) write((fd), (s), strlen(s))
X
X/*
X * lwrite() - write() a "string literal" (char [])
X */
X
X#define	lwrite(fd, lit)	(void) write((fd), (lit), sizeof(lit)-1)
X
X/*
X * basename(s) - assumes the string does not end in '/'
X *
X * This used to be
X *
X * #define basename(s)	(strrchr((s),'/') ? strrchr((s),'/')+1 : (s))
X *
X * but some systems don't have strrchr() and I didn't want to mess
X * with switching between strrchr() and rindex().
X */
X
Xstatic char *
Xbasename(s)
Xchar *s;
X{
X	char *p;
X
X	if (!s || !*s)
X		return s;
X	
X	for(p = s; *s; s++)
X		if(*s == '/')
X			p = s+1;
X	return p;
X}
X
Xstatic char cmdopt[] = "v";
Xstatic char cmdusg[] = " [-v] [filename]\n";
X
Xstatic int flag_v = 0;
X
Xstatic void
Xusage(cmd)
Xchar *cmd;
X{
X	static char prefix[] = "usage: ";
X	char *p = cmd ? basename(cmd) : "a.out";
X	int fd = 2;
X
X	lwrite(fd, prefix);
X	swrite(fd, p);
X	lwrite(fd, cmdusg);
X}
X
X/*
X * cmdline() - return the index of the first non-option argument
X */
Xstatic int
Xcmdline(argc, argv)
Xint argc;
Xchar **argv;
X{
X	extern void     exit();
X	extern char    *optarg; /* points to the option argument */
X	extern int      opterr;	/* when 0, getopt is silent on errors */
X	extern int      optind;	/* argv[optind] is the next argument */
X	extern int      optopt;	/* current option letter */
X
X	int errflag = 0;	/* usage error flag */
X	int errcode = 1;	/* usage error exit code */
X	int c;
X
X	while((c = getopt(argc, argv, cmdopt)) != -1)
X		switch(c)
X		{
X		case 'v':
X			flag_v = 1;
X			break;
X		case '?':
X			errflag = 1;
X			if(optopt == '?')
X				errcode = 0;
X			break;
X		}
X
X	if(errflag)
X	{
X		usage(argv[0]);
X		exit(errcode);
X	}
X
X	return optind;
X}
X
Xextern struct utmp *ud;	/* dummy declaration so I can use sizeof() */
X
X/*
X * LINSIZ is the number of bytes per line of the text representation
X * of a utmp file (including the newline).  The size of such a text
X * file had better be a multiple of LINSIZ.
X */
Xstatic int LINSIZ = 1		/* 1 for the newline */
X
X#ifdef	HAVE_ut_user
X# define nut_user	(sizeof(ud->ut_user)/sizeof(ud->ut_user[0]))
X+			1+nut_user
X#endif
X
X#ifdef	HAVE_ut_id
X# define nut_id		(sizeof(ud->ut_id  )/sizeof(ud->ut_id  [0]))
X+			1+nut_id
X#endif
X
X#ifdef	HAVE_ut_line
X# define nut_line	(sizeof(ud->ut_line)/sizeof(ud->ut_line[0]))
X+			1+nut_line
X#endif
X
X#ifdef	HAVE_ut_pid
X# define nut_pid	5
X+			1+nut_pid
X#endif
X
X#ifdef	HAVE_ut_type
X# if defined(SHORT_TYPES)
X#  define nut_type	4
X# else
X#  define nut_type	10
X# endif
X+			1+nut_type
X#endif
X
X#ifdef	HAVE_ut_exit
X# define nut_exit	17
X+			1+nut_exit
X#endif
X
X#ifdef	HAVE_ut_name
X# define nut_name	(sizeof(ud->ut_name)/sizeof(ud->ut_name[0]))
X+			1+nut_name
X#endif
X
X#ifdef	HAVE_ut_host
X# define nut_host	(sizeof(ud->ut_host)/sizeof(ud->ut_host[0]))
X+			1+nut_host
X#endif
X
X#ifdef	HAVE_ut_time
X# define nut_time	10
X+			nut_time
X#endif
X;			/* End of LINSIZ initialization */
X
X/*
X * VIS_SPC - visible printing character used to represent a space
X * 
X * VIS_TAB - visible printing character used to represent a tab
X * 
X * VIS_NUL - visible printing character used to represent a NUL
X * 
X * The way the i/o translation works, the representation of each field
X * must have no white space.  These visible characters are used to
X * represent the invisible characters.  (Note the inverse mapping
X * will be wrong if the utmp file actually contains any of these
X * characters.)
X * 
X * These characters are not just an artifact of using scanf() to read in
X * each field.  I used visible characters so that the extents and
X * boundaries of the array fields would be clear.
X * 
X * ENVIS() and DEVIS() perform the translations on the given character
X * array.
X */
X
X#define VIS_NUL	'_'
X#define VIS_TAB	'^'
X#define VIS_SPC	'~'
X#define CHR_NUL	0
X#define CHR_TAB	9
X#define CHR_SPC	32
X
X#define ENVIS(str)						\
X		{						\
X			int i;					\
X								\
X			for(i=0; i<sizeof(str)-1; i++)		\
X			switch((str)[i])			\
X			{					\
X			case CHR_NUL: (str)[i]=VIS_NUL; break;	\
X			case CHR_TAB: (str)[i]=VIS_TAB; break;	\
X			case CHR_SPC: (str)[i]=VIS_SPC; break;	\
X			}					\
X		}
X
X#define DEVIS(str)						\
X		{						\
X			int i;					\
X								\
X			for(i=0; i<sizeof(str)-1; i++)		\
X			switch((str)[i])			\
X			{					\
X			case VIS_NUL: (str)[i]=CHR_NUL; break;	\
X			case VIS_TAB: (str)[i]=CHR_TAB; break;	\
X			case VIS_SPC: (str)[i]=CHR_SPC; break;	\
X			}					\
X		}
X
X/*
X * These macros convert the given character args to an integer. (I
X * assume ints are at least 4 bytes long...)
X * 
X * They serve as hash functions, with the useful property that they can
X * be used in case labels when given constants as arguments.
X */
X
X#define char1int(a)		((unsigned)(a))
X#define char2int(a,b)		((char1int(a)    <<8) + (char1int(b)))
X#define char3int(a,b,c)		((char2int(a,b)  <<8) + (char1int(c)))
X#define char4int(a,b,c,d)	((char3int(a,b,c)<<8) + (char1int(d)))
X
X#ifdef	HAVE_ut_time
X/*
X * cvt_pb_pm() - convert Abbreviated Month name (cftime(3C) field
X * descriptor %b) to Month Number (01 - 12) (cftime(3C) field
X * descriptor %m).
X */
X
Xchar           *
Xcvt_pb_pm(pb)
X	char           *pb;
X{
X	char           *pm = "??";
X
X	switch(char3int(pb[0],pb[1],pb[2]))
X	{
X	case char3int('J','a','n'): pm = "01"; break;
X	case char3int('F','e','b'): pm = "02"; break;
X	case char3int('M','a','r'): pm = "03"; break;
X	case char3int('A','p','r'): pm = "04"; break;
X	case char3int('M','a','y'): pm = "05"; break;
X	case char3int('J','u','n'): pm = "06"; break;
X	case char3int('J','u','l'): pm = "07"; break;
X	case char3int('A','u','g'): pm = "08"; break;
X	case char3int('S','e','p'): pm = "09"; break;
X	case char3int('O','c','t'): pm = "10"; break;
X	case char3int('N','o','v'): pm = "11"; break;
X	case char3int('D','e','c'): pm = "12"; break;
X	}
X	return (pm);
X}
X
X/*
X * utgets_time() - set the ut_time field from the given file
X */
Xutgets_time(f, ut)
X	FILE           *f;
X	struct utmp    *ut;	/* utmp structure to print */
X{
X	(void)fscanf(f,"%ld",		&ut->ut_time);
X}
X
X/*
X * utputs_time() - print the ut_time field to the given file
X *
X * Note that if flag_v is set the output will not be invertible by
X * utgets().
X */
Xutputs_time(f, ut)
X	FILE           *f;
X	struct utmp    *ut;	/* utmp structure to print */
X{
X	char  time[BUFSIZ];
X	int   ntime;
X	char *ptime;
X
X	if(!flag_v)
X	{
X		(void)fprintf(f,"%*ld",	nut_time,	ut->ut_time);
X		return;
X	}
X
X	/* make sure time[] starts out null */
X	for(ptime = time; ptime < time + sizeof time; ptime++)
X		*ptime = '\0';
X#if 0
X	(void)memset(time,0,sizeof(time)/sizeof(time[0]));
X#endif
X
X	/*
X	 * Format the time field in a compact, sortable form
X	 *
X         * ctime returns a pointer to a 26-character string in the
X	 * following form:
X	 *
X	 *	Sun Sep 16 01:03:52 1973\n\0
X	 *	012345678901234567890123 4 5
X	 *
X	 * I want the time displayed as
X	 *
X	 *	yy/mm/dd hh:mm:ss
X	 *      01234567890123456
X	 *
X	 * Since cftime is not available everywhere, I will construct
X	 * the time myself using the result of ctime().
X	 */
X#if 0
X	/* This is how simple it is with cftime() */
X	ntime =	cftime(time, "%y/%m/%d %T", &(ut->ut_time));
X#endif
X
X	ntime = 0;
X	ptime = ctime(&(ut->ut_time));
X
X	strncat(time, ptime + 22, 2); ntime += 2;		/* yy */
X	time[ntime++] = '/';
X	strncat(time, cvt_pb_pm(ptime + 4), 2); ntime += 2;	/* mm */
X	time[ntime++] = '/';
X
X	strncat(time, ptime +  8, 2); ntime += 2;		/* dd */
X	if(time[ntime-2] == ' ')		/* add leading '0' */
X		time[ntime-2] = '0';
X
X	time[ntime++] = ' ';
X	strncat(time, ptime + 11, 8); ntime += 8;	  /* hh:mm:ss */
X
X	time[ntime] = '\0';		/* just to be paranoid */
X
X	assert(ntime < (sizeof(time)/sizeof(time[0])) - 1);
X
X	(void)fprintf(f, "%*s",	ntime,		time);
X}
X#endif
X
X/*
X * utgets() - translate a text line from the given file into a struct
X * utmp
X */
Xstruct utmp    *
Xutgets(f)
X	FILE           *f;
X{
X	static struct utmp utb;
X	static struct utmp *ut = &utb;
X
X	if (!f || feof(f))
X		return (struct utmp *) 0;
X
X	/*
X	 * Read in the fields
X	 */
X#ifdef	HAVE_ut_user
X	{
X		char            user[nut_user + 1];
X
X		(void) fscanf(f, "%s", user);
X		user[nut_user] = '\0';
X		DEVIS(user);
X		(void) strncpy(ut->ut_user, user, nut_user);
X	}
X#endif
X#ifdef	HAVE_ut_id
X	{
X		char            id[nut_id + 1];
X
X		(void) fscanf(f, "%s", id);
X		id[nut_id] = '\0';
X		DEVIS(id);
X		(void) strncpy(ut->ut_id, id, nut_id);
X	}
X#endif
X#ifdef	HAVE_ut_line
X	{
X		char            line[nut_line + 1];
X
X		(void) fscanf(f, "%s", line);
X		line[nut_line] = '\0';
X		DEVIS(line);
X		(void) strncpy(ut->ut_line, line, nut_line);
X	}
X#endif
X#ifdef	HAVE_ut_pid
X	{
X		long	pid;
X	/* (void)fscanf(f,"%hd",		&ut->ut_pid); */
X
X		(void)fscanf(f,"%ld",		&pid);
X		ut->ut_pid = pid;
X	}
X#endif
X#ifdef	HAVE_ut_type
X	{
X	char type[20];
X	short btype = 999;
X
X	(void)fscanf(f,"%s",	type);
X	/* decode the type */
X# if defined(SHORT_TYPES)
X	switch(char4int(type[0],type[1],type[2],type[3]))
X	{
X	case char4int('N','U','L','L'): btype = EMPTY;		break;
X	case char4int('R','L','V','L'): btype = RUN_LVL;	break;
X	case char4int('B','O','O','T'): btype = BOOT_TIME;	break;
X	case char4int('O','L','D','T'): btype = OLD_TIME;	break;
X	case char4int('N','E','W','T'): btype = NEW_TIME;	break;
X	case char4int('I','N','I','P'): btype = INIT_PROCESS;	break;
X	case char4int('L','O','G','P'): btype = LOGIN_PROCESS;	break;
X	case char4int('U','S','R','P'): btype = USER_PROCESS;	break;
X	case char4int('D','E','D','P'): btype = DEAD_PROCESS;	break;
X	case char4int('A','C','C','T'): btype = ACCOUNTING;	break;
X	default:
X		fprintf(stderr, "bad type: %s\n", type);
X	}
X# else
X	switch(char4int(type[0],type[1],type[2],type[3]))
X	{
X	case char4int('E','M','P','T'): btype = EMPTY;		break;
X	case char4int('R','U','N','_'): btype = RUN_LVL;	break;
X	case char4int('B','O','O','T'): btype = BOOT_TIME;	break;
X	case char4int('O','L','D','_'): btype = OLD_TIME;	break;
X	case char4int('N','E','W','_'): btype = NEW_TIME;	break;
X	case char4int('I','N','I','T'): btype = INIT_PROCESS;	break;
X	case char4int('L','O','G','I'): btype = LOGIN_PROCESS;	break;
X	case char4int('U','S','E','R'): btype = USER_PROCESS;	break;
X	case char4int('D','E','A','D'): btype = DEAD_PROCESS;	break;
X	case char4int('A','C','C','O'): btype = ACCOUNTING;	break;
X	default:
X		fprintf(stderr, "bad type: %s\n", type);
X	}
X# endif
X	ut->ut_type = btype;
X	}
X#endif
X#ifdef	HAVE_ut_exit
X	(void)fscanf(f," term=%hd",	&ut->ut_exit.e_termination);
X	(void)fscanf(f," exit=%hd",	&ut->ut_exit.e_exit);
X#endif
X#ifdef	HAVE_ut_name
X	{
X		char            name[nut_name + 1];
X
X		(void) fscanf(f, "%s", name);
X		name[nut_name] = '\0';
X		DEVIS(name);
X		(void) strncpy(ut->ut_name, name, nut_name);
X	}
X#endif
X#ifdef	HAVE_ut_host
X	{
X		char            host[nut_host + 1];
X
X		(void) fscanf(f, "%s", host);
X		host[nut_host] = '\0';
X		DEVIS(host);
X		(void) strncpy(ut->ut_host, host, nut_host);
X	}
X#endif
X#ifdef	HAVE_ut_time
X	utgets_time(f, ut);
X#endif
X
X	(void) fscanf(f, "\n");
X
X	return ut;
X}
X
X/*
X * utputs() - translate from a struct utmp to a text line in the given
X * file
X */
Xutputs(f, ut)
X	FILE           *f;
X	struct utmp    *ut;	/* utmp structure to print */
X{
X	if (!ut || !f)
X		return 0;
X
X	/*
X	 * Print out the fields
X	 */
X
X#ifdef	HAVE_ut_user
X	{
X		char            user[nut_user + 1];
X
X		(void) strncpy(user, ut->ut_user, nut_user);
X		user[nut_user] = '\0';
X		ENVIS(user);
X		(void) fprintf(f, "%*s ", nut_user, user);
X	}
X#endif
X#ifdef	HAVE_ut_id
X	{
X		char            id[nut_id + 1];
X
X		(void) strncpy(id, ut->ut_id, nut_id);
X		id[nut_id] = '\0';
X		ENVIS(id);
X		(void) fprintf(f, "%*s ", nut_id, id);
X	}
X#endif
X#ifdef	HAVE_ut_line
X	{
X		char            line[nut_line + 1];
X
X		(void) strncpy(line, ut->ut_line, nut_line);
X		line[nut_line] = '\0';
X		ENVIS(line);
X		(void) fprintf(f, "%*s ", nut_line, line);
X	}
X#endif
X#ifdef	HAVE_ut_pid
X	(void)fprintf(f,"%*d ",	nut_pid,	ut->ut_pid);
X#endif
X#ifdef	HAVE_ut_type
X	{
X	char *type;
X
X# if defined(SHORT_TYPES)
X	switch(ut->ut_type)
X	{
X	case EMPTY:		type = "NULL";	break;
X	case RUN_LVL:		type = "RLVL";	break;
X	case BOOT_TIME:		type = "BOOT";	break;
X	case OLD_TIME:		type = "OLDT";	break;
X	case NEW_TIME:		type = "NEWT";	break;
X	case INIT_PROCESS:	type = "INIP";	break;
X	case LOGIN_PROCESS:	type = "LOGP";	break;
X	case USER_PROCESS:	type = "USRP";	break;
X	case DEAD_PROCESS:	type = "DEDP";	break;
X	case ACCOUNTING:	type = "ACCT";	break;
X	default:		type = "unk!";	break;
X	}
X	(void)fprintf(f,"%*s ",	nut_type,	type); /* 5 bytes */
X# else
X	switch(ut->ut_type)
X	{
X	case EMPTY:		type = "EMPTY";		break;
X	case RUN_LVL:		type = "RUN_LVL";	break;
X	case BOOT_TIME:		type = "BOOT_TIME";	break;
X	case OLD_TIME:		type = "OLD_TIME";	break;
X	case NEW_TIME:		type = "NEW_TIME";	break;
X	case INIT_PROCESS:	type = "INIT_PROC";	break;
X	case LOGIN_PROCESS:	type = "LOGIN_PROC";	break;
X	case USER_PROCESS:	type = "USER_PROC";	break;
X	case DEAD_PROCESS:	type = "DEAD_PROC";	break;
X	case ACCOUNTING:	type = "ACCOUNTING";	break;
X	default:		type = "unknown!";	break;
X	}
X	(void)fprintf(f,"%-*s ", nut_type,	type); /* 11 bytes */
X# endif
X	}
X#endif
X#ifdef	HAVE_ut_exit
X	(void)fprintf(f,"term=%*d exit=%*d ",
X		3,
X		ut->ut_exit.e_termination,
X		3,
X		ut->ut_exit.e_exit
X	);
X#endif
X#ifdef	HAVE_ut_name
X	{
X		char            name[nut_name + 1];
X
X		(void) strncpy(name, ut->ut_name, nut_name);
X		name[nut_name] = '\0';
X		ENVIS(name);
X		(void) fprintf(f, "%*s ", nut_name, name);
X	}
X#endif
X#ifdef	HAVE_ut_host
X	{
X		char            host[nut_host + 1];
X
X		(void) strncpy(host, ut->ut_host, nut_host);
X		host[nut_host] = '\0';
X		ENVIS(host);
X		(void) fprintf(f, "%*s ", nut_host, host);
X	}
X#endif
X#ifdef	HAVE_ut_time
X	utputs_time(f, ut);
X#endif
X
X	(void) putc('\n', f);
X
X	return 1;
X}
X
X/*
X * utget() - read a struct utmp from the given file
X */
Xstruct utmp    *
Xutget(f)
X	FILE           *f;
X{
X	static struct utmp utb;
X	static struct utmp *ut = &utb;
X
X	if (fread((char *) ut, sizeof(*ut), 1, f) != 1)
X		return (struct utmp *) 0;
X
X	return ut;
X}
X
X/*
X * utput() - write a struct utmp to the given file
X */
Xutput(f, ut)
X	FILE           *f;
X	struct utmp    *ut;
X{
X	if (!ut || !f)
X		return 0;
X
X	return fwrite((char *) ut, sizeof(*ut), 1, f);
X}
X
X/*
X * docopy() - copy from i to o, translating as appropriate
X */
Xdocopy(i, o)
X	FILE           *i;
X	FILE           *o;
X{
X	FILE           *tmp = tmpfile();
X	int             ilen = 0;	/* size of i file */
X	int             binary = 0;	/* 1 if i file is binary */
X	int             c;
X
X	/*
X	 * The textual representation may contain only printing
X	 * characters - only the visible characters and space.
X	 * 
X	 * If there is any character which is not ascii or is not
X	 * printable, then treat the file as binary.
X	 */
X
X	while ((c = getc(i)) != EOF)
X	{
X		ilen++;
X
X		if (!isascii(c) || !(isprint(c) || c == '\n'))
X		{
X			binary = 1;
X		}
X
X		(void) putc(c, tmp);
X	}
X	if(ilen == 0)
X		return;
X
X	rewind(tmp);
X
X	if (binary)
X	{
X#if 0
X		(void) fprintf(stderr, "Input is Binary\n");
X#endif
X		if (ilen % sizeof(struct utmp))
X		{
X			(void) fprintf(stderr, "Binary wrong size!\n");
X		}
X		while (utputs(o, utget(tmp)))
X			continue;
X	}
X	else
X	{
X#if 0
X		(void) fprintf(stderr, "Input is Text\n");
X#endif
X		if (ilen % LINSIZ)
X		{
X			(void) fprintf(stderr, "Text wrong size!\n");
X		}
X		while (utput(o, utgets(tmp)))
X			continue;
X	}
X}
X
Xmain(ac, av)
X	int             ac;
X	char          **av;
X{
X	FILE           *in = stdin;
X	FILE           *out = stdout;
X	int             optc = cmdline(ac, av);
X
X	ac -= optc;
X	av += optc;
X
X	switch (ac)
X	{
X#if 0
X	case 2:
X		if ((out = fopen(av[1], "w")) == (FILE *) 0)
X		{
X			exit(1);
X		}
X		/* PASSTHROUGH */
X#endif
X	case 1:
X		if ((in = fopen(av[0], "r")) == (FILE *) 0)
X		{
X			exit(1);
X		}
X		break;
X	case 0:
X		if (isatty(0))
X		{
X			if ((in = fopen(UTMP_FILE, "r")) == (FILE *) 0)
X			{
X				exit(1);
X			}
X		}
X		break;
X	default:
X		exit(1);
X	}
X
X	docopy(in, out);
X
X	return 0;
X}
END_OF_FILE
if test 19486 -ne `wc -c <'utmp.c'`; then
    echo shar: \"'utmp.c'\" unpacked with wrong size!
fi
# end of 'utmp.c'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(866 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#-------
X# Installation parameters
X#-------
XBINDIR	      = $(root)/pub/bin/$(ht)
XBINMOD	      = 500
XBINOWN	      =	dws
XBINGRP	      =	dws
XMANDIR	      = $(root)/pub/man/man1
XMANMOD	      = 400
XMANOWN	      =	dws
XMANGRP	      =	dws
X
XLINKER	      = $(CC)
XLDFLAGS	      =
XSHAR	      = shar
X
XPROG	      = utmp
XPLUG	      = README
XPAGE	      = utmp.1
XTEXT	      = Makefile
XHDRS	      =
XSRCS	      = utmp.c
XOBJS	      = utmp.o
X
Xall:		$(PROG)
X
X$(PROG):	$(OBJS)
X		$(LINKER) $(LDFLAGS) $(OBJS) -o $(PROG)
X
Xclean:;		rm -f $(OBJS)
X
Xclobber:	clean
X		rm -f $(PROG) $(PROG).shar
X
Xinstall:	$(PROG) $(PAGE)
X		install -m $(BINMOD) -o $(BINOWN) -g $(BINGRP) -s $(PROG) $(BINDIR)
X		install -m $(MANMOD) -o $(MANOWN) -g $(MANGRP) -c $(PAGE) $(MANDIR)
X
Xuninstall:;	rm -f $(BINDIR)/$(PROG)
X		rm -f $(MANDIR)/$(PAGE)
X
Xshar:;		$(SHAR) $(PLUG) $(PAGE) $(HDRS) $(SRCS) $(TEXT) > $(PROG).shar
END_OF_FILE
if test 866 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
echo shar: End of shell archive.
exit 0
