source: branches/3.2/mindi-busybox/editors/sed.c @ 3232

Last change on this file since 3232 was 3232, checked in by bruno, 5 years ago
  • Update mindi-busybox to 1.21.1
File size: 40.3 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * sed.c - very minimalist version of sed
4 *
5 * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley
6 * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org>
7 * Copyright (C) 2002  Matt Kraai
8 * Copyright (C) 2003 by Glenn McGrath
9 * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
10 *
11 * MAINTAINER: Rob Landley <rob@landley.net>
12 *
13 * Licensed under GPLv2, see file LICENSE in this source tree.
14 */
15
16/* Code overview.
17 *
18 * Files are laid out to avoid unnecessary function declarations.  So for
19 * example, every function add_cmd calls occurs before add_cmd in this file.
20 *
21 * add_cmd() is called on each line of sed command text (from a file or from
22 * the command line).  It calls get_address() and parse_cmd_args().  The
23 * resulting sed_cmd_t structures are appended to a linked list
24 * (G.sed_cmd_head/G.sed_cmd_tail).
25 *
26 * add_input_file() adds a FILE* to the list of input files.  We need to
27 * know all input sources ahead of time to find the last line for the $ match.
28 *
29 * process_files() does actual sedding, reading data lines from each input FILE*
30 * (which could be stdin) and applying the sed command list (sed_cmd_head) to
31 * each of the resulting lines.
32 *
33 * sed_main() is where external code calls into this, with a command line.
34 */
35
36/* Supported features and commands in this version of sed:
37 *
38 * - comments ('#')
39 * - address matching: num|/matchstr/[,num|/matchstr/|$]command
40 * - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags)
41 * - edit commands: (a)ppend, (i)nsert, (c)hange
42 * - file commands: (r)ead
43 * - backreferences in substitution expressions (\0, \1, \2...\9)
44 * - grouped commands: {cmd1;cmd2}
45 * - transliteration (y/source-chars/dest-chars/)
46 * - pattern space hold space storing / swapping (g, h, x)
47 * - labels / branching (: label, b, t, T)
48 *
49 * (Note: Specifying an address (range) to match is *optional*; commands
50 * default to the whole pattern space if no specific address match was
51 * requested.)
52 *
53 * Todo:
54 * - Create a wrapper around regex to make libc's regex conform with sed
55 *
56 * Reference
57 * http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
58 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
59 */
60
61//usage:#define sed_trivial_usage
62//usage:       "[-inr] [-f FILE]... [-e CMD]... [FILE]...\n"
63//usage:       "or: sed [-inr] CMD [FILE]..."
64//usage:#define sed_full_usage "\n\n"
65//usage:       "    -e CMD  Add CMD to sed commands to be executed"
66//usage:     "\n    -f FILE Add FILE contents to sed commands to be executed"
67//usage:     "\n    -i[SFX] Edit files in-place (otherwise sends to stdout)"
68//usage:     "\n        Optionally back files up, appending SFX"
69//usage:     "\n    -n  Suppress automatic printing of pattern space"
70//usage:     "\n    -r  Use extended regex syntax"
71//usage:     "\n"
72//usage:     "\nIf no -e or -f, the first non-option argument is the sed command string."
73//usage:     "\nRemaining arguments are input files (stdin if none)."
74//usage:
75//usage:#define sed_example_usage
76//usage:       "$ echo \"foo\" | sed -e 's/f[a-zA-Z]o/bar/g'\n"
77//usage:       "bar\n"
78
79#include "libbb.h"
80#include "xregex.h"
81
82#if 0
83# define dbg(...) bb_error_msg(__VA_ARGS__)
84#else
85# define dbg(...) ((void)0)
86#endif
87
88
89enum {
90    OPT_in_place = 1 << 0,
91};
92
93/* Each sed command turns into one of these structures. */
94typedef struct sed_cmd_s {
95    /* Ordered by alignment requirements: currently 36 bytes on x86 */
96    struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
97
98    /* address storage */
99    regex_t *beg_match;     /* sed -e '/match/cmd' */
100    regex_t *end_match;     /* sed -e '/match/,/end_match/cmd' */
101    regex_t *sub_match;     /* For 's/sub_match/string/' */
102    int beg_line;           /* 'sed 1p'   0 == apply commands to all lines */
103    int beg_line_orig;      /* copy of the above, needed for -i */
104    int end_line;           /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
105
106    FILE *sw_file;          /* File (sw) command writes to, -1 for none. */
107    char *string;           /* Data string for (saicytb) commands. */
108
109    unsigned which_match;   /* (s) Which match to replace (0 for all) */
110
111    /* Bitfields (gcc won't group them if we don't) */
112    unsigned invert:1;      /* the '!' after the address */
113    unsigned in_match:1;    /* Next line also included in match? */
114    unsigned sub_p:1;       /* (s) print option */
115
116    char sw_last_char;      /* Last line written by (sw) had no '\n' */
117
118    /* GENERAL FIELDS */
119    char cmd;               /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
120} sed_cmd_t;
121
122static const char semicolon_whitespace[] ALIGN1 = "; \n\r\t\v";
123
124struct globals {
125    /* options */
126    int be_quiet, regex_type;
127    FILE *nonstdout;
128    char *outname, *hold_space;
129
130    /* List of input files */
131    int input_file_count, current_input_file;
132    FILE **input_file_list;
133
134    regmatch_t regmatch[10];
135    regex_t *previous_regex_ptr;
136
137    /* linked list of sed commands */
138    sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
139
140    /* Linked list of append lines */
141    llist_t *append_head;
142
143    char *add_cmd_line;
144
145    struct pipeline {
146        char *buf;  /* Space to hold string */
147        int idx;    /* Space used */
148        int len;    /* Space allocated */
149    } pipeline;
150} FIX_ALIASING;
151#define G (*(struct globals*)&bb_common_bufsiz1)
152struct BUG_G_too_big {
153    char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
154};
155#define INIT_G() do { \
156    G.sed_cmd_tail = &G.sed_cmd_head; \
157} while (0)
158
159
160#if ENABLE_FEATURE_CLEAN_UP
161static void sed_free_and_close_stuff(void)
162{
163    sed_cmd_t *sed_cmd = G.sed_cmd_head;
164
165    llist_free(G.append_head, free);
166
167    while (sed_cmd) {
168        sed_cmd_t *sed_cmd_next = sed_cmd->next;
169
170        if (sed_cmd->sw_file)
171            xprint_and_close_file(sed_cmd->sw_file);
172
173        if (sed_cmd->beg_match) {
174            regfree(sed_cmd->beg_match);
175            free(sed_cmd->beg_match);
176        }
177        if (sed_cmd->end_match) {
178            regfree(sed_cmd->end_match);
179            free(sed_cmd->end_match);
180        }
181        if (sed_cmd->sub_match) {
182            regfree(sed_cmd->sub_match);
183            free(sed_cmd->sub_match);
184        }
185        free(sed_cmd->string);
186        free(sed_cmd);
187        sed_cmd = sed_cmd_next;
188    }
189
190    free(G.hold_space);
191
192    while (G.current_input_file < G.input_file_count)
193        fclose(G.input_file_list[G.current_input_file++]);
194}
195#else
196void sed_free_and_close_stuff(void);
197#endif
198
199/* If something bad happens during -i operation, delete temp file */
200
201static void cleanup_outname(void)
202{
203    if (G.outname) unlink(G.outname);
204}
205
206/* strcpy, replacing "\from" with 'to'. If to is NUL, replacing "\any" with 'any' */
207
208static void parse_escapes(char *dest, const char *string, int len, char from, char to)
209{
210    int i = 0;
211
212    while (i < len) {
213        if (string[i] == '\\') {
214            if (!to || string[i+1] == from) {
215                *dest++ = to ? to : string[i+1];
216                i += 2;
217                continue;
218            }
219            *dest++ = string[i++];
220        }
221        /* TODO: is it safe wrt a string with trailing '\\' ? */
222        *dest++ = string[i++];
223    }
224    *dest = '\0';
225}
226
227static char *copy_parsing_escapes(const char *string, int len)
228{
229    const char *s;
230    char *dest = xmalloc(len + 1);
231
232    /* sed recognizes \n */
233    /* GNU sed also recognizes \t and \r */
234    for (s = "\nn\tt\rr"; *s; s += 2) {
235        parse_escapes(dest, string, len, s[1], s[0]);
236        string = dest;
237        len = strlen(dest);
238    }
239    return dest;
240}
241
242
243/*
244 * index_of_next_unescaped_regexp_delim - walks left to right through a string
245 * beginning at a specified index and returns the index of the next regular
246 * expression delimiter (typically a forward slash ('/')) not preceded by
247 * a backslash ('\').  A negative delimiter disables square bracket checking.
248 */
249static int index_of_next_unescaped_regexp_delim(int delimiter, const char *str)
250{
251    int bracket = -1;
252    int escaped = 0;
253    int idx = 0;
254    char ch;
255
256    if (delimiter < 0) {
257        bracket--;
258        delimiter = -delimiter;
259    }
260
261    for (; (ch = str[idx]) != '\0'; idx++) {
262        if (bracket >= 0) {
263            if (ch == ']'
264             && !(bracket == idx - 1 || (bracket == idx - 2 && str[idx - 1] == '^'))
265            ) {
266                bracket = -1;
267            }
268        } else if (escaped)
269            escaped = 0;
270        else if (ch == '\\')
271            escaped = 1;
272        else if (bracket == -1 && ch == '[')
273            bracket = idx;
274        else if (ch == delimiter)
275            return idx;
276    }
277
278    /* if we make it to here, we've hit the end of the string */
279    bb_error_msg_and_die("unmatched '%c'", delimiter);
280}
281
282/*
283 *  Returns the index of the third delimiter
284 */
285static int parse_regex_delim(const char *cmdstr, char **match, char **replace)
286{
287    const char *cmdstr_ptr = cmdstr;
288    unsigned char delimiter;
289    int idx = 0;
290
291    /* verify that the 's' or 'y' is followed by something.  That something
292     * (typically a 'slash') is now our regexp delimiter... */
293    if (*cmdstr == '\0')
294        bb_error_msg_and_die("bad format in substitution expression");
295    delimiter = *cmdstr_ptr++;
296
297    /* save the match string */
298    idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
299    *match = copy_parsing_escapes(cmdstr_ptr, idx);
300
301    /* save the replacement string */
302    cmdstr_ptr += idx + 1;
303    idx = index_of_next_unescaped_regexp_delim(- (int)delimiter, cmdstr_ptr);
304    *replace = copy_parsing_escapes(cmdstr_ptr, idx);
305
306    return ((cmdstr_ptr - cmdstr) + idx);
307}
308
309/*
310 * returns the index in the string just past where the address ends.
311 */
312static int get_address(const char *my_str, int *linenum, regex_t ** regex)
313{
314    const char *pos = my_str;
315
316    if (isdigit(*my_str)) {
317        *linenum = strtol(my_str, (char**)&pos, 10);
318        /* endstr shouldnt ever equal NULL */
319    } else if (*my_str == '$') {
320        *linenum = -1;
321        pos++;
322    } else if (*my_str == '/' || *my_str == '\\') {
323        int next;
324        char delimiter;
325        char *temp;
326
327        delimiter = '/';
328        if (*my_str == '\\')
329            delimiter = *++pos;
330        next = index_of_next_unescaped_regexp_delim(delimiter, ++pos);
331        temp = copy_parsing_escapes(pos, next);
332        *regex = xzalloc(sizeof(regex_t));
333        xregcomp(*regex, temp, G.regex_type|REG_NEWLINE);
334        free(temp);
335        /* Move position to next character after last delimiter */
336        pos += (next+1);
337    }
338    return pos - my_str;
339}
340
341/* Grab a filename.  Whitespace at start is skipped, then goes to EOL. */
342static int parse_file_cmd(/*sed_cmd_t *sed_cmd,*/ const char *filecmdstr, char **retval)
343{
344    int start = 0, idx, hack = 0;
345
346    /* Skip whitespace, then grab filename to end of line */
347    while (isspace(filecmdstr[start]))
348        start++;
349    idx = start;
350    while (filecmdstr[idx] && filecmdstr[idx] != '\n')
351        idx++;
352
353    /* If lines glued together, put backslash back. */
354    if (filecmdstr[idx] == '\n')
355        hack = 1;
356    if (idx == start)
357        bb_error_msg_and_die("empty filename");
358    *retval = xstrndup(filecmdstr+start, idx-start+hack+1);
359    if (hack)
360        (*retval)[idx] = '\\';
361
362    return idx;
363}
364
365static int parse_subst_cmd(sed_cmd_t *sed_cmd, const char *substr)
366{
367    int cflags = G.regex_type;
368    char *match;
369    int idx;
370
371    /*
372     * A substitution command should look something like this:
373     *    s/match/replace/ #gIpw
374     *    ||     |        |||
375     *    mandatory       optional
376     */
377    idx = parse_regex_delim(substr, &match, &sed_cmd->string);
378
379    /* determine the number of back references in the match string */
380    /* Note: we compute this here rather than in the do_subst_command()
381     * function to save processor time, at the expense of a little more memory
382     * (4 bits) per sed_cmd */
383
384    /* process the flags */
385
386    sed_cmd->which_match = 1;
387    while (substr[++idx]) {
388        /* Parse match number */
389        if (isdigit(substr[idx])) {
390            if (match[0] != '^') {
391                /* Match 0 treated as all, multiple matches we take the last one. */
392                const char *pos = substr + idx;
393/* FIXME: error check? */
394                sed_cmd->which_match = (unsigned)strtol(substr+idx, (char**) &pos, 10);
395                idx = pos - substr;
396            }
397            continue;
398        }
399        /* Skip spaces */
400        if (isspace(substr[idx]))
401            continue;
402
403        switch (substr[idx]) {
404        /* Replace all occurrences */
405        case 'g':
406            if (match[0] != '^')
407                sed_cmd->which_match = 0;
408            break;
409        /* Print pattern space */
410        case 'p':
411            sed_cmd->sub_p = 1;
412            break;
413        /* Write to file */
414        case 'w':
415        {
416            char *temp;
417            idx += parse_file_cmd(/*sed_cmd,*/ substr+idx, &temp);
418            break;
419        }
420        /* Ignore case (gnu exension) */
421        case 'I':
422            cflags |= REG_ICASE;
423            break;
424        /* Comment */
425        case '#':
426            // while (substr[++idx]) continue;
427            idx += strlen(substr + idx); // same
428            /* Fall through */
429        /* End of command */
430        case ';':
431        case '}':
432            goto out;
433        default:
434            bb_error_msg_and_die("bad option in substitution expression");
435        }
436    }
437 out:
438    /* compile the match string into a regex */
439    if (*match != '\0') {
440        /* If match is empty, we use last regex used at runtime */
441        sed_cmd->sub_match = xzalloc(sizeof(regex_t));
442        dbg("xregcomp('%s',%x)", match, cflags);
443        xregcomp(sed_cmd->sub_match, match, cflags);
444        dbg("regcomp ok");
445    }
446    free(match);
447
448    return idx;
449}
450
451/*
452 *  Process the commands arguments
453 */
454static const char *parse_cmd_args(sed_cmd_t *sed_cmd, const char *cmdstr)
455{
456    static const char cmd_letters[] = "saicrw:btTydDgGhHlnNpPqx={}";
457    enum {
458        IDX_s = 0,
459        IDX_a,
460        IDX_i,
461        IDX_c,
462        IDX_r,
463        IDX_w,
464        IDX_colon,
465        IDX_b,
466        IDX_t,
467        IDX_T,
468        IDX_y,
469        IDX_d,
470        IDX_D,
471        IDX_g,
472        IDX_G,
473        IDX_h,
474        IDX_H,
475        IDX_l,
476        IDX_n,
477        IDX_N,
478        IDX_p,
479        IDX_P,
480        IDX_q,
481        IDX_x,
482        IDX_equal,
483        IDX_lbrace,
484        IDX_rbrace,
485        IDX_nul
486    };
487    struct chk { char chk[sizeof(cmd_letters)-1 == IDX_nul ? 1 : -1]; };
488
489    unsigned idx = strchrnul(cmd_letters, sed_cmd->cmd) - cmd_letters;
490
491    /* handle (s)ubstitution command */
492    if (idx == IDX_s) {
493        cmdstr += parse_subst_cmd(sed_cmd, cmdstr);
494    }
495    /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
496    else if (idx <= IDX_c) { /* a,i,c */
497        if (idx < IDX_c) { /* a,i */
498            if (sed_cmd->end_line || sed_cmd->end_match)
499                bb_error_msg_and_die("command '%c' uses only one address", sed_cmd->cmd);
500        }
501        for (;;) {
502            if (*cmdstr == '\n' || *cmdstr == '\\') {
503                cmdstr++;
504                break;
505            }
506            if (!isspace(*cmdstr))
507                break;
508            cmdstr++;
509        }
510        sed_cmd->string = xstrdup(cmdstr);
511        /* "\anychar" -> "anychar" */
512        parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), '\0', '\0');
513        cmdstr += strlen(cmdstr);
514    }
515    /* handle file cmds: (r)ead */
516    else if (idx <= IDX_w) { /* r,w */
517        if (idx < IDX_w) { /* r */
518            if (sed_cmd->end_line || sed_cmd->end_match)
519                bb_error_msg_and_die("command '%c' uses only one address", sed_cmd->cmd);
520        }
521        cmdstr += parse_file_cmd(/*sed_cmd,*/ cmdstr, &sed_cmd->string);
522        if (sed_cmd->cmd == 'w') {
523            sed_cmd->sw_file = xfopen_for_write(sed_cmd->string);
524            sed_cmd->sw_last_char = '\n';
525        }
526    }
527    /* handle branch commands */
528    else if (idx <= IDX_T) { /* :,b,t,T */
529        int length;
530
531        cmdstr = skip_whitespace(cmdstr);
532        length = strcspn(cmdstr, semicolon_whitespace);
533        if (length) {
534            sed_cmd->string = xstrndup(cmdstr, length);
535            cmdstr += length;
536        }
537    }
538    /* translation command */
539    else if (idx == IDX_y) {
540        char *match, *replace;
541        int i = cmdstr[0];
542
543        cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1;
544        /* \n already parsed, but \delimiter needs unescaping. */
545        parse_escapes(match, match, strlen(match), i, i);
546        parse_escapes(replace, replace, strlen(replace), i, i);
547
548        sed_cmd->string = xzalloc((strlen(match) + 1) * 2);
549        for (i = 0; match[i] && replace[i]; i++) {
550            sed_cmd->string[i*2] = match[i];
551            sed_cmd->string[i*2+1] = replace[i];
552        }
553        free(match);
554        free(replace);
555    }
556    /* if it wasnt a single-letter command that takes no arguments
557     * then it must be an invalid command.
558     */
559    else if (idx >= IDX_nul) { /* not d,D,g,G,h,H,l,n,N,p,P,q,x,=,{,} */
560        bb_error_msg_and_die("unsupported command %c", sed_cmd->cmd);
561    }
562
563    /* give back whatever's left over */
564    return cmdstr;
565}
566
567
568/* Parse address+command sets, skipping comment lines. */
569
570static void add_cmd(const char *cmdstr)
571{
572    sed_cmd_t *sed_cmd;
573    unsigned len, n;
574
575    /* Append this line to any unfinished line from last time. */
576    if (G.add_cmd_line) {
577        char *tp = xasprintf("%s\n%s", G.add_cmd_line, cmdstr);
578        free(G.add_cmd_line);
579        cmdstr = G.add_cmd_line = tp;
580    }
581
582    /* If this line ends with unescaped backslash, request next line. */
583    n = len = strlen(cmdstr);
584    while (n && cmdstr[n-1] == '\\')
585        n--;
586    if ((len - n) & 1) { /* if odd number of trailing backslashes */
587        if (!G.add_cmd_line)
588            G.add_cmd_line = xstrdup(cmdstr);
589        G.add_cmd_line[len-1] = '\0';
590        return;
591    }
592
593    /* Loop parsing all commands in this line. */
594    while (*cmdstr) {
595        /* Skip leading whitespace and semicolons */
596        cmdstr += strspn(cmdstr, semicolon_whitespace);
597
598        /* If no more commands, exit. */
599        if (!*cmdstr) break;
600
601        /* if this is a comment, jump past it and keep going */
602        if (*cmdstr == '#') {
603            /* "#n" is the same as using -n on the command line */
604            if (cmdstr[1] == 'n')
605                G.be_quiet++;
606            cmdstr = strpbrk(cmdstr, "\n\r");
607            if (!cmdstr) break;
608            continue;
609        }
610
611        /* parse the command
612         * format is: [addr][,addr][!]cmd
613         *            |----||-----||-|
614         *            part1 part2  part3
615         */
616
617        sed_cmd = xzalloc(sizeof(sed_cmd_t));
618
619        /* first part (if present) is an address: either a '$', a number or a /regex/ */
620        cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
621        sed_cmd->beg_line_orig = sed_cmd->beg_line;
622
623        /* second part (if present) will begin with a comma */
624        if (*cmdstr == ',') {
625            int idx;
626
627            cmdstr++;
628            idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
629            if (!idx)
630                bb_error_msg_and_die("no address after comma");
631            cmdstr += idx;
632        }
633
634        /* skip whitespace before the command */
635        cmdstr = skip_whitespace(cmdstr);
636
637        /* Check for inversion flag */
638        if (*cmdstr == '!') {
639            sed_cmd->invert = 1;
640            cmdstr++;
641
642            /* skip whitespace before the command */
643            cmdstr = skip_whitespace(cmdstr);
644        }
645
646        /* last part (mandatory) will be a command */
647        if (!*cmdstr)
648            bb_error_msg_and_die("missing command");
649        sed_cmd->cmd = *cmdstr++;
650        cmdstr = parse_cmd_args(sed_cmd, cmdstr);
651
652        /* Add the command to the command array */
653        *G.sed_cmd_tail = sed_cmd;
654        G.sed_cmd_tail = &sed_cmd->next;
655    }
656
657    /* If we glued multiple lines together, free the memory. */
658    free(G.add_cmd_line);
659    G.add_cmd_line = NULL;
660}
661
662/* Append to a string, reallocating memory as necessary. */
663
664#define PIPE_GROW 64
665
666static void pipe_putc(char c)
667{
668    if (G.pipeline.idx == G.pipeline.len) {
669        G.pipeline.buf = xrealloc(G.pipeline.buf,
670                G.pipeline.len + PIPE_GROW);
671        G.pipeline.len += PIPE_GROW;
672    }
673    G.pipeline.buf[G.pipeline.idx++] = c;
674}
675
676static void do_subst_w_backrefs(char *line, char *replace)
677{
678    int i, j;
679
680    /* go through the replacement string */
681    for (i = 0; replace[i]; i++) {
682        /* if we find a backreference (\1, \2, etc.) print the backref'ed text */
683        if (replace[i] == '\\') {
684            unsigned backref = replace[++i] - '0';
685            if (backref <= 9) {
686                /* print out the text held in G.regmatch[backref] */
687                if (G.regmatch[backref].rm_so != -1) {
688                    j = G.regmatch[backref].rm_so;
689                    while (j < G.regmatch[backref].rm_eo)
690                        pipe_putc(line[j++]);
691                }
692                continue;
693            }
694            /* I _think_ it is impossible to get '\' to be
695             * the last char in replace string. Thus we dont check
696             * for replace[i] == NUL. (counterexample anyone?) */
697            /* if we find a backslash escaped character, print the character */
698            pipe_putc(replace[i]);
699            continue;
700        }
701        /* if we find an unescaped '&' print out the whole matched text. */
702        if (replace[i] == '&') {
703            j = G.regmatch[0].rm_so;
704            while (j < G.regmatch[0].rm_eo)
705                pipe_putc(line[j++]);
706            continue;
707        }
708        /* Otherwise just output the character. */
709        pipe_putc(replace[i]);
710    }
711}
712
713static int do_subst_command(sed_cmd_t *sed_cmd, char **line_p)
714{
715    char *line = *line_p;
716    unsigned match_count = 0;
717    bool altered = 0;
718    bool prev_match_empty = 1;
719    bool tried_at_eol = 0;
720    regex_t *current_regex;
721
722    current_regex = sed_cmd->sub_match;
723    /* Handle empty regex. */
724    if (!current_regex) {
725        current_regex = G.previous_regex_ptr;
726        if (!current_regex)
727            bb_error_msg_and_die("no previous regexp");
728    }
729    G.previous_regex_ptr = current_regex;
730
731    /* Find the first match */
732    dbg("matching '%s'", line);
733    if (REG_NOMATCH == regexec(current_regex, line, 10, G.regmatch, 0)) {
734        dbg("no match");
735        return 0;
736    }
737    dbg("match");
738
739    /* Initialize temporary output buffer. */
740    G.pipeline.buf = xmalloc(PIPE_GROW);
741    G.pipeline.len = PIPE_GROW;
742    G.pipeline.idx = 0;
743
744    /* Now loop through, substituting for matches */
745    do {
746        int start = G.regmatch[0].rm_so;
747        int end = G.regmatch[0].rm_eo;
748        int i;
749
750        match_count++;
751
752        /* If we aren't interested in this match, output old line to
753         * end of match and continue */
754        if (sed_cmd->which_match
755         && (sed_cmd->which_match != match_count)
756        ) {
757            for (i = 0; i < end; i++)
758                pipe_putc(*line++);
759            /* Null match? Print one more char */
760            if (start == end && *line)
761                pipe_putc(*line++);
762            goto next;
763        }
764
765        /* Print everything before the match */
766        for (i = 0; i < start; i++)
767            pipe_putc(line[i]);
768
769        /* Then print the substitution string,
770         * unless we just matched empty string after non-empty one.
771         * Example: string "cccd", pattern "c*", repl "R":
772         * result is "RdR", not "RRdR": first match "ccc",
773         * second is "" before "d", third is "" after "d".
774         * Second match is NOT replaced!
775         */
776        if (prev_match_empty || start != 0 || start != end) {
777            //dbg("%d %d %d", prev_match_empty, start, end);
778            dbg("inserting replacement at %d in '%s'", start, line);
779            do_subst_w_backrefs(line, sed_cmd->string);
780            /* Flag that something has changed */
781            altered = 1;
782        } else {
783            dbg("NOT inserting replacement at %d in '%s'", start, line);
784        }
785
786        /* If matched string is empty (f.e. "c*" pattern),
787         * copy verbatim one char after it before attempting more matches
788         */
789        prev_match_empty = (start == end);
790        if (prev_match_empty) {
791            if (!line[end]) {
792                tried_at_eol = 1;
793            } else {
794                pipe_putc(line[end]);
795                end++;
796            }
797        }
798
799        /* Advance past the match */
800        dbg("line += %d", end);
801        line += end;
802
803        /* if we're not doing this globally, get out now */
804        if (sed_cmd->which_match != 0)
805            break;
806 next:
807        /* Exit if we are at EOL and already tried matching at it */
808        if (*line == '\0') {
809            if (tried_at_eol)
810                break;
811            tried_at_eol = 1;
812        }
813
814//maybe (end ? REG_NOTBOL : 0) instead of unconditional REG_NOTBOL?
815    } while (regexec(current_regex, line, 10, G.regmatch, REG_NOTBOL) != REG_NOMATCH);
816
817    /* Copy rest of string into output pipeline */
818    while (1) {
819        char c = *line++;
820        pipe_putc(c);
821        if (c == '\0')
822            break;
823    }
824
825    free(*line_p);
826    *line_p = G.pipeline.buf;
827    return altered;
828}
829
830/* Set command pointer to point to this label.  (Does not handle null label.) */
831static sed_cmd_t *branch_to(char *label)
832{
833    sed_cmd_t *sed_cmd;
834
835    for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
836        if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) {
837            return sed_cmd;
838        }
839    }
840    bb_error_msg_and_die("can't find label for jump to '%s'", label);
841}
842
843static void append(char *s)
844{
845    llist_add_to_end(&G.append_head, xstrdup(s));
846}
847
848static void flush_append(void)
849{
850    char *data;
851
852    /* Output appended lines. */
853    while ((data = (char *)llist_pop(&G.append_head))) {
854        fprintf(G.nonstdout, "%s\n", data);
855        free(data);
856    }
857}
858
859static void add_input_file(FILE *file)
860{
861    G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
862    G.input_file_list[G.input_file_count++] = file;
863}
864
865/* Get next line of input from G.input_file_list, flushing append buffer and
866 * noting if we ran out of files without a newline on the last line we read.
867 */
868enum {
869    NO_EOL_CHAR = 1,
870    LAST_IS_NUL = 2,
871};
872static char *get_next_line(char *gets_char)
873{
874    char *temp = NULL;
875    int len;
876    char gc;
877
878    flush_append();
879
880    /* will be returned if last line in the file
881     * doesn't end with either '\n' or '\0' */
882    gc = NO_EOL_CHAR;
883    while (G.current_input_file < G.input_file_count) {
884        FILE *fp = G.input_file_list[G.current_input_file];
885        /* Read line up to a newline or NUL byte, inclusive,
886         * return malloc'ed char[]. length of the chunk read
887         * is stored in len. NULL if EOF/error */
888        temp = bb_get_chunk_from_file(fp, &len);
889        if (temp) {
890            /* len > 0 here, it's ok to do temp[len-1] */
891            char c = temp[len-1];
892            if (c == '\n' || c == '\0') {
893                temp[len-1] = '\0';
894                gc = c;
895                if (c == '\0') {
896                    int ch = fgetc(fp);
897                    if (ch != EOF)
898                        ungetc(ch, fp);
899                    else
900                        gc = LAST_IS_NUL;
901                }
902            }
903            /* else we put NO_EOL_CHAR into *gets_char */
904            break;
905
906        /* NB: I had the idea of peeking next file(s) and returning
907         * NO_EOL_CHAR only if it is the *last* non-empty
908         * input file. But there is a case where this won't work:
909         * file1: "a woo\nb woo"
910         * file2: "c no\nd no"
911         * sed -ne 's/woo/bang/p' input1 input2 => "a bang\nb bang"
912         * (note: *no* newline after "b bang"!) */
913        }
914        /* Close this file and advance to next one */
915        fclose(fp);
916        G.current_input_file++;
917    }
918    *gets_char = gc;
919    return temp;
920}
921
922/* Output line of text. */
923/* Note:
924 * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
925 * Without them, we had this:
926 * echo -n thingy >z1
927 * echo -n again >z2
928 * >znull
929 * sed "s/i/z/" z1 z2 znull | hexdump -vC
930 * output:
931 * gnu sed 4.1.5:
932 * 00000000  74 68 7a 6e 67 79 0a 61  67 61 7a 6e              |thzngy.agazn|
933 * bbox:
934 * 00000000  74 68 7a 6e 67 79 61 67  61 7a 6e                 |thzngyagazn|
935 */
936static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
937{
938    char lpc = *last_puts_char;
939
940    /* Need to insert a '\n' between two files because first file's
941     * last line wasn't terminated? */
942    if (lpc != '\n' && lpc != '\0') {
943        fputc('\n', file);
944        lpc = '\n';
945    }
946    fputs(s, file);
947
948    /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
949    if (s[0])
950        lpc = 'x';
951
952    /* had trailing '\0' and it was last char of file? */
953    if (last_gets_char == LAST_IS_NUL) {
954        fputc('\0', file);
955        lpc = 'x'; /* */
956    } else
957    /* had trailing '\n' or '\0'? */
958    if (last_gets_char != NO_EOL_CHAR) {
959        fputc(last_gets_char, file);
960        lpc = last_gets_char;
961    }
962
963    if (ferror(file)) {
964        xfunc_error_retval = 4;  /* It's what gnu sed exits with... */
965        bb_error_msg_and_die(bb_msg_write_error);
966    }
967    *last_puts_char = lpc;
968}
969
970#define sed_puts(s, n) (puts_maybe_newline(s, G.nonstdout, &last_puts_char, n))
971
972static int beg_match(sed_cmd_t *sed_cmd, const char *pattern_space)
973{
974    int retval = sed_cmd->beg_match && !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0);
975    if (retval)
976        G.previous_regex_ptr = sed_cmd->beg_match;
977    return retval;
978}
979
980/* Process all the lines in all the files */
981
982static void process_files(void)
983{
984    char *pattern_space, *next_line;
985    int linenum = 0;
986    char last_puts_char = '\n';
987    char last_gets_char, next_gets_char;
988    sed_cmd_t *sed_cmd;
989    int substituted;
990
991    /* Prime the pump */
992    next_line = get_next_line(&next_gets_char);
993
994    /* Go through every line in each file */
995 again:
996    substituted = 0;
997
998    /* Advance to next line.  Stop if out of lines. */
999    pattern_space = next_line;
1000    if (!pattern_space)
1001        return;
1002    last_gets_char = next_gets_char;
1003
1004    /* Read one line in advance so we can act on the last line,
1005     * the '$' address */
1006    next_line = get_next_line(&next_gets_char);
1007    linenum++;
1008
1009    /* For every line, go through all the commands */
1010 restart:
1011    for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
1012        int old_matched, matched;
1013
1014        old_matched = sed_cmd->in_match;
1015
1016        /* Determine if this command matches this line: */
1017
1018        dbg("match1:%d", sed_cmd->in_match);
1019        dbg("match2:%d", (!sed_cmd->beg_line && !sed_cmd->end_line
1020                && !sed_cmd->beg_match && !sed_cmd->end_match));
1021        dbg("match3:%d", (sed_cmd->beg_line > 0
1022            && (sed_cmd->end_line || sed_cmd->end_match
1023                ? (sed_cmd->beg_line <= linenum)
1024                : (sed_cmd->beg_line == linenum)
1025                )
1026            ));
1027        dbg("match4:%d", (beg_match(sed_cmd, pattern_space)));
1028        dbg("match5:%d", (sed_cmd->beg_line == -1 && next_line == NULL));
1029
1030        /* Are we continuing a previous multi-line match? */
1031        sed_cmd->in_match = sed_cmd->in_match
1032            /* Or is no range necessary? */
1033            || (!sed_cmd->beg_line && !sed_cmd->end_line
1034                && !sed_cmd->beg_match && !sed_cmd->end_match)
1035            /* Or did we match the start of a numerical range? */
1036            || (sed_cmd->beg_line > 0
1037                && (sed_cmd->end_line || sed_cmd->end_match
1038                  /* note: even if end is numeric and is < linenum too,
1039                   * GNU sed matches! We match too, therefore we don't
1040                   * check here that linenum <= end.
1041                   * Example:
1042                   * printf '1\n2\n3\n4\n' | sed -n '1{N;N;d};1p;2,3p;3p;4p'
1043                   * first three input lines are deleted;
1044                   * 4th line is matched and printed
1045                   * by "2,3" (!) and by "4" ranges
1046                   */
1047                ? (sed_cmd->beg_line <= linenum)    /* N,end */
1048                : (sed_cmd->beg_line == linenum)    /* N */
1049                )
1050                )
1051            /* Or does this line match our begin address regex? */
1052            || (beg_match(sed_cmd, pattern_space))
1053            /* Or did we match last line of input? */
1054            || (sed_cmd->beg_line == -1 && next_line == NULL);
1055
1056        /* Snapshot the value */
1057        matched = sed_cmd->in_match;
1058
1059        dbg("cmd:'%c' matched:%d beg_line:%d end_line:%d linenum:%d",
1060            sed_cmd->cmd, matched, sed_cmd->beg_line, sed_cmd->end_line, linenum);
1061
1062        /* Is this line the end of the current match? */
1063
1064        if (matched) {
1065            /* once matched, "n,xxx" range is dead, disabling it */
1066            if (sed_cmd->beg_line > 0) {
1067                sed_cmd->beg_line = -2;
1068            }
1069            sed_cmd->in_match = !(
1070                /* has the ending line come, or is this a single address command? */
1071                (sed_cmd->end_line
1072                    ? sed_cmd->end_line == -1
1073                        ? !next_line
1074                        : (sed_cmd->end_line <= linenum)
1075                    : !sed_cmd->end_match
1076                )
1077                /* or does this line matches our last address regex */
1078                || (sed_cmd->end_match && old_matched
1079                     && (regexec(sed_cmd->end_match,
1080                        pattern_space, 0, NULL, 0) == 0)
1081                )
1082            );
1083        }
1084
1085        /* Skip blocks of commands we didn't match */
1086        if (sed_cmd->cmd == '{') {
1087            if (sed_cmd->invert ? matched : !matched) {
1088                unsigned nest_cnt = 0;
1089                while (1) {
1090                    if (sed_cmd->cmd == '{')
1091                        nest_cnt++;
1092                    if (sed_cmd->cmd == '}') {
1093                        nest_cnt--;
1094                        if (nest_cnt == 0)
1095                            break;
1096                    }
1097                    sed_cmd = sed_cmd->next;
1098                    if (!sed_cmd)
1099                        bb_error_msg_and_die("unterminated {");
1100                }
1101            }
1102            continue;
1103        }
1104
1105        /* Okay, so did this line match? */
1106        if (sed_cmd->invert ? matched : !matched)
1107            continue; /* no */
1108
1109        /* Update last used regex in case a blank substitute BRE is found */
1110        if (sed_cmd->beg_match) {
1111            G.previous_regex_ptr = sed_cmd->beg_match;
1112        }
1113
1114        /* actual sedding */
1115        dbg("pattern_space:'%s' next_line:'%s' cmd:%c",
1116                pattern_space, next_line, sed_cmd->cmd);
1117        switch (sed_cmd->cmd) {
1118
1119        /* Print line number */
1120        case '=':
1121            fprintf(G.nonstdout, "%d\n", linenum);
1122            break;
1123
1124        /* Write the current pattern space up to the first newline */
1125        case 'P':
1126        {
1127            char *tmp = strchr(pattern_space, '\n');
1128            if (tmp) {
1129                *tmp = '\0';
1130                /* TODO: explain why '\n' below */
1131                sed_puts(pattern_space, '\n');
1132                *tmp = '\n';
1133                break;
1134            }
1135            /* Fall Through */
1136        }
1137
1138        /* Write the current pattern space to output */
1139        case 'p':
1140            /* NB: we print this _before_ the last line
1141             * (of current file) is printed. Even if
1142             * that line is nonterminated, we print
1143             * '\n' here (gnu sed does the same) */
1144            sed_puts(pattern_space, '\n');
1145            break;
1146        /* Delete up through first newline */
1147        case 'D':
1148        {
1149            char *tmp = strchr(pattern_space, '\n');
1150            if (tmp) {
1151                overlapping_strcpy(pattern_space, tmp + 1);
1152                goto restart;
1153            }
1154        }
1155        /* discard this line. */
1156        case 'd':
1157            goto discard_line;
1158
1159        /* Substitute with regex */
1160        case 's':
1161            if (!do_subst_command(sed_cmd, &pattern_space))
1162                break;
1163            dbg("do_subst_command succeeded:'%s'", pattern_space);
1164            substituted |= 1;
1165
1166            /* handle p option */
1167            if (sed_cmd->sub_p)
1168                sed_puts(pattern_space, last_gets_char);
1169            /* handle w option */
1170            if (sed_cmd->sw_file)
1171                puts_maybe_newline(
1172                    pattern_space, sed_cmd->sw_file,
1173                    &sed_cmd->sw_last_char, last_gets_char);
1174            break;
1175
1176        /* Append line to linked list to be printed later */
1177        case 'a':
1178            append(sed_cmd->string);
1179            break;
1180
1181        /* Insert text before this line */
1182        case 'i':
1183            sed_puts(sed_cmd->string, '\n');
1184            break;
1185
1186        /* Cut and paste text (replace) */
1187        case 'c':
1188            /* Only triggers on last line of a matching range. */
1189            if (!sed_cmd->in_match)
1190                sed_puts(sed_cmd->string, '\n');
1191            goto discard_line;
1192
1193        /* Read file, append contents to output */
1194        case 'r':
1195        {
1196            FILE *rfile;
1197            rfile = fopen_for_read(sed_cmd->string);
1198            if (rfile) {
1199                char *line;
1200
1201                while ((line = xmalloc_fgetline(rfile))
1202                        != NULL)
1203                    append(line);
1204                xprint_and_close_file(rfile);
1205            }
1206
1207            break;
1208        }
1209
1210        /* Write pattern space to file. */
1211        case 'w':
1212            puts_maybe_newline(
1213                pattern_space, sed_cmd->sw_file,
1214                &sed_cmd->sw_last_char, last_gets_char);
1215            break;
1216
1217        /* Read next line from input */
1218        case 'n':
1219            if (!G.be_quiet)
1220                sed_puts(pattern_space, last_gets_char);
1221            if (next_line) {
1222                free(pattern_space);
1223                pattern_space = next_line;
1224                last_gets_char = next_gets_char;
1225                next_line = get_next_line(&next_gets_char);
1226                substituted = 0;
1227                linenum++;
1228                break;
1229            }
1230            /* fall through */
1231
1232        /* Quit.  End of script, end of input. */
1233        case 'q':
1234            /* Exit the outer while loop */
1235            free(next_line);
1236            next_line = NULL;
1237            goto discard_commands;
1238
1239        /* Append the next line to the current line */
1240        case 'N':
1241        {
1242            int len;
1243            /* If no next line, jump to end of script and exit. */
1244            /* http://www.gnu.org/software/sed/manual/sed.html:
1245             * "Most versions of sed exit without printing anything
1246             * when the N command is issued on the last line of
1247             * a file. GNU sed prints pattern space before exiting
1248             * unless of course the -n command switch has been
1249             * specified. This choice is by design."
1250             */
1251            if (next_line == NULL) {
1252                //goto discard_line;
1253                goto discard_commands; /* GNU behavior */
1254            }
1255            /* Append next_line, read new next_line. */
1256            len = strlen(pattern_space);
1257            pattern_space = xrealloc(pattern_space, len + strlen(next_line) + 2);
1258            pattern_space[len] = '\n';
1259            strcpy(pattern_space + len+1, next_line);
1260            last_gets_char = next_gets_char;
1261            next_line = get_next_line(&next_gets_char);
1262            linenum++;
1263            break;
1264        }
1265
1266        /* Test/branch if substitution occurred */
1267        case 't':
1268            if (!substituted) break;
1269            substituted = 0;
1270            /* Fall through */
1271        /* Test/branch if substitution didn't occur */
1272        case 'T':
1273            if (substituted) break;
1274            /* Fall through */
1275        /* Branch to label */
1276        case 'b':
1277            if (!sed_cmd->string) goto discard_commands;
1278            else sed_cmd = branch_to(sed_cmd->string);
1279            break;
1280        /* Transliterate characters */
1281        case 'y':
1282        {
1283            int i, j;
1284            for (i = 0; pattern_space[i]; i++) {
1285                for (j = 0; sed_cmd->string[j]; j += 2) {
1286                    if (pattern_space[i] == sed_cmd->string[j]) {
1287                        pattern_space[i] = sed_cmd->string[j + 1];
1288                        break;
1289                    }
1290                }
1291            }
1292
1293            break;
1294        }
1295        case 'g':   /* Replace pattern space with hold space */
1296            free(pattern_space);
1297            pattern_space = xstrdup(G.hold_space ? G.hold_space : "");
1298            break;
1299        case 'G':   /* Append newline and hold space to pattern space */
1300        {
1301            int pattern_space_size = 2;
1302            int hold_space_size = 0;
1303
1304            if (pattern_space)
1305                pattern_space_size += strlen(pattern_space);
1306            if (G.hold_space)
1307                hold_space_size = strlen(G.hold_space);
1308            pattern_space = xrealloc(pattern_space,
1309                    pattern_space_size + hold_space_size);
1310            if (pattern_space_size == 2)
1311                pattern_space[0] = 0;
1312            strcat(pattern_space, "\n");
1313            if (G.hold_space)
1314                strcat(pattern_space, G.hold_space);
1315            last_gets_char = '\n';
1316
1317            break;
1318        }
1319        case 'h':   /* Replace hold space with pattern space */
1320            free(G.hold_space);
1321            G.hold_space = xstrdup(pattern_space);
1322            break;
1323        case 'H':   /* Append newline and pattern space to hold space */
1324        {
1325            int hold_space_size = 2;
1326            int pattern_space_size = 0;
1327
1328            if (G.hold_space)
1329                hold_space_size += strlen(G.hold_space);
1330            if (pattern_space)
1331                pattern_space_size = strlen(pattern_space);
1332            G.hold_space = xrealloc(G.hold_space,
1333                    hold_space_size + pattern_space_size);
1334
1335            if (hold_space_size == 2)
1336                *G.hold_space = 0;
1337            strcat(G.hold_space, "\n");
1338            if (pattern_space)
1339                strcat(G.hold_space, pattern_space);
1340
1341            break;
1342        }
1343        case 'x': /* Exchange hold and pattern space */
1344        {
1345            char *tmp = pattern_space;
1346            pattern_space = G.hold_space ? G.hold_space : xzalloc(1);
1347            last_gets_char = '\n';
1348            G.hold_space = tmp;
1349            break;
1350        }
1351        } /* switch */
1352    } /* for each cmd */
1353
1354    /*
1355     * Exit point from sedding...
1356     */
1357 discard_commands:
1358    /* we will print the line unless we were told to be quiet ('-n')
1359       or if the line was suppressed (ala 'd'elete) */
1360    if (!G.be_quiet)
1361        sed_puts(pattern_space, last_gets_char);
1362
1363    /* Delete and such jump here. */
1364 discard_line:
1365    flush_append();
1366    free(pattern_space);
1367
1368    goto again;
1369}
1370
1371/* It is possible to have a command line argument with embedded
1372 * newlines.  This counts as multiple command lines.
1373 * However, newline can be escaped: 's/e/z\<newline>z/'
1374 * We check for this.
1375 */
1376
1377static void add_cmd_block(char *cmdstr)
1378{
1379    char *sv, *eol;
1380
1381    cmdstr = sv = xstrdup(cmdstr);
1382    do {
1383        eol = strchr(cmdstr, '\n');
1384 next:
1385        if (eol) {
1386            /* Count preceding slashes */
1387            int slashes = 0;
1388            char *sl = eol;
1389
1390            while (sl != cmdstr && *--sl == '\\')
1391                slashes++;
1392            /* Odd number of preceding slashes - newline is escaped */
1393            if (slashes & 1) {
1394                overlapping_strcpy(eol - 1, eol);
1395                eol = strchr(eol, '\n');
1396                goto next;
1397            }
1398            *eol = '\0';
1399        }
1400        add_cmd(cmdstr);
1401        cmdstr = eol + 1;
1402    } while (eol);
1403    free(sv);
1404}
1405
1406int sed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1407int sed_main(int argc UNUSED_PARAM, char **argv)
1408{
1409    unsigned opt;
1410    llist_t *opt_e, *opt_f;
1411    char *opt_i;
1412
1413#if ENABLE_LONG_OPTS
1414    static const char sed_longopts[] ALIGN1 =
1415        /* name             has_arg             short */
1416        "in-place\0"        Optional_argument   "i"
1417        "regexp-extended\0" No_argument         "r"
1418        "quiet\0"           No_argument         "n"
1419        "silent\0"          No_argument         "n"
1420        "expression\0"      Required_argument   "e"
1421        "file\0"            Required_argument   "f";
1422#endif
1423
1424    int status = EXIT_SUCCESS;
1425
1426    INIT_G();
1427
1428    /* destroy command strings on exit */
1429    if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);
1430
1431    /* Lie to autoconf when it starts asking stupid questions. */
1432    if (argv[1] && strcmp(argv[1], "--version") == 0) {
1433        puts("This is not GNU sed version 4.0");
1434        return 0;
1435    }
1436
1437    /* do normal option parsing */
1438    opt_e = opt_f = NULL;
1439    opt_i = NULL;
1440    opt_complementary = "e::f::" /* can occur multiple times */
1441                        "nn"; /* count -n */
1442
1443    IF_LONG_OPTS(applet_long_options = sed_longopts);
1444
1445    /* -i must be first, to match OPT_in_place definition */
1446    opt = getopt32(argv, "i::rne:f:", &opt_i, &opt_e, &opt_f,
1447                &G.be_quiet); /* counter for -n */
1448    //argc -= optind;
1449    argv += optind;
1450    if (opt & OPT_in_place) { // -i
1451        atexit(cleanup_outname);
1452    }
1453    if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r
1454    //if (opt & 0x4) G.be_quiet++; // -n
1455    while (opt_e) { // -e
1456        add_cmd_block(llist_pop(&opt_e));
1457    }
1458    while (opt_f) { // -f
1459        char *line;
1460        FILE *cmdfile;
1461        cmdfile = xfopen_for_read(llist_pop(&opt_f));
1462        while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
1463            add_cmd(line);
1464            free(line);
1465        }
1466        fclose(cmdfile);
1467    }
1468    /* if we didn't get a pattern from -e or -f, use argv[0] */
1469    if (!(opt & 0x18)) {
1470        if (!*argv)
1471            bb_show_usage();
1472        add_cmd_block(*argv++);
1473    }
1474    /* Flush any unfinished commands. */
1475    add_cmd("");
1476
1477    /* By default, we write to stdout */
1478    G.nonstdout = stdout;
1479
1480    /* argv[0..(argc-1)] should be names of file to process. If no
1481     * files were specified or '-' was specified, take input from stdin.
1482     * Otherwise, we process all the files specified. */
1483    if (argv[0] == NULL) {
1484        if (opt & OPT_in_place)
1485            bb_error_msg_and_die(bb_msg_requires_arg, "-i");
1486        add_input_file(stdin);
1487    } else {
1488        int i;
1489
1490        for (i = 0; argv[i]; i++) {
1491            struct stat statbuf;
1492            int nonstdoutfd;
1493            FILE *file;
1494            sed_cmd_t *sed_cmd;
1495
1496            if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) {
1497                add_input_file(stdin);
1498                process_files();
1499                continue;
1500            }
1501            file = fopen_or_warn(argv[i], "r");
1502            if (!file) {
1503                status = EXIT_FAILURE;
1504                continue;
1505            }
1506            add_input_file(file);
1507            if (!(opt & OPT_in_place)) {
1508                continue;
1509            }
1510
1511            /* -i: process each FILE separately: */
1512
1513            G.outname = xasprintf("%sXXXXXX", argv[i]);
1514            nonstdoutfd = xmkstemp(G.outname);
1515            G.nonstdout = xfdopen_for_write(nonstdoutfd);
1516
1517            /* Set permissions/owner of output file */
1518            fstat(fileno(file), &statbuf);
1519            /* chmod'ing AFTER chown would preserve suid/sgid bits,
1520             * but GNU sed 4.2.1 does not preserve them either */
1521            fchmod(nonstdoutfd, statbuf.st_mode);
1522            fchown(nonstdoutfd, statbuf.st_uid, statbuf.st_gid);
1523
1524            process_files();
1525            fclose(G.nonstdout);
1526            G.nonstdout = stdout;
1527
1528            if (opt_i) {
1529                char *backupname = xasprintf("%s%s", argv[i], opt_i);
1530                xrename(argv[i], backupname);
1531                free(backupname);
1532            }
1533            /* else unlink(argv[i]); - rename below does this */
1534            xrename(G.outname, argv[i]); //TODO: rollback backup on error?
1535            free(G.outname);
1536            G.outname = NULL;
1537
1538            /* Re-enable disabled range matches */
1539            for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
1540                sed_cmd->beg_line = sed_cmd->beg_line_orig;
1541            }
1542        }
1543        /* Here, to handle "sed 'cmds' nonexistent_file" case we did:
1544         * if (G.current_input_file >= G.input_file_count)
1545         *  return status;
1546         * but it's not needed since process_files() works correctly
1547         * in this case too. */
1548    }
1549    process_files();
1550
1551    return status;
1552}
Note: See TracBrowser for help on using the repository browser.