commit 3bcdc8b606e964a795664158ef5781b5eab1c3e6
parent cd98c3d1e55549e2c8fc932234887b0a9f301a81
Author: Morel Bérenger <berengermorel76@gmail.com>
Date: Sat, 2 Jan 2021 21:40:02 +0100
Adds fbmon alpha
Diffstat:
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( ®ex, regex_str, REG_NEWLINE );
+ if( err )
+ {
+ char errbuf[1024];
+ size_t errlen = regerror( err, ®ex, 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( ®ex, 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( ®ex );
+
+ 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