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