head	1.17;
access;
symbols
	perseant-exfatfs-base-20250801:1.17
	perseant-exfatfs-base-20240630:1.17
	perseant-exfatfs:1.17.0.36
	perseant-exfatfs-base:1.17
	cjep_sun2x:1.17.0.34
	cjep_sun2x-base:1.17
	cjep_staticlib_x-base1:1.17
	cjep_staticlib_x:1.17.0.32
	cjep_staticlib_x-base:1.17
	phil-wifi-20200421:1.17
	phil-wifi-20200411:1.17
	phil-wifi-20200406:1.17
	pgoyette-compat-merge-20190127:1.17
	pgoyette-compat-20190127:1.17
	pgoyette-compat-20190118:1.17
	pgoyette-compat-1226:1.17
	pgoyette-compat-1126:1.17
	pgoyette-compat-1020:1.17
	pgoyette-compat-0930:1.17
	pgoyette-compat-0906:1.17
	pgoyette-compat-0728:1.17
	pgoyette-compat-0625:1.17
	pgoyette-compat-0521:1.17
	pgoyette-compat-0502:1.17
	pgoyette-compat-0422:1.17
	pgoyette-compat-0415:1.17
	pgoyette-compat-0407:1.17
	pgoyette-compat-0330:1.17
	pgoyette-compat-0322:1.17
	pgoyette-compat-0315:1.17
	pgoyette-compat:1.17.0.30
	pgoyette-compat-base:1.17
	perseant-stdc-iso10646:1.17.0.28
	perseant-stdc-iso10646-base:1.17
	prg-localcount2-base3:1.17
	prg-localcount2-base2:1.17
	prg-localcount2-base1:1.17
	prg-localcount2:1.17.0.26
	prg-localcount2-base:1.17
	pgoyette-localcount-20170426:1.17
	bouyer-socketcan-base1:1.17
	pgoyette-localcount-20170320:1.17
	bouyer-socketcan:1.17.0.24
	bouyer-socketcan-base:1.17
	pgoyette-localcount-20170107:1.17
	pgoyette-localcount-20161104:1.17
	localcount-20160914:1.17
	pgoyette-localcount-20160806:1.17
	pgoyette-localcount-20160726:1.17
	pgoyette-localcount:1.17.0.22
	pgoyette-localcount-base:1.17
	netbsd-5-2-3-RELEASE:1.17
	netbsd-5-1-5-RELEASE:1.17
	yamt-pagecache-base9:1.17
	yamt-pagecache-tag8:1.17
	tls-earlyentropy:1.17.0.18
	tls-earlyentropy-base:1.17
	riastradh-xf86-video-intel-2-7-1-pre-2-21-15:1.17
	riastradh-drm2-base3:1.17
	netbsd-5-2-2-RELEASE:1.17
	netbsd-5-1-4-RELEASE:1.17
	netbsd-5-2-1-RELEASE:1.17
	netbsd-5-1-3-RELEASE:1.17
	agc-symver:1.17.0.20
	agc-symver-base:1.17
	tls-maxphys-base:1.17
	yamt-pagecache-base8:1.17
	netbsd-5-2:1.17.0.16
	yamt-pagecache-base7:1.17
	netbsd-5-2-RELEASE:1.17
	netbsd-5-2-RC1:1.17
	yamt-pagecache-base6:1.17
	yamt-pagecache-base5:1.17
	yamt-pagecache-base4:1.17
	netbsd-5-1-2-RELEASE:1.17
	netbsd-5-1-1-RELEASE:1.17
	yamt-pagecache-base3:1.17
	yamt-pagecache-base2:1.17
	yamt-pagecache:1.17.0.14
	yamt-pagecache-base:1.17
	bouyer-quota2-nbase:1.17
	bouyer-quota2:1.17.0.12
	bouyer-quota2-base:1.17
	matt-nb5-pq3:1.17.0.10
	matt-nb5-pq3-base:1.17
	netbsd-5-1:1.17.0.8
	netbsd-5-1-RELEASE:1.17
	netbsd-5-1-RC4:1.17
	netbsd-5-1-RC3:1.17
	netbsd-5-1-RC2:1.17
	netbsd-5-1-RC1:1.17
	netbsd-5-0-2-RELEASE:1.17
	netbsd-5-0-1-RELEASE:1.17
	jym-xensuspend-nbase:1.17
	netbsd-5-0:1.17.0.6
	netbsd-5-0-RELEASE:1.17
	netbsd-5-0-RC4:1.17
	netbsd-5-0-RC3:1.17
	netbsd-5-0-RC2:1.17
	jym-xensuspend:1.17.0.4
	jym-xensuspend-base:1.17
	netbsd-5-0-RC1:1.17
	netbsd-5:1.17.0.2
	netbsd-5-base:1.17
	matt-mips64-base2:1.16
	matt-mips64:1.16.0.26
	mjf-devfs2:1.16.0.24
	mjf-devfs2-base:1.16
	netbsd-4-0-1-RELEASE:1.16
	wrstuden-revivesa-base-3:1.16
	wrstuden-revivesa-base-2:1.16
	wrstuden-fixsa-newbase:1.16
	wrstuden-revivesa-base-1:1.16
	yamt-pf42-base4:1.16
	yamt-pf42-base3:1.16
	hpcarm-cleanup-nbase:1.16
	yamt-pf42-baseX:1.16
	yamt-pf42-base2:1.16
	wrstuden-revivesa:1.16.0.22
	wrstuden-revivesa-base:1.16
	yamt-pf42:1.16.0.20
	yamt-pf42-base:1.16
	keiichi-mipv6-nbase:1.16
	keiichi-mipv6:1.16.0.18
	keiichi-mipv6-base:1.16
	matt-armv6-nbase:1.16
	matt-armv6-prevmlocking:1.16
	wrstuden-fixsa-base-1:1.16
	netbsd-4-0:1.16.0.16
	netbsd-4-0-RELEASE:1.16
	cube-autoconf:1.16.0.14
	cube-autoconf-base:1.16
	netbsd-4-0-RC5:1.16
	netbsd-4-0-RC4:1.16
	netbsd-4-0-RC3:1.16
	netbsd-4-0-RC2:1.16
	netbsd-4-0-RC1:1.16
	matt-armv6:1.16.0.12
	matt-armv6-base:1.16
	matt-mips64-base:1.16
	hpcarm-cleanup:1.16.0.10
	hpcarm-cleanup-base:1.16
	netbsd-3-1-1-RELEASE:1.12.2.4
	netbsd-3-0-3-RELEASE:1.12.2.4
	wrstuden-fixsa:1.16.0.8
	wrstuden-fixsa-base:1.16
	abandoned-netbsd-4-base:1.16
	abandoned-netbsd-4:1.16.0.4
	netbsd-3-1:1.12.2.4.0.4
	netbsd-3-1-RELEASE:1.12.2.4
	netbsd-3-0-2-RELEASE:1.12.2.4
	netbsd-3-1-RC4:1.12.2.4
	netbsd-3-1-RC3:1.12.2.4
	netbsd-3-1-RC2:1.12.2.4
	netbsd-3-1-RC1:1.12.2.4
	netbsd-4:1.16.0.6
	netbsd-4-base:1.16
	chap-midi-nbase:1.16
	netbsd-3-0-1-RELEASE:1.12.2.4
	chap-midi:1.16.0.2
	chap-midi-base:1.16
	netbsd-3-0:1.12.2.4.0.2
	netbsd-3-0-RELEASE:1.12.2.4
	netbsd-3-0-RC6:1.12.2.4
	netbsd-3-0-RC5:1.12.2.4
	netbsd-3-0-RC4:1.12.2.4
	netbsd-3-0-RC3:1.12.2.4
	netbsd-3-0-RC2:1.12.2.4
	netbsd-3-0-RC1:1.12.2.4
	netbsd-2-0-3-RELEASE:1.10.4.2
	netbsd-2-1:1.10.6.1.0.2
	netbsd-2-1-RELEASE:1.10.6.1
	netbsd-2-1-RC6:1.10.6.1
	netbsd-2-1-RC5:1.10.6.1
	netbsd-2-1-RC4:1.10.6.1
	netbsd-2-1-RC3:1.10.6.1
	netbsd-2-1-RC2:1.10.6.1
	netbsd-2-1-RC1:1.10.6.1
	netbsd-2-0-2-RELEASE:1.10.4.2
	netbsd-3:1.12.0.2
	netbsd-3-base:1.12
	netbsd-2-0-1-RELEASE:1.10
	netbsd-2:1.10.0.6
	netbsd-2-base:1.10
	netbsd-2-0-RELEASE:1.10
	netbsd-2-0-RC5:1.10
	netbsd-2-0-RC4:1.10
	netbsd-2-0-RC3:1.10
	netbsd-2-0-RC2:1.10
	netbsd-2-0-RC1:1.10
	netbsd-2-0:1.10.0.4
	netbsd-2-0-base:1.10
	netbsd-1-6-PATCH002-RELEASE:1.10
	netbsd-1-6-PATCH002:1.10
	netbsd-1-6-PATCH002-RC4:1.10
	netbsd-1-6-PATCH002-RC3:1.10
	netbsd-1-6-PATCH002-RC2:1.10
	netbsd-1-6-PATCH002-RC1:1.10
	netbsd-1-6-PATCH001:1.10
	netbsd-1-6-PATCH001-RELEASE:1.10
	netbsd-1-6-PATCH001-RC3:1.10
	netbsd-1-6-PATCH001-RC2:1.10
	netbsd-1-6-PATCH001-RC1:1.10
	fvdl_fs64_base:1.10
	netbsd-1-6-RELEASE:1.10
	netbsd-1-6-RC3:1.10
	netbsd-1-6-RC2:1.10
	netbsd-1-6-RC1:1.10
	netbsd-1-6:1.10.0.2
	netbsd-1-6-base:1.10
	netbsd-1-5-PATCH003:1.8
	netbsd-1-5-PATCH002:1.8
	netbsd-1-5-PATCH001:1.8
	nvi_1_79:1.1.1.6
	netbsd-1-5-RELEASE:1.8
	netbsd-1-5-BETA2:1.8
	netbsd-1-5-BETA:1.8
	netbsd-1-4-PATCH003:1.8
	netbsd-1-5-ALPHA2:1.8
	netbsd-1-5:1.8.0.10
	netbsd-1-5-base:1.8
	minoura-xpg4dl-base:1.8
	minoura-xpg4dl:1.8.0.8
	netbsd-1-4-PATCH002:1.8
	wrstuden-devbsize-19991221:1.8
	wrstuden-devbsize:1.8.0.6
	wrstuden-devbsize-base:1.8
	comdex-fall-1999:1.8.0.4
	comdex-fall-1999-base:1.8
	netbsd-1-4-PATCH001:1.8
	netbsd-1-4-RELEASE:1.8
	netbsd-1-4:1.8.0.2
	netbsd-1-4-base:1.8
	netbsd-1-3-PATCH003:1.7
	netbsd-1-3-PATCH003-CANDIDATE2:1.7
	netbsd-1-3-PATCH003-CANDIDATE1:1.7
	netbsd-1-3-PATCH003-CANDIDATE0:1.7
	netbsd-1-3-PATCH002:1.7
	netbsd-1-3-PATCH001:1.7
	netbsd-1-3-RELEASE:1.7
	netbsd-1-3-BETA:1.7
	netbsd-1-3:1.7.0.2
	netbsd-1-3-base:1.7
	netbsd-1-2-PATCH001:1.7
	netbsd-1-2-RELEASE:1.7
	netbsd-1-2-BETA:1.7
	netbsd-1-2:1.7.0.4
	netbsd-1-2-base:1.7
	nvi_1_66:1.1.1.5
	BOSTIC:1.1.1
	netbsd-1-1-PATCH001:1.6
	netbsd-1-1-RELEASE:1.6
	netbsd-1-1:1.6.0.2
	netbsd-1-1-base:1.6
	netbsd-1-0-PATCH06:1.5
	netbsd-1-0-PATCH05:1.5
	netbsd-1-0-PATCH04:1.5
	netbsd-1-0-PATCH03:1.5
	netbsd-1-0-PATCH02:1.5
	netbsd-1-0-PATCH1:1.5
	netbsd-1-0-PATCH0:1.5
	netbsd-1-0-RELEASE:1.5
	netbsd-1-0:1.5.0.2
	nvi-1-34b:1.1.1.4
	nvi-1-33b:1.1.1.3
	netbsd-1-0-base:1.3
	nvi-1-11b:1.1.1.2
	nvi-1-03:1.1.1.1
	bostic-nvi:1.1.1;
