524 sed -i usage: link("test", "test-e") Err#17 EEXIST
[illumos-gate.git] / usr / src / cmd / sed / main.c
blobc3699152a22e8128d8cc9a56065c534d4f772712
1 /*
2 * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
3 * Copyright (c) 1992 Diomidis Spinellis.
4 * Copyright (c) 1992, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
8 * Diomidis Spinellis of Imperial College, University of London.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 4. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
35 #include <sys/types.h>
36 #include <sys/mman.h>
37 #include <sys/param.h>
38 #include <sys/stat.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <libgen.h>
44 #include <limits.h>
45 #include <locale.h>
46 #include <regex.h>
47 #include <stddef.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <libintl.h>
54 #include "defs.h"
55 #include "extern.h"
58 * Linked list of units (strings and files) to be compiled
60 struct s_compunit {
61 struct s_compunit *next;
62 enum e_cut {CU_FILE, CU_STRING} type;
63 char *s; /* Pointer to string or fname */
67 * Linked list pointer to compilation units and pointer to current
68 * next pointer.
70 static struct s_compunit *script, **cu_nextp = &script;
73 * Linked list of files to be processed
75 struct s_flist {
76 char *fname;
77 struct s_flist *next;
81 * Linked list pointer to files and pointer to current
82 * next pointer.
84 static struct s_flist *files, **fl_nextp = &files;
86 FILE *infile; /* Current input file */
87 FILE *outfile; /* Current output file */
89 int aflag, eflag, nflag;
90 int rflags = 0;
91 static int rval; /* Exit status */
93 static int ispan; /* Whether inplace editing spans across files */
96 * Current file and line number; line numbers restart across compilation
97 * units, but span across input files. The latter is optional if editing
98 * in place.
100 const char *fname; /* File name. */
101 const char *outfname; /* Output file name */
102 static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */
103 static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */
104 static const char *inplace; /* Inplace edit file extension. */
105 ulong_t linenum;
107 static void add_compunit(enum e_cut, char *);
108 static void add_file(char *);
109 static void usage(void);
110 static char *getln(FILE *, size_t *);
114 main(int argc, char *argv[])
116 int c, fflag;
117 char *temp_arg;
119 (void) setlocale(LC_ALL, "");
121 #ifndef TEXT_DOMAIN
122 #define TEXT_DOMAIN "SYS_TEST"
123 #endif
124 (void) textdomain(TEXT_DOMAIN);
126 fflag = 0;
127 inplace = NULL;
129 while ((c = getopt(argc, argv, "EI:ae:f:i:lnr")) != -1)
130 switch (c) {
131 case 'r': /* Gnu sed compat */
132 case 'E':
133 rflags = REG_EXTENDED;
134 break;
135 case 'I':
136 inplace = optarg;
137 ispan = 1; /* span across input files */
138 break;
139 case 'a':
140 aflag = 1;
141 break;
142 case 'e':
143 eflag = 1;
144 if (asprintf(&temp_arg, "%s\n", optarg) <= 1)
145 err(1, "asprintf");
146 add_compunit(CU_STRING, temp_arg);
147 break;
148 case 'f':
149 fflag = 1;
150 add_compunit(CU_FILE, optarg);
151 break;
152 case 'i':
153 inplace = optarg;
154 ispan = 0; /* don't span across input files */
155 break;
156 case 'l':
157 /* On SunOS, setlinebuf "returns no useful value */
158 (void) setlinebuf(stdout);
159 break;
160 case 'n':
161 nflag = 1;
162 break;
163 default:
164 case '?':
165 usage();
167 argc -= optind;
168 argv += optind;
170 /* First usage case; script is the first arg */
171 if (!eflag && !fflag && *argv) {
172 add_compunit(CU_STRING, *argv);
173 argv++;
176 compile();
178 /* Continue with first and start second usage */
179 if (*argv)
180 for (; *argv; argv++)
181 add_file(*argv);
182 else
183 add_file(NULL);
184 process();
185 cfclose(prog, NULL);
186 if (fclose(stdout))
187 err(1, "stdout");
188 return (rval);
191 static void
192 usage(void)
194 (void) fputs(_("usage: sed script [-Ealn] [-i extension] [file ...]\n"
195 " sed [-Ealn] [-i extension] [-e script] ... "
196 "[-f script_file] ... [file ...]"),
197 stderr);
198 exit(1);
202 * Like fgets, but go through the chain of compilation units chaining them
203 * together. Empty strings and files are ignored.
205 char *
206 cu_fgets(char *buf, int n, int *more)
208 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF;
209 static FILE *f; /* Current open file */
210 static char *s; /* Current pointer inside string */
211 static char string_ident[30];
212 char *p;
214 again:
215 switch (state) {
216 case ST_EOF:
217 if (script == NULL) {
218 if (more != NULL)
219 *more = 0;
220 return (NULL);
222 linenum = 0;
223 switch (script->type) {
224 case CU_FILE:
225 if ((f = fopen(script->s, "r")) == NULL)
226 err(1, "%s", script->s);
227 fname = script->s;
228 state = ST_FILE;
229 goto again;
230 case CU_STRING:
231 if (((size_t)snprintf(string_ident,
232 sizeof (string_ident), "\"%s\"", script->s)) >=
233 sizeof (string_ident) - 1)
234 (void) strcpy(string_ident +
235 sizeof (string_ident) - 6, " ...\"");
236 fname = string_ident;
237 s = script->s;
238 state = ST_STRING;
239 goto again;
241 /*NOTREACHED*/
243 case ST_FILE:
244 if ((p = fgets(buf, n, f)) != NULL) {
245 linenum++;
246 if (linenum == 1 && buf[0] == '#' && buf[1] == 'n')
247 nflag = 1;
248 if (more != NULL)
249 *more = !feof(f);
250 return (p);
252 script = script->next;
253 (void) fclose(f);
254 state = ST_EOF;
255 goto again;
256 case ST_STRING:
257 if (linenum == 0 && s[0] == '#' && s[1] == 'n')
258 nflag = 1;
259 p = buf;
260 for (;;) {
261 if (n-- <= 1) {
262 *p = '\0';
263 linenum++;
264 if (more != NULL)
265 *more = 1;
266 return (buf);
268 switch (*s) {
269 case '\0':
270 state = ST_EOF;
271 if (s == script->s) {
272 script = script->next;
273 goto again;
274 } else {
275 script = script->next;
276 *p = '\0';
277 linenum++;
278 if (more != NULL)
279 *more = 0;
280 return (buf);
282 case '\n':
283 *p++ = '\n';
284 *p = '\0';
285 s++;
286 linenum++;
287 if (more != NULL)
288 *more = 0;
289 return (buf);
290 default:
291 *p++ = *s++;
295 /* NOTREACHED */
296 return (NULL);
300 * Like fgets, but go through the list of files chaining them together.
301 * Set len to the length of the line.
304 mf_fgets(SPACE *sp, enum e_spflag spflag)
306 struct stat sb;
307 size_t len;
308 char *p;
309 int c;
310 static int firstfile;
312 if (infile == NULL) {
313 /* stdin? */
314 if (files->fname == NULL) {
315 if (inplace != NULL)
316 errx(1,
317 _("-I or -i may not be used with stdin"));
318 infile = stdin;
319 fname = "stdin";
320 outfile = stdout;
321 outfname = "stdout";
323 firstfile = 1;
326 for (;;) {
327 if (infile != NULL && (c = getc(infile)) != EOF) {
328 (void) ungetc(c, infile);
329 break;
331 /* If we are here then either eof or no files are open yet */
332 if (infile == stdin) {
333 sp->len = 0;
334 return (0);
336 if (infile != NULL) {
337 (void) fclose(infile);
338 if (*oldfname != '\0') {
339 /* if there was a backup file, remove it */
340 (void) unlink(oldfname);
341 if (link(fname, oldfname) != 0) {
342 warn("link()");
343 (void) unlink(tmpfname);
344 exit(1);
346 *oldfname = '\0';
348 if (*tmpfname != '\0') {
349 if (outfile != NULL && outfile != stdout)
350 if (fclose(outfile) != 0) {
351 warn("fclose()");
352 (void) unlink(tmpfname);
353 exit(1);
355 outfile = NULL;
356 if (rename(tmpfname, fname) != 0) {
357 /* this should not happen really! */
358 warn("rename()");
359 (void) unlink(tmpfname);
360 exit(1);
362 *tmpfname = '\0';
364 outfname = NULL;
366 if (firstfile == 0)
367 files = files->next;
368 else
369 firstfile = 0;
370 if (files == NULL) {
371 sp->len = 0;
372 return (0);
374 fname = files->fname;
375 if (inplace != NULL) {
376 char bn[PATH_MAX];
377 char dn[PATH_MAX];
378 (void) strlcpy(bn, fname, sizeof (bn));
379 (void) strlcpy(dn, fname, sizeof (dn));
380 if (lstat(fname, &sb) != 0)
381 err(1, "%s", fname);
382 if (!(sb.st_mode & S_IFREG))
383 fatal(_("in-place editing only "
384 "works for regular files"));
385 if (*inplace != '\0') {
386 (void) strlcpy(oldfname, fname,
387 sizeof (oldfname));
388 len = strlcat(oldfname, inplace,
389 sizeof (oldfname));
390 if (len > sizeof (oldfname))
391 fatal(_("name too long"));
393 len = snprintf(tmpfname, sizeof (tmpfname),
394 "%s/.!%ld!%s", dirname(dn), (long)getpid(),
395 basename(bn));
396 if (len >= sizeof (tmpfname))
397 fatal(_("name too long"));
398 (void) unlink(tmpfname);
399 if ((outfile = fopen(tmpfname, "w")) == NULL)
400 err(1, "%s", fname);
401 if (fchown(fileno(outfile), sb.st_uid, sb.st_gid) != 0)
402 warn("fchown()");
403 if (fchmod(fileno(outfile), sb.st_mode & 07777) != 0)
404 warn("fchmod()");
405 outfname = tmpfname;
406 if (!ispan) {
407 linenum = 0;
408 resetstate();
410 } else {
411 outfile = stdout;
412 outfname = "stdout";
414 if ((infile = fopen(fname, "r")) == NULL) {
415 warn("%s", fname);
416 rval = 1;
417 continue;
421 * We are here only when infile is open and we still have something
422 * to read from it.
424 * Use fgetln so that we can handle essentially infinite input data.
425 * Can't use the pointer into the stdio buffer as the process space
426 * because the ungetc() can cause it to move.
428 p = getln(infile, &len);
429 if (ferror(infile))
430 errx(1, "%s: %s", fname, strerror(errno ? errno : EIO));
431 if (len != 0 && p[len - 1] == '\n')
432 len--;
433 cspace(sp, p, len, spflag);
435 linenum++;
437 return (1);
441 * Add a compilation unit to the linked list
443 static void
444 add_compunit(enum e_cut type, char *s)
446 struct s_compunit *cu;
448 if ((cu = malloc(sizeof (struct s_compunit))) == NULL)
449 err(1, "malloc");
450 cu->type = type;
451 cu->s = s;
452 cu->next = NULL;
453 *cu_nextp = cu;
454 cu_nextp = &cu->next;
458 * Add a file to the linked list
460 static void
461 add_file(char *s)
463 struct s_flist *fp;
465 if ((fp = malloc(sizeof (struct s_flist))) == NULL)
466 err(1, "malloc");
467 fp->next = NULL;
468 *fl_nextp = fp;
469 fp->fname = s;
470 fl_nextp = &fp->next;
474 lastline(void)
476 int ch;
478 if (files->next != NULL && (inplace == NULL || ispan))
479 return (0);
480 if ((ch = getc(infile)) == EOF)
481 return (1);
482 (void) ungetc(ch, infile);
483 return (0);
486 char *
487 getln(FILE *in, size_t *lenp)
489 static char *buffer = NULL;
490 static size_t sz = 0;
492 size_t len = 0;
494 for (;;) {
495 if (sz <= (len + 1)) {
496 char *nb;
497 if ((nb = realloc(buffer, sz + LINE_MAX)) == NULL) {
498 err(1, "realloc");
500 buffer = nb;
501 sz += LINE_MAX;
504 buffer[len] = 0;
506 if (fgets(buffer + len, sz - len, in) == NULL) {
507 /* END OF FILE */
508 *lenp = len;
509 break;
512 len += strlen(buffer + len);
514 if (buffer[len - 1] == '\n') {
515 /* got the new line */
516 *lenp = len;
517 break;
521 return (buffer);