1 /*- 2 * Copyright (c) 2013 Johann 'Myrkraverk' Oskarsson. 3 * Copyright (c) 1992 Diomidis Spinellis. 4 * Copyright (c) 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Diomidis Spinellis of Imperial College, University of London. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 4. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #ifndef lint 39 static const char copyright[] = 40 "@(#) Copyright (c) 1992, 1993\n\ 41 The Regents of the University of California. All rights reserved.\n"; 42 #endif 43 44 #ifndef lint 45 static const char sccsid[] = "@(#)main.c 8.2 (Berkeley) 1/3/94"; 46 #endif 47 48 #include <sys/types.h> 49 #include <sys/mman.h> 50 #include <sys/param.h> 51 #include <sys/stat.h> 52 53 #include <err.h> 54 #include <errno.h> 55 #include <fcntl.h> 56 #include <libgen.h> 57 #include <limits.h> 58 #include <locale.h> 59 #include <regex.h> 60 #include <stddef.h> 61 #include <stdio.h> 62 #include <stdlib.h> 63 #include <string.h> 64 #include <unistd.h> 65 66 #include "defs.h" 67 #include "extern.h" 68 69 /* 70 * Linked list of units (strings and files) to be compiled 71 */ 72 struct s_compunit { 73 struct s_compunit *next; 74 enum e_cut {CU_FILE, CU_STRING} type; 75 const char *s; /* Pointer to string or fname */ 76 }; 77 78 /* 79 * Linked list pointer to compilation units and pointer to current 80 * next pointer. 81 */ 82 static struct s_compunit *script, **cu_nextp = &script; 83 84 /* 85 * Linked list of files to be processed 86 */ 87 struct s_flist { 88 const char *fname; 89 struct s_flist *next; 90 }; 91 92 /* 93 * Linked list pointer to files and pointer to current 94 * next pointer. 95 */ 96 static struct s_flist *files, **fl_nextp = &files; 97 98 FILE *infile; /* Current input file */ 99 FILE *outfile; /* Current output file */ 100 101 int aflag, eflag, nflag; 102 int rflags = 0; 103 static int rval; /* Exit status */ 104 105 static int ispan; /* Whether inplace editing spans across files */ 106 107 /* 108 * Current file and line number; line numbers restart across compilation 109 * units, but span across input files. The latter is optional if editing 110 * in place. 111 */ 112 const char *fname; /* File name. */ 113 const char *outfname; /* Output file name */ 114 static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ 115 static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ 116 static const char *inplace; /* Inplace edit file extension. */ 117 u_long linenum; 118 119 static void add_compunit(enum e_cut, const char *); 120 static void add_file(const char *); 121 static void usage(void); 122 123 int 124 main(int argc, char *argv[]) 125 { 126 int c, fflag; 127 128 (void) setlocale(LC_ALL, ""); 129 130 fflag = 0; 131 inplace = NULL; 132 133 while ((c = getopt(argc, argv, "EI:ae:f:i:lnru")) != -1) 134 switch (c) { 135 case 'r': /* Gnu sed compat */ 136 case 'E': 137 rflags = REG_EXTENDED; 138 break; 139 case 'I': 140 inplace = optarg; 141 ispan = 1; /* span across input files */ 142 break; 143 case 'a': 144 aflag = 1; 145 break; 146 case 'e': 147 eflag = 1; 148 add_compunit(CU_STRING, optarg); 149 break; 150 case 'f': 151 fflag = 1; 152 add_compunit(CU_FILE, optarg); 153 break; 154 case 'i': 155 inplace = optarg; 156 ispan = 0; /* don't span across input files */ 157 break; 158 case 'l': 159 if(setvbuf(stdout, NULL, _IOLBF, 0) != 0) 160 warnx("setting line buffered output failed"); 161 break; 162 case 'n': 163 nflag = 1; 164 break; 165 case 'u': 166 if(setvbuf(stdout, NULL, _IONBF, 0) != 0) 167 warnx("setting unbuffered output failed"); 168 break; 169 default: 170 case '?': 171 usage(); 172 } 173 argc -= optind; 174 argv += optind; 175 176 /* First usage case; script is the first arg */ 177 if (!eflag && !fflag && *argv) { 178 add_compunit(CU_STRING, *argv); 179 argv++; 180 } 181 182 compile(); 183 184 /* Continue with first and start second usage */ 185 if (*argv) 186 for (; *argv; argv++) 187 add_file(*argv); 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 const char * 212 cu_fgets(int *more) 213 { 214 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 215 static FILE *f; /* Current open file */ 216 static const char *s; /* Current pointer inside string */ 217 static char string_ident[30], *lastresult; 218 static size_t lastsize; 219 char *p; 220 const char *start; 221 222 again: 223 switch (state) { 224 case ST_EOF: 225 if (script == NULL) { 226 if (more != NULL) 227 *more = 0; 228 return (NULL); 229 } 230 linenum = 0; 231 switch (script->type) { 232 case CU_FILE: 233 if ((f = fopen(script->s, "r")) == NULL) 234 err(1, "%s", script->s); 235 fname = script->s; 236 state = ST_FILE; 237 goto again; 238 case CU_STRING: 239 if (((size_t)snprintf(string_ident, 240 sizeof(string_ident), "\"%s\"", script->s)) >= 241 sizeof(string_ident) - 1) 242 (void)strcpy(string_ident + 243 sizeof(string_ident) - 6, " ...\""); 244 fname = string_ident; 245 s = script->s; 246 state = ST_STRING; 247 goto again; 248 } 249 case ST_FILE: 250 p = lastresult; 251 if (getline(&p, &lastsize, f) != -1) { 252 linenum++; 253 if (linenum == 1 && p[0] == '#' && p[1] == 'n') 254 nflag = 1; 255 if (more != NULL) 256 *more = !feof(f); 257 return (lastresult = p); 258 } else if (ferror(f)) 259 err(1, "%s", script->s); 260 script = script->next; 261 (void)fclose(f); 262 state = ST_EOF; 263 goto again; 264 case ST_STRING: 265 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 266 nflag = 1; 267 else if (s[0] == '\0') { 268 state = ST_EOF; 269 script = script->next; 270 goto again; 271 } 272 start = s; 273 for (;;) { 274 switch (*s) { 275 case '\0': 276 state = ST_EOF; 277 script = script->next; 278 /* FALLTHROUGH */ 279 case '\n': 280 s++; 281 linenum++; 282 if (more != NULL) 283 *more = 0; 284 return (start); 285 default: 286 s++; 287 } 288 } 289 } 290 /* NOTREACHED */ 291 return (NULL); 292 } 293 294 /* 295 * Like fgets, but go through the list of files chaining them together. 296 * Set len to the length of the line. 297 */ 298 int 299 mf_fgets(SPACE *sp, enum e_spflag spflag) 300 { 301 struct stat sb; 302 ssize_t len; 303 char *dirbuf, *basebuf; 304 static char *p = NULL; 305 static size_t plen = 0; 306 int c; 307 static int firstfile; 308 309 if (infile == NULL) { 310 /* stdin? */ 311 if (files->fname == NULL) { 312 if (inplace != NULL) 313 errx(1, "-I or -i may not be used with stdin"); 314 infile = stdin; 315 fname = "stdin"; 316 outfile = stdout; 317 outfname = "stdout"; 318 } 319 firstfile = 1; 320 } 321 322 for (;;) { 323 if (infile != NULL && (c = getc(infile)) != EOF) { 324 (void)ungetc(c, infile); 325 break; 326 } 327 /* If we are here then either eof or no files are open yet */ 328 if (infile == stdin) { 329 sp->len = 0; 330 return (0); 331 } 332 if (infile != NULL) { 333 fclose(infile); 334 if (*oldfname != '\0') { 335 /* if there was a backup file, remove it */ 336 unlink(oldfname); 337 /* 338 * Backup the original. Note that hard links 339 * are not supported on all filesystems. 340 */ 341 if ((link(fname, oldfname) != 0) && 342 (rename(fname, oldfname) != 0)) { 343 warn("rename()"); 344 if (*tmpfname) 345 unlink(tmpfname); 346 exit(1); 347 } 348 *oldfname = '\0'; 349 } 350 if (*tmpfname != '\0') { 351 if (outfile != NULL && outfile != stdout) 352 if (fclose(outfile) != 0) { 353 warn("fclose()"); 354 unlink(tmpfname); 355 exit(1); 356 } 357 outfile = NULL; 358 if (rename(tmpfname, fname) != 0) { 359 /* this should not happen really! */ 360 warn("rename()"); 361 unlink(tmpfname); 362 exit(1); 363 } 364 *tmpfname = '\0'; 365 } 366 outfname = NULL; 367 } 368 if (firstfile == 0) 369 files = files->next; 370 else 371 firstfile = 0; 372 if (files == NULL) { 373 sp->len = 0; 374 return (0); 375 } 376 fname = files->fname; 377 if (inplace != NULL) { 378 if (lstat(fname, &sb) != 0) 379 err(1, "%s", fname); 380 if (!(sb.st_mode & S_IFREG)) 381 errx(1, "%s: %s %s", fname, 382 "in-place editing only", 383 "works for regular files"); 384 if (*inplace != '\0') { 385 strlcpy(oldfname, fname, 386 sizeof(oldfname)); 387 len = strlcat(oldfname, inplace, 388 sizeof(oldfname)); 389 if ((size_t)len > sizeof(oldfname)) 390 errx(1, "%s: name too long", fname); 391 } 392 if ((dirbuf = strdup(fname)) == NULL || 393 (basebuf = strdup(fname)) == NULL) 394 err(1, "strdup"); 395 len = snprintf(tmpfname, sizeof(tmpfname), 396 "%s/.!%ld!%s", dirname(dirbuf), (long)getpid(), 397 basename(basebuf)); 398 free(dirbuf); 399 free(basebuf); 400 if ((size_t)len >= sizeof(tmpfname)) 401 errx(1, "%s: name too long", fname); 402 unlink(tmpfname); 403 if (outfile != NULL && outfile != stdout) 404 fclose(outfile); 405 if ((outfile = fopen(tmpfname, "w")) == NULL) 406 err(1, "%s", fname); 407 fchown(fileno(outfile), sb.st_uid, sb.st_gid); 408 fchmod(fileno(outfile), sb.st_mode & ALLPERMS); 409 outfname = tmpfname; 410 if (!ispan) { 411 linenum = 0; 412 resetstate(); 413 } 414 } else { 415 outfile = stdout; 416 outfname = "stdout"; 417 } 418 if ((infile = fopen(fname, "r")) == NULL) { 419 warn("%s", fname); 420 rval = 1; 421 continue; 422 } 423 } 424 /* 425 * We are here only when infile is open and we still have something 426 * to read from it. 427 * 428 * Use getline() so that we can handle essentially infinite input 429 * data. The p and plen are static so each invocation gives 430 * getline() the same buffer which is expanded as needed. 431 */ 432 len = getline(&p, &plen, infile); 433 if (len == -1) 434 err(1, "%s", fname); 435 if (len != 0 && p[len - 1] == '\n') { 436 sp->append_newline = 1; 437 len--; 438 } else if (!lastline()) { 439 sp->append_newline = 1; 440 } else { 441 sp->append_newline = 0; 442 } 443 cspace(sp, p, len, spflag); 444 445 linenum++; 446 447 return (1); 448 } 449 450 /* 451 * Add a compilation unit to the linked list 452 */ 453 static void 454 add_compunit(enum e_cut type, const char *s) 455 { 456 struct s_compunit *cu; 457 458 if ((cu = malloc(sizeof(struct s_compunit))) == NULL) 459 err(1, "malloc"); 460 cu->type = type; 461 cu->s = s; 462 cu->next = NULL; 463 *cu_nextp = cu; 464 cu_nextp = &cu->next; 465 } 466 467 /* 468 * Add a file to the linked list 469 */ 470 static void 471 add_file(const char *s) 472 { 473 struct s_flist *fp; 474 475 if ((fp = malloc(sizeof(struct s_flist))) == NULL) 476 err(1, "malloc"); 477 fp->next = NULL; 478 *fl_nextp = fp; 479 fp->fname = s; 480 fl_nextp = &fp->next; 481 } 482 483 static int 484 next_files_have_lines(void) 485 { 486 struct s_flist *file; 487 FILE *file_fd; 488 int ch; 489 490 file = files; 491 while ((file = file->next) != NULL) { 492 if ((file_fd = fopen(file->fname, "r")) == NULL) 493 continue; 494 495 if ((ch = getc(file_fd)) != EOF) { 496 /* 497 * This next file has content, therefore current 498 * file doesn't contains the last line. 499 */ 500 ungetc(ch, file_fd); 501 fclose(file_fd); 502 return (1); 503 } 504 505 fclose(file_fd); 506 } 507 508 return (0); 509 } 510 511 int 512 lastline(void) 513 { 514 int ch; 515 516 if (feof(infile)) { 517 return !( 518 (inplace == NULL || ispan) && 519 next_files_have_lines()); 520 } 521 if ((ch = getc(infile)) == EOF) { 522 return !( 523 (inplace == NULL || ispan) && 524 next_files_have_lines()); 525 } 526 ungetc(ch, infile); 527 return (0); 528 } 529