/*
 * util.c - special built-in shell utilities for etsh
 */
/*-
 * Copyright (c) 2004-2019
 *	Jeffrey Allen Neitzel <jan (at) etsh (dot) nl>.
 *	All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JEFFREY ALLEN NEITZEL ``AS IS'', AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL JEFFREY ALLEN NEITZEL BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *	@(#)$Id: util.c,v 1.10 2019/03/18 00:56:29 jneitzel Exp $
 */
/*
 *	Derived from:
 *		- osh-20080629
 *			fd2.c	(r404 2008-05-09 16:52:15Z jneitzel)
 *			goto.c	(r465 2008-06-25 22:11:58Z jneitzel)
 *			if.c	(r465 2008-06-25 22:11:58Z jneitzel)
 */
/*-
 * Copyright (C) Caldera International Inc.  2001-2002.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code and documentation must retain the above
 *    copyright notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed or owned by Caldera
 *      International, Inc.
 * 4. Neither the name of Caldera International, Inc. nor the names of other
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
 * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT,
 * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * The following license covers the documentation for the
 * sbi_pwd() function:
 */
/*-
 *	$OpenBSD: pwd.1,v 1.21 2014/05/28 14:16:27 jmc Exp $
 *	$NetBSD: pwd.1,v 1.10 1995/09/07 06:47:30 jtc Exp $
 *
 *  Copyright (c) 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 *  This code is derived from software contributed to Berkeley by
 *  the Institute of Electrical and Electronics Engineers, Inc.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the University nor the names of its contributors
 *     may be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *
 *      @(#)pwd.1	8.2 (Berkeley) 4/28/95
 */

#define	ETSH_SHELL

#include "defs.h"
#include "sh_err.h"
#include "pexec.h"
#include "sh.h"
#include "strtoint.h"

#define	IS_SBI(k)	\
	((k) == SBI_ECHO || (k) == SBI_FD2 || (k) == SBI_GOTO || (k) == SBI_IF || (k) == SBI_PWD)

static	int	sbi_echo(int, char **);
/*@maynotreturn@*/
static	int	sbi_fd2(int, char **);
/*@maynotreturn@*/
static	int	sbi_goto(int, char **);
/*@maynotreturn@*/
static	int	sbi_if(int, char **);
static	int	sbi_pwd(int, char **);

/*
 * Execute the shell utility specified by key w/ the argument
 * count ac and the argument vector pointed to by av.  Return
 * status s of the last call in the chain on success.  Do not
 * return on invalid-utility error.
 */
int
uexec(enum sbikey key, int ac, char **av)
{
	int (*util)(int, char **);
	int r;
	static int cnt, cnt1, s;

	if (cnt == 0)
		setmyerrexit(&ut_errexit);

	switch (key) {
	case SBI_ECHO:	util = &sbi_echo;	break;
	case SBI_FD2:	util = &sbi_fd2;	break;
	case SBI_GOTO:	util = &sbi_goto;	break;
	case SBI_IF:	util = &sbi_if;		break;
	case SBI_PWD:	util = &sbi_pwd;	break;
	default:
		sh_err(FC_ERR, FMT3S, getmyname(), __func__, "Invalid utility");
		/*NOTREACHED*/
		return FC_ERR;
	}

	cnt1 = cnt++;

	r = (*util)(ac, av);

	if (cnt-- > cnt1)
		s = r;

#ifdef	DEBUG_PROC
	if (cnt == 0)
		fd_print(FD2, "%s: return %d;\n", __func__, s);
#endif

	return s;
}

/*
 * NAME
 *	echo - write arguments to standard output
 *
 * SYNOPSIS
 *	echo [-n] [string ...]
 *
 * DESCRIPTION
 *	Echo writes its string arguments (if any) separated by
 *	blanks and terminated by a newline to the standard output.
 *	If `-n' is specified, the terminating newline is not written.
 */
static int
sbi_echo(int argc, char **argv)
{
	bool nopt;
	char **avp, **ave;

	argc--, argv++;
	if (*argv != NULL && EQUAL(*argv, "-n")) {
		argc--, argv++;
		nopt = true;
	} else
		nopt = false;

	for (avp = argv, ave = &argv[argc]; avp < ave; avp++)
		fd_print(FD1, "%s%s", *avp, (avp + 1 < ave) ? " " : "");
	if (!nopt)
		fd_print(FD1, FMT1S, "");

	return SH_TRUE;
}

