This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / usr.bin / vi / search.c
CommitLineData
1e64b3ba
JH
1/*-
2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. 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#ifndef lint
35static char sccsid[] = "@(#)search.c 8.32 (Berkeley) 1/9/94";
36#endif /* not lint */
37
38#include <sys/types.h>
39
40#include <ctype.h>
41#include <errno.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46#include "vi.h"
47#include "interrupt.h"
48
49static int check_delta __P((SCR *, EXF *, long, recno_t));
50static int ctag_conv __P((SCR *, char **, int *));
51static int get_delta __P((SCR *, char **, long *, u_int *));
52static int resetup __P((SCR *, regex_t **, enum direction,
53 char *, char **, long *, u_int *));
54static void search_intr __P((int));
55
56/*
57 * resetup --
58 * Set up a search for a regular expression.
59 */
60static int
61resetup(sp, rep, dir, ptrn, epp, deltap, flagp)
62 SCR *sp;
63 regex_t **rep;
64 enum direction dir;
65 char *ptrn, **epp;
66 long *deltap;
67 u_int *flagp;
68{
69 u_int flags;
70 int delim, eval, re_flags, replaced;
71 char *p, *t;
72
73 /* Set return information the default. */
74 *deltap = 0;
75
76 /*
77 * Use saved pattern if no pattern supplied, or if only a delimiter
78 * character is supplied. Only the pattern was saved, historic vi
79 * did not reuse any delta supplied.
80 */
81 flags = *flagp;
82 if (ptrn == NULL)
83 goto prev;
84 if (ptrn[1] == '\0') {
85 if (epp != NULL)
86 *epp = ptrn + 1;
87 goto prev;
88 }
89 if (ptrn[0] == ptrn[1] && ptrn[2] == '\0') {
90 if (epp != NULL)
91 *epp = ptrn + 2;
92prev: if (!F_ISSET(sp, S_SRE_SET)) {
93 msgq(sp, M_ERR, "No previous search pattern.");
94 return (1);
95 }
96 *rep = &sp->sre;
97
98 /* Empty patterns set the direction. */
99 if (LF_ISSET(SEARCH_SET)) {
100 F_SET(sp, S_SRE_SET);
101 sp->searchdir = dir;
102 sp->sre = **rep;
103 }
104 return (0);
105 }
106
107 re_flags = 0; /* Set RE flags. */
108 if (O_ISSET(sp, O_EXTENDED))
109 re_flags |= REG_EXTENDED;
110 if (O_ISSET(sp, O_IGNORECASE))
111 re_flags |= REG_ICASE;
112
113 if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */
114 /* Set delimiter. */
115 delim = *ptrn++;
116
117 /* Find terminating delimiter, handling escaped delimiters. */
118 for (p = t = ptrn;;) {
119 if (p[0] == '\0' || p[0] == delim) {
120 if (p[0] == delim)
121 ++p;
122 *t = '\0';
123 break;
124 }
125 if (p[1] == delim && p[0] == '\\')
126 ++p;
127 *t++ = *p++;
128 }
129
130 /*
131 * If characters after the terminating delimiter, it may
132 * be an error, or may be an offset. In either case, we
133 * return the end of the string, whatever it may be.
134 */
135 if (*p) {
136 if (get_delta(sp, &p, deltap, flagp))
137 return (1);
138 if (*p && LF_ISSET(SEARCH_TERM)) {
139 msgq(sp, M_ERR,
140 "Characters after search string and/or delta.");
141 return (1);
142 }
143 }
144 if (epp != NULL)
145 *epp = p;
146
147 /* Check for "/ " or other such silliness. */
148 if (*ptrn == '\0')
149 goto prev;
150
151 if (re_conv(sp, &ptrn, &replaced))
152 return (1);
153 } else if (LF_ISSET(SEARCH_TAG)) {
154 if (ctag_conv(sp, &ptrn, &replaced))
155 return (1);
156 re_flags &= ~(REG_EXTENDED | REG_ICASE);
157 }
158
159 /* Compile the RE. */
160 if (eval = regcomp(*rep, ptrn, re_flags))
161 re_error(sp, eval, *rep);
162 else if (LF_ISSET(SEARCH_SET)) {
163 F_SET(sp, S_SRE_SET);
164 sp->searchdir = dir;
165 sp->sre = **rep;
166 }
167
168 /* Free up any extra memory. */
169 if (replaced)
170 FREE_SPACE(sp, ptrn, 0);
171 return (eval);
172}
173
174/*
175 * ctag_conv --
176 * Convert a tags search path into something that the POSIX
177 * 1003.2 RE functions can handle.
178 */
179static int
180ctag_conv(sp, ptrnp, replacedp)
181 SCR *sp;
182 char **ptrnp;
183 int *replacedp;
184{
185 size_t blen, len;
186 int lastdollar;
187 char *bp, *p, *t;
188
189 *replacedp = 0;
190
191 len = strlen(p = *ptrnp);
192
193 /* Max memory usage is 2 times the length of the string. */
194 GET_SPACE_RET(sp, bp, blen, len * 2);
195
196 t = bp;
197
198 /* The last charcter is a '/' or '?', we just strip it. */
199 if (p[len - 1] == '/' || p[len - 1] == '?')
200 p[len - 1] = '\0';
201
202 /* The next-to-last character is a '$', and it's magic. */
203 if (p[len - 2] == '$') {
204 lastdollar = 1;
205 p[len - 2] = '\0';
206 } else
207 lastdollar = 0;
208
209 /* The first character is a '/' or '?', we just strip it. */
210 if (p[0] == '/' || p[0] == '?')
211 ++p;
212
213 /* The second character is a '^', and it's magic. */
214 if (p[0] == '^')
215 *t++ = *p++;
216
217 /*
218 * Escape every other magic character we can find, stripping the
219 * backslashes ctags inserts to escape the search delimiter
220 * characters.
221 */
222 while (p[0]) {
223 /* Ctags escapes the search delimiter characters. */
224 if (p[0] == '\\' && (p[1] == '/' || p[1] == '?'))
225 ++p;
226 else if (strchr("^.[]$*", p[0]))
227 *t++ = '\\';
228 *t++ = *p++;
229 }
230 if (lastdollar)
231 *t++ = '$';
232 *t++ = '\0';
233
234 *ptrnp = bp;
235 *replacedp = 1;
236 return (0);
237}
238
239/*
240 * search_intr --
241 * Set the interrupt bit in any screen that is interruptible.
242 *
243 * XXX
244 * In the future this may be a problem. The user should be able to move to
245 * another screen and keep typing while this runs. If so, and the user has
246 * more than one search/global (see ex/ex_global.c) running, it will be hard
247 * to decide which one to stop.
248 */
249static void
250search_intr(signo)
251 int signo;
252{
253 SCR *sp;
254
255 for (sp = __global_list->dq.cqh_first;
256 sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
257 if (F_ISSET(sp, S_INTERRUPTIBLE))
258 F_SET(sp, S_INTERRUPTED);
259}
260
261#define EMPTYMSG "File empty; nothing to search."
262#define EOFMSG "Reached end-of-file without finding the pattern."
263#define NOTFOUND "Pattern not found."
264#define SOFMSG "Reached top-of-file without finding the pattern."
265#define WRAPMSG "Search wrapped."
266
267int
268f_search(sp, ep, fm, rm, ptrn, eptrn, flagp)
269 SCR *sp;
270 EXF *ep;
271 MARK *fm, *rm;
272 char *ptrn, **eptrn;
273 u_int *flagp;
274{
275 DECLARE_INTERRUPTS;
276 regmatch_t match[1];
277 regex_t *re, lre;
278 recno_t lno;
279 size_t coff, len;
280 long delta;
281 u_int flags;
282 int eval, rval, wrapped;
283 char *l;
284
285 if (file_lline(sp, ep, &lno))
286 return (1);
287 flags = *flagp;
288 if (lno == 0) {
289 if (LF_ISSET(SEARCH_MSG))
290 msgq(sp, M_INFO, EMPTYMSG);
291 return (1);
292 }
293
294 re = &lre;
295 if (resetup(sp, &re, FORWARD, ptrn, eptrn, &delta, flagp))
296 return (1);
297
298 /*
299 * Start searching immediately after the cursor. If at the end of the
300 * line, start searching on the next line. This is incompatible (read
301 * bug fix) with the historic vi -- searches for the '$' pattern never
302 * moved forward, and "-t foo" didn't work if "foo" was the first thing
303 * in the file.
304 */
305 if (LF_ISSET(SEARCH_FILE)) {
306 lno = 1;
307 coff = 0;
308 } else {
309 if ((l = file_gline(sp, ep, fm->lno, &len)) == NULL) {
310 GETLINE_ERR(sp, fm->lno);
311 return (1);
312 }
313 if (fm->cno + 1 >= len) {
314 if (fm->lno == lno) {
315 if (!O_ISSET(sp, O_WRAPSCAN)) {
316 if (LF_ISSET(SEARCH_MSG))
317 msgq(sp, M_INFO, EOFMSG);
318 return (1);
319 }
320 lno = 1;
321 } else
322 lno = fm->lno + 1;
323 coff = 0;
324 } else {
325 lno = fm->lno;
326 coff = fm->cno + 1;
327 }
328 }
329
330 /*
331 * Set up busy message, interrupts.
332 *
333 * F_search is called from the ex_tagfirst() routine, which runs
334 * before the screen really exists. Make sure we don't step on
335 * anything.
336 */
337 if (sp->s_position != NULL)
338 busy_on(sp, 1, "Searching...");
339 SET_UP_INTERRUPTS(search_intr);
340
341 for (rval = 1, wrapped = 0;; ++lno, coff = 0) {
342 if (F_ISSET(sp, S_INTERRUPTED)) {
343 msgq(sp, M_INFO, "Interrupted.");
344 break;
345 }
346 if (wrapped && lno > fm->lno ||
347 (l = file_gline(sp, ep, lno, &len)) == NULL) {
348 if (wrapped) {
349 if (LF_ISSET(SEARCH_MSG))
350 msgq(sp, M_INFO, NOTFOUND);
351 break;
352 }
353 if (!O_ISSET(sp, O_WRAPSCAN)) {
354 if (LF_ISSET(SEARCH_MSG))
355 msgq(sp, M_INFO, EOFMSG);
356 break;
357 }
358 lno = 0;
359 wrapped = 1;
360 continue;
361 }
362
363 /* If already at EOL, just keep going. */
364 if (len && coff == len)
365 continue;
366
367 /* Set the termination. */
368 match[0].rm_so = coff;
369 match[0].rm_eo = len;
370
371#if defined(DEBUG) && 0
372 TRACE(sp, "F search: %lu from %u to %u\n",
373 lno, coff, len ? len - 1 : len);
374#endif
375 /* Search the line. */
376 eval = regexec(re, l, 1, match,
377 (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND);
378 if (eval == REG_NOMATCH)
379 continue;
380 if (eval != 0) {
381 re_error(sp, eval, re);
382 break;
383 }
384
385 /* Warn if wrapped. */
386 if (wrapped && O_ISSET(sp, O_WARN) && LF_ISSET(SEARCH_MSG))
387 msgq(sp, M_INFO, WRAPMSG);
388
389 /*
390 * If an offset, see if it's legal. It's possible to match
391 * past the end of the line with $, so check for that case.
392 */
393 if (delta) {
394 if (check_delta(sp, ep, delta, lno))
395 break;
396 rm->lno = delta + lno;
397 rm->cno = 0;
398 } else {
399#if defined(DEBUG) && 0
400 TRACE(sp, "found: %qu to %qu\n",
401 match[0].rm_so, match[0].rm_eo);
402#endif
403 rm->lno = lno;
404 rm->cno = match[0].rm_so;
405
406 /*
407 * If a change command, it's possible to move beyond
408 * the end of a line. Historic vi generally got this
409 * wrong (try "c?$<cr>"). Not all that sure this gets
410 * it right, there are lots of strange cases.
411 */
412 if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len)
413 rm->cno = len ? len - 1 : 0;
414 }
415 rval = 0;
416 break;
417 }
418
419interrupt_err:
420
421 /* Turn off busy message, interrupts. */
422 if (sp->s_position != NULL)
423 busy_off(sp);
424 TEAR_DOWN_INTERRUPTS;
425
426 return (rval);
427}
428
429int
430b_search(sp, ep, fm, rm, ptrn, eptrn, flagp)
431 SCR *sp;
432 EXF *ep;
433 MARK *fm, *rm;
434 char *ptrn, **eptrn;
435 u_int *flagp;
436{
437 DECLARE_INTERRUPTS;
438 regmatch_t match[1];
439 regex_t *re, lre;
440 recno_t lno;
441 size_t coff, len, last;
442 long delta;
443 u_int flags;
444 int eval, rval, wrapped;
445 char *l;
446
447 if (file_lline(sp, ep, &lno))
448 return (1);
449 flags = *flagp;
450 if (lno == 0) {
451 if (LF_ISSET(SEARCH_MSG))
452 msgq(sp, M_INFO, EMPTYMSG);
453 return (1);
454 }
455
456 re = &lre;
457 if (resetup(sp, &re, BACKWARD, ptrn, eptrn, &delta, flagp))
458 return (1);
459
460 /* If in the first column, start searching on the previous line. */
461 if (fm->cno == 0) {
462 if (fm->lno == 1) {
463 if (!O_ISSET(sp, O_WRAPSCAN)) {
464 if (LF_ISSET(SEARCH_MSG))
465 msgq(sp, M_INFO, SOFMSG);
466 return (1);
467 }
468 } else
469 lno = fm->lno - 1;
470 } else
471 lno = fm->lno;
472
473 /* Turn on busy message, interrupts. */
474 busy_on(sp, 1, "Searching...");
475
476 if (F_ISSET(sp->gp, G_ISFROMTTY))
477 SET_UP_INTERRUPTS(search_intr);
478
479 for (rval = 1, wrapped = 0, coff = fm->cno;; --lno, coff = 0) {
480 if (F_ISSET(sp, S_INTERRUPTED)) {
481 msgq(sp, M_INFO, "Interrupted.");
482 break;
483 }
484 if (wrapped && lno < fm->lno || lno == 0) {
485 if (wrapped) {
486 if (LF_ISSET(SEARCH_MSG))
487 msgq(sp, M_INFO, NOTFOUND);
488 break;
489 }
490 if (!O_ISSET(sp, O_WRAPSCAN)) {
491 if (LF_ISSET(SEARCH_MSG))
492 msgq(sp, M_INFO, SOFMSG);
493 break;
494 }
495 if (file_lline(sp, ep, &lno))
496 goto err;
497 if (lno == 0) {
498 if (LF_ISSET(SEARCH_MSG))
499 msgq(sp, M_INFO, EMPTYMSG);
500 break;
501 }
502 ++lno;
503 wrapped = 1;
504 continue;
505 }
506
507 if ((l = file_gline(sp, ep, lno, &len)) == NULL)
508 goto err;
509
510 /* Set the termination. */
511 match[0].rm_so = 0;
512 match[0].rm_eo = coff ? coff : len;
513
514#if defined(DEBUG) && 0
515 TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo);
516#endif
517 /* Search the line. */
518 eval = regexec(re, l, 1, match,
519 (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND);
520 if (eval == REG_NOMATCH)
521 continue;
522 if (eval != 0) {
523 re_error(sp, eval, re);
524 break;
525 }
526
527 /* Warn if wrapped. */
528 if (wrapped && O_ISSET(sp, O_WARN) && LF_ISSET(SEARCH_MSG))
529 msgq(sp, M_INFO, WRAPMSG);
530
531 if (delta) {
532 if (check_delta(sp, ep, delta, lno))
533 break;
534 rm->lno = delta + lno;
535 rm->cno = 0;
536 } else {
537#if defined(DEBUG) && 0
538 TRACE(sp, "found: %qu to %qu\n",
539 match[0].rm_so, match[0].rm_eo);
540#endif
541 /*
542 * Find the last acceptable one in this line. This
543 * is really painful, we need a cleaner interface to
544 * regexec to make this possible.
545 */
546 for (;;) {
547 last = match[0].rm_so;
548 match[0].rm_so = match[0].rm_eo + 1;
549 if (match[0].rm_so >= len ||
550 coff && match[0].rm_so >= coff)
551 break;
552 match[0].rm_eo = coff ? coff : len;
553 eval = regexec(re, l, 1, match,
554 (match[0].rm_so == 0 ? 0 : REG_NOTBOL) |
555 REG_STARTEND);
556 if (eval == REG_NOMATCH)
557 break;
558 if (eval != 0) {
559 re_error(sp, eval, re);
560 goto err;
561 }
562 }
563 rm->lno = lno;
564
565 /* See comment in f_search(). */
566 if (!LF_ISSET(SEARCH_EOL) && last >= len)
567 rm->cno = len ? len - 1 : 0;
568 else
569 rm->cno = last;
570 }
571 rval = 0;
572 break;
573 }
574
575 /* Turn off busy message, interrupts. */
576interrupt_err:
577err: busy_off(sp);
578
579 if (F_ISSET(sp->gp, G_ISFROMTTY))
580 TEAR_DOWN_INTERRUPTS;
581
582 return (rval);
583}
584
585/*
586 * re_conv --
587 * Convert vi's regular expressions into something that the
588 * the POSIX 1003.2 RE functions can handle.
589 *
590 * There are three conversions we make to make vi's RE's (specifically
591 * the global, search, and substitute patterns) work with POSIX RE's.
592 *
593 * 1: If O_MAGIC is not set, strip backslashes from the magic character
594 * set (.[]*~) that have them, and add them to the ones that don't.
595 * 2: If O_MAGIC is not set, the string "\~" is replaced with the text
596 * from the last substitute command's replacement string. If O_MAGIC
597 * is set, it's the string "~".
598 * 3: The pattern \<ptrn\> does "word" searches, convert it to use the
599 * new RE escapes.
600 */
601int
602re_conv(sp, ptrnp, replacedp)
603 SCR *sp;
604 char **ptrnp;
605 int *replacedp;
606{
607 size_t blen, needlen;
608 int magic;
609 char *bp, *p, *t;
610
611 /*
612 * First pass through, we figure out how much space we'll need.
613 * We do it in two passes, on the grounds that most of the time
614 * the user is doing a search and won't have magic characters.
615 * That way we can skip the malloc and memmove's.
616 */
617 for (p = *ptrnp, magic = 0, needlen = 0; *p != '\0'; ++p)
618 switch (*p) {
619 case '\\':
620 switch (*++p) {
621 case '<':
622 magic = 1;
623 needlen += sizeof(RE_WSTART);
624 break;
625 case '>':
626 magic = 1;
627 needlen += sizeof(RE_WSTOP);
628 break;
629 case '~':
630 if (!O_ISSET(sp, O_MAGIC)) {
631 magic = 1;
632 needlen += sp->repl_len;
633 }
634 break;
635 case '.':
636 case '[':
637 case ']':
638 case '*':
639 if (!O_ISSET(sp, O_MAGIC)) {
640 magic = 1;
641 needlen += 1;
642 }
643 break;
644 default:
645 needlen += 2;
646 }
647 break;
648 case '~':
649 if (O_ISSET(sp, O_MAGIC)) {
650 magic = 1;
651 needlen += sp->repl_len;
652 }
653 break;
654 case '.':
655 case '[':
656 case ']':
657 case '*':
658 if (!O_ISSET(sp, O_MAGIC)) {
659 magic = 1;
660 needlen += 2;
661 }
662 break;
663 default:
664 needlen += 1;
665 break;
666 }
667
668 if (!magic) {
669 *replacedp = 0;
670 return (0);
671 }
672
673 /*
674 * Get enough memory to hold the final pattern.
675 *
676 * XXX
677 * It's nul-terminated, for now.
678 */
679 GET_SPACE_RET(sp, bp, blen, needlen + 1);
680
681 for (p = *ptrnp, t = bp; *p != '\0'; ++p)
682 switch (*p) {
683 case '\\':
684 switch (*++p) {
685 case '<':
686 memmove(t, RE_WSTART, sizeof(RE_WSTART) - 1);
687 t += sizeof(RE_WSTART) - 1;
688 break;
689 case '>':
690 memmove(t, RE_WSTOP, sizeof(RE_WSTOP) - 1);
691 t += sizeof(RE_WSTOP) - 1;
692 break;
693 case '~':
694 if (O_ISSET(sp, O_MAGIC))
695 *t++ = '~';
696 else {
697 memmove(t, sp->repl, sp->repl_len);
698 t += sp->repl_len;
699 }
700 break;
701 case '.':
702 case '[':
703 case ']':
704 case '*':
705 if (O_ISSET(sp, O_MAGIC))
706 *t++ = '\\';
707 *t++ = *p;
708 break;
709 default:
710 *t++ = '\\';
711 *t++ = *p;
712 }
713 break;
714 case '~':
715 if (O_ISSET(sp, O_MAGIC)) {
716 memmove(t, sp->repl, sp->repl_len);
717 t += sp->repl_len;
718 } else
719 *t++ = '~';
720 break;
721 case '.':
722 case '[':
723 case ']':
724 case '*':
725 if (!O_ISSET(sp, O_MAGIC))
726 *t++ = '\\';
727 *t++ = *p;
728 break;
729 default:
730 *t++ = *p;
731 break;
732 }
733 *t = '\0';
734
735 *ptrnp = bp;
736 *replacedp = 1;
737 return (0);
738}
739
740/*
741 * get_delta --
742 * Get a line delta. The trickiness is that the delta can be pretty
743 * complicated, i.e. "+3-2+3++- ++" is allowed.
744 *
745 * !!!
746 * In historic vi, if you had a delta on a search pattern which was used as
747 * a motion command, the command became a line mode command regardless of the
748 * cursor positions. A fairly common trick is to use a delta of "+0" to make
749 * the command a line mode command. This is the only place that knows about
750 * delta's, so we set the return flag information here.
751 */
752static int
753get_delta(sp, dp, valp, flagp)
754 SCR *sp;
755 char **dp;
756 long *valp;
757 u_int *flagp;
758{
759 char *p;
760 long val, tval;
761
762 for (tval = 0, p = *dp; *p != '\0'; *flagp |= SEARCH_DELTA) {
763 if (isblank(*p)) {
764 ++p;
765 continue;
766 }
767 if (*p == '+' || *p == '-') {
768 if (!isdigit(*(p + 1))) {
769 if (*p == '+') {
770 if (tval == LONG_MAX)
771 goto overflow;
772 ++tval;
773 } else {
774 if (tval == LONG_MIN)
775 goto underflow;
776 --tval;
777 }
778 ++p;
779 continue;
780 }
781 } else
782 if (!isdigit(*p))
783 break;
784
785 errno = 0;
786 val = strtol(p, &p, 10);
787 if (errno == ERANGE) {
788 if (val == LONG_MAX)
789overflow: msgq(sp, M_ERR, "Delta value overflow.");
790 else if (val == LONG_MIN)
791underflow: msgq(sp, M_ERR, "Delta value underflow.");
792 else
793 msgq(sp, M_SYSERR, NULL);
794 return (1);
795 }
796 if (val >= 0) {
797 if (LONG_MAX - val < tval)
798 goto overflow;
799 } else
800 if (-(LONG_MIN - tval) > val)
801 goto underflow;
802 tval += val;
803 }
804 *dp = p;
805 *valp = tval;
806 return (0);
807}
808
809/*
810 * check_delta --
811 * Check a line delta to see if it's legal.
812 */
813static int
814check_delta(sp, ep, delta, lno)
815 SCR *sp;
816 EXF *ep;
817 long delta;
818 recno_t lno;
819{
820 /* A delta can overflow a record number. */
821 if (delta < 0) {
822 if (lno < LONG_MAX && delta >= (long)lno) {
823 msgq(sp, M_ERR, "Search offset before line 1.");
824 return (1);
825 }
826 } else {
827 if (ULONG_MAX - lno < delta) {
828 msgq(sp, M_ERR, "Delta value overflow.");
829 return (1);
830 }
831 if (file_gline(sp, ep, lno + delta, NULL) == NULL) {
832 msgq(sp, M_ERR, "Search offset past end-of-file.");
833 return (1);
834 }
835 }
836 return (0);
837}
838
839/*
840 * re_error --
841 * Report a regular expression error.
842 */
843void
844re_error(sp, errcode, preg)
845 SCR *sp;
846 int errcode;
847 regex_t *preg;
848{
849 size_t s;
850 char *oe;
851
852 s = regerror(errcode, preg, "", 0);
853 if ((oe = malloc(s)) == NULL)
854 msgq(sp, M_SYSERR, NULL);
855 else {
856 (void)regerror(errcode, preg, oe, s);
857 msgq(sp, M_ERR, "RE error: %s", oe);
858 }
859 free(oe);
860}