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