/*@noreturn@*/
static	void	fd2_usage(void);

/*
 * NAME
 *	fd2 - redirect from/to file descriptor 2
 *
 * SYNOPSIS
 *	fd2 [-e] [-f file] [--] command [arg ...]
 *
 * DESCRIPTION
 *	See the fd2(1) manual page for full details.
 */
static int
sbi_fd2(int argc, char **argv)
{
	bool eopt;
	enum sbikey key;
	int efd, nfd, ofd, opt;
	char *file;

	setmyname(argv[0]);

	ofd = FD1, efd = FD2;
	eopt = false, file = NULL;
	while ((opt = getopt(argc, argv, ":ef:")) != -1)
		switch (opt) {
		case 'e':
			eopt = true;
			break;
		case 'f':
			file = optarg;
			break;
		default:
			fd2_usage();
		}
	argc -= optind;
	argv += optind;
	if (argc < 1)
		fd2_usage();

	if (file != NULL) {
		if ((nfd = open(file, O_WRONLY|O_APPEND|O_CREAT, 0666)) == -1)
			sh_err(FC_ERR, FMT3S, getmyname(), file, ERR_CREATE);
		if (dup2(nfd, efd) == -1)
			sh_err(FC_ERR, FMT2S, getmyname(), strerror(errno));
		if (eopt && dup2(efd, ofd) == -1)
			sh_err(FC_ERR, FMT2S, getmyname(), strerror(errno));
		(void)close(nfd);
	} else {
		if (eopt)
			ofd = FD2, efd = FD1;
		if (dup2(ofd, efd) == -1)
			sh_err(FC_ERR, FMT2S, getmyname(), strerror(errno));
	}

	/*
	 * Try to execute the specified command.
	 */
	key = cmd_lookup(argv[0]);
	if (IS_SBI(key))
		return uexec(key, argc, argv);

	(void)pexec_err(argv[0], argv);
	/*NOTREACHED*/
	return FC_ERR;
}

static void
fd2_usage(void)
{

	sh_err(FC_ERR, FD2_USAGE, getmyname());
}

static	off_t	offset;

static	bool	getlabel(/*@out@*/ char *, int, size_t);
static	int	xgetc(void);

/*
 * NAME
 *	goto - transfer command
 *
 * SYNOPSIS
 *	goto label [...]
 *
 * DESCRIPTION
 *	See the goto(1) manual page for full details.
 */
static int
sbi_goto(int argc, char **argv)
{
	size_t siz;
	char label[LABELMAX];

	setmyname(argv[0]);

	if (argc < 2 || *argv[1] == EOS || isatty(FD0) != 0)
		sh_err(FC_ERR, FMT2S, getmyname(), ERR_GENERIC);
	if ((siz = strlen(argv[1]) + 1) > sizeof(label))
		sh_err(FC_ERR, FMT3S, getmyname(), argv[1], ERR_LABTOOLONG);
	if (lseek(FD0, (off_t)0, SEEK_SET) == -1)
		sh_err(FC_ERR, FMT2S, getmyname(), ERR_SEEK);

	while (getlabel(label, U_CHAR(*argv[1] & 0377), siz))
		if (strcmp(label, argv[1]) == 0) {
			(void)lseek(FD0, offset, SEEK_SET);
			return SH_TRUE;
		}

	fd_print(FD2, FMT3S, getmyname(), argv[1], ERR_LABNOTFOUND);
	return SH_FALSE;
}

/*
 * Search for the first occurrence of a possible label with both
 * the same first character (fc) and the same length (siz - 1)
 * as argv[1], and copy this possible label to buf.
 * Return true  (1) if possible label found.
 * Return false (0) at end-of-file.
 */
static bool
getlabel(char *buf, int fc, size_t siz)
{
	int c;
	char *b;

	while ((c = xgetc()) != EOF) {
		/* `:' may be preceded by blanks. */
		while (c == SPC || c == TAB)
			c = xgetc();
		if (c != COLON) {
			while (c != EOL && c != EOF)
				c = xgetc();
			continue;
		}

		/* Prepare for possible label. */
		while ((c = xgetc()) == SPC || c == TAB)
			;	/* nothing   */
		if (c != fc)	/* not label */
			continue;

		/*
		 * Try to copy possible label (first word only)
		 * to buf, ignoring it if it becomes too long.
		 */
		b = buf;
		do {
			if (c == EOL || c == SPC || c == TAB || c == EOF) {
				*b = EOS;
				break;
			}
			*b = c;
			c = xgetc();
		} while (++b < &buf[siz]);

		/* Ignore any remaining characters on labelled line. */
		while (c != EOL && c != EOF)
			c = xgetc();
		if (c == EOF)
			break;

		if ((size_t)(b - buf) != siz - 1)	/* not label */
			continue;
		return true;
	}

	*buf = EOS;
	return false;
}