locks; strict;
comment	@ * @;


1.17
date	2008.10.29.16.49.36;	author christos;	state dead;
branches;
next	1.16;

1.16
date	2005.10.16.15.58.59;	author aymeric;	state Exp;
branches;
next	1.15;

1.15
date	2005.10.08.12.41.48;	author aymeric;	state Exp;
branches;
next	1.14;

1.14
date	2005.09.06.21.50.51;	author aymeric;	state Exp;
branches;
next	1.13;

1.13
date	2005.09.06.21.36.10;	author aymeric;	state Exp;
branches;
next	1.12;

1.12
date	2005.02.12.12.53.22;	author aymeric;	state Exp;
branches
	1.12.2.1;
next	1.11;

1.11
date	2004.11.05.19.50.12;	author dsl;	state Exp;
branches;
next	1.10;

1.10
date	2002.04.09.01.47.32;	author thorpej;	state Exp;
branches
	1.10.4.1
	1.10.6.1;
next	1.9;

1.9
date	2001.03.31.11.37.49;	author aymeric;	state Exp;
branches;
next	1.8;

1.8
date	98.01.09.08.07.41;	author perry;	state Exp;
branches;
next	1.7;

1.7
date	96.05.20.03.47.24;	author mrg;	state Exp;
branches;
next	1.6;

1.6
date	95.04.24.07.42.19;	author cgd;	state Exp;
branches;
next	1.5;

1.5
date	94.08.17.20.12.12;	author cgd;	state Exp;
branches
	1.5.2.1;
next	1.4;

1.4
date	94.08.17.16.35.41;	author cgd;	state Exp;
branches;
next	1.3;

1.3
date	94.03.28.04.29.06;	author cgd;	state Exp;
branches;
next	1.2;

1.2
date	94.01.24.06.40.08;	author cgd;	state Exp;
branches;
next	1.1;

1.1
date	94.01.24.05.53.01;	author cgd;	state Exp;
branches
	1.1.1.1;
next	;

1.12.2.1
date	2005.09.09.15.00.58;	author tron;	state Exp;
branches;
next	1.12.2.2;

1.12.2.2
date	2005.09.09.15.01.44;	author tron;	state Exp;
branches;
next	1.12.2.3;

1.12.2.3
date	2005.10.15.15.41.50;	author riz;	state Exp;
branches;
next	1.12.2.4;

1.12.2.4
date	2005.10.21.13.17.03;	author tron;	state Exp;
branches;
next	;

1.10.4.1
date	2005.02.12.12.24.13;	author aymeric;	state Exp;
branches;
next	1.10.4.2;

1.10.4.2
date	2005.02.12.12.46.26;	author aymeric;	state Exp;
branches;
next	;

1.10.6.1
date	2005.05.06.14.42.07;	author riz;	state Exp;
branches;
next	;

1.5.2.1
date	94.08.17.20.12.12;	author cgd;	state dead;
branches;
next	1.5.2.2;

1.5.2.2
date	94.08.17.20.12.13;	author cgd;	state Exp;
branches;
next	;

1.1.1.1
date	94.01.24.05.53.02;	author cgd;	state Exp;
branches;
next	1.1.1.2;

1.1.1.2
date	94.03.28.02.53.09;	author cgd;	state Exp;
branches;
next	1.1.1.3;

1.1.1.3
date	94.08.17.16.16.19;	author cgd;	state Exp;
branches;
next	1.1.1.4;

1.1.1.4
date	94.08.17.19.17.07;	author cgd;	state Exp;
branches;
next	1.1.1.5;

1.1.1.5
date	96.05.20.01.53.59;	author mrg;	state Exp;
branches;
next	1.1.1.6;

1.1.1.6
date	2001.03.31.11.29.46;	author aymeric;	state Exp;
branches;
next	;


desc
@@


1.17
log
@bye old vi!
@
text
@/*	$NetBSD: ex_argv.c,v 1.16 2005/10/16 15:58:59 aymeric Exp $	*/

/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
 *
 * See the LICENSE file for redistribution information.
 */

#include "config.h"

#include <sys/cdefs.h>
#ifndef lint
#if 0
static const char sccsid[] = "@@(#)ex_argv.c	10.26 (Berkeley) 9/20/96";
#else
__RCSID("$NetBSD: ex_argv.c,v 1.16 2005/10/16 15:58:59 aymeric Exp $");
#endif
#endif /* not lint */

#include <sys/types.h>
#include <sys/queue.h>

#include <bitstring.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../common/common.h"

static int argv_alloc __P((SCR *, size_t));
static int argv_comp __P((const void *, const void *));
static int argv_fexp __P((SCR *, EXCMD *,
	char *, size_t, char *, size_t *, char **, size_t *, int));
static int argv_lexp __P((SCR *, EXCMD *, char *));
static int argv_sexp __P((SCR *, char **, size_t *, size_t *));

/*
 * argv_init --
 *	Build  a prototype arguments list.
 *
 * PUBLIC: int argv_init __P((SCR *, EXCMD *));
 */
int
argv_init(sp, excp)
	SCR *sp;
	EXCMD *excp;
{
	EX_PRIVATE *exp;

	exp = EXP(sp);
	exp->argsoff = 0;
	argv_alloc(sp, 1);

	excp->argv = exp->args;
	excp->argc = exp->argsoff;
	return (0);
}

/*
 * argv_exp0 --
 *	Append a string to the argument list.
 *
 * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t));
 */
int
argv_exp0(sp, excp, cmd, cmdlen)
	SCR *sp;
	EXCMD *excp;
	char *cmd;
	size_t cmdlen;
{
	EX_PRIVATE *exp;

	exp = EXP(sp);
	argv_alloc(sp, cmdlen);
	memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen);
	exp->args[exp->argsoff]->bp[cmdlen] = '\0';
	exp->args[exp->argsoff]->len = cmdlen;
	++exp->argsoff;
	excp->argv = exp->args;
	excp->argc = exp->argsoff;
	return (0);
}

/*
 * argv_exp1 --
 *	Do file name expansion on a string, and append it to the
 *	argument list.
 *
 * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int));
 */
int
argv_exp1(sp, excp, cmd, cmdlen, is_bang)
	SCR *sp;
	EXCMD *excp;
	char *cmd;
	size_t cmdlen;
	int is_bang;
{
	EX_PRIVATE *exp;
	size_t blen, len;
	char *bp, *p, *t;

	GET_SPACE_RET(sp, bp, blen, 512);

	len = 0;
	exp = EXP(sp);
	if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
		FREE_SPACE(sp, bp, blen);
		return (1);
	}

	/* If it's empty, we're done. */
	if (len != 0) {
		for (p = bp, t = bp + len; p < t; ++p)
			if (!isblank(*p))
				break;
		if (p == t)
			goto ret;
	} else
		goto ret;

	(void)argv_exp0(sp, excp, bp, len);

ret:	FREE_SPACE(sp, bp, blen);
	return (0);
}

/*
 * argv_exp2 --
 *	Do file name and shell expansion on a string, and append it to
 *	the argument list.
 *
 * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t));
 */
int
argv_exp2(sp, excp, cmd, cmdlen)
	SCR *sp;
	EXCMD *excp;
	char *cmd;
	size_t cmdlen;
{
	size_t blen, len, n;
	int rval;
	char *bp, *mp, *p;
	int shellmeta_ok;

	GET_SPACE_RET(sp, bp, blen, 512);

#define	SHELLECHO	"echo "
#define	SHELLOFFSET	(sizeof(SHELLECHO) - 1)
	memcpy(bp, SHELLECHO, SHELLOFFSET);
	p = bp + SHELLOFFSET;
	len = SHELLOFFSET;

#if defined(DEBUG) && 0
	TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
#endif

	if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
		rval = 1;
		goto err;
	}

#if defined(DEBUG) && 0
	TRACE(sp, "before shell: %d: {%s}\n", len, bp);
