/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)nfslogtab.c	1.9	99/11/22 SMI"

/*
 * Manipulates the nfslogtab
 */

#ifndef _REENTRANT
#define	_REENTRANT
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <utmpx.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include "nfslogtab.h"

#ifndef	LINTHAPPY
#define	LINTHAPPY
#endif

static void logtab_ent_list_free(struct logtab_ent_list *);

/*
 * Retrieves the next entry from nfslogtab.
 * Assumes the file is locked.
 * '*lepp' points to the new entry if successful.
 * Returns:
 *      > 0  valid entry
 *      = 0  end of file
 *      < 0  error
 */
int
logtab_getent(FILE *fd, struct logtab_ent **lepp)
{
	char line[MAXBUFSIZE + 1];
	char *p;
	char *lasts, *tmp;
	char *w = " \t";
	struct logtab_ent *lep = NULL;
	int error = 0;

	if ((lep = (struct logtab_ent *)malloc(sizeof (*lep))) == NULL) {
		return (-1);
	}
	(void) memset((char *)lep, 0, sizeof (*lep));

	if ((p = fgets(line, MAXBUFSIZE, fd)) == NULL)  {
		error = 0;
		goto errout;
	}

	line[strlen(line) - 1] = '\0';

	tmp = (char *)strtok_r(p, w, &lasts);
	if (tmp == NULL) {
		error = -1;
		goto errout;
	}
	if ((lep->le_buffer = strdup(tmp)) == NULL) {
		error = -1;
		goto errout;
	}

	tmp = (char *)strtok_r(NULL, w, &lasts);
	if (tmp == NULL) {
		error = -1;
		goto errout;
	}
	if ((lep->le_path = strdup(tmp)) == NULL) {
		error = -1;
		goto errout;
	}

	tmp = (char *)strtok_r(NULL, w, &lasts);
	if (tmp == NULL) {
		error = -1;
		goto errout;
	}
	if ((lep->le_tag = strdup(tmp)) == NULL) {
		error = -1;
		goto errout;
	}

	tmp = (char *)strtok_r(NULL, w, &lasts);
	if (tmp == NULL) {
		error = -1;
		goto errout;
	}
	lep->le_state = atoi(tmp);

	*lepp = lep;
	return (1);

errout:
	logtab_ent_free(lep);

	return (error);
}

/*
 * Append an entry to the logtab file.
 */
int
logtab_putent(FILE *fd, struct logtab_ent *lep)
{
	int r;

	if (fseek(fd, 0L, SEEK_END) < 0)
		return (errno);

	r = fprintf(fd, "%s\t%s\t%s\t%d\n",
		lep->le_buffer,
		lep->le_path,
		lep->le_tag,
		lep->le_state);

	return (r);
}

#ifndef	LINTHAPPY
/*
 * Searches the nfslogtab file looking for the next entry which matches
 * the search criteria. The search is continued at the current position
 * in the nfslogtab file.
 * If 'buffer' != NULL, then buffer is matched.
 * If 'path' != NULL, then path is matched.
 * If 'tag' != NULL, then tag is matched.
 * If 'state' != -1, then state is matched.
 * 'buffer', 'path' and 'tag' can all be non-NULL, which means the entry must
 * satisfy all requirements.
 *
 * Returns 0 on success, ENOENT otherwise.
 * If found, '*lepp' points to the matching entry, otherwise '*lepp' is
 * undefined.
 */
static int
logtab_findent(FILE *fd, char *buffer, char *path, char *tag, int state,
		struct logtab_ent **lepp)
{
	boolean_t found = B_FALSE;

	while (!found && (logtab_getent(fd, lepp) > 0)) {
		found = B_TRUE;
		if (buffer != NULL)
			found = strcmp(buffer, (*lepp)->le_buffer) == 0;
		if (path != NULL)
			found = found && (strcmp(path, (*lepp)->le_path) == 0);
		if (tag != NULL)
			found = found && (strcmp(tag, (*lepp)->le_tag) == 0);
		if (state != -1)
			found = found && (state == (*lepp)->le_state);
		if (!found)
			logtab_ent_free(*lepp);
	}

	return (found ? 0 : ENOENT);
}
#endif

/*
 * Remove all entries which match the search criteria.
 * If 'buffer' != NULL, then buffer is matched.
 * If 'path' != NULL, then path is matched.
 * If 'tag' != NULL, then tag is matched.
 * If 'state' != -1, then state is matched.
 * 'buffer', 'path' and 'tag' can all be non-NULL, which means the entry must
 * satisfy all requirements.
 * The file is assumed to be locked.
 * Read the entries into a linked list of logtab_ent structures
 * minus the entries to be removed, then truncate the nfslogtab
 * file and write it back to the file from the linked list.
 *
 * On success returns 0, -1 otherwise.
 * Entry not found is treated as success since it was going to be removed
 * anyway.
 */
