tools

various tools
git clone git://deadbeef.fr/tools.git
Log | Files | Refs | README | LICENSE

commit 3bcdc8b606e964a795664158ef5781b5eab1c3e6
parent cd98c3d1e55549e2c8fc932234887b0a9f301a81
Author: Morel Bérenger <berengermorel76@gmail.com>
Date:   Sat,  2 Jan 2021 21:40:02 +0100

Adds fbmon alpha

Diffstat:
Mbtl/src/utils.hpp | 26+++++++++-----------------
Mbuild.ninja | 1+
Afbmon/fbmon.ninja | 15+++++++++++++++
Afbmon/src/fbmon.cpp | 457+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afbmon/src/service_info.cpp | 426+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afbmon/src/service_info.hpp | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 1002 insertions(+), 17 deletions(-)

diff --git a/btl/src/utils.hpp b/btl/src/utils.hpp @@ -74,28 +74,20 @@ int esc_fputs( const char *s, FILE *stream ); // be null-terminated. inline size_t ungets( char const* const str, size_t str_sz ); -inline uint16_t find_unescaped( int end, int esc, char ** str, size_t str_sz ) +// search for a specific character, if not preceded by an escape one. +// return the distance, or end - start if not found +inline uint16_t find_unescaped( int ch, int esc_ch, char const* start, char const* end ) { - char* ch = *str; - - for( ; str_sz != 0 && *ch && *ch != end; ++ch, --str_sz ) + uint16_t ret = 0; + for( ; start != end && *start != ch; ++start, ++ret ) { - if( *ch == esc ) + if( *start == esc_ch ) { - ++ch; + ++start; + ++ret; } } - char* start = *str; - *str = ch; - assert( ch >= start ); - return static_cast<uint16_t>( ch - start ); -} - -inline uint16_t find_unescaped( int end, int esc, char ** str ) -{ - //specific implementation would be faster, since less - //comparison to do, but for now, let's code faster. - return find_unescaped( end, esc, str, strlen( *str ) ); + return ret; } // returns the size in characters of terminal fd in w and h. diff --git a/build.ninja b/build.ninja @@ -2,3 +2,4 @@ include conf.ninja subninja btl/btl.ninja subninja lmerge/lmerge.ninja +subninja fbmon/fbmon.ninja diff --git a/fbmon/fbmon.ninja b/fbmon/fbmon.ninja @@ -0,0 +1,15 @@ +PROJECT = fbmon + +CXXFLAGS = $$CXXFLAGS -fcolor-diagnostics -fno-rtti -fno-exceptions + +SRC = ./$PROJECT/src +DST = ./build/$PROJECT/src + +BTL_LIB = ./build/btl/ +BTL_INC = ./btl/src/ +CXXFLAGS = $CXXFLAGS -I$BTL_INC -DFILE_PTR=FILE* + +CXXFLAGS = $CXXFLAGS -I$SRC +build $DST/$PROJECT.cpp.o: cxx $SRC/$PROJECT.cpp +build $DST/service_info.cpp.o: cxx $SRC/service_info.cpp +build $DST/../$PROJECT: ld $DST/$PROJECT.cpp.o $DST/service_info.cpp.o $BTL_LIB/btl.a diff --git a/fbmon/src/fbmon.cpp b/fbmon/src/fbmon.cpp @@ -0,0 +1,457 @@ +// Copyright (c) 2020 Morel Bérenger +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#ifdef LIBCPP_MUSL_STATIC +#define __GLIBC_PREREQ(x,y) 0 +#endif + +#include <string.h> +#include <assert.h> +#include <math.h> + +#include <iterator> +#include <algorithm> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <poll.h> + +#include <termios.h> +#include <sys/ioctl.h> + +#include <utils.hpp> +#include <vector.hpp> +#include <optparser.hpp> + +#include "service_info.hpp" + +// compatibility: +// this code assumes (because of inotify API and behavior) a linux +// kernel, v2.6.21 minimum. Previous versions may contain bugs, but +// I won't hunt them. If you spot one, feel free to send the patch, +// *if* it does not makes the code more complex. +#include <linux/limits.h> +#include <sys/inotify.h> + +void print_help( char const* pgm, FILE* target, opt_desc_t const* start, opt_desc_t const* end ); + +static const char serv_infos_desc[] = +"description of a service to watch, separated with ':': path:filename:start_regex:death_regex:alive_delay"; + +bool close_file( service_info_t& svc ); +bool linux_event_handling( size_t start, vector<service_info_t>& svcs, vector<pollfd>& pfds ); +void term_resize( int ); +int update_screen_info( void ); + +static bool term_resized = true; +area_t g_screen_area; + +int main( int argc, char **argv ) +{ + vector<service_info_t> services; + vector<pollfd> pfds; + int refresh_delay = 1;//TODO: use. Or remove, not sure. Why keep? To show date? + //int fbdev; + //char* fbpath = nullptr; + //char* ftpath = nullptr; + + opt_desc_t opts[] = + { + STD_HELP, + { "service", serv_infos_desc, 's', 0, &services, add_serv_info, show_serv_info }, + { "refresh_delay", "seconds between 2 refresh (r<0: on event only)", 'r', 0, &refresh_delay, set<int>, show<int> }, + //{ "framebuffer", "path to the framebuffer device", 'f', 0, &fbpath, set<char const**>, show<char*> }, + //{ "font", "path to the font to use (PSF formats)", 'F', 0, &ftpath, set<char const**>, show<char*> }, + }; + + // adds the "master" inotify watched + // must be done before parsing arguments + g_inotify_watch_fd = inotify_init(); + if( -1 == g_inotify_watch_fd ) + { + syserr( "failed to initialise inotify" ); + return EXIT_FAILURE; + } + fputs( "inotify initialised\n", stderr ); + + //TODO: catch resize events + if( -1 == update_screen_info() ) + { + syserr( "failed to retrieve screen's size (input term, really)" ); + return EXIT_FAILURE; + } + + auto b_opts = std::begin( opts ); + auto e_opts = std::end( opts ); + + char **arg = &argv[1]; + assert( argc > 0 ); + for( int iarg = 1; iarg != argc; ++iarg, ++arg ) + { + auto error = parse_cmd_opt( *arg, b_opts, e_opts ); + switch( error ) + { + case MAX_COUNT: + arg_warning( *arg, error ); + break; + case NONE: + case IGNORED: + break; + case SET_NO_VAL: + case SET_VAL_IGN: + case SET_FAIL: + case BAD_ARGS: + case BAD_SETTER: + print_help( argv[0], stderr, b_opts, e_opts ); + arg_error( *arg, error ); + return EXIT_FAILURE; + } + } + + if( opts[0].count ) //check if --help was found + { + print_help( argv[0], stderr, b_opts, e_opts ); + return EXIT_SUCCESS; + } + if( services.empty() ) + { + print_help( argv[0], stderr, b_opts, e_opts ); + return EXIT_FAILURE; + } + + // this program works with the assumption already opened FDs + // are "packed", the numbers do not have holes. This assumption + // is done for ease of code and performance reasons. + // reserve nb services pollfd instances plus: + // _ inotify fd + const size_t INOTIF_FD = pfds.size(); + pfds.reserve( pfds.size() + 1 + INOTIF_FD + services.size() ); + pfds.push_back( { g_inotify_watch_fd, POLLIN | POLLPRI, 0 } ); + + uint8_t max_width = 0; + for( auto const &svc : services ) + { + pfds.push_back( { svc.file_fd, POLLIN|POLLPRI, 0 } ); + //compute max width to use with some margins + max_width = std::max( max_width, static_cast<uint8_t>( name_sz( svc ) + 3 ) ); + fprintf( stderr, "now polling fd %d\n", svc.file_fd ); + } + const uint16_t max_cols = static_cast<uint16_t>( ceil( g_screen_area.w / static_cast<float>( max_width ) ) ); + const uint16_t col_size = g_screen_area.w / max_cols; + const uint16_t row_size = 2; + + uint16_t col = 0, row = 0; + for( auto &svc : services ) + { + svc.draw_area.x = 2 + col * col_size; + svc.draw_area.y = 1 + row * row_size; + //TODO: implement limits, so that a more graphical mode can be implemented + //svc.draw_area.w = ...; + //svc.draw_area.h = ...; + ++col; + row += col / max_cols; + col %= max_cols; + } + + int nbev; + bool stop = false; + assert( g_inotify_watch_fd != 0 && "TODO: 0 can't be disabled in poll" ); + int prev_time = time( nullptr ); + int curr_time; + int dist_time = 0; + while( !stop ) + { + if( dist_time == 0 ) + { + dist_time = refresh_delay; + } + nbev = poll( pfds.data(), pfds.size(), dist_time * 1000 ); + curr_time = time( nullptr ); + dist_time = curr_time - prev_time; + prev_time = curr_time; + //got interrupted or reached timeout + if( -1 == nbev || 0 == nbev ) + { + for( size_t i = 1; i < pfds.size(); ++i ) + { + refresh( services[i-1], dist_time ); + } + continue; + } + + for( size_t i = 0; i < pfds.size(); ++i ) + { + short rev = pfds[i].revents; + if( 0 == rev ) + { + continue; + } + + --nbev; + // dynamic FDs: target files + if( i > INOTIF_FD ) + { + if( rev & ( POLLIN | POLLPRI ) ) + { + update( services[i-1], pfds[i].fd, pfds[INOTIF_FD].fd, dist_time ); + } + else + { + refresh( services[i-1], dist_time ); + } + } + else + { + assert( g_inotify_watch_fd >= 0 ); + if( INOTIF_FD == i && linux_event_handling( INOTIF_FD, services, pfds ) ) + { + abort(); + } + } + } + } + return EXIT_SUCCESS; +} + +void term_resize( int ) +{ + term_resized = true; +} + +int update_screen_info( void ) +{ + winsize wsz; + if( term_resized ) + { + int ioc = ioctl( STDIN_FILENO, TIOCGWINSZ, &wsz ); + //TODO move in loop + //TODO don't do anything as long as areas are not ok + //if( -1 == ioc && errno != EINTR ) + if( -1 == ioc && errno != EINTR ) + { + perror( "open" ); + return -1; + } + if( g_screen_area.w != wsz.ws_row || g_screen_area.h != wsz.ws_col ) + { + g_screen_area.x = 0; + g_screen_area.y = 0; + g_screen_area.w = wsz.ws_col; + g_screen_area.h = wsz.ws_row; + // TODO update all client's areas + } + return 0; + } + return -1; +} + +void print_help( char const* pgm, FILE* target, opt_desc_t const* start, opt_desc_t const* end ) +{ + fputs( "Usage: ", target ); + fputs( pgm, target ); + fputs( " [OPTIONS]\n" + "Description:\n" + "This program constantly watches log files to guess a service's state: starting, alive, dead.\n" + "Guessing is based on 3 things:\n" + "\t* a \"starting\" regex;\n" + "\t* a \"dead\" regex;\n" + "\t* a time-to-start delay, in seconds;\n" + "\n" + "Results are printed on choosen TTY.\n" + "For each watched file:\n" + "\t* full path of file\n" + "\t* current status\n" + "\t* timestamp of last known running state\n" + "\t* timestamp of last known death\n" + "\n" + "Regex syntax POSIX Extended (for now)\n" + //"Regex syntax is intentionnally trivial and a lot simpler that what you might know, only reserved characters are:\n" + //"\t* '*': matches the SMALLER possible string\n" + //"\t* '(': start the ONLY allowed capture\n" + //"\t* ')': ends the capture\n" + //"\t* '|': ends the regex\n" + //"\t* '\\': escape next character\n" + "TODO:\n" + "do not scoll, instead, update informations\n" + "show an even log\n" + "have colours to know more easily a service's state\n" + "reduce footprint and dependencies.\n" + "static build (monitors should be 100% independent)\n" + "only work when the correct TTY is \"focused\"\n" + "Options:\n" + , target ); + print_opts( target, start, end ); +} + +// for each service, there can be 2 kind of events registered: +// * event on parent directory: +// only interesting if pdir is removed (unregister (0ed it?) the +// service, close file if open) or if affected file is watched file +// * event on watched file: +// enable polling of target FD +// but it's also possible that polling was enabled for file. If so, it +// should stay enabled as long as read() returns the maximum number of bytes. +// +// read a chunk (can't do better, number of bytes there can't be guessed) +// for each event in chunk: +// locate the service it belongs to +// if event affects directory +// linked pollfd is inotify_fd + distance( services.begin(), service ) +// if event implies a file change of name or deletion: +// close FD in service +// if event is appearance of target +// open file and store into service +// polling becomes FD +// if event affects target file +// enable polling +// + +// pfds[start]: inotify's pollfd +// pfds[start+x]: service N's pollfd +bool linux_event_handling( size_t start, vector<service_info_t>& svcs, vector<pollfd>& pfds ) +{ + //that code taken from manpage is *really* ugly + char buf[sizeof(inotify_event) + PATH_MAX] __attribute__ ((aligned(__alignof__(struct inotify_event)))); + assert( pfds[start].fd > 0 ); + ssize_t len = read( pfds[start].fd, buf, sizeof( buf ) ); + if( -1 == len && errno == EINVAL ) + { + fputs( "failed to read: inotify's API is fucked up.", stderr ); + return true; + } + const inotify_event* event; + for( const char * ptr = buf; ptr < buf + len; ptr += sizeof( inotify_event ) + event->len ) + { + event = reinterpret_cast<const inotify_event*>( ptr ); + + auto svc_it = std::find_if( svcs.begin(), svcs.end(), + [&]( service_info_t const& s ) + { + return s.dir_wfd == event->wd || s.file_wfd == event->wd; + } ); + if( svc_it == svcs.end() ) + { + continue; + } + + assert( svc_it >= svcs.begin() ); + pollfd& pfd = pfds[start + 1 + static_cast<size_t>( svc_it - svcs.begin() ) ]; + // work on dir + if( svc_it->dir_wfd == event->wd ) + { + assert( event->mask & ( + IN_DELETE_SELF | IN_MOVE_SELF | // disable service + IN_DELETE | IN_MOVE | // close target FD and update pollfd + IN_CREATE // open target and updated pollfd + ) ); + + if( 0 != strcmp( svc_it->path + 1 + svc_it->filename, event->name ) ) + { + continue; + } + // this will happen when a file gets IN_MOVE, because it can be both + // IN_MOVED_TO and IN_MOVED_FROM + if( event->mask & ( IN_DELETE | IN_MOVE ) && svc_it->file_fd != -1 ) + { + if( close_file( *svc_it ) ) + { + fputs( "fucking API!\n", stderr ); + } + pfd.fd = -1; + } + if( event->mask & ( IN_CREATE ) ) + { + //TODO: this code is everything but safe. Should be moved out of here. + assert( svc_it->file_fd == -1 ); + svc_it->file_wfd = inotify_add_watch( g_inotify_watch_fd, svc_it->path, 0 + | IN_MODIFY + //| IN_MOVE_SELF // would be dup of dir_wfd, I guess + //| IN_DELETE_SELF // would be dup of dir_wfd, I guess + | IN_EXCL_UNLINK // don't care about unlinked files + ); + if( -1 == svc_it->file_wfd ) + { + fprintf( stderr, HEADER_LOG ": failed to watch file: \"%s\" %s(0x%x)", + svc_it->path, strerror( errno ), errno ); + return true; + } + if( -1 == ( pfd.fd = svc_it->file_fd = open( svc_it->path, O_RDONLY ) ) ) + { + syserr( "unable to open service's file" ); + return true; + } + } + if( event->mask & ( IN_DELETE_SELF | IN_MOVE_SELF ) ) + { + // failure here is not that much of a problem... + + //TODO: actually remove both pollfd and service instances + // from the vectors (swap with last element and resize) + if( close_file( *svc_it ) ) + { + fputs( "fucking API!\n", stderr ); + } + if( -1 == inotify_rm_watch( g_inotify_watch_fd, svc_it->dir_wfd ) ) + { + syserr( "failed to remove dir WD" ); + } + svc_it->dir_wfd = -1; + svc_it->birth_time = UINT16_MAX; + pfd.fd = -1; + } + } + else + { + assert( event->mask & IN_MODIFY ); + assert( svc_it->file_fd >= 0 ); + pfd.fd = svc_it->file_fd; + } + + + assert( -1 != event->wd ); + assert( svc_it->dir_wfd != svc_it->file_wfd ); + } + return false; +} + +bool close_file( service_info_t& svc ) +{ + bool ret = false; + if( -1 == close( svc.file_fd ) ) + { + syserr( "failed to close service FD" ); + ret = true; + } + svc.file_fd = -1; + if( -1 == inotify_rm_watch( g_inotify_watch_fd, svc.file_wfd ) ) + { + syserr( "failed to remove file WD" ); + ret = true; + } + svc.file_wfd = -1; + return ret; +} + +// red +char const* g_color_down = "41"; +// green +char const* g_color_up = "42"; +// yellow +char const* g_color_wake = "43"; diff --git a/fbmon/src/service_info.cpp b/fbmon/src/service_info.cpp @@ -0,0 +1,426 @@ +// Copyright (c) 2020 Morel Bérenger +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#include <string.h> +#include <assert.h> +#include <string.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <poll.h> + +#include <iterator> + +#include <utils.hpp> +#include <vector.hpp> +#include <memory.hpp> + +#include "service_info.hpp" + +// linux only +#include <sys/inotify.h> + +//bool svi_read( pollfd& pfd, poll_ev& pev ); +void svi_release( service_info_t* svi ); +//void svi_error( pollfd& pfd, poll_ev& pev ); + +// return true on error. +// parses a CLI arg, stores result into a service_info +// instance, which is then added to "var", a vector of +// service_info. +bool add_serv_info( void* var, char* arg ) +{ + if( !var ) + { + return true; + } + + // initialize the service_info_t object (dynamic) + auto services = reinterpret_cast<vector<service_info_t>*>( var ); + service_info_t svi; + uint16_t next = 0; + size_t arglen = strlen( arg ); + char * end = arg + arglen; + if( arglen > UINT16_MAX ) + { + return true; + } + + //change the ':' separators into \0 since we use C(++). This + //avoids copying data into other memory areas and use of pointers. + svi.path = arg; + if( arg[next] ) + { + svi.filename = next += find_unescaped( ':', '\\', arg + next, end ); + arg[next] = 0; ++next; + } + if( arg[next] ) + { + svi.svstart = next += find_unescaped( ':', '\\', arg + next, end ); + arg[next] = 0; ++next; + } + if( arg[next] ) + { + svi.svstop = next += find_unescaped( ':', '\\', arg + next, end ); + arg[next] = 0; ++next; + } + if( arg[next] ) + { + next += find_unescaped( ':', '\\', arg + next, end ); + arg[next] = 0; ++next; + } + + //TODO verify this works as intended + if( svi.filename >= arglen || svi.svstart >= arglen || svi.svstop >= arglen ) + { + fputs( HEADER_LOG ": missing value", stderr ); + return true; + } + + errno = 0; + unsigned long tmp = strtoul( arg + next, &end, 10 ); + if( errno || *end ) + { + syserr( "failed to read delay time" ); + return true; + } + if( tmp > UINT16_MAX ) + { + fputs( HEADER_LOG ": delay value is out of bounds (max: 65535)", stderr ); + return true; + } + svi.live_delay = static_cast<uint16_t>( tmp ); + + // cmdline arg checked, add watch and open target if it exists + // parent dir watch + svi.dir_wfd = inotify_add_watch( g_inotify_watch_fd, svi.path, 0 + | IN_CREATE // should close target and open new + | IN_DELETE // should close target + | IN_MOVE // should close target + //| IN_MODIFY // modify any file + | IN_MOVE_SELF + | IN_DELETE_SELF + | IN_EXCL_UNLINK // don't care about unlinked files + ); + if( -1 == svi.dir_wfd ) + { + fprintf( stderr, HEADER_LOG ": failed to watch dir: \"%s\" %s\n", + svi.path, strerror( errno ) ); + return true; + } + + fprintf( stderr, "watcher added on %s\n", svi.path ); + + //move back the '\0' to a '/' to have a proper, complete filename... :/ + svi.path[svi.filename] = '/'; + pollfd tmppfd; //unused + if( file_watch( svi, tmppfd ) ) + { + return true; + } + + services->push_back( std::move( svi ) ); + return false; +} + +bool file_watch( service_info_t& svi, pollfd& pfd ) +{ + svi.file_fd = open( svi.path, O_RDONLY | O_NOCTTY ); + if( -1 != svi.file_fd ) + { + fprintf( stderr, "opened %s in read-only mode\n", svi.path ); + svi.file_wfd = inotify_add_watch( g_inotify_watch_fd, svi.path, 0 + | IN_MODIFY + //| IN_MOVE_SELF // would be dup of dir_wfd, I guess + //| IN_DELETE_SELF // would be dup of dir_wfd, I guess + | IN_EXCL_UNLINK // don't care about unlinked files + ); + if( -1 == svi.file_wfd ) + { + fprintf( stderr, HEADER_LOG ": failed to watch file: \"%s\" %s\n", + svi.path, strerror( errno ) ); + return true; // if could open, why would it fail to register inotify? + } + fprintf( stderr, "watching %s\n", svi.path ); + } + else + { + switch( errno ) + { + case EINTR: //TODO sig received + fprintf( stderr, HEADER_LOG " signal caught\n" ); + break; + case ENOENT: //files does not exists yet + fprintf( stderr, HEADER_LOG " file %s does not exist yet\n", svi.path ); + break; + + case EACCES: //no rights to open + case EFAULT: //pathname out of accessible @space + case ELOOP: //too many symlinks followed + case EMFILE: //per-process limit reached + case ENAMETOOLONG: //pathname too long + case ENFILE: //system limit reached + case ENOMEM: //out of kernel memory + case ENOTDIR: //a component should be a dir but is not + case EFBIG: //file too big for opening + case EOVERFLOW: //file too big for opening + case EPERM: //either O_NOATIME without rights or file sealed? + fprintf( stderr, HEADER_LOG " unable to open file %s: %s(%d)", svi.path, strerror( errno ), errno ); + return true; + + case EINVAL: //debug + case EDQUOT: //? + case ENODEV: //? + + case EWOULDBLOCK: //can't happen + case EOPNOTSUPP: //can't happen + case ENOSPC: //can't happen + case EEXIST: //can't happen + case EISDIR: //can't happen + case ENXIO: //can't happen + case EROFS: //can't happen + case ETXTBSY: //can't happen + case EBADF: //can't happen + default: //can't happen + fprintf( stderr, HEADER_LOG " BUG: %s (%x)\n", strerror( errno ), errno ); + abort(); + } + } + return false; +} + +bool show_serv_info( void const* val, FILE* target ) +{ + if( !val ) + { + return true; + } + auto svis = reinterpret_cast<const vector<service_info_t>*>( val ); + + fprintf( target, "(current: " ); + for( auto svi : *svis ) + { + fprintf( target, "path=\"%s\" start=\"%s\" stop=\"%s\" delay=%d; " + , svi.path + , svi.path + svi.svstart + , svi.path + svi.svstop + , svi.live_delay + ); + } + fprintf( target, ")" ); + return false; +} + +//void svi_release( service_info_t* svi ) +//{ +// assert( g_inotify_watch_fd >= 0 ); +// inotify_rm_watch( svi->dir_wfd, g_inotify_watch_fd ); +//} + +//void svi_error( pollfd& pfd, poll_ev& pev ) +//{ +// svi_release( pfd, pev ); +//} +// +//bool svi_read( pollfd& pfd, poll_ev& pev ) +//{ +// char buf[1024]; +// ssize_t rd = read( pfd.fd, buf, sizeof( buf ) ); +// if( -1 == rd ) +// { +// fputs( HEADER_LOG ": failed to read: ", stderr ); +// fputs( strerror( errno ), stderr ); +// return true; +// } +// +// inotify_event ev; +// // TODO: +// // poll sur fd réel (pas de inotify) +// // surveiller actions de déplacement/suppression du fichier +// // ouvrir le fichier en amount si existant +// // ouvrir le fichier lors d'un IN_CREATE +// // filtrer: un seul fichier doit être vraiment surveillé +// for( char* ptr = buf; ptr < buf + rd; +// ptr += sizeof( inotify_event* ) + ev.len ) +// { +// memcpy( &ev, ptr, sizeof( inotify_event ) ); +// +// if( ev.mask &(IN_DELETE | IN_MOVE ) ) +// { +// } +// if( ev.mask & IN_MODIFY ) +// { +// } +// if( ev.mask &(IN_MOVE_SELF | IN_DELETE_SELF ) ) +// { +// } +// } +//} + +size_t name_sz( service_info_t const& svc ) +{ + //TODO: implement a real identifier + return svc.filename; +} + +//TODO: time implementation sucks +bool refresh( service_info_t & svc, int time ) +{ + status_t s = status( svc ); + + char const* color = nullptr; + switch( s ) + { + case DOWN: + color = g_color_down; + break; + case WAKING: + color = g_color_wake; + svc.birth_time += time; + if( svc.birth_time < svc.live_delay ) + { + break; + } + case UP: // fallthrough + color = g_color_up; + break; + } + assert( color ); + + fprintf( stdout, "\x1B[%d;%dH" "\x1B[30;%sm" "%.*s" "\x1B[m\n" , + svc.draw_area.y, svc.draw_area.x, + color, + name_sz( svc ), name( svc ) ); + return false; +} + +bool update( service_info_t & svc, int &pfd, int &inotify_pfd, int time ) +{ + const size_t MAX_CHARS = 4095; + char buf[MAX_CHARS+1]; + assert( pfd >= 0 ); + ssize_t len = read( svc.file_fd, buf, MAX_CHARS ); + if( -1 == len ) + { + syserr( "reading target file failed" ); + fprintf( stderr, "%d %d\n", svc.file_fd, pfd ); + return true; + } + + buf[len] = 0; + if( MAX_CHARS != len ) // done with this file, for now + { + pfd = -pfd; + } + else //disable inotify to (try to) prevent loosing data if target moved + { + inotify_pfd = -inotify_pfd; + } + + status_t s = status( svc ); + + char const* regex_str = svc.path + 1; + switch( s ) + { + case DOWN: + regex_str += svc.svstart; + break; + case WAKING: + svc.birth_time += time; + if( svc.birth_time >= svc.live_delay ) + { + s = UP; + } + case UP: // fallthrough + regex_str += svc.svstop; + break; + } + assert( regex_str ); + + regex_t regex; + int err = regcomp( &regex, regex_str, REG_NEWLINE ); + if( err ) + { + char errbuf[1024]; + size_t errlen = regerror( err, &regex, errbuf, sizeof( errbuf ) ); + fprintf( stderr, "failed to compile regex: %.*s (%d)\n", static_cast<int>( errlen ), errbuf, err ); + return true; + } + const size_t NB_MATCHES = 2; + regmatch_t matches[NB_MATCHES]; //1st if match, 2nd for capture, don't care for others + //TODO: implement capture showing + int ret = regexec( &regex, buf, NB_MATCHES, matches, 0 ); + if( REG_NOMATCH != ret ) + { + if( s == DOWN ) + { + svc.birth_time = 0; + s = svc.birth_time <= svc.live_delay ? WAKING : UP; + } + else + { + svc.birth_time = UINT16_MAX; + s = DOWN; + } + } + + char const* color = nullptr; + switch( s ) + { + case DOWN: + color = g_color_down; + break; + case UP: + color = g_color_up; + break; + case WAKING: + color = g_color_wake; + break; + } + assert( color ); + regfree( &regex ); + + fprintf( stdout, "\x1B[%d;%dH" "\x1B[30;%sm" "%.*s" "\x1B[m\n" , + svc.draw_area.y, svc.draw_area.x, + color, + name_sz( svc ), name( svc ) ); + return false; +} + +status_t status( service_info_t const& svc ) +{ + if( svc.birth_time == UINT16_MAX ) + { + return DOWN; + } + if( svc.birth_time < svc.live_delay ) + { + return WAKING; + } + return UP; +} + +char const* name( service_info_t const& svc ) +{ + return svc.path; +} + +int g_inotify_watch_fd; diff --git a/fbmon/src/service_info.hpp b/fbmon/src/service_info.hpp @@ -0,0 +1,94 @@ +// Copyright (c) 2020 Morel Bérenger +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#ifndef SERVICE_INFO_HPP +#define SERVICE_INFO_HPP + +#include <regex.h> + +struct area_t +{ + uint16_t x, y; + uint16_t w, h; +}; + +enum status_t +{ + DOWN, + WAKING, + UP, +}; + +bool add_serv_info( void* var, char* arg ); +bool show_serv_info( void const* val, FILE* target ); + +// registers info about a service: +// * pointer to the filepath of logs to analyse +// * directory inotify watcher +// * file inotify watcher if the file exists +// * open file descriptor if the file exists +// * time since last "starting" regex was met, without "stoping" regex +// * offset from filepath at which directory name ends +struct service_info_t +{ + // passed argument is kept as is, and occurrences of ':' are + // used as end-of-string. The .*_sz vars are relative to + // previous index, 0 for filename_sz. + char * path = nullptr; //path to watch with inotify + int dir_wfd = -1; //inotify fd for parent dir + int file_wfd = -1; //file to read data from + int file_fd = -1; //file to read data from + uint16_t filename = UINT16_MAX;//path offset, regex showing start of svc + uint16_t svstart = UINT16_MAX;//path offset, regex showing start of svc + uint16_t svstop = UINT16_MAX;//path offset, regex showind death of svc + //TODO: use for service identification + //uint16_t svcname = UINT16_MAX;//path offset, regex showing start of svc + uint16_t live_delay = 0; //seconds needed to be alive since birth + uint16_t birth_time = UINT16_MAX;//seconds since birth, MAX when not alive + + area_t draw_area; +}; + +bool file_watch( service_info_t& svc, pollfd& pfd ); + +size_t name_sz( service_info_t const& svc ); +char const* name( service_info_t const& svc ); +status_t status( service_info_t const& svc ); + +bool print_status( service_info_t const& svc ); +bool refresh( service_info_t & svc, int time ); +bool update( service_info_t & svc, int& pfd, int& inotify_pfd, int time ); + +// stores the "master" inotify. Inotify is used to avoid +// active probing files and still be able to handle log +// rotations. +extern int g_inotify_watch_fd; + +// stores the screen's dimensions, and the place in which +// to start drawing (might be handy later). +// Initialised at startup +// TODO: keep it up to date with ioctl and SIGWINCH +extern area_t g_screen_area; + +extern char const* g_color_down; +extern char const* g_color_up; +extern char const* g_color_wake; + +char const* move_to( service_info_t const& svc ); + +#endif