/*
 * If not at end-of-file, return the next character from the standard
 * input as an unsigned char converted to an int while incrementing
 * the global offset.  Otherwise, return EOF at end-of-file.
 */
static int
xgetc(void)
{
	int nc;

	offset++;
	nc = getchar();
	if (nc != EOF) {
		nc = U_CHAR(nc & 0377);
		return nc;
	}
	return EOF;
}

static	int	iac;
static	int	iap;
static	char	**iav;

/*@maynotreturn@*/
static	void	doex(bool);
static	bool	e1(void);
static	bool	e2(void);
static	bool	e3(void);
static	bool	equal(/*@null@*/ const char *, /*@null@*/ const char *);
static	bool	expr(void);
static	bool	ifaccess(/*@null@*/ const char *, int);
static	bool	ifstat1(/*@null@*/ const char *, mode_t);
static	bool	ifstat2(/*@null@*/ const char *, /*@null@*/ const char *, int);
/*@null@*/
static	char	*nxtarg(bool);

/*
 * NAME
 *	if - conditional command
 *
 * SYNOPSIS
 *	if [expression [command [arg ...]]]
 *
 * DESCRIPTION
 *	See the if(1) manual page for full details.
 */
static int
sbi_if(int argc, char **argv)
{
	bool re;		/* return value of expr() */

	setmyname(argv[0]);

	if (argc > 1) {
		iac = argc;
		iav = argv;
		iap = 1;
		re  = expr();
		if (re && iap < iac)
			doex(!FORKED);
	} else
		re = false;

	return re ? SH_TRUE : SH_FALSE;
}

/*
 * Evaluate the expression.
 * Return true (1) or false (0).
 */
static bool
expr(void)
{
	bool re;

	re = e1();
	if (equal(nxtarg(RETERR), "-o"))
		return re | expr();
	iap--;
	return re;
}

static bool
e1(void)
{
	bool re;

	re = e2();
	if (equal(nxtarg(RETERR), "-a"))
		return re & e1();
	iap--;
	return re;
}

static bool
e2(void)
{

	if (equal(nxtarg(RETERR), "!"))
		return !e3();
	iap--;
	return e3();
}