#endif

	/*
	 * Do shell word expansion -- it's very, very hard to figure out what
	 * magic characters the user's shell expects.  Historically, it was a
	 * union of v7 shell and csh meta characters.  We match that practice
	 * by default, so ":read \%" tries to read a file named '%'.  It would
	 * make more sense to pass any special characters through the shell,
	 * but then, if your shell was csh, the above example will behave
	 * differently in nvi than in vi.  If you want to get other characters
	 * passed through to your shell, change the "meta" option.
	 *
	 * To avoid a function call per character, we do a first pass through
	 * the meta characters looking for characters that aren't expected
	 * to be there, and then we can ignore them in the user's argument.
	 */
	if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
		n = 0;
	else {
		shellmeta_ok = 1;
		for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p)
			if (isblank(*p) || isalnum((unsigned char)*p)) {
				shellmeta_ok = 0;
				break;
			}

		p = bp + SHELLOFFSET;
		n = len - SHELLOFFSET;
		if (shellmeta_ok) {
			for (; n > 0; --n, ++p)
				if (strchr(mp, *p) != NULL)
					break;
		} else
			for (; n > 0; --n, ++p)
				if (!isblank(*p) && !isalnum((unsigned char)*p)
				    && strchr(mp, *p) != NULL)
					break;
	}

	/*
	 * If we found a meta character in the string, fork a shell to expand
	 * it.  Unfortunately, this is comparatively slow.  Historically, it
	 * didn't matter much, since users don't enter meta characters as part
	 * of pathnames that frequently.  The addition of filename completion
	 * broke that assumption because it's easy to use.  As a result, lots
	 * folks have complained that the expansion code is too slow.  So, we
	 * detect filename completion as a special case, and do it internally.
	 * Note that this code assumes that the <asterisk> character is the
	 * match-anything meta character.  That feels safe -- if anyone writes
	 * a shell that doesn't follow that convention, I'd suggest giving them
	 * a festive hot-lead enema.
	 */
	switch (n) {
	case 0:
		p = bp + SHELLOFFSET;
		len -= SHELLOFFSET;
		rval = argv_exp3(sp, excp, p, len);
		break;
	case 1:
		if (*p == '*') {
			*p = '\0';
			rval = argv_lexp(sp, excp, bp + SHELLOFFSET);
			break;
		}
		/* FALLTHROUGH */
	default:
		if (argv_sexp(sp, &bp, &blen, &len)) {
			rval = 1;
			goto err;
		}
		p = bp;
		rval = argv_exp3(sp, excp, p, len);
		break;
	}

err:	FREE_SPACE(sp, bp, blen);
	return (rval);
}

/*
 * argv_exp3 --
 *	Take a string and break it up into an argv, which is appended
 *	to the argument list.
 *
 * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t));
 */
int
argv_exp3(sp, excp, cmd, cmdlen)
	SCR *sp;
	EXCMD *excp;
	char *cmd;
	size_t cmdlen;
{
	EX_PRIVATE *exp;
	size_t len;
	int ch, off;
	char *ap, *p;

	for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
		/* Skip any leading whitespace. */
		for (; cmdlen > 0; --cmdlen, ++cmd) {
			ch = *cmd;
			if (!isblank(ch))
				break;
		}
		if (cmdlen == 0)
			break;

		/*
		 * Determine the length of this whitespace delimited
		 * argument.
		 *
		 * QUOTING NOTE:
		 *
		 * Skip any character preceded by the user's quoting
		 * character.
		 */
		for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
			ch = *cmd;
			if ((IS_ESCAPE(sp, excp, ch) || ch == '\\') &&
			    cmdlen > 1) {
				++cmd;
				--cmdlen;
			} else if (isblank(ch))
				break;
		}

		/*
		 * Copy the argument into place.
		 *
		 * QUOTING NOTE:
		 *
		 * Lose quote chars.
		 */
		argv_alloc(sp, len);
		off = exp->argsoff;
		exp->args[off]->len = len;
		for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
			if (IS_ESCAPE(sp, excp, *ap) || *ap == '\\')
				++ap;
		*p = '\0';
	}
	excp->argv = exp->args;
	excp->argc = exp->argsoff;

#if defined(DEBUG) && 0
	for (cnt = 0; cnt < exp->argsoff; ++cnt)
		TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
#endif
	return (0);
}

/*
 * argv_fexp --
 *	Do file name and bang command expansion.
 */
static int
argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
	SCR *sp;
	EXCMD *excp;
	char *cmd, *p, **bpp;
	size_t cmdlen, *lenp, *blenp;
	int is_bang;
{
	EX_PRIVATE *exp;
	char *bp, *t;
	size_t blen, len, off, tlen;

	/* Replace file name characters. */
	for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
		switch (*cmd) {
		case '!':
			if (!is_bang)
				goto ins_ch;
			exp = EXP(sp);
			if (exp->lastbcomm == NULL) {
				msgq(sp, M_ERR,
				    "115|No previous command to replace \"!\"");
				return (1);
			}
			len += tlen = strlen(exp->lastbcomm);
			off = p - bp;
			ADD_SPACE_RET(sp, bp, blen, len);
			p = bp + off;
			memcpy(p, exp->lastbcomm, tlen);
			p += tlen;
			F_SET(excp, E_MODIFY);
			break;
		case '%':
			if ((t = sp->frp->name) == NULL) {
				msgq(sp, M_ERR,
				    "116|No filename to substitute for %%");
				return (1);
			}
			tlen = strlen(t);
			len += tlen;
			off = p - bp;
			ADD_SPACE_RET(sp, bp, blen, len);
			p = bp + off;
			memcpy(p, t, tlen);
			p += tlen;
			F_SET(excp, E_MODIFY);
			break;
		case '#':
			if ((t = sp->alt_name) == NULL) {
				msgq(sp, M_ERR,
				    "117|No filename to substitute for #");
				return (1);
			}
			len += tlen = strlen(t);
			off = p - bp;
			ADD_SPACE_RET(sp, bp, blen, len);
			p = bp + off;
			memcpy(p, t, tlen);
			p += tlen;
			F_SET(excp, E_MODIFY);
			break;
		case '\\':
			/*
			 * QUOTING NOTE:
			 *
			 * Strip any backslashes that protected the file
			 * expansion characters.
			 */
			if (cmdlen > 1 &&
			    (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
				++cmd;
				--cmdlen;
			}
			/* FALLTHROUGH */
		default:
ins_ch:			++len;
			off = p - bp;
			ADD_SPACE_RET(sp, bp, blen, len);
			p = bp + off;
			*p++ = *cmd;
		}

	/* Nul termination. */
	++len;
	off = p - bp;
	ADD_SPACE_RET(sp, bp, blen, len);
	p = bp + off;
	*p = '\0';

	/* Return the new string length, buffer, buffer length. */
	*lenp = len - 1;
	*bpp = bp;
	*blenp = blen;
	return (0);
}

/*
 * argv_alloc --
 *	Make more space for arguments.
 */
static int
argv_alloc(sp, len)
	SCR *sp;
	size_t len;
{
	ARGS *ap;
	EX_PRIVATE *exp;
	int cnt, off;

	/*
	 * Allocate room for another argument, always leaving
	 * enough room for an ARGS structure with a length of 0.
	 */
#define	INCREMENT	20
	exp = EXP(sp);
	off = exp->argsoff;
	if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
		cnt = exp->argscnt + INCREMENT;
		REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
		if (exp->args == NULL) {
			(void)argv_free(sp);
			goto mem;
		}
		memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
		exp->argscnt = cnt;
	}

	/* First argument. */
	if (exp->args[off] == NULL) {
		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
		if (exp->args[off] == NULL)
			goto mem;
	}

	/* First argument buffer. */
	ap = exp->args[off];
	ap->len = 0;
	if (ap->blen < len + 1) {
		ap->blen = len + 1;
		REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
		if (ap->bp == NULL) {
			ap->bp = NULL;
			ap->blen = 0;
			F_CLR(ap, A_ALLOCATED);
mem:			msgq(sp, M_SYSERR, NULL);
			return (1);
		}
		F_SET(ap, A_ALLOCATED);
	}

	/* Second argument. */
	if (exp->args[++off] == NULL) {
		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
		if (exp->args[off] == NULL)
			goto mem;
	}
	/* 0 length serves as end-of-argument marker. */
	exp->args[off]->len = 0;
	return (0);
}

/*
 * argv_free --
 *	Free up argument structures.
 *
 * PUBLIC: int argv_free __P((SCR *));
 */
int
argv_free(sp)
	SCR *sp;
{
	EX_PRIVATE *exp;
	int off;

	exp = EXP(sp);
	if (exp->args != NULL) {
		for (off = 0; off < exp->argscnt; ++off) {
			if (exp->args[off] == NULL)
				continue;
			if (F_ISSET(exp->args[off], A_ALLOCATED))
				free(exp->args[off]->bp);
			free(exp->args[off]);
		}
		free(exp->args);
	}
	exp->args = NULL;
	exp->argscnt = 0;
	exp->argsoff = 0;
	return (0);
}

/*
 * argv_lexp --
 *	Find all file names matching the prefix and append them to the
 *	buffer.
 */
static int
argv_lexp(sp, excp, path)
	SCR *sp;
	EXCMD *excp;
	char *path;
{
	struct dirent *dp;
	DIR *dirp;
	EX_PRIVATE *exp;
	int off;
	size_t dlen, len, nlen;
	char *dname, *name, *p;

	exp = EXP(sp);

	/* Set up the name and length for comparison. */
	if ((p = strrchr(path, '/')) == NULL) {
		dname = ".";
		dlen = 0;
		name = path;
	} else { 
		if (p == path) {
			dname = "/";
			dlen = 1;
		} else {
			*p = '\0';
			dname = path;
			dlen = strlen(path);
		}
		name = p + 1;
	}
	nlen = strlen(name);

	/*
	 * XXX
	 * We don't use the d_namlen field, it's not portable enough; we
	 * assume that d_name is nul terminated, instead.
	 */
	if ((dirp = opendir(dname)) == NULL) {
		msgq_str(sp, M_SYSERR, dname, "%s");
		return (1);
	}
	for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
		if (nlen == 0) {
			if (dp->d_name[0] == '.')
				continue;
			len = strlen(dp->d_name);
		} else {
			len = strlen(dp->d_name);
			if (len < nlen || memcmp(dp->d_name, name, nlen))
				continue;
		}

		/* Directory + name + slash + null. */
		argv_alloc(sp, dlen + len + 2);
		p = exp->args[exp->argsoff]->bp;
		if (dlen != 0) {
			memcpy(p, dname, dlen);
			p += dlen;
			if (dlen > 1 || dname[0] != '/')
				*p++ = '/';
		}
		memcpy(p, dp->d_name, len + 1);
		exp->args[exp->argsoff]->len = dlen + len + 1;
		++exp->argsoff;
		excp->argv = exp->args;
		excp->argc = exp->argsoff;
	}
	closedir(dirp);

	if (off == exp->argsoff) {
		/*
		 * If we didn't find a match, complain that the expansion
		 * failed.  We can't know for certain that's the error, but
		 * it's a good guess, and it matches historic practice. 
		 */
		msgq(sp, M_ERR, "304|Shell expansion failed");
		return (1);
	}
	qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
	return (0);
}

/*
 * argv_comp --
 *	Alphabetic comparison.
 */
static int
argv_comp(a, b)
	const void *a, *b;
{
	return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));
}

/*
 * argv_sexp --
 *	Fork a shell, pipe a command through it, and read the output into
 *	a buffer.
 */
