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

Last change on this file was 1770, checked in by Bruno Cornec, 16 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.