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