1 /* 2 * Copyright (c) 2011 Gary Mills 3 * Copyright 2010 Nexenta Systems, Inc. All rights reserved. 4 * Copyright (c) 1992 Diomidis Spinellis. 5 * Copyright (c) 1992, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Diomidis Spinellis of Imperial College, University of London. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/types.h> 37 #include <sys/mman.h> 38 #include <sys/param.h> 39 #include <sys/stat.h> 40 41 #include <err.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <libgen.h> 45 #include <limits.h> 46 #include <locale.h> 47 #include <regex.h> 48 #include <stddef.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <unistd.h> 53 #include <libintl.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 static int rval; /* Exit status */ 93 94 static int ispan; /* Whether inplace editing spans across files */ 95 96 /* 97 * Current file and line number; line numbers restart across compilation 98 * units, but span across input files. The latter is optional if editing 99 * in place. 100 */ 101 const char *fname; /* File name. */ 102 const char *outfname; /* Output file name */ 103 static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ 104 static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ 105 static const char *inplace; /* Inplace edit file extension. */ 106 ulong_t linenum; 107 108 static void add_compunit(enum e_cut, char *); 109 static void add_file(char *); 110 static void usage(void); 111 static char *getln(FILE *, size_t *); 112 113 114 int 115 main(int argc, char *argv[]) 116 { 117 int c, fflag; 118 char *temp_arg; 119 120 (void) setlocale(LC_ALL, ""); 121 122 #ifndef TEXT_DOMAIN 123 #define TEXT_DOMAIN "SYS_TEST" 124 #endif 125 (void) textdomain(TEXT_DOMAIN); 126 127 fflag = 0; 128 inplace = NULL; 129 130 while ((c = getopt(argc, argv, "EI:ae:f:i:lnr")) != -1) 131 switch (c) { 132 case 'r': /* Gnu sed compat */ 133 case 'E': 134 rflags = REG_EXTENDED; 135 break; 136 case 'I': 137 inplace = optarg; 138 ispan = 1; /* span across input files */ 139 break; 140 case 'a': 141 aflag = 1; 142 break; 143 case 'e': 144 eflag = 1; 145 if (asprintf(&temp_arg, "%s\n", optarg) < 1) 146 err(1, "asprintf"); 147 add_compunit(CU_STRING, temp_arg); 148 break; 149 case 'f': 150 fflag = 1; 151 add_compunit(CU_FILE, optarg); 152 break; 153 case 'i': 154 inplace = optarg; 155 ispan = 0; /* don't span across input files */ 156 break; 157 case 'l': 158 /* On SunOS, setlinebuf "returns no useful value */ 159 (void) setlinebuf(stdout); 160 break; 161 case 'n': 162 nflag = 1; 163 break; 164 default: 165 case '?': 166 usage(); 167 } 168 argc -= optind; 169 argv += optind; 170 171 /* First usage case; script is the first arg */ 172 if (!eflag && !fflag && *argv) { 173 add_compunit(CU_STRING, *argv); 174 argv++; 175 } 176 177 compile(); 178 179 /* Continue with first and start second usage */ 180 if (*argv) 181 for (; *argv; argv++) 182 add_file(*argv); 183 else 184 add_file(NULL); 185 process(); 186 cfclose(prog, NULL); 187 if (fclose(stdout)) 188 err(1, "stdout"); 189 return (rval); 190 } 191 192 static void 193 usage(void) 194 { 195 (void) fputs(_("usage: sed script [-Ealn] [-i extension] [file...]\n" 196 " sed [-Ealn] [-i extension] [-e script]... " 197 "[-f script_file]... [file...]\n"), 198 stderr); 199 exit(1); 200 } 201 202 /* 203 * Like fgets, but go through the chain of compilation units chaining them 204 * together. Empty strings and files are ignored. 205 */ 206 char * 207 cu_fgets(char *buf, int n, int *more) 208 { 209 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 210 static FILE *f; /* Current open file */ 211 static char *s; /* Current pointer inside string */ 212 static char string_ident[30]; 213 char *p; 214 215 again: 216 switch (state) { 217 case ST_EOF: 218 if (script == NULL) { 219 if (more != NULL) 220 *more = 0; 221 return (NULL); 222 } 223 linenum = 0; 224 switch (script->type) { 225 case CU_FILE: 226 if ((f = fopen(script->s, "r")) == NULL) 227 err(1, "%s", script->s); 228 fname = script->s; 229 state = ST_FILE; 230 goto again; 231 case CU_STRING: 232 if (((size_t)snprintf(string_ident, 233 sizeof (string_ident), "\"%s\"", script->s)) >= 234 sizeof (string_ident) - 1) 235 (void) strcpy(string_ident + 236 sizeof (string_ident) - 6, " ...\""); 237 fname = string_ident; 238 s = script->s; 239 state = ST_STRING; 240 goto again; 241 } 242 /*NOTREACHED*/ 243 244 case ST_FILE: 245 if ((p = fgets(buf, n, f)) != NULL) { 246 linenum++; 247 if (linenum == 1 && buf[0] == '#' && buf[1] == 'n') 248 nflag = 1; 249 if (more != NULL) 250 *more = !feof(f); 251 return (p); 252 } 253 script = script->next; 254 (void) fclose(f); 255 state = ST_EOF; 256 goto again; 257 case ST_STRING: 258 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 259 nflag = 1; 260 p = buf; 261 for (;;) { 262 if (n-- <= 1) { 263 *p = '\0'; 264 linenum++; 265 if (more != NULL) 266 *more = 1; 267 return (buf); 268 } 269 switch (*s) { 270 case '\0': 271 state = ST_EOF; 272 if (s == script->s) { 273 script = script->next; 274 goto again; 275 } else { 276 script = script->next; 277 *p = '\0'; 278 linenum++; 279 if (more != NULL) 280 *more = 0; 281 return (buf); 282 } 283 case '\n': 284 *p++ = '\n'; 285 *p = '\0'; 286 s++; 287 linenum++; 288 if (more != NULL) 289 *more = 0; 290 return (buf); 291 default: 292 *p++ = *s++; 293 } 294 } 295 } 296 /* NOTREACHED */ 297 return (NULL); 298 } 299 300 /* 301 * Like fgets, but go through the list of files chaining them together. 302 * Set len to the length of the line. 303 */ 304 int 305 mf_fgets(SPACE *sp, enum e_spflag spflag) 306 { 307 struct stat sb, nsb; 308 size_t len; 309 char *p; 310 int c; 311 static int firstfile; 312 313 if (infile == NULL) { 314 /* stdin? */ 315 if (files->fname == NULL) { 316 if (inplace != NULL) 317 errx(1, 318 _("-I or -i may not be used with stdin")); 319 infile = stdin; 320 fname = "stdin"; 321 outfile = stdout; 322 outfname = "stdout"; 323 } 324 firstfile = 1; 325 } 326 327 for (;;) { 328 if (infile != NULL && (c = getc(infile)) != EOF) { 329 (void) ungetc(c, infile); 330 break; 331 } 332 /* If we are here then either eof or no files are open yet */ 333 if (infile == stdin) { 334 sp->len = 0; 335 return (0); 336 } 337 if (infile != NULL) { 338 (void) fclose(infile); 339 if (*oldfname != '\0') { 340 /* if there was a backup file, remove it */ 341 (void) unlink(oldfname); 342 /* 343 * Backup the original. Note that hard links 344 * are not supported on all filesystems. 345 */ 346 if ((link(fname, oldfname) != 0) && 347 (rename(fname, oldfname) != 0)) { 348 warn("rename()"); 349 if (*tmpfname) 350 (void) unlink(tmpfname); 351 exit(1); 352 } 353 *oldfname = '\0'; 354 } 355 if (*tmpfname != '\0') { 356 if (outfile != NULL && outfile != stdout) 357 if (fclose(outfile) != 0) { 358 warn("fclose()"); 359 (void) unlink(tmpfname); 360 exit(1); 361 } 362 outfile = NULL; 363 if (rename(tmpfname, fname) != 0) { 364 /* this should not happen really! */ 365 warn("rename()"); 366 (void) unlink(tmpfname); 367 exit(1); 368 } 369 *tmpfname = '\0'; 370 } 371 outfname = NULL; 372 } 373 if (firstfile == 0) 374 files = files->next; 375 else 376 firstfile = 0; 377 if (files == NULL) { 378 sp->len = 0; 379 return (0); 380 } 381 fname = files->fname; 382 if (inplace != NULL) { 383 char bn[PATH_MAX]; 384 char dn[PATH_MAX]; 385 (void) strlcpy(bn, fname, sizeof (bn)); 386 (void) strlcpy(dn, fname, sizeof (dn)); 387 if (lstat(fname, &sb) != 0) 388 err(1, "%s", fname); 389 if (!(sb.st_mode & S_IFREG)) 390 fatal(_("in-place editing only " 391 "works for regular files")); 392 if (*inplace != '\0') { 393 (void) strlcpy(oldfname, fname, 394 sizeof (oldfname)); 395 len = strlcat(oldfname, inplace, 396 sizeof (oldfname)); 397 if (len > sizeof (oldfname)) 398 fatal(_("name too long")); 399 } 400 len = snprintf(tmpfname, sizeof (tmpfname), 401 "%s/.!%ld!%s", dirname(dn), (long)getpid(), 402 basename(bn)); 403 if (len >= sizeof (tmpfname)) 404 fatal(_("name too long")); 405 (void) unlink(tmpfname); 406 if ((outfile = fopen(tmpfname, "w")) == NULL) 407 err(1, "%s", fname); 408 /* 409 * Some file systems don't support chown or 410 * chmod fully. On those, the owner/group and 411 * permissions will already be set to what 412 * they need to be. 413 */ 414 if (fstat(fileno(outfile), &nsb) != 0) { 415 warn("fstat()"); 416 } 417 if (((sb.st_uid != nsb.st_uid) || 418 (sb.st_gid != nsb.st_gid)) && 419 (fchown(fileno(outfile), sb.st_uid, sb.st_gid) 420 != 0)) 421 warn("fchown()"); 422 if ((sb.st_mode != nsb.st_mode) && 423 (fchmod(fileno(outfile), sb.st_mode & 07777) != 0)) 424 warn("fchmod()"); 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 fgetln so that we can handle essentially infinite input data. 445 * Can't use the pointer into the stdio buffer as the process space 446 * because the ungetc() can cause it to move. 447 */ 448 p = getln(infile, &len); 449 if (ferror(infile)) 450 errx(1, "%s: %s", fname, strerror(errno ? errno : EIO)); 451 if (len != 0 && p[len - 1] == '\n') 452 len--; 453 cspace(sp, p, len, spflag); 454 455 linenum++; 456 457 return (1); 458 } 459 460 /* 461 * Add a compilation unit to the linked list 462 */ 463 static void 464 add_compunit(enum e_cut type, char *s) 465 { 466 struct s_compunit *cu; 467 468 if ((cu = malloc(sizeof (struct s_compunit))) == NULL) 469 err(1, "malloc"); 470 cu->type = type; 471 cu->s = s; 472 cu->next = NULL; 473 *cu_nextp = cu; 474 cu_nextp = &cu->next; 475 } 476 477 /* 478 * Add a file to the linked list 479 */ 480 static void 481 add_file(char *s) 482 { 483 struct s_flist *fp; 484 485 if ((fp = malloc(sizeof (struct s_flist))) == NULL) 486 err(1, "malloc"); 487 fp->next = NULL; 488 *fl_nextp = fp; 489 fp->fname = s; 490 fl_nextp = &fp->next; 491 } 492 493 int 494 lastline(void) 495 { 496 int ch; 497 498 if (files->next != NULL && (inplace == NULL || ispan)) 499 return (0); 500 if ((ch = getc(infile)) == EOF) 501 return (1); 502 (void) ungetc(ch, infile); 503 return (0); 504 } 505 506 char * 507 getln(FILE *in, size_t *lenp) 508 { 509 static char *buffer = NULL; 510 static size_t sz = 0; 511 512 size_t len = 0; 513 514 for (;;) { 515 if (sz <= (len + 1)) { 516 char *nb; 517 if ((nb = realloc(buffer, sz + LINE_MAX)) == NULL) { 518 err(1, "realloc"); 519 } 520 buffer = nb; 521 sz += LINE_MAX; 522 } 523 524 buffer[len] = 0; 525 526 if (fgets(buffer + len, sz - len, in) == NULL) { 527 /* END OF FILE */ 528 *lenp = len; 529 break; 530 } 531 532 len += strlen(buffer + len); 533 534 if (buffer[len - 1] == '\n') { 535 /* got the new line */ 536 *lenp = len; 537 break; 538 } 539 } 540 541 return (buffer); 542 } 543