overgrown/src/tail.c

210 lines
5.7 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2023 Marceline Cramer <mars@tebibyte.media>
* 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 <errno.h>
#include <sysexits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}