Last active
August 29, 2015 14:05
-
-
Save amosshapira/4a5dae5f9ca6ffb6a6b9 to your computer and use it in GitHub Desktop.
Just a silly exercise in implementing the most efficient way to print the last X lines of a file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* most efficient way to print the last X lines of a file. | |
* | |
* it's done using my favourite system call - mmap(2). Instead of reading the file | |
* from start to finish and count newlines, the program maps the file into memory and | |
* scans it backwards from the end, counting newline characters as it goes. When it decided | |
* thet it found the beginning of the X line from the end (or reached the beginning of the file) | |
* it prints the entire block of lines in one call. | |
* | |
* Options to improve: | |
* 1. Use direct call to write(2) on file descriptor 1 instead of fwrite() | |
* 2. The structure is a bit redundant - we can achieve the same with pass-by-reference | |
* arguments to mmap_file() | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <fcntl.h> | |
#include <string.h> | |
#include <errno.h> | |
#include <sys/mman.h> | |
#include <sys/stat.h> | |
struct mapped_file { | |
void *start; | |
size_t size; | |
}; | |
struct mapped_file *mmap_file(const char *filename) | |
{ | |
int fd = open(filename, O_RDONLY); | |
if (fd < 0) { | |
fprintf(stderr, "open %s: %s", filename, strerror(errno)); | |
exit(1); | |
} | |
struct stat st_buf; | |
if (fstat(fd, &st_buf) < 0) { | |
fprintf(stderr, "fstat %s: %s", filename, strerror(errno)); | |
exit(1); | |
} | |
struct mapped_file *file = malloc(sizeof(struct mapped_file)); | |
if (file == NULL) { | |
fprintf(stderr, "malloc failed\n"); | |
exit(1); | |
} | |
file->size = st_buf.st_size; | |
file->start = mmap(0, file->size, PROT_READ, MAP_SHARED, fd, 0); | |
if (file->start == MAP_FAILED) { | |
fprintf(stderr, "mmap %s: %s", filename, strerror(errno)); | |
exit(1); | |
} | |
return file; | |
} | |
void print_last_lines(struct mapped_file *file, int lines) | |
{ | |
char *p = (char *)file->start; | |
char *q = p + file->size - 1; | |
// if the last character in the file is a newline then skip its count | |
// otherwise the last line after it will count as an empty one, so we'll | |
// print one less line | |
if (*q == '\n') | |
--q; | |
for (; q > p; --q) | |
{ | |
if (*q == '\n' && --lines == 0) { | |
++q; | |
break; | |
} | |
} | |
fwrite(q, 1, file->size - (q - p), stdout); | |
} | |
int main(int argc, char **argv) | |
{ | |
if (argc != 3) { | |
fprintf(stderr, "Usage: lastlines number-of-lines file-name\n"); | |
exit(1); | |
} | |
int lines = atoi(argv[1]); | |
char *filename = argv[2]; | |
if (lines < 0) { | |
fprintf(stderr, "Number of lines is %d, it should be a non-negative integer", lines); | |
exit(1); | |
} | |
if (lines == 0) { | |
// nothing to print | |
exit(0); | |
} | |
struct mapped_file *file = mmap_file(filename); | |
if (file->size == 0) { | |
// file is empty | |
exit(0); | |
} | |
print_last_lines(file, lines); | |
exit(0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment