1 /* $OpenBSD: main.c,v 1.47 2024/07/17 20:57:16 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 <err.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <limits.h> 45 #include <regex.h> 46 #include <stddef.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <unistd.h> 51 #include <libgen.h> 52 53 #include "defs.h" 54 #include "extern.h" 55 56 /* 57 * Linked list of units (strings and files) to be compiled 58 */ 59 struct s_compunit { 60 struct s_compunit *next; 61 enum e_cut {CU_FILE, CU_STRING} type; 62 char *s; /* Pointer to string or fname */ 63 }; 64 65 /* 66 * Linked list pointer to compilation units and pointer to current 67 * next pointer. 68 */ 69 static struct s_compunit *script, **cu_nextp = &script; 70 71 /* 72 * Linked list of files to be processed 73 */ 74 struct s_flist { 75 char *fname; 76 struct s_flist *next; 77 }; 78 79 /* 80 * Linked list pointer to files and pointer to current 81 * next pointer. 82 */ 83 static struct s_flist *files, **fl_nextp = &files; 84 85 FILE *infile; /* Current input file */ 86 FILE *outfile; /* Current output file */ 87 88 int Eflag, aflag, eflag, nflag; 89 static int rval; /* Exit status */ 90 91 /* 92 * Current file and line number; line numbers restart across compilation 93 * units, but span across input files. The latter is optional if editing 94 * in place. 95 */ 96 const char *fname; /* File name. */ 97 const char *outfname; /* Output file name */ 98 static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ 99 static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ 100 char *inplace; /* Inplace edit file extension */ 101 u_long linenum; 102 103 static void add_compunit(enum e_cut, char *); 104 static void add_file(char *); 105 static int next_files_have_lines(void); 106 107 int termwidth; 108 109 int pledge_wpath, pledge_rpath; 110 111 int 112 main(int argc, char *argv[]) 113 { 114 struct winsize win; 115 int c, fflag; 116 char *p; 117 118 fflag = 0; 119 inplace = NULL; 120 while ((c = getopt(argc, argv, "Eae:f:i::nru")) != -1) 121 switch (c) { 122 case 'E': 123 case 'r': 124 Eflag = 1; 125 break; 126 case 'a': 127 aflag = 1; 128 break; 129 case 'e': 130 eflag = 1; 131 add_compunit(CU_STRING, optarg); 132 break; 133 case 'f': 134 fflag = 1; 135 add_compunit(CU_FILE, optarg); 136 break; 137 case 'i': 138 inplace = optarg ? optarg : ""; 139 break; 140 case 'n': 141 nflag = 1; 142 break; 143 case 'u': 144 setvbuf(stdout, NULL, _IOLBF, 0); 145 break; 146 default: 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 err(1, "pledge"); 171 } else { 172 if (pledge("stdio rpath wpath cpath", NULL) == -1) 173 err(1, "pledge"); 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 err(1, "pledge"); 189 } 190 for (; *argv; argv++) 191 add_file(*argv); 192 } else { 193 if (!pledge_wpath && !pledge_rpath) { 194 if (pledge("stdio", NULL) == -1) 195 err(1, "pledge"); 196 } else if (pledge_rpath) { 197 if (pledge("stdio rpath", NULL) == -1) 198 err(1, "pledge"); 199 } else if (pledge_wpath) { 200 if (pledge("stdio wpath cpath", NULL) == -1) 201 err(1, "pledge"); 202 } 203 add_file(NULL); 204 } 205 process(); 206 cfclose(prog, NULL); 207 if (fclose(stdout)) 208 err(1, "stdout"); 209 exit (rval); 210 } 211 212 /* 213 * Like getline, but go through the chain of compilation units chaining them 214 * together. Empty strings and files are ignored. 215 */ 216 char * 217 cu_getline(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 err(1, "%s", script->s); 239 fname = script->s; 240 state = ST_FILE; 241 goto again; 242 case CU_STRING: 243 len = snprintf(string_ident, sizeof(string_ident), 244 "\"%s\"", script->s); 245 if (len >= sizeof(string_ident)) 246 strlcpy(string_ident + 247 sizeof(string_ident) - 6, " ...\"", 5); 248 fname = string_ident; 249 s = script->s; 250 state = ST_STRING; 251 goto again; 252 } 253 case ST_FILE: 254 if (getline(outbuf, outsize, f) != -1) { 255 p = *outbuf; 256 linenum++; 257 if (linenum == 1 && p[0] == '#' && p[1] == 'n') 258 nflag = 1; 259 return (*outbuf); 260 } 261 script = script->next; 262 (void)fclose(f); 263 state = ST_EOF; 264 goto again; 265 case ST_STRING: 266 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 267 nflag = 1; 268 p = *outbuf; 269 len = *outsize; 270 for (;;) { 271 if (len <= 1) { 272 *outbuf = xrealloc(*outbuf, 273 *outsize + _POSIX2_LINE_MAX); 274 p = *outbuf + *outsize - len; 275 len += _POSIX2_LINE_MAX; 276 *outsize += _POSIX2_LINE_MAX; 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 return (*outbuf); 289 } 290 case '\n': 291 *p++ = '\n'; 292 *p = '\0'; 293 s++; 294 linenum++; 295 return (*outbuf); 296 default: 297 *p++ = *s++; 298 len--; 299 } 300 } 301 } 302 303 return (NULL); 304 } 305 306 void 307 finish_file(void) 308 { 309 if (infile != NULL) { 310 fclose(infile); 311 if (*oldfname != '\0') { 312 if (rename(fname, oldfname) != 0) { 313 warn("rename %s to %s", fname, oldfname); 314 unlink(tmpfname); 315 exit(1); 316 } 317 *oldfname = '\0'; 318 } 319 if (*tmpfname != '\0') { 320 if (outfile != NULL && outfile != stdout) 321 fclose(outfile); 322 outfile = NULL; 323 if (rename(tmpfname, fname) != 0) { 324 warn("rename %s to %s", tmpfname, fname); 325 unlink(tmpfname); 326 exit(1); 327 } 328 *tmpfname = '\0'; 329 } 330 outfname = NULL; 331 } 332 } 333 334 /* 335 * Like getline, but go through the list of files chaining them together. 336 * Set len to the length of the line. 337 */ 338 int 339 mf_getline(SPACE *sp, enum e_spflag spflag) 340 { 341 struct stat sb; 342 size_t len; 343 char dirbuf[PATH_MAX]; 344 static char *p; 345 static size_t psize; 346 int c, fd; 347 static int firstfile; 348 349 if (infile == NULL) { 350 /* stdin? */ 351 if (files->fname == NULL) { 352 if (inplace != NULL) 353 errx(1, "-i may not be used with stdin"); 354 infile = stdin; 355 fname = "stdin"; 356 outfile = stdout; 357 outfname = "stdout"; 358 } 359 360 firstfile = 1; 361 } 362 363 for (;;) { 364 if (infile != NULL && (c = getc(infile)) != EOF) { 365 (void)ungetc(c, infile); 366 break; 367 } 368 /* If we are here then either eof or no files are open yet */ 369 if (infile == stdin) { 370 sp->len = 0; 371 return (0); 372 } 373 finish_file(); 374 if (firstfile == 0) 375 files = files->next; 376 else 377 firstfile = 0; 378 if (files == NULL) { 379 sp->len = 0; 380 return (0); 381 } 382 fname = files->fname; 383 if (inplace != NULL) { 384 if (stat(fname, &sb) != 0) 385 err(1, "%s", fname); 386 if (!S_ISREG(sb.st_mode)) 387 errx(1, "%s: %s %s", fname, 388 "in-place editing only", 389 "works for regular files"); 390 if (*inplace != '\0') { 391 (void)strlcpy(oldfname, fname, 392 sizeof(oldfname)); 393 len = strlcat(oldfname, inplace, 394 sizeof(oldfname)); 395 if (len >= sizeof(oldfname)) 396 errc(1, ENAMETOOLONG, "%s", fname); 397 } 398 len = strlcpy(dirbuf, fname, sizeof(dirbuf)); 399 if (len >= sizeof(dirbuf)) 400 errc(1, ENAMETOOLONG, "%s", fname); 401 len = snprintf(tmpfname, sizeof(tmpfname), 402 "%s/sedXXXXXXXXXX", dirname(dirbuf)); 403 if (len >= sizeof(tmpfname)) 404 errc(1, ENAMETOOLONG, "%s", fname); 405 if ((fd = mkstemp(tmpfname)) == -1) 406 err(1, "%s", fname); 407 (void)fchown(fd, sb.st_uid, sb.st_gid); 408 (void)fchmod(fd, sb.st_mode & ALLPERMS); 409 if ((outfile = fdopen(fd, "w")) == NULL) { 410 warn("%s", fname); 411 unlink(tmpfname); 412 exit(1); 413 } 414 outfname = tmpfname; 415 linenum = 0; 416 resetstate(); 417 } else { 418 outfile = stdout; 419 outfname = "stdout"; 420 } 421 if ((infile = fopen(fname, "r")) == NULL) { 422 warn("%s", fname); 423 rval = 1; 424 continue; 425 } 426 } 427 428 /* 429 * We are here only when infile is open and we still have something 430 * to read from it. 431 * 432 * Use getline() so that we can handle essentially infinite input 433 * data. The p and psize are static so each invocation gives 434 * getline() the same buffer which is expanded as needed. 435 */ 436 len = getline(&p, &psize, infile); 437 if ((ssize_t)len == -1) 438 err(1, "%s", fname); 439 if (len != 0 && p[len - 1] == '\n') { 440 sp->append_newline = 1; 441 len--; 442 } else if (!lastline()) { 443 sp->append_newline = 1; 444 } else { 445 sp->append_newline = 0; 446 } 447 cspace(sp, p, len, spflag); 448 449 linenum++; 450 451 return (1); 452 } 453 454 /* 455 * Add a compilation unit to the linked list 456 */ 457 static void 458 add_compunit(enum e_cut type, char *s) 459 { 460 struct s_compunit *cu; 461 462 cu = xmalloc(sizeof(struct s_compunit)); 463 cu->type = type; 464 cu->s = s; 465 cu->next = NULL; 466 *cu_nextp = cu; 467 cu_nextp = &cu->next; 468 } 469 470 /* 471 * Add a file to the linked list 472 */ 473 static void 474 add_file(char *s) 475 { 476 struct s_flist *fp; 477 478 fp = xmalloc(sizeof(struct s_flist)); 479 fp->next = NULL; 480 *fl_nextp = fp; 481 fp->fname = s; 482 fl_nextp = &fp->next; 483 } 484 485 486 static int 487 next_files_have_lines(void) 488 { 489 struct s_flist *file; 490 FILE *file_fd; 491 int ch; 492 493 file = files; 494 while ((file = file->next) != NULL) { 495 if ((file_fd = fopen(file->fname, "r")) == NULL) 496 continue; 497 498 if ((ch = getc(file_fd)) != EOF) { 499 /* 500 * This next file has content, therefore current 501 * file doesn't contains the last line. 502 */ 503 ungetc(ch, file_fd); 504 fclose(file_fd); 505 return (1); 506 } 507 fclose(file_fd); 508 } 509 return (0); 510 } 511 512 int 513 lastline(void) 514 { 515 int ch; 516 517 if (feof(infile)) { 518 return !( 519 (inplace == NULL) && 520 next_files_have_lines()); 521 } 522 if ((ch = getc(infile)) == EOF) { 523 return !( 524 (inplace == NULL) && 525 next_files_have_lines()); 526 } 527 ungetc(ch, infile); 528 return (0); 529 } 530