190 lines
5.1 KiB
C
190 lines
5.1 KiB
C
/*
|
||
* Copyright (c) 2023 Emma Tebibyte <emma@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 <unistd.h>
|
||
|
||
#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;
|
||
}
|