static bool
e3(void)
{
	bool re;
	pid_t cpid, tpid;
	long int ai, ci;
	int cstat, d;
	char *a, *b, *c;

	if ((a = nxtarg(RETERR)) == NULL)
		sh_err(FC_ERR, FMT3S, getmyname(), iav[iap - 2], ERR_EXPR);

	/*
	 * Deal w/ parentheses for grouping.
	 */
	if (equal(a, "(")) {
		re = expr();
		if (!equal(nxtarg(RETERR), ")"))
			sh_err(FC_ERR, FMT3S, getmyname(), a, ERR_PAREN);
		return re;
	}

	/*
	 * Execute { command [arg ...] } to obtain its exit status.
	 */
	if (equal(a, "{")) {
		if ((cpid = fork()) == -1)
			sh_err(FC_ERR, FMT2S, getmyname(), ERR_FORK);
		if (cpid == 0)
			/**** Child! ****/
			doex(FORKED);
		else {
			/**** Parent! ****/
			tpid = wait(&cstat);
			while ((a = nxtarg(RETERR)) != NULL && !equal(a, "}"))
				;	/* nothing */
			if (a == NULL)
				iap--;
			return (tpid == cpid && cstat == 0) ? true : false;
		}
	}

	/*
	 * file existence/permission tests
	 */
	if (equal(a, "-e"))
		return ifaccess(nxtarg(!RETERR), F_OK);
	if (equal(a, "-r"))
		return ifaccess(nxtarg(!RETERR), R_OK);
	if (equal(a, "-w"))
		return ifaccess(nxtarg(!RETERR), W_OK);
	if (equal(a, "-x"))
		return ifaccess(nxtarg(!RETERR), X_OK);

	/*
	 * file existence/type tests
	 */
	if (equal(a, "-d"))
		return ifstat1(nxtarg(!RETERR), S_IFDIR);
	if (equal(a, "-f"))
		return ifstat1(nxtarg(!RETERR), S_IFREG);
	if (equal(a, "-h"))
		return ifstat1(nxtarg(!RETERR), S_IFLNK);
	if (equal(a, "-s"))
		return ifstat1(nxtarg(!RETERR), F_GZ);
	if (equal(a, "-t")) {
		/* Does the descriptor refer to a terminal device? */
		if ((b = nxtarg(RETERR)) == NULL || *b == EOS) {
			sh_err(FC_ERR, FMT3S, getmyname(), a, ERR_DIGIT);
			/*NOTREACHED*/
		}
		if (*b >= '0' && *b <= '9' && *(b + 1) == EOS) {
			d = *b - '0';
			if (IS_DIGIT(d, *b))
				return isatty(d) != 0;
		}
		sh_err(FC_ERR, FMT3S, getmyname(), b, ERR_BADDIGIT);
	}

	/* This is the same as "$1" != "", for example, in the shell... */
	if (equal(a, "-n"))
		return strlen(nxtarg(!RETERR)) != 0;

	/* And this is the same as "$1" = "", for example. */
	if (equal(a, "-z"))
		return strlen(nxtarg(!RETERR)) == 0;

	/*
	 * binary comparisons
	 */
	if ((b = nxtarg(RETERR)) == NULL)
		sh_err(FC_ERR, FMT3S, getmyname(), a, ERR_OPERATOR);
	if (equal(b, "!="))
		return !equal(a, nxtarg(!RETERR));
	if (equal(b, "=="))
		return  equal(a, nxtarg(!RETERR));
	if (equal(b,  "="))
		return  equal(a, nxtarg(!RETERR));
	if (equal(b, "<"))
		return strcmp(a, nxtarg(!RETERR)) < 0;
	if (equal(b, ">"))
		return strcmp(a, nxtarg(!RETERR)) > 0;
	if (equal(b, "-ef"))
		return ifstat2(a, nxtarg(!RETERR), F_EF);
	if (equal(b, "-nt"))
		return ifstat2(a, nxtarg(!RETERR), F_NT);
	if (equal(b, "-ot"))
		return ifstat2(a, nxtarg(!RETERR), F_OT);
	if (equal(b, "-eq") || equal(b, "-ne") || equal(b, "-gt") ||
	    equal(b, "-ge") || equal(b, "-lt") || equal(b, "-le")) {
		if ((c = nxtarg(RETERR)) == NULL)
			sh_err(FC_ERR, FMT3S, getmyname(), b, ERR_INTEGER);
		if (strtoint(a, &ai) && strtoint(c, &ci)) {
			if (equal(b, "-eq"))
				return ai == ci;
			if (equal(b, "-ne"))
				return ai != ci;
			if (equal(b, "-gt"))
				return ai >  ci;
			if (equal(b, "-ge"))
				return ai >= ci;
			if (equal(b, "-lt"))
				return ai <  ci;
			if (equal(b, "-le"))
				return ai <= ci;
		} else
			sh_err(FC_ERR, NULL);
	}
	sh_err(FC_ERR, FMT3S, getmyname(), b, ERR_OPUNKNOWN);
	/*NOTREACHED*/
	return false;
}

static void
doex(bool forked)
{
	enum sbikey xak;
	char **xap, **xav;

	if (iap < 2 || iap > iac)	/* should never (but can) be true */
		sh_err(FC_ERR, FMT2S, getmyname(), ERR_AVIINVAL);

	xav = xap = &iav[iap];
	while (*xap != NULL) {
		if (forked && equal(*xap, "}"))
			break;
		xap++;
	}
	if (forked && xap - xav > 0 && !equal(*xap, "}"))
		sh_err(FC_ERR, FMT3S, getmyname(), iav[iap - 1], ERR_BRACE);
	*xap = NULL;
	if (xav[0] == NULL) {
		if (forked)
			sh_err(FC_ERR, FMT3S, getmyname(), iav[iap - 1], ERR_COMMAND);
		else
			/* Currently unreachable; do not remove. */
			sh_err(FC_ERR, FMT2S, getmyname(), ERR_COMMAND);
	}

	/* Invoke the ":" or "exit" special command. */
	if (equal(xav[0], ":"))
		_exit(SH_TRUE);
	if (equal(xav[0], "exit")) {
		(void)lseek(FD0, (off_t)0, SEEK_END);
		_exit(SH_TRUE);
	}

	xak = cmd_lookup(xav[0]);
	if (IS_SBI(xak)) {
		if (forked)
			_exit(uexec(xak, (int)(xap - xav), xav));
		else
			(void)uexec(xak, (int)(xap - xav), xav);
		return;
	}

	(void)pexec_err(xav[0], xav);
}

