#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/statfs.h>
#include <mntent.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/param.h>
#include <dirent.h>

#define AUTOFS		"autofs"
#define MOUNT_TABLE	"/etc/mtab"

#define S_MAGIC_NFS 		0x6969
#define S_MAGIC_AUTOFS		0x0187

#define	LINESZ 1024

struct dir_list {
	char *dir;			/* stat(dir/.auto_test) */
	int error;			/* error = 0 if expect success */
					/* error != 0 if expect error */
	struct dir_list *next;
};

extern int errno;

static int cmpfstype(struct statfs *buf, const char *fsname)
{
	if ((!strcmp("autofs", fsname) && buf->f_type == S_MAGIC_AUTOFS) ||
	    (!strcmp("nfs",    fsname) && buf->f_type == S_MAGIC_NFS))
		return 0;
	return 1;
}

static const char *getfsname(struct statfs *buf)
{
	if (buf->f_type == S_MAGIC_AUTOFS)
		return "autofs";
	if (buf->f_type == S_MAGIC_NFS)
		return "nfs";
	return "UNKNOWNFSTYPE";
}

static int read_input(char *data_file, struct dir_list **listpp)
{
	FILE *fp;
	char line[LINESZ];
	char *lp;
	struct dir_list *listp, *prev;
	int len = 0;

	*listpp = NULL;
	if ((fp = fopen(data_file, "r")) == NULL) {
		fprintf(stderr, "couldn't open %s for reading.\n", data_file);
		return (1);
	}

	while (!feof(fp)) {
		/*
		 * read data file one line at a time
		 */
		if (fgets(line, LINESZ, fp) == NULL) {
			return (0);	/* EOF */
		}
		len = strlen(line);
		if (len <= 0) {
			continue;
		}
		lp = &line[len - 1];

trim:
		/* trim trailing white space */
		while (lp >= line && isspace(*(char *)lp))
			*lp-- = '\0';
		if (lp < line) {			/* empty line */
			continue;
		}

		/*
		 * Ignore comments. Comments start with '#'
		 * which must be preceded by a whitespace, unless
		 * if '#' is the first character in the line.
		 */
		lp = line;
		while ((lp = strchr(lp, '#'))) {
			if (lp == line || isspace(*(lp-1))) {
				*lp-- = '\0';
				goto trim;
			}
			lp++;
		}

		listp = (struct dir_list *) malloc(sizeof (*listp));
		if (listp == NULL) {
			fprintf(stderr, "read_input: malloc failed\n");
			return (ENOMEM);
		}
		(void) memset((char *)listp, 0, sizeof (*listp));

		/*
		 * get first string from line which corresponds to
		 * the path
		 */
		lp = line;
		while (!isspace(*lp) && (lp < line + len))
			lp++;
		/*
		 * at end of path string
		 */
		*(lp++) = '\0';		/* now line contains only path string */

		/*
		 * get second and last component, the expected result
		 */
		while (isspace(*lp) && (lp < line + len))
			lp++;
		if (lp < line + len) {
			listp->error = atoi(lp);
		} else {
			fprintf(stderr,
			    "'%s' not in expected format - ignoring\n", line);
			continue;
		}
		listp->error = atoi(lp);
		listp->dir = (char *) malloc(len - strlen(lp));
		if (listp->dir == NULL) {
			fprintf(stderr, "read_input: malloc failed\n");
			return (ENOMEM);
		}
		(void) strcpy(listp->dir, line);
		/*
		 * Append element to list
		 */
		if (*listpp == NULL)
			*listpp = listp;	/* first element */
		else
			prev->next = listp;	/* append */
		prev = listp;
	}
	return (0);
}

/*
 * returns 0 if MOUNT_TABLE was read successfully, errno otherwise.
 * if MOUNT_TABLE ok, then *times contains the number of occurances
 * of mntpnt in the MOUNT_TABLE
 */
