/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2006 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(); int 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( "Option requires an argument: -%c\n"), c); } break; case 'I': /* -I replstr: Insert mode. replstr *is* required. */ INSERT = PER_LINE = LEGAL = TRUE; N_ARGS = 0; if ((optarg != NULL) && (*optarg != '\0')) { INSPAT = optarg; } else { ermsg(gettext( "Option requires an argument: -%c\n"), c); } 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 != NULL) && (*optarg != '\0')) { 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 == NULL) || (*optarg == '\0')) { ermsg(gettext( "Option requires an argument: -%c\n"), c); } else if ((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 != NULL) && (*optarg != '\0')) { 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 ((optarg == NULL) || (*optarg == '\0')) { ermsg(gettext( "Option requires an argument: -%c\n"), c); } else 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 */ if ((optarg == NULL) || (*optarg == '\0')) { ermsg(gettext( "Option requires an argument: -%c\n"), c); } else { 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) { return (exitstat); } else { /* * if exitstat was set, to match XCU4 complience, * return that value, otherwise, return 1. */ return (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 && *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); } }