/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdlib.h>
#include "files_common.h"
#include <time.h>
#include <exec_attr.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <ctype.h>
#include <synch.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

/*
 * files/getexecattr.c -- "files" backend for nsswitch "exec_attr" database
 *
 * _execattr_files_read_line and _execattr_files_XY_all code based on
 * nss_files_read_line and nss_files_XY_all respectively, from files_common.c
 */


/* externs from libnsl */
extern int _doexeclist(nss_XbyY_args_t *);
extern int _readbufline(char *, int, char *, int, int *);
extern char *_exec_wild_id(char *, const char *);
extern void _exec_cleanup(nss_status_t, nss_XbyY_args_t *);


/*
 * check_match: returns 1 if matching entry found, else returns 0.
 */
static int
check_match(nss_XbyY_args_t *argp, const char *line, int linelen)
{
	const char	*limit, *linep, *keyp;
	_priv_execattr	*_priv_exec = (_priv_execattr *)(argp->key.attrp);
	const char	*exec_field[6];
	int		i;

	exec_field[0] = _priv_exec->name;	/* name */
	exec_field[1] = _priv_exec->policy;	/* policy */
	exec_field[2] = _priv_exec->type;	/* type */
	exec_field[3] = NULL;			/* res1 */
	exec_field[4] = NULL;			/* res2 */
	exec_field[5] = _priv_exec->id;		/* id */
	/* No need to check attr field */

	linep = line;
	limit = line + linelen;

	for (i = 0; i < 6; i++) {
		keyp = exec_field[i];
		if (keyp) {
			/* compare field */
			while (*keyp && linep < limit &&
			    *linep != ':' && *keyp == *linep) {
				keyp++;
				linep++;
			}
			if (*keyp || linep == limit || *linep != ':')
				return (0);
		} else {
			/* skip field */
			while (linep < limit && *linep != ':')
				linep++;
		}
		linep++;
	}
	return (1);
}


static nss_status_t
_exec_files_XY_all(files_backend_ptr_t be,
    nss_XbyY_args_t *argp,
    int getby_flag)
{
	int		parse_stat = 0;
	int		lastlen = 0;
	int		exec_fd = 0;
	int		f_size = 0;
	time_t		f_time = 0;
	static time_t	read_time = 0;
	char		*first;
	char		*last;
	static char	*f_buf = NULL;
	struct stat	f_stat;
	nss_status_t	res = NSS_NOTFOUND;
	_priv_execattr	*_priv_exec = (_priv_execattr *)(argp->key.attrp);
	static rwlock_t	exec_lock;

	if (((be->buf == NULL) &&
	    ((be->buf = (char *)calloc(1, be->minbuf)) == NULL)) ||
	    (be->filename == NULL) ||
	    (rw_rdlock(&exec_lock) != 0)) {
		return (NSS_UNAVAIL);
	}

	/*
	 * check the size and the time stamp on the file
	 */
	if (stat(be->filename, &f_stat) != 0) {
		(void) _nss_files_endent(be, 0);
		(void) rw_unlock(&exec_lock);
		return (NSS_UNAVAIL);
	}

	f_size = f_stat.st_size;
	f_time = f_stat.st_mtime;

	while (f_time > read_time) {
		/*
		 * file has been modified since we last read it.
		 * read it into the buffer with rw lock.
		 */
		(void) rw_unlock(&exec_lock);
		if (rw_wrlock(&exec_lock) != 0) {
			(void) _nss_files_endent(be, 0);
			return (NSS_UNAVAIL);
		}
		if ((be->f = fopen(be->filename, "rF")) == 0) {
			(void) _nss_files_endent(be, 0);
			(void) rw_unlock(&exec_lock);
			return (NSS_UNAVAIL);
		}
		exec_fd = fileno(be->f);
		if (f_buf != NULL)
			free(f_buf);
		if ((f_buf = malloc(f_size)) == NULL) {
			(void) _nss_files_endent(be, 0);
			(void) rw_unlock(&exec_lock);
			return (NSS_UNAVAIL);
		}
		if (read(exec_fd, f_buf, f_size) < f_size) {
			free(f_buf);
			(void) _nss_files_endent(be, 0);
			(void) rw_unlock(&exec_lock);
			return (NSS_UNAVAIL);
		}
		read_time = f_time;
		(void) rw_unlock(&exec_lock);
		/*
		 * verify that the file did not change after
		 * we read it.
		 */
		if (rw_rdlock(&exec_lock) != 0) {
			free(f_buf);
			(void) _nss_files_endent(be, 0);
			return (NSS_UNAVAIL);
		}
		if (stat(be->filename, &f_stat) != 0) {
			free(f_buf);
			(void) _nss_files_endent(be, 0);
			(void) rw_unlock(&exec_lock);
			return (NSS_UNAVAIL);
		}
		f_size = f_stat.st_size;
		f_time = f_stat.st_mtime;
	}

	res = NSS_NOTFOUND;
	/*CONSTCOND*/
	while (1) {
		int	linelen = 0;
		char	*instr = be->buf;

		linelen = _readbufline(f_buf, f_size, instr, be->minbuf,
		    &lastlen);
		if (linelen < 0) {
			/* End of file */
			break;
		}

		/*
		 * If the entry doesn't contain the filter string then
		 * it can't be the entry we want, so don't bother looking
		 * more closely at it.
		 */
		switch (getby_flag) {
		case NSS_DBOP_EXECATTR_BYNAME:
			if (strstr(instr, _priv_exec->name) == NULL)
				continue;
			break;
		case NSS_DBOP_EXECATTR_BYID:
			if (strstr(instr, _priv_exec->id) == NULL)
				continue;
			break;
		case NSS_DBOP_EXECATTR_BYNAMEID:
			if ((strstr(instr, _priv_exec->name) == NULL) ||
			    (strstr(instr, _priv_exec->id) == NULL))
				continue;
			break;
		default:
			break;
		}
		if (((_priv_exec->policy != NULL) &&
		    (strstr(instr, _priv_exec->policy) == NULL)) ||
		    ((_priv_exec->type != NULL) &&
		    (strstr(instr, _priv_exec->type) == NULL)))
				continue;

		/*
		 * Get rid of white spaces, comments etc.
		 */
		if ((last = strchr(instr, '#')) == NULL)
			last = instr + linelen;
		*last-- = '\0';	/* Nuke '\n' or #comment */
		/*
		 * Skip leading whitespace.  Normally there isn't any,
		 * so it's not worth calling strspn().
		 */
		for (first = instr; isspace(*first); first++)
			;
		if (*first == '\0')
			continue;
		/*
		 * Found something non-blank on the line.  Skip back
		 * over any trailing whitespace;  since we know there's
		 * non-whitespace earlier in the line, checking for
		 * termination is easy.
		 */
		while (isspace(*last))
			--last;
		linelen = last - first + 1;
		if (first != instr)
			instr = first;

		/* Check the entry */
		argp->returnval = NULL;
		argp->returnlen = 0;
		if (check_match(argp, instr, linelen) == 0)
			continue;

		/* Marshall the data */
		parse_stat = (*argp->str2ent)(instr, linelen, argp->buf.result,
		    argp->buf.buffer, argp->buf.buflen);
		if (parse_stat == NSS_STR_PARSE_SUCCESS) {
			argp->returnval = (argp->buf.result != NULL)?
			    argp->buf.result : argp->buf.buffer;
			argp->returnlen = linelen;
			res = NSS_SUCCESS;
			if (IS_GET_ONE(_priv_exec->search_flag)) {
				break;
			} else if (_doexeclist(argp) == 0) {
				res = NSS_UNAVAIL;
				break;
			}
		} else if (parse_stat == NSS_STR_PARSE_ERANGE) {
			argp->erange = 1;
			break;
		} /* else if (parse_stat == NSS_STR_PARSE_PARSE) don't care ! */
	}

	(void) _nss_files_endent(be, 0);
	(void) rw_unlock(&exec_lock);

	return (res);
}


