1/* $NetBSD: main.c,v 1.38 2021/03/11 15:45:55 christos Exp $ */ 2 3/*- 4 * Copyright (c) 2013 Johann 'Myrkraverk' Oskarsson. 5 * Copyright (c) 1992 Diomidis Spinellis. 6 * Copyright (c) 1992, 1993 7 * The Regents of the University of California. All rights reserved. 8 * 9 * This code is derived from software contributed to Berkeley by 10 * Diomidis Spinellis of Imperial College, University of London. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37#if HAVE_NBTOOL_CONFIG_H 38#include "nbtool_config.h" 39#endif 40 41#include <sys/cdefs.h> 42__RCSID("$NetBSD: main.c,v 1.38 2021/03/11 15:45:55 christos Exp $"); 43#ifdef __FBSDID 44__FBSDID("$FreeBSD: head/usr.bin/sed/main.c 252231 2013-06-26 04:14:19Z pfg $"); 45#endif 46 47#ifndef lint 48__COPYRIGHT("@(#) Copyright (c) 1992, 1993\ 49 The Regents of the University of California. All rights reserved."); 50#endif 51 52#if 0 53static const char sccsid[] = "@(#)main.c 8.2 (Berkeley) 1/3/94"; 54#endif 55 56#include <sys/types.h> 57#include <sys/mman.h> 58#include <sys/param.h> 59#include <sys/stat.h> 60 61#include <err.h> 62#include <errno.h> 63#include <fcntl.h> 64#include <libgen.h> 65#include <limits.h> 66#include <locale.h> 67#include <regex.h> 68#include <stddef.h> 69#define _WITH_GETLINE 70#include <stdio.h> 71#include <stdlib.h> 72#include <string.h> 73#include <unistd.h> 74 75#include "defs.h" 76#include "extern.h" 77 78/* 79 * Linked list of units (strings and files) to be compiled 80 */ 81struct s_compunit { 82 struct s_compunit *next; 83 enum e_cut {CU_FILE, CU_STRING} type; 84 char *s; /* Pointer to string or fname */ 85}; 86 87/* 88 * Linked list pointer to compilation units and pointer to current 89 * next pointer. 90 */ 91static struct s_compunit *script, **cu_nextp = &script; 92 93/* 94 * Linked list of files to be processed 95 */ 96struct s_flist { 97 char *fname; 98 struct s_flist *next; 99}; 100 101/* 102 * Linked list pointer to files and pointer to current 103 * next pointer. 104 */ 105static struct s_flist *files, **fl_nextp = &files; 106 107FILE *infile; /* Current input file */ 108FILE *outfile; /* Current output file */ 109 110int aflag, eflag, nflag; 111int rflags = 0; 112static int rval; /* Exit status */ 113 114static int ispan; /* Whether inplace editing spans across files */ 115 116/* 117 * Current file and line number; line numbers restart across compilation 118 * units, but span across input files. The latter is optional if editing 119 * in place. 120 */ 121const char *fname; /* File name. */ 122const char *outfname; /* Output file name */ 123static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ 124static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ 125static const char *inplace; /* Inplace edit file extension. */ 126u_long linenum; 127 128static void add_compunit(enum e_cut, char *); 129static void add_file(char *); 130static void usage(void) __dead; 131 132int 133main(int argc, char *argv[]) 134{ 135 int c, fflag; 136 char *temp_arg; 137 138 setprogname(argv[0]); 139 (void) setlocale(LC_ALL, ""); 140 141 fflag = 0; 142 inplace = NULL; 143 144 while ((c = getopt(argc, argv, "EGI::ae:f:gi::lnru")) != -1) 145 switch (c) { 146 case 'r': /* Gnu sed compat */ 147 case 'E': 148 rflags |= REG_EXTENDED; 149 break; 150 case 'G': 151 rflags &= ~REG_GNU; 152 break; 153 case 'I': 154 inplace = optarg ? optarg : __UNCONST(""); 155 ispan = 1; /* span across input files */ 156 break; 157 case 'a': 158 aflag = 1; 159 break; 160 case 'e': 161 eflag = 1; 162 temp_arg = xmalloc(strlen(optarg) + 2); 163 strcpy(temp_arg, optarg); 164 strcat(temp_arg, "\n"); 165 add_compunit(CU_STRING, temp_arg); 166 break; 167 case 'f': 168 fflag = 1; 169 add_compunit(CU_FILE, optarg); 170 break; 171 case 'g': 172 rflags |= REG_GNU; 173 break; 174 case 'i': 175 inplace = optarg ? optarg : __UNCONST(""); 176 ispan = 0; /* don't span across input files */ 177 break; 178 case 'l': 179#ifdef _IOLBF 180 c = setvbuf(stdout, NULL, _IOLBF, 0); 181#else 182 c = setlinebuf(stdout); 183#endif 184 if (c) 185 warn("setting line buffered output failed"); 186 break; 187 case 'n': 188 nflag = 1; 189 break; 190 case 'u': 191#ifdef _IONBF 192 c = setvbuf(stdout, NULL, _IONBF, 0); 193#else 194 c = -1; 195 errno = EOPNOTSUPP; 196#endif 197 if (c) 198 warn("setting unbuffered output failed"); 199 break; 200 default: 201 case '?': 202 usage(); 203 } 204 argc -= optind; 205 argv += optind; 206 207 /* First usage case; script is the first arg */ 208 if (!eflag && !fflag && *argv) { 209 add_compunit(CU_STRING, *argv); 210 argv++; 211 } 212 213 compile(); 214 215 /* Continue with first and start second usage */ 216 if (*argv) 217 for (; *argv; argv++) 218 add_file(*argv); 219 else 220 add_file(NULL); 221 process(); 222 cfclose(prog, NULL); 223 if (fclose(stdout)) 224 err(1, "stdout"); 225 exit(rval); 226} 227 228static void 229usage(void) 230{ 231 (void)fprintf(stderr, 232 "Usage: %s [-aEGglnru] command [file ...]\n" 233 "\t%s [-aEGglnru] [-e command] [-f command_file] [-I[extension]]\n" 234 "\t [-i[extension]] [file ...]\n", getprogname(), getprogname()); 235 exit(1); 236} 237 238/* 239 * Like fgets, but go through the chain of compilation units chaining them 240 * together. Empty strings and files are ignored. 241 */ 242char * 243cu_fgets(char *buf, int n, int *more) 244{ 245 static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; 246 static FILE *f; /* Current open file */ 247 static char *s; /* Current pointer inside string */ 248 static char string_ident[30]; 249 char *p; 250 251again: 252 switch (state) { 253 case ST_EOF: 254 if (script == NULL) { 255 if (more != NULL) 256 *more = 0; 257 return (NULL); 258 } 259 linenum = 0; 260 switch (script->type) { 261 case CU_FILE: 262 if ((f = fopen(script->s, "r")) == NULL) 263 err(1, "%s", script->s); 264 fname = script->s; 265 state = ST_FILE; 266 goto again; 267 case CU_STRING: 268 if (((size_t)snprintf(string_ident, 269 sizeof(string_ident), "\"%s\"", script->s)) >= 270 sizeof(string_ident) - 1) 271 (void)strcpy(string_ident + 272 sizeof(string_ident) - 6, " ...\""); 273 fname = string_ident; 274 s = script->s; 275 state = ST_STRING; 276 goto again; 277 default: 278 abort(); 279 } 280 case ST_FILE: 281 if ((p = fgets(buf, n, f)) != NULL) { 282 linenum++; 283 if (linenum == 1 && buf[0] == '#' && buf[1] == 'n') 284 nflag = 1; 285 if (more != NULL) 286 *more = !feof(f); 287 return (p); 288 } 289 script = script->next; 290 (void)fclose(f); 291 state = ST_EOF; 292 goto again; 293 case ST_STRING: 294 if (linenum == 0 && s[0] == '#' && s[1] == 'n') 295 nflag = 1; 296 p = buf; 297 for (;;) { 298 if (n-- <= 1) { 299 *p = '\0'; 300 linenum++; 301 if (more != NULL) 302 *more = 1; 303 return (buf); 304 } 305 switch (*s) { 306 case '\0': 307 state = ST_EOF; 308 if (s == script->s) { 309 script = script->next; 310 goto again; 311 } else { 312 script = script->next; 313 *p = '\0'; 314 linenum++; 315 if (more != NULL) 316 *more = 0; 317 return (buf); 318 } 319 case '\n': 320 *p++ = '\n'; 321 *p = '\0'; 322 s++; 323 linenum++; 324 if (more != NULL) 325 *more = 0; 326 return (buf); 327 default: 328 *p++ = *s++; 329 } 330 } 331 } 332 /* NOTREACHED */ 333 return (NULL); 334} 335 336/* 337 * Like fgets, but go through the list of files chaining them together. 338 * Set len to the length of the line. 339 */ 340int 341mf_fgets(SPACE *sp, enum e_spflag spflag) 342{ 343 struct stat sb; 344 size_t len; 345 static char *p = NULL; 346 static size_t plen = 0; 347 int c; 348 static int firstfile; 349 350 if (infile == NULL) { 351 /* stdin? */ 352 if (files->fname == NULL) { 353 if (inplace != NULL) 354 errx(1, "-I or -i may not be used with stdin"); 355 infile = stdin; 356 fname = "stdin"; 357 outfile = stdout; 358 outfname = "stdout"; 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 if (infile != NULL) { 374 fclose(infile); 375 if (*oldfname != '\0') { 376 /* if there was a backup file, remove it */ 377 unlink(oldfname); 378 /* 379 * Backup the original. Note that hard links 380 * are not supported on all filesystems. 381 */ 382 if ((link(fname, oldfname) != 0) && 383 (rename(fname, oldfname) != 0)) { 384 warn("rename()"); 385 if (*tmpfname) 386 unlink(tmpfname); 387 exit(1); 388 } 389 *oldfname = '\0'; 390 } 391 if (*tmpfname != '\0') { 392 if (outfile != NULL && outfile != stdout) 393 if (fclose(outfile) != 0) { 394 warn("fclose()"); 395 unlink(tmpfname); 396 exit(1); 397 } 398 outfile = NULL; 399 if (rename(tmpfname, fname) != 0) { 400 /* this should not happen really! */ 401 warn("rename()"); 402 unlink(tmpfname); 403 exit(1); 404 } 405 *tmpfname = '\0'; 406 } 407 outfname = NULL; 408 } 409 if (firstfile == 0) 410 files = files->next; 411 else 412 firstfile = 0; 413 if (files == NULL) { 414 sp->len = 0; 415 return (0); 416 } 417 fname = files->fname; 418 if (inplace != NULL) { 419 if (lstat(fname, &sb) != 0) 420 err(1, "%s", fname); 421 if (!(sb.st_mode & S_IFREG)) 422 errx(1, "%s: %s %s", fname, 423 "in-place editing only", 424 "works for regular files"); 425 if (*inplace != '\0') { 426 strlcpy(oldfname, fname, 427 sizeof(oldfname)); 428 len = strlcat(oldfname, inplace, 429 sizeof(oldfname)); 430 if (len > sizeof(oldfname)) 431 errx(1, "%s: name too long", fname); 432 } 433 char d_name[PATH_MAX], f_name[PATH_MAX]; 434 (void)strlcpy(d_name, fname, sizeof(d_name)); 435 (void)strlcpy(f_name, fname, sizeof(f_name)); 436 len = (size_t)snprintf(tmpfname, sizeof(tmpfname), 437 "%s/.!%ld!%s", dirname(d_name), (long)getpid(), 438 basename(f_name)); 439 if (len >= sizeof(tmpfname)) 440 errx(1, "%s: name too long", fname); 441 unlink(tmpfname); 442 if (outfile != NULL && outfile != stdout) 443 fclose(outfile); 444 if ((outfile = fopen(tmpfname, "w")) == NULL) 445 err(1, "%s", fname); 446 fchown(fileno(outfile), sb.st_uid, sb.st_gid); 447 fchmod(fileno(outfile), sb.st_mode & ALLPERMS); 448 outfname = tmpfname; 449 if (!ispan) { 450 linenum = 0; 451 resetstate(); 452 } 453 } else { 454 outfile = stdout; 455 outfname = "stdout"; 456 } 457 if ((infile = fopen(fname, "r")) == NULL) { 458 warn("%s", fname); 459 rval = 1; 460 continue; 461 } 462 } 463 /* 464 * We are here only when infile is open and we still have something 465 * to read from it. 466 * 467 * Use getline() so that we can handle essentially infinite input 468 * data. The p and plen are static so each invocation gives 469 * getline() the same buffer which is expanded as needed. 470 */ 471 ssize_t slen = getline(&p, &plen, infile); 472 if (slen == -1) 473 err(1, "%s", fname); 474 if (slen != 0 && p[slen - 1] == '\n') { 475 sp->append_newline = 1; 476 slen--; 477 } else if (!lastline()) { 478 sp->append_newline = 1; 479 } else { 480 sp->append_newline = 0; 481 } 482 cspace(sp, p, (size_t)slen, spflag); 483 484 linenum++; 485 486 return (1); 487} 488 489/* 490 * Add a compilation unit to the linked list 491 */ 492static void 493add_compunit(enum e_cut type, char *s) 494{ 495 struct s_compunit *cu; 496 497 cu = xmalloc(sizeof(struct s_compunit)); 498 cu->type = type; 499 cu->s = s; 500 cu->next = NULL; 501 *cu_nextp = cu; 502 cu_nextp = &cu->next; 503} 504 505/* 506 * Add a file to the linked list 507 */ 508static void 509add_file(char *s) 510{ 511 struct s_flist *fp; 512 513 fp = xmalloc(sizeof(struct s_flist)); 514 fp->next = NULL; 515 *fl_nextp = fp; 516 fp->fname = s; 517 fl_nextp = &fp->next; 518} 519 520static int 521next_files_have_lines(void) 522{ 523 struct s_flist *file; 524 FILE *file_fd; 525 int ch; 526 527 file = files; 528 while ((file = file->next) != NULL) { 529 if ((file_fd = fopen(file->fname, "r")) == NULL) 530 continue; 531 532 if ((ch = getc(file_fd)) != EOF) { 533 /* 534 * This next file has content, therefore current 535 * file doesn't contains the last line. 536 */ 537 ungetc(ch, file_fd); 538 fclose(file_fd); 539 return (1); 540 } 541 542 fclose(file_fd); 543 } 544 545 return (0); 546} 547 548int 549lastline(void) 550{ 551 int ch; 552 553 if (feof(infile)) { 554 return !( 555 (inplace == NULL || ispan) && 556 next_files_have_lines()); 557 } 558 if ((ch = getc(infile)) == EOF) { 559 return !( 560 (inplace == NULL || ispan) && 561 next_files_have_lines()); 562 } 563 ungetc(ch, infile); 564 return (0); 565} 566