/* * Copyright (c) 2023 Emma Tebibyte * Copyright (c) 2023 Marceline Cramer * 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 #include "yac.h" typedef struct string_t { char *mem; int len; int capacity; } string_t; string_t *string_new() { string_t *string = calloc(1, sizeof(string_t)); string->capacity = 1024; string->len = 0; string->mem = calloc(string->capacity, sizeof(char)); return string; } void string_putc(string_t *string, char c) { string->mem[string->len] = c; string->len++; if (string->len >= string->capacity) { string->capacity *= 2; string->mem = realloc(string->mem, string->capacity * sizeof(char)); } } void tailc(FILE *f, long num) {} void tailn(FILE *f, long num) { string_t *lines[num]; int cursor = 0; int looped = 0; lines[cursor] = string_new(); int c = fgetc(f); for (;;) { if (c == EOF) { string_putc(lines[cursor], '\0'); break; } string_putc(lines[cursor], c); if (c == '\n') { string_putc(lines[cursor], '\0'); if ((c = fgetc(f)) == EOF) { break; } if (++cursor >= num) { looped = 1; cursor = 0; } lines[cursor] = string_new(); } else { c = fgetc(f); } } int read = looped ? cursor + 1 : 0; do { if (read >= num) { read = 0; } fputs(lines[read]->mem, stdout); } while (read++ != cursor); } void tailf(FILE *file) { int byte; while(true) { if ((byte = fgetc(file)) != EOF) { putchar(byte); } } } 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 = strtol(optarg, NULL, 10); break; case 'f': f = true; case 'n': n = true; num = strtol(optarg, NULL, 10); 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); } if (f) { tailf(file); } fclose(file); } } return EX_OK; }