static int
argv_sexp(sp, bpp, blenp, lenp)
	SCR *sp;
	char **bpp;
	size_t *blenp, *lenp;
{
	enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
	FILE *ifp;
	pid_t pid;
	size_t blen, len;
	int ch, std_output[2];
	char *bp, *p, *sh, *sh_path;

	/* Secure means no shell access. */
	if (O_ISSET(sp, O_SECURE)) {
		msgq(sp, M_ERR,
"289|Shell expansions not supported when the secure edit option is set");
		return (1);
	}

	sh_path = O_STR(sp, O_SHELL);
	if ((sh = strrchr(sh_path, '/')) == NULL)
		sh = sh_path;
	else
		++sh;

	/* Local copies of the buffer variables. */
	bp = *bpp;
	blen = *blenp;

	/*
	 * There are two different processes running through this code, named
	 * the utility (the shell) and the parent. The utility reads standard
	 * input and writes standard output and standard error output.  The
	 * parent writes to the utility, reads its standard output and ignores
	 * its standard error output.  Historically, the standard error output
	 * was discarded by vi, as it produces a lot of noise when file patterns
	 * don't match.
	 *
	 * The parent reads std_output[0], and the utility writes std_output[1].
	 */
	ifp = NULL;
	std_output[0] = std_output[1] = -1;
	if (pipe(std_output) < 0) {
		msgq(sp, M_SYSERR, "pipe");
		return (1);
	}
	if ((ifp = fdopen(std_output[0], "r")) == NULL) {
		msgq(sp, M_SYSERR, "fdopen");
		goto err;
	}

	/*
	 * Do the minimal amount of work possible, the shell is going to run
	 * briefly and then exit.  We sincerely hope.
	 */
	switch (pid = vfork()) {
	case -1:			/* Error. */
		msgq(sp, M_SYSERR, "vfork");
err:		if (ifp != NULL)
			(void)fclose(ifp);
		else if (std_output[0] != -1)
			close(std_output[0]);
		if (std_output[1] != -1)
			close(std_output[0]);
		return (1);
	case 0:				/* Utility. */
		/* Redirect stdout to the write end of the pipe. */
		(void)dup2(std_output[1], STDOUT_FILENO);

		/* Close the utility's file descriptors. */
		(void)close(std_output[0]);
		(void)close(std_output[1]);
		(void)close(STDERR_FILENO);

		/*
		 * XXX
		 * Assume that all shells have -c.
		 */
		execl(sh_path, sh, "-c", bp, NULL);
		msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
		_exit(127);
	default:			/* Parent. */
		/* Close the pipe ends the parent won't use. */
		(void)close(std_output[1]);
		break;
	}

	/*
	 * Copy process standard output into a buffer.
	 *
	 * !!!
	 * Historic vi apparently discarded leading \n and \r's from
	 * the shell output stream.  We don't on the grounds that any
	 * shell that does that is broken.
	 */
	for (p = bp, len = 0, ch = EOF;
	    (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
		if (blen < 5) {
			ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);
			p = bp + len;
			blen = *blenp - len;
		}

	/* Delete the final newline, nul terminate the string. */
	if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
		--p;
		--len;
	}
	*p = '\0';
	*lenp = len;
	*bpp = bp;		/* *blenp is already updated. */

	if (ferror(ifp))
		goto ioerr;
	if (fclose(ifp)) {
ioerr:		msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
alloc_err:	rval = SEXP_ERR;
	} else
		rval = SEXP_OK;

	/*
	 * Wait for the process.  If the shell process fails (e.g., "echo $q"
	 * where q wasn't a defined variable) or if the returned string has
	 * no characters or only blank characters, (e.g., "echo $5"), complain
	 * that the shell expansion failed.  We can't know for certain that's
	 * the error, but it's a good guess, and it matches historic practice.
	 * This won't catch "echo foo_$5", but that's not a common error and
	 * historic vi didn't catch it either.
	 */
	if (proc_wait(sp, (long)pid, sh, 1, 0))
		rval = SEXP_EXPANSION_ERR;

	for (p = bp; len; ++p, --len)
		if (!isblank(*p))
			break;
	if (len == 0)
		rval = SEXP_EXPANSION_ERR;

	if (rval == SEXP_EXPANSION_ERR)
		msgq(sp, M_ERR, "304|Shell expansion failed");

	return (rval == SEXP_OK ? 0 : 1);
}
@


1.16
log
@the last revision introduced a bug in the handling of escape characters while
trying to correct a corner case. I will work on a final solution later.
Thanks to David Brownlee for noticing and notifying me.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.15 2005/10/08 12:41:48 aymeric Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: ex_argv.c,v 1.15 2005/10/08 12:41:48 aymeric Exp $");
@


1.15
log
@accept ^V again as a quoting character. At least one person uses it. (hi abs!)
While there, fix a bug that would swallow a quoting character if it were the
last one.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.14 2005/09/06 21:50:51 aymeric Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: ex_argv.c,v 1.14 2005/09/06 21:50:51 aymeric Exp $");
d313 1
a313 2
			if ((IS_ESCAPE(sp, excp, *ap) || *ap == '\\') &&
			    len > 1)
@


