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