210 lines
5.7 KiB
C
210 lines
5.7 KiB
C
/*
|
||
* 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;
|
||
}
|