tools

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

service_info.cpp (10485B)


      1 // Copyright (c) 2020 Morel Bérenger
      2 // 
      3 // This software is provided 'as-is', without any express or implied
      4 // warranty. In no event will the authors be held liable for any damages
      5 // arising from the use of this software.
      6 // 
      7 // Permission is granted to anyone to use this software for any purpose,
      8 // including commercial applications, and to alter it and redistribute it
      9 // freely, subject to the following restrictions:
     10 // 
     11 // 1. The origin of this software must not be misrepresented; you must not
     12 //    claim that you wrote the original software. If you use this software
     13 //    in a product, an acknowledgment in the product documentation would be
     14 //    appreciated but is not required.
     15 // 2. Altered source versions must be plainly marked as such, and must not be
     16 //    misrepresented as being the original software.
     17 // 3. This notice may not be removed or altered from any source distribution.
     18 
     19 #include <string.h>
     20 #include <assert.h>
     21 #include <string.h>
     22 #include <errno.h>
     23 #include <limits.h>
     24 #include <fnmatch.h>
     25 
     26 #include <sys/types.h>
     27 #include <sys/stat.h>
     28 #include <fcntl.h>
     29 #include <unistd.h>
     30 #include <poll.h>
     31 
     32 #include <iterator>
     33 
     34 #include <utils.hpp>
     35 #include <vector.hpp>
     36 #include <memory.hpp>
     37 
     38 #include "service_info.hpp"
     39 
     40 // linux only
     41 #include <sys/inotify.h>
     42 
     43 //bool svi_read( pollfd& pfd, poll_ev& pev );
     44 void svi_release( service_info_t* svi );
     45 //void svi_error( pollfd& pfd, poll_ev& pev );
     46 
     47 // return true on error.
     48 // parses a CLI arg, stores result into a service_info
     49 // instance, which is then added to "var", a vector of
     50 // service_info.
     51 bool add_serv_info( void* var, char* arg )
     52 {
     53 	if( !var )
     54 	{
     55 		return true;
     56 	}
     57 
     58 	// initialize the service_info_t object (dynamic)
     59 	auto services = reinterpret_cast<vector<service_info_t>*>( var );
     60 	service_info_t svi;
     61 	uint16_t next = 0;
     62 	size_t arglen = strlen( arg );
     63 	char * end = arg + arglen;
     64 	if( arglen > UINT16_MAX )
     65 	{
     66 		return true;
     67 	}
     68 
     69 	//change the ':' separators into \0 since we use C(++). This
     70 	//avoids copying data into other memory areas and use of pointers.
     71 	svi.path = arg;
     72 	if( arg[next] )
     73 	{
     74 		svi.filename = next += find_unescaped( ':', '\\', arg + next, end );
     75 		arg[next] = 0; ++next;
     76 	}
     77 	if( arg[next] )
     78 	{
     79 		svi.svstart = next += find_unescaped( ':', '\\', arg + next, end );
     80 		arg[next] = 0; ++next;
     81 	}
     82 	if( arg[next] )
     83 	{
     84 		svi.svstop  = next += find_unescaped( ':', '\\', arg + next, end );
     85 		arg[next] = 0; ++next;
     86 	}
     87 	if( arg[next] )
     88 	{
     89 		next += find_unescaped( ':', '\\', arg + next, end );
     90 		arg[next] = 0; ++next;
     91 	}
     92 
     93 	//TODO verify this works as intended
     94 	if( svi.filename >= arglen || svi.svstart >= arglen || svi.svstop >= arglen )
     95 	{
     96 		fputs( HEADER_LOG ": missing value", stderr );
     97 		return true;
     98 	}
     99 
    100 	errno = 0;
    101 	unsigned long tmp = strtoul( arg + next, &end, 10 );
    102 	if( errno || *end )
    103 	{
    104 		syserr( "failed to read delay time" );
    105 		return true;
    106 	}
    107 	if( tmp > UINT16_MAX )
    108 	{
    109 		fputs( HEADER_LOG ": delay value is out of bounds (max: 65535)", stderr );
    110 		return true;
    111 	}
    112 	svi.live_delay = static_cast<uint16_t>( tmp );
    113 
    114 	// cmdline arg checked, add watch and open target if it exists
    115 	// parent dir watch
    116 	svi.dir_wfd = inotify_add_watch( g_inotify_watch_fd, svi.path, 0
    117 			| IN_CREATE // should close target and open new
    118 			| IN_DELETE // should close target
    119 			| IN_MOVE // should close target
    120 			//| IN_MODIFY // modify any file
    121 			| IN_MOVE_SELF
    122 			| IN_DELETE_SELF
    123 			| IN_EXCL_UNLINK // don't care about unlinked files
    124 			);
    125 	if( -1 == svi.dir_wfd )
    126 	{
    127 		fprintf( stderr, HEADER_LOG ": failed to watch dir: \"%s\" %s\n",
    128 				svi.path, strerror( errno ) );
    129 		return true;
    130 	}
    131 
    132 	fprintf( stderr, "watcher added on %s\n", svi.path );
    133 
    134 	//move back the '\0' to a '/' to have a proper, complete filename... :/
    135 	svi.path[svi.filename] = '/';
    136 	pollfd tmppfd; //unused
    137 	if( file_watch( svi, tmppfd ) )
    138 	{
    139 		return true;
    140 	}
    141 
    142 	services->push_back( std::move( svi ) );
    143 	return false;
    144 }
    145 
    146 bool file_watch( service_info_t& svi, pollfd& pfd )
    147 {
    148 	svi.file_fd = open( svi.path, O_RDONLY | O_NOCTTY );
    149 	if( -1 != svi.file_fd )
    150 	{
    151 		fprintf( stderr, "opened %s in read-only mode\n", svi.path );
    152 		svi.file_wfd = inotify_add_watch( g_inotify_watch_fd, svi.path, 0
    153 				| IN_MODIFY
    154 				//| IN_MOVE_SELF // would be dup of dir_wfd, I guess
    155 				//| IN_DELETE_SELF // would be dup of dir_wfd, I guess
    156 				| IN_EXCL_UNLINK // don't care about unlinked files
    157 				);
    158 		if( -1 == svi.file_wfd )
    159 		{
    160 			fprintf( stderr, HEADER_LOG ": failed to watch file: \"%s\" %s\n",
    161 					svi.path, strerror( errno ) );
    162 			return true; // if could open, why would it fail to register inotify?
    163 		}
    164 		fprintf( stderr, "watching %s\n", svi.path );
    165 	}
    166 	else
    167 	{
    168 		switch( errno )
    169 		{
    170 			case EINTR: //TODO sig received
    171 				fprintf( stderr, HEADER_LOG " signal caught\n" );
    172 				break;
    173 			case ENOENT: //files does not exists yet
    174 				fprintf( stderr, HEADER_LOG " file %s does not exist yet\n", svi.path );
    175 				break;
    176 
    177 			case EACCES: //no rights to open
    178 			case EFAULT: //pathname out of accessible @space
    179 			case ELOOP: //too many symlinks followed
    180 			case EMFILE: //per-process limit reached
    181 			case ENAMETOOLONG: //pathname too long
    182 			case ENFILE: //system limit reached
    183 			case ENOMEM: //out of kernel memory
    184 			case ENOTDIR: //a component should be a dir but is not
    185 			case EFBIG: //file too big for opening
    186 			case EOVERFLOW: //file too big for opening
    187 			case EPERM: //either O_NOATIME without rights or file sealed?
    188 				fprintf( stderr, HEADER_LOG " unable to open file %s: %s(%d)", svi.path, strerror( errno ), errno );
    189 				return true;
    190 
    191 			case EINVAL: //debug
    192 			case EDQUOT: //?
    193 			case ENODEV: //?
    194 
    195 			case EWOULDBLOCK: //can't happen
    196 			case EOPNOTSUPP: //can't happen
    197 			case ENOSPC: //can't happen
    198 			case EEXIST: //can't happen
    199 			case EISDIR: //can't happen
    200 			case ENXIO: //can't happen
    201 			case EROFS: //can't happen
    202 			case ETXTBSY: //can't happen
    203 			case EBADF: //can't happen
    204 			default: //can't happen
    205 				fprintf( stderr, HEADER_LOG " BUG: %s (%x)\n", strerror( errno ), errno );
    206 				abort();
    207 		}
    208 	}
    209 	return false;
    210 }
    211 
    212 bool show_serv_info( void const* val, FILE* target )
    213 {
    214 	if( !val )
    215 	{
    216 		return true;
    217 	}
    218 	auto svis = reinterpret_cast<const vector<service_info_t>*>( val );
    219 
    220 	fprintf( target, "(current: " );
    221 	for( auto svi : *svis )
    222 	{
    223 		fprintf( target, "path=\"%s\" start=\"%s\" stop=\"%s\" delay=%d; "
    224 				, svi.path
    225 				, svi.path + svi.svstart
    226 				, svi.path + svi.svstop
    227 				, svi.live_delay
    228 				);
    229 	}
    230 	fprintf( target, ")" );
    231 	return false;
    232 }
    233 
    234 //void svi_release( service_info_t* svi )
    235 //{
    236 //	assert( g_inotify_watch_fd >= 0 );
    237 //	inotify_rm_watch( svi->dir_wfd, g_inotify_watch_fd );
    238 //}
    239 
    240 //void svi_error( pollfd& pfd, poll_ev& pev )
    241 //{
    242 //	svi_release( pfd, pev );
    243 //}
    244 //
    245 //bool svi_read( pollfd& pfd, poll_ev& pev )
    246 //{
    247 //	char buf[1024];
    248 //	ssize_t rd = read( pfd.fd, buf, sizeof( buf ) );
    249 //	if( -1 == rd )
    250 //	{
    251 //		fputs( HEADER_LOG ": failed to read: ", stderr );
    252 //		fputs( strerror( errno ), stderr );
    253 //		return true;
    254 //	}
    255 //
    256 //	inotify_event ev;
    257 //	// TODO:
    258 //	// poll sur fd réel (pas de inotify)
    259 //	// surveiller actions de déplacement/suppression du fichier
    260 //	// ouvrir le fichier en amount si existant
    261 //	// ouvrir le fichier lors d'un IN_CREATE
    262 //	// filtrer: un seul fichier doit être vraiment surveillé
    263 //	for( char* ptr = buf; ptr < buf + rd;
    264 //			ptr += sizeof( inotify_event* ) + ev.len )
    265 //	{
    266 //		memcpy( &ev, ptr, sizeof( inotify_event ) );
    267 //
    268 //		if( ev.mask &(IN_DELETE | IN_MOVE ) )
    269 //		{
    270 //		}
    271 //		if( ev.mask & IN_MODIFY )
    272 //		{
    273 //		}
    274 //		if( ev.mask &(IN_MOVE_SELF | IN_DELETE_SELF ) )
    275 //		{
    276 //		}
    277 //	}
    278 //}
    279 
    280 size_t name_sz( service_info_t const& svc )
    281 {
    282 	//TODO: implement a real identifier
    283 	return svc.filename;
    284 }
    285 
    286 //TODO: time implementation sucks
    287 bool refresh( service_info_t & svc, time_t time )
    288 {
    289 	status_t s = status( svc );
    290 
    291 	char const* color = nullptr;
    292 	switch( s )
    293 	{
    294 		case DOWN:
    295 			color = g_color_down;
    296 			break;
    297 		case WAKING:
    298 			color = g_color_wake;
    299 			assert( time < UINT16_MAX );
    300 			svc.birth_time += time;
    301 			if( svc.birth_time < svc.live_delay )
    302 			{
    303 				break;
    304 			}
    305 		case UP: // fallthrough
    306 			color = g_color_up;
    307 			break;
    308 	}
    309 	assert( color );
    310 
    311 	assert( name_sz( svc ) < INT_MAX );
    312 	fprintf( stdout, "\x1B[%d;%dH" "\x1B[30;%sm" "%.*s" "\x1B[m\n" ,
    313 			svc.draw_area.y, svc.draw_area.x,
    314 			color,
    315 			static_cast<int>( name_sz( svc ) ), name( svc ) );
    316 	return false;
    317 }
    318 
    319 bool update( service_info_t & svc )
    320 {
    321 	char buf[4096];
    322 	size_t to_read = sizeof( buf ) - 1;
    323 	char *start = buf;
    324 
    325 	status_t s = status( svc );
    326 	ssize_t rd;
    327 	while( ( rd = read( svc.file_fd, start, to_read ) ) > 0 )
    328 	{
    329 		start[rd] = '\0';
    330 		char * end_line = start + rd - 1;
    331 		while( end_line >= start && *end_line != '\n' )
    332 		{
    333 			--end_line;
    334 		}
    335 		if( end_line < start )
    336 		{
    337 			fputs( "buffer too small", stderr ); //TODO mark as DOWN?
    338 			return true;
    339 		}
    340 		const char * const next_start = end_line + 1;
    341 		char const* start_line = end_line;
    342 		bool found = false;
    343 		uint16_t pattern = 0;
    344 
    345 		do
    346 		{
    347 			do
    348 			{
    349 				--start_line;
    350 			} while( *start_line != '\n' && start_line >= buf );
    351 
    352 			++start_line;
    353 			*end_line = '\0'; //POSIX standard only works nul-term, not sizes.
    354 
    355 			pattern = svc.svstart;
    356 			found = FNM_NOMATCH != fnmatch( svc.path + 1 + pattern , start_line, 0 );
    357 			if( !found )
    358 			{
    359 				pattern = svc.svstop;
    360 				found = FNM_NOMATCH != fnmatch( svc.path + 1 + pattern , start_line, 0 );
    361 			}
    362 
    363 			*end_line = '\n'; //revert changes had to do because of POSIX shit
    364 			--start_line;
    365 		} while( !found && start_line >= buf );
    366 
    367 		if( found )
    368 		{
    369 			assert( pattern != 0 );
    370 			if( pattern == svc.svstart )
    371 			{
    372 				s = WAKING;
    373 				svc.birth_time = 0;
    374 			}
    375 			else
    376 			{
    377 				s = DOWN;
    378 				svc.birth_time = UINT16_MAX;
    379 			}
    380 		}
    381 
    382 		if( static_cast<size_t>( rd ) == to_read )
    383 		{
    384 			assert( buf + sizeof( buf ) > next_start );
    385 			size_t offset = static_cast<size_t>( buf + sizeof( buf ) - next_start );
    386 			memmove( buf, next_start, offset );
    387 			start = buf + offset;
    388 			to_read = sizeof( buf ) - offset - 1;
    389 		}
    390 	}
    391 	//TODO: handle errors more correctly
    392 	//  EINTR: signal received
    393 	//  EAGAIN: file is non-blocking (should not happen but...)
    394 	if( -1 == rd )
    395 	{
    396 		syserr( "reading target file failed" );
    397 		fprintf( stderr, "%d\n", svc.file_fd );
    398 		return true;
    399 	}
    400 	return false;
    401 }
    402 
    403 status_t status( service_info_t const& svc )
    404 {
    405 	if( svc.birth_time == UINT16_MAX )
    406 	{
    407 		return DOWN;
    408 	}
    409 	if( svc.birth_time < svc.live_delay )
    410 	{
    411 		return WAKING;
    412 	}
    413 	return UP;
    414 }
    415 
    416 char const* name( service_info_t const& svc )
    417 {
    418 	return svc.path;
    419 }
    420 
    421 int g_inotify_watch_fd;