/*
 * Check access permission for file according to mode while
 * dealing w/ special cases for the superuser and `-x':
 *	- Always grant search access on directories.
 *	- If not a directory, require at least one execute bit.
 * Return true  (1) if access is granted.
 * Return false (0) if access is denied.
 */
static bool
ifaccess(const char *file, int mode)
{
	struct stat sb;
	bool ra;

	if (file == NULL || *file == EOS)
		return false;

	ra = access(file, mode) == 0;

	if (ra && mode == X_OK && sheuid == 0) {
		if (stat(file, &sb) < 0)
			ra = false;
		else if (S_ISDIR(sb.st_mode))
			ra = true;
		else
			ra = (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) != 0;
	}

	return ra;
}

static bool
ifstat1(const char *file, mode_t type)
{
	struct stat sb;
	bool rs;

	if (file == NULL || *file == EOS)
		return false;

	if (type == S_IFLNK) {
		if (lstat(file, &sb) < 0)
			rs = false;
		else
			rs = (sb.st_mode & S_IFMT) == type;
	} else if (stat(file, &sb) < 0)
		rs = false;
	else if (type == S_IFDIR || type == S_IFREG)
		rs = (sb.st_mode & S_IFMT) == type;
	else if (type == F_GZ)
		rs = sb.st_size > (off_t)0;
	else
		rs = false;

	return rs;
}

static bool
ifstat2(const char *file1, const char *file2, int act)
{
	struct stat sb1, sb2;
	bool rs;

	if (file1 == NULL || *file1 == EOS)
		return false;
	if (file2 == NULL || *file2 == EOS)
		return false;

	if (stat(file1, &sb1) < 0)
		return false;
	if (stat(file2, &sb2) < 0)
		return false;

	if (act == F_OT)
		rs = sb1.st_mtime < sb2.st_mtime;
	else if (act == F_NT)
		rs = sb1.st_mtime > sb2.st_mtime;
	else if (act == F_EF)
		rs = sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino;
	else
		rs = false;

	return rs;
}

static char *
nxtarg(bool reterr)
{
	char *nap;

	if (iap < 1 || iap > iac)	/* should never (but can) be true */
		sh_err(FC_ERR, FMT2S, getmyname(), ERR_AVIINVAL);

	if (iap == iac) {
		if (reterr) {
			iap++;
			return NULL;
		}
		sh_err(FC_ERR, FMT3S, getmyname(), iav[iap - 1], ERR_ARGUMENT);
	}
	nap = iav[iap];
	iap++;
	return nap;
}

static bool
equal(const char *a, const char *b)
{

	if (a == NULL || b == NULL)
		return false;
	return EQUAL(a, b);
}

/*
 * NAME
 *	pwd - print the pathname of the current working directory
 *
 * SYNOPSIS
 *	pwd
 *
 * DESCRIPTION
 *	Pwd prints the absolute physical pathname of the current working
 *	directory to the standard output.
 *
 * EXIT STATUS / RETURN VALUE
 *	0    The current working directory pathname was successfully printed.
 *
 *	2    An error was detected by getcwd(3), or a usage error was
 *	     detected by pwd itself.
 */
static int
sbi_pwd(int argc, char **argv)
{
	char *b, buf[PATHMAX + 1];
	int   s;

	setmyname(argv[0]);

	if (argc != 1)
		sh_err(FC_ERR, PWD_USAGE, getmyname());

	b = NULL;

	errno = 0;
	b = getcwd(buf, sizeof(buf));
	if (errno != 0)
		sh_err(FC_ERR, FMT2S, getmyname(), strerror(errno));

	fd_print(FD1, "%s\n", b);
	s = SH_TRUE;

	return s;
}
