Skip to content

Instantly share code, notes, and snippets.

@amosshapira
Last active August 29, 2015 14:05
Show Gist options
  • Save amosshapira/4a5dae5f9ca6ffb6a6b9 to your computer and use it in GitHub Desktop.
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.
/* 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