1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1990, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * John B. Roll Jr. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $ 35 */ 36 37 #if 0 38 #ifndef lint 39 static const char copyright[] = 40 "@(#) Copyright (c) 1990, 1993\n\ 41 The Regents of the University of California. All rights reserved.\n"; 42 #endif /* not lint */ 43 44 #ifndef lint 45 static char sccsid[] = "@(#)xargs.c 8.1 (Berkeley) 6/6/93"; 46 #endif /* not lint */ 47 #endif 48 #include <sys/cdefs.h> 49 __FBSDID("$FreeBSD$"); 50 51 #include <sys/types.h> 52 #include <sys/wait.h> 53 #include <sys/time.h> 54 #include <sys/limits.h> 55 #include <sys/resource.h> 56 #include <err.h> 57 #include <errno.h> 58 #include <fcntl.h> 59 #include <getopt.h> 60 #include <langinfo.h> 61 #include <locale.h> 62 #include <paths.h> 63 #include <regex.h> 64 #include <stdbool.h> 65 #include <stdio.h> 66 #include <stdlib.h> 67 #include <string.h> 68 #include <unistd.h> 69 70 #include "pathnames.h" 71 72 static void parse_input(int, char *[]); 73 static void prerun(int, char *[]); 74 static int prompt(void); 75 static void run(char **); 76 static void usage(void); 77 bool strnsubst(char **, const char *, const char *, size_t); 78 static pid_t xwait(int block, int *status); 79 static void xexit(const char *, const int); 80 static void waitchildren(const char *, int); 81 static void pids_init(void); 82 static int pids_empty(void); 83 static int pids_full(void); 84 static void pids_add(pid_t pid); 85 static int pids_remove(pid_t pid); 86 static int findslot(pid_t pid); 87 static int findfreeslot(void); 88 static void clearslot(int slot); 89 90 static char echo[] = _PATH_ECHO; 91 static char **av, **bxp, **ep, **endxp, **xp; 92 static char *argp, *bbp, *ebp, *inpline, *p, *replstr; 93 static const char *eofstr; 94 static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag; 95 static int cnt, Iflag, jfound, Lflag, Sflag, wasquoted, xflag; 96 static int curprocs, maxprocs; 97 static pid_t *childpids; 98 99 static volatile int childerr; 100 101 extern char **environ; 102 103 static const char *optstr = "+0E:I:J:L:n:oP:pR:S:s:rtx"; 104 105 static const struct option long_options[] = 106 { 107 {"exit", no_argument, NULL, 'x'}, 108 {"interactive", no_argument, NULL, 'p'}, 109 {"max-args", required_argument, NULL, 'n'}, 110 {"max-chars", required_argument, NULL, 's'}, 111 {"max-procs", required_argument, NULL, 'P'}, 112 {"no-run-if-empty", no_argument, NULL, 'r'}, 113 {"null", no_argument, NULL, '0'}, 114 {"verbose", no_argument, NULL, 't'}, 115 116 {NULL, no_argument, NULL, 0}, 117 }; 118 119 int 120 main(int argc, char *argv[]) 121 { 122 long arg_max; 123 int ch, Jflag, nargs, nflag, nline; 124 size_t linelen; 125 struct rlimit rl; 126 char *endptr; 127 const char *errstr; 128 129 inpline = replstr = NULL; 130 ep = environ; 131 eofstr = ""; 132 Jflag = nflag = 0; 133 134 (void)setlocale(LC_ALL, ""); 135 136 /* 137 * POSIX.2 limits the exec line length to ARG_MAX - 2K. Running that 138 * caused some E2BIG errors, so it was changed to ARG_MAX - 4K. Given 139 * that the smallest argument is 2 bytes in length, this means that 140 * the number of arguments is limited to: 141 * 142 * (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2. 143 * 144 * We arbitrarily limit the number of arguments to 5000. This is 145 * allowed by POSIX.2 as long as the resulting minimum exec line is 146 * at least LINE_MAX. Realloc'ing as necessary is possible, but 147 * probably not worthwhile. 148 */ 149 nargs = 5000; 150 if ((arg_max = sysconf(_SC_ARG_MAX)) == -1) 151 errx(1, "sysconf(_SC_ARG_MAX) failed"); 152 nline = arg_max - 4 * 1024; 153 while (*ep != NULL) { 154 /* 1 byte for each '\0' */ 155 nline -= strlen(*ep++) + 1 + sizeof(*ep); 156 } 157 maxprocs = 1; 158 while ((ch = getopt_long(argc, argv, optstr, long_options, NULL)) != -1) 159 switch (ch) { 160 case 'E': 161 eofstr = optarg; 162 break; 163 case 'I': 164 Jflag = 0; 165 Iflag = 1; 166 Lflag = 1; 167 replstr = optarg; 168 break; 169 case 'J': 170 Iflag = 0; 171 Jflag = 1; 172 replstr = optarg; 173 break; 174 case 'L': 175 Lflag = strtonum(optarg, 0, INT_MAX, &errstr); 176 if (errstr) 177 errx(1, "-L %s: %s", optarg, errstr); 178 break; 179 case 'n': 180 nflag = 1; 181 nargs = strtonum(optarg, 1, INT_MAX, &errstr); 182 if (errstr) 183 errx(1, "-n %s: %s", optarg, errstr); 184 break; 185 case 'o': 186 oflag = 1; 187 break; 188 case 'P': 189 maxprocs = strtonum(optarg, 0, INT_MAX, &errstr); 190 if (errstr) 191 errx(1, "-P %s: %s", optarg, errstr); 192 if (getrlimit(RLIMIT_NPROC, &rl) != 0) 193 errx(1, "getrlimit failed"); 194 if (maxprocs == 0 || maxprocs > rl.rlim_cur) 195 maxprocs = rl.rlim_cur; 196 break; 197 case 'p': 198 pflag = 1; 199 break; 200 case 'R': 201 Rflag = strtol(optarg, &endptr, 10); 202 if (*endptr != '\0') 203 errx(1, "replacements must be a number"); 204 break; 205 case 'r': 206 /* GNU compatibility */ 207 break; 208 case 'S': 209 Sflag = strtoul(optarg, &endptr, 10); 210 if (*endptr != '\0') 211 errx(1, "replsize must be a number"); 212 break; 213 case 's': 214 nline = strtonum(optarg, 0, INT_MAX, &errstr); 215 if (errstr) 216 errx(1, "-s %s: %s", optarg, errstr); 217 break; 218 case 't': 219 tflag = 1; 220 break; 221 case 'x': 222 xflag = 1; 223 break; 224 case '0': 225 zflag = 1; 226 break; 227 case '?': 228 default: 229 usage(); 230 } 231 argc -= optind; 232 argv += optind; 233 234 if (!Iflag && Rflag) 235 usage(); 236 if (!Iflag && Sflag) 237 usage(); 238 if (Iflag && !Rflag) 239 Rflag = 5; 240 if (Iflag && !Sflag) 241 Sflag = 255; 242 if (xflag && !nflag) 243 usage(); 244 if (Iflag || Lflag) 245 xflag = 1; 246 if (replstr != NULL && *replstr == '\0') 247 errx(1, "replstr may not be empty"); 248 249 pids_init(); 250 251 /* 252 * Allocate pointers for the utility name, the utility arguments, 253 * the maximum arguments to be read from stdin and the trailing 254 * NULL. 255 */ 256 linelen = 1 + argc + nargs + 1; 257 if ((av = bxp = malloc(linelen * sizeof(char *))) == NULL) 258 errx(1, "malloc failed"); 259 260 /* 261 * Use the user's name for the utility as argv[0], just like the 262 * shell. Echo is the default. Set up pointers for the user's 263 * arguments. 264 */ 265 if (*argv == NULL) 266 cnt = strlen(*bxp++ = echo); 267 else { 268 do { 269 if (Jflag && strcmp(*argv, replstr) == 0) { 270 char **avj; 271 jfound = 1; 272 argv++; 273 for (avj = argv; *avj; avj++) 274 cnt += strlen(*avj) + 1; 275 break; 276 } 277 cnt += strlen(*bxp++ = *argv) + 1; 278 } while (*++argv != NULL); 279 } 280 281 /* 282 * Set up begin/end/traversing pointers into the array. The -n 283 * count doesn't include the trailing NULL pointer, so the malloc 284 * added in an extra slot. 285 */ 286 endxp = (xp = bxp) + nargs; 287 288 /* 289 * Allocate buffer space for the arguments read from stdin and the 290 * trailing NULL. Buffer space is defined as the default or specified 291 * space, minus the length of the utility name and arguments. Set up 292 * begin/end/traversing pointers into the array. The -s count does 293 * include the trailing NULL, so the malloc didn't add in an extra 294 * slot. 295 */ 296 nline -= cnt; 297 if (nline <= 0) 298 errx(1, "insufficient space for command"); 299 300 if ((bbp = malloc((size_t)(nline + 1))) == NULL) 301 errx(1, "malloc failed"); 302 ebp = (argp = p = bbp) + nline - 1; 303 for (;;) 304 parse_input(argc, argv); 305 } 306 307 static void 308 parse_input(int argc, char *argv[]) 309 { 310 int ch, foundeof; 311 char **avj; 312 313 foundeof = 0; 314 315 switch (ch = getchar()) { 316 case EOF: 317 /* No arguments since last exec. */ 318 if (p == bbp) { 319 waitchildren(*av, 1); 320 exit(rval); 321 } 322 goto arg1; 323 case ' ': 324 case '\t': 325 /* Quotes escape tabs and spaces. */ 326 if (insingle || indouble || zflag) 327 goto addch; 328 goto arg2; 329 case '\0': 330 if (zflag) { 331 /* 332 * Increment 'count', so that nulls will be treated 333 * as end-of-line, as well as end-of-argument. This 334 * is needed so -0 works properly with -I and -L. 335 */ 336 count++; 337 goto arg2; 338 } 339 goto addch; 340 case '\n': 341 if (zflag) 342 goto addch; 343 count++; /* Indicate end-of-line (used by -L) */ 344 345 /* Quotes do not escape newlines. */ 346 arg1: if (insingle || indouble) { 347 warnx("unterminated quote"); 348 xexit(*av, 1); 349 } 350 arg2: 351 foundeof = *eofstr != '\0' && 352 strncmp(argp, eofstr, p - argp) == 0; 353 354 /* Do not make empty args unless they are quoted */ 355 if ((argp != p || wasquoted) && !foundeof) { 356 *p++ = '\0'; 357 *xp++ = argp; 358 if (Iflag) { 359 size_t curlen; 360 361 if (inpline == NULL) 362 curlen = 0; 363 else { 364 /* 365 * If this string is not zero 366 * length, append a space for 367 * separation before the next 368 * argument. 369 */ 370 if ((curlen = strlen(inpline))) 371 strcat(inpline, " "); 372 } 373 curlen++; 374 /* 375 * Allocate enough to hold what we will 376 * be holding in a second, and to append 377 * a space next time through, if we have 378 * to. 379 */ 380 inpline = realloc(inpline, curlen + 2 + 381 strlen(argp)); 382 if (inpline == NULL) { 383 warnx("realloc failed"); 384 xexit(*av, 1); 385 } 386 if (curlen == 1) 387 strcpy(inpline, argp); 388 else 389 strcat(inpline, argp); 390 } 391 } 392 393 /* 394 * If max'd out on args or buffer, or reached EOF, 395 * run the command. If xflag and max'd out on buffer 396 * but not on args, object. Having reached the limit 397 * of input lines, as specified by -L is the same as 398 * maxing out on arguments. 399 */ 400 if (xp == endxp || p > ebp || ch == EOF || 401 (Lflag <= count && xflag) || foundeof) { 402 if (xflag && xp != endxp && p > ebp) { 403 warnx("insufficient space for arguments"); 404 xexit(*av, 1); 405 } 406 if (jfound) { 407 for (avj = argv; *avj; avj++) 408 *xp++ = *avj; 409 } 410 prerun(argc, av); 411 if (ch == EOF || foundeof) { 412 waitchildren(*av, 1); 413 exit(rval); 414 } 415 p = bbp; 416 xp = bxp; 417 count = 0; 418 } 419 argp = p; 420 wasquoted = 0; 421 break; 422 case '\'': 423 if (indouble || zflag) 424 goto addch; 425 insingle = !insingle; 426 wasquoted = 1; 427 break; 428 case '"': 429 if (insingle || zflag) 430 goto addch; 431 indouble = !indouble; 432 wasquoted = 1; 433 break; 434 case '\\': 435 if (zflag) 436 goto addch; 437 /* Backslash escapes anything, is escaped by quotes. */ 438 if (!insingle && !indouble && (ch = getchar()) == EOF) { 439 warnx("backslash at EOF"); 440 xexit(*av, 1); 441 } 442 /* FALLTHROUGH */ 443 default: 444 addch: if (p < ebp) { 445 *p++ = ch; 446 break; 447 } 448 449 /* If only one argument, not enough buffer space. */ 450 if (bxp == xp) { 451 warnx("insufficient space for argument"); 452 xexit(*av, 1); 453 } 454 /* Didn't hit argument limit, so if xflag object. */ 455 if (xflag) { 456 warnx("insufficient space for arguments"); 457 xexit(*av, 1); 458 } 459 460 if (jfound) { 461 for (avj = argv; *avj; avj++) 462 *xp++ = *avj; 463 } 464 prerun(argc, av); 465 xp = bxp; 466 cnt = ebp - argp; 467 memcpy(bbp, argp, (size_t)cnt); 468 p = (argp = bbp) + cnt; 469 *p++ = ch; 470 break; 471 } 472 } 473 474 /* 475 * Do things necessary before run()'ing, such as -I substitution, 476 * and then call run(). 477 */ 478 static void 479 prerun(int argc, char *argv[]) 480 { 481 char **tmp, **tmp2, **avj; 482 int repls; 483 484 repls = Rflag; 485 486 if (argc == 0 || repls == 0) { 487 *xp = NULL; 488 run(argv); 489 return; 490 } 491 492 avj = argv; 493 494 /* 495 * Allocate memory to hold the argument list, and 496 * a NULL at the tail. 497 */ 498 tmp = malloc((argc + 1) * sizeof(char *)); 499 if (tmp == NULL) { 500 warnx("malloc failed"); 501 xexit(*argv, 1); 502 } 503 tmp2 = tmp; 504 505 /* 506 * Save the first argument and iterate over it, we 507 * cannot do strnsubst() to it. 508 */ 509 if ((*tmp++ = strdup(*avj++)) == NULL) { 510 warnx("strdup failed"); 511 xexit(*argv, 1); 512 } 513 514 /* 515 * For each argument to utility, if we have not used up 516 * the number of replacements we are allowed to do, and 517 * if the argument contains at least one occurrence of 518 * replstr, call strnsubst(), else just save the string. 519 * Iterations over elements of avj and tmp are done 520 * where appropriate. 521 */ 522 while (--argc) { 523 *tmp = *avj++; 524 if (repls && strstr(*tmp, replstr) != NULL) { 525 if (strnsubst(tmp++, replstr, inpline, (size_t)Sflag)) { 526 warnx("comamnd line cannot be assembled, too long"); 527 xexit(*argv, 1); 528 } 529 if (repls > 0) 530 repls--; 531 } else { 532 if ((*tmp = strdup(*tmp)) == NULL) { 533 warnx("strdup failed"); 534 xexit(*argv, 1); 535 } 536 tmp++; 537 } 538 } 539 540 /* 541 * Run it. 542 */ 543 *tmp = NULL; 544 run(tmp2); 545 546 /* 547 * Walk from the tail to the head, free along the way. 548 */ 549 for (; tmp2 != tmp; tmp--) 550 free(*tmp); 551 /* 552 * Now free the list itself. 553 */ 554 free(tmp2); 555 556 /* 557 * Free the input line buffer, if we have one. 558 */ 559 if (inpline != NULL) { 560 free(inpline); 561 inpline = NULL; 562 } 563 } 564 565 static void 566 run(char **argv) 567 { 568 pid_t pid; 569 int fd; 570 char **avec; 571 572 /* 573 * If the user wants to be notified of each command before it is 574 * executed, notify them. If they want the notification to be 575 * followed by a prompt, then prompt them. 576 */ 577 if (tflag || pflag) { 578 (void)fprintf(stderr, "%s", *argv); 579 for (avec = argv + 1; *avec != NULL; ++avec) 580 (void)fprintf(stderr, " %s", *avec); 581 /* 582 * If the user has asked to be prompted, do so. 583 */ 584 if (pflag) 585 /* 586 * If they asked not to exec, return without execution 587 * but if they asked to, go to the execution. If we 588 * could not open their tty, break the switch and drop 589 * back to -t behaviour. 590 */ 591 switch (prompt()) { 592 case 0: 593 return; 594 case 1: 595 goto exec; 596 case 2: 597 break; 598 } 599 (void)fprintf(stderr, "\n"); 600 (void)fflush(stderr); 601 } 602 exec: 603 childerr = 0; 604 switch (pid = vfork()) { 605 case -1: 606 warn("vfork"); 607 xexit(*argv, 1); 608 case 0: 609 if (oflag) { 610 if ((fd = open(_PATH_TTY, O_RDONLY)) == -1) 611 err(1, "can't open /dev/tty"); 612 } else { 613 fd = open(_PATH_DEVNULL, O_RDONLY); 614 } 615 if (fd > STDIN_FILENO) { 616 if (dup2(fd, STDIN_FILENO) != 0) 617 err(1, "can't dup2 to stdin"); 618 close(fd); 619 } 620 execvp(argv[0], argv); 621 childerr = errno; 622 _exit(1); 623 } 624 pids_add(pid); 625 waitchildren(*argv, 0); 626 } 627 628 /* 629 * Wait for a tracked child to exit and return its pid and exit status. 630 * 631 * Ignores (discards) all untracked child processes. 632 * Returns -1 and sets errno to ECHILD if no tracked children exist. 633 * If block is set, waits indefinitely for a child process to exit. 634 * If block is not set and no children have exited, returns 0 immediately. 635 */ 636 static pid_t 637 xwait(int block, int *status) { 638 pid_t pid; 639 640 if (pids_empty()) { 641 errno = ECHILD; 642 return (-1); 643 } 644 645 while ((pid = waitpid(-1, status, block ? 0 : WNOHANG)) > 0) 646 if (pids_remove(pid)) 647 break; 648 649 return (pid); 650 } 651 652 static void 653 xexit(const char *name, const int exit_code) { 654 waitchildren(name, 1); 655 exit(exit_code); 656 } 657 658 static void 659 waitchildren(const char *name, int waitall) 660 { 661 pid_t pid; 662 int status; 663 int cause_exit = 0; 664 665 while ((pid = xwait(waitall || pids_full(), &status)) > 0) { 666 /* 667 * If we couldn't invoke the utility or if utility exited 668 * because of a signal or with a value of 255, warn (per 669 * POSIX), and then wait until all other children have 670 * exited before exiting 1-125. POSIX requires us to stop 671 * reading if child exits because of a signal or with 255, 672 * but it does not require us to exit immediately; waiting 673 * is preferable to orphaning. 674 */ 675 if (childerr != 0 && cause_exit == 0) { 676 errno = childerr; 677 waitall = 1; 678 cause_exit = errno == ENOENT ? 127 : 126; 679 warn("%s", name); 680 } else if (WIFSIGNALED(status)) { 681 waitall = cause_exit = 1; 682 warnx("%s: terminated with signal %d; aborting", 683 name, WTERMSIG(status)); 684 } else if (WEXITSTATUS(status) == 255) { 685 waitall = cause_exit = 1; 686 warnx("%s: exited with status 255; aborting", name); 687 } else if (WEXITSTATUS(status)) 688 rval = 1; 689 } 690 691 if (cause_exit) 692 exit(cause_exit); 693 if (pid == -1 && errno != ECHILD) 694 err(1, "waitpid"); 695 } 696 697 #define NOPID (0) 698 699 static void 700 pids_init(void) 701 { 702 int i; 703 704 if ((childpids = malloc(maxprocs * sizeof(*childpids))) == NULL) 705 errx(1, "malloc failed"); 706 707 for (i = 0; i < maxprocs; i++) 708 clearslot(i); 709 } 710 711 static int 712 pids_empty(void) 713 { 714 715 return (curprocs == 0); 716 } 717 718 static int 719 pids_full(void) 720 { 721 722 return (curprocs >= maxprocs); 723 } 724 725 static void 726 pids_add(pid_t pid) 727 { 728 int slot; 729 730 slot = findfreeslot(); 731 childpids[slot] = pid; 732 curprocs++; 733 } 734 735 static int 736 pids_remove(pid_t pid) 737 { 738 int slot; 739 740 if ((slot = findslot(pid)) < 0) 741 return (0); 742 743 clearslot(slot); 744 curprocs--; 745 return (1); 746 } 747 748 static int 749 findfreeslot(void) 750 { 751 int slot; 752 753 if ((slot = findslot(NOPID)) < 0) 754 errx(1, "internal error: no free pid slot"); 755 return (slot); 756 } 757 758 static int 759 findslot(pid_t pid) 760 { 761 int slot; 762 763 for (slot = 0; slot < maxprocs; slot++) 764 if (childpids[slot] == pid) 765 return (slot); 766 return (-1); 767 } 768 769 static void 770 clearslot(int slot) 771 { 772 773 childpids[slot] = NOPID; 774 } 775 776 /* 777 * Prompt the user about running a command. 778 */ 779 static int 780 prompt(void) 781 { 782 regex_t cre; 783 size_t rsize; 784 int match; 785 char *response; 786 FILE *ttyfp; 787 788 if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL) 789 return (2); /* Indicate that the TTY failed to open. */ 790 (void)fprintf(stderr, "?..."); 791 (void)fflush(stderr); 792 if ((response = fgetln(ttyfp, &rsize)) == NULL || 793 regcomp(&cre, nl_langinfo(YESEXPR), REG_EXTENDED) != 0) { 794 (void)fclose(ttyfp); 795 return (0); 796 } 797 response[rsize - 1] = '\0'; 798 match = regexec(&cre, response, 0, NULL, 0); 799 (void)fclose(ttyfp); 800 regfree(&cre); 801 return (match == 0); 802 } 803 804 static void 805 usage(void) 806 { 807 808 fprintf(stderr, 809 "usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]\n" 810 " [-J replstr] [-L number] [-n number [-x]] [-P maxprocs]\n" 811 " [-s size] [utility [argument ...]]\n"); 812 exit(1); 813 } 814