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