/*
 * 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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * lockfs
 *	user interface to lockfs functionality
 */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mntent.h>
#include <sys/mnttab.h>
#include <errno.h>
#include <sys/lockfs.h>
#include <sys/filio.h>

#define	bzero(s, n)	memset(s, 0, n);

/*
 * command line processing
 */
extern char	*optarg;
extern int	optind;
extern int	opterr;

extern void exit();

static void exitusage();
static void printstatusline(char *, char *, char *);
static void printstatus(char *);
static void flushfs(char *);
static void lockfs(char *);
static void getmntnames();
static void getcmdnames(int, char **, int);

/*
 * -a = all
 * -v = verbose
 */
int all		= 0;
int verbose	= 0;

/*
 * exitstatus
 *	0 all ok
 *	1 internal error
 *	2 system call error
 */
int exitstatus	= 0;

/*
 * list of filenames
 */
struct filename {
	struct filename	*fn_next;
	char		*fn_name;
};
struct filename	*fnanchor	= 0;

/*
 * default request is `file system lock status'
 * default lock type is `unlock'
 * -wnduhfe changes them
 */
int request	= _FIOLFSS;
ushort_t	lock	= LOCKFS_ULOCK;

/*
 * default comment is null
 *	-c changes it
 */
caddr_t comment	= 0;
ulong_t	comlen	= 0;

/*
 * for prettyprint
 */
int firsttime	= 0;

/*
 * no unlocks printed
 */
int no_unlocks_printed	= 0;

/*
 * file system was modified during hlock/wlock/elock
 */
#define	LOCKWARN(FN, S)	\
{ \
	if (verbose) \
		printf("WARNING: %s was modified while %s locked\n", FN, S); \
	exitstatus = 2; \
}

/*
 * forward reference
 */
char	*malloc();

int
main(int argc, char *argv[])
{
	int		c;
	struct filename	*fnp;

	exitstatus = 0;

	/*
	 * process command line
	 */
	opterr = 0;
	optarg = 0;

	while ((c = getopt(argc, argv, "vfwnduheac:")) != -1)
		switch (c) {
		case 'v':
			verbose = 1;
			break;
		case 'f':
			request = _FIOFFS;
			break;
		case 'w':
			lock    = LOCKFS_WLOCK;
			request = _FIOLFS;
			break;
		case 'n':
			lock    = LOCKFS_NLOCK;
			request = _FIOLFS;
			break;
		case 'd':
			lock    = LOCKFS_DLOCK;
			request = _FIOLFS;
			break;
		case 'h':
			lock    = LOCKFS_HLOCK;
			request = _FIOLFS;
			break;
		case 'e':
			lock	= LOCKFS_ELOCK;
			request = _FIOLFS;
			break;
		case 'u':
			lock    = LOCKFS_ULOCK;
			request = _FIOLFS;
			break;
		case 'a':
			all = 1;
			break;
		case 'c':
			comment = optarg;
			comlen  = strlen(optarg)+1;
			request = _FIOLFS;
			break;
		default:
			exitusage();
			break;
		}

	if (argc == 1) {
		no_unlocks_printed = 1;
		all = 1;
	}

	if (all)
		/*
		 * use /etc/mtab
		 */
		getmntnames();
	else
		/*
		 * use command line
		 */
		getcmdnames(argc, argv, optind);

	/*
	 * for each filename, doit
	 */
	for (fnp = fnanchor; fnp; fnp = fnp->fn_next) {
		switch (request) {
		case _FIOLFSS:
			printstatus(fnp->fn_name);
			break;
		case _FIOLFS:
			lockfs(fnp->fn_name);
			break;
		case _FIOFFS:
			flushfs(fnp->fn_name);
			break;
		default:
			break;
		}
	}

	/*
	 * all done
	 */
	return (exitstatus);
}
/*
 * exitusage
 *	bad command line, give hint
 */
void
exitusage()
{
	printf("usage: lockfs [-dfhnuw] [-c string] [-a] [file system ...]\n");
	exit(1);
}
/*
 * printstatusline
 * 	prettyprint the status line
 */
void
printstatusline(char *fn, char *locktype, char *comment)
{
	if (firsttime++ == 0)
		printf("%-20s %-10s %s\n", "Filesystem", "Locktype", "Comment");
	printf("%-20s %-10s %s\n", fn, locktype, comment);
}
/*
 * printstatus
 *	get and prettyprint file system lock status
 */
