1 /* $OpenBSD: main.c,v 1.36 2017/12/13 16:06:34 millert Exp $ */ 2 3 /*- 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 * 3. 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/ioctl.h> 38 #include <sys/stat.h> 39 40 #include <ctype.h> 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <limits.h> 44 #include <regex.h> 45 #include <stddef.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <libgen.h> 51 52 #include "defs.h" 53 #include "extern.h" 54 55 /* 56 * Linked list of units (strings and files) to be compiled 57 */ 58 struct s_compunit { 59 struct s_compunit *next; 60 enum e_cut {CU_FILE, CU_STRING} type; 61 char *s; /* Pointer to string or fname */ 62 }; 63 64 /* 65 * Linked list pointer to compilation units and pointer to current 66 * next pointer. 67 */ 68 static struct s_compunit *script, **cu_nextp = &script; 69 70 /* 71 * Linked list of files to be processed 72 */ 73 struct s_flist { 74 char *fname; 75 struct s_flist *next; 76 }; 77 78 /* 79 * Linked list pointer to files and pointer to current 80 * next pointer. 81 */ 82 static struct s_flist *files, **fl_nextp = &files; 83 84 FILE *infile; /* Current input file */ 85 FILE *outfile; /* Current output file */ 86 87 int Eflag, aflag, eflag, nflag; 88 static int rval; /* Exit status */ 89 90 /* 91 * Current file and line number; line numbers restart across compilation 92 * units, but span across input files. The latter is optional if editing 93 * in place. 94 */ 95 const char *fname; /* File name. */ 96 const char *outfname; /* Output file name */ 97 static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ 98 static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ 99 char *inplace; /* Inplace edit file extension */ 100 u_long linenum; 101 102 static void add_compunit(enum e_cut, char *); 103 static void add_file(char *); 104 static int next_files_have_lines(void); 105 106 int termwidth; 107 108 int pledge_wpath, pledge_rpath; 109 110 int 111 main(int argc, char *argv[]) 112 { 113 struct winsize win; 114 int c, fflag; 115 char *p; 116 117 fflag = 0; 118 inplace = NULL; 119 while ((c = getopt(argc, argv, "Eae:f:i::nru")) != -1) 120 switch (c) { 121 case 'E': 122 case 'r': 123 Eflag = 1; 124 break; 125 case 'a': 126 aflag = 1; 127 break; 128 case 'e': 129 eflag = 1; 130 add_compunit(CU_STRING, optarg); 131 break; 132 case 'f': 133 fflag = 1; 134 add_compunit(CU_FILE, optarg); 135 break; 136 case 'i': 137 inplace = optarg ? optarg : ""; 138 break; 139 case 'n': 140 nflag = 1; 141 break; 142 case 'u': 143 setvbuf(stdout, NULL, _IOLBF, 0); 144 break; 145 default: 146 case '?': 147 (void)fprintf(stderr, 148 "usage: sed [-aEnru] [-i[extension]] command [file ...]\n" 149 " sed [-aEnru] [-e command] [-f command_file] [-i[extension]] [file ...]\n"); 150 exit(1); 151 } 152 argc -= optind; 153 argv += optind; 154 155 termwidth = 0; 156 if ((p = getenv("COLUMNS")) != NULL) 157 termwidth = strtonum(p, 0, INT_MAX, NULL); 158 if (termwidth == 0 && ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && 159 win.ws_col > 0) 160 termwidth = win.ws_col; 161 if (termwidth == 0) 162 termwidth = 80; 163 164 if (inplace != NULL) { 165 if (pledge("stdio rpath wpath cpath fattr chown", NULL) == -1) 166 error(FATAL, "pledge: %s", strerror(errno)); 167 } else { 168 if (pledge("stdio rpath wpath cpath", NULL) == -1) 169 error(FATAL, "pledge: %s", strerror(errno)); 170 } 171 172 /* First usage case; script is the first arg */ 173 if (!eflag && !fflag && *argv) { 174 add_compunit(CU_STRING, *argv); 175 argv++; 176 } 177 178 compile(); 179 180 /* Continue with first and start second usage */ 181 if (*argv) { 182 if (!pledge_wpath && inplace == NULL) { 183 if (pledge("stdio rpath", NULL) == -1) 184 error(FATAL, "pledge: %s", strerror(errno)); 185 } 186 for (; *argv; argv++) 187 add_file(*argv); 188 } else { 189 if (!pledge_wpath && !pledge_rpath) { 190 if (pledge("stdio", NULL) == -1) 191 error(FATAL, "pledge: %s", strerror(errno)); 192 } else if (pledge_rpath) { 193 if (pledge("stdio rpath", NULL) == -1) 194 error(FATAL, "pledge: %s", strerror(errno)); 195 } else if (pledge_wpath) { 196 if (pledge("stdio wpath cpath", NULL) == -1) 197 error(FATAL, "pledge: %s", strerror(errno)); 198 } 199 add_file(NULL); 200 } 201 process(); 202 cfclose(prog, NULL); 203 if (fclose(stdout)) 204 error(FATAL, "stdout: %s", strerror(errno)); 205 exit (rval); 206 } 207 208 /* 209 * Like fgets, but go through the chain of compilation units chaining them 210 * together. Empty strings and files are ignored. 211 */ 212 char * 213 cu_fgets(char **outbuf, size_t *outsize) 214 { 215 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 216 static FILE *f; /* Current open file */ 217 static char *s; /* Current pointer inside string */ 218 static char string_ident[30]; 219 size_t len; 220 char *p; 221 222 if (*outbuf == NULL) 223 *outsize = 0; 224 225 again: 226 switch (state) { 227 case ST_EOF: 228 if (script == NULL) 229 return (NULL); 230 linenum = 0; 231 switch (script->type) { 232 case CU_FILE: 233 if ((f = fopen(script->s, "r")) == NULL) 234 error(FATAL, 235 "%s: %s", script->s, strerror(errno)); 236 fname = script->s; 237 state = ST_FILE; 238 goto again; 239 case CU_STRING: 240 len = snprintf(string_ident, sizeof(string_ident), 241 "\"%s\"", script->s); 242 if (len >= sizeof(string_ident)) 243 strlcpy(string_ident + 244 sizeof(string_ident) - 6, " ...\"", 5); 245 fname = string_ident; 246 s = script->s; 247 state = ST_STRING; 248 goto again; 249 } 250 case ST_FILE: 251 if ((p = fgetln(f, &len)) != NULL) { 252 linenum++; 253 if (len >= *outsize) { 254 free(*outbuf); 255 *outsize = ROUNDLEN(len + 1); 256 *outbuf = xmalloc(*outsize); 257 } 258 memcpy(*outbuf, p, len); 259 (*outbuf)[len] = '\0'; 260 if (linenum == 1 && p[0] == '#' && p[1] == 'n') 261 nflag = 1; 262 return (*outbuf); 263 } 264 script = script->next; 265 (void)fclose(f); 266 state = ST_EOF; 267 goto again; 268 case ST_STRING: 269 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 270 nflag = 1; 271 p = *outbuf; 272 len = *outsize; 273 for (;;) { 274 if (len <= 1) { 275 *outbuf = xrealloc(*outbuf, 276 *outsize + _POSIX2_LINE_MAX); 277 p = *outbuf + *outsize - len; 278 len += _POSIX2_LINE_MAX; 279 *outsize += _POSIX2_LINE_MAX; 280 } 281 switch (*s) { 282 case '\0': 283 state = ST_EOF; 284 if (s == script->s) { 285 script = script->next; 286 goto again; 287 } else { 288 script = script->next; 289 *p = '\0'; 290 linenum++; 291 return (*outbuf); 292 } 293 case '\n': 294 *p++ = '\n'; 295 *p = '\0'; 296 s++; 297 linenum++; 298 return (*outbuf); 299 default: 300 *p++ = *s++; 301 len--; 302 } 303 } 304 } 305 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 size_t len; 318 char *p; 319 int c, fd; 320 static int firstfile; 321 322 if (infile == NULL) { 323 /* stdin? */ 324 if (files->fname == NULL) { 325 if (inplace != NULL) 326 error(FATAL, "-i may not be used with stdin"); 327 infile = stdin; 328 fname = "stdin"; 329 outfile = stdout; 330 outfname = "stdout"; 331 } 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 (rename(fname, oldfname) != 0) { 350 warning("rename()"); 351 unlink(tmpfname); 352 exit(1); 353 } 354 *oldfname = '\0'; 355 } 356 if (*tmpfname != '\0') { 357 if (outfile != NULL && outfile != stdout) 358 fclose(outfile); 359 outfile = NULL; 360 rename(tmpfname, fname); 361 *tmpfname = '\0'; 362 } 363 outfname = NULL; 364 } 365 if (firstfile == 0) 366 files = files->next; 367 else 368 firstfile = 0; 369 if (files == NULL) { 370 sp->len = 0; 371 return (0); 372 } 373 fname = files->fname; 374 if (inplace != NULL) { 375 if (lstat(fname, &sb) != 0) 376 error(FATAL, "%s: %s", fname, 377 strerror(errno ? errno : EIO)); 378 if (!S_ISREG(sb.st_mode)) 379 error(FATAL, "%s: %s %s", fname, 380 "in-place editing only", 381 "works for regular files"); 382 if (*inplace != '\0') { 383 strlcpy(oldfname, fname, 384 sizeof(oldfname)); 385 len = strlcat(oldfname, inplace, 386 sizeof(oldfname)); 387 if (len > sizeof(oldfname)) 388 error(FATAL, "%s: name too long", fname); 389 } 390 len = snprintf(tmpfname, sizeof(tmpfname), "%s/sedXXXXXXXXXX", 391 dirname(fname)); 392 if (len >= sizeof(tmpfname)) 393 error(FATAL, "%s: name too long", fname); 394 if ((fd = mkstemp(tmpfname)) == -1) 395 error(FATAL, "%s: %s", fname, strerror(errno)); 396 if ((outfile = fdopen(fd, "w")) == NULL) { 397 unlink(tmpfname); 398 error(FATAL, "%s", fname); 399 } 400 fchown(fileno(outfile), sb.st_uid, sb.st_gid); 401 fchmod(fileno(outfile), sb.st_mode & ALLPERMS); 402 outfname = tmpfname; 403 linenum = 0; 404 resetranges(); 405 } else { 406 outfile = stdout; 407 outfname = "stdout"; 408 } 409 if ((infile = fopen(fname, "r")) == NULL) { 410 warning("%s", strerror(errno)); 411 rval = 1; 412 continue; 413 } 414 } 415 416 /* 417 * We are here only when infile is open and we still have something 418 * to read from it. 419 * 420 * Use fgetln so that we can handle essentially infinite input data. 421 * Can't use the pointer into the stdio buffer as the process space 422 * because the ungetc() can cause it to move. 423 */ 424 p = fgetln(infile, &len); 425 if (ferror(infile)) 426 error(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO)); 427 if (len != 0 && p[len - 1] == '\n') { 428 sp->append_newline = 1; 429 len--; 430 } else if (!lastline()) { 431 sp->append_newline = 1; 432 } else { 433 sp->append_newline = 0; 434 } 435 cspace(sp, p, len, spflag); 436 437 linenum++; 438 439 return (1); 440 } 441 442 /* 443 * Add a compilation unit to the linked list 444 */ 445 static void 446 add_compunit(enum e_cut type, char *s) 447 { 448 struct s_compunit *cu; 449 450 cu = xmalloc(sizeof(struct s_compunit)); 451 cu->type = type; 452 cu->s = s; 453 cu->next = NULL; 454 *cu_nextp = cu; 455 cu_nextp = &cu->next; 456 } 457 458 /* 459 * Add a file to the linked list 460 */ 461 static void 462 add_file(char *s) 463 { 464 struct s_flist *fp; 465 466 fp = xmalloc(sizeof(struct s_flist)); 467 fp->next = NULL; 468 *fl_nextp = fp; 469 fp->fname = s; 470 fl_nextp = &fp->next; 471 } 472 473 474 static int 475 next_files_have_lines() 476 { 477 struct s_flist *file; 478 FILE *file_fd; 479 int ch; 480 481 file = files; 482 while ((file = file->next) != NULL) { 483 if ((file_fd = fopen(file->fname, "r")) == NULL) 484 continue; 485 486 if ((ch = getc(file_fd)) != EOF) { 487 /* 488 * This next file has content, therefore current 489 * file doesn't contains the last line. 490 */ 491 ungetc(ch, file_fd); 492 fclose(file_fd); 493 return (1); 494 } 495 fclose(file_fd); 496 } 497 return (0); 498 } 499 500 int 501 lastline(void) 502 { 503 int ch; 504 505 if (feof(infile)) { 506 return !( 507 (inplace == NULL) && 508 next_files_have_lines()); 509 } 510 if ((ch = getc(infile)) == EOF) { 511 return !( 512 (inplace == NULL) && 513 next_files_have_lines()); 514 } 515 ungetc(ch, infile); 516 return (0); 517 } 518