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