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