[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

utmpx implementation



I've implemented utmpx for FreeBSD, and placed it under BSD license. This 
should be a standards-compliant (SUSv2) implementation, that is approximately
equivilent to the utmp in glibc or Solaris. The major difference from Linux
is that it uses /var/run/utmpx, and getutent becomes getutxent.

I haven't tested this thing very heavily yet, so please look it over closely.
If someone would like to take on writing some code to test it thoroughly, I'd
be very appreciative. I have it working with sysvinit (major reduction in the
size and ugliness of that patch) and shellutils. I'll be better able to tell
how well it's working after I get Matthew Garrett's libshadow ported over.  

Enjoy,

	---Nathan
/*
 * Copyright (c) 2002
 *      Nathan Hawkins.  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.
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 */

#ifndef _UTMPX_H_
#define _UTMPX_H_

#include <sys/types.h>
#include <sys/time.h>

#define _PATH_UTMPX	"/var/run/utmpx"
#define UTMPX_FILE	_PATH_UTMPX

/* features in utmpx */
#define _HAVE_UT_PID    1
#define _HAVE_UT_ID     1
#define _HAVE_UT_TV     1
#define _HAVE_UT_TYPE   1
#define _HAVE_UT_HOST   1

/* size of character arrays in struct utmpx */
#define UT_LINESIZE     32
#define UT_NAMESIZE     32
#define UT_HOSTSIZE     256

struct exit_status {
	short int e_termination;    /* Process termination status.  */
	short int e_exit;           /* Process exit status.  */
};

struct utmpx
{
	pid_t		ut_pid;
	char		ut_id[4];
	/* reserve space for IPv6 addresses */
	u_int32_t	ut_addr6[4];
	long int	ut_session; /* used by X? */
	struct timeval	ut_tv;
	struct exit_status ut_exit;
	short int	ut_type;
	char		ut_line[UT_LINESIZE];
	char		ut_user[UT_NAMESIZE];
	char		ut_host[UT_HOSTSIZE];
	char		_unused[20];
};

/* for compatibility */
#define ut_name	ut_user

/* values for ut_type */
#define EMPTY		0	/* Empty struct  */
#define RUN_LVL		1	/* SysV runlevel  */
#define BOOT_TIME	2	/* System boot  */
#define NEW_TIME	3	/* Mark new time at time change */
#define OLD_TIME	4	/* Mark old time at time change */
#define INIT_PROCESS	5	/* Process started by init */
#define LOGIN_PROCESS	6	/* User login */
#define USER_PROCESS	7	/* Normal process.  */
#define DEAD_PROCESS	8	/* Terminated process.  */
#define ACCOUNTING	9

__BEGIN_DECLS
void		endutxent(void);
void		setutxent(void);
struct utmpx	*getutxent(void);
struct utmpx	*getutxid(const struct utmpx *);
struct utmpx	*getutxline(const struct utmpx *);
struct utmpx	*pututxline(const struct utmpx *);
void		utmpxname(const char *);
__END_DECLS

#endif /* !_UTMPX_H_ */
/*
 * Copyright (c) 2002
 *      Nathan Hawkins.  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.
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 */

#include <sys/file.h>
#include <stdio.h>
#include <fcntl.h>
#include "utmpx.h"

static int	utx_fd=-1;
static off_t	utx_offset=-1;
static struct utmpx utx_last;
static char	default_utmpxfile[]=_PATH_UTMPX;
static char	*utmpxfile=&default_utmpxfile[0];
static int	utx_ro=0;

static int
openutxfile (void)
{
	if(utx_fd == -1) {
		utx_fd = open(utmpxfile, O_RDWR);
		if(utx_fd == -1) {
			utx_fd = open(utmpxfile, O_RDONLY);
			utx_ro = 1; /* set read-only flag */
			if(utx_fd == -1)
				return (1);
	    	} else 
			utx_ro=0; /* clear read-only flag */
	}
	utx_offset = 0;
	return (0);
}

/*
 * setutxent rewinds the utmpx file. If the file isn't open, it does nothing.
 */
void
setutxent (void)
{
	if (utx_fd == -1)
		return;

	utx_offset = lseek(utx_fd, 0, SEEK_SET);
}

/*
 * endutxent closes the utmpx file, and restores the filename to the default.
 */
void
endutxent (void)
{
	if (utx_fd != -1) {
		close (utx_fd);
		utx_fd = -1;
	}
	utmpxfile = default_utmpxfile;
}

/*
 * utmpxname is a nonstandard function used to select an alternate utmpx file.
 * The string passed here isn't used until one of the other functions opens the
 * file. endutxent resets the filename to default.
 */
void
utmpxname (const char *utmpx)
{
	if (utmpx != NULL)
		utmpxfile = (char *)utmpx;
}

static struct utmpx *
getutxent_internal (struct utmpx *ut)
{
	/* either read a full struct utmpx from the file or return NULL */
	if (read(utx_fd, ut, sizeof (struct utmpx)) !=
		sizeof (struct utmpx)) 
			return (NULL);
	
	utx_offset += sizeof (struct utmpx);
	return (ut);
}
struct utmpx *
getutxent_r (struct utmpx *buffer)
{
	struct utmpx *ut;
	int flag;

	/* open the file if it isn't already */
	if (utx_fd == -1 && openutxfile())
		return (NULL);
	/* lock the file for reading */
	flag=flock(utx_fd,LOCK_SH);
	if (flag == -1)
		return (NULL); /* failed to get the lock */
	ut=getutxent_internal(buffer);
	flag=flock(utx_fd,LOCK_UN);
	if (flag == -1)
		return (NULL); /* failed to unlock for some reason */
	return (ut);
}


/* 
 * According to SUSv2:
 *
 * The getutxid() function searches forward from the current point in the
 * database. If the ut_type value of the utmpx structure pointed to by id is
 * BOOT_TIME, OLD_TIME or NEW_TIME, then it stops when it finds an entry with a
 * matching ut_type value. If the ut_type value is INIT_PROCESS, LOGIN_PROCESS,
 * USER_PROCESS, or DEAD_PROCESS, then it stops when it finds an entry whose
 * type is one of these four and whose ut_id member matches the ut_id member of
 * the utmpx structure pointed to by id. If the end of the database is reached
 * without a match, getutxid() fails.
 */
struct utmpx *
getutxid_r (const struct utmpx *ut, struct utmpx *buffer)
{
	int finished;
	struct utmpx *last;
	int flag;

	/* open the file if it isn't already */
	if (utx_fd == -1 && openutxfile())
		return (NULL);
	/* lock the file for reading */
	flag=flock(utx_fd,LOCK_SH);
	if (flag == -1)
		return (NULL); /* failed to get the lock */

	for (;;) {
		if ((last=getutxent_internal(buffer)) == NULL)
			break;
		switch (ut->ut_type) {
			case BOOT_TIME:
			case OLD_TIME:
			case NEW_TIME:
				if (ut->ut_type == last->ut_type)
					finished = 1;
				break;
			case INIT_PROCESS:
			case LOGIN_PROCESS:
			case USER_PROCESS:
			case DEAD_PROCESS:
				if (ut->ut_type == last->ut_type &&
				    strncmp(ut->ut_id,last->ut_id,4) == 0)
					finished = 1;
				break;
			/* evidently this function ignores other types */
			default:
				break;
		}
		if (finished)
			break;
	}
	flag=flock(utx_fd,LOCK_UN);
	if (flag == -1)
		return (NULL); /* failed to unlock for some reason */
	return (last);
}

/* 
 * According to SUSv2:
 *
 * The getutxline() function searches forward from the current point in the
 * database until it finds an entry of the type LOGIN_PROCESS or USER_PROCESS
 * which also has a ut_line value matching that in the utmpx structure pointed
 * to by line. If the end of the database is reached without a match,
 * getutxline() fails.
 */
struct utmpx *
getutxline_r (const struct utmpx *ut, struct utmpx *buffer)
{
	int finished;
	struct utmpx *last;
	int flag;

	/* open the file if it isn't already */
	if (utx_fd == -1 && openutxfile())
		return (NULL);
	/* lock the file for reading */
	flag=flock(utx_fd,LOCK_SH);
	if (flag == -1)
		return (NULL); /* failed to get the lock */

	for (;;) {
		if ((last=getutxent_internal(buffer)) == NULL)
			break;
		switch (last->ut_type) {
			case LOGIN_PROCESS:
			case USER_PROCESS:
				if (strncmp(ut->ut_line,last->ut_line,UT_LINESIZE) == 0)
					finished = 1;
				break;
			/* evidently this function ignores other types */
			default:
				break;
		}
		if (finished)
			break;
	}
	flag=flock(utx_fd,LOCK_UN);
	if (flag == -1)
		return (NULL); /* failed to unlock for some reason */
	return (last);
}

/*
 * According to SUSv2:
 *
 * If the process has appropriate privileges, the pututxline() function writes
 * out the structure into the user accounting database. It uses getutxid() to
 * search for a record that satisfies the request. If this search succeeds,
 * then the entry is replaced. Otherwise, a new entry is made at the end of the
 * user accounting database.
 *
 * The implicit read done by pututxline() (if it finds that it is not already
 * at the correct place in the user accounting database) will not modify the
 * static structure returned by getutxent(), getutxid() or getutxline(), if the
 * application has just modified this structure and passed the pointer back to
 * pututxline().
 */
struct utmpx *
pututxline (const struct utmpx *ut)
{
	struct utmpx findbuf;
	struct utmpx *found;
	int flag;

	if (utx_ro)
		return (NULL); /* don't have write access to utmpx */

	if ((found=getutxid_r(ut,&findbuf))!=NULL) {
		/* try to rewind the file by exactly one record */
		utx_offset = lseek(utx_fd, utx_offset - sizeof (struct utmpx),
			SEEK_CUR);
	} else {
		/* seek to the end of the file */
		utx_offset = lseek(utx_fd, 0, SEEK_END);
	}
	/* lock the file for writing */
	flag=flock(utx_fd,LOCK_EX);
	if (flag == -1)
		return (NULL); /* failed to get the lock */

	if (write(utx_fd, ut, sizeof (struct utmpx)) != sizeof (struct utmpx)) {
		/*
		 * In case of failed write to the end of the file, truncate the
		 * record. This keeps the file format from getting corrupted.
		 */
		if (found != NULL) {
			ftruncate(utx_fd, utx_offset);
			return (NULL);
		} else {
			return (NULL);
		}
	}
	utx_offset += sizeof (struct utmpx);
	flag=flock(utx_fd,LOCK_UN);
	if (flag == -1)
		return (NULL); /* failed to unlock for some reason */
	return ((struct utmpx *)ut);
}

struct utmpx *
getutxent(void)
{
	getutxent_r(&utx_last);
}

struct utmpx *
getutxid (const struct utmpx *ut)
{
	getutxid_r(ut,&utx_last);
}

struct utmpx *
getutxline (const struct utmpx *ut)
{
	getutxid_r(ut,&utx_last);
}

Attachment: pgpKyOYghi1tx.pgp
Description: PGP signature


Reply to: