/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)fsck.c	1.25	93/12/16 SMI"	/* SVr4.0 1.18	*/

#include	<stdio.h>
#include	<errno.h>
#include	<limits.h>
#include	<fcntl.h>
#include	<string.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/wait.h>
#include	<sys/vfstab.h>
#include	<sys/mntent.h>
#include	<locale.h>
#include	<libintl.h>

#define	ARGV_MAX	16
#define	FSTYPE_MAX	8
#define	VFS_PATH	"/usr/lib/fs"
#define	VFS_PATH2	"/etc/fs"

#define	CHECK(xx, yy)\
	if (xx == (yy)-1) {\
		fprintf(stderr, gettext("%s: too many arguments\n"), myname); \
		usage(); \
	}
#define	OPTION(flag)\
		options++; \
		nargv[nargc++] = flag; \
		CHECK(nargc, ARGV_MAX); \
		break
#define	OPTARG(flag)\
		nargv[nargc++] = flag; \
		CHECK(nargc, ARGV_MAX); \
		if (optarg) {\
			nargv[nargc++] = optarg; \
			CHECK(nargc, ARGV_MAX); \
		}\
		break


int	nrun, ndisks;
int	maxrun = 8;	/* should be based on the machine resources */

extern char	*optarg;
extern int	optind;
extern char	*default_fstype();

int	nargc = 2;
int	options = 0;
int	mnt_passno = 0;
int	exitstat = 0;
char	*nargv[ARGV_MAX];
char	*myname, *fstype;
char	*malloc();
char	vfstab[] = VFSTAB;
char	pflg = 0, Vflg = 0;

/*
 * Keep an idea of the last device arg type as a hint to the
 * type of the next arg. In the case of mountall, it's very likely
 * to be the same type and the next entry in the file. This should
 * help speed vfstab lookups.
 */
enum dev_arg_t { UNKNOWN, SPECIAL, FSCKDEV, MOUNTPT };
enum dev_arg_t arg_hint = UNKNOWN;

static struct devlist {
	char *name;
	char *fsname;
	pid_t pid;
	struct devlist *nxt;
} *newdev(), *getdev();

/*
 * private copy vfstab functions
 */
static struct vfstab	vfsave = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};

static int
vfdup(struct vfstab *vp)
{
	if (vfsave.vfs_special != NULL) {
		free(vfsave.vfs_special);
		vfsave.vfs_special = NULL;
	}
	if ((vp->vfs_special != NULL) &&
	    ((vfsave.vfs_special = strdup(vp->vfs_special)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	if (vfsave.vfs_fsckdev != NULL) {
		free(vfsave.vfs_fsckdev);
		vfsave.vfs_fsckdev = NULL;
	}
	if ((vp->vfs_fsckdev != NULL) &&
	    ((vfsave.vfs_fsckdev = strdup(vp->vfs_fsckdev)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	if (vfsave.vfs_mountp != NULL) {
		free(vfsave.vfs_mountp);
		vfsave.vfs_mountp = NULL;
	}
	if ((vp->vfs_mountp != NULL) &&
	    ((vfsave.vfs_mountp = strdup(vp->vfs_mountp)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	if (vfsave.vfs_fstype != NULL) {
		free(vfsave.vfs_fstype);
		vfsave.vfs_fstype = NULL;
	}
	if ((vp->vfs_fstype != NULL) &&
	    ((vfsave.vfs_fstype = strdup(vp->vfs_fstype)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	if (vfsave.vfs_fsckpass != NULL) {
		free(vfsave.vfs_fsckpass);
		vfsave.vfs_fsckpass = NULL;
	}
	if ((vp->vfs_fsckpass != NULL) &&
	    ((vfsave.vfs_fsckpass = strdup(vp->vfs_fsckpass)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	if (vfsave.vfs_automnt != NULL) {
		free(vfsave.vfs_automnt);
		vfsave.vfs_automnt = NULL;
	}
	if ((vp->vfs_automnt != NULL) &&
	    ((vfsave.vfs_automnt = strdup(vp->vfs_automnt)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	if (vfsave.vfs_mntopts != NULL) {
		free(vfsave.vfs_mntopts);
		vfsave.vfs_mntopts = NULL;
	}
	if ((vp->vfs_mntopts != NULL) &&
	    ((vfsave.vfs_mntopts = strdup(vp->vfs_mntopts)) == NULL)) {
		perror(myname);
		return (4);	/* XXX */
	}

	*vp = vfsave;
	return (0);
}

static int
mygetvfsent(FILE *fp, struct vfstab *vp)
{
	int	error;

	if ((error = getvfsent(fp, vp)) != 0)
		return (error);
	return (vfdup(vp));
}

static int
mygetvfsany(FILE *fp, struct vfstab *vp, struct vfstab *vrefp)
{
	int	error;

	if ((error = getvfsany(fp, vp, vrefp)) != 0)
		return (error);
	return (vfdup(vp));
}

main(argc, argv)
	int	argc;
	char	*argv[];
{
	int	cc, ret, other_than_ufs = 0;
	int	questflg = 0, Fflg = 0, Vflg = 0, sanity = 0;
	char	*subopt;
	FILE	*fd = NULL;
	struct vfstab	vget, vref;
	int preencnt = 0;
	struct devlist *dp, *devs = NULL;
	int status;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	myname = strrchr(argv[0], '/');
	if (myname)
		myname++;
	else
		myname = argv[0];

	while ((cc = getopt(argc, argv, "?F:mnNo:VyY")) != -1) {
		switch (cc) {
		case '?':
			questflg++;
			if (questflg > 1)
				usage();
			nargv[nargc++] = "-?";
			CHECK(nargc, ARGV_MAX);
			break;
		case 'F':
			Fflg++;
			/* check for more that one -F */
			if (Fflg > 1) {
				fprintf(stderr,
				gettext("%s: more than one fstype specified\n"),
					myname);
				usage();
			}
			fstype = optarg;
			if (strlen(fstype) > (size_t) FSTYPE_MAX) {
				fprintf(stderr,
			gettext("%s: Fstype %s exceeds %d characters\n"),
					myname, fstype, FSTYPE_MAX);
						exit(1);
			}
			break;
		case 'm':
			sanity++;
			OPTION("-m");
		case 'n':
			OPTION("-n");
		case 'N':
			OPTION("-N");
		case 'o':
			subopt = optarg;
			while (*subopt != '\0') {
				if (*subopt == 'p') {
					pflg++;
					break;
				}
				subopt++;
			}
			OPTARG("-o");
		case 'V':
			Vflg++;
			if (Vflg > 1)
				usage();
			break;
		case 'y':
			OPTION("-y");
		case 'Y':
			OPTION("-Y");
		}
		optarg = NULL;
	}

	/* copy '--' to specific */
	if (strcmp(argv[optind-1], "--") == 0) {
		nargv[nargc++] = argv[optind-1];
		CHECK(nargc, ARGV_MAX);
	}

	if (questflg) {
		if (Fflg) {
			nargc = 2;
			nargv[nargc++] = "-?";
			nargv[nargc] = NULL;
			do_exec(fstype, nargv);
		}
		usage();
	}

	if ((sanity) && (options > 1)) {
		usage();
	}

	if (optind == argc) {	/* no device name is specified */
		if (fstype == NULL) {
			if ((argc > 2) && (sanity)) {
				usage();
			}
		}
		/*
		 * Try to check UFS filesystems first, then check other
		 * filesystems if they exist.
		 * Note: Parallel checking is only available in UFS for now.
		 */
		if (fstype == NULL || strcmp(fstype, MNTTYPE_UFS) == 0) {
			if ((fd = fopen(vfstab, "r")) == NULL) {
				fprintf(stderr,
					gettext("%s: cannot open vfstab\n"),
					myname);
				exit(1);
			}
			while ((ret = mygetvfsent(fd, &vget)) == 0) {
				if (strcmp(vget.vfs_fstype, MNTTYPE_UFS) &&
				    numbers(vget.vfs_fsckpass)) {
					other_than_ufs ++;
					continue;
				}
				if (numbers(vget.vfs_fsckpass))
					mnt_passno = atoi(vget.vfs_fsckpass);
				else
					continue;
				if (mnt_passno < 1)
					continue;
				if (pflg == 0 || mnt_passno == 1) {
					status = execute(vget.vfs_fsckdev,
					    MNTTYPE_UFS, Vflg, fd);
					/* return the highest exit code */
					if (status > exitstat)
						exitstat = status;
				} else if (preen_addev(vget.vfs_fsckdev) == 0) {
					preencnt++;
					dp = newdev(&vget);
					dp->nxt = devs;
					devs = dp;
				} else {
					/*
					 * preening setup failed, so
					 * execute serially here...
					 */
					fprintf(stderr,
					gettext("%s: preen_addev error\n"),
						myname);
					status = execute(vget.vfs_fsckdev,
					    MNTTYPE_UFS, Vflg, fd);
					/* return the highest exit code */
					if (status > exitstat)
						exitstat = status;
				}
			}
			fclose(fd);
			if (ret > 0)
				vfserror(ret);
			if (pflg && exitstat == 0) {
				fsck_dopreen(&devs, preencnt);
			}
		}
		else
			other_than_ufs = 1;

		if (other_than_ufs) {
			if ((fd = fopen(vfstab, "r")) == NULL) {
				fprintf(stderr,
					gettext("%s: cannot open vfstab\n"),
					myname);
				exit(1);
			}
			while ((ret = mygetvfsent(fd, &vget)) == 0)
				if (strcmp(vget.vfs_fstype, MNTTYPE_UFS) &&
				    numbers(vget.vfs_fsckpass) &&
				    vget.vfs_fsckdev != NULL &&
				    (fstype == NULL ||
				    strcmp(fstype, vget.vfs_fstype) == 0)) {
					status = execute(vget.vfs_fsckdev,
					    vget.vfs_fstype, Vflg, fd);
					/* return the highest exit code */
					if (status > exitstat)
						exitstat = status;
				}
			fclose(fd);
			if (ret > 0)
				vfserror(ret);
		}

	} else {	/* device name is specified */
		if (fstype == NULL && (fd = fopen(vfstab, "r")) == NULL) {
			fprintf(stderr, gettext("%s: cannot open vfstab\n"),
				myname);
			exit(1);
		}
		while (optind < argc) {
			/*
			 * If "-F FStype" is specified, use that fs type.
			 * Otherwise, determine the fs type from /etc/vfstab
			 * if the entry exists.  Otherwise, determine the
			 * local or remote fs type from /etc/default/df
			 * or /etc/dfs/fstypes respectively.
			 */
			if (fstype == NULL) {
				if ((argc > 3) && (sanity)) {
					usage();
				}
				/* must check for both special && raw devices */
				vfsnull(&vref);

				/*
				 * Find the vfstab entry for this device.
				 * arg_hint tells us what to try to match,
				 * based on the type of the last arg. If
				 * arg_hint equals UNKNOWN, then we're not
				 * sure of the type and need to fallthrough
				 * all 3 possibilities for vfstab lookup.
				 * Try it as a mountpt first, since that's
				 * what mountall gives us.
				 */
try_again:
				switch (arg_hint) {
				case UNKNOWN:
					/* FALLTHROUGH */

				case MOUNTPT:
					vref.vfs_mountp = argv[optind];
					if ((ret = mygetvfsany(fd, &vget,
						&vref)) == -1 ||
						vget.vfs_fstype == NULL) {

						vref.vfs_mountp = NULL;
						rewind(fd);

						if (arg_hint == MOUNTPT) {
							arg_hint = UNKNOWN;
							goto try_again;
						}
						/* FALLTHROUGH */
					} else {
						/* Found it */
						if (vget.vfs_fsckdev != NULL) {
							argv[optind] =
							vget.vfs_fsckdev;
						}
						arg_hint = MOUNTPT;
						break;
					}

				case FSCKDEV:
					vref.vfs_fsckdev = argv[optind];
					if ((ret = mygetvfsany(fd, &vget,
						&vref)) == -1 ||
						vget.vfs_fstype == NULL) {

						vref.vfs_fsckdev = NULL;
						rewind(fd);

						if (arg_hint == FSCKDEV) {
							arg_hint = UNKNOWN;
							goto try_again;
						}
						/* FALLTHROUGH */
					} else {
						/* Found it */
						arg_hint = FSCKDEV;
						break;
					}

				case SPECIAL:
					vref.vfs_special = argv[optind];
					if ((ret = mygetvfsany(fd, &vget,
						&vref)) == -1 ||
						vget.vfs_fstype == NULL) {

						vref.vfs_special = NULL;
						rewind(fd);

						if (arg_hint == SPECIAL) {
							arg_hint = UNKNOWN;
							goto try_again;
						}
						/* FALLTHROUGH */
					} else {
						/* Found it */
						arg_hint = SPECIAL;
						break;
					}
				}

				if (ret == 0 && vget.vfs_fstype) {
					if ((pflg) && (strcmp(vget.vfs_fstype,
					    MNTTYPE_UFS) == 0) && (preen_addev(
					    vget.vfs_fsckdev) == 0)) {
						preencnt++;
						dp = newdev(&vget);
						dp->nxt = devs;
						devs = dp;
					} else {
						status = execute(argv[optind],
						    vget.vfs_fstype, Vflg, fd);
						if (status > exitstat)
							exitstat = status;
					}
				} else if (ret == -1 ||
				    vget.vfs_fstype == NULL) {
					fstype =
					    default_fstype(argv[optind]);
					status = execute(argv[optind], fstype,
					    Vflg, fd);
					/* return the highest exit code */
					if (status > exitstat)
						exitstat = status;
				} else
					vfserror(ret);
			} else {
				status = execute(argv[optind], fstype,
				    Vflg, NULL);
				/* return the highest exit code */
				if (status > exitstat)
					exitstat = status;
			}
			optind++;
		}
		if (fd != NULL)
			fclose(fd);
		if ((pflg) && (exitstat == 0)) {
			fsck_dopreen(&devs, preencnt);
		}
	}
	exit(exitstat);
}

static
fsck_dopreen(devp, ndevs)
	struct devlist **devp;
	int ndevs;
{
	char name[1024];
	int rc;
	register int i;
	struct devlist *bl, *bdp;
	struct devlist *badlist;

	bl = badlist = NULL;
	while (ndevs > 0) {
		if (nrun > maxrun)
			waiter(&bl, &badlist);
		rc = preen_getdev(name);
		switch (rc) {
		case 0:
			break;
		case 1:
			bdp = getdev(name, devp);
			if (bdp == NULL) {
				fprintf(stderr,
					gettext("%s: unknown dev: `%s'\n"),
					myname, name);
				exit(1);
			}
			bdp->nxt = bl;
			bl = bdp;
			startdisk(bdp);
			ndevs--;
			break;
		case 2:
			waiter(&bl, &badlist);
			break;
		default:
			fprintf(stderr,
			gettext("%s: bad return `%d' from preen_getdev\n"),
				myname, rc);
			break;
		}
	}
	while (bl != NULL) {
		waiter(&bl, &badlist);
	}

	if (badlist != NULL)
		print_badlist(badlist);
}

static
startdisk(dp)
	struct devlist *dp;
{
	pid_t pid;

	nrun++;
	if ((pid = fork()) == -1) {
		perror("fork");
		exit(1);
	} else if (pid == 0) {
		exitstat = execute(dp->name, MNTTYPE_UFS, Vflg, NULL);
		exit(exitstat);
	} else {
		dp->pid = pid;
	}
}

static
waiter(blp, badlist)
	struct devlist **blp;
	struct devlist **badlist;
{
	pid_t curpid;
	int status;
	register struct devlist *bdp, *pbdp;

	curpid = wait(&status);
	if (curpid == -1) {
		perror("wait");
		exit(1);
	}

	for (pbdp = NULL, bdp = *blp; bdp != NULL; pbdp = bdp, bdp = bdp->nxt) {
		if (bdp->pid == curpid) {
			break;
		}
	}
	if (bdp == NULL)
		return;
	nrun--;

	if (pbdp)
		pbdp->nxt = bdp->nxt;
	else
		*blp = bdp->nxt;
	preen_releasedev(bdp->name);

	if (WTERMSIG(status)) {
		printf(gettext("%s (%s): EXITED WITH SIGNAL %d\n"),
			bdp->name, bdp->fsname, WTERMSIG(status));
		status = status&0377 | 8<<8;
	}
	if (WHIBYTE(status) != 0) {
		if (WHIBYTE(status) > exitstat)
			exitstat = WHIBYTE(status);
		while (*badlist != NULL)
			badlist = &(*badlist)->nxt;
		*badlist = bdp;
		bdp->nxt = NULL;
	}
}

static
print_badlist(lp)
	struct devlist *lp;
{
	int x, len;

	printf(
gettext("\nTHE FOLLOWING FILE SYSTEM(S) HAD AN UNEXPECTED INCONSISTENCY:"));
	for (x = 3; lp != NULL; lp = lp->nxt) {
		len = strlen(lp->name) + strlen(lp->fsname) + 5;
		x += len;
		if (x >= 80) {
			printf("\n   ");
			x = len + 3;
		} else {
			printf(" ");
		}
		printf("%s (%s)%s", lp->name, lp->fsname,
		    lp->nxt ? "," : "\n");
	}
}

/*
 * allocate and initialize a `devlist' structure
 */
static
struct devlist *
newdev(vfsp)
	struct vfstab *vfsp;
{
	struct devlist *dp;
	extern char *strdup();

	dp = (struct devlist *)malloc(sizeof (struct devlist));
	if (dp == NULL) {
		fprintf(stderr, gettext("%s: out of memory\n"), myname);
		exit(1);
	}
	dp->name = strdup(vfsp->vfs_fsckdev);
	dp->fsname = strdup(vfsp->vfs_mountp);
	if (dp->name == NULL || dp->fsname == NULL) {
		fprintf(stderr, gettext("%s: out of memory\n"), myname);
		exit(1);
	}
	return (dp);
}

/*
 * locate the devlist structure in the given list that matches `name'.
 * If found, the structure is removed from the list, and a pointer to
 * it is returned.  If not, NULL is returned.
 */
static
struct devlist *
getdev(name, list)
	char *name;
	struct devlist **list;
{
	register struct devlist *p, *lp;

	for (lp = NULL, p = *list; p != NULL; lp = p, p = p->nxt) {
		if (strcmp(p->name, name) == 0)
			break;
	}

	if (p != NULL) {
		if (lp != NULL)
			lp->nxt = p->nxt;
		else
			*list = p->nxt;
	}
	return (p);
}

/* see if all numbers */
numbers(yp)
	char	*yp;
{
	if (yp == NULL)
		return (0);
	while ('0' <= *yp && *yp <= '9')
		yp++;
	if (*yp)
		return (0);
	return (1);
}

execute(fsckdev, fstype, Vflg, fd)
	char	*fsckdev, *fstype;
	int	Vflg;
	FILE	*fd;
{
	int	st;
	pid_t	fk;
	char	full_path[PATH_MAX];
	char	*vfs_path = VFS_PATH;
	int	status = 0;

	nargv[nargc] = fsckdev;

	if (Vflg) {
		prnt_cmd(stdout, fstype);
		return (0);
	}

	if (fd)
		fcntl(fileno(fd), F_SETFD, 1);	/* close on exec */

	if ((fk = fork()) == (pid_t)-1) {
		fprintf(stderr,
			gettext("%s: cannot fork.  Try again later\n"),
			myname);
		perror(myname);
		exit(1);
	}

	if (fk == 0) {
		/* Try to exec the fstype dependent portion of the fsck. */
		do_exec(fstype, nargv);
	} else {
		/* parent waits for child */
		if (wait(&st) == (pid_t)-1) {
			fprintf(stderr, gettext("%s: bad wait\n"), myname);
			perror(myname);
			exit(1);
		}

		if ((st & 0xff) == 0x7f) {
			fprintf(stderr,
				gettext("%s: warning: the following command"
				" (process %d) was stopped by signal %d\n"),
				myname, fk, (st >> 8) & 0xff);
			prnt_cmd(stderr, fstype);
			status = ((st >> 8) & 0xff) | 0x80;
		} else if (st & 0xff) {
			if (st & 0x80)
				fprintf(stderr,
				gettext("%s: warning: the following command"
				" (process %d) was terminated by signal %d"
				" and dumped core\n"),
				myname, fk, st & 0x7f);
			else
				fprintf(stderr,
				gettext("%s: warning: the following command"
				" (process %d) was terminated by signal %d\n"),
				myname, fk, st & 0x7f);

			prnt_cmd(stderr, fstype);
			status = ((st & 0xff) | 0x80);
		} else if (st & 0xff00)
			status = (st >> 8) & 0xff;
	}

	return (status);
}

do_exec(fstype, nargv)
	char	*fstype, *nargv[];
{
	char	full_path[PATH_MAX];
	char	*vfs_path = VFS_PATH;

	if (strlen(fstype) > (size_t) FSTYPE_MAX) {
		fprintf(stderr,
			gettext("%s: Fstype %s exceeds %d characters\n"),
			myname, fstype, FSTYPE_MAX);
		exit(1);
	}
	/* build the full pathname of the fstype dependent command. */
	sprintf(full_path, "%s/%s/%s", vfs_path, fstype, myname);

	/* set the new argv[0] to the filename */
	nargv[1] = myname;
	/* Try to exec the fstype dependent portion of the fsck. */
	execv(full_path, &nargv[1]);
	if (errno == EACCES) {
		fprintf(stderr,
			gettext("%s: cannot execute %s - permission denied\n"),
			myname, full_path);
	}
	if (errno == ENOEXEC) {
		nargv[0] = "sh";
		nargv[1] = full_path;
		execv("/sbin/sh", &nargv[0]);
	}
	/* second path to try */
	vfs_path = VFS_PATH2;
	/* build the full pathname of the fstype dependent command. */
	sprintf(full_path, "%s/%s/%s", vfs_path, fstype, myname);

	/* set the new argv[0] to the filename */
	nargv[1] = myname;
	/* Try to exec the second fstype dependent portion of the fsck. */
	execv(full_path, &nargv[1]);
	if (errno == EACCES) {
		fprintf(stderr,
			gettext("%s: cannot execute %s - permission denied\n"),
			myname, full_path);
		exit(1);
	}
	if (errno == ENOEXEC) {
		nargv[0] = "sh";
		nargv[1] = full_path;
		execv("/sbin/sh", &nargv[0]);
	}
	fprintf(stderr,
		gettext("%s: operation not applicable to FSType %s\n"),
		myname, fstype);
	exit(1);
}

prnt_cmd(fd, fstype)
	FILE	*fd;
	char	*fstype;
{
	char	**argp;

	fprintf(fd, "%s -F %s", myname, fstype);
	for (argp = &nargv[2]; *argp; argp++)
		fprintf(fd, " %s", *argp);
	fprintf(fd, "\n");
}

vfserror(flag)
	int	flag;
{
	switch (flag) {
	case VFS_TOOLONG:
		fprintf(stderr,
			gettext("%s: line in vfstab exceeds %d characters\n"),
			myname, VFS_LINE_MAX-2);
		break;
	case VFS_TOOFEW:
		fprintf(stderr,
			gettext("%s: line in vfstab has too few entries\n"),
			myname);
		break;
	case VFS_TOOMANY:
		fprintf(stderr,
			gettext("%s: line in vfstab has too many entries\n"),
			myname);
		break;
	}
	exit(1);
}

int opterr = 1, optind = 1, optopt = 0;
char *optarg = 0;

int
getopt(int argc, char * const *argv, const char *opts)
{
	static int sp = 1;
	register int c;
	register char *cp;

	if (sp == 1)
		if (optind >= argc ||
		    argv[optind][0] != '-' || argv[optind][1] == '\0')
			return (-1);
		else if (strcmp(argv[optind], "--") == 0) {
			optind++;
			return (-1);
		}
	optopt = c = argv[optind][sp];
	if (c == ':' || (cp = strchr(opts, c)) == 0) {
		if (opterr)
			fprintf(stderr,
				gettext("%s: illegal option -- %c\n"),
				*argv, c);
		if (argv[optind][++sp] == '\0') {
			optind++;
			sp = 1;
		}
		return ('?');
	}
	if (*++cp == ':') {
		if (argv[optind][sp+1] != '\0')
			optarg = &argv[optind++][sp+1];
		else if (++optind >= argc) {
			if (opterr)
				fprintf(stderr,
		gettext("%s: option requires an argument -- %c\n"), *argv, c);
			sp = 1;
			return ('?');
		} else
			optarg = argv[optind++];
		sp = 1;
	} else if (*cp == ';') {
		if (argv[optind][++sp] != '\0')
			if (isoptarg(c, &argv[optind][sp])) {
				optarg = &argv[optind++][sp];
				sp = 1;
			} else
				optarg = NULL;
		else {
			sp = 1;
			if (++optind >= argc || !isoptarg(c, &argv[optind][0]))
				optarg = NULL;
			else
				optarg = argv[optind++];
		}
	} else {
		if (argv[optind][++sp] == '\0') {
			sp = 1;
			optind++;
		}
		optarg = NULL;
	}
	return (c);
}

isoptarg(cc, arg)
	int	cc;
	char	*arg;
{
	if (cc == 's' || cc == 'S') {
		while (*arg >= '0' && *arg <= '9')
			arg++;
		if (*arg++ != ':')
			return (0);
		while (*arg >= '0' && *arg <= '9')
			arg++;
		if (*arg)
			return (0);
		return (1);
	}
	return (0);
}

usage()
{
	fprintf(stderr,
		gettext("Usage:\n%s [-F FSType] [-V] [-m] [special ...]\n"
			"%s [-F FSType] [-V] [-y|Y|n|N]"
			" [-o specific_options] [special ...]\n"),
			myname, myname);

	exit(1);
}
