/* * logger-tail.c - return last lines of a file * * Copyright (C) 2003-2023 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 "../weechat-plugin.h" #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) { if (!string_start || !string_ptr) return NULL; 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; } /* * Compares two lines in arraylist. */ int logger_tail_lines_cmp_cb (void *data, struct t_arraylist *arraylist, void *pointer1, void *pointer2) { /* make C compiler happy */ (void) data; (void) arraylist; return weechat_strcmp ((const char *)pointer1, (const char *)pointer2); } /* * Frees a line in arraylist. */ void logger_tail_lines_free_cb (void *data, struct t_arraylist *arraylist, void *pointer) { /* make C compiler happy */ (void) data; (void) arraylist; free (pointer); } /* * Returns last lines of a file. * * Note: result must be freed after use. */ struct t_arraylist * logger_tail_file (const char *filename, int 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, *line; struct t_arraylist *list_lines; if (!filename || (lines < 1)) return NULL; /* 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 "lines" lines in list */ part_of_line = NULL; list_lines = weechat_arraylist_new (lines, 0, 1, &logger_tail_lines_cmp_cb, NULL, &logger_tail_lines_free_cb, NULL); while (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); weechat_arraylist_free (list_lines); 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]) { if (part_of_line) { line = malloc ((strlen (pos_eol) + strlen (part_of_line) + 1)); if (!line) { free (part_of_line); weechat_arraylist_free (list_lines); close (fd); return NULL; } strcpy (line, pos_eol); strcat (line, part_of_line); free (part_of_line); part_of_line = NULL; weechat_arraylist_insert (list_lines, 0, line); } else { weechat_arraylist_insert (list_lines, 0, strdup (pos_eol)); } lines--; if (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); weechat_arraylist_free (list_lines); 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 list_lines; }