summaryrefslogtreecommitdiff
path: root/usr/src/cmd/xargs
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/xargs
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/xargs')
-rw-r--r--usr/src/cmd/xargs/Makefile68
-rw-r--r--usr/src/cmd/xargs/xargs.c1405
2 files changed, 1473 insertions, 0 deletions
diff --git a/usr/src/cmd/xargs/Makefile b/usr/src/cmd/xargs/Makefile
new file mode 100644
index 0000000000..b6b91fd9cf
--- /dev/null
+++ b/usr/src/cmd/xargs/Makefile
@@ -0,0 +1,68 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+PROG= xargs
+XPG6PROG= xargs
+XD= exobjs.xpg6
+EXOBJS= xargs.o
+XPG6EXOBJS= exobjs.xpg6/xargs.o
+
+include ../Makefile.cmd
+
+.KEEP_STATE:
+
+all: $(PROG) $(XPG6)
+
+$(PROG): $(EXOBJS)
+ $(LINK.c) -o $@ $(EXOBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+
+$(XPG6): $(XD) $(XPG6EXOBJS)
+ $(LINK.c) -o $@ $(XPG6EXOBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+install: all $(ROOTPROG) $(ROOTXPG6PROG)
+
+clean:
+ -@rm -rf $(EXOBJS) $(XD)
+
+lint: lint_PROG
+
+$(XPG6EXOBJS): $(XD)
+
+$(XD)/%.o: %.c
+ $(COMPILE.c) -o $@ $<
+
+$(XD):
+ -@mkdir -p $@
+
+
+include ../Makefile.targ
+
+$(XPG6) := CFLAGS += -DXPG6
diff --git a/usr/src/cmd/xargs/xargs.c b/usr/src/cmd/xargs/xargs.c
new file mode 100644
index 0000000000..9b3d85ed39
--- /dev/null
+++ b/usr/src/cmd/xargs/xargs.c
@@ -0,0 +1,1405 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <wchar.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stropts.h>
+#include <poll.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#define HEAD 0
+#define TAIL 1
+#define FALSE 0
+#define TRUE 1
+#define MAXSBUF 255
+#define MAXIBUF 512
+#define MAXINSERTS 5
+#define BUFSIZE LINE_MAX
+#define MAXARGS 255
+#define INSPAT_STR "{}" /* default replstr string for -[Ii] */
+#define FORK_RETRY 5
+
+#define QBUF_STARTLEN 255 /* start size of growable string buffer */
+#define QBUF_INC 100 /* how much to grow a growable string by */
+
+static wctype_t blank;
+static char *arglist[MAXARGS+1];
+static char argbuf[BUFSIZE+1];
+static char *next = argbuf;
+static char *lastarg = "";
+static char **ARGV = arglist;
+static char *LEOF = "_";
+static char *INSPAT = INSPAT_STR;
+static char ins_buf[MAXIBUF];
+static char *p_ibuf;
+
+static struct inserts {
+ char **p_ARGV; /* where to put newarg ptr in arg list */
+ char *p_skel; /* ptr to arg template */
+} saveargv[MAXINSERTS];
+
+static off_t file_offset = 0;
+static int PROMPT = -1;
+static int BUFLIM = BUFSIZE;
+static int N_ARGS = 0;
+static int N_args = 0;
+static int N_lines = 0;
+static int DASHX = FALSE;
+static int MORE = TRUE;
+static int PER_LINE = FALSE;
+static int ERR = FALSE;
+static int OK = TRUE;
+static int LEGAL = FALSE;
+static int TRACE = FALSE;
+static int INSERT = FALSE;
+static int linesize = 0;
+static int ibufsize = 0;
+static char *yesstr; /* the string contains int'l for "yes" */
+static int exitstat = 0; /* our exit status */
+static int mac; /* modified argc, after parsing */
+static char **mav; /* modified argv, after parsing */
+static int n_inserts; /* # of insertions. */
+static int inquote = 0; /* processing a quoted string */
+
+/*
+ * the pio structure is used to save any pending input before the
+ * user replies to a prompt. the pending input is saved here,
+ * for the appropriate processing later.
+ */
+typedef struct pio {
+ struct pio *next; /* next in stack */
+ char *start; /* starting addr of the buffer */
+ char *cur; /* ptr to current char in buf */
+ size_t length; /* number of bytes remaining */
+} pio;
+
+static pio *queued_data = NULL;
+
+/* our usage message: */
+#define USAGEMSG "Usage: xargs: [-t] [-p] [-e[eofstr]] [-E eofstr] "\
+ "[-I replstr] [-i[replstr]] [-L #] [-l[#]] [-n # [-x]] [-s size] "\
+ "[cmd [args ...]]\n"
+
+static int echoargs();
+static int getchr(void);
+static wchar_t getwchr(void);
+static void ungetwchr(wchar_t);
+static int lcall(char *sub, char **subargs);
+static int xindex(char *as1, char *as2);
+static void addibuf(struct inserts *p);
+static void ermsg(char *messages, ...);
+static char *addarg(char *arg);
+static char *checklen(char *arg);
+static size_t store_wchr(char **, size_t *, size_t, wchar_t);
+static char *getarg();
+static char *insert(char *pattern, char *subst);
+static void usage();
+static void parseargs();
+static void saveinput();
+
+
+void
+main(int argc, char **argv)
+{
+ int j;
+ struct inserts *psave;
+ int c;
+ int initsize;
+ char *cmdname, *initbuf, **initlist;
+
+
+ /* initialization */
+
+ blank = wctype("blank");
+ n_inserts = 0;
+ psave = saveargv;
+ (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);
+
+ /*
+ * now we get the appropriate "yes" string for our locale.
+ * since this may be a multibyte character, we store the
+ * string which is returned. later on, when we're looking for
+ * a "y" in response to our prompt, we'll use the first
+ * multibyte character of yesstr as a comparision.
+ */
+ initbuf = nl_langinfo(YESSTR); /* initbuf is a tmp placeholder here */
+ if ((yesstr = malloc(strlen(initbuf) + 1)) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+ (void) strcpy(yesstr, initbuf);
+
+ parseargs(argc, argv);
+
+ /* handling all of xargs arguments: */
+ while ((c = getopt(mac, mav, "tpe:E:I:i:L:l:n:s:x")) != EOF) {
+ switch (c) {
+ case 't': /* -t: turn trace mode on */
+ TRACE = TRUE;
+ break;
+
+ case 'p': /* -p: turn on prompt mode. */
+ if ((PROMPT = open("/dev/tty", O_RDONLY)) == -1) {
+ perror(gettext("can't read from tty for -p"));
+ } else {
+ TRACE = TRUE;
+ }
+ break;
+
+ case 'e':
+ /*
+ * -e[eofstr]: set/disable end-of-file.
+ * N.B. that an argument *isn't* required here; but
+ * parseargs forced an argument if not was given. The
+ * forced argument is the default...
+ */
+ LEOF = optarg; /* can be empty */
+ break;
+
+ case 'E':
+ /*
+ * -E eofstr: change end-of-file string.
+ * eofstr *is* required here:
+ */
+ LEOF = optarg;
+#ifdef XPG6
+ if (LEOF == NULL) {
+#else
+ if ((LEOF == NULL) || (*LEOF == NULL)) {
+#endif
+ ermsg(gettext("Must specify subargment to -E "
+ " flag\n"));
+ }
+ break;
+
+ case 'I':
+ /* -I replstr: Insert mode. replstr *is* required. */
+ INSERT = PER_LINE = LEGAL = TRUE;
+ N_ARGS = 0;
+ if (*optarg) {
+ INSPAT = optarg;
+ } else {
+ ermsg(gettext("Must specify subargment "
+ "for -I\n"));
+ }
+ break;
+
+ case 'i':
+ /*
+ * -i [replstr]: insert mode, with *optional* replstr.
+ * N.B. that an argument *isn't* required here; if
+ * it's not given, then the string INSPAT_STR will
+ * be assumed.
+ *
+ * Since getopts(3C) doesn't handle the case of an
+ * optional variable argument at all, we have to
+ * parse this by hand:
+ */
+
+ INSERT = PER_LINE = LEGAL = TRUE;
+ N_ARGS = 0;
+ if (optarg[0] != NULL) {
+ INSPAT = optarg;
+ } else {
+ /*
+ * here, there is no next argument. so
+ * we reset INSPAT to the INSPAT_STR.
+ * we *have* to do this, as -i/I may have
+ * been given previously, and XCU4 requires
+ * that only "the last one specified takes
+ * effect".
+ */
+ INSPAT = INSPAT_STR;
+ }
+ break;
+
+ case 'L':
+ /*
+ * -L number: # of times cmd is executed
+ * number *is* required here:
+ */
+ PER_LINE = TRUE;
+ N_ARGS = 0;
+ INSERT = FALSE;
+ if (optarg && (PER_LINE = atoi(optarg)) <= 0) {
+ ermsg(gettext("#lines must be positive "
+ "int: %s\n"), optarg);
+ }
+ break;
+
+ case 'l':
+ /*
+ * -l [number]: # of times cmd is executed
+ * N.B. that an argument *isn't* required here; if
+ * it's not given, then 1 is assumed.
+ *
+ * parseargs handles the optional arg processing.
+ */
+
+ PER_LINE = LEGAL = TRUE; /* initialization */
+ N_ARGS = 0;
+ INSERT = FALSE;
+
+ if (optarg[0] != NULL) {
+ if ((PER_LINE = atoi(optarg)) <= 0)
+ PER_LINE = 1;
+ }
+ break;
+
+ case 'n': /* -n number: # stdin args */
+ /*
+ * -n number: # stdin args.
+ * number *is* required here:
+ */
+ if ((N_ARGS = atoi(optarg)) <= 0) {
+ ermsg(gettext("#args must be positive "
+ "int: %s\n"), optarg);
+ } else {
+ LEGAL = DASHX || N_ARGS == 1;
+ INSERT = PER_LINE = FALSE;
+ }
+ break;
+
+ case 's': /* -s size: set max size of each arg list */
+ BUFLIM = atoi(optarg);
+ if (BUFLIM > BUFSIZE || BUFLIM <= 0) {
+ ermsg(gettext("0 < max-cmd-line-size <= %d: "
+ "%s\n"), BUFSIZE, optarg);
+ }
+ break;
+
+ case 'x': /* -x: terminate if args > size limit */
+ DASHX = LEGAL = TRUE;
+ break;
+
+ default:
+ /*
+ * bad argument. complain and get ready to die.
+ */
+ ERR = TRUE;
+ usage();
+
+ exit(2);
+ break;
+ }
+ }
+
+ /*
+ * if anything called ermsg(), something screwed up, so
+ * we exit early.
+ */
+ if (OK == FALSE) {
+ ERR = TRUE;
+ usage();
+ exit(2);
+ }
+
+ /*
+ * we're finished handling xargs's options, so now pick up
+ * the command name (if any), and it's options.
+ */
+
+
+ mac -= optind; /* dec arg count by what we've processed */
+ mav += optind; /* inc to current mav */
+
+ if (mac <= 0) { /* if there're no more args to process, */
+ cmdname = "/usr/bin/echo"; /* our default command */
+ *ARGV++ = addarg(cmdname); /* use the default cmd. */
+ } else { /* otherwise keep parsing rest of the string. */
+ /*
+ * note that we can't use getopts(3C), and *must* parse
+ * this by hand, as we don't know apriori what options the
+ * command will take.
+ */
+ cmdname = *mav; /* get the command name */
+
+
+ /* pick up the remaining args from the command line: */
+ while ((OK == TRUE) && (mac-- > 0)) {
+ /*
+ * while we haven't crapped out, and there's
+ * work to do:
+ */
+ if (INSERT && ! ERR) {
+ if (xindex(*mav, INSPAT) != -1) {
+ if (++n_inserts > MAXINSERTS) {
+ ermsg(gettext("too many args "
+ "with %s\n"), INSPAT);
+ ERR = TRUE;
+ }
+ psave->p_ARGV = ARGV;
+ (psave++)->p_skel = *mav;
+ }
+ }
+ *ARGV++ = addarg(*mav++);
+ }
+ }
+
+ /* pick up args from standard input */
+
+ initbuf = next;
+ initlist = ARGV;
+ initsize = linesize;
+
+ while (OK && MORE) {
+ N_args = 0;
+ N_lines = 0;
+ next = initbuf;
+ ARGV = initlist;
+ linesize = initsize;
+ if (*lastarg) {
+ *ARGV++ = addarg(lastarg);
+ lastarg = "";
+ }
+
+ while (((ARGV - arglist) < MAXARGS) &&
+ ((*ARGV++ = getarg()) != NULL) && OK)
+ ;
+
+ /* insert arg if requested */
+
+ if (!ERR && INSERT) {
+ if ((!MORE) && (N_lines == 0)) {
+ exit(exitstat);
+ }
+ /* no more input lines */
+ p_ibuf = ins_buf;
+ ARGV--;
+ j = ibufsize = 0;
+ for (psave = saveargv; ++j <= n_inserts; ++psave) {
+ addibuf(psave);
+ if (ERR)
+ break;
+ }
+ }
+ *ARGV = 0;
+
+ if (n_inserts > 0) {
+ int t_ninserts;
+
+ /*
+ * if we've done any insertions, re-calculate the
+ * linesize. bomb out if we've exceeded our length.
+ */
+ t_ninserts = n_inserts;
+ n_inserts = 0; /* inserts have been done */
+ linesize = 0; /* recalculate this */
+
+ /* for each current argument in the list: */
+ for (ARGV = arglist; *ARGV != NULL; ARGV++) {
+ /* recalculate everything. */
+ if (checklen(*ARGV) != 0) {
+ if (N_ARGS && (N_args >= N_ARGS)) {
+ N_lines = N_args = 0;
+ OK = FALSE;
+ ERR = TRUE;
+ }
+ }
+ }
+ n_inserts = t_ninserts;
+ }
+
+ /* exec command */
+
+ if (!ERR) {
+ if (!MORE &&
+ (PER_LINE && N_lines == 0 || N_ARGS && N_args == 0))
+ exit(exitstat);
+ OK = TRUE;
+ j = TRACE ? echoargs() : TRUE;
+ if (j) {
+ /*
+ * for xcu4, all invocations of cmdname must
+ * return 0, in order for us to return 0.
+ * so if we have a non-zero status here,
+ * quit immediately.
+ */
+ if ((exitstat |= lcall(cmdname, arglist)) == 0)
+ continue;
+ }
+ }
+ }
+
+ (void) lseek(0, file_offset, SEEK_SET);
+ if (OK) {
+ exit(exitstat);
+ } else {
+ /*
+ * if exitstat was set, to match XCU4 complience,
+ * return that value, otherwise, return 1.
+ */
+ exit(exitstat ? exitstat : 1);
+ }
+}
+
+static void
+queue(char *buffer, int len, int where)
+{
+ pio *new, *element;
+
+ if ((new = malloc(sizeof (pio))) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+ new->cur = new->start = buffer;
+ new->length = len;
+
+ if (where == TAIL) {
+ new->next = NULL;
+ if (queued_data == NULL) {
+ queued_data = new;
+ } else {
+ element = queued_data;
+ while (element->next != NULL) {
+ element = element->next;
+ }
+ element->next = new;
+ }
+ } else {
+ file_offset -= len;
+ new->next = queued_data;
+ queued_data = new;
+ }
+}
+
+static char *
+checklen(char *arg)
+{
+ int oklen;
+
+ oklen = TRUE;
+ linesize += strlen(arg) + 1;
+ if (linesize >= BUFLIM) {
+ /*
+ * we skip this if there're inserts. we'll handle the
+ * argument counting after all the insertions have
+ * been done.
+ */
+ if (n_inserts == 0) {
+ lastarg = arg;
+ oklen = OK = FALSE;
+
+ if (LEGAL) {
+ ERR = TRUE;
+ ermsg(gettext("arg list too long\n"));
+ } else if (N_args > 1) {
+ N_args = 1;
+ } else {
+ ermsg(gettext("a single arg was greater than "
+ "the max arglist size of %d characters\n"),
+ BUFLIM);
+ ERR = TRUE;
+ }
+ }
+ }
+ return (oklen ? arg : 0);
+}
+
+static char *
+addarg(char *arg)
+{
+ if (checklen(arg) != 0) {
+ (void) strcpy(next, arg);
+ arg = next;
+ next += strlen(arg) + 1;
+ return (arg);
+ }
+ return ((char *)0);
+}
+
+/*
+ * store_wchr() : append a wchar_t to a char buffer, resize buffer if required.
+ *
+ * Given a pointer to the beginning of a string buffer, the length of the
+ * buffer and an offset indicating the next place to write within that
+ * buffer, the passed wchar_t will be appended to the buffer if there is
+ * enough space. If there is not enough space, an attempt to reallocate the
+ * buffer will be made and if successful the passed pointer and size will be
+ * updated to describe the reallocated block. Returns the new value for
+ * 'offset' (it will be incremented by the number of bytes written).
+ */
+static size_t
+store_wchr(char **buffer, size_t *buflen, size_t offset, wchar_t c)
+{
+ int bytes;
+
+ /*
+ * Make sure that there is enough room in the buffer to store the
+ * maximum length of c.
+ */
+ if ((offset + MB_CUR_MAX) > *buflen) {
+ /*
+ * Not enough room so attempt to reallocate. Add 'MB_CUR_MAX' to
+ * buffer length to ensure that there is always enough room to
+ * store 'c' if realloc succeeds, no matter what QBUF_INC is
+ * defined as.
+ */
+ *buflen += (QBUF_INC + MB_CUR_MAX);
+ if ((*buffer = realloc(*buffer, *buflen)) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+ }
+ /* store bytes from wchar into buffer */
+ bytes = wctomb(*buffer + offset, c);
+ if (bytes == -1) {
+ /* char was invalid */
+ bytes = 1;
+ *(*buffer + offset) = (char)c;
+ }
+
+ /* return new value for offset */
+ return (offset + bytes);
+}
+
+static char *
+getarg()
+{
+ int bytes;
+ wchar_t c;
+ char *arg;
+ char *retarg, *requeue_buf;
+ size_t requeue_offset = 0, requeue_len;
+ char mbc[MB_LEN_MAX];
+
+ while (iswspace(c = getwchr()) || c == '\n')
+ ;
+
+ if (c == '\0') {
+ MORE = FALSE;
+ return (0);
+ }
+
+ /*
+ * While we are reading in an argument, it is possible that we will
+ * reach the maximum length of the overflow buffer and we'll have to
+ * requeue what we have read so far. To handle this we allocate an
+ * initial buffer here which will keep an unprocessed copy of the data
+ * that we read in (this buffer will grow as required).
+ */
+ requeue_len = (size_t)QBUF_STARTLEN;
+ if ((requeue_buf = (char *)malloc(requeue_len)) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+
+ for (arg = next; ; c = getwchr()) {
+ bytes = wctomb(mbc, c);
+
+ /*
+ * Store the char that we have read before processing it in case
+ * the current argument needs to be requeued.
+ */
+ requeue_offset = store_wchr(&requeue_buf, &requeue_len,
+ requeue_offset, c);
+
+ /* Check for overflow the input buffer */
+ if ((next + ((bytes == -1) ? 1 : bytes)) >= &argbuf[BUFLIM]) {
+ /*
+ * It's only an error if there are no Args in buffer
+ * already.
+ */
+ if ((N_ARGS || PER_LINE) && LEGAL) {
+ ERR = TRUE;
+ ermsg(gettext("Argument list too long\n"));
+ free(requeue_buf);
+ return (0);
+ } else if (N_args == 0) {
+ lastarg = "";
+ ERR = TRUE;
+ ermsg(gettext("A single arg was greater than "
+ "the max arglist size of %d characters\n"),
+ BUFSIZE);
+ free(requeue_buf);
+ return (0);
+ }
+ /*
+ * Otherwise we put back the current argument
+ * and use what we have collected so far...
+ */
+ queue(requeue_buf, requeue_offset, HEAD);
+ /* reset inquote because we have requeued the quotes */
+ inquote = 0;
+ return (NULL);
+ }
+
+
+ if (iswctype(c, blank) && inquote == 0) {
+ if (INSERT) {
+ if (bytes == -1) {
+ *next++ = (char)c;
+ } else {
+ (void) wctomb(next, c);
+ next += bytes;
+ }
+ continue;
+ }
+
+ /* skip over trailing whitespace till next arg */
+ while (iswctype((c = getwchr()), blank) &&
+ (c != '\n') && (c != '\0'))
+ ;
+
+ /*
+ * if there was space till end of line then the last
+ * character was really a newline...
+ */
+ if (c == L'\n' || c == L'\0') {
+ ungetwchr(L'\n');
+ } else {
+ /* later code needs to know this was a space */
+ ungetwchr(c);
+ c = L' ';
+ }
+ goto end_arg;
+ }
+ switch (c) {
+ case L'\0':
+ case L'\n':
+ if (inquote) {
+ *next++ = '\0';
+ ermsg(gettext("Missing quote: %s\n"), arg);
+ ERR = TRUE;
+ free(requeue_buf);
+ return (0);
+ }
+
+ N_lines++;
+end_arg: *next++ = '\0';
+ /* we finished without requeuing so free requeue_buf */
+ free(requeue_buf);
+ if (strcmp(arg, LEOF) == 0 || (c == '\0' &&
+ strlen(arg) == 0)) {
+ MORE = FALSE;
+ /* absorb the rest of the line */
+ if ((c != '\n') && (c != '\0'))
+ while (c = getwchr())
+ if ((c == '\n') || (c == '\0'))
+ break;
+ return (0);
+ } else {
+ ++N_args;
+ if (retarg = checklen(arg)) {
+ if ((PER_LINE &&
+ N_lines >= PER_LINE &&
+ (c == '\0' || c == '\n')) ||
+ (N_ARGS && N_args >= N_ARGS)) {
+ N_lines = N_args = 0;
+ lastarg = "";
+ OK = FALSE;
+ }
+ }
+ return (retarg);
+ }
+
+ case '"':
+ if (inquote == 1) /* in single quoted string */
+ goto is_default;
+ if (inquote == 2) /* terminating double quote */
+ inquote = 0;
+ else /* starting quoted string */
+ inquote = 2;
+ break;
+
+ case '\'':
+ if (inquote == 2) /* in double quoted string */
+ goto is_default;
+ if (inquote == 1) /* terminating single quote */
+ inquote = 0;
+ else /* starting quoted string */
+ inquote = 1;
+ break;
+
+ case L'\\':
+ c = getwchr();
+ /* store quoted char for potential requeueing */
+ requeue_offset = store_wchr(&requeue_buf, &requeue_len,
+ requeue_offset, c);
+
+ default:
+is_default: if (bytes == -1) {
+ *next++ = (char)c;
+ } else {
+ (void) wctomb(next, c);
+ next += bytes;
+ }
+ break;
+ }
+ }
+}
+
+
+/*
+ * ermsg(): print out an error message, and indicate failure globally.
+ *
+ * Assumes that message has already been gettext()'d. It would be
+ * nice if we could just do the gettext() here, but we can't, since
+ * since xgettext(1M) wouldn't be able to pick up our error message.
+ */
+/* PRINTFLIKE1 */
+static void
+ermsg(char *messages, ...)
+{
+ va_list ap;
+
+ va_start(ap, messages);
+
+ (void) fprintf(stderr, "xargs: ");
+ (void) vfprintf(stderr, messages, ap);
+
+ va_end(ap);
+ OK = FALSE;
+}
+
+
+/*
+ * Function: int rpmatch(char *)
+ *
+ * Description:
+ *
+ * Internationalized get yes / no answer.
+ *
+ * Inputs:
+ * s -> Pointer to answer to compare against.
+ *
+ * Returns:
+ * TRUE -> Answer was affirmative
+ * FALSE -> Answer was negative
+ */
+
+static int
+rpmatch(char *s)
+{
+ static char *default_yesexpr = "^[Yy].*";
+ static char *compiled_yesexpr = (char *)NULL;
+
+ /* Execute once to initialize */
+ if (compiled_yesexpr == (char *)NULL) {
+ char *yesexpr;
+
+ /* get yes expression according to current locale */
+ yesexpr = nl_langinfo(YESEXPR);
+ /*
+ * If the was no expression or if there is a compile error
+ * use default yes expression. Anchor
+ */
+ if ((yesexpr == (char *)NULL) || (*yesexpr == (char)NULL) ||
+ ((compiled_yesexpr =
+ regcmp(yesexpr, 0)) == NULL))
+ compiled_yesexpr = regcmp(default_yesexpr, 0);
+ }
+
+ /* match yesexpr */
+ if (regex(compiled_yesexpr, s) == NULL) {
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+static int
+echoargs()
+{
+ char **anarg;
+ char **tanarg; /* tmp ptr */
+ int i;
+ char reply[LINE_MAX];
+
+ tanarg = anarg = arglist-1;
+
+ /*
+ * write out each argument, separated by a space. the tanarg
+ * nonsense is for xcu4 testsuite compliance - so that an
+ * extra space isn't echoed after the last argument.
+ */
+ while (*++anarg) { /* while there's an argument */
+ ++tanarg; /* follow anarg */
+ (void) write(2, *anarg, strlen(*anarg));
+
+ if (*++tanarg) { /* if there's another argument: */
+ (void) write(2, " ", 1); /* add a space */
+ --tanarg; /* reset back to anarg */
+ }
+ }
+ if (PROMPT == -1) {
+ (void) write(2, "\n", 1);
+ return (TRUE);
+ }
+
+ /*
+ * at this point, there may be unexpected input pending on stdin,
+ * if one has used the -n flag. this presents a problem, because
+ * if we simply do a read(), we'll get the extra input, instead
+ * of our desired y/n input. so, we see if there's any extra
+ * input, and if there is, then we will store it.
+ */
+
+ saveinput();
+
+ (void) write(2, "?...", 4); /* ask the user for input */
+
+ for (i = 0; i < LINE_MAX && read(PROMPT, &reply[i], 1) > 0; i++) {
+ if (reply[i] == '\n') {
+ if (i == 0)
+ return (FALSE);
+ break;
+ }
+ }
+ reply[i] = 0;
+
+ /* flush remainder of line if necessary */
+ if (i == LINE_MAX) {
+ char bitbucket;
+
+ while ((read(PROMPT, &bitbucket, 1) > 0) && (bitbucket != '\n'))
+ ;
+ }
+
+ /*
+ * now we have to figure out whether the user typed an
+ * internationalized version of 'y' for yes. note that in some
+ * countries, they've gotten used to typing an ASCII 'y'! so
+ * even if our int'l version fails, we will check for an ASCII
+ * 'y', in order to be backwards compatible.
+ */
+ return (rpmatch(reply));
+}
+
+
+static char *
+insert(char *pattern, char *subst)
+{
+ static char buffer[MAXSBUF+1];
+ int len, ipatlen;
+ char *pat;
+ char *bufend;
+ char *pbuf;
+
+ len = strlen(subst);
+ ipatlen = strlen(INSPAT) - 1;
+ pat = pattern - 1;
+ pbuf = buffer;
+ bufend = &buffer[MAXSBUF];
+
+ while (*++pat) {
+ if (xindex(pat, INSPAT) == 0) {
+ if (pbuf + len >= bufend) {
+ break;
+ } else {
+ (void) strcpy(pbuf, subst);
+ pat += ipatlen;
+ pbuf += len;
+ }
+ } else {
+ *pbuf++ = *pat;
+ if (pbuf >= bufend)
+ break;
+ }
+ }
+
+ if (!*pat) {
+ *pbuf = '\0';
+ return (buffer);
+ } else {
+ ermsg(gettext("Maximum argument size with insertion via %s's "
+ "exceeded\n"), INSPAT);
+ ERR = TRUE;
+ return (0);
+ }
+}
+
+
+static void
+addibuf(struct inserts *p)
+{
+ char *newarg, *skel, *sub;
+ int l;
+
+ skel = p->p_skel;
+ sub = *ARGV;
+ linesize -= strlen(skel) + 1;
+ newarg = insert(skel, sub);
+ if (ERR)
+ return;
+
+ if (checklen(newarg)) {
+ if ((ibufsize += (l = strlen(newarg) + 1)) > MAXIBUF) {
+ ermsg(gettext("Insert buffer overflow\n"));
+ ERR = TRUE;
+ }
+ (void) strcpy(p_ibuf, newarg);
+ *(p->p_ARGV) = p_ibuf;
+ p_ibuf += l;
+ }
+}
+
+
+/*
+ * getchr(): get the next character.
+ * description:
+ * we get the next character from pio.structure, if there's a character
+ * to get. this may happen when we've had to flush stdin=/dev/tty,
+ * but still wanted to preserve the characters for later processing.
+ *
+ * otherwise we just get the character from stdin.
+ */
+static int
+getchr(void)
+{
+ char c;
+
+ do {
+ if (queued_data == NULL) {
+ char *buffer;
+ int len;
+
+ if ((buffer = malloc(BUFSIZE)) == NULL) {
+ perror(gettext(
+ "xargs: Memory allocation failure"));
+ exit(1);
+ }
+
+ if ((len = read(0, buffer, BUFSIZE)) == 0)
+ return (0);
+ if (len == -1) {
+ perror(gettext("xargs: Read failure"));
+ exit(1);
+ }
+
+ queue(buffer, len, TAIL);
+ }
+
+ file_offset++;
+ c = *queued_data->cur++; /* get the next character */
+ if (--queued_data->length == 0) { /* at the end of buffer? */
+ pio *nxt = queued_data->next;
+
+ free(queued_data->start);
+ free(queued_data);
+ queued_data = nxt;
+ }
+ } while (c == '\0');
+ return (c);
+}
+
+
+static wchar_t
+getwchr(void)
+{
+ int i;
+ wchar_t wch;
+ unsigned char buffer[MB_LEN_MAX + 1];
+
+ for (i = 0; i < (int)MB_CUR_MAX; ) {
+ if ((buffer[i++] = getchr()) == NULL) {
+ /* We have reached EOF */
+ if (i == 1) {
+ /* TRUE EOF has been reached */
+ return (NULL);
+ }
+ /*
+ * We have some characters in our buffer still so it
+ * must be an invalid character right before EOF.
+ */
+ break;
+ }
+
+ /* If this succeeds then we are done */
+ if (mbtowc(&wch, (char *)buffer, i) != -1)
+ return (wch);
+ }
+
+ /*
+ * We have now encountered an illegal character sequence.
+ * There is nothing much we can do at this point but
+ * return an error. If we attempt to recover we may in fact
+ * return garbage as arguments, from the customer's point
+ * of view. After all what if they are feeding us a file
+ * generated in another locale?
+ */
+ errno = EILSEQ;
+ perror(gettext("xargs: Corrupt input file"));
+ exit(1);
+ /* NOTREACHED */
+}
+
+
+static void
+ungetwchr(wchar_t wch)
+{
+ char *buffer;
+ int bytes;
+
+ if ((buffer = malloc(MB_LEN_MAX)) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+ bytes = wctomb(buffer, wch);
+ queue(buffer, bytes, HEAD);
+}
+
+
+static int
+lcall(char *sub, char **subargs)
+{
+ int retcode, retry = 0;
+ pid_t iwait, child;
+
+ for (; ; ) {
+ switch (child = fork()) {
+ default:
+ while ((iwait = wait(&retcode)) != child &&
+ iwait != (pid_t)-1)
+ ;
+ if (iwait == (pid_t)-1) {
+ perror(gettext("xargs: Wait failure"));
+ exit(122);
+ /* NOTREACHED */
+ }
+ if (WIFSIGNALED(retcode)) {
+ ermsg(gettext("Child killed with signal %d\n"),
+ WTERMSIG(retcode));
+ exit(125);
+ /* NOTREACHED */
+ }
+ if ((WEXITSTATUS(retcode) & 0377) == 0377) {
+ ermsg(gettext("Command could not continue "
+ "processing data\n"));
+ exit(124);
+ /* NOTREACHED */
+ }
+ return (WEXITSTATUS(retcode));
+ case 0:
+ (void) execvp(sub, subargs);
+ perror(gettext("xargs: Could not exec command"));
+ if (errno == EACCES)
+ exit(126);
+ exit(127);
+ /* NOTREACHED */
+ case -1:
+ if (errno != EAGAIN && retry++ < FORK_RETRY) {
+ perror(gettext("xargs: Could not fork child"));
+ exit(123);
+ }
+ (void) sleep(1);
+ }
+ }
+}
+
+
+/*
+ * If `s2' is a substring of `s1' return the offset of the first
+ * occurrence of `s2' in `s1', else return -1.
+ */
+static int
+xindex(char *as1, char *as2)
+{
+ char *s1, *s2, c;
+ int offset;
+
+ s1 = as1;
+ s2 = as2;
+ c = *s2;
+
+ while (*s1) {
+ if (*s1++ == c) {
+ offset = s1 - as1 - 1;
+ s2++;
+ while ((c = *s2++) == *s1++ && c)
+ ;
+ if (c == 0)
+ return (offset);
+ s1 = offset + as1 + 1;
+ s2 = as2;
+ c = *s2;
+ }
+ }
+ return (-1);
+}
+
+
+static void
+usage()
+{
+ ermsg(gettext(USAGEMSG));
+ OK = FALSE;
+}
+
+
+
+/*
+ * parseargs(): modify the args
+ * since the -e, -i and -l flags all take optional subarguments,
+ * and getopts(3C) is clueless about this nonsense, we change the
+ * our local argument count and strings to separate this out,
+ * and make it easier to handle via getopts(3c).
+ *
+ * -e -> "-e ""
+ * -e3 -> "-e "3"
+ * -Estr -> "-E "str"
+ * -i -> "-i "{}"
+ * -irep -> "-i "rep"
+ * -l -> "-i "1"
+ * -l10 -> "-i "10"
+ *
+ * since the -e, -i and -l flags all take optional subarguments,
+ */
+static void
+parseargs(int ac, char **av)
+{
+ int i; /* current argument */
+ int cflag; /* 0 = not processing cmd arg */
+
+ if ((mav = malloc((ac * 2 + 1) * sizeof (char *))) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+
+ /* for each argument, see if we need to change things: */
+ for (i = mac = cflag = 0; (av[i] != NULL) && i < ac; i++, mac++) {
+ if ((mav[mac] = strdup(av[i])) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+
+ /* -- has been found or argument list is fully processes */
+ if (cflag)
+ continue;
+
+ /*
+ * if we're doing special processing, and we've got a flag
+ */
+ else if ((av[i][0] == '-') && (av[i][1] != NULL)) {
+ char *def;
+
+ switch (av[i][1]) {
+ case 'e':
+ def = ""; /* -e with no arg turns off eof */
+ goto process_special;
+ case 'i':
+ def = INSPAT_STR;
+ goto process_special;
+ case 'l':
+ def = "1";
+process_special:
+ /*
+ * if there's no sub-option, we *must* add
+ * a default one. this is because xargs must
+ * be able to distinguish between a valid
+ * suboption, and a command name.
+ */
+ if (av[i][2] == NULL) {
+ mav[++mac] = strdup(def);
+ } else {
+ /* clear out our version: */
+ mav[mac][2] = NULL;
+ mav[++mac] = strdup(&av[i][2]);
+ }
+ if (mav[mac] == NULL) {
+ perror(gettext("xargs: Memory"
+ " allocation failure"));
+ exit(1);
+ }
+ break;
+
+ /* flags with required subarguments: */
+
+ /*
+ * there are two separate cases here. either the
+ * flag can have the normal XCU4 handling
+ * (of the form: -X subargument); or it can have
+ * the old solaris 2.[0-4] handling (of the
+ * form: -Xsubargument). in order to maintain
+ * backwards compatibility, we must support the
+ * latter case. we handle the latter possibility
+ * first so both the old solaris way of handling
+ * and the new XCU4 way of handling things are allowed.
+ */
+ case 'n': /* FALLTHROUGH */
+ case 's': /* FALLTHROUGH */
+ case 'E': /* FALLTHROUGH */
+ case 'I': /* FALLTHROUGH */
+ case 'L':
+ /*
+ * if the second character isn't null, then
+ * the user has specified the old syntax.
+ * we move the subargument into our
+ * mod'd argument list.
+ */
+ if (av[i][2] != NULL) {
+ /* first clean things up: */
+ mav[mac][2] = NULL;
+
+ /* now add the separation: */
+ ++mac; /* inc to next mod'd arg */
+ if ((mav[mac] = strdup(&av[i][2])) ==
+ NULL) {
+ perror(gettext("xargs: Memory"
+ " allocation failure"));
+ exit(1);
+ }
+ break;
+ }
+ i++;
+ mac++;
+#ifdef XPG6
+ if (av[i] != NULL) {
+ if ((mav[mac] = strdup(av[i]))
+ == NULL) {
+ perror(gettext("xargs: Memory"
+ " allocation failure"));
+ exit(1);
+ }
+ }
+#else
+ if (av[i] == NULL) {
+ if ((mav[mac++] = strdup("")) == NULL) {
+ perror(gettext("xargs: Memory "
+ " allocation failure"));
+ exit(1);
+ }
+ mav[mac] = NULL;
+ return;
+ }
+ if ((mav[mac] = strdup(av[i])) == NULL) {
+ perror(gettext("xargs: Memory"
+ " allocation failure"));
+ exit(1);
+ }
+
+#endif
+ break;
+
+ /* flags */
+ case 'p' :
+ case 't' :
+ case 'x' :
+ break;
+
+ case '-' :
+ default:
+ /*
+ * here we've hit the cmd argument. so
+ * we'll stop special processing, as the
+ * cmd may have a "-i" etc., argument,
+ * and we don't want to add a "" to it.
+ */
+ cflag = 1;
+ break;
+ }
+ } else if (i > 0) { /* if we're not the 1st arg */
+ /*
+ * if it's not a flag, then it *must* be the cmd.
+ * set cflag, so we don't mishandle the -[eil] flags.
+ */
+ cflag = 1;
+ }
+ }
+
+ mav[mac] = NULL;
+}
+
+
+/*
+ * saveinput(): pick up any pending input, so it can be processed later.
+ *
+ * description:
+ * the purpose of this routine is to allow us to handle the user
+ * typing in a 'y' or 'n', when there's existing characters already
+ * in stdin. this happens when one gives the "-n" option along with
+ * "-p". the problem occurs when the user first types in more arguments
+ * than specified by the -n number. echoargs() wants to read stdin
+ * in order to get the user's response, but if there's already stuff
+ * there, echoargs() won't read the proper character.
+ *
+ * the solution provided by this routine is to pick up all characters
+ * (if any), and store them for later processing.
+ */
+
+void
+saveinput()
+{
+ char *buffer; /* ptr to the floating data buffer */
+ struct strpeek speek; /* to see what's on the queue */
+ struct strpeek *ps;
+
+ /* if we're not in -p mode, skip */
+ if (PROMPT == -1) {
+ return;
+ }
+
+
+ /* now see if there's any activity pending: */
+ ps = &speek;
+ ps->ctlbuf.maxlen = 0;
+ ps->ctlbuf.len = 0;
+ ps->ctlbuf.buf = NULL;
+ ps->flags = 0;
+ ps->databuf.maxlen = MAX_INPUT;
+ ps->databuf.len = 0;
+ if ((buffer = malloc((size_t)MAX_INPUT)) == NULL) {
+ perror(gettext("xargs: Memory allocation failure"));
+ exit(1);
+ }
+ ps->databuf.buf = (char *)buffer;
+
+ if (ioctl(PROMPT, I_PEEK, ps) == -1) {
+ perror(gettext("xargs: I_PEEK failure"));
+ exit(1);
+ }
+
+ if (ps->databuf.len > 0) {
+ int len;
+
+ if ((len = read(PROMPT, buffer, ps->databuf.len)) == -1) {
+ perror(gettext("xargs: read failure"));
+ exit(1);
+ }
+ queue(buffer, len, TAIL);
+ }
+}