1 /* $OpenBSD: main.c,v 1.37 2018/07/11 06:57:18 martijn 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 if (termwidth <= 8) 164 termwidth = 1; 165 else 166 termwidth -= 8; 167 168 if (inplace != NULL) { 169 if (pledge("stdio rpath wpath cpath fattr chown", NULL) == -1) 170 error(FATAL, "pledge: %s", strerror(errno)); 171 } else { 172 if (pledge("stdio rpath wpath cpath", NULL) == -1) 173 error(FATAL, "pledge: %s", strerror(errno)); 174 } 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 if (!pledge_wpath && inplace == NULL) { 187 if (pledge("stdio rpath", NULL) == -1) 188 error(FATAL, "pledge: %s", strerror(errno)); 189 } 190 for (; *argv; argv++) 191 add_file(*argv); 192 } else { 193 if (!pledge_wpath && !pledge_rpath) { 194 if (pledge("stdio", NULL) == -1) 195 error(FATAL, "pledge: %s", strerror(errno)); 196 } else if (pledge_rpath) { 197 if (pledge("stdio rpath", NULL) == -1) 198 error(FATAL, "pledge: %s", strerror(errno)); 199 } else if (pledge_wpath) { 200 if (pledge("stdio wpath cpath", NULL) == -1) 201 error(FATAL, "pledge: %s", strerror(errno)); 202 } 203 add_file(NULL); 204 } 205 process(); 206 cfclose(prog, NULL); 207 if (fclose(stdout)) 208 error(FATAL, "stdout: %s", strerror(errno)); 209 exit (rval); 210 } 211 212 /* 213 * Like fgets, but go through the chain of compilation units chaining them 214 * together. Empty strings and files are ignored. 215 */ 216 char * 217 cu_fgets(char **outbuf, size_t *outsize) 218 { 219 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 220 static FILE *f; /* Current open file */ 221 static char *s; /* Current pointer inside string */ 222 static char string_ident[30]; 223 size_t len; 224 char *p; 225 226 if (*outbuf == NULL) 227 *outsize = 0; 228 229 again: 230 switch (state) { 231 case ST_EOF: 232 if (script == NULL) 233 return (NULL); 234 linenum = 0; 235 switch (script->type) { 236 case CU_FILE: 237 if ((f = fopen(script->s, "r")) == NULL) 238 error(FATAL, 239 "%s: %s", script->s, strerror(errno)); 240 fname = script->s; 241 state = ST_FILE; 242 goto again; 243 case CU_STRING: 244 len = snprintf(string_ident, sizeof(string_ident), 245 "\"%s\"", script->s); 246 if (len >= sizeof(string_ident)) 247 strlcpy(string_ident + 248 sizeof(string_ident) - 6, " ...\"", 5); 249 fname = string_ident; 250 s = script->s; 251 state = ST_STRING; 252 goto again; 253 } 254 case ST_FILE: 255 if ((p = fgetln(f, &len)) != NULL) { 256 linenum++; 257 if (len >= *outsize) { 258 free(*outbuf); 259 *outsize = ROUNDLEN(len + 1); 260 *outbuf = xmalloc(*outsize); 261 } 262 memcpy(*outbuf, p, len); 263 (*outbuf)[len] = '\0'; 264 if (linenum == 1 && p[0] == '#' && p[1] == 'n') 265 nflag = 1; 266 return (*outbuf); 267 } 268 script = script->next; 269 (void)fclose(f); 270 state = ST_EOF; 271 goto again; 272 case ST_STRING: 273 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 274 nflag = 1; 275 p = *outbuf; 276 len = *outsize; 277 for (;;) { 278 if (len <= 1) { 279 *outbuf = xrealloc(*outbuf, 280 *outsize + _POSIX2_LINE_MAX); 281 p = *outbuf + *outsize - len; 282 len += _POSIX2_LINE_MAX; 283 *outsize += _POSIX2_LINE_MAX; 284 } 285 switch (*s) { 286 case '\0': 287 state = ST_EOF; 288 if (s == script->s) { 289 script = script->next; 290 goto again; 291 } else { 292 script = script->next; 293 *p = '\0'; 294 linenum++; 295 return (*outbuf); 296 } 297 case '\n': 298 *p++ = '\n'; 299 *p = '\0'; 300 s++; 301 linenum++; 302 return (*outbuf); 303 default: 304 *p++ = *s++; 305 len--; 306 } 307 } 308 } 309 310 return (NULL); 311 } 312 313 /* 314 * Like fgets, but go through the list of files chaining them together. 315 * Set len to the length of the line. 316 */ 317 int 318 mf_fgets(SPACE *sp, enum e_spflag spflag) 319 { 320 struct stat sb; 321 size_t len; 322 char *p; 323 int c, fd; 324 static int firstfile; 325 326 if (infile == NULL) { 327 /* stdin? */ 328 if (files->fname == NULL) { 329 if (inplace != NULL) 330 error(FATAL, "-i may not be used with stdin"); 331 infile = stdin; 332 fname = "stdin"; 333 outfile = stdout; 334 outfname = "stdout"; 335 } 336 337 firstfile = 1; 338 } 339 340 for (;;) { 341 if (infile != NULL && (c = getc(infile)) != EOF) { 342 (void)ungetc(c, infile); 343 break; 344 } 345 /* If we are here then either eof or no files are open yet */ 346 if (infile == stdin) { 347 sp->len = 0; 348 return (0); 349 } 350 if (infile != NULL) { 351 fclose(infile); 352 if (*oldfname != '\0') { 353 if (rename(fname, oldfname) != 0) { 354 warning("rename()"); 355 unlink(tmpfname); 356 exit(1); 357 } 358 *oldfname = '\0'; 359 } 360 if (*tmpfname != '\0') { 361 if (outfile != NULL && outfile != stdout) 362 fclose(outfile); 363 outfile = NULL; 364 rename(tmpfname, fname); 365 *tmpfname = '\0'; 366 } 367 outfname = NULL; 368 } 369 if (firstfile == 0) 370 files = files->next; 371 else 372 firstfile = 0; 373 if (files == NULL) { 374 sp->len = 0; 375 return (0); 376 } 377 fname = files->fname; 378 if (inplace != NULL) { 379 if (lstat(fname, &sb) != 0) 380 error(FATAL, "%s: %s", fname, 381 strerror(errno ? errno : EIO)); 382 if (!S_ISREG(sb.st_mode)) 383 error(FATAL, "%s: %s %s", fname, 384 "in-place editing only", 385 "works for regular files"); 386 if (*inplace != '\0') { 387 strlcpy(oldfname, fname, 388 sizeof(oldfname)); 389 len = strlcat(oldfname, inplace, 390 sizeof(oldfname)); 391 if (len > sizeof(oldfname)) 392 error(FATAL, "%s: name too long", fname); 393 } 394 len = snprintf(tmpfname, sizeof(tmpfname), "%s/sedXXXXXXXXXX", 395 dirname(fname)); 396 if (len >= sizeof(tmpfname)) 397 error(FATAL, "%s: name too long", fname); 398 if ((fd = mkstemp(tmpfname)) == -1) 399 error(FATAL, "%s: %s", fname, strerror(errno)); 400 if ((outfile = fdopen(fd, "w")) == NULL) { 401 unlink(tmpfname); 402 error(FATAL, "%s", fname); 403 } 404 fchown(fileno(outfile), sb.st_uid, sb.st_gid); 405 fchmod(fileno(outfile), sb.st_mode & ALLPERMS); 406 outfname = tmpfname; 407 linenum = 0; 408 resetranges(); 409 } else { 410 outfile = stdout; 411 outfname = "stdout"; 412 } 413 if ((infile = fopen(fname, "r")) == NULL) { 414 warning("%s", strerror(errno)); 415 rval = 1; 416 continue; 417 } 418 } 419 420 /* 421 * We are here only when infile is open and we still have something 422 * to read from it. 423 * 424 * Use fgetln so that we can handle essentially infinite input data. 425 * Can't use the pointer into the stdio buffer as the process space 426 * because the ungetc() can cause it to move. 427 */ 428 p = fgetln(infile, &len); 429 if (ferror(infile)) 430 error(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO)); 431 if (len != 0 && p[len - 1] == '\n') { 432 sp->append_newline = 1; 433 len--; 434 } else if (!lastline()) { 435 sp->append_newline = 1; 436 } else { 437 sp->append_newline = 0; 438 } 439 cspace(sp, p, len, spflag); 440 441 linenum++; 442 443 return (1); 444 } 445 446 /* 447 * Add a compilation unit to the linked list 448 */ 449 static void 450 add_compunit(enum e_cut type, char *s) 451 { 452 struct s_compunit *cu; 453 454 cu = xmalloc(sizeof(struct s_compunit)); 455 cu->type = type; 456 cu->s = s; 457 cu->next = NULL; 458 *cu_nextp = cu; 459 cu_nextp = &cu->next; 460 } 461 462 /* 463 * Add a file to the linked list 464 */ 465 static void 466 add_file(char *s) 467 { 468 struct s_flist *fp; 469 470 fp = xmalloc(sizeof(struct s_flist)); 471 fp->next = NULL; 472 *fl_nextp = fp; 473 fp->fname = s; 474 fl_nextp = &fp->next; 475 } 476 477 478 static int 479 next_files_have_lines() 480 { 481 struct s_flist *file; 482 FILE *file_fd; 483 int ch; 484 485 file = files; 486 while ((file = file->next) != NULL) { 487 if ((file_fd = fopen(file->fname, "r")) == NULL) 488 continue; 489 490 if ((ch = getc(file_fd)) != EOF) { 491 /* 492 * This next file has content, therefore current 493 * file doesn't contains the last line. 494 */ 495 ungetc(ch, file_fd); 496 fclose(file_fd); 497 return (1); 498 } 499 fclose(file_fd); 500 } 501 return (0); 502 } 503 504 int 505 lastline(void) 506 { 507 int ch; 508 509 if (feof(infile)) { 510 return !( 511 (inplace == NULL) && 512 next_files_have_lines()); 513 } 514 if ((ch = getc(infile)) == EOF) { 515 return !( 516 (inplace == NULL) && 517 next_files_have_lines()); 518 } 519 ungetc(ch, infile); 520 return (0); 521 } 522