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 } 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) { 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