1 /* $OpenBSD: main.c,v 1.33 2016/07/14 08:31:18 semarie 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 109 main(int argc, char *argv[]) 110 { 111 struct winsize win; 112 int c, fflag; 113 char *p; 114 115 fflag = 0; 116 inplace = NULL; 117 while ((c = getopt(argc, argv, "Eae:f:i::nru")) != -1) 118 switch (c) { 119 case 'E': 120 case 'r': 121 Eflag = 1; 122 break; 123 case 'a': 124 aflag = 1; 125 break; 126 case 'e': 127 eflag = 1; 128 add_compunit(CU_STRING, optarg); 129 break; 130 case 'f': 131 fflag = 1; 132 add_compunit(CU_FILE, optarg); 133 break; 134 case 'i': 135 inplace = optarg ? optarg : ""; 136 break; 137 case 'n': 138 nflag = 1; 139 break; 140 case 'u': 141 setvbuf(stdout, NULL, _IOLBF, 0); 142 break; 143 default: 144 case '?': 145 (void)fprintf(stderr, 146 "usage: sed [-aEnru] [-i[extension]] command [file ...]\n" 147 " sed [-aEnru] [-e command] [-f command_file] [-i[extension]] [file ...]\n"); 148 exit(1); 149 } 150 argc -= optind; 151 argv += optind; 152 153 termwidth = 0; 154 if ((p = getenv("COLUMNS")) != NULL) 155 termwidth = strtonum(p, 0, INT_MAX, NULL); 156 if (termwidth == 0 && ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && 157 win.ws_col > 0) 158 termwidth = win.ws_col; 159 if (termwidth == 0) 160 termwidth = 80; 161 162 if (inplace != NULL) { 163 if (pledge("stdio rpath wpath cpath fattr chown", NULL) == -1) 164 error(FATAL, "pledge: %s", strerror(errno)); 165 } else { 166 if (pledge("stdio rpath wpath cpath", NULL) == -1) 167 error(FATAL, "pledge: %s", strerror(errno)); 168 } 169 170 /* First usage case; script is the first arg */ 171 if (!eflag && !fflag && *argv) { 172 add_compunit(CU_STRING, *argv); 173 argv++; 174 } 175 176 compile(); 177 178 /* Continue with first and start second usage */ 179 if (*argv) 180 for (; *argv; argv++) 181 add_file(*argv); 182 else 183 add_file(NULL); 184 process(); 185 cfclose(prog, NULL); 186 if (fclose(stdout)) 187 error(FATAL, "stdout: %s", strerror(errno)); 188 exit (rval); 189 } 190 191 /* 192 * Like fgets, but go through the chain of compilation units chaining them 193 * together. Empty strings and files are ignored. 194 */ 195 char * 196 cu_fgets(char **outbuf, size_t *outsize) 197 { 198 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 199 static FILE *f; /* Current open file */ 200 static char *s; /* Current pointer inside string */ 201 static char string_ident[30]; 202 size_t len; 203 char *p; 204 205 if (*outbuf == NULL) 206 *outsize = 0; 207 208 again: 209 switch (state) { 210 case ST_EOF: 211 if (script == NULL) 212 return (NULL); 213 linenum = 0; 214 switch (script->type) { 215 case CU_FILE: 216 if ((f = fopen(script->s, "r")) == NULL) 217 error(FATAL, 218 "%s: %s", script->s, strerror(errno)); 219 fname = script->s; 220 state = ST_FILE; 221 goto again; 222 case CU_STRING: 223 if ((snprintf(string_ident, 224 sizeof(string_ident), "\"%s\"", script->s)) >= 225 sizeof(string_ident)) 226 strlcpy(string_ident + 227 sizeof(string_ident) - 6, " ...\"", 5); 228 fname = string_ident; 229 s = script->s; 230 state = ST_STRING; 231 goto again; 232 } 233 case ST_FILE: 234 if ((p = fgetln(f, &len)) != NULL) { 235 linenum++; 236 if (len >= *outsize) { 237 free(*outbuf); 238 *outsize = ROUNDLEN(len + 1); 239 *outbuf = xmalloc(*outsize); 240 } 241 memcpy(*outbuf, p, len); 242 (*outbuf)[len] = '\0'; 243 if (linenum == 1 && p[0] == '#' && p[1] == 'n') 244 nflag = 1; 245 return (*outbuf); 246 } 247 script = script->next; 248 (void)fclose(f); 249 state = ST_EOF; 250 goto again; 251 case ST_STRING: 252 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 253 nflag = 1; 254 p = *outbuf; 255 len = *outsize; 256 for (;;) { 257 if (len <= 1) { 258 *outbuf = xrealloc(*outbuf, 259 *outsize + _POSIX2_LINE_MAX); 260 p = *outbuf + *outsize - len; 261 len += _POSIX2_LINE_MAX; 262 *outsize += _POSIX2_LINE_MAX; 263 } 264 switch (*s) { 265 case '\0': 266 state = ST_EOF; 267 if (s == script->s) { 268 script = script->next; 269 goto again; 270 } else { 271 script = script->next; 272 *p = '\0'; 273 linenum++; 274 return (*outbuf); 275 } 276 case '\n': 277 *p++ = '\n'; 278 *p = '\0'; 279 s++; 280 linenum++; 281 return (*outbuf); 282 default: 283 *p++ = *s++; 284 len--; 285 } 286 } 287 } 288 /* NOTREACHED */ 289 } 290 291 /* 292 * Like fgets, but go through the list of files chaining them together. 293 * Set len to the length of the line. 294 */ 295 int 296 mf_fgets(SPACE *sp, enum e_spflag spflag) 297 { 298 struct stat sb; 299 size_t len; 300 char *p; 301 int c, fd; 302 static int firstfile; 303 304 if (infile == NULL) { 305 /* stdin? */ 306 if (files->fname == NULL) { 307 if (inplace != NULL) 308 error(FATAL, "-i may not be used with stdin"); 309 infile = stdin; 310 fname = "stdin"; 311 outfile = stdout; 312 outfname = "stdout"; 313 } 314 315 firstfile = 1; 316 } 317 318 for (;;) { 319 if (infile != NULL && (c = getc(infile)) != EOF) { 320 (void)ungetc(c, infile); 321 break; 322 } 323 /* If we are here then either eof or no files are open yet */ 324 if (infile == stdin) { 325 sp->len = 0; 326 return (0); 327 } 328 if (infile != NULL) { 329 fclose(infile); 330 if (*oldfname != '\0') { 331 if (rename(fname, oldfname) != 0) { 332 error(WARNING, "rename()"); 333 unlink(tmpfname); 334 exit(1); 335 } 336 *oldfname = '\0'; 337 } 338 if (*tmpfname != '\0') { 339 if (outfile != NULL && outfile != stdout) 340 fclose(outfile); 341 outfile = NULL; 342 rename(tmpfname, fname); 343 *tmpfname = '\0'; 344 } 345 outfname = NULL; 346 } 347 if (firstfile == 0) 348 files = files->next; 349 else 350 firstfile = 0; 351 if (files == NULL) { 352 sp->len = 0; 353 return (0); 354 } 355 fname = files->fname; 356 if (inplace != NULL) { 357 if (lstat(fname, &sb) != 0) 358 error(FATAL, "%s: %s", fname, 359 strerror(errno ? errno : EIO)); 360 if (!S_ISREG(sb.st_mode)) 361 error(FATAL, "%s: %s %s", fname, 362 "in-place editing only", 363 "works for regular files"); 364 if (*inplace != '\0') { 365 strlcpy(oldfname, fname, 366 sizeof(oldfname)); 367 len = strlcat(oldfname, inplace, 368 sizeof(oldfname)); 369 if (len > sizeof(oldfname)) 370 error(FATAL, "%s: name too long", fname); 371 } 372 len = snprintf(tmpfname, sizeof(tmpfname), "%s/sedXXXXXXXXXX", 373 dirname(fname)); 374 if (len >= sizeof(tmpfname)) 375 error(FATAL, "%s: name too long", fname); 376 if ((fd = mkstemp(tmpfname)) == -1) 377 error(FATAL, "%s: %s", fname, strerror(errno)); 378 if ((outfile = fdopen(fd, "w")) == NULL) { 379 unlink(tmpfname); 380 error(FATAL, "%s", fname); 381 } 382 fchown(fileno(outfile), sb.st_uid, sb.st_gid); 383 fchmod(fileno(outfile), sb.st_mode & ALLPERMS); 384 outfname = tmpfname; 385 linenum = 0; 386 resetranges(); 387 } else { 388 outfile = stdout; 389 outfname = "stdout"; 390 } 391 if ((infile = fopen(fname, "r")) == NULL) { 392 error(WARNING, "%s", strerror(errno)); 393 rval = 1; 394 continue; 395 } 396 } 397 398 /* 399 * We are here only when infile is open and we still have something 400 * to read from it. 401 * 402 * Use fgetln so that we can handle essentially infinite input data. 403 * Can't use the pointer into the stdio buffer as the process space 404 * because the ungetc() can cause it to move. 405 */ 406 p = fgetln(infile, &len); 407 if (ferror(infile)) 408 error(FATAL, "%s: %s", fname, strerror(errno ? errno : EIO)); 409 if (len != 0 && p[len - 1] == '\n') { 410 sp->append_newline = 1; 411 len--; 412 } else if (!lastline()) { 413 sp->append_newline = 1; 414 } else { 415 sp->append_newline = 0; 416 } 417 cspace(sp, p, len, spflag); 418 419 linenum++; 420 421 return (1); 422 } 423 424 /* 425 * Add a compilation unit to the linked list 426 */ 427 static void 428 add_compunit(enum e_cut type, char *s) 429 { 430 struct s_compunit *cu; 431 432 cu = xmalloc(sizeof(struct s_compunit)); 433 cu->type = type; 434 cu->s = s; 435 cu->next = NULL; 436 *cu_nextp = cu; 437 cu_nextp = &cu->next; 438 } 439 440 /* 441 * Add a file to the linked list 442 */ 443 static void 444 add_file(char *s) 445 { 446 struct s_flist *fp; 447 448 fp = xmalloc(sizeof(struct s_flist)); 449 fp->next = NULL; 450 *fl_nextp = fp; 451 fp->fname = s; 452 fl_nextp = &fp->next; 453 } 454 455 456 static int 457 next_files_have_lines() 458 { 459 struct s_flist *file; 460 FILE *file_fd; 461 int ch; 462 463 file = files; 464 while ((file = file->next) != NULL) { 465 if ((file_fd = fopen(file->fname, "r")) == NULL) 466 continue; 467 468 if ((ch = getc(file_fd)) != EOF) { 469 /* 470 * This next file has content, therefore current 471 * file doesn't contains the last line. 472 */ 473 ungetc(ch, file_fd); 474 fclose(file_fd); 475 return (1); 476 } 477 fclose(file_fd); 478 } 479 return (0); 480 } 481 482 int 483 lastline(void) 484 { 485 int ch; 486 487 if (feof(infile)) { 488 return !( 489 (inplace == NULL) && 490 next_files_have_lines()); 491 } 492 if ((ch = getc(infile)) == EOF) { 493 return !( 494 (inplace == NULL) && 495 next_files_have_lines()); 496 } 497 ungetc(ch, infile); 498 return (0); 499 } 500