static int search_mnttab(char *mntpnt, int *times)
{
	FILE *fp;
	struct mntent *mnt;
	struct flock fl = {F_RDLCK, SEEK_SET, 0, 0, 0};
	char tmp[MAXPATHLEN];
	int error = 0;

	*times = 0;
	if ((fp = fopen(MOUNT_TABLE, "r")) == NULL) {
		error = errno;
		fprintf(stderr, "search_mnttab: could not open %s error=%d\n",
		    MOUNT_TABLE, error);
		return (error);
	}
	rewind(fp);

	if ((fcntl(fileno(fp), F_SETLKW, &fl)) < 0) {
		error = errno;
		fprintf(stderr, "search_mnttab: could not lock %s\n",
		    MOUNT_TABLE);
		fclose(fp);
		return (error);
	}

	while ((mnt = getmntent(fp)) != NULL) {
		if (error)
			break;
		(void) sprintf(tmp, "%s", mntpnt);
		if ((strcmp(mnt->mnt_dir, tmp) == 0) &&
		    (strcmp(mnt->mnt_type, AUTOFS) != 0)) {
			(*times)++;
		}
	}

	fclose(fp);
	return (error == -1 ? 0 : error);
}

/*
 * returns 0 if opendir returns the expected result; errno otherwise
 * check MNTTAB for mounted path if 'check' is non-zero
 * checks that the file system mounted is 'fstype' if 'fstype' is not NULL.
 *
 */
static int do_opendir(struct dir_list *p, int check, char *fstype)
{
	DIR *dirp;
	struct statfs buf;
	int error = 0, times;

	fprintf(stdout, "\t%s", p->dir);
	if (p->error)
		fprintf(stdout, "\texpect failure\n");
	else
		fprintf(stdout, "\texpect success\n");

	if ((dirp = opendir(p->dir)) == NULL) {
		if (!p->error) {
			/*
			 * expected success but failed
			 */
			error = errno;
			fprintf(stdout,
			    "\topendir(%s) unexpected error %d\n",
			    p->dir, error);
		}
	} else {
		if (closedir(dirp)) {
			fprintf(stdout,
			    "closedir(%s) failed - errno=%d - ignored\n",
			    p->dir, errno);
		}

		if (p->error) {
			/*
			 * succeeded, but expected failure
			 */
			error = 1;
			fprintf(stdout,
			    "\topendir(%s) expected error but succeeded.\n",
			    p->dir);
			goto done;
		}

		/*
		 * sucessful opendir(). Check proper filesystem
		 * has been mounted?
		 */
		if (check) {
			if ((error = search_mnttab(p->dir, &times))) {
				fprintf(stderr, "search_mnttab failed\n");
				error = 1;
				goto done;
			}
			if (times != 1) {
				error = 1;
				fprintf(stdout,
				    "error: %s found %d times in %s expected one.\n",
				    p->dir, times, MOUNT_TABLE);
				goto done;
			}
		}

		if (fstype) {
			/*
			 * check if fstype matches
			 */
			if (statfs(p->dir, &buf) == -1) {
				error = errno;
				fprintf(stderr,
				    "opendir: statfs failed, errno=%d\n",
				    error);
				goto done;
			}
			if (cmpfstype(&buf, fstype))
			{
				/*
				 * filesystem types don't match
				 */
				error = 1;
				fprintf(stderr,
				    "error:%s - fstype=%s (%x) expected %s\n",
				    p->dir, getfsname(&buf), buf.f_type, fstype);
			}
		}
	}
done:
	if (error)
		fprintf(stdout, "\t%s\t- NOT OK -\n", p->dir);
	else
		fprintf(stdout, "\t%s\t- OK -\n", p->dir);

	return (error);
}

int main (int argc, char **argv)
{
	struct dir_list *listp, *p;
	int error;
	int fail = 0, attempt = 0;
	int check;

	if (argc != 4) {
		fprintf(stderr, "usage: %s <data_file> <fstype> <0|1>\n",
		    argv[0]);
		fail++;
		return (1);
	}

	check = atoi(argv[3]);		/* check MOUNT_TABLE? */

	if ((error = read_input(argv[1], &listp))) {
		fprintf(stderr, "error reading data file %s: %d\n",
		    argv[1], error);
		fail++;
		goto done;
	}

	for (p = listp; p; p = p->next) {
		attempt++;
		if (do_opendir(p, check, argv[2]) != 0)
			fail++;
	}

done:
	if (!fail) {
		fprintf(stdout, "%s:\tSUCCEEDED\n", argv[0]);
	} else {
		fprintf(stdout, "%s:\tFAILED\n", argv[0]);
		fprintf(stdout, "\tFailed: %d of %d\n", fail, attempt);
	}

	return (fail ? 1 : 0);
}