1.14
log
@In argv_exp3() where we cut a line into args, check for '\' as the escape
character instead of using the IS_ESCAPE() macro which tests for ^V because the
former is mandated by the standards, and the latter is insane.
This is a very small part in addressing PR bin/26046 by lukem@@.
Before, in order to escape a special character, you had to use a literal ^V,
which is type ^V twice before the character; whereas now, you use \.
Because the fix will remain partial for a while, you have to remove \ from
your shellmeta option otherwise the \ is swallowed by the invoked shell that
handles arguments expansion.
Please complain if you want ^V^V to also work, but please don't call me a
heretic.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.13 2005/09/06 21:36:10 aymeric Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: ex_argv.c,v 1.13 2005/09/06 21:36:10 aymeric Exp $");
d294 2
a295 1
			if (ch == '\\' && cmdlen > 1) {
d313 2
a314 1
			if (*ap == '\\')
@


1.13
log
@From code inspection, nvi had the undocumented and non-working functionality
of ignoring alphanumerical and blank characters from the shellmeta option.
The former code was using a character pointed to by a pointer as a boolean
to check whether to enable this functionality, but in the meantime the pointer
was used for something else. Introduce a variable for this boolean so that
the functionality actually works.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.12 2005/02/12 12:53:22 aymeric Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: ex_argv.c,v 1.12 2005/02/12 12:53:22 aymeric Exp $");
d294 1
a294 1
			if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
d312 1
a312 1
			if (IS_ESCAPE(sp, excp, *ap))
@


1.12
log
@Fix the RCSID's to be $NetBSD$ instead of $NetBSD
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.11 2004/11/05 19:50:12 dsl Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD$");
d154 1
d194 1
d196 2
a197 1
			if (isblank(*p) || isalnum((unsigned char)*p))
d199 2
d203 1
a203 1
		if (*p != '\0') {
@


1.12.2.1
log
@Pull up following revision(s) (requested by aymeric in ticket #753):
	usr.bin/vi/ex/ex_argv.c: revision 1.13
From code inspection, nvi had the undocumented and non-working functionality
of ignoring alphanumerical and blank characters from the shellmeta option.
The former code was using a character pointed to by a pointer as a boolean
to check whether to enable this functionality, but in the meantime the pointer
was used for something else. Introduce a variable for this boolean so that
the functionality actually works.
@
text
@d1 1
a1 1
/*	$NetBSD$	*/
a153 1
	int shellmeta_ok;
a192 1
		shellmeta_ok = 1;
d194 1
a194 2
			if (isblank(*p) || isalnum((unsigned char)*p)) {
				shellmeta_ok = 0;
a195 2
			}

d198 1
a198 1
		if (shellmeta_ok) {
@


1.12.2.2
log
@Pull up following revision(s) (requested by aymeric in ticket #753):
	usr.bin/vi/ex/ex_argv.c: revision 1.14
In argv_exp3() where we cut a line into args, check for '\' as the escape
character instead of using the IS_ESCAPE() macro which tests for ^V because the
former is mandated by the standards, and the latter is insane.
This is a very small part in addressing PR bin/26046 by lukem@@.
Before, in order to escape a special character, you had to use a literal ^V,
which is type ^V twice before the character; whereas now, you use \.
Because the fix will remain partial for a while, you have to remove \ from
your shellmeta option otherwise the \ is swallowed by the invoked shell that
handles arguments expansion.
Please complain if you want ^V^V to also work, but please don't call me a
heretic.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.12.2.1 2005/09/09 15:00:58 tron Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: ex_argv.c,v 1.12.2.1 2005/09/09 15:00:58 tron Exp $");
d294 1
a294 1
			if (ch == '\\' && cmdlen > 1) {
d312 1
a312 1
			if (*ap == '\\')
@


1.12.2.3
log
@Pull up following revision(s) (requested by aymeric in ticket #878):
	usr.bin/vi/ex/ex_argv.c: revision 1.15
accept ^V again as a quoting character. At least one person uses it. (hi
abs!)
While there, fix a bug that would swallow a quoting character if it were the
last one.
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.12.2.2 2005/09/09 15:01:44 tron Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: ex_argv.c,v 1.12.2.2 2005/09/09 15:01:44 tron Exp $");
d294 1
a294 2
			if ((IS_ESCAPE(sp, excp, ch) || ch == '\\') &&
			    cmdlen > 1) {
d312 1
a312 2
			if ((IS_ESCAPE(sp, excp, *ap) || *ap == '\\') &&
			    len > 1)
@


1.12.2.4
log
@Pull up following revision(s) (requested by aymeric in ticket #898):
	usr.bin/vi/ex/ex_argv.c: revision 1.16
the last revision introduced a bug in the handling of escape characters
while
trying to correct a corner case. I will work on a final solution later.
Thanks to David Brownlee for noticing and notifying me.
@
text
@d1 1
a1 1
/*	$NetBSD$	*/
d19 1
a19 1
__RCSID("$NetBSD$");
d313 2
a314 1
			if (IS_ESCAPE(sp, excp, *ap) || *ap == '\\')
@


1.11
log
@Add (unsigned char) cast to ctype functions
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.10 2002/04/09 01:47:32 thorpej Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD");
@


1.10
log
@Use __RCSID() and __COPYRIGHT().
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.9 2001/03/31 11:37:49 aymeric Exp $	*/
d194 1
a194 1
			if (isblank(*p) || isalnum(*p))
d204 2
a205 2
				if (!isblank(*p) &&
				    !isalnum(*p) && strchr(mp, *p) != NULL)
@


1.10.6.1
log
@Pull up revision 1.12 (requested by aymeric in ticket #1195):
Fix the RCSID's to be $NetBSD$ instead of $NetBSD
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.10 2002/04/09 01:47:32 thorpej Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD$");
@


1.10.4.1
log
@Fix the RCSID's to be $NetBSD$ instead of $NetBSD
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.10 2002/04/09 01:47:32 thorpej Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD$");
@


1.10.4.2
log
@Backout previous. Sorry.
@
text
@d19 1
a19 1
__RCSID("$NetBSD");
@


1.9
log
@merge changes after import of nvi 1.79
@
text
@d1 1
a1 1
/*	$NetBSD: ex_argv.c,v 1.8 1998/01/09 08:07:41 perry Exp $	*/
d14 1
d16 1
d18 3
@


1.8
log
@RCS Id Police.
@
text
@d1 1
a1 1
/*	$NetBSD$	*/
d15 1
a15 1
static const char sccsid[] = "@@(#)ex_argv.c	10.19 (Berkeley) 3/30/96";
d23 1
d34 1
d36 2
a37 1
	       char *, size_t, char *, size_t *, char **, size_t *, int));
d79 1
a79 1
	memmove(exp->args[exp->argsoff]->bp, cmd, cmdlen);
d154 1
a154 1
	memmove(bp, SHELLECHO, SHELLOFFSET);
d191 2
d194 1
a194 1
			for (p = bp, n = len; n > 0; --n, ++p)
d198 1
a198 1
			for (p = bp, n = len; n > 0; --n, ++p)
d203 28
a230 1
	if (n > 0) {
d236 2
a237 3
	} else {
		p = bp + SHELLOFFSET;
		len -= SHELLOFFSET;
a239 6
#if defined(DEBUG) && 0
	TRACE(sp, "after shell: %d: {%s}\n", len, bp);
#endif

	rval = argv_exp3(sp, excp, p, len);

d302 1
a302 1
			if (IS_ESCAPE(sp, excp, *(u_char *)ap))
d330 1
a330 1
	size_t blen, len, tlen;
d345 1
d347 2
a348 1
			memmove(p, exp->lastbcomm, tlen);
d360 1
d362 2
a363 1
			memmove(p, t, tlen);
d374 1
d376 2
a377 1
			memmove(p, t, tlen);
d396 1
d398 1
d404 1
d406 1
d512 99
d641 1
d721 1
d723 2
a724 3
		*--p = '\0';
	} else
		*p = '\0';
@


1.7
log
@merge in nvi 1.66
@
text
@d1 2
@


1.6
log
@cast a few pointers to clean up warnings.  Bostic said that this
should be fixed differently, when i pointed it out to him a while ago,
but was planning to completely rework the code for a later version so
didn't bother.
@
text
@d4 2
d7 1
a7 27
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
d10 2
d13 1
a13 1
static char sccsid[] = "@@(#)ex_argv.c	8.38 (Berkeley) 8/17/94";
a17 1
#include <sys/time.h>
a22 1
#include <signal.h>
a25 1
#include <termios.h>
d28 1
a28 6
#include "compat.h"
#include <db.h>
#include <regex.h>

#include "vi.h"
#include "excmd.h"
d31 1
a31 1
static int argv_fexp __P((SCR *, EXCMDARG *,
d38 2
d42 1
a42 1
argv_init(sp, ep, excp)
d44 1
a44 2
	EXF *ep;
	EXCMDARG *excp;
d60 2
d64 1
a64 1
argv_exp0(sp, ep, excp, cmd, cmdlen)
d66 1
a66 2
	EXF *ep;
	EXCMDARG *excp;
d87 2
d91 1
a91 1
argv_exp1(sp, ep, excp, cmd, cmdlen, is_bang)
d93 1
a93 2
	EXF *ep;
	EXCMDARG *excp;
d121 2
a122 2
	(void)argv_exp0(sp, ep, excp, bp, len);
		
d131 2
d135 1
a135 1
argv_exp2(sp, ep, excp, cmd, cmdlen, is_bang)
d137 1
a137 2
	EXF *ep;
	EXCMDARG *excp;
a139 1
	int is_bang;
d157 1
a157 1
	if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, is_bang)) {
d178 1
a178 1
	 * to be there.
d180 5
a184 11
	for (p = mp = O_STR(sp, O_META); *p != '\0'; ++p)
		if (isblank(*p) || isalnum(*p))
			break;
	if (*p != '\0') {
		for (p = bp, n = len; n > 0; --n, ++p)
			if (strchr(mp, *p) != NULL)
				break;
	} else
		for (p = bp, n = len; n > 0; --n, ++p)
			if (!isblank(*p) &&
			    !isalnum(*p) && strchr(mp, *p) != NULL)
d186 10
d211 1
a211 1
	rval = argv_exp3(sp, ep, excp, p, len);
d221 2
d225 1
a225 1
argv_exp3(sp, ep, excp, cmd, cmdlen)
d227 1
a227 2
	EXF *ep;
	EXCMDARG *excp;
d257 1
a257 1
			if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
d275 1
a275 1
			if (IS_ESCAPE(sp, *(u_char *)ap))
d296 1
a296 1
	EXCMDARG *excp;
d314 1
a314 1
				    "No previous command to replace \"!\"");
d326 1
a326 1
				    "No filename to substitute for %%");
d339 1
a339 1
				    "No filename to substitute for #");
d355 2
a356 1
			if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#')) {
d406 1
a406 1
		memset(&exp->args[off], 0, INCREMENT * sizeof(ARGS *));
d447 2
d464 1
a464 1
			FREE(exp->args[off], sizeof(ARGS));
d466 1
a466 1
		FREE(exp->args, exp->argscnt * sizeof(ARGS *));
d485 1
d489 1
a489 1
	int ch, rval, output[2];
d492 6
a497 2
	bp = *bpp;
	blen = *blenp;
d505 3
d509 9
a517 5
	 * There are two different processes running through this code.
	 * They are named the utility and the parent. The utility reads
	 * from standard input and writes to the parent.  The parent reads
	 * from the utility and writes into the buffer.  The parent reads
	 * from output[0], and the utility writes to output[1].
d519 3
a521 1
	if (pipe(output) < 0) {
d525 1
a525 1
	if ((ifp = fdopen(output[0], "r")) == NULL) {
d531 2
a532 2
	 * Do the minimal amount of work possible, the shell is going
	 * to run briefly and then exit.  Hopefully.
a533 1
	SIGBLOCK(sp->gp);
a535 2
		SIGUNBLOCK(sp->gp);

d537 6
a542 2
err:		(void)close(output[0]);
		(void)close(output[1]);
d545 2
a546 6
		/* The utility has default signal behavior. */
		sig_end();

		/* Redirect stdout/stderr to the write end of the pipe. */
		(void)dup2(output[1], STDOUT_FILENO);
		(void)dup2(output[1], STDERR_FILENO);
d549 3
a551 2
		(void)close(output[0]);
		(void)close(output[1]);
d553 4
a556 1
		/* Assumes that all shells have -c. */
d558 1
a558 2
		msgq(sp, M_ERR,
		    "Error: execl: %s: %s", sh_path, strerror(errno));
d561 2
a562 4
		SIGUNBLOCK(sp->gp);

		/* Close the pipe end the parent won't use. */
		(void)close(output[1]);
a565 2
	rval = 0;

d567 1
a567 1
	 * Copy process output into a buffer.
d577 1
a577 1
			ADD_SPACE_GOTO(sp, bp, blen, *blenp * 2);
d589 21
d611 5
a615 5
	if (ferror(ifp)) {
		msgq(sp, M_ERR, "I/O error: %s", sh);
binc_err:	rval = 1;
	}
	(void)fclose(ifp);
d617 2
a618 1
	*bpp = bp;		/* *blenp is already updated. */
d620 1
a620 2
	/* Wait for the process. */
	return (proc_wait(sp, (long)pid, sh, 0) || rval);
@


1.5
log
@clean up import.
@
text
@d297 1
a297 1
			if (IS_ESCAPE(sp, *ap))
@


1.5.2.1
log
@file ex_argv.c was added on branch netbsd-1-0 on 1994-08-17 20:12:13 +0000
@
text
@d1 609
@


1.5.2.2
log
@clean up import.
@
text
@a0 609
/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
 */

#ifndef lint
static char sccsid[] = "@@(#)ex_argv.c	8.38 (Berkeley) 8/17/94";
#endif /* not lint */

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/time.h>

#include <bitstring.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include "compat.h"
#include <db.h>
#include <regex.h>

#include "vi.h"
#include "excmd.h"

static int argv_alloc __P((SCR *, size_t));
static int argv_fexp __P((SCR *, EXCMDARG *,
	       char *, size_t, char *, size_t *, char **, size_t *, int));
static int argv_sexp __P((SCR *, char **, size_t *, size_t *));

/*
 * argv_init --
 *	Build  a prototype arguments list.
 */
int
argv_init(sp, ep, excp)
	SCR *sp;
	EXF *ep;
	EXCMDARG *excp;
{
	EX_PRIVATE *exp;

	exp = EXP(sp);
	exp->argsoff = 0;
	argv_alloc(sp, 1);

	excp->argv = exp->args;
	excp->argc = exp->argsoff;
	return (0);
}

/*
 * argv_exp0 --
 *	Append a string to the argument list.
 */
int
argv_exp0(sp, ep, excp, cmd, cmdlen)
	SCR *sp;
	EXF *ep;
	EXCMDARG *excp;
	char *cmd;
	size_t cmdlen;
{
	EX_PRIVATE *exp;

	exp = EXP(sp);
	argv_alloc(sp, cmdlen);
	memmove(exp->args[exp->argsoff]->bp, cmd, cmdlen);
	exp->args[exp->argsoff]->bp[cmdlen] = '\0';
	exp->args[exp->argsoff]->len = cmdlen;
	++exp->argsoff;
	excp->argv = exp->args;
	excp->argc = exp->argsoff;
	return (0);
}

/*
 * argv_exp1 --
 *	Do file name expansion on a string, and append it to the
 *	argument list.
 */
int
argv_exp1(sp, ep, excp, cmd, cmdlen, is_bang)
	SCR *sp;
	EXF *ep;
	EXCMDARG *excp;
	char *cmd;
	size_t cmdlen;
	int is_bang;
{
	EX_PRIVATE *exp;
	size_t blen, len;
	char *bp, *p, *t;

	GET_SPACE_RET(sp, bp, blen, 512);

	len = 0;
	exp = EXP(sp);
	if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
		FREE_SPACE(sp, bp, blen);
		return (1);
	}

	/* If it's empty, we're done. */
	if (len != 0) {
		for (p = bp, t = bp + len; p < t; ++p)
			if (!isblank(*p))
				break;
		if (p == t)
			goto ret;
	} else
		goto ret;

	(void)argv_exp0(sp, ep, excp, bp, len);
		
ret:	FREE_SPACE(sp, bp, blen);
	return (0);
}

/*
 * argv_exp2 --
 *	Do file name and shell expansion on a string, and append it to
 *	the argument list.
 */
int
argv_exp2(sp, ep, excp, cmd, cmdlen, is_bang)
	SCR *sp;
	EXF *ep;
	EXCMDARG *excp;
	char *cmd;
	size_t cmdlen;
	int is_bang;
{
	size_t blen, len, n;
	int rval;
	char *bp, *mp, *p;

	GET_SPACE_RET(sp, bp, blen, 512);

#define	SHELLECHO	"echo "
#define	SHELLOFFSET	(sizeof(SHELLECHO) - 1)
	memmove(bp, SHELLECHO, SHELLOFFSET);
	p = bp + SHELLOFFSET;
	len = SHELLOFFSET;

#if defined(DEBUG) && 0
	TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
#endif

	if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, is_bang)) {
		rval = 1;
		goto err;
	}

#if defined(DEBUG) && 0
	TRACE(sp, "before shell: %d: {%s}\n", len, bp);
#endif

	/*
	 * Do shell word expansion -- it's very, very hard to figure out what
	 * magic characters the user's shell expects.  Historically, it was a
	 * union of v7 shell and csh meta characters.  We match that practice
	 * by default, so ":read \%" tries to read a file named '%'.  It would
	 * make more sense to pass any special characters through the shell,
	 * but then, if your shell was csh, the above example will behave
	 * differently in nvi than in vi.  If you want to get other characters
	 * passed through to your shell, change the "meta" option.
	 *
	 * To avoid a function call per character, we do a first pass through
	 * the meta characters looking for characters that aren't expected
	 * to be there.
	 */
	for (p = mp = O_STR(sp, O_META); *p != '\0'; ++p)
		if (isblank(*p) || isalnum(*p))
			break;
	if (*p != '\0') {
		for (p = bp, n = len; n > 0; --n, ++p)
			if (strchr(mp, *p) != NULL)
				break;
	} else
		for (p = bp, n = len; n > 0; --n, ++p)
			if (!isblank(*p) &&
			    !isalnum(*p) && strchr(mp, *p) != NULL)
				break;
	if (n > 0) {
		if (argv_sexp(sp, &bp, &blen, &len)) {
			rval = 1;
			goto err;
		}
		p = bp;
	} else {
		p = bp + SHELLOFFSET;
		len -= SHELLOFFSET;
	}

#if defined(DEBUG) && 0
	TRACE(sp, "after shell: %d: {%s}\n", len, bp);
#endif

	rval = argv_exp3(sp, ep, excp, p, len);

err:	FREE_SPACE(sp, bp, blen);
	return (rval);
}

/*
 * argv_exp3 --
 *	Take a string and break it up into an argv, which is appended
 *	to the argument list.
 */
int
argv_exp3(sp, ep, excp, cmd, cmdlen)
	SCR *sp;
	EXF *ep;
	EXCMDARG *excp;
	char *cmd;
	size_t cmdlen;
{
	EX_PRIVATE *exp;
	size_t len;
	int ch, off;
	char *ap, *p;

	for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
		/* Skip any leading whitespace. */
		for (; cmdlen > 0; --cmdlen, ++cmd) {
			ch = *cmd;
			if (!isblank(ch))
				break;
		}
		if (cmdlen == 0)
			break;

		/*
		 * Determine the length of this whitespace delimited
		 * argument.
		 *
		 * QUOTING NOTE:
		 *
		 * Skip any character preceded by the user's quoting
		 * character.
		 */
		for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
			ch = *cmd;
			if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
				++cmd;
				--cmdlen;
			} else if (isblank(ch))
				break;
		}

		/*
		 * Copy the argument into place.
		 *
		 * QUOTING NOTE:
		 *
		 * Lose quote chars.
		 */
		argv_alloc(sp, len);
		off = exp->argsoff;
		exp->args[off]->len = len;
		for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
			if (IS_ESCAPE(sp, *ap))
				++ap;
		*p = '\0';
	}
	excp->argv = exp->args;
	excp->argc = exp->argsoff;

#if defined(DEBUG) && 0
	for (cnt = 0; cnt < exp->argsoff; ++cnt)
		TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
#endif
	return (0);
}

/*
 * argv_fexp --
 *	Do file name and bang command expansion.
 */
static int
argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
	SCR *sp;
	EXCMDARG *excp;
	char *cmd, *p, **bpp;
	size_t cmdlen, *lenp, *blenp;
	int is_bang;
{
	EX_PRIVATE *exp;
	char *bp, *t;
	size_t blen, len, tlen;

	/* Replace file name characters. */
	for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
		switch (*cmd) {
		case '!':
			if (!is_bang)
				goto ins_ch;
			exp = EXP(sp);
			if (exp->lastbcomm == NULL) {
				msgq(sp, M_ERR,
				    "No previous command to replace \"!\"");
				return (1);
			}
			len += tlen = strlen(exp->lastbcomm);
			ADD_SPACE_RET(sp, bp, blen, len);
			memmove(p, exp->lastbcomm, tlen);
			p += tlen;
			F_SET(excp, E_MODIFY);
			break;
		case '%':
			if ((t = sp->frp->name) == NULL) {
				msgq(sp, M_ERR,
				    "No filename to substitute for %%");
				return (1);
			}
			tlen = strlen(t);
			len += tlen;
			ADD_SPACE_RET(sp, bp, blen, len);
			memmove(p, t, tlen);
			p += tlen;
			F_SET(excp, E_MODIFY);
			break;
		case '#':
			if ((t = sp->alt_name) == NULL) {
				msgq(sp, M_ERR,
				    "No filename to substitute for #");
				return (1);
			}
			len += tlen = strlen(t);
			ADD_SPACE_RET(sp, bp, blen, len);
			memmove(p, t, tlen);
			p += tlen;
			F_SET(excp, E_MODIFY);
			break;
		case '\\':
			/*
			 * QUOTING NOTE:
			 *
			 * Strip any backslashes that protected the file
			 * expansion characters.
			 */
			if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#')) {
				++cmd;
				--cmdlen;
			}
			/* FALLTHROUGH */
		default:
ins_ch:			++len;
			ADD_SPACE_RET(sp, bp, blen, len);
			*p++ = *cmd;
		}

	/* Nul termination. */
	++len;
	ADD_SPACE_RET(sp, bp, blen, len);
	*p = '\0';

	/* Return the new string length, buffer, buffer length. */
	*lenp = len - 1;
	*bpp = bp;
	*blenp = blen;
	return (0);
}

/*
 * argv_alloc --
 *	Make more space for arguments.
 */
static int
argv_alloc(sp, len)
	SCR *sp;
	size_t len;
{
	ARGS *ap;
	EX_PRIVATE *exp;
	int cnt, off;

	/*
	 * Allocate room for another argument, always leaving
	 * enough room for an ARGS structure with a length of 0.
	 */
#define	INCREMENT	20
	exp = EXP(sp);
	off = exp->argsoff;
	if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
		cnt = exp->argscnt + INCREMENT;
		REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
		if (exp->args == NULL) {
			(void)argv_free(sp);
			goto mem;
		}
		memset(&exp->args[off], 0, INCREMENT * sizeof(ARGS *));
		exp->argscnt = cnt;
	}

	/* First argument. */
	if (exp->args[off] == NULL) {
		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
		if (exp->args[off] == NULL)
			goto mem;
	}

	/* First argument buffer. */
	ap = exp->args[off];
	ap->len = 0;
	if (ap->blen < len + 1) {
		ap->blen = len + 1;
		REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
		if (ap->bp == NULL) {
			ap->bp = NULL;
			ap->blen = 0;
			F_CLR(ap, A_ALLOCATED);
mem:			msgq(sp, M_SYSERR, NULL);
			return (1);
		}
		F_SET(ap, A_ALLOCATED);
	}

	/* Second argument. */
	if (exp->args[++off] == NULL) {
		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
		if (exp->args[off] == NULL)
			goto mem;
	}
	/* 0 length serves as end-of-argument marker. */
	exp->args[off]->len = 0;
	return (0);
}