int
logtab_rement(FILE *fd, char *buffer, char *path, char *tag, int state)
{
	struct logtab_ent_list *head = NULL, *tail = NULL, *tmpl;
	struct logtab_ent *lep;
	int remcnt = 0;		/* remove count */
	int error = 0;
	boolean_t found;

	rewind(fd);
	while ((error = logtab_getent(fd, &lep)) > 0) {
		found = B_TRUE;
		if (buffer != NULL)
			found = strcmp(buffer, lep->le_buffer) == 0;
		if (path != NULL)
			found = found && (strcmp(path, lep->le_path) == 0);
		if (tag != NULL)
			found = found && (strcmp(tag, lep->le_tag) == 0);
		if (state != -1)
			found = found && (state == lep->le_state);
		if (found) {
			remcnt++;
			logtab_ent_free(lep);
		} else {
			tmpl = (struct logtab_ent_list *)
				malloc(sizeof (struct logtab_ent));
			if (tmpl == NULL) {
				error = ENOENT;
				break;
			}

			tmpl->lel_le = lep;
			tmpl->lel_next = NULL;
			if (head == NULL) {
				/*
				 * empty list
				 */
				head = tail = tmpl;
			} else {
				/*
				 * Add to the end of the list and remember
				 * the new last element.
				 */
				tail->lel_next = tmpl;
				tail = tmpl;	/* remember the last element */
			}
		}
	}

	if (error)
		goto deallocate;

	if (remcnt == 0) {
		/*
		 * Entry not found, nothing to do
		 */
		goto deallocate;
	}

	if (ftruncate(fileno(fd), 0) < 0) {
		error = -1;
		goto deallocate;
	}

	for (tmpl = head; tmpl != NULL; tmpl = tmpl->lel_next)
		(void) logtab_putent(fd, tmpl->lel_le);

deallocate:
	logtab_ent_list_free(head);

	return (error);
}

/*
 * Deactivate all entries matching search criteria.
 * If 'buffer' != NULL then match buffer.
 * If 'path' != NULL then match path.
 * If 'tag' != NULL then match tag.
 * Note that 'buffer', 'path' and 'tag' can al be non-null at the same time.
 *
 * Rewrites the nfslogtab file with the updated state for each entry.
 * Assumes the nfslogtab file has been locked for writing.
 * Returns 0 on success, -1 on failure.
 */
int
logtab_deactivate(FILE *fd, char *buffer, char *path, char *tag)
{
	struct logtab_ent_list *lelp, *head = NULL, *tail = NULL;
	struct logtab_ent *lep;
	boolean_t found;
	int error = 0;
	int count = 0;

	rewind(fd);
	while ((error = logtab_getent(fd, &lep)) > 0) {
		found = B_TRUE;
		if (buffer != NULL)
			found = strcmp(buffer, lep->le_buffer) == 0;
		if (path != NULL)
			found = found && (strcmp(path, lep->le_path) == 0);
		if (tag != NULL)
			found = found && (strcmp(tag, lep->le_tag) == 0);
		if (found && (lep->le_state == LES_ACTIVE)) {
			count++;
			lep->le_state = LES_INACTIVE;
		}

		lelp = (struct logtab_ent_list *)
			malloc(sizeof (struct logtab_ent));
		if (lelp == NULL) {
			error = ENOENT;
			break;
		}

		lelp->lel_le = lep;
		lelp->lel_next = NULL;
		if (head == NULL) {
			/*
			 * empty list
			 */
			head = tail = lelp;
		} else {
			/*
			 * Add to the end of the list and remember
			 * the new last element.
			 */
			tail->lel_next = lelp;
			tail = lelp;	/* remember the last element */
		}
	}

	if (error)
		goto deallocate;

	if (count == 0) {
		/*
		 * done
		 */
		error = 0;
		goto deallocate;
	}

	if (ftruncate(fileno(fd), 0) < 0) {
		error = -1;
		goto deallocate;
	}

	for (lelp = head; lelp != NULL; lelp = lelp->lel_next)
		(void) logtab_putent(fd, lelp->lel_le);

deallocate:
	logtab_ent_list_free(head);

	return (error);
}

/*
 * Deactivates all entries if nfslogtab exists and is older than boot time
 * This will only happen the first time it is called.
 * Assumes 'fd' has been locked by the caller.
 * Returns 0 on success, otherwise -1.
 */
int
logtab_deactivate_after_boot(FILE *fd)
{
	struct stat st;
	struct utmpx *utmpxp;
	int error = 0;

	if ((fstat(fileno(fd), &st) == 0) &&
	    ((utmpxp = getutxent()) != NULL) &&
	    (utmpxp->ut_xtime > st.st_mtime)) {
		if (logtab_deactivate(fd, NULL, NULL, NULL))
			error = -1;
	}

	return (error);
}

void
logtab_ent_free(struct logtab_ent *lep)
{
	if (lep->le_buffer)
		free(lep->le_buffer);
	if (lep->le_path)
		free(lep->le_path);
	if (lep->le_tag)
		free(lep->le_tag);
	free(lep);
}

static void
logtab_ent_list_free(struct logtab_ent_list *head)
{
	struct logtab_ent_list *lelp, *next;

	if (head == NULL)
		return;

	for (lelp = head; lelp != NULL; lelp = next) {
		if (lelp->lel_le != NULL)
			logtab_ent_free(lelp->lel_le);
		next = lelp->lel_next;
		free(lelp);
	}
}
