diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4eadf24 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CC=clang +CFLAGS=-pedantic -Wall -Wextra -std=c99 -D_GNU_SOURCE -O3 + +all: shorten + +shorten: shorten.o + $(CC) $(CFLAGS) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm *.o diff --git a/shorten.c b/shorten.c new file mode 100644 index 0000000..488ba4a --- /dev/null +++ b/shorten.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUF_CAP 1024 + +int main(int argc, char **argv) { + if (argc != 3) { + printf("%s: Invalid number of arguments.\n", argv[0]); + printf("Usage: %s linecount filename\n", argv[0]); + printf("to remove linecount lines from the end of the file\n"); + } + + int number = atoi(argv[1]); + char* file = argv[2]; // No copy + int count = 0; + + if (number <= 0) { + printf("Bad number of line\n"); + exit(1); + } + + struct stat file_stat; + if (stat(file, &file_stat) < 0) { + printf("Stat failed: %d\n", errno); + return 2; + } + int file_size = file_stat.st_size; + if (file_size == 0) { + printf("No change: file does not end with a newline\n"); + return 1; + } + + int fd = open(file, O_RDWR | O_APPEND); + + if (lseek(fd, -1, SEEK_END) < 0) { + printf("Lseek1 failed: %d\n", errno); + close(fd); + return 2; + } + char c; + if (read(fd, &c, 1) < 0) { + printf("Read1 failed: %d\n", errno); + close(fd); + return 2; + } + + if (c != '\n') { + printf("No change: file does not end with a newline\n"); + close(fd); + return 1; + } + + while (1) { + int buf_size = MIN(BUF_CAP, file_size); // We can't seek back before the beginning of the file + char buf[buf_size]; + + // Go back in the file to read the length of the buffer + if (lseek(fd, -buf_size, SEEK_CUR) < 0) { + printf("Lseek2 failed: %d\n", errno); + close(fd); + return 2; + } + + // ~lseek(fd, buf_size, os.SEEK_CUR) + int buf_len = read(fd, buf, buf_size); + if (buf_len < 0) { + printf("Read2 failed: %d\n", errno); + close(fd); + return 2; + } + + char* char_pos = memrchr(buf, '\n', buf_len); + if (char_pos != NULL) { + count++; + // Go to position found ; a read would read a '\n' + if (lseek(fd, (ssize_t)(char_pos - buf) - buf_len, SEEK_CUR) < 0) { + printf("Lseek3 failed: %d\n", errno); + close(fd); + return 2; + } + // "resize" the file to look for next '\n' from this point + file_size -= buf_len - (ssize_t)(char_pos - buf); + } else { + // Go back to previous position + if (lseek(fd, -buf_len, SEEK_CUR) < 0) { + printf("Lseek4 failed: %d\n", errno); + close(fd); + return 2; + } + file_size -= buf_len; + } + + if (file_size < 0) + break; + + if (count == number + 1) { + file_size++; // Keep the last '\n' + if (ftruncate(fd, file_size) < 0) { + printf("Ftruncate failed: %d\n", errno); + close(fd); + return 2; + } + close(fd); + return 0; + } + + // Beginning of file? + if (lseek(fd, 0, SEEK_CUR) == 0) { + printf("No change: file does not end with a newline\n"); + return 1; + } + + } + + close(fd); + + if (count < number + 1) { + printf("No change: requested removal would leave empty file\n"); + return 3; + } +}