/*
 * argv_free --
 *	Free up argument structures.
 */
int
argv_free(sp)
	SCR *sp;
{
	EX_PRIVATE *exp;
	int off;

	exp = EXP(sp);
	if (exp->args != NULL) {
		for (off = 0; off < exp->argscnt; ++off) {
			if (exp->args[off] == NULL)
				continue;
			if (F_ISSET(exp->args[off], A_ALLOCATED))
				free(exp->args[off]->bp);
			FREE(exp->args[off], sizeof(ARGS));
		}
		FREE(exp->args, exp->argscnt * sizeof(ARGS *));
	}
	exp->args = NULL;
	exp->argscnt = 0;
	exp->argsoff = 0;
	return (0);
}

/*
 * argv_sexp --
 *	Fork a shell, pipe a command through it, and read the output into
 *	a buffer.
 */
static int
argv_sexp(sp, bpp, blenp, lenp)
	SCR *sp;
	char **bpp;
	size_t *blenp, *lenp;
{
	FILE *ifp;
	pid_t pid;
	size_t blen, len;
	int ch, rval, output[2];
	char *bp, *p, *sh, *sh_path;

	bp = *bpp;
	blen = *blenp;

	sh_path = O_STR(sp, O_SHELL);
	if ((sh = strrchr(sh_path, '/')) == NULL)
		sh = sh_path;
	else
		++sh;

	/*
	 * There are two different processes running through this code.
	 * They are named the utility and the parent. The utility reads
	 * from standard input and writes to the parent.  The parent reads
	 * from the utility and writes into the buffer.  The parent reads
	 * from output[0], and the utility writes to output[1].
	 */
	if (pipe(output) < 0) {
		msgq(sp, M_SYSERR, "pipe");
		return (1);
	}
	if ((ifp = fdopen(output[0], "r")) == NULL) {
		msgq(sp, M_SYSERR, "fdopen");
		goto err;
	}

	/*
	 * Do the minimal amount of work possible, the shell is going
	 * to run briefly and then exit.  Hopefully.
	 */
	SIGBLOCK(sp->gp);
	switch (pid = vfork()) {
	case -1:			/* Error. */
		SIGUNBLOCK(sp->gp);

		msgq(sp, M_SYSERR, "vfork");
err:		(void)close(output[0]);
		(void)close(output[1]);
		return (1);
	case 0:				/* Utility. */
		/* The utility has default signal behavior. */
		sig_end();

		/* Redirect stdout/stderr to the write end of the pipe. */
		(void)dup2(output[1], STDOUT_FILENO);
		(void)dup2(output[1], STDERR_FILENO);

		/* Close the utility's file descriptors. */
		(void)close(output[0]);
		(void)close(output[1]);

		/* Assumes that all shells have -c. */
		execl(sh_path, sh, "-c", bp, NULL);
		msgq(sp, M_ERR,
		    "Error: execl: %s: %s", sh_path, strerror(errno));
		_exit(127);
	default:			/* Parent. */
		SIGUNBLOCK(sp->gp);

		/* Close the pipe end the parent won't use. */
		(void)close(output[1]);
		break;
	}

	rval = 0;

	/*
	 * Copy process output into a buffer.
	 *
	 * !!!
	 * Historic vi apparently discarded leading \n and \r's from
	 * the shell output stream.  We don't on the grounds that any
	 * shell that does that is broken.
	 */
	for (p = bp, len = 0, ch = EOF;
	    (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
		if (blen < 5) {
			ADD_SPACE_GOTO(sp, bp, blen, *blenp * 2);
			p = bp + len;
			blen = *blenp - len;
		}

	/* Delete the final newline, nul terminate the string. */
	if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
		--len;
		*--p = '\0';
	} else
		*p = '\0';
	*lenp = len;

	if (ferror(ifp)) {
		msgq(sp, M_ERR, "I/O error: %s", sh);
binc_err:	rval = 1;
	}
	(void)fclose(ifp);

	*bpp = bp;		/* *blenp is already updated. */

	/* Wait for the process. */
	return (proc_wait(sp, (long)pid, sh, 0) || rval);
}
@


1.4
log
@clean up import.  still have to hack some things.
@
text
@d35 1
a35 1
static const char sccsid[] = "@@(#)ex_argv.c	8.37 (Berkeley) 8/17/94";
@


1.3
log
@nvi 1.11(beta) from bostic.  reconcile conflicts/kill rcsids.
@
text
@d35 1
a35 1
static char sccsid[] = "@@(#)ex_argv.c	8.28 (Berkeley) 3/14/94";
d88 1
a88 1
 *	Put a string into an argv.
d113 2
a114 1
 *	Do file name expansion on a string, and leave it in a string.
d127 1
a127 1
	char *bp;
d138 9
a146 7
	argv_alloc(sp, len);
	memmove(exp->args[exp->argsoff]->bp, bp, len);
	exp->args[exp->argsoff]->bp[len] = '\0';
	exp->args[exp->argsoff]->len = len;
	++exp->argsoff;
	excp->argv = exp->args;
	excp->argc = exp->argsoff;
d148 3
a150 1
	FREE_SPACE(sp, bp, blen);
d156 2
a157 2
 *	Do file name and shell expansion on a string, and break
 *	it up into an argv.
d170 1
a170 1
	char *bp, *p;
d194 12
a205 3
	 * Do shell word expansion -- it's very, very hard to figure out
	 * what magic characters the user's shell expects.  If it's not
	 * pure vanilla, don't even try.
d207 2
a208 2
	for (p = bp, n = len; n > 0; --n, ++p)
		if (!isalnum(*p) && !isblank(*p) && *p != '/' && *p != '.')
d210 9
d242 2
a243 1
 *	Take a string and break it up into an argv.
d297 1
a297 1
			if (IS_ESCAPE(sp, *ap)) {
a298 2
				--exp->args[off]->len;
			}
d336 1
a336 1
				    "No previous command to replace \"!\".");
