Correct BSD License clause numbering from 1-2-4 to 1-2-3.
[dragonfly.git] / usr.bin / sed / main.c
1 /*-
2  * Copyright (c) 1992 Diomidis Spinellis.
3  * Copyright (c) 1992, 1993
4  *      The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Diomidis Spinellis of Imperial College, University of London.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#) Copyright (c) 1992, 1993 The Regents of the University of California.  All rights reserved.
34  * @(#)main.c   8.2 (Berkeley) 1/3/94
35  * $FreeBSD: src/usr.bin/sed/main.c,v 1.41 2008/02/09 09:12:02 dwmalone Exp $
36  * $DragonFly: src/usr.bin/sed/main.c,v 1.4 2008/04/08 13:23:38 swildner Exp $
37  */
38
39 #include <sys/types.h>
40 #include <sys/mman.h>
41 #include <sys/param.h>
42 #include <sys/stat.h>
43
44 #include <err.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <libgen.h>
48 #include <limits.h>
49 #include <locale.h>
50 #include <regex.h>
51 #include <stddef.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <unistd.h>
56
57 #include "defs.h"
58 #include "extern.h"
59
60 /*
61  * Linked list of units (strings and files) to be compiled
62  */
63 struct s_compunit {
64         struct s_compunit *next;
65         enum e_cut {CU_FILE, CU_STRING} type;
66         char *s;                        /* Pointer to string or fname */
67 };
68
69 /*
70  * Linked list pointer to compilation units and pointer to current
71  * next pointer.
72  */
73 static struct s_compunit *script, **cu_nextp = &script;
74
75 /*
76  * Linked list of files to be processed
77  */
78 struct s_flist {
79         char *fname;
80         struct s_flist *next;
81 };
82
83 /*
84  * Linked list pointer to files and pointer to current
85  * next pointer.
86  */
87 static struct s_flist *files, **fl_nextp = &files;
88
89 FILE *infile;                   /* Current input file */
90 FILE *outfile;                  /* Current output file */
91
92 int aflag, eflag, nflag;
93 int rflags = 0;
94 static int rval;                /* Exit status */
95
96 static int ispan;               /* Whether inplace editing spans across files */
97
98 /*
99  * Current file and line number; line numbers restart across compilation
100  * units, but span across input files.  The latter is optional if editing
101  * in place.
102  */
103 const char *fname;              /* File name. */
104 const char *outfname;           /* Output file name */
105 static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */
106 static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */
107 static const char *inplace;     /* Inplace edit file extension. */
108 u_long linenum;
109
110 static void add_compunit(enum e_cut, char *);
111 static void add_file(char *);
112 static void usage(void);
113
114 int
115 main(int argc, char *argv[])
116 {
117         int c, fflag;
118         char *temp_arg;
119
120         (void) setlocale(LC_ALL, "");
121
122         fflag = 0;
123         inplace = NULL;
124
125         while ((c = getopt(argc, argv, "EI:ae:f:i:ln")) != -1)
126                 switch (c) {
127                 case 'E':
128                         rflags = REG_EXTENDED;
129                         break;
130                 case 'I':
131                         inplace = optarg;
132                         ispan = 1;      /* span across input files */
133                         break;
134                 case 'a':
135                         aflag = 1;
136                         break;
137                 case 'e':
138                         eflag = 1;
139                         if ((temp_arg = malloc(strlen(optarg) + 2)) == NULL)
140                                 err(1, "malloc");
141                         strcpy(temp_arg, optarg);
142                         strcat(temp_arg, "\n");
143                         add_compunit(CU_STRING, temp_arg);
144                         break;
145                 case 'f':
146                         fflag = 1;
147                         add_compunit(CU_FILE, optarg);
148                         break;
149                 case 'i':
150                         inplace = optarg;
151                         ispan = 0;      /* don't span across input files */
152                         break;
153                 case 'l':
154                         if(setlinebuf(stdout) != 0)
155                                 warnx("setlinebuf() failed");
156                         break;
157                 case 'n':
158                         nflag = 1;
159                         break;
160                 default:
161                 case '?':
162                         usage();
163                 }
164         argc -= optind;
165         argv += optind;
166
167         /* First usage case; script is the first arg */
168         if (!eflag && !fflag && *argv) {
169                 add_compunit(CU_STRING, *argv);
170                 argv++;
171         }
172
173         compile();
174
175         /* Continue with first and start second usage */
176         if (*argv)
177                 for (; *argv; argv++)
178                         add_file(*argv);
179         else
180                 add_file(NULL);
181         process();
182         cfclose(prog, NULL);
183         if (fclose(stdout))
184                 err(1, "stdout");
185         exit(rval);
186 }
187
188 static void
189 usage(void)
190 {
191         (void)fprintf(stderr, "%s\n%s\n",
192                 "usage: sed script [-Ealn] [-i extension] [file ...]",
193                 "       sed [-Ealn] [-i extension] [-e script] ... [-f script_file] ... [file ...]");
194         exit(1);
195 }
196
197 /*
198  * Like fgets, but go through the chain of compilation units chaining them
199  * together.  Empty strings and files are ignored.
200  */
201 char *
202 cu_fgets(char *buf, int n, int *more)
203 {
204         static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF;
205         static FILE *f;         /* Current open file */
206         static char *s;         /* Current pointer inside string */
207         static char string_ident[30];
208         char *p;
209
210 again:
211         switch (state) {
212         case ST_EOF:
213                 if (script == NULL) {
214                         if (more != NULL)
215                                 *more = 0;
216                         return (NULL);
217                 }
218                 linenum = 0;
219                 switch (script->type) {
220                 case CU_FILE:
221                         if ((f = fopen(script->s, "r")) == NULL)
222                                 err(1, "%s", script->s);
223                         fname = script->s;
224                         state = ST_FILE;
225                         goto again;
226                 case CU_STRING:
227                         if (((size_t)snprintf(string_ident,
228                             sizeof(string_ident), "\"%s\"", script->s)) >=
229                             sizeof(string_ident) - 1)
230                                 (void)strcpy(string_ident +
231                                     sizeof(string_ident) - 6, " ...\"");
232                         fname = string_ident;
233                         s = script->s;
234                         state = ST_STRING;
235                         goto again;
236                 }
237         case ST_FILE:
238                 if ((p = fgets(buf, n, f)) != NULL) {
239                         linenum++;
240                         if (linenum == 1 && buf[0] == '#' && buf[1] == 'n')
241                                 nflag = 1;
242                         if (more != NULL)
243                                 *more = !feof(f);
244                         return (p);
245                 }
246                 script = script->next;
247                 (void)fclose(f);
248                 state = ST_EOF;
249                 goto again;
250         case ST_STRING:
251                 if (linenum == 0 && s[0] == '#' && s[1] == 'n')
252                         nflag = 1;
253                 p = buf;
254                 for (;;) {
255                         if (n-- <= 1) {
256                                 *p = '\0';
257                                 linenum++;
258                                 if (more != NULL)
259                                         *more = 1;
260                                 return (buf);
261                         }
262                         switch (*s) {
263                         case '\0':
264                                 state = ST_EOF;
265                                 if (s == script->s) {
266                                         script = script->next;
267                                         goto again;
268                                 } else {
269                                         script = script->next;
270                                         *p = '\0';
271                                         linenum++;
272                                         if (more != NULL)
273                                                 *more = 0;
274                                         return (buf);
275                                 }
276                         case '\n':
277                                 *p++ = '\n';
278                                 *p = '\0';
279                                 s++;
280                                 linenum++;
281                                 if (more != NULL)
282                                         *more = 0;
283                                 return (buf);
284                         default:
285                                 *p++ = *s++;
286                         }
287                 }
288         }
289         /* NOTREACHED */
290         return (NULL);
291 }
292
293 /*
294  * Like fgets, but go through the list of files chaining them together.
295  * Set len to the length of the line.
296  */
297 int
298 mf_fgets(SPACE *sp, enum e_spflag spflag)
299 {
300         struct stat sb;
301         size_t len;
302         char *p;
303         int c;
304         static int firstfile;
305
306         if (infile == NULL) {
307                 /* stdin? */
308                 if (files->fname == NULL) {
309                         if (inplace != NULL)
310                                 errx(1, "-I or -i may not be used with stdin");
311                         infile = stdin;
312                         fname = "stdin";
313                         outfile = stdout;
314                         outfname = "stdout";
315                 }
316                 firstfile = 1;
317         }
318
319         for (;;) {
320                 if (infile != NULL && (c = getc(infile)) != EOF) {
321                         (void)ungetc(c, infile);
322                         break;
323                 }
324                 /* If we are here then either eof or no files are open yet */
325                 if (infile == stdin) {
326                         sp->len = 0;
327                         return (0);
328                 }
329                 if (infile != NULL) {
330                         fclose(infile);
331                         if (*oldfname != '\0') {
332                                 if (rename(fname, oldfname) != 0) {
333                                         warn("rename()");
334                                         unlink(tmpfname);
335                                         exit(1);
336                                 }
337                                 *oldfname = '\0';
338                         }
339                         if (*tmpfname != '\0') {
340                                 if (outfile != NULL && outfile != stdout)
341                                         fclose(outfile);
342                                 outfile = NULL;
343                                 rename(tmpfname, fname);
344                                 *tmpfname = '\0';
345                         }
346                         outfname = NULL;
347                 }
348                 if (firstfile == 0)
349                         files = files->next;
350                 else
351                         firstfile = 0;
352                 if (files == NULL) {
353                         sp->len = 0;
354                         return (0);
355                 }
356                 fname = files->fname;
357                 if (inplace != NULL) {
358                         if (lstat(fname, &sb) != 0)
359                                 err(1, "%s", fname);
360                         if (!(sb.st_mode & S_IFREG))
361                                 errx(1, "%s: %s %s", fname,
362                                     "in-place editing only",
363                                     "works for regular files");
364                         if (*inplace != '\0') {
365                                 strlcpy(oldfname, fname,
366                                     sizeof(oldfname));
367                                 len = strlcat(oldfname, inplace,
368                                     sizeof(oldfname));
369                                 if (len > sizeof(oldfname))
370                                         errx(1, "%s: name too long", fname);
371                         }
372                         len = snprintf(tmpfname, sizeof(tmpfname),
373                             "%s/.!%ld!%s", dirname(fname), (long)getpid(),
374                             basename(fname));
375                         if (len >= sizeof(tmpfname))
376                                 errx(1, "%s: name too long", fname);
377                         unlink(tmpfname);
378                         if ((outfile = fopen(tmpfname, "w")) == NULL)
379                                 err(1, "%s", fname);
380                         fchown(fileno(outfile), sb.st_uid, sb.st_gid);
381                         fchmod(fileno(outfile), sb.st_mode & ALLPERMS);
382                         outfname = tmpfname;
383                         if (!ispan) {
384                                 linenum = 0;
385                                 resetstate();
386                         }
387                 } else {
388                         outfile = stdout;
389                         outfname = "stdout";
390                 }
391                 if ((infile = fopen(fname, "r")) == NULL) {
392                         warn("%s", fname);
393                         rval = 1;
394                         continue;
395                 }
396         }
397         /*
398          * We are here only when infile is open and we still have something
399          * to read from it.
400          *
401          * Use fgetln so that we can handle essentially infinite input data.
402          * Can't use the pointer into the stdio buffer as the process space
403          * because the ungetc() can cause it to move.
404          */
405         p = fgetln(infile, &len);
406         if (ferror(infile))
407                 errx(1, "%s: %s", fname, strerror(errno ? errno : EIO));
408         if (len != 0 && p[len - 1] == '\n')
409                 len--;
410         cspace(sp, p, len, spflag);
411
412         linenum++;
413
414         return (1);
415 }
416
417 /*
418  * Add a compilation unit to the linked list
419  */
420 static void
421 add_compunit(enum e_cut type, char *s)
422 {
423         struct s_compunit *cu;
424
425         if ((cu = malloc(sizeof(struct s_compunit))) == NULL)
426                 err(1, "malloc");
427         cu->type = type;
428         cu->s = s;
429         cu->next = NULL;
430         *cu_nextp = cu;
431         cu_nextp = &cu->next;
432 }
433
434 /*
435  * Add a file to the linked list
436  */
437 static void
438 add_file(char *s)
439 {
440         struct s_flist *fp;
441
442         if ((fp = malloc(sizeof(struct s_flist))) == NULL)
443                 err(1, "malloc");
444         fp->next = NULL;
445         *fl_nextp = fp;
446         fp->fname = s;
447         fl_nextp = &fp->next;
448 }
449
450 int
451 lastline(void)
452 {
453         int ch;
454
455         if (files->next != NULL && (inplace == NULL || ispan))
456                 return (0);
457         if ((ch = getc(infile)) == EOF)
458                 return (1);
459         ungetc(ch, infile);
460         return (0);
461 }