/* * logger-tail.c - return last lines of a file * * Copyright (C) 2003-2020 Sébastien Helleu * * This file is part of WeeChat, the extensible chat client. * * WeeChat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * WeeChat 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WeeChat. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "logger.h" #include "logger-tail.h" #define LOGGER_TAIL_BUFSIZE 4096 /* * Searches for last EOL in a string. */ const char * logger_tail_last_eol (const char *string_start, const char *string_ptr) { while (string_ptr >= string_start) { if ((string_ptr[0] == '\n') || (string_ptr[0] == '\r')) return string_ptr; string_ptr--; } /* no end-of-line found in string */ return NULL; } /* * Returns last lines of a file. * * Note: result must be freed after use with function logger_tail_free(). */ struct t_logger_line * logger_tail_file (const char *filename, int n_lines) { int fd; off_t file_length, file_pos; size_t to_read; ssize_t bytes_read; char buf[LOGGER_TAIL_BUFSIZE + 1]; char *ptr_buf, *pos_eol, *part_of_line, *new_part_of_line; struct t_logger_line *ptr_line, *new_line; /* open file */ fd = open (filename, O_RDONLY); if (fd == -1) return NULL; /* seek to the end of file */ file_length = lseek (fd, (off_t)0, SEEK_END); if (file_length <= 0) { close (fd); return NULL; } to_read = file_length; file_pos = file_length - LOGGER_TAIL_BUFSIZE; if (file_pos < 0) file_pos = 0; else to_read = LOGGER_TAIL_BUFSIZE; lseek (fd, file_pos, SEEK_SET); /* loop until we have "n_lines" lines in list */ part_of_line = NULL; ptr_line = NULL; while (n_lines > 0) { lseek (fd, file_pos, SEEK_SET); bytes_read = read (fd, buf, to_read); if (bytes_read <= 0) { if (part_of_line) free (part_of_line); logger_tail_free (ptr_line); close (fd); return NULL; } buf[bytes_read] = '\0'; ptr_buf = buf + bytes_read - 1; while (ptr_buf && (ptr_buf >= buf)) { pos_eol = (char *)logger_tail_last_eol (buf, ptr_buf); if ((pos_eol && (pos_eol[1] || part_of_line)) || (file_pos == 0)) { /* use data and part_of_line (if existing) to build a new line */ if (!pos_eol) { ptr_buf = NULL; pos_eol = buf; } else { ptr_buf = pos_eol - 1; pos_eol[0] = '\0'; pos_eol++; } if (part_of_line || pos_eol[0]) { new_line = malloc (sizeof (*new_line)); if (!new_line) { logger_tail_free (ptr_line); ptr_line = NULL; break; } if (part_of_line) { new_line->data = malloc ((strlen (pos_eol) + strlen (part_of_line) + 1)); if (!new_line->data) { free (part_of_line); free (new_line); logger_tail_free (ptr_line); close (fd); return NULL; } strcpy (new_line->data, pos_eol); strcat (new_line->data, part_of_line); free (part_of_line); part_of_line = NULL; } else { new_line->data = strdup (pos_eol); } new_line->next_line = ptr_line; ptr_line = new_line; n_lines--; if (n_lines <= 0) break; } } else if (!pos_eol) { /* * beginning of read buffer reached without EOL, then we * add string to part_of_line, we'll use that later */ if (part_of_line) { new_part_of_line = malloc (strlen (buf) + strlen (part_of_line) + 1); if (!new_part_of_line) { free (part_of_line); logger_tail_free (ptr_line); close (fd); return NULL; } strcpy (new_part_of_line, buf); strcat (new_part_of_line, part_of_line); free (part_of_line); part_of_line = new_part_of_line; } else { part_of_line = malloc (strlen (buf) + 1); strcpy (part_of_line, buf); } ptr_buf = NULL; } else ptr_buf = pos_eol - 1; } if (file_pos == 0) break; to_read = file_pos; file_pos -= LOGGER_TAIL_BUFSIZE; if (file_pos < 0) file_pos = 0; else to_read = LOGGER_TAIL_BUFSIZE; } if (part_of_line) free (part_of_line); close (fd); return ptr_line; } /* * Frees structure returned by function "logger_tail_file". */ void logger_tail_free (struct t_logger_line *lines) { struct t_logger_line *ptr_line, *next_line; if (!lines) return; ptr_line = lines; while (ptr_line) { next_line = ptr_line->next_line; if (ptr_line->data) free (ptr_line->data); free (ptr_line); ptr_line = next_line; } }