/*
**  getut(3C)
**
**  getut; getutent, getutid, getutline, pututline,
**         endutent, utmpname - access utmp file entry
**
**  Version 1.0 - Lew Pitcher (93/01/27)
**  Version 1.1 - Lew Pitcher (95/01/23) 
**                Changed logic to support pututline() function.
**  Version 1.2 - Lew Pitcher (95/04/11)
**		  open utmpname for read access. pututline() opens for write
*/
#include <sys/types.h>
#include <utmp.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

/* Minix may not have OLD_TIME or NEW_TIME defined */
#ifndef OLD_TIME
#define OLD_TIME 3
#endif
#ifndef NEW_TIME
#define NEW_TIME 4
#endif
/***************************************************/
typedef struct utmp *p_utmp_t;		/* type for ptr to utmp record	*/

#ifndef U_NULL
#define U_NULL	((p_utmp_t)NULL)
#endif

static char         n_utmp[80] = UTMP;  /* from utmp.h - default file   */
static int          f_utmp = -1;        /* file is not open yet         */
static struct utmp *p_utmp = U_NULL;	/* no utmp entry available      */
static struct utmp r_utmp;		/* utmp record for getutent()	*/

/*
** utmpname allows the user to change the name of the file
** examined, from UTMP to any other file. It is most often
** expected that this other file will be WTMP. If the file
** does not exist, this will not be apparent until the 1st
** attempt to reference the file is made. utmpname doesn't
** open the file. It just closes the old file if it is 
** currently open and saves the new file name. If the file
** name given is longer than 79 characters, utmpname returns
** 0. Otherwise, it will return 1.
*/
int utmpname(file)
char *file;
{
	endutent();
	if (strlen(file) < 80)
	{
		strcpy(n_utmp,file);
		return 1;
	}
	else return 0;
}

/*
** endutent closes the currently open file
*/
endutent()
{
	if (f_utmp != -1) close(f_utmp);        /* close utmp file       */
	f_utmp = -1;                            /* zap utmp file_pointer */
	p_utmp = U_NULL;			/* zap utmp entry_pointer*/
}

/*
** setutent resets the input stream to the beginning of the
** file. This reset should be done before each search for a
** new entry if it is desired that the entire file be 
** examined.
*/
setutent()
{
	if (f_utmp != -1)       lseek(f_utmp,0l,SEEK_SET);
	p_utmp = U_NULL;
}

/*
** getutent reads in the next entry from a utmp-like file. If
** the file is not already open, it opens it. If it reaches
** the end of the file, it fails (returning NULL).
*/
struct utmp *getutent()
{
	if ((f_utmp == -1) && ((f_utmp=open(n_utmp,O_RDONLY)) == -1))
		p_utmp = U_NULL; 
	else if (read(f_utmp,&r_utmp,sizeof r_utmp) != sizeof r_utmp)
		p_utmp = U_NULL;     
	else
		p_utmp = &r_utmp;           
	return p_utmp;
}

/*
** getutid searches forward from the current point in the utmp
** file until it finds an entry with a ut_type matching id->ut_type
** if the type specified is RUN_LVL, BOOT_TIME, OLD_TIME, or
** NEW_TIME. If the type specified in id is INIT_PROCESS,
** LOGIN_PROCESS, USER_PROCESS, or DEAD_PROCESS, then getutid will
** return a pointer to the first entry whos type is one of these
** four and whose ut_id field matches id->ut_id. If the end of file
** is reached without a match, getutid fails (returning NULL).
*/
struct utmp *getutid(id)
struct utmp *id;
{
	struct utmp *ut = p_utmp;       /* save getutent() pointer   */
	static struct utmp r_ut;        /* local copy of utmp record */

	if (ut == U_NULL)	ut = getutent();
	switch (id->ut_type)
	{
		case RUN_LVL:
		case BOOT_TIME:
		case OLD_TIME:
		case NEW_TIME:
			while ((ut != U_NULL) && (ut->ut_type != id->ut_type))
				ut = getutent();
			break;

		case INIT_PROCESS:
		case LOGIN_PROCESS:
		case USER_PROCESS:
		case DEAD_PROCESS:
			while ((ut != U_NULL) &&
			       !(((ut->ut_type == INIT_PROCESS) ||
				  (ut->ut_type == LOGIN_PROCESS) ||
				  (ut->ut_type == USER_PROCESS) ||
				  (ut->ut_type == DEAD_PROCESS)) &&
				 (strncmp(ut->ut_id,
					  id->ut_id,
					  sizeof ut->ut_id) == 0)))
				ut = getutent();   
			break;

		default:
			ut = getutent();	/* give him something */
			break;
	}
	if (ut != U_NULL) ut = (p_utmp_t) memcpy(&r_ut,ut,sizeof r_ut);
	return ut;
}

/*
** getutline searches forward from the current point in the
** utmp file until it finds an entry of type LOGIN_PROCESS
** or USER_PROCESS that also has a ut_line string matching
** the line->ut_line string. If the end of file is reached
** without a match, getutline fails (returning NULL).
*/
struct utmp *getutline(line)
struct utmp *line;
{
	struct utmp *ut = p_utmp;
	static struct utmp r_ut;

	if (ut == U_NULL)	ut = getutent();
	for (; ut != U_NULL;	ut = getutent())
		if (((ut->ut_type == LOGIN_PROCESS) || (ut->ut_type == USER_PROCESS)) &&
		    (strncmp(ut->ut_line,line->ut_line,sizeof ut->ut_line) == 0))
			break;

	if (ut != U_NULL) ut = (p_utmp_t) memcpy(&r_ut,ut,sizeof r_ut);
	return ut;
}

/*
** pututline writes out the supplied utmp structure into the utmp file.
** It uses getutid to search forward for the proper place if it finds
** that it is not already at the proper place. It is expected that
** normally the user of pututline will have searched for the proper entry
** using one of the getut routines. If so, pututline will not search.
** If pututline does not find a matching slot for the new entry, it will
** add a new entry to the end of the file. It returns a pointer to the
** utmp structure.
*/
struct utmp *pututline(utmp)
struct utmp *utmp;
{
	struct utmp	*ut,
			ulocal;

	/* make a copy of the entry and search utmp for a match */
	ut = getutid(memcpy(&ulocal, utmp, sizeof ulocal));
	if (f_utmp != -1)	/* utmp open for read access */
	{
		int w_utmp;	/* fd for utmp write */

		if ((w_utmp = open(n_utmp,O_WRONLY)) > 0)
		{
			long utmp_posn;

			utmp_posn = lseek(f_utmp,0l,SEEK_CUR);
			if (ut != U_NULL)	utmp_posn -= (sizeof ulocal);
			lseek(w_utmp,utmp_posn,SEEK_SET);
			write(w_utmp,&ulocal,sizeof ulocal);
			close(w_utmp);
			p_utmp = (p_utmp_t) memcpy(&r_utmp,&ulocal,sizeof ulocal);
		}
	}
	return (p_utmp_t) memcpy(utmp,&ulocal,sizeof ulocal);
}

