/* * Copyright (c) 2023 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This file is part of YAC coreutils. * * YAC coreutils is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * YAC coreutils is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include "yac.h" void tailc(FILE *file, long num) { int byte; char buf[num]; long offset = -num; fseek(file, offset, SEEK_END); for (int i = 0; (byte = fgetc(file)) != EOF; i++) { buf[i] = byte; } fputs(buf, stdout); } void tailf(FILE *file) { int byte; while(true) { if ((byte = fgetc(file)) != EOF) { putchar(byte); } } } void tailn(FILE *file, long num) { char *buf = calloc(4096, 1); char *lines[num]; int byte; int lc = 0; for (int bc = 0; (byte = fgetc(file)) != EOF; bc++) { if (bc == sizeof(buf)) { int err; if (err = realloc(buf, sizeof(buf) * 2) == NULL) { // TODO: error handling } } buf[bc] = byte; if (byte == '\n') { lines[lc] = buf; bc = 0; lc++; } if (lc == num) { for (int i = 0; i < lc; i++) { lines[i] = lines[i + 1]; } lc--; } } int i; if ((i = lc - num) < 0) { i = 0; } for (; i < lc; i++) { fputs(lines[i], stdout); } } int main(int argc, char *argv[]) { bool c = false; bool f = false; bool n = false; int i; int opt; long num; void (*fn)(FILE *, long) = tailn; extern int optind; while ((opt = getopt(argc, argv, "c:fn:")) != -1) { switch (opt) { /* * From tail(1p): * * -c number The application shall ensure that the number option-argument * is a decimal integer, optionally including a sign. The sign * shall affect the location in the file, measured in bytes, to * begin the copying: * * ┌─────┬────────────────────────────────────────┐ * │Sign │ Copying Starts │ * ├─────┼────────────────────────────────────────┤ * │ + │ Relative to the beginning of the file. │ * │ - │ Relative to the end of the file. │ * │none │ Relative to the end of the file. │ * └─────┴────────────────────────────────────────┘ * The application shall ensure that if the sign of the number * option-argument is '+', the number option-argument is a non- * zero decimal integer. * * The origin for counting shall be 1; that is, -c +1 represents * the first byte of the file, -c -1 the last. * * -f If the input file is a regular file or if the file operand * specifies a FIFO, do not terminate after the last line of the * input file has been copied, but read and copy further bytes * from the input file when they become available. If no file * operand is specified and standard input is a pipe or FIFO, * the -f option shall be ignored. If the input file is not a * FIFO, pipe, or regular file, it is unspecified whether or not * the -f option shall be ignored. * * -n number This option shall be equivalent to -c number, except the * starting location in the file shall be measured in lines * instead of bytes. The origin for counting shall be 1; that * is, -n +1 represents the first line of the file, -n -1 the * last. * * If neither -c nor -n is specified, -n 10 shall be assumed. */ case 'c': c = true; fn = tailc; num = (long)optarg; break; case 'f': f = true; case 'n': n = true; num = (long)optarg; break; default: fprintf( stderr, "Usage: %s (-f) [-c characters] [-n lines] file...\n", argv[0] ); return EX_USAGE; } } if (!n && !c) { num = 10; } else if (n && c) { fprintf( stderr, "Usage: %s (-f) [-c characters] [-n lines] file...\n", argv[0] ); return EX_USAGE; } FILE *file; if (optind = argc) { fn(stdin, num); if (f) { tailf(stdin); } } else { for (i = optind; i < argc; i++) { if ((file = rpath(argv[0], argv[i])) != NULL) { fn(file, num); fclose(file); } if (f) { tailf(file); } } } return EX_OK; }