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