/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 1999-2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#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 *);

typedef int (*_exec_XY_check_func) (nss_XbyY_args_t *);


/*
 * check_match: returns 1 if matching entry found, else returns 0.
 */
static int
check_match(nss_XbyY_args_t *argp)
{
	_priv_execattr	*_priv_exec = (_priv_execattr *)(argp->key.attrp);
	const char	*name = _priv_exec->name;
	const char	*type = _priv_exec->type;
	const char	*id = _priv_exec->id;
	const char	*policy = _priv_exec->policy;
	execstr_t	*exec = (execstr_t *)argp->returnval;

	if ((policy && exec->policy && (strcmp(policy, exec->policy) != 0)) ||
	    (name && exec->name && (strcmp(name, exec->name) != 0)) ||
	    (type && exec->type && (strcmp(type, exec->type) != 0)) ||
	    (id && exec->id && (strcmp(id, exec->id) != 0))) {
		return (0);
	}

	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		*key = NULL;
	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 = __nsl_fopen(be->filename, "r")) == 0) {
			(void) _nss_files_endent(be, 0);
			(void) rw_unlock(&exec_lock);
			return (NSS_UNAVAIL);
		}
		exec_fd = __nsl_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;
	while (1) {
		int	linelen = 0;
		int	check_stat = 0;
		char	*instr = be->buf;

		linelen = _readbufline(f_buf, f_size, instr, be->minbuf,
		    &lastlen);
		if (linelen < 0) {
			/* End of file */
			argp->erange = 0;
			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 ((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;

		/*
		 * Parse the entry.
		 */
		argp->returnval = NULL;
		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;
			if (check_match(argp)) {
				res = NSS_SUCCESS;
				if (_priv_exec->search_flag == GET_ONE) {
					break;
				} else if (_doexeclist(argp) == 0) {
					res = NSS_UNAVAIL;
					break;
				}
			} else {
				argp->returnval = NULL;
				memset(argp->buf.buffer, NULL,
				    argp->buf.buflen);
			}
		} 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)
{
	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 = strdup(_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;
	_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;
	_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
};

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));
}