PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/commands/sed/process.c

http://github.com/vivekp/minix-nbsd
C | 683 lines | 509 code | 44 blank | 130 comment | 155 complexity | 45900a94f296da0017eda308ec375da0 MD5 | raw file
Possible License(s): AGPL-1.0, BSD-3-Clause
  1. /* $NetBSD: process.c,v 1.37 2006/06/18 05:16:41 gdamore Exp $ */
  2. /*-
  3. * Copyright (c) 1992, 1993, 1994
  4. * The Regents of the University of California. All rights reserved.
  5. *
  6. * This code is derived from software contributed to Berkeley by
  7. * Diomidis Spinellis of Imperial College, University of London.
  8. *
  9. * Redistribution and use in source and binary forms, with or without
  10. * modification, are permitted provided that the following conditions
  11. * are met:
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in the
  16. * documentation and/or other materials provided with the distribution.
  17. * 3. Neither the name of the University nor the names of its contributors
  18. * may be used to endorse or promote products derived from this software
  19. * without specific prior written permission.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  22. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24. * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  25. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  26. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  27. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  28. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  30. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  31. * SUCH DAMAGE.
  32. */
  33. /*-
  34. * Copyright (c) 1992 Diomidis Spinellis.
  35. *
  36. * This code is derived from software contributed to Berkeley by
  37. * Diomidis Spinellis of Imperial College, University of London.
  38. *
  39. * Redistribution and use in source and binary forms, with or without
  40. * modification, are permitted provided that the following conditions
  41. * are met:
  42. * 1. Redistributions of source code must retain the above copyright
  43. * notice, this list of conditions and the following disclaimer.
  44. * 2. Redistributions in binary form must reproduce the above copyright
  45. * notice, this list of conditions and the following disclaimer in the
  46. * documentation and/or other materials provided with the distribution.
  47. * 3. All advertising materials mentioning features or use of this software
  48. * must display the following acknowledgement:
  49. * This product includes software developed by the University of
  50. * California, Berkeley and its contributors.
  51. * 4. Neither the name of the University nor the names of its contributors
  52. * may be used to endorse or promote products derived from this software
  53. * without specific prior written permission.
  54. *
  55. * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  56. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  57. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  58. * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  59. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  60. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  61. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  62. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  63. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  64. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  65. * SUCH DAMAGE.
  66. */
  67. #if HAVE_NBTOOL_CONFIG_H
  68. #include "nbtool_config.h"
  69. #endif
  70. #include <sys/cdefs.h>
  71. #include <sys/types.h>
  72. #include <sys/stat.h>
  73. #include <sys/ioctl.h>
  74. #include <sys/uio.h>
  75. #include <ctype.h>
  76. #include <errno.h>
  77. #include <fcntl.h>
  78. #include <limits.h>
  79. #include <regex.h>
  80. #include <stdio.h>
  81. #include <stdlib.h>
  82. #include <string.h>
  83. #include <unistd.h>
  84. #include <termios.h>
  85. #include "defs.h"
  86. #include "extern.h"
  87. static SPACE HS, PS, SS;
  88. #define pd PS.deleted
  89. #define ps PS.space
  90. #define psl PS.len
  91. #define hs HS.space
  92. #define hsl HS.len
  93. static __inline int applies(struct s_command *);
  94. static void flush_appends(void);
  95. static void lputs(char *);
  96. static __inline int regexec_e(regex_t *, const char *, int, int, size_t);
  97. static void regsub(SPACE *, char *, char *);
  98. static int substitute(struct s_command *);
  99. struct s_appends *appends; /* Array of pointers to strings to append. */
  100. static int appendx; /* Index into appends array. */
  101. int appendnum; /* Size of appends array. */
  102. static int lastaddr; /* Set by applies if last address of a range. */
  103. static int sdone; /* If any substitutes since last line input. */
  104. /* Iov structure for 'w' commands. */
  105. static regex_t *defpreg;
  106. size_t maxnsub;
  107. regmatch_t *match;
  108. #define OUT(s) { fwrite(s, sizeof(u_char), psl, stdout); }
  109. void
  110. process(void)
  111. {
  112. struct s_command *cp;
  113. SPACE tspace;
  114. size_t len, oldpsl;
  115. char *p;
  116. oldpsl = 0;
  117. for (linenum = 0; mf_fgets(&PS, REPLACE);) {
  118. pd = 0;
  119. top:
  120. cp = prog;
  121. redirect:
  122. while (cp != NULL) {
  123. if (!applies(cp)) {
  124. cp = cp->next;
  125. continue;
  126. }
  127. switch (cp->code) {
  128. case '{':
  129. cp = cp->u.c;
  130. goto redirect;
  131. case 'a':
  132. if (appendx >= appendnum) {
  133. appends = xrealloc(appends,
  134. sizeof(struct s_appends) *
  135. (appendnum * 2));
  136. appendnum *= 2;
  137. }
  138. appends[appendx].type = AP_STRING;
  139. appends[appendx].s = cp->t;
  140. appends[appendx].len = strlen(cp->t);
  141. appendx++;
  142. break;
  143. case 'b':
  144. cp = cp->u.c;
  145. goto redirect;
  146. case 'c':
  147. pd = 1;
  148. psl = 0;
  149. if (cp->a2 == NULL || lastaddr)
  150. (void)printf("%s", cp->t);
  151. break;
  152. case 'd':
  153. pd = 1;
  154. goto new;
  155. case 'D':
  156. if (psl == 0)
  157. pd = 1;
  158. if (pd)
  159. goto new;
  160. if ((p = memchr(ps, '\n', psl - 1)) == NULL) {
  161. pd = 1;
  162. goto new;
  163. } else {
  164. psl -= (p + 1) - ps;
  165. memmove(ps, p + 1, psl);
  166. goto top;
  167. }
  168. case 'g':
  169. cspace(&PS, hs, hsl, REPLACE);
  170. break;
  171. case 'G':
  172. if (hs == NULL)
  173. cspace(&HS, "\n", 1, REPLACE);
  174. cspace(&PS, hs, hsl, 0);
  175. break;
  176. case 'h':
  177. cspace(&HS, ps, psl, REPLACE);
  178. break;
  179. case 'H':
  180. cspace(&HS, ps, psl, 0);
  181. break;
  182. case 'i':
  183. (void)printf("%s", cp->t);
  184. break;
  185. case 'l':
  186. lputs(ps);
  187. break;
  188. case 'n':
  189. if (!nflag && !pd)
  190. OUT(ps)
  191. flush_appends();
  192. if (!mf_fgets(&PS, REPLACE))
  193. exit(0);
  194. pd = 0;
  195. break;
  196. case 'N':
  197. flush_appends();
  198. if (!mf_fgets(&PS, 0)) {
  199. if (!nflag && !pd)
  200. OUT(ps)
  201. exit(0);
  202. }
  203. break;
  204. case 'p':
  205. if (pd)
  206. break;
  207. OUT(ps)
  208. break;
  209. case 'P':
  210. if (pd)
  211. break;
  212. if ((p = memchr(ps, '\n', psl - 1)) != NULL) {
  213. oldpsl = psl;
  214. psl = (p + 1) - ps;
  215. }
  216. OUT(ps)
  217. if (p != NULL)
  218. psl = oldpsl;
  219. break;
  220. case 'q':
  221. if (!nflag && !pd)
  222. OUT(ps)
  223. flush_appends();
  224. exit(0);
  225. case 'r':
  226. if (appendx >= appendnum) {
  227. appends = xrealloc(appends,
  228. sizeof(struct s_appends) *
  229. (appendnum * 2));
  230. appendnum *= 2;
  231. }
  232. appends[appendx].type = AP_FILE;
  233. appends[appendx].s = cp->t;
  234. appends[appendx].len = strlen(cp->t);
  235. appendx++;
  236. break;
  237. case 's':
  238. sdone |= substitute(cp);
  239. break;
  240. case 't':
  241. if (sdone) {
  242. sdone = 0;
  243. cp = cp->u.c;
  244. goto redirect;
  245. }
  246. break;
  247. case 'w':
  248. if (pd)
  249. break;
  250. if (cp->u.fd == -1 && (cp->u.fd = open(cp->t,
  251. O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
  252. DEFFILEMODE)) == -1)
  253. err(FATAL, "%s: %s",
  254. cp->t, strerror(errno));
  255. if (write(cp->u.fd, ps, psl) != psl)
  256. err(FATAL, "%s: %s",
  257. cp->t, strerror(errno));
  258. break;
  259. case 'x':
  260. if (hs == NULL)
  261. cspace(&HS, "\n", 1, REPLACE);
  262. tspace = PS;
  263. PS = HS;
  264. HS = tspace;
  265. break;
  266. case 'y':
  267. if (pd)
  268. break;
  269. for (p = ps, len = psl; --len; ++p)
  270. *p = cp->u.y[(int)*p];
  271. break;
  272. case ':':
  273. case '}':
  274. break;
  275. case '=':
  276. (void)printf("%lu\n", linenum);
  277. }
  278. cp = cp->next;
  279. } /* for all cp */
  280. new: if (!nflag && !pd)
  281. OUT(ps)
  282. flush_appends();
  283. } /* for all lines */
  284. }
  285. /*
  286. * TRUE if the address passed matches the current program state
  287. * (lastline, linenumber, ps).
  288. */
  289. #define MATCH(a) \
  290. (a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, psl) : \
  291. (a)->type == AT_LINE ? linenum == (a)->u.l : lastline
  292. /*
  293. * Return TRUE if the command applies to the current line. Sets the inrange
  294. * flag to process ranges. Interprets the non-select (``!'') flag.
  295. */
  296. static __inline int
  297. applies(struct s_command *cp)
  298. {
  299. int r;
  300. lastaddr = 0;
  301. if (cp->a1 == NULL && cp->a2 == NULL)
  302. r = 1;
  303. else if (cp->a2) {
  304. if (cp->inrange) {
  305. if (MATCH(cp->a2)) {
  306. cp->inrange = 0;
  307. lastaddr = 1;
  308. }
  309. r = 1;
  310. } else if (cp->a1 && MATCH(cp->a1)) {
  311. /*
  312. * If the second address is a number less than or
  313. * equal to the line number first selected, only
  314. * one line shall be selected.
  315. * -- POSIX 1003.2
  316. */
  317. if (cp->a2->type == AT_LINE &&
  318. linenum >= cp->a2->u.l)
  319. lastaddr = 1;
  320. else
  321. cp->inrange = 1;
  322. r = 1;
  323. } else
  324. r = 0;
  325. } else
  326. r = MATCH(cp->a1);
  327. return (cp->nonsel ? ! r : r);
  328. }
  329. /*
  330. * substitute --
  331. * Do substitutions in the pattern space. Currently, we build a
  332. * copy of the new pattern space in the substitute space structure
  333. * and then swap them.
  334. */
  335. static int
  336. substitute(struct s_command *cp)
  337. {
  338. SPACE tspace;
  339. regex_t *re;
  340. size_t re_off, slen;
  341. int lastempty, n;
  342. char *s;
  343. s = ps;
  344. re = cp->u.s->re;
  345. if (re == NULL) {
  346. if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) {
  347. linenum = cp->u.s->linenum;
  348. err(COMPILE, "\\%d not defined in the RE",
  349. cp->u.s->maxbref);
  350. }
  351. }
  352. if (!regexec_e(re, s, 0, 0, psl))
  353. return (0);
  354. SS.len = 0; /* Clean substitute space. */
  355. slen = psl;
  356. n = cp->u.s->n;
  357. lastempty = 1;
  358. switch (n) {
  359. case 0: /* Global */
  360. do {
  361. if (lastempty || match[0].rm_so != match[0].rm_eo) {
  362. /* Locate start of replaced string. */
  363. re_off = match[0].rm_so;
  364. /* Copy leading retained string. */
  365. cspace(&SS, s, re_off, APPEND);
  366. /* Add in regular expression. */
  367. regsub(&SS, s, cp->u.s->new);
  368. }
  369. /* Move past this match. */
  370. if (match[0].rm_so != match[0].rm_eo) {
  371. s += match[0].rm_eo;
  372. slen -= match[0].rm_eo;
  373. lastempty = 0;
  374. } else {
  375. if (match[0].rm_so == 0)
  376. cspace(&SS,
  377. s, match[0].rm_so + 1, APPEND);
  378. else
  379. cspace(&SS,
  380. s + match[0].rm_so, 1, APPEND);
  381. s += match[0].rm_so + 1;
  382. slen -= match[0].rm_so + 1;
  383. lastempty = 1;
  384. }
  385. } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
  386. /* Copy trailing retained string. */
  387. if (slen > 0)
  388. cspace(&SS, s, slen, APPEND);
  389. break;
  390. default: /* Nth occurrence */
  391. while (--n) {
  392. s += match[0].rm_eo;
  393. slen -= match[0].rm_eo;
  394. if (!regexec_e(re, s, REG_NOTBOL, 0, slen))
  395. return (0);
  396. }
  397. /* FALLTHROUGH */
  398. case 1: /* 1st occurrence */
  399. /* Locate start of replaced string. */
  400. re_off = match[0].rm_so + (s - ps);
  401. /* Copy leading retained string. */
  402. cspace(&SS, ps, re_off, APPEND);
  403. /* Add in regular expression. */
  404. regsub(&SS, s, cp->u.s->new);
  405. /* Copy trailing retained string. */
  406. s += match[0].rm_eo;
  407. slen -= match[0].rm_eo;
  408. cspace(&SS, s, slen, APPEND);
  409. break;
  410. }
  411. /*
  412. * Swap the substitute space and the pattern space, and make sure
  413. * that any leftover pointers into stdio memory get lost.
  414. */
  415. tspace = PS;
  416. PS = SS;
  417. SS = tspace;
  418. SS.space = SS.back;
  419. /* Handle the 'p' flag. */
  420. if (cp->u.s->p)
  421. OUT(ps)
  422. /* Handle the 'w' flag. */
  423. if (cp->u.s->wfile && !pd) {
  424. if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile,
  425. O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1)
  426. err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno));
  427. if (write(cp->u.s->wfd, ps, psl) != psl)
  428. err(FATAL, "%s: %s", cp->u.s->wfile, strerror(errno));
  429. }
  430. return (1);
  431. }
  432. /*
  433. * Flush append requests. Always called before reading a line,
  434. * therefore it also resets the substitution done (sdone) flag.
  435. */
  436. static void
  437. flush_appends(void)
  438. {
  439. FILE *f;
  440. int count, i;
  441. char buf[8 * 1024];
  442. for (i = 0; i < appendx; i++)
  443. switch (appends[i].type) {
  444. case AP_STRING:
  445. fwrite(appends[i].s, sizeof(char), appends[i].len,
  446. stdout);
  447. break;
  448. case AP_FILE:
  449. /*
  450. * Read files probably shouldn't be cached. Since
  451. * it's not an error to read a non-existent file,
  452. * it's possible that another program is interacting
  453. * with the sed script through the file system. It
  454. * would be truly bizarre, but possible. It's probably
  455. * not that big a performance win, anyhow.
  456. */
  457. if ((f = fopen(appends[i].s, "r")) == NULL)
  458. break;
  459. while ((count =
  460. fread(buf, sizeof(char), sizeof(buf), f)) > 0)
  461. (void)fwrite(buf, sizeof(char), count, stdout);
  462. (void)fclose(f);
  463. break;
  464. }
  465. if (ferror(stdout))
  466. err(FATAL, "stdout: %s", strerror(errno ? errno : EIO));
  467. appendx = sdone = 0;
  468. }
  469. static void
  470. lputs(char *s)
  471. {
  472. int count;
  473. char *escapes, *p;
  474. #ifndef HAVE_NBTOOL_CONFIG_H
  475. struct winsize win;
  476. #endif
  477. static int termwidth = -1;
  478. if (termwidth == -1) {
  479. if ((p = getenv("COLUMNS")) != NULL)
  480. termwidth = atoi(p);
  481. #ifndef HAVE_NBTOOL_CONFIG_H
  482. else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 &&
  483. win.ws_col > 0)
  484. termwidth = win.ws_col;
  485. #endif
  486. else
  487. termwidth = 60;
  488. }
  489. for (count = 0; *s; ++s) {
  490. if (count >= termwidth) {
  491. (void)printf("\\\n");
  492. count = 0;
  493. }
  494. if (isascii((unsigned char)*s) && isprint((unsigned char)*s) &&
  495. *s != '\\') {
  496. (void)putchar(*s);
  497. count++;
  498. } else {
  499. escapes = "\\\a\b\f\n\r\t\v";
  500. (void)putchar('\\');
  501. if ((p = strchr(escapes, *s)) != NULL) {
  502. (void)putchar("\\abfnrtv"[p - escapes]);
  503. count += 2;
  504. } else {
  505. (void)printf("%03o", *(u_char *)s);
  506. count += 4;
  507. }
  508. }
  509. }
  510. (void)putchar('$');
  511. (void)putchar('\n');
  512. if (ferror(stdout))
  513. err(FATAL, "stdout: %s", strerror(errno ? errno : EIO));
  514. }
  515. static __inline int
  516. regexec_e(regex_t *preg, const char *string, int eflags, int nomatch, size_t slen)
  517. {
  518. int eval;
  519. #ifndef REG_STARTEND
  520. char *buf;
  521. #endif
  522. if (preg == NULL) {
  523. if (defpreg == NULL)
  524. err(FATAL, "first RE may not be empty");
  525. } else
  526. defpreg = preg;
  527. /* Set anchors, discounting trailing newline (if any). */
  528. if (slen > 0 && string[slen - 1] == '\n')
  529. slen--;
  530. #ifndef REG_STARTEND
  531. if ((buf = malloc(slen + 1)) == NULL)
  532. err(1, NULL);
  533. (void)memcpy(buf, string, slen);
  534. buf[slen] = '\0';
  535. eval = regexec(defpreg, buf,
  536. nomatch ? 0 : maxnsub + 1, match, eflags);
  537. free(buf);
  538. #else
  539. match[0].rm_so = 0;
  540. match[0].rm_eo = slen;
  541. eval = regexec(defpreg, string,
  542. nomatch ? 0 : maxnsub + 1, match, eflags | REG_STARTEND);
  543. #endif
  544. switch(eval) {
  545. case 0:
  546. return (1);
  547. case REG_NOMATCH:
  548. return (0);
  549. }
  550. err(FATAL, "RE error: %s", strregerror(eval, defpreg));
  551. /* NOTREACHED */
  552. return (0);
  553. }
  554. /*
  555. * regsub - perform substitutions after a regexp match
  556. * Based on a routine by Henry Spencer
  557. */
  558. static void
  559. regsub(SPACE *sp, char *string, char *src)
  560. {
  561. int len, no;
  562. char c, *dst;
  563. #define NEEDSP(reqlen) \
  564. if (sp->len + (reqlen) + 1 >= sp->blen) { \
  565. size_t newlen = sp->blen + (reqlen) + 1024; \
  566. sp->space = sp->back = xrealloc(sp->back, newlen); \
  567. sp->blen = newlen; \
  568. dst = sp->space + sp->len; \
  569. }
  570. dst = sp->space + sp->len;
  571. while ((c = *src++) != '\0') {
  572. if (c == '&')
  573. no = 0;
  574. else if (c == '\\' && isdigit((unsigned char)*src))
  575. no = *src++ - '0';
  576. else
  577. no = -1;
  578. if (no < 0) { /* Ordinary character. */
  579. if (c == '\\' && (*src == '\\' || *src == '&'))
  580. c = *src++;
  581. NEEDSP(1);
  582. *dst++ = c;
  583. ++sp->len;
  584. } else if (match[no].rm_so != -1 && match[no].rm_eo != -1) {
  585. len = match[no].rm_eo - match[no].rm_so;
  586. NEEDSP(len);
  587. memmove(dst, string + match[no].rm_so, len);
  588. dst += len;
  589. sp->len += len;
  590. }
  591. }
  592. NEEDSP(1);
  593. *dst = '\0';
  594. }
  595. /*
  596. * aspace --
  597. * Append the source space to the destination space, allocating new
  598. * space as necessary.
  599. */
  600. void
  601. cspace(SPACE *sp, char *p, size_t len, enum e_spflag spflag)
  602. {
  603. size_t tlen;
  604. /* Make sure SPACE has enough memory and ramp up quickly. */
  605. tlen = sp->len + len + 1;
  606. if (tlen > sp->blen) {
  607. size_t newlen = tlen + 1024;
  608. sp->space = sp->back = xrealloc(sp->back, newlen);
  609. sp->blen = newlen;
  610. }
  611. if (spflag == REPLACE)
  612. sp->len = 0;
  613. memmove(sp->space + sp->len, p, len);
  614. sp->space[sp->len += len] = '\0';
  615. }
  616. /*
  617. * Close all cached opened files and report any errors
  618. */
  619. void
  620. cfclose(struct s_command *cp, struct s_command *end)
  621. {
  622. for (; cp != end; cp = cp->next)
  623. switch(cp->code) {
  624. case 's':
  625. if (cp->u.s->wfd != -1 && close(cp->u.s->wfd))
  626. err(FATAL,
  627. "%s: %s", cp->u.s->wfile, strerror(errno));
  628. cp->u.s->wfd = -1;
  629. break;
  630. case 'w':
  631. if (cp->u.fd != -1 && close(cp->u.fd))
  632. err(FATAL, "%s: %s", cp->t, strerror(errno));
  633. cp->u.fd = -1;
  634. break;
  635. case '{':
  636. cfclose(cp->u.c, cp->next);
  637. break;
  638. }
  639. }