d346 1
a346 1
			if (sp->frp->cname == NULL && sp->frp->name == NULL) {
d348 1
a348 1
				    "No filename to substitute for %%.");
d351 1
a351 1
			tlen = strlen(t = FILENAME(sp->frp));
d359 1
a359 6
			/*
			 * Try the alternate file name first, then the
			 * previously edited file.
			 */
			if (sp->alt_name == NULL && (sp->p_frp == NULL ||
			    sp->frp->cname == NULL && sp->frp->name == NULL)) {
d361 1
a361 1
				    "No filename to substitute for #.");
a363 4
			if (sp->alt_name != NULL)
				t = sp->alt_name;
			else
				t = FILENAME(sp->frp);
d377 1
a377 1
			if (cmdlen > 1 && cmd[1] == '%' || cmd[1] == '#')
d379 2
d539 1
d542 2
d549 3
d565 3
a567 1
	default:
d592 1
a592 1
	if (p > bp && p[-1] == '\n' || p[-1] == '\r') {
d608 1
a608 1
	return (proc_wait(sp, (long)pid, sh, 0) | rval);
@


1.2
log
@more Ids than you'll ever want.
@
text
@d2 1
a2 1
 * Copyright (c) 1993
d35 1
a35 2
/* from: static char sccsid[] = "@@(#)ex_argv.c	8.26 (Berkeley) 1/2/94"; */
static char *rcsid = "$Id$";
d39 2
d42 1
d45 3
d50 1
d53 4
a228 1
	CHAR_T vlit;
a233 1
	(void)term_key_ch(sp, K_VLNEXT, &vlit);
d246 1
a246 1
		 * argument.  
d253 4
a256 3
		for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len)
			if ((ch = *cmd) == vlit && cmdlen > 1) {
				++cmd; 
d260 2
a261 1
				
d273 1
a273 1
			if (*ap == vlit) {
d394 1
a394 1
	ARGS *ap;					
d420 2
a421 2
			goto mem;				
	}						
d424 4
a427 4
	ap = exp->args[off];			
	ap->len = 0;					
	if (ap->blen < len + 1) {			
		ap->blen = len + 1;			
d430 8
a437 8
			ap->bp = NULL;		
			ap->blen = 0;			
			F_CLR(ap, A_ALLOCATED);	
mem:			msgq(sp, M_SYSERR, NULL);	
			return (1);			
		}					
		F_SET(ap, A_ALLOCATED);		
	}						
d443 2
a444 2
			goto mem;				
	}						
d446 1
a446 1
	exp->args[off]->len = 0;			
d519 1
a519 1
		
@


1.1
log
@Initial revision
@
text
@d35 2
a36 1
static char sccsid[] = "@@(#)ex_argv.c	8.26 (Berkeley) 1/2/94";
@


1.1.1.1
log
@nvi 1.03, from ftp.cs.berkeley.edu, per keith bostic's permission.
@
text
@@


1.1.1.2
log
@nvi/nex 1.11beta from bostic.
@
text
@d2 1
a2 1
 * Copyright (c) 1993, 1994
d35 1
a35 1
static char sccsid[] = "@@(#)ex_argv.c	8.28 (Berkeley) 3/14/94";
a38 2
#include <sys/queue.h>
#include <sys/time.h>
a39 1
#include <bitstring.h>
a41 3
#include <limits.h>
#include <signal.h>
#include <stdio.h>
a43 1
#include <termios.h>
a45 4
#include "compat.h"
#include <db.h>
#include <regex.h>

d218 1
d224 1
d237 1
a237 1
		 * argument.
d244 3
a246 4
		for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
			ch = *cmd;
			if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
				++cmd;
d250 1
a250 2
		}

d262 1
a262 1
			if (IS_ESCAPE(sp, *ap)) {
d383 1
a383 1
	ARGS *ap;
d409 2
a410 2
			goto mem;
	}
d413 4
a416 4
	ap = exp->args[off];
	ap->len = 0;
	if (ap->blen < len + 1) {
		ap->blen = len + 1;
d419 8
a426 8
			ap->bp = NULL;
			ap->blen = 0;
			F_CLR(ap, A_ALLOCATED);
mem:			msgq(sp, M_SYSERR, NULL);
			return (1);
		}
		F_SET(ap, A_ALLOCATED);
	}
d432 2
a433 2
			goto mem;
	}
d435 1
a435 1
	exp->args[off]->len = 0;
d508 1
a508 1

@


1.1.1.3
log
@new public version of nvi
@
text
@d35 1
a35 1
static const char sccsid[] = "@@(#)ex_argv.c	8.37 (Berkeley) 8/17/94";
d88 1
a88 1
 *	Append a string to the argument list.
d113 1
a113 2
 *	Do file name expansion on a string, and append it to the
 *	argument list.
d126 1
a126 1
	char *bp, *p, *t;
d137 7
a143 9
	/* If it's empty, we're done. */
	if (len != 0) {
		for (p = bp, t = bp + len; p < t; ++p)
			if (!isblank(*p))
				break;
		if (p == t)
			goto ret;
	} else
		goto ret;
d145 1
a145 3
	(void)argv_exp0(sp, ep, excp, bp, len);
		
ret:	FREE_SPACE(sp, bp, blen);
d151 2
a152 2
 *	Do file name and shell expansion on a string, and append it to
 *	the argument list.
d165 1
a165 1
	char *bp, *mp, *p;
d189 3
a191 12
	 * Do shell word expansion -- it's very, very hard to figure out what
	 * magic characters the user's shell expects.  Historically, it was a
	 * union of v7 shell and csh meta characters.  We match that practice
	 * by default, so ":read \%" tries to read a file named '%'.  It would
	 * make more sense to pass any special characters through the shell,
	 * but then, if your shell was csh, the above example will behave
	 * differently in nvi than in vi.  If you want to get other characters
	 * passed through to your shell, change the "meta" option.
	 *
	 * To avoid a function call per character, we do a first pass through
	 * the meta characters looking for characters that aren't expected
	 * to be there.
d193 2
a194 2
	for (p = mp = O_STR(sp, O_META); *p != '\0'; ++p)
		if (isblank(*p) || isalnum(*p))
a195 9
	if (*p != '\0') {
		for (p = bp, n = len; n > 0; --n, ++p)
			if (strchr(mp, *p) != NULL)
				break;
	} else
		for (p = bp, n = len; n > 0; --n, ++p)
			if (!isblank(*p) &&
			    !isalnum(*p) && strchr(mp, *p) != NULL)
				break;
d219 1
a219 2
 *	Take a string and break it up into an argv, which is appended
 *	to the argument list.
d273 1
a273 1
			if (IS_ESCAPE(sp, *ap))
d275 2
d314 1
a314 1
				    "No previous command to replace \"!\"");
d324 1
a324 1
			if ((t = sp->frp->name) == NULL) {
d326 1
a326 1
				    "No filename to substitute for %%");
d329 1
a329 1
			tlen = strlen(t);
d337 6
a342 1
			if ((t = sp->alt_name) == NULL) {
d344 1
a344 1
				    "No filename to substitute for #");
d347 4
d364 1
a364 1
			if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#')) {
a365 2
				--cmdlen;
			}
a523 1
	SIGBLOCK(sp->gp);
a525 2
		SIGUNBLOCK(sp->gp);

a530 3
		/* The utility has default signal behavior. */
		sig_end();

d544 1
a544 3
	default:			/* Parent. */
		SIGUNBLOCK(sp->gp);

d569 1
a569 1
	if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
d585 1
a585 1
	return (proc_wait(sp, (long)pid, sh, 0) || rval);
@


1.1.1.4
log
@new public version of nvi
@
text
@d35 1
a35 1
static char sccsid[] = "@@(#)ex_argv.c	8.38 (Berkeley) 8/17/94";
@


1.1.1.5
log
@import of nvi 1.66
@
text
@a3 2
 * Copyright (c) 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
d5 27
a31 1
 * See the LICENSE file for redistribution information.
a33 2
#include "config.h"