/*
 * If search for exact match for id failed, get_wild checks if we have
 * a wild-card entry for that id.
 */
static nss_status_t
get_wild(files_backend_ptr_t be, nss_XbyY_args_t *argp, int getby_flag)
{
	const char	*orig_id = NULL;
	char		*old_id = NULL;
	char		*wild_id = NULL;
	nss_status_t	res = NSS_NOTFOUND;
	_priv_execattr	*_priv_exec = (_priv_execattr *)(argp->key.attrp);

	orig_id = _priv_exec->id;
	old_id = strdup(_priv_exec->id);
	wild_id = old_id;
	while ((wild_id = _exec_wild_id(wild_id, _priv_exec->type)) != NULL) {
		_priv_exec->id = wild_id;
		res = _exec_files_XY_all(be, argp, getby_flag);
		if (res == NSS_SUCCESS)
			break;
	}
	_priv_exec->id = orig_id;
	if (old_id)
		free(old_id);

	return (res);
}


static nss_status_t
getbynam(files_backend_ptr_t be, void *a)
{
	nss_status_t	res;
	nss_XbyY_args_t	*argp = (nss_XbyY_args_t *)a;

	res =  _exec_files_XY_all(be, argp, NSS_DBOP_EXECATTR_BYNAME);

	_exec_cleanup(res, argp);

	return (res);
}


static nss_status_t
getbyid(files_backend_ptr_t be, void *a)
{
	nss_status_t	res;
	nss_XbyY_args_t	*argp = (nss_XbyY_args_t *)a;
	/*LINTED*/
	_priv_execattr	*_priv_exec = (_priv_execattr *)(argp->key.attrp);

	res = _exec_files_XY_all(be, argp, NSS_DBOP_EXECATTR_BYID);

	if (res != NSS_SUCCESS)
		res = get_wild(be, argp, NSS_DBOP_EXECATTR_BYID);

	_exec_cleanup(res, argp);

	return (res);
}


static nss_status_t
getbynameid(files_backend_ptr_t be, void *a)
{
	nss_status_t	res;
	nss_XbyY_args_t	*argp = (nss_XbyY_args_t *)a;
	/*LINTED*/
	_priv_execattr	*_priv_exec = (_priv_execattr *)(argp->key.attrp);

	res = _exec_files_XY_all(be, argp, NSS_DBOP_EXECATTR_BYNAMEID);

	if (res != NSS_SUCCESS)
		res = get_wild(be, argp, NSS_DBOP_EXECATTR_BYNAMEID);

	_exec_cleanup(res, argp);

	return (res);
}


static files_backend_op_t execattr_ops[] = {
	_nss_files_destr,
	_nss_files_endent,
	_nss_files_setent,
	_nss_files_getent_netdb,
	getbynam,
	getbyid,
	getbynameid
};

/*ARGSUSED*/
nss_backend_t  *
_nss_files_exec_attr_constr(const char *dummy1,
    const char *dummy2,
    const char *dummy3,
    const char *dummy4,
    const char *dummy5,
    const char *dummy6,
    const char *dummy7)
{
	return (_nss_files_constr(execattr_ops,
	    sizeof (execattr_ops)/sizeof (execattr_ops[0]),
	    EXECATTR_FILENAME, NSS_LINELEN_EXECATTR, NULL));
}