void
printstatus(char *fn)
{
	int		fd;
	int		fsmod	= 0;
	char		*locktype;
	char		commentbuffer[LOCKFS_MAXCOMMENTLEN+1];
	struct lockfs	lf;

	fd = open64(fn, O_RDONLY);
	if (fd == -1) {
		if (errno == EIO)
			printstatusline(fn, "EIO", "May be hard locked");
		else
			perror(fn);
		exitstatus = 2;
		return;
	}

	bzero((caddr_t)&lf, sizeof (struct lockfs));

	lf.lf_flags   = LOCKFS_MOD;
	lf.lf_comlen  = LOCKFS_MAXCOMMENTLEN;
	lf.lf_comment = commentbuffer;

	if (ioctl(fd, _FIOLFSS, &lf) == -1) {
		perror(fn);
		close(fd);
		exitstatus = 2;
		return;
	}
	switch (lf.lf_lock) {
	case LOCKFS_ULOCK:
		if (no_unlocks_printed)
			goto out;
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(unlock)";
		else
			locktype = "unlock";
		break;
	case LOCKFS_WLOCK:
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(write)";
		else {
			locktype = "write";
			fsmod = LOCKFS_IS_MOD(&lf);
		}
		break;
	case LOCKFS_NLOCK:
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(name)";
		else
			locktype = "name";
		break;
	case LOCKFS_DLOCK:
		locktype = "delete";
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(delete)";
		else
			locktype = "delete";
		break;
	case LOCKFS_HLOCK:
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(hard)";
		else {
			locktype = "hard";
			fsmod = LOCKFS_IS_MOD(&lf);
		}
		break;
	case LOCKFS_ELOCK:
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(error)";
		else {
			locktype = "error";
			fsmod = LOCKFS_IS_MOD(&lf);
		}
		break;
	default:
		if (LOCKFS_IS_BUSY(&lf))
			locktype = "(unknown)";
		else
			locktype = "unknown";
		break;
	}
	lf.lf_comment[lf.lf_comlen] = '\0';
	printstatusline(fn, locktype, lf.lf_comment);
	if (fsmod)
		LOCKWARN(fn, locktype);
out:
	close(fd);
}
/*
 * flushfs
 *	push and invalidate at least the data that is *currently* dirty
 */
void
flushfs(char *fn)
{
	int		fd;

	fd = open64(fn, O_RDONLY);
	if (fd == -1) {
		perror(fn);
		exitstatus = 2;
		return;
	}

	if (ioctl(fd, _FIOFFS, NULL) == -1) {
		perror(fn);
		close(fd);
		exitstatus = 2;
		return;
	}
	close(fd);
}
/*
 * lockfs
 *	lock the file system
 */
void
lockfs(char *fn)
{
	int		fd;
	struct lockfs	lf;

	fd = open64(fn, O_RDONLY);
	if (fd == -1) {
		perror(fn);
		exitstatus = 2;
		return;
	}

	bzero((caddr_t)&lf, sizeof (struct lockfs));

	lf.lf_flags = LOCKFS_MOD;
	if (ioctl(fd, _FIOLFSS, &lf) == -1) {
		perror(fn);
		close(fd);
		exitstatus = 2;
		return;
	}

	if (!LOCKFS_IS_BUSY(&lf) && LOCKFS_IS_MOD(&lf)) {
		if (LOCKFS_IS_HLOCK(&lf))
			LOCKWARN(fn, "hard");
		if (LOCKFS_IS_ELOCK(&lf))
			LOCKWARN(fn, "error");
		if (LOCKFS_IS_WLOCK(&lf))
			LOCKWARN(fn, "write");
	}

	lf.lf_lock	= lock;
	lf.lf_flags	= 0;
	lf.lf_key	= lf.lf_key;
	lf.lf_comment	= comment;
	lf.lf_comlen	= (comment) ? strlen(comment)+1 : 0;

	if (ioctl(fd, _FIOLFS, &lf) == -1) {
		perror(fn);
		close(fd);
		exitstatus = 2;
		return;
	}
	close(fd);
}
/*
 * getmntnames
 *	file names from /etc/mtab
 */
void
getmntnames()
{
	int		fnlen;
	struct filename	*fnp;
	struct filename	*fnpc;
	FILE		*mnttab;
	struct mnttab	mnt, *mntp = &mnt;

	fnpc = fnanchor;

	if ((mnttab = fopen(MNTTAB, "r")) == NULL) {
		fprintf(stderr, "Can't open %s\n", MNTTAB);
		perror(MNTTAB);
		exit(32);
	}
	while ((getmntent(mnttab, mntp)) == 0) {
		if (strcmp(mntp->mnt_fstype, MNTTYPE_UFS) != 0)
			continue;
		fnlen = strlen(mntp->mnt_mountp) + 1;
		fnp = (struct filename *)malloc(sizeof (struct filename));
		fnp->fn_name = malloc((uint_t)fnlen);
		strcpy(fnp->fn_name, mntp->mnt_mountp);
		fnp->fn_next = NULL;
		if (fnpc)
			fnpc->fn_next = fnp;
		else
			fnanchor = fnp;
		fnpc = fnp;
	}
	fclose(mnttab);
}
/*
 * getcmdnames
 *	file names from command line
 */
void
getcmdnames(int argc, char **argv, int i)
{
	struct filename	*fnp;
	struct filename	*fnpc;

	for (fnpc = fnanchor; i < argc; ++i) {
		fnp = (struct filename *)malloc(sizeof (struct filename));
		fnp->fn_name = *(argv+i);
		fnp->fn_next = NULL;
		if (fnpc)
			fnpc->fn_next = fnp;
		else
			fnanchor = fnp;
		fnpc = fnp;
	}
}