1 /* $OpenBSD: main.c,v 1.35 2017/08/01 18:05:53 martijn Exp $ */
4 * Copyright (c) 2016, 2017
5 * mirabilos <m@mirbsd.org>
6 * Copyright (c) 1992 Diomidis Spinellis.
7 * Copyright (c) 1992, 1993
8 * The Regents of the University of California. All rights reserved.
10 * This code is derived from software contributed to Berkeley by
11 * Diomidis Spinellis of Imperial College, University of London.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. 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.
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
38 #include <sys/types.h>
39 #include <sys/ioctl.h>
57 __RCSID("$MirOS: src/usr.bin/sed/main.c,v 1.3 2017/11/20 01:23:57 tg Exp $");
60 * Linked list of units (strings and files) to be compiled
63 struct s_compunit
*next
;
64 enum e_cut
{CU_FILE
, CU_STRING
} type
;
65 char *s
; /* Pointer to string or fname */
69 * Linked list pointer to compilation units and pointer to current
72 static struct s_compunit
*script
, **cu_nextp
= &script
;
75 * Linked list of files to be processed
83 * Linked list pointer to files and pointer to current
86 static struct s_flist
*files
, **fl_nextp
= &files
;
88 FILE *infile
; /* Current input file */
89 FILE *outfile
; /* Current output file */
91 int Eflag
, aflag
, eflag
, nflag
;
92 static int rval
; /* Exit status */
95 * Current file and line number; line numbers restart across compilation
96 * units, but span across input files. The latter is optional if editing
99 const char *fname
; /* File name. */
100 const char *outfname
; /* Output file name */
101 static char oldfname
[PATH_MAX
]; /* Old file name (for in-place editing) */
102 static char tmpfname
[PATH_MAX
]; /* Temporary file name (for in-place editing) */
103 const char *inplace
; /* Inplace edit file extension */
106 static void add_compunit(enum e_cut
, char *);
107 static void add_file(char *);
108 static int next_files_have_lines(void);
112 int pledge_wpath
, pledge_rpath
;
115 main(int argc
, char *argv
[])
121 while ((c
= getopt(argc
, argv
, "Eae:f:i::nru")) != -1)
132 add_compunit(CU_STRING
, optarg
);
136 add_compunit(CU_FILE
, optarg
);
139 inplace
= optarg
? optarg
: "";
145 setvbuf(stdout
, NULL
, _IOLBF
, 0);
149 (void)fprintf(stderr
,
150 "usage: sed [-aEnru] [-i[extension]] command [file ...]\n"
151 " sed [-aEnru] [-e command] [-f command_file] [-i[extension]] [file ...]\n");
159 #if defined(__OpenBSD__) && !defined(__MirBSD__)
160 if (inplace
!= NULL
) {
161 if (pledge("stdio rpath wpath cpath fattr chown", NULL
) == -1)
162 error(FATAL
, "pledge: %s", strerror(errno
));
164 if (pledge("stdio rpath wpath cpath", NULL
) == -1)
165 error(FATAL
, "pledge: %s", strerror(errno
));
169 /* First usage case; script is the first arg */
170 if (!eflag
&& !fflag
&& *argv
) {
171 add_compunit(CU_STRING
, *argv
);
177 /* Continue with first and start second usage */
179 #if defined(__OpenBSD__) && !defined(__MirBSD__)
180 if (!pledge_wpath
&& inplace
== NULL
) {
181 if (pledge("stdio rpath", NULL
) == -1)
182 error(FATAL
, "pledge: %s", strerror(errno
));
185 for (; *argv
; argv
++)
188 #if defined(__OpenBSD__) && !defined(__MirBSD__)
189 if (!pledge_wpath
&& !pledge_rpath
) {
190 if (pledge("stdio", NULL
) == -1)
191 error(FATAL
, "pledge: %s", strerror(errno
));
192 } else if (pledge_rpath
) {
193 if (pledge("stdio rpath", NULL
) == -1)
194 error(FATAL
, "pledge: %s", strerror(errno
));
195 } else if (pledge_wpath
) {
196 if (pledge("stdio wpath cpath", NULL
) == -1)
197 error(FATAL
, "pledge: %s", strerror(errno
));
205 error(FATAL
, "stdout: %s", strerror(errno
));
210 * Like fgets, but go through the chain of compilation units chaining them
211 * together. Empty strings and files are ignored.
214 cu_fgets(char **outbuf
, size_t *outsize
)
216 static enum {ST_EOF
, ST_FILE
, ST_STRING
} state
= ST_EOF
;
217 static FILE *f
; /* Current open file */
218 static char *s
; /* Current pointer inside string */
219 static char string_ident
[30];
230 goto cu_fgets_nilreturn
;
232 switch (script
->type
) {
234 if ((f
= fopen(script
->s
, "r")) == NULL
)
236 "%s: %s", script
->s
, strerror(errno
));
241 if (((size_t)snprintf(string_ident
,
242 sizeof(string_ident
), "\"%s\"", script
->s
)) >=
243 sizeof(string_ident
))
244 strlcpy(string_ident
+
245 sizeof(string_ident
) - 6, " ...\"", 5);
246 fname
= string_ident
;
252 if ((p
= fgetln(f
, &len
)) != NULL
) {
254 if (len
>= *outsize
) {
256 *outsize
= ROUNDLEN(len
+ 1);
257 *outbuf
= xmalloc(*outsize
);
259 memcpy(*outbuf
, p
, len
);
260 (*outbuf
)[len
] = '\0';
261 if (linenum
== 1 && p
[0] == '#' && p
[1] == 'n')
265 script
= script
->next
;
270 if (linenum
== 0 && s
[0] == '#' && s
[1] == 'n')
276 *outbuf
= xrealloc(*outbuf
,
277 *outsize
+ _POSIX2_LINE_MAX
);
278 p
= *outbuf
+ *outsize
- len
;
279 len
+= _POSIX2_LINE_MAX
;
280 *outsize
+= _POSIX2_LINE_MAX
;
285 if (s
== script
->s
) {
286 script
= script
->next
;
289 script
= script
->next
;
307 /* but GCC doesn't care, so: */
313 * Like fgets, but go through the list of files chaining them together.
314 * Set len to the length of the line.
317 mf_fgets(SPACE
*sp
, enum e_spflag spflag
)
323 static int firstfile
;
325 if (infile
== NULL
) {
327 if (files
->fname
== NULL
) {
329 error(FATAL
, "-i may not be used with stdin");
340 if (infile
!= NULL
&& (c
= getc(infile
)) != EOF
) {
341 (void)ungetc(c
, infile
);
344 /* If we are here then either eof or no files are open yet */
345 if (infile
== stdin
) {
349 if (infile
!= NULL
) {
351 if (*oldfname
!= '\0') {
352 if (rename(fname
, oldfname
) != 0) {
359 if (*tmpfname
!= '\0') {
360 if (outfile
!= NULL
&& outfile
!= stdout
)
363 rename(tmpfname
, fname
);
376 fname
= files
->fname
;
377 if (inplace
!= NULL
) {
380 if (lstat(fname
, &sb
) != 0)
381 error(FATAL
, "%s: %s", fname
,
382 strerror(errno
? errno
: EIO
));
383 if (!S_ISREG(sb
.st_mode
))
384 error(FATAL
, "%s: %s %s", fname
,
385 "in-place editing only",
386 "works for regular files");
387 if (*inplace
!= '\0') {
388 strlcpy(oldfname
, fname
,
390 len
= strlcat(oldfname
, inplace
,
392 if (len
> sizeof(oldfname
))
393 error(FATAL
, "%s: name too long", fname
);
395 len
= snprintf(tmpfname
, sizeof(tmpfname
), "%s/sedXXXXXXXXXX",
396 dirname(tmpdirname
= strdup(fname
)));
398 if (len
>= sizeof(tmpfname
))
399 error(FATAL
, "%s: name too long", fname
);
400 if ((fd
= mkstemp(tmpfname
)) == -1)
401 error(FATAL
, "%s: %s", fname
, strerror(errno
));
402 if ((outfile
= fdopen(fd
, "w")) == NULL
) {
404 error(FATAL
, "%s", fname
);
406 fchown(fileno(outfile
), sb
.st_uid
, sb
.st_gid
);
407 fchmod(fileno(outfile
), sb
.st_mode
& ALLPERMS
);
415 if ((infile
= fopen(fname
, "r")) == NULL
) {
416 warning("%s", strerror(errno
));
423 * We are here only when infile is open and we still have something
426 * Use fgetln so that we can handle essentially infinite input data.
427 * Can't use the pointer into the stdio buffer as the process space
428 * because the ungetc() can cause it to move.
430 p
= fgetln(infile
, &len
);
432 error(FATAL
, "%s: %s", fname
, strerror(errno
? errno
: EIO
));
433 if (len
!= 0 && p
[len
- 1] == '\n') {
434 sp
->append_newline
= 1;
436 } else if (!lastline()) {
437 sp
->append_newline
= 1;
439 sp
->append_newline
= 0;
441 cspace(sp
, p
, len
, spflag
);
449 * Add a compilation unit to the linked list
452 add_compunit(enum e_cut type
, char *s
)
454 struct s_compunit
*cu
;
456 cu
= xmalloc(sizeof(struct s_compunit
));
461 cu_nextp
= &cu
->next
;
465 * Add a file to the linked list
472 fp
= xmalloc(sizeof(struct s_flist
));
476 fl_nextp
= &fp
->next
;
481 next_files_have_lines(void)
483 struct s_flist
*file
;
488 while ((file
= file
->next
) != NULL
) {
489 if ((file_fd
= fopen(file
->fname
, "r")) == NULL
)
492 if ((ch
= getc(file_fd
)) != EOF
) {
494 * This next file has content, therefore current
495 * file doesn't contains the last line.
514 next_files_have_lines());
516 if ((ch
= getc(infile
)) == EOF
) {
519 next_files_have_lines());