1/*- 2 * Copyright (c) 2013 Johann 'Myrkraverk' Oskarsson. 3 * Copyright (c) 1992 Diomidis Spinellis. 4 * Copyright (c) 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Diomidis Spinellis of Imperial College, University of London. 9 * 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. 21 * 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. 33 */ 34 35#include <sys/cdefs.h> 36__FBSDID("$FreeBSD: stable/11/usr.bin/sed/main.c 362688 2020-06-27 14:31:33Z 0mp $"); 37 38#ifndef lint 39static const char copyright[] = 40"@(#) Copyright (c) 1992, 1993\n\ 41 The Regents of the University of California. All rights reserved.\n"; 42#endif 43 44#ifndef lint 45static const char sccsid[] = "@(#)main.c 8.2 (Berkeley) 1/3/94"; 46#endif 47 48#include <sys/types.h> 49#include <sys/mman.h> 50#include <sys/param.h> 51#include <sys/stat.h> 52 53#include <err.h> 54#include <errno.h> 55#include <fcntl.h> 56#include <libgen.h> 57#include <limits.h> 58#include <locale.h> 59#include <regex.h> 60#include <stddef.h> 61#define _WITH_GETLINE 62#include <stdio.h> 63#include <stdlib.h> 64#include <string.h> 65#include <unistd.h> 66 67#include "defs.h" 68#include "extern.h" 69 70/* 71 * Linked list of units (strings and files) to be compiled 72 */ 73struct s_compunit { 74 struct s_compunit *next; 75 enum e_cut {CU_FILE, CU_STRING} type; 76 char *s; /* Pointer to string or fname */ 77}; 78 79/* 80 * Linked list pointer to compilation units and pointer to current 81 * next pointer. 82 */ 83static struct s_compunit *script, **cu_nextp = &script; 84 85/* 86 * Linked list of files to be processed 87 */ 88struct s_flist { 89 char *fname; 90 struct s_flist *next; 91}; 92 93/* 94 * Linked list pointer to files and pointer to current 95 * next pointer. 96 */ 97static struct s_flist *files, **fl_nextp = &files; 98 99FILE *infile; /* Current input file */ 100FILE *outfile; /* Current output file */ 101 102int aflag, eflag, nflag; 103int rflags = 0; 104int quit = 0; 105static int rval; /* Exit status */ 106 107static int ispan; /* Whether inplace editing spans across files */ 108 109/* 110 * Current file and line number; line numbers restart across compilation 111 * units, but span across input files. The latter is optional if editing 112 * in place. 113 */ 114const char *fname; /* File name. */ 115const char *outfname; /* Output file name */ 116static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ 117static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ 118const char *inplace; /* Inplace edit file extension. */ 119u_long linenum; 120 121static void add_compunit(enum e_cut, char *); 122static void add_file(char *); 123static void usage(void); 124 125int 126main(int argc, char *argv[]) 127{ 128 int c, fflag, fflagstdin; 129 char *temp_arg; 130 131 (void) setlocale(LC_ALL, ""); 132 133 fflag = 0; 134 fflagstdin = 0; 135 inplace = NULL; 136 137 while ((c = getopt(argc, argv, "EI:ae:f:i:lnru")) != -1) 138 switch (c) { 139 case 'r': /* Gnu sed compat */ 140 case 'E': 141 rflags = REG_EXTENDED; 142 break; 143 case 'I': 144 inplace = optarg; 145 ispan = 1; /* span across input files */ 146 break; 147 case 'a': 148 aflag = 1; 149 break; 150 case 'e': 151 eflag = 1; 152 if ((temp_arg = malloc(strlen(optarg) + 2)) == NULL) 153 err(1, "malloc"); 154 strcpy(temp_arg, optarg); 155 strcat(temp_arg, "\n"); 156 add_compunit(CU_STRING, temp_arg); 157 break; 158 case 'f': 159 fflag = 1; 160 if (strcmp(optarg, "-") == 0) 161 fflagstdin = 1; 162 add_compunit(CU_FILE, optarg); 163 break; 164 case 'i': 165 inplace = optarg; 166 ispan = 0; /* don't span across input files */ 167 break; 168 case 'l': 169 if(setvbuf(stdout, NULL, _IOLBF, 0) != 0) 170 warnx("setting line buffered output failed"); 171 break; 172 case 'n': 173 nflag = 1; 174 break; 175 case 'u': 176 if(setvbuf(stdout, NULL, _IONBF, 0) != 0) 177 warnx("setting unbuffered output failed"); 178 break; 179 default: 180 case '?': 181 usage(); 182 } 183 argc -= optind; 184 argv += optind; 185 186 /* First usage case; script is the first arg */ 187 if (!eflag && !fflag && *argv) { 188 add_compunit(CU_STRING, *argv); 189 argv++; 190 } 191 192 compile(); 193 194 /* Continue with first and start second usage */ 195 if (*argv) 196 for (; *argv; argv++) 197 add_file(*argv); 198 else if (fflagstdin) 199 exit(rval); 200 else 201 add_file(NULL); 202 process(); 203 cfclose(prog, NULL); 204 if (fclose(stdout)) 205 err(1, "stdout"); 206 exit(rval); 207} 208 209static void 210usage(void) 211{ 212 (void)fprintf(stderr, 213 "usage: %s script [-Ealnru] [-i extension] [file ...]\n" 214 "\t%s [-Ealnu] [-i extension] [-e script] ... [-f script_file]" 215 " ... [file ...]\n", getprogname(), getprogname()); 216 exit(1); 217} 218 219/* 220 * Like fgets, but go through the chain of compilation units chaining them 221 * together. Empty strings and files are ignored. 222 */ 223char * 224cu_fgets(char *buf, int n, int *more) 225{ 226 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 227 static FILE *f; /* Current open file */ 228 static char *s; /* Current pointer inside string */ 229 static char string_ident[30]; 230 char *p; 231 232again: 233 switch (state) { 234 case ST_EOF: 235 if (script == NULL) { 236 if (more != NULL) 237 *more = 0; 238 return (NULL); 239 } 240 linenum = 0; 241 switch (script->type) { 242 case CU_FILE: 243 if (strcmp(script->s, "-") == 0) { 244 f = stdin; 245 fname = "stdin"; 246 } else { 247 if ((f = fopen(script->s, "r")) == NULL) 248 err(1, "%s", script->s); 249 fname = script->s; 250 } 251 state = ST_FILE; 252 goto again; 253 case CU_STRING: 254 if (((size_t)snprintf(string_ident, 255 sizeof(string_ident), "\"%s\"", script->s)) >= 256 sizeof(string_ident) - 1) 257 (void)strcpy(string_ident + 258 sizeof(string_ident) - 6, " ...\""); 259 fname = string_ident; 260 s = script->s; 261 state = ST_STRING; 262 goto again; 263 } 264 case ST_FILE: 265 if ((p = fgets(buf, n, f)) != NULL) { 266 linenum++; 267 if (linenum == 1 && buf[0] == '#' && buf[1] == 'n') 268 nflag = 1; 269 if (more != NULL) 270 *more = !feof(f); 271 return (p); 272 } 273 script = script->next; 274 (void)fclose(f); 275 state = ST_EOF; 276 goto again; 277 case ST_STRING: 278 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 279 nflag = 1; 280 p = buf; 281 for (;;) { 282 if (n-- <= 1) { 283 *p = '\0'; 284 linenum++; 285 if (more != NULL) 286 *more = 1; 287 return (buf); 288 } 289 switch (*s) { 290 case '\0': 291 state = ST_EOF; 292 if (s == script->s) { 293 script = script->next; 294 goto again; 295 } else { 296 script = script->next; 297 *p = '\0'; 298 linenum++; 299 if (more != NULL) 300 *more = 0; 301 return (buf); 302 } 303 case '\n': 304 *p++ = '\n'; 305 *p = '\0'; 306 s++; 307 linenum++; 308 if (more != NULL) 309 *more = 0; 310 return (buf); 311 default: 312 *p++ = *s++; 313 } 314 } 315 } 316 /* NOTREACHED */ 317 return (NULL); 318} 319 320/* 321 * Like fgets, but go through the list of files chaining them together. 322 * Set len to the length of the line. 323 */ 324int 325mf_fgets(SPACE *sp, enum e_spflag spflag) 326{ 327 struct stat sb; 328 ssize_t len; 329 static char *p = NULL; 330 static size_t plen = 0; 331 int c; 332 static int firstfile; 333 334 if (infile == NULL) { 335 /* stdin? */ 336 if (files->fname == NULL) { 337 if (inplace != NULL) 338 errx(1, "-I or -i may not be used with stdin"); 339 infile = stdin; 340 fname = "stdin"; 341 outfile = stdout; 342 outfname = "stdout"; 343 } 344 firstfile = 1; 345 } 346 347 for (;;) { 348 if (infile != NULL && (c = getc(infile)) != EOF && !quit) { 349 (void)ungetc(c, infile); 350 break; 351 } 352 /* If we are here then either eof or no files are open yet */ 353 if (infile == stdin) { 354 sp->len = 0; 355 return (0); 356 } 357 if (infile != NULL) { 358 fclose(infile); 359 if (*oldfname != '\0') { 360 /* if there was a backup file, remove it */ 361 unlink(oldfname); 362 /* 363 * Backup the original. Note that hard links 364 * are not supported on all filesystems. 365 */ 366 if ((link(fname, oldfname) != 0) && 367 (rename(fname, oldfname) != 0)) { 368 warn("rename()"); 369 if (*tmpfname) 370 unlink(tmpfname); 371 exit(1); 372 } 373 *oldfname = '\0'; 374 } 375 if (*tmpfname != '\0') { 376 if (outfile != NULL && outfile != stdout) 377 if (fclose(outfile) != 0) { 378 warn("fclose()"); 379 unlink(tmpfname); 380 exit(1); 381 } 382 outfile = NULL; 383 if (rename(tmpfname, fname) != 0) { 384 /* this should not happen really! */ 385 warn("rename()"); 386 unlink(tmpfname); 387 exit(1); 388 } 389 *tmpfname = '\0'; 390 } 391 outfname = NULL; 392 } 393 if (firstfile == 0) 394 files = files->next; 395 else 396 firstfile = 0; 397 if (files == NULL) { 398 sp->len = 0; 399 return (0); 400 } 401 fname = files->fname; 402 if (inplace != NULL) { 403 if (lstat(fname, &sb) != 0) 404 err(1, "%s", fname); 405 if (!S_ISREG(sb.st_mode)) 406 errx(1, "%s: %s %s", fname, 407 "in-place editing only", 408 "works for regular files"); 409 if (*inplace != '\0') { 410 strlcpy(oldfname, fname, 411 sizeof(oldfname)); 412 len = strlcat(oldfname, inplace, 413 sizeof(oldfname)); 414 if (len > (ssize_t)sizeof(oldfname)) 415 errx(1, "%s: name too long", fname); 416 } 417 len = snprintf(tmpfname, sizeof(tmpfname), 418 "%s/.!%ld!%s", dirname(fname), (long)getpid(), 419 basename(fname)); 420 if (len >= (ssize_t)sizeof(tmpfname)) 421 errx(1, "%s: name too long", fname); 422 unlink(tmpfname); 423 if (outfile != NULL && outfile != stdout) 424 fclose(outfile); 425 if ((outfile = fopen(tmpfname, "w")) == NULL) 426 err(1, "%s", fname); 427 fchown(fileno(outfile), sb.st_uid, sb.st_gid); 428 fchmod(fileno(outfile), sb.st_mode & ALLPERMS); 429 outfname = tmpfname; 430 if (!ispan) { 431 linenum = 0; 432 resetstate(); 433 } 434 } else { 435 outfile = stdout; 436 outfname = "stdout"; 437 } 438 if ((infile = fopen(fname, "r")) == NULL) { 439 warn("%s", fname); 440 rval = 1; 441 continue; 442 } 443 } 444 /* 445 * We are here only when infile is open and we still have something 446 * to read from it. 447 * 448 * Use getline() so that we can handle essentially infinite input 449 * data. The p and plen are static so each invocation gives 450 * getline() the same buffer which is expanded as needed. 451 */ 452 len = getline(&p, &plen, infile); 453 if (len == -1) 454 err(1, "%s", fname); 455 if (len != 0 && p[len - 1] == '\n') { 456 sp->append_newline = 1; 457 len--; 458 } else if (!lastline()) { 459 sp->append_newline = 1; 460 } else { 461 sp->append_newline = 0; 462 } 463 cspace(sp, p, len, spflag); 464 465 linenum++; 466 467 return (1); 468} 469 470/* 471 * Add a compilation unit to the linked list 472 */ 473static void 474add_compunit(enum e_cut type, char *s) 475{ 476 struct s_compunit *cu; 477 478 if ((cu = malloc(sizeof(struct s_compunit))) == NULL) 479 err(1, "malloc"); 480 cu->type = type; 481 cu->s = s; 482 cu->next = NULL; 483 *cu_nextp = cu; 484 cu_nextp = &cu->next; 485} 486 487/* 488 * Add a file to the linked list 489 */ 490static void 491add_file(char *s) 492{ 493 struct s_flist *fp; 494 495 if ((fp = malloc(sizeof(struct s_flist))) == NULL) 496 err(1, "malloc"); 497 fp->next = NULL; 498 *fl_nextp = fp; 499 fp->fname = s; 500 fl_nextp = &fp->next; 501} 502 503static int 504next_files_have_lines(void) 505{ 506 struct s_flist *file; 507 FILE *file_fd; 508 int ch; 509 510 file = files; 511 while ((file = file->next) != NULL) { 512 if ((file_fd = fopen(file->fname, "r")) == NULL) 513 continue; 514 515 if ((ch = getc(file_fd)) != EOF) { 516 /* 517 * This next file has content, therefore current 518 * file doesn't contains the last line. 519 */ 520 ungetc(ch, file_fd); 521 fclose(file_fd); 522 return (1); 523 } 524 525 fclose(file_fd); 526 } 527 528 return (0); 529} 530 531int 532lastline(void) 533{ 534 int ch; 535 536 if (feof(infile)) { 537 return !( 538 (inplace == NULL || ispan) && 539 next_files_have_lines()); 540 } 541 if ((ch = getc(infile)) == EOF) { 542 return !( 543 (inplace == NULL || ispan) && 544 next_files_have_lines()); 545 } 546 ungetc(ch, infile); 547 return (0); 548} 549