source: MondoRescue/branches/stable/mindi-busybox/editors/sed.c @ 1770

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 12 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod? silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian? keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René? Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

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