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;