d35 1
a35 1
static const char sccsid[] = "@@(#)ex_argv.c	10.19 (Berkeley) 3/30/96";
d40 1
d46 1
d50 1
d53 6
a58 1
#include "../common/common.h"
d61 1
a61 1
static int argv_fexp __P((SCR *, EXCMD *,
a67 2
 *
 * PUBLIC: int argv_init __P((SCR *, EXCMD *));
d70 1
a70 1
argv_init(sp, excp)
d72 2
a73 1
	EXCMD *excp;
a88 2
 *
 * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t));
d91 1
a91 1
argv_exp0(sp, excp, cmd, cmdlen)
d93 2
a94 1
	EXCMD *excp;
a114 2
 *
 * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int));
d117 1
a117 1
argv_exp1(sp, excp, cmd, cmdlen, is_bang)
d119 2
a120 1
	EXCMD *excp;
d148 2
a149 2
	(void)argv_exp0(sp, excp, bp, len);

a157 2
 *
 * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t));
d160 1
a160 1
argv_exp2(sp, excp, cmd, cmdlen)
d162 2
a163 1
	EXCMD *excp;
d166 1
d184 1
a184 1
	if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
d205 1
a205 1
	 * to be there, and then we can ignore them in the user's argument.
d207 11
a217 5
	if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
		n = 0;
	else {
		for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p)
			if (isblank(*p) || isalnum(*p))
a218 10
		if (*p != '\0') {
			for (p = bp, n = len; n > 0; --n, ++p)
				if (strchr(mp, *p) != NULL)
					break;
		} else
			for (p = bp, n = len; n > 0; --n, ++p)
				if (!isblank(*p) &&
				    !isalnum(*p) && strchr(mp, *p) != NULL)
					break;
	}
d234 1
a234 1
	rval = argv_exp3(sp, excp, p, len);
a243 2
 *
 * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t));
d246 1
a246 1
argv_exp3(sp, excp, cmd, cmdlen)
d248 2
a249 1
	EXCMD *excp;
d279 1
a279 1
			if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
d297 1
a297 1
			if (IS_ESCAPE(sp, excp, *ap))
d318 1
a318 1
	EXCMD *excp;
d336 1
a336 1
				    "115|No previous command to replace \"!\"");
d348 1
a348 1
				    "116|No filename to substitute for %%");
d361 1
a361 1
				    "117|No filename to substitute for #");
d377 1
a377 2
			if (cmdlen > 1 &&
			    (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
d427 1
a427 1
		memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
a467 2
 *
 * PUBLIC: int argv_free __P((SCR *));
d483 1
a483 1
			free(exp->args[off]);
d485 1
a485 1
		free(exp->args);
a503 1
	enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
d507 1
a507 1
	int ch, std_output[2];
d510 2
a511 6
	/* Secure means no shell access. */
	if (O_ISSET(sp, O_SECURE)) {
		msgq(sp, M_ERR,
"289|Shell expansions not supported when the secure edit option is set");
		return (1);
	}
a518 3
	bp = *bpp;
	blen = *blenp;

d520 5
a524 9
	 * There are two different processes running through this code, named
	 * the utility (the shell) and the parent. The utility reads standard
	 * input and writes standard output and standard error output.  The
	 * parent writes to the utility, reads its standard output and ignores
	 * its standard error output.  Historically, the standard error output
	 * was discarded by vi, as it produces a lot of noise when file patterns
	 * don't match.
	 *
	 * The parent reads std_output[0], and the utility writes std_output[1].
d526 1
a526 3
	ifp = NULL;
	std_output[0] = std_output[1] = -1;
	if (pipe(std_output) < 0) {
d530 1
a530 1
	if ((ifp = fdopen(std_output[0], "r")) == NULL) {
d536 2
a537 2
	 * Do the minimal amount of work possible, the shell is going to run
	 * briefly and then exit.  We sincerely hope.
d539 1
d542 2
d545 2
a546 6
err:		if (ifp != NULL)
			(void)fclose(ifp);
		else if (std_output[0] != -1)
			close(std_output[0]);
		if (std_output[1] != -1)
			close(std_output[0]);
d549 6
a554 2
		/* Redirect stdout to the write end of the pipe. */
		(void)dup2(std_output[1], STDOUT_FILENO);
d557 2
a558 3
		(void)close(std_output[0]);
		(void)close(std_output[1]);
		(void)close(STDERR_FILENO);
d560 1
a560 4
		/*
		 * XXX
		 * Assume that all shells have -c.
		 */
d562 2
a563 1
		msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
d566 4
a569 2
		/* Close the pipe ends the parent won't use. */
		(void)close(std_output[1]);
d573 2
d576 1
a576 1
	 * Copy process standard output into a buffer.
d586 1
a586 1
			ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);
a597 1
	*bpp = bp;		/* *blenp is already updated. */
d599 5
a603 7
	if (ferror(ifp))
		goto ioerr;
	if (fclose(ifp)) {
ioerr:		msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
alloc_err:	rval = SEXP_ERR;
	} else
		rval = SEXP_OK;
d605 1
a605 20
	/*
	 * Wait for the process.  If the shell process fails (e.g., "echo $q"
	 * where q wasn't a defined variable) or if the returned string has
	 * no characters or only blank characters, (e.g., "echo $5"), complain
	 * that the shell expansion failed.  We can't know for certain that's
	 * the error, but it's a good guess, and it matches historic practice.
	 * This won't catch "echo foo_$5", but that's not a common error and
	 * historic vi didn't catch it either.
	 */
	if (proc_wait(sp, (long)pid, sh, 1, 0))
		rval = SEXP_EXPANSION_ERR;

	for (p = bp; len; ++p, --len)
		if (!isblank(*p))
			break;
	if (len == 0)
		rval = SEXP_EXPANSION_ERR;

	if (rval == SEXP_EXPANSION_ERR)
		msgq(sp, M_ERR, "304|Shell expansion failed");
d607 2
a608 1
	return (rval == SEXP_OK ? 0 : 1);
@


1.1.1.6
log
@import of nvi 1.79
@
text
@d13 1
a13 1
static const char sccsid[] = "@@(#)ex_argv.c	10.26 (Berkeley) 9/20/96";
a20 1
#include <dirent.h>
a30 1
static int argv_comp __P((const void *, const void *));
d32 1
a32 2
	char *, size_t, char *, size_t *, char **, size_t *, int));
static int argv_lexp __P((SCR *, EXCMD *, char *));
d74 1
a74 1
	memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen);
d149 1
a149 1
	memcpy(bp, SHELLECHO, SHELLOFFSET);
a185 2
		p = bp + SHELLOFFSET;
		n = len - SHELLOFFSET;
d187 1
a187 1
			for (; n > 0; --n, ++p)
d191 1
a191 1
			for (; n > 0; --n, ++p)
d196 1
a196 28

	/*
	 * If we found a meta character in the string, fork a shell to expand
	 * it.  Unfortunately, this is comparatively slow.  Historically, it
	 * didn't matter much, since users don't enter meta characters as part
	 * of pathnames that frequently.  The addition of filename completion
	 * broke that assumption because it's easy to use.  As a result, lots
	 * folks have complained that the expansion code is too slow.  So, we
	 * detect filename completion as a special case, and do it internally.
	 * Note that this code assumes that the <asterisk> character is the
	 * match-anything meta character.  That feels safe -- if anyone writes
	 * a shell that doesn't follow that convention, I'd suggest giving them
	 * a festive hot-lead enema.
	 */
	switch (n) {
	case 0:
		p = bp + SHELLOFFSET;
		len -= SHELLOFFSET;
		rval = argv_exp3(sp, excp, p, len);
		break;
	case 1:
		if (*p == '*') {
			*p = '\0';
			rval = argv_lexp(sp, excp, bp + SHELLOFFSET);
			break;
		}
		/* FALLTHROUGH */
	default:
d202 3
a204 2
		rval = argv_exp3(sp, excp, p, len);
		break;
d207 6
d303 1
a303 1
	size_t blen, len, off, tlen;
a317 1
			off = p - bp;
d319 1
a319 2
			p = bp + off;
			memcpy(p, exp->lastbcomm, tlen);
a330 1
			off = p - bp;
d332 1
a332 2
			p = bp + off;
			memcpy(p, t, tlen);
a342 1
			off = p - bp;
d344 1
a344 2
			p = bp + off;
			memcpy(p, t, tlen);
a362 1
			off = p - bp;
a363 1
			p = bp + off;
a368 1
	off = p - bp;
a369 1
	p = bp + off;
a474 99
 * argv_lexp --
 *	Find all file names matching the prefix and append them to the
 *	buffer.
 */
static int
argv_lexp(sp, excp, path)
	SCR *sp;
	EXCMD *excp;
	char *path;
{
	struct dirent *dp;
	DIR *dirp;
	EX_PRIVATE *exp;
	int off;
	size_t dlen, len, nlen;
	char *dname, *name, *p;

	exp = EXP(sp);

	/* Set up the name and length for comparison. */
	if ((p = strrchr(path, '/')) == NULL) {
		dname = ".";
		dlen = 0;
		name = path;
	} else { 
		if (p == path) {
			dname = "/";
			dlen = 1;
		} else {
			*p = '\0';
			dname = path;
			dlen = strlen(path);
		}
		name = p + 1;
	}
	nlen = strlen(name);

	/*
	 * XXX
	 * We don't use the d_namlen field, it's not portable enough; we
	 * assume that d_name is nul terminated, instead.
	 */
	if ((dirp = opendir(dname)) == NULL) {
		msgq_str(sp, M_SYSERR, dname, "%s");
		return (1);
	}
	for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
		if (nlen == 0) {
			if (dp->d_name[0] == '.')
				continue;
			len = strlen(dp->d_name);
		} else {
			len = strlen(dp->d_name);
			if (len < nlen || memcmp(dp->d_name, name, nlen))
				continue;
		}

		/* Directory + name + slash + null. */
		argv_alloc(sp, dlen + len + 2);
		p = exp->args[exp->argsoff]->bp;
		if (dlen != 0) {
			memcpy(p, dname, dlen);
			p += dlen;
			if (dlen > 1 || dname[0] != '/')
				*p++ = '/';
		}
		memcpy(p, dp->d_name, len + 1);
		exp->args[exp->argsoff]->len = dlen + len + 1;
		++exp->argsoff;
		excp->argv = exp->args;
		excp->argc = exp->argsoff;
	}
	closedir(dirp);

	if (off == exp->argsoff) {
		/*
		 * If we didn't find a match, complain that the expansion
		 * failed.  We can't know for certain that's the error, but
		 * it's a good guess, and it matches historic practice. 
		 */
		msgq(sp, M_ERR, "304|Shell expansion failed");
		return (1);
	}
	qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
	return (0);
}

/*
 * argv_comp --
 *	Alphabetic comparison.
 */
static int
argv_comp(a, b)
	const void *a, *b;
{
	return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));
}

/*
a504 1
	/* Local copies of the buffer variables. */
a583 1
		--p;
d585 3
a587 2
	}
	*p = '\0';
@

