├── LICENSE.md ├── README.md ├── config.c ├── config.h ├── list.h ├── logger.c ├── logger.h ├── udp.c ├── udp.mak ├── udp.vpj ├── udp.vpw ├── udp.vpwhistu ├── udp.vtg └── udpproxy.conf /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | udpproxy is free software; you can redistribute it and/or 3 | modify it under the terms of the GNU Lesser General Public 4 | License as published by the Free Software Foundation; either 5 | version 2.1 of the License, or (at your option) any later version. 6 | 7 | udpproxy is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | Lesser General Public License for more details. 11 | 12 | You should have received a copy of the GNU Lesser General Public 13 | License along with ijkPlayer; if not, write to the Free Software 14 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # udpproxy 2 | UDPProxy sends FFMPEG time-sliced HLS sequence to 3 | the specified IP and port address. The program contains 4 | the friendly log and config reading functions, use the 5 | list_head double-linked list function provided by the 6 | Linux kernel, and also includes the function of inotify 7 | for folder monitoring. -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * handle config file(s). 3 | * format is: 4 | * section-name1: 5 | * keyword1 (scalar) = value1 6 | * keyword2 = value2 7 | * keyword3 = 'this is a really big multi-line \ 8 | * value with spaces on the end ' 9 | * keyword4 (array) = val1, val2, 'val 3 ', val4 10 | * keyword5 (hash) = v1 = this, \ 11 | * v2 = " that ", \ 12 | * v3 = fooey 13 | * section-name2: 14 | * keyword4 = value1 15 | * ... 16 | * Copyright (c) 2015-2019 rlandjon 17 | * 18 | * This file is part of udpproxy. 19 | * 20 | * udpproxy is free software; you can redistribute it and/or 21 | * modify it under the terms of the GNU Lesser General Public 22 | * License as published by the Free Software Foundation; either 23 | * version 2.1 of the License, or (at your option) any later version. 24 | * 25 | * udpproxy is distributed in the hope that it will be useful, 26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 28 | * Lesser General Public License for more details. 29 | * 30 | * You should have received a copy of the GNU Lesser General Public 31 | * License along with ijkPlayer; if not, write to the Free Software 32 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 33 | * 34 | * The 'type' of a value defaults to scalar and does not need to be given. 35 | * Continuation lines have a backslash as the last character on a line. 36 | * Supports #include files to any depth via recursion. 37 | * Can (single or double) quote values to maintain whitespace. 38 | * Multiple values use a comma as a separator. 39 | * You can provide commas in your values by escaping them with a backslash. 40 | * ie: keyword = 'This has a comma here \, as part of this sentence' 41 | * To get a backslash in your values, escape it with another backslash. 42 | * Can handle multiple concurrent config files. cfg_read_config_file() 43 | * returns a config descriptor index you use in subsequent calls. 44 | * 45 | * Sample usage to print out sections, keywords and values of foobar.conf 46 | * assuming all keywords are of type scalar (otherwise, use cfg_get_type_str() 47 | * to find the type of a value so you can use the correct cfg_get_*() calls): 48 | * 49 | * char **sp, **kp, **sections, **keywords, *value, *error ; 50 | * int cfg_index ; 51 | * 52 | * cfg_index = cfg_read_config_file( "foobar.conf" ) ; 53 | * if ( cfg_error_msg( cfg_index )) { // check for errors 54 | * error = cfg_error_msg( cfg_index ) ; 55 | * fprintf( stderr, "Error: %s", error ) ; 56 | * exit(1) ; 57 | * } 58 | * 59 | * sections = cfg_get_sections( cfg_index ) ; 60 | * for ( sp = sections ; *sp ; sp++ ) { 61 | * printf( "\t%s\n", *sp ) ; 62 | * keywords = cfg_get_keywords( cfg_index, *sp ) ; 63 | * for ( kp = keywords ; *kp ; kp++ ) { 64 | * value = cfg_get_value( cfg_index, *sp, *kp ) ; 65 | * printf( "\t\t\%s = \'%s\'\n", *kp, value ) ; 66 | * } 67 | * } 68 | * 69 | * User callable functions begin with 'cfg_': 70 | * char cfg_get_type_str( int, char *, char * ) 71 | * int cfg_get_type( int, char *, char * ) 72 | * int cfg_read_config_file( char * ) 73 | * void cfg_show_configs() 74 | * int cfg_set_debug( int ) 75 | * void cfg_debug( const char *fmt, ... ) 76 | * char *cfg_get_value( int indx, char *section, char *keyword ) 77 | * char *cfg_get_hash_value( int, char *, char *, char * ) 78 | * char *cfg_get_filename( int ) 79 | * char *cfg_error_msg( int ) 80 | * char **cfg_get_sections( int ) 81 | * char **cfg_get_keywords( int, char * ) 82 | * char **cfg_get_values( int indx, char *section, char *keyword ) 83 | * char **cfg_get_hash_keys( int, char *, char * ) 84 | * 85 | * all other functions should not be called directly 86 | */ 87 | 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include "config.h" 93 | 94 | 95 | // globals 96 | 97 | static int debug_flag = 0 ; 98 | static int config_entry_num = 0 ; 99 | static struct config *config_head = (struct config *)NULL ; 100 | 101 | // internal functions 102 | static void set_err_msg( const char *whoami, int, const char *fmt, ... ) ; 103 | static struct config *make_config_entry() ; 104 | static struct hash *make_hash_value_entry( char *, char *) ; 105 | static struct section *make_section_entry( char * ) ; 106 | static int process_file( int, struct config *, char * ) ; 107 | static int store_values( int, short, char *, char *) ; 108 | static void trim_whitespace( char *p ) ; 109 | static int translate_escape_chars( char *, char *, int ) ; 110 | static int translate_back_escape_chars( char *, char * ) ; 111 | 112 | // user-callable internal functions 113 | void cfg_debug( const char *fmt, ... ) ; 114 | void cfg_show_configs() ; 115 | int cfg_set_debug( int ) ; 116 | int cfg_read_config_file( char * ) ; 117 | char **cfg_get_sections( int ) ; 118 | char **cfg_get_hash_keys( int, char *, char *) ; 119 | char *cfg_get_hash_value( int, char *, char *, char *) ; 120 | char **cfg_get_keywords( int, char * ) ; 121 | char *cfg_get_value( int, char *, char * ) ; 122 | char **cfg_get_values( int, char *, char * ) ; 123 | char *cfg_get_filename( int ) ; 124 | char *cfg_error_msg( int ) ; 125 | char *cfg_get_type_str( int, char *, char * ) ; 126 | int cfg_get_type( int, char *, char * ) ; 127 | 128 | // extern functions 129 | extern void *malloc( size_t ); 130 | extern void exit( int ); 131 | extern int strncmp( const char *, const char *, size_t ); 132 | extern size_t strlen( const char * ); 133 | 134 | 135 | /* 136 | * Turn debugging on or off. 137 | * Return the previous value. 138 | * 139 | * Inputs: 140 | * value: 0 (off) | !0 (on) 141 | * Returns: 142 | * previous (integer) value 143 | */ 144 | 145 | int 146 | cfg_set_debug( int value ) 147 | { 148 | int old_debug = debug_flag ; 149 | 150 | debug_flag = value ; 151 | return( old_debug ) ; 152 | } 153 | 154 | 155 | /* 156 | * check and get error message. 157 | * 158 | * Inputs: 159 | * config-index-number returned by cfg_read_config_file() 160 | * Returns: 161 | * string (char *) if there was an error 162 | * NULL if there is no error 163 | */ 164 | 165 | char * 166 | cfg_error_msg( int indx ) 167 | { 168 | struct config *ptr ; 169 | char *i_am = "cfg_error_msg()" ; 170 | 171 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 172 | if (( ptr->index == indx ) && ( ptr->error )) { 173 | cfg_debug( "%s: Found error msg for config index %d\n", 174 | i_am, indx ) ; 175 | return( ptr->error ) ; 176 | } 177 | } 178 | return( (char *)NULL ) ; 179 | } 180 | 181 | 182 | /* 183 | * get the config filename 184 | * 185 | * Inputs: 186 | * integer index number returned by cfg_read_config_file() 187 | * Returns: 188 | * filename (char *) 189 | */ 190 | 191 | char * 192 | cfg_get_filename( int indx ) 193 | { 194 | struct config *ptr ; 195 | char *i_am = "cfg_filename()" ; 196 | 197 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 198 | if (( ptr->index == indx ) && ( ptr->file )) { 199 | return( ptr->file ) ; 200 | } 201 | } 202 | return( (char *)NULL ) ; 203 | } 204 | 205 | 206 | 207 | 208 | /* 209 | * read a config file. 210 | * format is: 211 | * section-name1: 212 | * keyword1 = value1 213 | * keyword2 = value2 214 | * ... 215 | * continuation lines have a backslash as the last character on a line. 216 | * supports #include files to any depth via recursion. 217 | * 218 | * Inputs: 219 | * filename 220 | * Returns: 221 | * integer index number used in other cfg_() calls 222 | */ 223 | 224 | int 225 | cfg_read_config_file( char *file ) 226 | { 227 | char *p ; 228 | char *i_am = "cfg_read_config_file()" ; 229 | struct config *ptr ; 230 | int indx ; 231 | 232 | cfg_debug( "%s: file = %s\n", i_am, file ) ; 233 | 234 | // allocate a new config entry 235 | 236 | ptr = make_config_entry() ; 237 | ptr->index = config_entry_num ; 238 | indx = config_entry_num++ ; 239 | 240 | /* now copy the filename into the config struct */ 241 | if (( p = (char *)malloc( strlen( file )+1 )) == NULL ) { 242 | fprintf( stderr, "Can\'t malloc for config filename\n" ) ; 243 | exit(1) ; 244 | } 245 | strcpy( p, file ) ; 246 | ptr->file = p ; 247 | 248 | config_head = ptr ; 249 | 250 | // now finally read the file 251 | 252 | if ( process_file( indx, ptr, file )) { 253 | return( indx ) ; 254 | } 255 | 256 | return( indx ) ; 257 | } 258 | 259 | 260 | /* 261 | * process a config file. 262 | * NOT to be called by user. 263 | * 264 | * Inputs: 265 | * integer index number returned by cfg_read_config_file 266 | * filename of config file. Could be a #include'd file 267 | * 268 | * Returns: 269 | * 0 = ok, 1 = not ok 270 | */ 271 | 272 | static int 273 | process_file( int indx, struct config *ptr, char *file ) 274 | { 275 | char *i_am = "process_file()" ; 276 | char line[ LINE_LEN ], total_line[ BUFFER_SIZE ], *equals_p ; 277 | char input_line[ LINE_LEN ] ; 278 | char keyword[ KEYWORD_LEN ], value[ LINE_LEN ], type_str[ TYPE_LEN ] ; 279 | char *bp1, *bp2, *p, *f ; 280 | struct section *sp ; 281 | FILE *fp ; 282 | short whitespace_flag, continuation_flag, len, total_len ; 283 | short line_number, type ; 284 | 285 | if (( fp = fopen( file, "r" )) == NULL ) { 286 | set_err_msg( i_am, indx, "Can\'t open: %s\n", file ) ; 287 | return(1) ; // error 288 | } 289 | 290 | total_line[0] = '\0' ; // for dealing with continuation lines 291 | total_len = 0 ; 292 | line_number = 0 ; 293 | 294 | while ( fgets( input_line, BUFFER_SIZE, fp ) != NULL ) { 295 | 296 | // we need to handle escape characters 297 | if ( translate_escape_chars( input_line, line, BUFFER_SIZE )) { 298 | set_err_msg( i_am, indx, 299 | "Translated buffer too long on line number %d in %s\n", 300 | line_number, file ) ; 301 | return(1) ; // error 302 | } 303 | 304 | line_number++ ; 305 | if (( p = index( line, '\n' )) != NULL ) 306 | *p = '\0' ; 307 | 308 | // skip blank lines 309 | if ( line[0] == '\0' ) 310 | continue ; 311 | 312 | // skip lines that are just whitespace 313 | whitespace_flag = 1 ; 314 | for ( p=line ; *p ; p++ ) { 315 | if (( *p != SPACE ) && ( *p != TAB )) { 316 | whitespace_flag = 0 ; 317 | break ; 318 | } 319 | } 320 | if ( whitespace_flag == 1 ) { 321 | continue ; 322 | } 323 | 324 | // look for include files 325 | if ( strncmp( line, "#include", sizeof( "#include" )-1) == 0 ) { 326 | // skip over spaces and get filename 327 | p = &line[ sizeof( "#include" ) ] ; 328 | for ( ; ( *p == SPACE ) || ( *p == TAB ) ; p++ ) ; 329 | f = p ; 330 | cfg_debug( "%s: Got include in file %s (%s)\n", i_am, file, f ) ; 331 | 332 | if ( process_file( indx, ptr, f )) { 333 | return(1) ; // error 334 | } 335 | 336 | } else { 337 | // skip comments 338 | if ( line[0] == '#' ) 339 | continue ; 340 | 341 | // see if it is a new section 342 | if (( line[0] != SPACE ) && ( line[0] != TAB )) { 343 | cfg_debug( "%s: Got a section: %s\n", i_am, line ) ; 344 | // its a section 345 | if (( p = index( line, ':' )) != NULL ) { 346 | *p = '\0' ; 347 | } 348 | // see if section name already exists 349 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 350 | if ( strcmp( line, sp->name ) == 0 ) { 351 | ptr->current_section = sp->name ; 352 | break ; 353 | } 354 | } 355 | if ( sp == (struct section *)NULL ) { 356 | // allocate space for section structure 357 | sp = make_section_entry( line ) ; 358 | ptr->current_section = sp->name ; 359 | 360 | // link into config list 361 | sp->next = ptr->section_ptr ; 362 | ptr->section_ptr = sp ; 363 | } 364 | } else { 365 | // not in a section anymore. 366 | cfg_debug( "%s: Got a NON-section: %s\n", i_am, line ) ; 367 | 368 | // trim trailing whitespace 369 | for ( p=line ; *p ; p++ ) ; // get to end of line 370 | p-- ; // last character 371 | while (( p >= &line[0] ) && 372 | (( *p == SPACE ) || ( *p == TAB ))) { 373 | *p-- = '\0' ; // replace whitespace with NULL 374 | } 375 | 376 | /* 377 | * see if a continuation line, which ends in a '\'. 378 | * If so, strip it, and add all the lines together. 379 | * Keep any whitespace immediately before the '\', 380 | * but strip leading whitespace on each line. 381 | */ 382 | 383 | len = strlen( line ) ; 384 | if ( line[ len-1 ] == ESCAPE ) { 385 | cfg_debug( "%s: continuation line %s\n", i_am, line ) ; 386 | line[ len-1 ] = '\0' ; 387 | continuation_flag = 1 ; 388 | total_len += len + 1 ; 389 | } else { 390 | continuation_flag = 0 ; 391 | } 392 | 393 | /* 394 | * skip over leading whitespace so that 395 | * p is now pointing to start of real data 396 | */ 397 | for ( p=line ; (( *p == SPACE ) || ( *p == TAB )) ; p++ ) ; 398 | 399 | if ( continuation_flag ) { 400 | strcat( total_line, p ) ; 401 | continue ; // get the next line 402 | } else { 403 | /* 404 | * see if it is the last line of a continuation. 405 | * If so, take care of last line and initialize 406 | * everything again. 407 | * Check that we aren't going to overrun our buffer. 408 | */ 409 | 410 | if ( total_line[0] ) { 411 | // do a length check 412 | len = strlen(p) ; 413 | total_len += len + 1 ; 414 | if ( total_len >= BUFFER_SIZE ) { 415 | set_err_msg( 416 | i_am, indx, 417 | "Line too long on line number %d in %s\n", 418 | line_number, file ) ; 419 | return(1) ; // error 420 | } 421 | 422 | strcat( total_line, p ) ; 423 | strcpy( line, total_line ) ; 424 | 425 | total_line[0] = '\0' ; 426 | total_len = 0 ; 427 | continuation_flag = 0 ; 428 | } 429 | } 430 | 431 | /* 432 | * ok, we're done with the line continuation stuff. 433 | * now skip over leading whitespace again with the line 434 | * of data - which might be several lines added together 435 | */ 436 | 437 | for ( p=line ; (( *p == SPACE ) || ( *p == TAB )) ; p++ ) ; 438 | cfg_debug( "%s: FINAL line = \'%s\'\n", i_am, p ) ; 439 | 440 | // we now expect to have a line that is a keyword line. 441 | 442 | equals_p = index( p, '=' ) ; 443 | if ( ! equals_p ) { 444 | set_err_msg( i_am, indx, 445 | "Invalid keyword entry (missing =) on line number %d in %s\n", 446 | line_number, file ) ; 447 | return(1) ; // error 448 | } 449 | // separate the keyword and value(s) 450 | *equals_p++ = '\0' ; 451 | 452 | // get the keyword. We've already skipped over whitespace 453 | len = strlen( p ) ; 454 | if ((len + 1) > KEYWORD_LEN ) { 455 | set_err_msg( i_am, indx, 456 | "keyword (\'%s\') too long on line number %d in %s\n", 457 | p, line_number, file ) ; 458 | return(1) ; // error 459 | } 460 | strcpy( keyword, p ) ; 461 | trim_whitespace( keyword ) ; 462 | 463 | /* 464 | * We now have a keyword. 465 | * If it doesn't have a type hint, then default to TYPE_SCALAR 466 | */ 467 | type = TYPE_SCALAR ; // default 468 | if (( bp1 = index( keyword, '(' )) && 469 | ( bp2 = index( keyword, ')' )) && 470 | ( bp2 > bp1 )) { 471 | 472 | /* 473 | * separate out the type string from the 474 | * keyword. we'll need to trim the keyword string 475 | * again to get rid of whitespace between the keyword 476 | * and the type string 477 | */ 478 | *bp1 = '\0' ; // clobber leading ')' 479 | *bp2 = '\0' ; // clobber trailing ')' 480 | trim_whitespace( keyword ) ; 481 | 482 | // check that we have room to store the type string 483 | len = bp2 - bp1 - 1 ; 484 | if ((len + 1) > TYPE_LEN ) { 485 | set_err_msg( i_am, indx, 486 | "Type (%s) too long on line number %d in %s\n", 487 | bp1+1, line_number, file ) ; 488 | return(1) ; // error 489 | } 490 | strcpy( type_str, bp1+1 ) ; 491 | // make lower case 492 | for ( bp2=type_str ; *bp2 ; bp2++ ) { 493 | *bp2 = tolower( *bp2 ) ; 494 | } 495 | 496 | // see what type of 'type' value we have 497 | if ( ! strcmp( type_str, TYPE_SCALAR_STR )) { 498 | type = TYPE_SCALAR ; 499 | } else if ( ! strcmp( type_str, TYPE_ARRAY_STR )) { 500 | type = TYPE_ARRAY ; 501 | } else if ( ! strcmp( type_str, TYPE_HASH_STR )) { 502 | type = TYPE_HASH ; 503 | } else { 504 | set_err_msg( i_am, indx, 505 | "Unknown Type (%s) on line number %d in %s\n", 506 | type_str, line_number, file ) ; 507 | return(1) ; // error 508 | } 509 | } 510 | 511 | // now get the value. make sure there is something there 512 | if ( ! *equals_p ) { 513 | set_err_msg( i_am, indx, 514 | "Invalid keyword entry on line number %d in %s\n", 515 | line_number, file ) ; 516 | return(1) ; // error 517 | } 518 | for ( p=equals_p ; ((*p == SPACE) || (*p == TAB)) ; p++ ) ; 519 | strcpy( value, p ) ; 520 | 521 | // now store it. 522 | if ( store_values( indx, type, keyword, p )) 523 | return(1) ; 524 | } 525 | } 526 | } 527 | fclose( fp ) ; 528 | return(0) ; // ok 529 | } 530 | 531 | 532 | /* 533 | * store keyword/value(s) 534 | * This function is overloaded so it it handles scalars, arrays 535 | * and hashes. Which inouyt argument it uses depends on the 536 | * value 'type' 537 | * 538 | * Inputs: 539 | * integer index number returned by cfg_read_config_file() 540 | * type type of value (TYPE_SCALAR, TYPE_ARRAY, TYPE_HASH) 541 | * keyword (char *) 542 | * values (char **) 543 | * value (char *) 544 | * Returns: 545 | * integer 0=ok, 1=not-ok 546 | */ 547 | 548 | static int 549 | store_values( int indx, short type, char *key, char *val ) 550 | { 551 | struct config *ptr ; 552 | struct section *sp ; 553 | struct keyword *kp ; 554 | struct hash *hash_p ; 555 | short found, len, last, value_count ; 556 | char *i_am, *section, *p, *p2, *p3, *next_p, *real_val_p ; 557 | char **array_p, real_value[ BUFFER_SIZE ] ; 558 | char *hash_name_p, *hash_value_p ; 559 | 560 | i_am = "store_values()" ; 561 | 562 | /* 563 | * find the correct section. 564 | * We have to go through all this hassle because we can't just use 565 | * a global to track the current session since there can be multiple 566 | * concurrent configs being processed. We know which config we want 567 | * by the index (Arg 1) 568 | * XXX is this true? can probably use a global... -rj 569 | */ 570 | 571 | found = 0 ; 572 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 573 | if ( ptr->index == indx ) { 574 | found++ ; 575 | break ; 576 | } 577 | } 578 | if ( ! found ) { 579 | set_err_msg( i_am, indx, 580 | "Could not find config entry for keyword=\'%s\', cfg indx=%d\n", 581 | key, indx ) ; 582 | return(1) ; 583 | } 584 | section = ptr->current_section ; 585 | cfg_debug( "%s: WANT section = \'%s\'\n", i_am, section ) ; 586 | 587 | // find the right section structure 588 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 589 | if ( ! strcmp( sp->name, section )) { 590 | cfg_debug( "%s: FOUND section = \'%s\'\n", i_am, section ) ; 591 | break ; 592 | } 593 | } 594 | 595 | if ( sp == (struct section *)NULL ) { 596 | set_err_msg( i_am, indx, 597 | "Can\'t find section struct for kywd=\'%s\', cfg indx=%d\n", 598 | key, indx ) ; 599 | return(1) ; 600 | } 601 | 602 | /* 603 | * now store the keyword. See if it already exists. 604 | * If it does, we'll end up overwriting the old value. 605 | */ 606 | 607 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 608 | if ( ! strcmp( kp->name, key )) { 609 | cfg_debug( "%s: FOUND key = \'%s\'\n", i_am, key ) ; 610 | break ; 611 | } 612 | } 613 | if ( kp == ( struct keyword *)NULL ) { 614 | cfg_debug( "%s: did NOT find key = \'%s\'\n", i_am, key ) ; 615 | 616 | kp = (struct keyword *)malloc( sizeof( struct keyword )) ; 617 | if ( kp == NULL ) { 618 | fprintf( stderr, "Can\'t malloc for keyword structure\n" ) ; 619 | exit(1) ; 620 | } 621 | kp->next = sp->keyword_ptr ; 622 | sp->keyword_ptr = kp ; 623 | 624 | // get space for the name 625 | len = strlen( key ) ; 626 | if (( p = (char *)malloc( len + 1 )) == NULL ) { 627 | fprintf( stderr, "Can\'t malloc for keyword name\n" ) ; 628 | exit(1) ; 629 | } 630 | strcpy( p, key ) ; 631 | kp->name = p ; 632 | kp->value = (char *)NULL ; 633 | kp->values = (char **)NULL ; 634 | kp->hash_value = (struct hash *)NULL ; 635 | } 636 | 637 | trim_whitespace( val ) ; 638 | 639 | // finally... store the value(s) 640 | 641 | if ( type == TYPE_SCALAR ) { 642 | // free up the old value if it exists 643 | if ( kp->value ) 644 | free( kp->value ) ; 645 | 646 | /* 647 | * see if value quoted and if so, strip. 648 | * Assume they may have used balanced single 649 | * OR double quotes but NOT both. ie: '"foobar "' 650 | */ 651 | 652 | real_val_p = val ; 653 | len = strlen( val ) ; 654 | if (( val[0] == SINGLE_QUOTE ) && 655 | ( val[ len-1 ] == SINGLE_QUOTE )) { 656 | val[len-1] = '\0' ; 657 | real_val_p++ ; 658 | } 659 | if (( val[0] == DOUBLE_QUOTE ) && 660 | ( val[ len-1 ] == DOUBLE_QUOTE )) { 661 | val[len-1] = '\0' ; 662 | real_val_p++ ; 663 | } 664 | 665 | // translate back escape and separator characters 666 | translate_back_escape_chars( real_val_p, real_value ) ; 667 | 668 | // get space for the new value 669 | len = strlen( real_value ) ; 670 | if (( p = (char *)malloc( len + 1 )) == NULL ) { 671 | fprintf( stderr, "Can\'t malloc for value name\n" ) ; 672 | exit(1) ; 673 | } 674 | strcpy( p, real_value ) ; 675 | kp->value = p ; 676 | kp->values = (char **)NULL ; 677 | kp->hash_value = (struct hash *)NULL ; 678 | kp->type = TYPE_SCALAR ; 679 | 680 | } else if ( type == TYPE_ARRAY ) { 681 | p = val ; 682 | last = 0 ; 683 | /* 684 | * need a count of how many entries we'll have so we can allocate 685 | * an array of pointers the right size. It will be terminated 686 | * with a NULL entry 687 | */ 688 | 689 | value_count = 1 ; // we have at least 1 value 690 | for ( p2=p ; *p2 ; p2++ ) { 691 | if ( *p2 == SEPARATOR ) 692 | value_count++ ; 693 | } 694 | 695 | // need to allocate space for array. Don't forget NULL termination (+1) 696 | array_p = (char **)malloc( sizeof( char *) * (value_count + 1)) ; 697 | if ( array_p == (char **)NULL ) { 698 | fprintf( stderr, "Can\'t malloc for section names\n" ) ; 699 | exit(1) ; 700 | } 701 | kp->values = array_p ; // point to our new (empty) array 702 | kp->value = (char *)NULL ; 703 | kp->hash_value = (struct hash *)NULL ; 704 | 705 | while ( *p ) { 706 | if ( p2 = index( p, SEPARATOR )) { 707 | *p2 = '\0' ; 708 | next_p = p2 + 1 ; 709 | while ( *next_p && ( *next_p == SPACE ) || ( *next_p == TAB )) 710 | next_p++ ; 711 | } else { 712 | last++ ; 713 | } 714 | 715 | trim_whitespace( p ) ; 716 | 717 | // handle quoted values 718 | real_val_p = p ; 719 | len = strlen( p ) ; 720 | if (( p[0] == SINGLE_QUOTE ) && 721 | ( p[ len-1 ] == SINGLE_QUOTE )) { 722 | p[len-1] = '\0' ; 723 | real_val_p++ ; 724 | } 725 | if (( p[0] == DOUBLE_QUOTE ) && 726 | ( p[ len-1 ] == DOUBLE_QUOTE )) { 727 | p[len-1] = '\0' ; 728 | real_val_p++ ; 729 | } 730 | 731 | // translate back escape and separator characters 732 | translate_back_escape_chars( real_val_p, real_value ) ; 733 | 734 | // get space for the new value and store value 735 | len = strlen( real_value ) ; 736 | if (( p3 = (char *)malloc( len + 1 )) == NULL ) { 737 | fprintf( stderr, "Can\'t malloc for value name\n" ) ; 738 | exit(1) ; 739 | } 740 | strcpy( p3, real_value ) ; 741 | 742 | *array_p = p3 ; // store pointer in our values array 743 | array_p++ ; // prepare to store next entry 744 | 745 | if ( last ) break ; // get out if last value 746 | 747 | p = next_p ; // point to start of next value 748 | } 749 | *array_p = (char *)NULL ; // terminate array 750 | kp->type = TYPE_ARRAY ; 751 | 752 | } else if ( type == TYPE_HASH ) { 753 | last = 0 ; 754 | p = val ; 755 | while ( *p ) { 756 | if ( p2 = index( p, SEPARATOR )) { 757 | *p2 = '\0' ; 758 | next_p = p2 + 1 ; 759 | while ( *next_p && ( *next_p == SPACE ) || ( *next_p == TAB )) 760 | next_p++ ; 761 | } else { 762 | last++ ; 763 | } 764 | 765 | // we should have a 'name = value' string pointed to by p 766 | 767 | if (( p2 = index( p, '=' )) == NULL ) { 768 | set_err_msg( i_am, indx, 769 | "did not get name = value for hash with cfg indx=%d\n", 770 | key, indx ) ; 771 | return(1) ; 772 | } 773 | // separate out the name 774 | *p2 = '\0' ; 775 | hash_name_p = p ; 776 | trim_whitespace( hash_name_p ) ; 777 | 778 | // separate out the value. skip over whitespace 779 | for ( p2++ ; ( *p2 == SPACE ) || ( *p2 == TAB ) ; p2++ ) ; 780 | hash_value_p = p2 ; 781 | trim_whitespace( hash_value_p ) ; 782 | 783 | // handle quoted values 784 | len = strlen( hash_value_p ) ; 785 | if (( hash_value_p[0] == SINGLE_QUOTE ) && 786 | ( hash_value_p[ len-1 ] == SINGLE_QUOTE )) { 787 | hash_value_p[len-1] = '\0' ; 788 | hash_value_p++ ; 789 | } 790 | if (( hash_value_p[0] == DOUBLE_QUOTE ) && 791 | ( hash_value_p[ len-1 ] == DOUBLE_QUOTE )) { 792 | hash_value_p[len-1] = '\0' ; 793 | hash_value_p++ ; 794 | } 795 | 796 | // translate back escape and separator characters 797 | translate_back_escape_chars( hash_value_p, real_value ) ; 798 | 799 | // get space for the new name and value and store 800 | hash_p = make_hash_value_entry( hash_name_p, real_value ) ; 801 | 802 | // link into keyword structure 803 | hash_p->next = kp->hash_value ; 804 | kp->hash_value = hash_p ; 805 | 806 | if ( last ) break ; // get out if last value 807 | 808 | p = next_p ; // point to start of next value 809 | } 810 | 811 | } else { 812 | set_err_msg( i_am, indx, 813 | "Invalid type (%d) for keyword=\'%s\', cfg indx=%d\n", 814 | type, key, indx ) ; 815 | return(1) ; 816 | } 817 | kp->type = type ; 818 | 819 | return(0) ; 820 | } 821 | 822 | /* 823 | * Show config entries. Used for debugging. 824 | * Writes to stdout 825 | * 826 | * Inputs: 827 | * None 828 | * Returns: 829 | * nothing (void). 830 | */ 831 | 832 | void 833 | cfg_show_configs() 834 | { 835 | struct config *ptr ; 836 | struct section *sp ; 837 | struct keyword *kp ; 838 | char *err, *file, *type_str, **vp ; 839 | short count, type ; 840 | 841 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 842 | err = ptr->error ; 843 | if ( err == (char *)NULL ) err = "" ; 844 | 845 | file = ptr->file ; 846 | if ( file == (char *)NULL ) file = "" ; 847 | 848 | printf( "%-20s %s\n", "Filename:", file ) ; 849 | printf( "%-20s %d\n", "Index:", ptr->index ) ; 850 | printf( "%-20s %s\n", "Error Msg:", err ) ; 851 | printf( "%-20s\n", "Data:" ) ; 852 | 853 | // now for each section 854 | 855 | count = 0 ; 856 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 857 | count++ ; 858 | printf( " %s\n", sp->name ) ; 859 | 860 | // now print the keyword/value(s) 861 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 862 | 863 | // convert the type to a string 864 | type = kp->type ; 865 | switch ( type ) { 866 | case TYPE_SCALAR: 867 | type_str = "scalar" ; 868 | break ; 869 | case TYPE_ARRAY: 870 | type_str = "array" ; 871 | break ; 872 | case TYPE_HASH: 873 | type_str = "hash" ; 874 | break ; 875 | default: 876 | type_str = "unknown" ; 877 | break ; 878 | } 879 | 880 | type = kp->type ; 881 | if ( type == TYPE_SCALAR ) { 882 | printf( " %-20s \'%s\'\n", kp->name, kp->value ) ; 883 | } else if ( type == TYPE_ARRAY ) { 884 | if ( kp->values ) { 885 | int first = 1 ; 886 | for ( vp = kp->values ; *vp ; vp++ ) { 887 | if ( first ) { 888 | printf( " %-20s \'%s\'\n", kp->name, *vp ) ; 889 | } else { 890 | printf( " %-20s \'%s\'\n", "", *vp ) ; 891 | } 892 | first = 0 ; 893 | } 894 | } 895 | } else if ( type == TYPE_HASH ) { 896 | if ( kp->hash_value ) { 897 | int first = 1 ; 898 | struct hash *hp ; 899 | for ( hp = kp->hash_value ; hp ; hp = hp->next ) { 900 | if ( first ) { 901 | printf( " %-20s %s = \'%s\'\n", 902 | kp->name, hp->name, hp->value ) ; 903 | } else { 904 | printf( " %-20s %s = \'%s\'\n", 905 | "", hp->name, hp->value ) ; 906 | } 907 | first = 0 ; 908 | } 909 | } 910 | } 911 | } 912 | } 913 | if ( count == 0 ) { 914 | printf( " \n" ) ; 915 | } 916 | printf( "\n" ) ; 917 | } 918 | } 919 | 920 | 921 | /* 922 | * debug output. 923 | * 924 | * Inputs: 925 | * formatting string 926 | * zero or more arguments referred to by format string. 927 | * Returns: 928 | * Nothing (void) 929 | */ 930 | 931 | void 932 | cfg_debug( const char *fmt, ... ) 933 | { 934 | va_list args ; 935 | char str[ 512 ], *p ; 936 | char *prefix = "Debug: " ; 937 | 938 | if ( debug_flag == 0 ) 939 | return ; 940 | 941 | va_start( args, fmt ) ; 942 | 943 | p = str ; 944 | sprintf( p, "%s", prefix ) ; 945 | p += strlen( prefix ) ; 946 | vsprintf( p, fmt, args ) ; 947 | fprintf( stderr, "%s", str ) ; 948 | 949 | va_end( args ) ; 950 | } 951 | 952 | 953 | /* 954 | * error message 955 | * 956 | * Inputs: 957 | * identifier string printed if debug_flag turned on 958 | * integer index number returned by cfg_read_config_file() 959 | * formatting string 960 | * zero or more arguments referred to by format string. 961 | * Returns: 962 | * Nothing (void) 963 | */ 964 | 965 | static void 966 | set_err_msg( const char *whoami, int indx, const char *fmt, ... ) 967 | { 968 | va_list args ; 969 | char error[ 512 ], *p ; 970 | struct config *ptr ; 971 | int found ; 972 | char *i_am = "set_err_msg()" ; 973 | 974 | va_start( args, fmt ) ; 975 | 976 | p = error ; 977 | *p = '\0' ; 978 | 979 | if ( debug_flag ) { 980 | sprintf( p, "%s: ", i_am ) ; 981 | p += strlen( i_am ) + 2 ; 982 | sprintf( p, "%s: ", whoami ) ; 983 | p += strlen( whoami ) + 2 ; 984 | } 985 | vsprintf( p, fmt, args ) ; 986 | 987 | va_end( args ) ; 988 | 989 | // see if we have a config entry built yet. 990 | found = 0 ; 991 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 992 | if ( ptr->index == indx ) { 993 | found++ ; 994 | break ; 995 | } 996 | } 997 | if ( found == 0 ) { 998 | // this shouldn't happen... 999 | cfg_debug( "%s: Need to build config entry for index %d\n", 1000 | i_am, indx ) ; 1001 | 1002 | ptr = make_config_entry() ; 1003 | ptr->index = indx ; 1004 | 1005 | config_head = ptr ; 1006 | } 1007 | 1008 | /* now copy the error message ptr into the config struct */ 1009 | if (( p = (char *)malloc( strlen( error )+1 )) == NULL ) { 1010 | fprintf( stderr, "Can\'t malloc for config error msg\n" ) ; 1011 | exit(1) ; 1012 | } 1013 | strcpy( p, error ) ; 1014 | ptr->error = p ; 1015 | } 1016 | 1017 | 1018 | 1019 | /* 1020 | * make a hash value entry 1021 | * 1022 | * Inputs: 1023 | * name (string) 1024 | * value (string) 1025 | * Returns: 1026 | * pointer to new hash value structure. 1027 | */ 1028 | 1029 | static struct hash * 1030 | make_hash_value_entry( char *name, char *value ) 1031 | { 1032 | struct hash *ptr ; 1033 | char *p ; 1034 | 1035 | ptr = (struct hash *)malloc( sizeof( struct hash )) ; 1036 | if ( ptr == NULL ) { 1037 | fprintf( stderr, "Can\'t malloc for hash structure\n" ) ; 1038 | exit(1) ; 1039 | } 1040 | 1041 | // allocate space for name and store 1042 | if (( p = (char *)malloc( strlen( name )+1 )) == NULL ) { 1043 | fprintf( stderr, "Can\'t malloc for hash name\n" ) ; 1044 | exit(1) ; 1045 | } 1046 | strcpy( p, name ) ; 1047 | ptr->name = p ; 1048 | 1049 | // allocate space for value and store 1050 | if (( p = (char *)malloc( strlen( value )+1 )) == NULL ) { 1051 | fprintf( stderr, "Can\'t malloc for hash value\n" ) ; 1052 | exit(1) ; 1053 | } 1054 | strcpy( p, value ) ; 1055 | 1056 | ptr->value = p ; 1057 | ptr->next = (struct hash *)NULL ; 1058 | 1059 | return( ptr ) ; 1060 | } 1061 | 1062 | 1063 | /* 1064 | * make a config entry 1065 | * 1066 | * Inputs: 1067 | * None 1068 | * Returns: 1069 | * pointer to new config structure. 1070 | * Globals used: 1071 | * config_head 1072 | */ 1073 | 1074 | static struct config * 1075 | make_config_entry() 1076 | { 1077 | struct config *ptr ; 1078 | 1079 | ptr = (struct config *)malloc( sizeof( struct config )) ; 1080 | if ( ptr == NULL ) { 1081 | fprintf( stderr, "Can\'t malloc for config structure\n" ) ; 1082 | exit(1) ; 1083 | } 1084 | ptr->next = config_head ; 1085 | ptr->error = '\0' ; 1086 | ptr->file = '\0' ; 1087 | ptr->index = -1 ; 1088 | ptr->current_section = (char *)NULL ; 1089 | ptr->section_ptr = (struct section *)NULL ; 1090 | ptr->section_names = (char **)NULL ; 1091 | 1092 | return( ptr ) ; 1093 | } 1094 | 1095 | 1096 | /* 1097 | * make a section entry 1098 | * 1099 | * Inputs: 1100 | * name (char *) of section 1101 | * Returns: 1102 | * pointer to new section structure. 1103 | */ 1104 | 1105 | static struct section * 1106 | make_section_entry( char *name ) 1107 | { 1108 | struct section *sp ; 1109 | char *p ; 1110 | 1111 | sp = (struct section *)malloc( sizeof( struct section )) ; 1112 | if ( sp == NULL ) { 1113 | fprintf( stderr, "Can\'t malloc for section structure\n" ) ; 1114 | exit(1) ; 1115 | } 1116 | 1117 | sp->keyword_ptr = (struct keyword *)NULL ; 1118 | sp->keyword_names = (char **)NULL ; 1119 | 1120 | // allocate space for name and store 1121 | if (( p = (char *)malloc( strlen( name )+1 )) == NULL ) { 1122 | fprintf( stderr, "Can\'t malloc for section name\n" ) ; 1123 | exit(1) ; 1124 | } 1125 | strcpy( p, name ) ; 1126 | sp->name = p ; 1127 | 1128 | return( sp ) ; 1129 | } 1130 | 1131 | 1132 | /* 1133 | * get the type of a value(s) and return a string 1134 | * 1135 | * Inputs: 1136 | * integer index number returned by cfg_read_config_file() 1137 | * section (char *) 1138 | * keyword (char *) 1139 | * Returns: 1140 | * string 1141 | * Usage: 1142 | * type = cfg_get_type_str( cfg_index, section, keyword ) ; 1143 | */ 1144 | 1145 | char * 1146 | cfg_get_type_str( int indx, char *section, char *keyword ) 1147 | { 1148 | struct config *ptr ; 1149 | struct section *sp ; 1150 | struct keyword *kp ; 1151 | 1152 | short type ; 1153 | 1154 | type = cfg_get_type( indx, section, keyword ) ; 1155 | 1156 | switch( type ) { 1157 | case TYPE_SCALAR: 1158 | return( TYPE_SCALAR_STR ) ; 1159 | case TYPE_ARRAY : 1160 | return( TYPE_ARRAY_STR ) ; 1161 | case TYPE_HASH: 1162 | return( TYPE_HASH_STR ) ; 1163 | default: 1164 | return( TYPE_UNKNOWN_STR ) ; 1165 | } 1166 | } 1167 | 1168 | 1169 | /* 1170 | * get the type of a value(s) 1171 | * 1172 | * Inputs: 1173 | * integer index number returned by cfg_read_config_file() 1174 | * section (char *) 1175 | * keyword (char *) 1176 | * Returns: 1177 | * type (integer > 0) 1178 | * 0 = unknown 1179 | * Usage: 1180 | * type = cfg_get_type( cfg_index, section, keyword ) ; 1181 | */ 1182 | 1183 | int 1184 | cfg_get_type( int indx, char *section, char *keyword ) 1185 | { 1186 | struct config *ptr ; 1187 | struct section *sp ; 1188 | struct keyword *kp ; 1189 | 1190 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1191 | if ( ptr->index == indx ) { 1192 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1193 | if ( ! strcmp( sp->name, section )) { 1194 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1195 | if ( ! strcmp( kp->name, keyword )) { 1196 | return( kp->type ) ; 1197 | break ; 1198 | } 1199 | } 1200 | break ; 1201 | } 1202 | } 1203 | break ; 1204 | } 1205 | } 1206 | return(0) ; 1207 | } 1208 | 1209 | 1210 | 1211 | /* 1212 | * get the hash keys of a hash key value 1213 | * 1214 | * Inputs: 1215 | * integer index number returned by cfg_read_config_file() 1216 | * section (char *) 1217 | * keyword (char *) 1218 | * Returns: 1219 | * pointer to array of strings (char **). 1220 | * Usage: 1221 | * char **keys = cfg_get_hash_keys( cfg_index, section, keyword ) ; 1222 | * char **p ; 1223 | * for ( p = keys ; *p ; p++ ) { 1224 | * printf( "key = %s\n", *p ) ; 1225 | * } 1226 | */ 1227 | 1228 | char ** 1229 | cfg_get_hash_keys( int indx, char *section, char *keyword ) 1230 | { 1231 | struct config *ptr ; 1232 | struct section *sp ; 1233 | struct keyword *kp ; 1234 | struct hash *hp ; 1235 | short found, count ; 1236 | char **p, **array_p, *name ; 1237 | 1238 | found = 0 ; 1239 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1240 | if ( ptr->index == indx ) { 1241 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1242 | if ( ! strcmp( sp->name, section )) { 1243 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1244 | if ( ! strcmp( kp->name, keyword )) { 1245 | found++ ; 1246 | break ; 1247 | } 1248 | } 1249 | break ; 1250 | } 1251 | } 1252 | break ; 1253 | } 1254 | } 1255 | 1256 | /* 1257 | * This will happen if we have no list to return. 1258 | * Need to create a dummy empty list to return 1259 | */ 1260 | if ( found == 0 ) { 1261 | // need to allocate space for dummy empty list of strings 1262 | p = (char **)malloc( sizeof( char *)) ; 1263 | if ( p == (char **)NULL ) { 1264 | fprintf( stderr, "Can\'t malloc for empty hash values list\n" ) ; 1265 | exit(1) ; 1266 | } 1267 | *p = (char *)NULL ; // empty list 1268 | return(p) ; 1269 | } 1270 | 1271 | /* 1272 | * need a count of how many entries we'll have so we can allocate 1273 | * an array of pointers the right size. It will be terminated 1274 | * with a NULL entry 1275 | */ 1276 | count = 0 ; 1277 | for ( hp = kp->hash_value ; hp ; hp = hp->next ) { 1278 | count++ ; 1279 | } 1280 | 1281 | // need to allocate space for array. Don't forget NULL termination (+1) 1282 | array_p = (char **)malloc( sizeof( char *) * (count + 1)) ; 1283 | if ( array_p == (char **)NULL ) { 1284 | fprintf( stderr, "Can\'t malloc for section names\n" ) ; 1285 | exit(1) ; 1286 | } 1287 | 1288 | // now fill the memory with addresses of section names 1289 | p = array_p ; 1290 | for ( hp = kp->hash_value ; hp ; hp = hp->next ) { 1291 | name = hp->name ; 1292 | *p = name ; 1293 | p++ ; 1294 | } 1295 | *p = (char *)NULL ; // terminate it 1296 | 1297 | return( array_p ) ; 1298 | } 1299 | 1300 | 1301 | 1302 | /* 1303 | * get a value 1304 | * 1305 | * Inputs: 1306 | * integer index number returned by cfg_read_config_file() 1307 | * section (char *) 1308 | * keyword (char *) 1309 | * Returns: 1310 | * value (char *) 1311 | * Usage: 1312 | * char **sp, **kp, **sections, **keywords, *value, *file ; 1313 | * file = cfg_get_filename( cfg_index ) ; 1314 | * printf( "%s:\n", file ) ; 1315 | * sections = cfg_get_sections( cfg_index ) ; 1316 | * for ( sp = sections ; *sp ; sp++ ) { 1317 | * printf( "\t%s\n", *sp ) ; 1318 | * keywords = cfg_get_keywords( cfg_index, *sp ) ; 1319 | * for ( kp = keywords ; *kp ; kp++ ) { 1320 | * value = cfg_get_value( cfg_index, *sp, *kp ) ; 1321 | * printf( "\t\t\%s = \'%s\'\n", *kp, value ) ; 1322 | * } 1323 | * } 1324 | */ 1325 | 1326 | char * 1327 | cfg_get_value( int indx, char *section, char *keyword ) 1328 | { 1329 | struct config *ptr ; 1330 | struct section *sp ; 1331 | struct keyword *kp ; 1332 | 1333 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1334 | if ( ptr->index == indx ) { 1335 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1336 | if ( ! strcmp( sp->name, section )) { 1337 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1338 | if ( ! strcmp( kp->name, keyword )) { 1339 | return( kp->value ) ; 1340 | break ; 1341 | } 1342 | } 1343 | break ; 1344 | } 1345 | } 1346 | break ; 1347 | } 1348 | } 1349 | return( (char *)NULL ) ; 1350 | } 1351 | 1352 | 1353 | /* 1354 | * get values 1355 | * 1356 | * Inputs: 1357 | * integer index number returned by cfg_read_config_file() 1358 | * section (char *) 1359 | * keyword (char *) 1360 | * Returns: 1361 | * values (char **) 1362 | * Usage: 1363 | * int cfg_index ; 1364 | * char **values, **vp, *section, *keyword ; 1365 | * values = cfg_get_values( cfg_index, section, keyword ) ; 1366 | * if ( values == (char **)NULL ) { 1367 | * printf( "No values found for section %s, keyword %s\n", 1368 | section, keyword ) ; 1369 | * } else { 1370 | * for ( vp = values ; *vp ; vp++ ) { 1371 | * printf( "\'%s\'\n ", *vp ) ; 1372 | * } 1373 | * } 1374 | */ 1375 | 1376 | char ** 1377 | cfg_get_values( int indx, char *section, char *keyword ) 1378 | { 1379 | struct config *ptr ; 1380 | struct section *sp ; 1381 | struct keyword *kp ; 1382 | 1383 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1384 | if ( ptr->index == indx ) { 1385 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1386 | if ( ! strcmp( sp->name, section )) { 1387 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1388 | if ( ! strcmp( kp->name, keyword )) { 1389 | return( kp->values ) ; 1390 | break ; 1391 | } 1392 | } 1393 | break ; 1394 | } 1395 | } 1396 | break ; 1397 | } 1398 | } 1399 | return( (char **)NULL ) ; 1400 | } 1401 | 1402 | /* 1403 | * return or create (if missing) an array of keyword names for 1404 | * the section. The first time we are called, we crawl through 1405 | * the linked list to get the keyword names, but we build a array 1406 | * of strings to return on subsequent calls. 1407 | * 1408 | * Inputs: 1409 | * integer index number returned by cfg_read_config_file() 1410 | * section (char *) 1411 | * Returns: 1412 | * pointer to array of strings (char **). 1413 | * Usage: 1414 | * char **keywords = cfg_get_keywords( cfg_index, section ) ; 1415 | * char **p ; 1416 | * for ( p = keywords ; *p ; p++ ) { 1417 | * printf( "keyword = %s\n", *p ) ; 1418 | * } 1419 | */ 1420 | 1421 | char ** 1422 | cfg_get_keywords( int indx, char* section ) 1423 | { 1424 | struct config *ptr ; 1425 | struct section *sp ; 1426 | struct keyword *kp ; 1427 | char *name, **p, *i_am ; 1428 | short num_keywords = 0, found = 0 ; 1429 | 1430 | i_am = "cfg_get_keywords()" ; 1431 | found = 0 ; 1432 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1433 | if ( ptr->index == indx ) { 1434 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1435 | if ( ! strcmp( sp->name, section )) { 1436 | // ok ,we have the right section in the right config 1437 | found++ ; 1438 | break ; 1439 | } 1440 | } 1441 | } 1442 | } 1443 | 1444 | /* 1445 | * This will happen if we have no list to return. 1446 | * Need to create a dummy empty list to return 1447 | */ 1448 | if ( found == 0 ) { 1449 | // need to allocate space for dummy empty list of strings 1450 | p = (char **)malloc( sizeof( char *)) ; 1451 | if ( p == (char **)NULL ) { 1452 | fprintf( stderr, "Can\'t malloc for empty keyword list\n" ) ; 1453 | exit(1) ; 1454 | } 1455 | *p = (char *)NULL ; // empty list 1456 | return(p) ; 1457 | } 1458 | 1459 | if ( sp->keyword_names != (char **)NULL ) { 1460 | cfg_debug( "%s: returning keyword names ptr for section %s cfg indx %d\n", 1461 | i_am, section, indx ) ; 1462 | return( sp->keyword_names ) ; 1463 | } 1464 | /* 1465 | * We don't already have the array built. Do it now. 1466 | * First find out how many keywords there are. 1467 | */ 1468 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1469 | num_keywords++ ; 1470 | } 1471 | cfg_debug( "%s: building keyword names (%d) struct for section %s cfg indx %d\n", 1472 | i_am, num_keywords, section, indx ) ; 1473 | 1474 | // need to allocate space for array. Don't forget NULL termination 1475 | p = (char **)malloc( sizeof( char *) * (num_keywords + 1)) ; 1476 | if ( p == (char **)NULL ) { 1477 | fprintf( stderr, "Can\'t malloc for section names\n" ) ; 1478 | exit(1) ; 1479 | } 1480 | sp->keyword_names = p ; 1481 | 1482 | // now fill the memory with addresses of section names 1483 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1484 | name = kp->name ; 1485 | *p = name ; 1486 | p++ ; 1487 | } 1488 | *p = (char *)NULL ; // terminate it 1489 | 1490 | return( sp->keyword_names ) ; 1491 | } 1492 | 1493 | 1494 | 1495 | /* 1496 | * return or create (if missing) an array of section names for 1497 | * the config file. The first time we are called, we crawl through 1498 | * the linked list to get the section names, but we build a array 1499 | * of strings to return on subsequent calls. 1500 | * 1501 | * Inputs: 1502 | * integer index number returned by cfg_read_config_file() 1503 | * Returns: 1504 | * pointer to array of strings (char **). 1505 | * Usage: 1506 | * char **sections = cfg_get_sections( cfg_index ) ; 1507 | * char **p ; 1508 | * for ( p = sections ; *p ; p++ ) { 1509 | * printf( "section = %s\n", *p ) ; 1510 | * } 1511 | */ 1512 | 1513 | char ** 1514 | cfg_get_sections( int indx ) 1515 | { 1516 | struct section *sp ; 1517 | struct config *ptr ; 1518 | char *name, **p, *i_am ; 1519 | int num_sections = 0 ; 1520 | 1521 | i_am = "cfg_get_sections()" ; 1522 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1523 | if ( ptr->index == indx ) { 1524 | if ( ptr->section_names != (char **)NULL ) { 1525 | cfg_debug( "%s: returning section names pointer for cfg indx %d\n", 1526 | i_am, indx ) ; 1527 | return( ptr->section_names ) ; 1528 | } 1529 | 1530 | /* 1531 | * We don't already have the array built. Do it now. 1532 | * First find out how many section names there are. 1533 | */ 1534 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1535 | num_sections++ ; 1536 | } 1537 | cfg_debug( "%s: building section names (%d) struct for cfg indx %d\n", 1538 | i_am, num_sections, indx ) ; 1539 | 1540 | // need to allocate space for array. Don't forget NULL termination 1541 | p = (char **)malloc( sizeof( char *) * (num_sections + 1) ) ; 1542 | if ( p == (char **)NULL ) { 1543 | fprintf( stderr, "Can\'t malloc for section names\n" ) ; 1544 | exit(1) ; 1545 | } 1546 | ptr->section_names = p ; 1547 | 1548 | // now fill the memory with addresses of section names 1549 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1550 | name = sp->name ; 1551 | *p = name ; 1552 | p++ ; 1553 | } 1554 | *p = (char *)NULL ; // terminate it 1555 | 1556 | break ; 1557 | } 1558 | } 1559 | 1560 | /* 1561 | * This will happen if we have no list or a bogus index number 1562 | * was passed to us. Need to create a dummy empty list to return 1563 | */ 1564 | if ( ptr == NULL ) { 1565 | // need to allocate space for dummy empty list of strings 1566 | p = (char **)malloc( sizeof( char *)) ; 1567 | if ( p == (char **)NULL ) { 1568 | fprintf( stderr, "Can\'t malloc for empty section list\n" ) ; 1569 | exit(1) ; 1570 | } 1571 | *p = (char *)NULL ; // empty list 1572 | return(p) ; 1573 | } 1574 | 1575 | return( ptr->section_names ) ; 1576 | } 1577 | 1578 | 1579 | /* 1580 | * get a hash value 1581 | * 1582 | * Inputs: 1583 | * integer index number returned by cfg_read_config_file() 1584 | * section (char *) 1585 | * keyword (char *) 1586 | * key (char *) 1587 | * Returns: 1588 | * value (string) 1589 | */ 1590 | 1591 | char * 1592 | cfg_get_hash_value( int indx, char *section, char *keyword, char *key ) 1593 | { 1594 | struct config *ptr ; 1595 | struct section *sp ; 1596 | struct keyword *kp ; 1597 | struct hash *hp ; 1598 | 1599 | for ( ptr = config_head ; ptr ; ptr = ptr->next ) { 1600 | if ( ptr->index == indx ) { 1601 | for ( sp=ptr->section_ptr ; sp ; sp=sp->next ) { 1602 | if ( ! strcmp( sp->name, section )) { 1603 | for ( kp=sp->keyword_ptr ; kp ; kp=kp->next ) { 1604 | if ( ! strcmp( kp->name, keyword )) { 1605 | for ( hp = kp->hash_value ; hp ; hp = hp->next ) { 1606 | if ( ! strcmp( key, hp->name )) { 1607 | return( hp->value ) ; 1608 | } 1609 | } 1610 | } 1611 | } 1612 | break ; 1613 | } 1614 | } 1615 | break ; 1616 | } 1617 | } 1618 | 1619 | return( (char *)NULL ) ; // not found 1620 | } 1621 | 1622 | 1623 | 1624 | /* 1625 | * trim the whitespace off a string 1626 | * 1627 | * Inputs: 1628 | * string (char *) 1629 | * Returns: 1630 | * nothing (void) 1631 | */ 1632 | 1633 | static void 1634 | trim_whitespace( char *p ) 1635 | { 1636 | char *orig_p ; 1637 | 1638 | orig_p = p ; 1639 | // trim trailing whitespace 1640 | for ( ; *p ; p++ ) ; // get to end of line 1641 | p-- ; // last character 1642 | while (( p >= orig_p ) && (( *p == SPACE ) || ( *p == TAB ))) { 1643 | *p-- = '\0' ; // replace whitespace with NULL 1644 | } 1645 | } 1646 | 1647 | 1648 | /* 1649 | * We need to disquise our escape and separator characters to 1650 | * allow us those characters as legitimate characters in our data. 1651 | * This makes parsing alot easier. 1652 | * It is obviously defeatable, but ur config file writers are not 1653 | * out to break their own configs/software. We just have to assume 1654 | * the strings we temporarily translate them to wil not be found 1655 | * in the config file 1656 | * 1657 | * Inputs: 1658 | * input string 1659 | * output string 1660 | * size of output buffer 1661 | * Returns: 1662 | * 0: ok 1663 | * 1: output buffer exceeded 1664 | */ 1665 | 1666 | static int 1667 | translate_escape_chars( char *input, char *output, int len ) 1668 | { 1669 | char *p, *p2, *i_am ; 1670 | short e_len, s_len, i_len ; 1671 | 1672 | e_len = strlen( ESCAPE_STR ); 1673 | s_len = strlen( SEPARATOR_STR ) ; 1674 | i_len = strlen( input ) ; 1675 | 1676 | p = input ; 1677 | p2 = output ; 1678 | while ( *p ) { 1679 | // disquise a escaped escape char 1680 | if (( *p == ESCAPE ) && ( *(p+1) == ESCAPE )) { 1681 | if ((i_len + e_len) > (len-1)) return(1) ; 1682 | 1683 | strcpy( p2, ESCAPE_STR ) ; 1684 | p2 += e_len ; 1685 | p += 2 ; 1686 | continue ; 1687 | } 1688 | 1689 | // disquise a separator char 1690 | if (( *p == ESCAPE ) && ( *(p+1) == SEPARATOR )) { 1691 | if ((i_len + s_len) > (len-1)) return(1) ; 1692 | 1693 | strcpy( p2, SEPARATOR_STR ) ; 1694 | p2 += s_len ; 1695 | p += 2 ; 1696 | continue ; 1697 | } 1698 | 1699 | *p2++ = *p++ ; 1700 | } 1701 | *p2 = '\0' ; // terminate output 1702 | 1703 | return(0) ; 1704 | } 1705 | 1706 | /* 1707 | * translate back our escape and separator characters 1708 | * 1709 | * Inputs: 1710 | * input string 1711 | * output string 1712 | * Returns: 1713 | * 0: ok 1714 | */ 1715 | 1716 | static int 1717 | translate_back_escape_chars( char *input, char *output ) 1718 | { 1719 | char *p, *p2 ; 1720 | short e_len, s_len ; 1721 | 1722 | e_len = strlen( ESCAPE_STR ); 1723 | s_len = strlen( SEPARATOR_STR ) ; 1724 | 1725 | p = input ; 1726 | p2 = output ; 1727 | while ( *p ) { 1728 | if ( strncmp( p, SEPARATOR_STR, s_len ) == 0 ) { 1729 | *p2++ = SEPARATOR ; 1730 | p += s_len ; 1731 | continue ; 1732 | } 1733 | 1734 | if ( strncmp( p, ESCAPE_STR, e_len ) == 0 ) { 1735 | *p2++ = ESCAPE ; 1736 | p += e_len ; 1737 | continue ; 1738 | } 1739 | 1740 | *p2++ = *p++ ; 1741 | } 1742 | *p2 = '\0' ; // terminate output 1743 | 1744 | return(0) ; 1745 | } 1746 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2019 rlandjon 2 | * 3 | * This file is part of udpproxy. 4 | * 5 | * udpproxy is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * udpproxy is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with ijkPlayer; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | //type of data 20 | 21 | #define TYPE_UNKNOWN 0 22 | #define TYPE_SCALAR 1 23 | #define TYPE_ARRAY 2 24 | #define TYPE_HASH 3 25 | 26 | #define TYPE_UNKNOWN_STR "unknown" 27 | #define TYPE_SCALAR_STR "scalar" 28 | #define TYPE_ARRAY_STR "array" 29 | #define TYPE_HASH_STR "hash" 30 | 31 | // buffer sizes 32 | #define TYPE_LEN 10 33 | #define KEYWORD_LEN 64 34 | #define LINE_LEN 256 // single line input 35 | #define BUFFER_SIZE 2048 // includes all continuation lines 36 | 37 | #define SEPARATOR ',' 38 | #define SPACE ' ' 39 | #define TAB '\t' 40 | #define DOUBLE_QUOTE '\"' 41 | #define SINGLE_QUOTE '\'' 42 | #define ESCAPE '\\' 43 | 44 | #define ESCAPE_STR "EsCaPe" 45 | #define SEPARATOR_STR "CoMmA" 46 | 47 | 48 | struct hash { 49 | char *name ; 50 | char *value ; 51 | struct hash *next ; 52 | }; 53 | 54 | struct keyword { 55 | char *name ; 56 | char *value ; 57 | char **values ; 58 | struct hash *hash_value ; 59 | short type ; 60 | struct keyword *next ; 61 | }; 62 | 63 | struct section { 64 | char *name ; 65 | char **keyword_names ; // short-cut list of names. 66 | struct keyword *keyword_ptr ; 67 | struct section *next ; 68 | } ; 69 | 70 | struct config { 71 | int index ; 72 | char *error ; 73 | char *file ; 74 | char *current_section ; 75 | char **section_names ; // short-cut list of names. 76 | struct section *section_ptr ; 77 | struct config *next ; 78 | } ; 79 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2019 rlandjon 2 | * 3 | * This file is part of udpproxy. 4 | * 5 | * udpproxy is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * udpproxy is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with ijkPlayer; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | #ifndef __LIST_H 20 | #define __LIST_H 21 | 22 | /* This file is from Linux Kernel (include/linux/list.h) 23 | * and modified by simply removing hardware prefetching of list items. 24 | * Here by copyright, credits attributed to wherever they belong. 25 | * Kulesh Shanmugasundaram (kulesh [squiggly] isis.poly.edu) 26 | */ 27 | 28 | /* 29 | * Simple doubly linked list implementation. 30 | * 31 | * Some of the internal functions (“__xxx”) are useful when 32 | * manipulating whole lists rather than single entries, as 33 | * sometimes we already know the next/prev entries and we can 34 | * generate better code by using them directly rather than 35 | * using the generic single-entry routines. 36 | */ 37 | 38 | struct list_head { 39 | struct list_head *next, *prev; 40 | }; 41 | 42 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 43 | 44 | #define LIST_HEAD(name) \ 45 | struct list_head name = LIST_HEAD_INIT(name) 46 | 47 | static inline void INIT_LIST_HEAD(struct list_head *list) 48 | { 49 | list->next = list; 50 | list->prev = list; 51 | } 52 | /* 53 | * Insert a new entry between two known consecutive entries. 54 | * 55 | * This is only for internal list manipulation where we know 56 | * the prev/next entries already! 57 | */ 58 | static inline void __list_add(struct list_head *new, 59 | struct list_head *prev, 60 | struct list_head *next) 61 | { 62 | next->prev = new; 63 | new->next = next; 64 | new->prev = prev; 65 | prev->next = new; 66 | } 67 | 68 | /** 69 | * list_add – add a new entry 70 | * @new: new entry to be added 71 | * @head: list head to add it after 72 | * 73 | * Insert a new entry after the specified head. 74 | * This is good for implementing stacks. 75 | */ 76 | static inline void list_add(struct list_head *new, struct list_head *head) 77 | { 78 | __list_add(new, head, head->next); 79 | } 80 | 81 | /** 82 | * list_add_tail – add a new entry 83 | * @new: new entry to be added 84 | * @head: list head to add it before 85 | * 86 | * Insert a new entry before the specified head. 87 | * This is useful for implementing queues. 88 | */ 89 | static inline void list_add_tail(struct list_head *new, struct list_head *head) 90 | { 91 | __list_add(new, head->prev, head); 92 | } 93 | 94 | /* 95 | * Delete a list entry by making the prev/next entries 96 | * point to each other. 97 | * 98 | * This is only for internal list manipulation where we know 99 | * the prev/next entries already! 100 | */ 101 | static inline void __list_del(struct list_head *prev, struct list_head *next) 102 | { 103 | next->prev = prev; 104 | prev->next = next; 105 | } 106 | 107 | /** 108 | * list_del – deletes entry from list. 109 | * @entry: the element to delete from the list. 110 | * Note: list_empty on entry does not return true after this, the entry is in an undefined state. 111 | */ 112 | static inline void list_del(struct list_head *entry) 113 | { 114 | __list_del(entry->prev, entry->next); 115 | entry->next = (void *) 0; 116 | entry->prev = (void *) 0; 117 | } 118 | 119 | /** 120 | * list_del_init – deletes entry from list and reinitialize it. 121 | * @entry: the element to delete from the list. 122 | */ 123 | static inline void list_del_init(struct list_head *entry) 124 | { 125 | __list_del(entry->prev, entry->next); 126 | INIT_LIST_HEAD(entry); 127 | } 128 | 129 | /** 130 | * list_move – delete from one list and add as another’s head 131 | * @list: the entry to move 132 | * @head: the head that will precede our entry 133 | */ 134 | static inline void list_move(struct list_head *list, struct list_head *head) 135 | { 136 | __list_del(list->prev, list->next); 137 | list_add(list, head); 138 | } 139 | 140 | /** 141 | * list_move_tail – delete from one list and add as another’s tail 142 | * @list: the entry to move 143 | * @head: the head that will follow our entry 144 | */ 145 | static inline void list_move_tail(struct list_head *list, 146 | struct list_head *head) 147 | { 148 | __list_del(list->prev, list->next); 149 | list_add_tail(list, head); 150 | } 151 | 152 | /** 153 | * list_empty – tests whether a list is empty 154 | * @head: the list to test. 155 | */ 156 | static inline int list_empty(struct list_head *head) 157 | { 158 | return head->next == head; 159 | } 160 | 161 | static inline void __list_splice(struct list_head *list, 162 | struct list_head *head) 163 | { 164 | struct list_head *first = list->next; 165 | struct list_head *last = list->prev; 166 | struct list_head *at = head->next; 167 | 168 | first->prev = head; 169 | head->next = first; 170 | 171 | last->next = at; 172 | at->prev = last; 173 | } 174 | 175 | /** 176 | * list_splice – join two lists 177 | * @list: the new list to add. 178 | * @head: the place to add it in the first list. 179 | */ 180 | static inline void list_splice(struct list_head *list, struct list_head *head) 181 | { 182 | if (!list_empty(list)) 183 | __list_splice(list, head); 184 | } 185 | 186 | /** 187 | * list_splice_init – join two lists and reinitialise the emptied list. 188 | * @list: the new list to add. 189 | * @head: the place to add it in the first list. 190 | * 191 | * The list at @list is reinitialised 192 | */ 193 | static inline void list_splice_init(struct list_head *list, 194 | struct list_head *head) 195 | { 196 | if (!list_empty(list)) { 197 | __list_splice(list, head); 198 | INIT_LIST_HEAD(list); 199 | } 200 | } 201 | 202 | /** 203 | * list_entry – get the struct for this entry 204 | * @ptr: the &struct list_head pointer. 205 | * @type: the type of the struct this is embedded in. 206 | * @member: the name of the list_struct within the struct. 207 | */ 208 | #define list_entry(ptr, type, member) \ 209 | ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 210 | 211 | /** 212 | * list_for_each - iterate over a list 213 | * @pos: the &struct list_head to use as a loop counter. 214 | * @head: the head for your list. 215 | */ 216 | #define list_for_each(pos, head) \ 217 | for (pos = (head)->next; pos != (head); \ 218 | pos = pos->next) 219 | /** 220 | * list_for_each_prev - iterate over a list backwards 221 | * @pos: the &struct list_head to use as a loop counter. 222 | * @head: the head for your list. 223 | */ 224 | #define list_for_each_prev(pos, head) \ 225 | for (pos = (head)->prev; pos != (head); \ 226 | pos = pos->prev) 227 | 228 | 229 | /** 230 | * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry 231 | * @pos: the &struct list_head to use as a loop cursor. 232 | * @n: another &struct list_head to use as temporary storage 233 | * @head: the head for your list. 234 | */ 235 | #define list_for_each_prev_safe(pos, n, head) \ 236 | for (pos = (head)->prev, n = pos->prev; \ 237 | pos != (head); \ 238 | pos = n, n = pos->prev) 239 | 240 | 241 | /** 242 | * list_for_each_safe - iterate over a list safe against removal of list entry 243 | * @pos: the &struct list_head to use as a loop counter. 244 | * @n: another &struct list_head to use as temporary storage 245 | * @head: the head for your list. 246 | */ 247 | #define list_for_each_safe(pos, n, head) \ 248 | for (pos = (head)->next, n = pos->next; pos != (head);\ 249 | pos = n, n = pos->next) 250 | 251 | 252 | /** 253 | * list_for_each_entry - iterate over list of given type 254 | * @pos: the type * to use as a loop counter. 255 | * @head: the head for your list. 256 | * @member: the name of the list_struct within the struct. 257 | */ 258 | #define list_for_each_entry(pos, head, member) \ 259 | for (pos = list_entry((head)->next,typeof(*pos), member); \ 260 | &pos->member != (head); \ 261 | pos = list_entry(pos->member.next, typeof(*pos), member)) 262 | 263 | /** 264 | * list_for_each_entry_safe – iterate over list of given type safe against removal of list entry 265 | * @pos: the type * to use as a loop counter. 266 | * @n: another type * to use as temporary storage 267 | * @head: the head for your list. 268 | * @member: the name of the list_struct within the struct. 269 | */ 270 | #define list_for_each_entry_safe(pos, n, head, member) \ 271 | for (pos = list_entry((head)->next, typeof(*pos), member), \ 272 | n = list_entry(pos->member.next, typeof(*pos), member); \ 273 | &pos->member != (head); \ 274 | pos = n, n = list_entry(n->member.next, typeof(*n), member)) 275 | 276 | #endif 277 | -------------------------------------------------------------------------------- /logger.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2019 rlandjon 2 | * 3 | * This file is part of udpproxy. 4 | * 5 | * udpproxy is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * udpproxy is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with ijkPlayer; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "logger.h" 27 | /* ------------------------------------------------------------------------- */ 28 | #define LOG_LEVEL_DEBUG 0 29 | #define LOG_LEVEL_USER 1 30 | #define LOG_LEVEL_INFO 2 31 | #define LOG_LEVEL_WARNING 3 32 | #define LOG_LEVEL_ERROR 4 33 | #define LOG_LEVEL_FATAL 5 34 | #define LOG_LEVEL_MAX 6 35 | static const char *log_level_str[] = { 36 | "debug", "user", "info", "warning", "error", "fatal", 37 | }; 38 | /* fields of logging timestamp */ 39 | struct log_tm { 40 | unsigned short tm_year; 41 | unsigned short tm_mon; 42 | unsigned short tm_mday; 43 | unsigned short tm_hour; 44 | } __attribute__((packed)); 45 | /* log filename may be one of the following forms: 46 | * - YYYY-MM-DD. 47 | * - YYYY-MM-DD_hh. 48 | * - YYYY-MM-DD_hh:mm:ss. 49 | * 50 | * strlen("YYYY-MM-DD_hh:mm:ss.") == 20 51 | * max(strlen(log_level_str[])) == strlen("warning") == 7 52 | * 53 | * PATH_BUFLEN should be >= 28 (including trailing '\0') */ 54 | #define PATH_BUFLEN 1024 55 | #define PATH_PREFIX_BUFLEN (PATH_BUFLEN \ 56 | - sizeof(unsigned long) \ 57 | - sizeof(void*) - sizeof(void*)) 58 | /* variables that will be initialized in logger_init() */ 59 | struct logger_var { 60 | unsigned long max_file_size; 61 | int (*rotate_trigger)(const struct tm *current, const struct log_tm *old, 62 | unsigned long max_file_size, unsigned long filesize); 63 | void (*get_filename)(char *buf, int level, const struct tm *ts); 64 | char path_prefix[PATH_PREFIX_BUFLEN]; 65 | } __attribute__((packed)); 66 | #define MAX_LOG_LEN (4096 \ 67 | - sizeof(FILE*) \ 68 | - sizeof(struct log_tm) \ 69 | - sizeof(unsigned int) \ 70 | - sizeof(unsigned long) \ 71 | - sizeof(pthread_mutex_t)) 72 | /* sizeof(logger_info) == 4096 */ 73 | struct logger_info { 74 | FILE *fp; 75 | struct log_tm ts; /* timestamp of last write */ 76 | unsigned int level; 77 | unsigned long filesize; 78 | pthread_mutex_t lock; 79 | char buf[MAX_LOG_LEN]; 80 | } __attribute__((packed)); 81 | struct logger_impl { 82 | struct logger_var var; 83 | struct logger_info o_o[LOG_LEVEL_MAX]; 84 | }; 85 | 86 | /* ------------------------------------------------------------------------- */ 87 | static void __new_log_level_file(struct logger_info *logger, 88 | struct logger_var *var, 89 | const struct tm *ts) { 90 | int len; 91 | char path[PATH_BUFLEN]; 92 | logger->filesize = 0; 93 | if (logger->fp && logger->fp != stdout && logger->fp != stderr) fclose(logger->fp); 94 | len = sprintf(path, "%s", var->path_prefix); 95 | var->get_filename(path + len, logger->level, ts); 96 | logger->fp = fopen(path, "a+"); 97 | if (!logger->fp) { 98 | fprintf(stderr, "cannot create/open file `%s', " 99 | "log content of level [%s] is redirected to %s.\n", 100 | path, log_level_str[logger->level], 101 | (logger->level <= LOG_LEVEL_INFO) ? "stdout" : "stderr"); 102 | logger->fp = (logger->level <= LOG_LEVEL_INFO) ? stdout : stderr; 103 | } 104 | } 105 | /* time format: YYYY-MM-DD hh:mm:ss.uuuuuu 106 | * size >= 27 */ 107 | static inline void current_datetime(char *buf, int size, struct tm *tp) { 108 | int len; 109 | struct timeval tv; 110 | gettimeofday(&tv, NULL); 111 | localtime_r(&tv.tv_sec, tp); 112 | len = strftime(buf, size, "%F_%T", tp); 113 | sprintf(buf + len, ".%06ld", tv.tv_usec); 114 | } 115 | #include 116 | #include 117 | static void generic_logger(struct logger_info *logger, 118 | struct logger_var *var, 119 | const char *filename, int line, /* extra info */ 120 | const char *fmt, va_list args) { 121 | struct tm tm; 122 | char timestr[32]; 123 | pthread_mutex_lock(&logger->lock); 124 | current_datetime(timestr, 32, &tm); 125 | if (var->rotate_trigger(&tm, &logger->ts, var->max_file_size, 126 | logger->filesize)) { 127 | __new_log_level_file(logger, var, &tm); 128 | logger->ts.tm_hour = tm.tm_hour; 129 | logger->ts.tm_mday = tm.tm_mday; 130 | logger->ts.tm_mon = tm.tm_mon; 131 | logger->ts.tm_year = tm.tm_year; 132 | } 133 | vsnprintf(logger->buf, MAX_LOG_LEN, fmt, args); 134 | logger->filesize += fprintf(logger->fp, "[%s] [%s] [%lu] [%s:%u]\t%s\n", 135 | log_level_str[logger->level], timestr, 136 | syscall(__NR_gettid), filename, line, 137 | logger->buf); 138 | fflush(logger->fp); /* flush cache to disk */ 139 | pthread_mutex_unlock(&logger->lock); 140 | } 141 | static inline void __vlogger(struct logger *l, int level, 142 | const char *filename, int line, 143 | const char *fmt, va_list args) { 144 | struct logger_impl *handler = l->handler; 145 | generic_logger(&handler->o_o[level], &handler->var, 146 | filename, line, fmt, args); 147 | } 148 | #ifndef NDEBUG 149 | void __logger_debug(struct logger *l, const char *filename, int line, 150 | const char *fmt, ...) { 151 | va_list args; 152 | va_start(args, fmt); 153 | __vlogger(l, LOG_LEVEL_DEBUG, filename, line, fmt, args); 154 | va_end(args); 155 | } 156 | #endif 157 | void __logger_user(struct logger *l, const char *filename, int line, 158 | const char *fmt, ...) { 159 | va_list args; 160 | va_start(args, fmt); 161 | __vlogger(l, LOG_LEVEL_USER, filename, line, fmt, args); 162 | va_end(args); 163 | } 164 | void __logger_info(struct logger *l, const char *filename, int line, 165 | const char *fmt, ...) { 166 | va_list args; 167 | va_start(args, fmt); 168 | __vlogger(l, LOG_LEVEL_INFO, filename, line, fmt, args); 169 | va_end(args); 170 | } 171 | void __logger_warning(struct logger *l, const char *filename, int line, 172 | const char *fmt, ...) { 173 | va_list args; 174 | va_start(args, fmt); 175 | __vlogger(l, LOG_LEVEL_WARNING, filename, line, fmt, args); 176 | va_end(args); 177 | } 178 | void __logger_error(struct logger *l, const char *filename, int line, 179 | const char *fmt, ...) { 180 | va_list args; 181 | va_start(args, fmt); 182 | __vlogger(l, LOG_LEVEL_ERROR, filename, line, fmt, args); 183 | va_end(args); 184 | } 185 | void __logger_fatal(struct logger *l, const char *filename, int line, 186 | const char *fmt, ...) { 187 | va_list args; 188 | va_start(args, fmt); 189 | __vlogger(l, LOG_LEVEL_FATAL, filename, line, fmt, args); 190 | va_end(args); 191 | } 192 | /* ------------------------------------------------------------------------- */ 193 | /* all possible combinations of rotating conditions */ 194 | static inline int trigger_size(const struct tm *current, 195 | const struct log_tm *old, 196 | unsigned long max_file_size, 197 | unsigned long filesize) { 198 | return (filesize >= max_file_size); 199 | } 200 | static inline void filename_size(char *buf, int level, const struct tm *ts) { 201 | sprintf(buf, "%04d-%02d-%02d_%02d:%02d:%02d.%s", 202 | ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, 203 | ts->tm_hour, ts->tm_min, ts->tm_sec, 204 | log_level_str[level]); 205 | } 206 | static inline int trigger_hour(const struct tm *current, 207 | const struct log_tm *old, 208 | unsigned long max_file_size, 209 | unsigned long filesize) { 210 | return (!((old->tm_hour == current->tm_hour) && 211 | (old->tm_mday == current->tm_mday) && 212 | (old->tm_mon == current->tm_mon) && 213 | (old->tm_year == current->tm_year))); 214 | } 215 | static inline void filename_hour(char *buf, int level, const struct tm *ts) { 216 | sprintf(buf, "%04d-%02d-%02d_%02d.%s", 217 | ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, 218 | log_level_str[level]); 219 | } 220 | static inline int trigger_day(const struct tm *current, 221 | const struct log_tm *old, 222 | unsigned long max_file_size, 223 | unsigned long filesize) { 224 | return (!((old->tm_mday == current->tm_mday) && 225 | (old->tm_mon == current->tm_mon) && 226 | (old->tm_year == current->tm_year))); 227 | } 228 | static inline void filename_day(char *buf, int level, const struct tm *ts) { 229 | sprintf(buf, "%04d-%02d-%02d.%s", 230 | ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, 231 | log_level_str[level]); 232 | } 233 | static inline int trigger_size_hour(const struct tm *current, 234 | const struct log_tm *old, 235 | unsigned long max_file_size, 236 | unsigned long filesize) { 237 | return (trigger_size(current, old, max_file_size, filesize) || 238 | trigger_hour(current, old, max_file_size, filesize)); 239 | } 240 | static inline void filename_size_hour(char *buf, int level, const struct tm *ts) { 241 | filename_size(buf, level, ts); 242 | } 243 | static inline int trigger_size_day(const struct tm *current, 244 | const struct log_tm *old, 245 | unsigned long max_file_size, 246 | unsigned long filesize) { 247 | return (trigger_size(current, old, max_file_size, filesize) || 248 | trigger_day(current, old, max_file_size, filesize)); 249 | } 250 | static inline void filename_size_day(char *buf, int level, const struct tm *ts) { 251 | filename_size(buf, level, ts); 252 | } 253 | static inline int trigger_none(const struct tm *current, 254 | const struct log_tm *old, 255 | unsigned long max_file_size, 256 | unsigned long filesize) { 257 | return 0; 258 | } 259 | static inline void filename_none(char *buf, int level, const struct tm *ts) 260 | { } 261 | static void logger_var_set_func(struct logger_var *var, unsigned flags) { 262 | switch (flags & LOGGER_ROTATE_FLAG_MASK) { 263 | case LOGGER_ROTATE_BY_SIZE: 264 | var->rotate_trigger = trigger_size; 265 | var->get_filename = filename_size; 266 | break; 267 | case LOGGER_ROTATE_PER_HOUR: 268 | case LOGGER_ROTATE_PER_HOUR | LOGGER_ROTATE_PER_DAY: 269 | var->rotate_trigger = trigger_hour; 270 | var->get_filename = filename_hour; 271 | break; 272 | case LOGGER_ROTATE_PER_DAY: 273 | var->rotate_trigger = trigger_day; 274 | var->get_filename = filename_day; 275 | break; 276 | case LOGGER_ROTATE_BY_SIZE | LOGGER_ROTATE_PER_HOUR: 277 | case LOGGER_ROTATE_BY_SIZE | LOGGER_ROTATE_PER_HOUR | LOGGER_ROTATE_PER_DAY: 278 | var->rotate_trigger = trigger_size_hour; 279 | var->get_filename = filename_size_hour; 280 | break; 281 | case LOGGER_ROTATE_BY_SIZE | LOGGER_ROTATE_PER_DAY: 282 | var->rotate_trigger = trigger_size_day; 283 | var->get_filename = filename_size_day; 284 | break; 285 | default: 286 | var->rotate_trigger = trigger_day; 287 | var->get_filename = filename_day; 288 | } 289 | } 290 | static inline void logger_var_set_path_prefix(struct logger_var *var, 291 | const char *prefix, 292 | unsigned int max_prefix_len) { 293 | unsigned int path_prefix_len = strlen(prefix); 294 | if (path_prefix_len > max_prefix_len) { 295 | fprintf(stderr, "prefix len is greater than %d, truncated.\n", 296 | max_prefix_len); 297 | path_prefix_len = max_prefix_len; 298 | } 299 | memcpy(var->path_prefix, prefix, path_prefix_len); 300 | var->path_prefix[path_prefix_len] = '\0'; 301 | } 302 | static inline void logger_var_init(struct logger_var *var, const char *prefix, 303 | unsigned int flags, 304 | unsigned int max_megabytes) { 305 | if (prefix) { 306 | logger_var_set_func(var, flags); 307 | var->max_file_size = max_megabytes << 20; 308 | logger_var_set_path_prefix(var, prefix, 309 | PATH_PREFIX_BUFLEN > PATH_BUFLEN - 27 ? 310 | PATH_BUFLEN - 27 - 1 : PATH_PREFIX_BUFLEN - 1); 311 | } else { 312 | var->rotate_trigger = trigger_none; 313 | var->get_filename = filename_none; 314 | } 315 | } 316 | /* ------------------------------------------------------------------------- */ 317 | int logger_init(struct logger *l, const char *prefix, 318 | unsigned int flags, unsigned int max_megabytes) { 319 | unsigned int i; 320 | struct logger_impl *handler; 321 | handler = malloc(sizeof(struct logger_impl)); 322 | if (!handler) return -1; 323 | memset(handler, 0, sizeof(struct logger_impl)); 324 | for (i = 0; i < LOG_LEVEL_MAX; ++i) { 325 | struct logger_info *logger = &handler->o_o[i]; 326 | if (i <= LOG_LEVEL_INFO) logger->fp = stdout; 327 | else logger->fp = stderr; 328 | logger->level = i; 329 | pthread_mutex_init(&logger->lock, NULL); 330 | } 331 | logger_var_init(&handler->var, prefix, flags, max_megabytes); 332 | l->handler = handler; 333 | return 0; 334 | } 335 | void logger_destroy(struct logger *l) { 336 | unsigned int i; 337 | struct logger_impl *handler = l->handler; 338 | if (!handler) return; 339 | for (i = 0; i < LOG_LEVEL_MAX; ++i) { 340 | struct logger_info *logger = &handler->o_o[i]; 341 | if (logger->fp && logger->fp != stdout && logger->fp != stderr) fclose(logger->fp); 342 | pthread_mutex_destroy(&logger->lock); 343 | } 344 | free(handler); 345 | l->handler = NULL; 346 | } 347 | /* ------------------------------------------------------------------------- */ 348 | /* singleton logger implementation */ 349 | static struct logger o_o; 350 | int log_init(const char *prefix, unsigned int flags, 351 | unsigned int max_megabytes) { 352 | return logger_init(&o_o, prefix, flags, max_megabytes); 353 | } 354 | void log_destroy(void) { 355 | logger_destroy(&o_o); 356 | } 357 | #ifndef NDEBUG 358 | void __log_debug(const char *filename, int line, const char *fmt, ...) { 359 | va_list args; 360 | va_start(args, fmt); 361 | __vlogger(&o_o, LOG_LEVEL_DEBUG, filename, line, fmt, args); 362 | va_end(args); 363 | } 364 | #endif 365 | void __log_user(const char *filename, int line, const char *fmt, ...) { 366 | va_list args; 367 | va_start(args, fmt); 368 | __vlogger(&o_o, LOG_LEVEL_USER, filename, line, fmt, args); 369 | va_end(args); 370 | } 371 | void __log_info(const char *filename, int line, const char *fmt, ...) { 372 | va_list args; 373 | va_start(args, fmt); 374 | __vlogger(&o_o, LOG_LEVEL_INFO, filename, line, fmt, args); 375 | va_end(args); 376 | } 377 | void __log_warning(const char *filename, int line, const char *fmt, ...) { 378 | va_list args; 379 | va_start(args, fmt); 380 | __vlogger(&o_o, LOG_LEVEL_WARNING, filename, line, fmt, args); 381 | va_end(args); 382 | } 383 | void __log_error(const char *filename, int line, const char *fmt, ...) { 384 | va_list args; 385 | va_start(args, fmt); 386 | __vlogger(&o_o, LOG_LEVEL_ERROR, filename, line, fmt, args); 387 | va_end(args); 388 | } 389 | void __log_fatal(const char *filename, int line, const char *fmt, ...) { 390 | va_list args; 391 | va_start(args, fmt); 392 | __vlogger(&o_o, LOG_LEVEL_FATAL, filename, line, fmt, args); 393 | va_end(args); 394 | } 395 | 396 | 397 | -------------------------------------------------------------------------------- /logger.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2019 rlandjon 2 | * 3 | * This file is part of udpproxy. 4 | * 5 | * udpproxy is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * udpproxy is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with ijkPlayer; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | #ifndef __GLOBAL_LOGGER_H__ 20 | #define __GLOBAL_LOGGER_H__ 21 | 22 | #ifndef __LOGGER_H__ 23 | #define __LOGGER_H__ 24 | struct logger { 25 | struct logger_impl *handler; 26 | }; 27 | /* flag definitions for logger_init() */ 28 | #define LOGGER_ROTATE_BY_SIZE 0x1 29 | #define LOGGER_ROTATE_PER_HOUR 0x2 30 | #define LOGGER_ROTATE_PER_DAY 0x4 31 | #define LOGGER_ROTATE_FLAG_MASK 0x7 32 | #define LOGGER_ROTATE_DEFAULT LOGGER_ROTATE_PER_DAY 33 | 34 | int logger_init(struct logger *, const char *prefix, 35 | unsigned int flags, unsigned int max_megabytes); 36 | void logger_destroy(struct logger *); 37 | #ifdef NDEBUG 38 | #define logger_debug(lp, fmt, ...) 39 | #else 40 | #define logger_debug(lp, fmt, ...) __logger_debug(lp, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 41 | #endif 42 | #define logger_user(lp, fmt, ...) __logger_user(lp, __FILE__, __LINE__, fmt, ##__VA_ARGS__) /* user specific logs */ 43 | #define logger_info(lp, fmt, ...) __logger_info(lp, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 44 | #define logger_warning(lp, fmt, ...) __logger_warning(lp, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 45 | #define logger_error(lp, fmt, ...) __logger_error(lp, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 46 | #define logger_fatal(lp, fmt, ...) __logger_fatal(lp, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 47 | /* ------------------------------------------------------------------------- */ 48 | #ifndef NDEBUG 49 | void __logger_debug(struct logger *, const char *filename, int line, 50 | const char *fmt, ...); 51 | #endif 52 | void __logger_user(struct logger *, const char *filename, int line, 53 | const char *fmt, ...); 54 | void __logger_info(struct logger *, const char *filename, int line, 55 | const char *fmt, ...); 56 | void __logger_warning(struct logger *, const char *filename, int line, 57 | const char *fmt, ...); 58 | void __logger_error(struct logger *, const char *filename, int line, 59 | const char *fmt, ...); 60 | void __logger_fatal(struct logger *, const char *filename, int line, 61 | const char *fmt, ...); 62 | #endif 63 | 64 | void log_destroy(void); 65 | #ifdef NDEBUG 66 | #define log_debug(fmt, ...) 67 | #else 68 | #define log_debug(fmt, ...) __log_debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 69 | #endif 70 | #define log_user(fmt, ...) __log_user(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 71 | #define log_info(fmt, ...) __log_info(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 72 | #define log_warning(fmt, ...) __log_warning(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 73 | #define log_error(fmt, ...) __log_error(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 74 | #define log_fatal(fmt, ...) __log_fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 75 | /* ------------------------------------------------------------------------- */ 76 | #ifndef NDEBUG 77 | void __log_debug(const char *filename, int line, const char *fmt, ...); 78 | #endif 79 | void __log_user(const char *filename, int line, const char *fmt, ...); 80 | void __log_info(const char *filename, int line, const char *fmt, ...); 81 | void __log_warning(const char *filename, int line, const char *fmt, ...); 82 | void __log_error(const char *filename, int line, const char *fmt, ...); 83 | void __log_fatal(const char *filename, int line, const char *fmt, ...); 84 | #endif 85 | -------------------------------------------------------------------------------- /udp.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2019 rlandjon 2 | * 3 | * This file is part of udpproxy. 4 | * 5 | * udpproxy is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * udpproxy is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with ijkPlayer; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "string.h" 39 | 40 | #include "list.h" 41 | #include "logger.h" 42 | #include "config.h" 43 | 44 | /*udp_datapack->type*/ 45 | #define REQ_FILE 0 46 | #define ACK_TRUE 1 47 | #define ACK_FAIL 2 48 | #define ACK_RECONNECT 3 49 | #define FILE_DATE 4 50 | 51 | 52 | /*udp_req->flag*/ 53 | #define CLINET_EMPTY 0 54 | #define CLINET_FIRST_REQ 1 55 | #define CLINET_TIME_OUT 2 56 | #define CLINET_LINK_ACK_WAIT 3 57 | #define CLINET_LINK_ACK_TRUE 4 58 | #define CLINET_LINK_ACK_FAIL 5 59 | 60 | /*send_ack flag.*/ 61 | #define MAX 1024 62 | #define MAX_UDP_FILE_COUNT 512 63 | #define UDP_HEAD_LEN 13 64 | #define TIMEVAL 45 65 | #define UDP_PACKET_SIZE 4096 66 | //微秒时间精度 67 | #define TIME_SCALE (1000000) 68 | 69 | 70 | /*udp包格式定义*/ 71 | struct udp_datapack { 72 | char type; /*数据包类型*/ 73 | long int label; /*文件包号、请求包文件位置信息*/ 74 | int size; /*包大小*/ 75 | int check; /*CRC*/ 76 | char data[MAX]; 77 | }; 78 | 79 | /*传输的文件信息*/ 80 | struct file_infor { 81 | int file_fd; 82 | char *file_path; //文件名 83 | unsigned long file_len; //文件长度 84 | int64_t timestamp; //文件开始时间戳信息 85 | off_t seek_flag; 86 | int timeout; /*超时计时*/ 87 | int dummy_flag; 88 | struct list_head list; 89 | }; 90 | 91 | struct Context { 92 | char *work_dir; 93 | char *log_dir; 94 | char *dummy_file_path; 95 | int sock_fd; 96 | int file_count; //当前保存的文件计数 97 | int send_buf_size; //默认分配1024大小保存单个文件 98 | char *send_buf; 99 | pthread_mutex_t file_list_mutex; 100 | pthread_cond_t file_list_cond; 101 | pthread_mutex_t wait_mutex; 102 | pthread_cond_t wait_cond; 103 | 104 | char *ip_addr; 105 | int port; 106 | int64_t start_wait_interval; //内部以微秒管理,开始等待start_wait_interval秒开始发送UDP数据 107 | int send_dummy_interval;//等待send_dummy_interval秒 还没有从FTP收到数据,开始发送dummy数据 108 | int exit; 109 | int64_t sent_timestamp; 110 | int64_t stream_start_timestamp; 111 | int64_t system_start_timestamp; 112 | struct list_head head; 113 | }; 114 | 115 | struct Context g_ctx; 116 | 117 | struct option long_options[] = 118 | { 119 | { "ip", 1, NULL, 'i' }, 120 | { "port", 1, NULL, 'p' }, 121 | { "start_wait_interval", 2, NULL, 't' }, 122 | { "work_dir", 2, NULL, 'w' }, 123 | { 0, 0, 0, 0 }, 124 | }; 125 | 126 | static char *const short_options = "i:p:tw"; 127 | /*互斥锁保护待发文件信息改变*/ 128 | 129 | 130 | /*crc校验*/ 131 | int crc_check(struct udp_datapack check_buff) { 132 | return 0; 133 | } 134 | 135 | /*crc校验判断*/ 136 | int crc_test(struct udp_datapack check_buff) { 137 | return 0; 138 | } 139 | 140 | static int64_t get_current_time(void) 141 | { 142 | struct timeval tv; 143 | gettimeofday(&tv,NULL); 144 | return (int64_t)tv.tv_sec * TIME_SCALE + tv.tv_usec; 145 | } 146 | 147 | 148 | /*待发文件列表初始化*/ 149 | void udp_init() { 150 | INIT_LIST_HEAD(&g_ctx.head); 151 | g_ctx.file_count = 0; 152 | g_ctx.send_buf_size = UDP_PACKET_SIZE; 153 | g_ctx.send_buf = calloc(g_ctx.send_buf_size, sizeof(char)); 154 | if ((g_ctx.sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 155 | perror("socket"); 156 | exit(1); 157 | } 158 | g_ctx.start_wait_interval = 0; 159 | g_ctx.exit = 0; 160 | g_ctx.sent_timestamp = 0LL; 161 | pthread_mutex_init(&g_ctx.file_list_mutex, NULL); 162 | pthread_cond_init(&g_ctx.file_list_cond, NULL); 163 | 164 | pthread_mutex_init(&g_ctx.wait_mutex,NULL); 165 | pthread_cond_init(&g_ctx.wait_cond,NULL); 166 | 167 | g_ctx.system_start_timestamp = get_current_time(); 168 | g_ctx.stream_start_timestamp = -1; 169 | 170 | g_ctx.ip_addr = NULL; 171 | g_ctx.port = -1; 172 | g_ctx.work_dir = NULL; 173 | g_ctx.log_dir = NULL; 174 | g_ctx.dummy_file_path = NULL; 175 | g_ctx.send_dummy_interval = 1800; 176 | 177 | } 178 | 179 | void remove_list_file(struct file_infor *del_info) { 180 | if (del_info) { 181 | close(del_info->file_fd); 182 | 183 | if (del_info->file_path) { 184 | free(del_info->file_path); 185 | del_info->file_path = NULL; 186 | } 187 | list_del(&del_info->list); 188 | 189 | free(del_info); 190 | del_info = NULL; 191 | } 192 | g_ctx.file_count--; 193 | } 194 | 195 | 196 | void udp_destroy() { 197 | struct list_head *pos,*n; 198 | struct file_infor *file_item = NULL; 199 | list_for_each_safe(pos, n, &g_ctx.head) { 200 | 201 | file_item = list_entry(pos, struct file_infor, list); 202 | 203 | printf("to= %s from= %lld\n", file_item->file_path, file_item->timestamp); 204 | 205 | remove_list_file(file_item); 206 | } 207 | 208 | list_empty(&g_ctx.head); 209 | 210 | if (g_ctx.send_buf) { 211 | free(g_ctx.send_buf); 212 | g_ctx.send_buf = NULL; 213 | } 214 | 215 | if (g_ctx.sock_fd != -1) close(g_ctx.sock_fd); 216 | 217 | if (g_ctx.work_dir) { 218 | free(g_ctx.work_dir); 219 | g_ctx.work_dir = NULL; 220 | } 221 | if (g_ctx.ip_addr) { 222 | free(g_ctx.ip_addr); 223 | g_ctx.ip_addr = NULL; 224 | } 225 | g_ctx.file_count = 0; 226 | pthread_mutex_destroy(&g_ctx.file_list_mutex); 227 | pthread_cond_destroy(&g_ctx.file_list_cond); 228 | } 229 | 230 | /*向待发文件列表中添加新项*/ 231 | void add_file_tail(struct file_infor *new_info) { 232 | if (new_info) { 233 | list_add_tail(&(new_info->list), &g_ctx.head); 234 | } 235 | } 236 | 237 | void add_file_after(struct file_infor *position) { 238 | if (!position) return; 239 | struct list_head *pos,*n; 240 | struct file_infor *file_item = NULL; 241 | list_for_each_prev_safe(pos, n, &g_ctx.head){ 242 | file_item = list_entry(pos, struct file_infor, list); 243 | //如果列表已经存在,不插入直接退出 244 | if (file_item && file_item->timestamp == position->timestamp) 245 | return; 246 | //找到第一个比当前待插入项时间戳小的后面插入 247 | else if (file_item && 248 | file_item->timestamp < position->timestamp) { 249 | list_add(&(position->list),&(file_item->list)); 250 | g_ctx.file_count++; 251 | pthread_cond_signal(&g_ctx.wait_cond); 252 | log_debug("add %s into list,file_infor=%p,filename=%s,timestamp=%lld,filecount=%d", 253 | position->dummy_flag ? "dummy file" : "file",position, position->file_path, 254 | position->timestamp,g_ctx.file_count); 255 | return; 256 | } 257 | } 258 | if (!file_item) { 259 | add_file_tail(position); 260 | g_ctx.file_count++; 261 | log_debug("add %s into list,file_infor=%p,filename=%s,timestamp=%lld,filecount=%d", 262 | position->dummy_flag ? "dummy file" : "file",position, position->file_path, 263 | position->timestamp,g_ctx.file_count); 264 | } 265 | } 266 | 267 | 268 | /*取得文件列表中末尾项*/ 269 | struct file_infor* get_list_tail() { 270 | struct list_head *pos,*n; 271 | struct file_infor *file_item = NULL; 272 | list_for_each_prev_safe(pos, n, &g_ctx.head) { 273 | file_item = list_entry(pos, struct file_infor, list); 274 | if (file_item) return file_item; 275 | } 276 | 277 | return NULL; 278 | } 279 | 280 | 281 | int custom_filter(const struct dirent *pDir) { 282 | if ((pDir->d_type != DT_DIR) 283 | && strcmp(pDir->d_name, ".") 284 | && strcmp(pDir->d_name, "..")) { 285 | return 1; 286 | } 287 | 288 | return 0; 289 | } 290 | 291 | int strtoi(const char *s) { 292 | if (!s) return; 293 | int64_t num, i; 294 | char ch; 295 | num = 0; 296 | for (i = 0; i < strlen(s); i++) { 297 | ch = s[i]; 298 | if (ch < '0' || ch > '9') break; 299 | num = num * 10 + (ch - '0'); 300 | } 301 | return num; 302 | } 303 | 304 | void add_by_file_name(const char *filename) { 305 | char filepath[1024]; 306 | int dummy_flag = 0; 307 | if (!filename) return; 308 | 309 | if (strtoi(filename) == 0) return; 310 | pthread_mutex_lock(&g_ctx.file_list_mutex); 311 | 312 | char *suffix = strrchr(filename, '.'); 313 | if (suffix && !strcmp(suffix, ".tmp")) { 314 | pthread_mutex_unlock(&g_ctx.file_list_mutex); 315 | return; 316 | } else if(suffix && !strcmp(suffix,".dummy")) 317 | dummy_flag = 1; 318 | 319 | struct file_infor *tmp = (struct file_infor *)calloc(1,sizeof(*tmp)); 320 | if (tmp) { 321 | memset(filepath, 0, sizeof(filepath)); 322 | sprintf(filepath, "%s/", g_ctx.work_dir); 323 | 324 | strcat(filepath, filename); 325 | tmp->file_path = strdup(filepath); 326 | 327 | tmp->seek_flag = 0; 328 | tmp->timestamp = ((int64_t)strtoi(filename)) * TIME_SCALE; 329 | tmp->dummy_flag = dummy_flag; 330 | while (g_ctx.file_count >= MAX_UDP_FILE_COUNT) { 331 | pthread_cond_wait(&g_ctx.file_list_cond, &g_ctx.file_list_mutex); 332 | } 333 | //add_file_tail(tmp); 334 | add_file_after(tmp); 335 | pthread_mutex_unlock(&g_ctx.file_list_mutex); 336 | } 337 | 338 | } 339 | int scan_dir(const char *dirpath, char *filename) { 340 | 341 | struct dirent **namelist; 342 | int n; 343 | int i; 344 | 345 | if (!dirpath && !filename) return -1; 346 | if (dirpath) { 347 | n = scandir(dirpath, &namelist, custom_filter, alphasort); 348 | if (n < 0) { 349 | perror("scandir"); 350 | } else { 351 | for (i = 0; i < n; i++) { 352 | 353 | add_by_file_name(namelist[i]->d_name); 354 | free(namelist[i]); 355 | 356 | } 357 | } 358 | 359 | free(namelist); 360 | } 361 | 362 | if (filename) { 363 | add_by_file_name(filename); 364 | } 365 | } 366 | 367 | 368 | /*循环读取目录文件信息*/ 369 | int event_loop() { 370 | struct inotify_event *event; 371 | int first_scan = 1; 372 | int fd, wd; 373 | int len; 374 | int nread; 375 | char buf[BUFSIZ]; 376 | 377 | fd = inotify_init(); 378 | if (fd < 0) { 379 | log_debug("inotify_init failed,fd=%d", fd); 380 | return -1; 381 | } 382 | 383 | wd = inotify_add_watch(fd, g_ctx.work_dir, 384 | IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVE); 385 | if (wd < 0) { 386 | log_debug("inotify_add_watch %s failed,ret=%d", g_ctx.work_dir, wd); 387 | return -1; 388 | } 389 | 390 | buf[sizeof(buf) - 1] = 0; 391 | while (!g_ctx.exit) { 392 | if (first_scan == 1) { 393 | scan_dir(g_ctx.work_dir, NULL); 394 | first_scan = 0; 395 | } 396 | 397 | while ((len = read(fd, buf, sizeof(buf) - 1)) > 0) { 398 | nread = 0; 399 | while (len > 0) { 400 | event = (struct inotify_event *)&buf[nread]; 401 | 402 | if (!(event->mask & IN_ISDIR) && 403 | event->mask & IN_CREATE || event->mask & IN_ATTRIB || 404 | event->mask & IN_MODIFY || event->mask & IN_MOVE) { 405 | 406 | if (g_ctx.work_dir && (strlen(g_ctx.work_dir) > 0)) 407 | scan_dir(NULL, event->name); 408 | 409 | } 410 | nread = nread + sizeof(struct inotify_event) + event->len; 411 | len = len - sizeof(struct inotify_event) - event->len; 412 | } 413 | } 414 | 415 | 416 | } 417 | } 418 | 419 | void wait_time(int64_t file_timestamp) { 420 | 421 | int64_t current_timestamp = get_current_time(); 422 | 423 | //初始第一个文件的时间为流开始时间 424 | if (g_ctx.stream_start_timestamp == -1) { 425 | g_ctx.stream_start_timestamp = file_timestamp; 426 | 427 | if (file_timestamp + g_ctx.start_wait_interval > current_timestamp) { 428 | usleep(g_ctx.start_wait_interval); 429 | } 430 | 431 | } 432 | 433 | } 434 | 435 | ssize_t readn(int fd, void *vptr, size_t n) { 436 | size_t nleft; 437 | ssize_t nread; 438 | char *ptr; 439 | ptr = vptr; 440 | nleft = n; 441 | while (nleft > 0) { 442 | if ((nread = read(fd, ptr, nleft)) < 0) { 443 | if (errno == EINTR) nread = 0; 444 | else return -1; 445 | } else if (nread == 0) break; 446 | nleft -= nread; 447 | ptr += nread; 448 | } 449 | return n - nleft; 450 | } 451 | 452 | 453 | ///*向客户端发送文件*/ 454 | int send_file(struct file_infor *file_item) { 455 | int buf_len = 0; 456 | int file_block_length = 0; 457 | static int first_send = 1; 458 | 459 | struct sockaddr_in serv_addr; 460 | memset(&serv_addr,0,sizeof(serv_addr)); 461 | serv_addr.sin_family = AF_INET; 462 | inet_pton(AF_INET, g_ctx.ip_addr, &serv_addr.sin_addr); 463 | serv_addr.sin_port = htons(g_ctx.port); 464 | 465 | wait_time(file_item->timestamp); 466 | 467 | 468 | file_item->file_fd = open(file_item->file_path, O_SYNC | O_RDONLY); 469 | 470 | struct stat fs; 471 | if (-1 != fstat(file_item->file_fd, &fs)) 472 | file_item->file_len = fs.st_size; 473 | 474 | 475 | int total_send_bytes = 0, read_bytes = 0, send_bytes = 0; 476 | while (total_send_bytes < file_item->file_len) { 477 | read_bytes = readn(file_item->file_fd, g_ctx.send_buf, g_ctx.send_buf_size); 478 | if (read_bytes <= 0) 479 | break; 480 | send_bytes = 0; 481 | while (send_bytes < read_bytes) { 482 | int block_len = sendto(g_ctx.sock_fd, g_ctx.send_buf + send_bytes, 483 | read_bytes - send_bytes, 0, (struct sockaddr *)&(serv_addr), 484 | sizeof(struct sockaddr_in)); 485 | if (block_len > 0) { 486 | send_bytes += block_len; 487 | } else { 488 | log_error("Send File:%s failed,timestamp=%lld Failed\n,truncted size=%d", 489 | file_item->file_path, file_item->timestamp, file_item->file_len - total_send_bytes); 490 | } 491 | } 492 | total_send_bytes += send_bytes; 493 | memset(g_ctx.send_buf, 0, g_ctx.send_buf_size); 494 | 495 | } 496 | } 497 | 498 | static int copy_dummy_file(const char *dummy_file_path,char*file_name) { 499 | int from_fd, to_fd; 500 | int bytes_read, bytes_write; 501 | char buffer[BUFFER_SIZE]; 502 | char *ptr; 503 | int ret = 0; 504 | char path[1024]; 505 | memset(path, 0, sizeof(path)); 506 | /* 打开源文件 */ 507 | if ((from_fd = open(dummy_file_path, O_RDONLY)) == -1) { 508 | ret = -1; 509 | /*open file readonly,返回-1表示出错,否则返回文件描述符*/ 510 | log_debug("Open %s Error:%s\n", dummy_file_path, strerror(errno)); 511 | return ret; 512 | } 513 | 514 | /* 创建目的文件 */ 515 | /* 使用了O_CREAT选项-创建文件,open()函数需要第3个参数, 516 | mode=S_IRUSR|S_IWUSR表示S_IRUSR 用户可以读 S_IWUSR 用户可以写*/ 517 | 518 | //把文件copy到工作目录,并且重命名文件为最后发送文件时间戳+1 519 | struct file_infor* file_info = get_list_tail(); 520 | if (file_info) { 521 | sprintf(path, "%s/%lld.dummy", g_ctx.work_dir, (file_info->timestamp + TIME_SCALE) / TIME_SCALE); 522 | } else 523 | sprintf(path, "%s/1.dummy",g_ctx.work_dir); 524 | 525 | if ((to_fd = open(path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) == -1) { 526 | ret = -1; 527 | log_debug( "Open %s Error:%s\n", path, strerror(errno)); 528 | return ret; 529 | 530 | } 531 | 532 | while (bytes_read = read(from_fd, buffer, BUFFER_SIZE)) { 533 | 534 | /* 一个致命的错误发生了 */ 535 | if ((bytes_read == -1) && (errno != EINTR)) { 536 | ret = -1; 537 | break; 538 | } else if (bytes_read > 0) { 539 | ptr = buffer; 540 | while (bytes_write = write(to_fd, ptr, bytes_read)) { 541 | /* 一个致命错误发生了 */ 542 | if ((bytes_write == -1) && (errno != EINTR)) { 543 | ret = -1; 544 | break; 545 | } 546 | /* 写完了所有读的字节 */ 547 | else if (bytes_write == bytes_read) break; 548 | /* 只写了一部分,继续写 */ 549 | else if (bytes_write > 0) { 550 | ptr += bytes_write; 551 | bytes_read -= bytes_write; 552 | } 553 | } 554 | /* 写的时候发生的致命错误 */ 555 | if (bytes_write == -1) { 556 | ret = -1; 557 | break; 558 | } 559 | } 560 | } 561 | strcpy(file_name,path); 562 | close(from_fd); 563 | close(to_fd); 564 | return ret; 565 | } 566 | 567 | 568 | /*发送文件处理*/ 569 | void* on_request() { 570 | int tmp; 571 | struct list_head *pos,*n; 572 | struct file_infor *file_item; 573 | while (!g_ctx.exit) { 574 | //test_ 575 | //当文件不够发送的时候 ,等待超时时间,并且发送dummy文件 576 | //if (g_ctx.file_count == 0) { 577 | while (g_ctx.file_count <= 0 && g_ctx.dummy_file_path) { 578 | pthread_mutex_lock(&g_ctx.wait_mutex); 579 | //test file_cout正常情况下应该设置成0 580 | struct timeval now; 581 | struct timespec outtime; 582 | gettimeofday(&now, NULL); 583 | outtime.tv_sec = now.tv_sec + g_ctx.send_dummy_interval; 584 | outtime.tv_nsec = now.tv_usec * 1000; 585 | if (ETIMEDOUT == pthread_cond_timedwait(&g_ctx.wait_cond, &g_ctx.wait_mutex, &outtime)) { 586 | char file_name[1024]; 587 | memset(file_name,0,sizeof(file_name)); 588 | 589 | copy_dummy_file(g_ctx.dummy_file_path,(char*)file_name); 590 | log_debug("wait more file to send,dummy file:%s add to list.",file_name); 591 | } 592 | pthread_mutex_unlock(&g_ctx.wait_mutex); 593 | } 594 | 595 | pthread_mutex_lock(&g_ctx.file_list_mutex); 596 | 597 | list_for_each_safe(pos, n, &g_ctx.head) { 598 | 599 | file_item = list_entry(pos, struct file_infor, list); 600 | send_file(file_item); 601 | if (file_item->dummy_flag == 0) 602 | g_ctx.sent_timestamp = file_item->timestamp; 603 | else 604 | remove(file_item->file_path);////dummy数据,删除copy的文件 605 | log_debug("sendfile file_item=%p,filename=%s,timestamp=%lld," 606 | "sent_timestamp=%lld,fd=%d", file_item, file_item->file_path, 607 | file_item->timestamp,g_ctx.sent_timestamp,file_item->file_fd); 608 | 609 | //remove(file_item->file_path); 610 | remove_list_file(file_item); 611 | pthread_cond_signal(&g_ctx.file_list_cond); 612 | break; 613 | } 614 | pthread_mutex_unlock(&g_ctx.file_list_mutex); 615 | } 616 | 617 | } 618 | 619 | int main(int argc, char *argv[]) { 620 | int i; 621 | struct timeval t_new; 622 | pthread_t req_tid; 623 | pthread_t res_tid; 624 | struct sockaddr_in serv_addr; 625 | 626 | //命令行参数解析 627 | int c; 628 | char *l_opt_arg; 629 | 630 | //config 631 | char *config_file, *error, *value; 632 | char **hkp, **hash_keys, **values, **keywords, **sections; 633 | char **sp, **kp, **vp, *hash_key, *type_s; 634 | char *keyword, *section; 635 | int cfg_index, type; 636 | 637 | 638 | udp_init(); 639 | while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { 640 | 641 | switch (c) { 642 | case 'i': 643 | l_opt_arg = optarg; 644 | g_ctx.ip_addr = strdup(l_opt_arg); 645 | break; 646 | case 'p': 647 | l_opt_arg = optarg; 648 | g_ctx.port = strtoi(l_opt_arg); 649 | break; 650 | case 't': 651 | l_opt_arg = optarg; 652 | g_ctx.start_wait_interval = ((int64_t)strtoi(l_opt_arg)) * TIME_SCALE; 653 | break; 654 | case 'w': 655 | l_opt_arg = optarg; 656 | g_ctx.work_dir = strdup(l_opt_arg); 657 | break; 658 | default: 659 | break; 660 | } 661 | 662 | } 663 | 664 | //config 解析 665 | 666 | cfg_index = cfg_read_config_file( "udpproxy.conf" ) ; 667 | 668 | if ( cfg_error_msg( cfg_index )) { 669 | error = cfg_error_msg( cfg_index ) ; 670 | } 671 | 672 | sections = cfg_get_sections( cfg_index ) ; 673 | for ( sp = sections ; *sp ; sp++ ) { 674 | section = *sp ; 675 | //printf( "%s\n", section ) ; 676 | keywords = cfg_get_keywords( cfg_index, *sp ) ; 677 | for ( kp = keywords ; *kp ; kp++ ) { 678 | keyword = *kp ; 679 | type = cfg_get_type( cfg_index, section, keyword ) ; 680 | type_s = cfg_get_type_str( cfg_index, section, keyword ) ; 681 | switch ( type ) { 682 | case TYPE_SCALAR: 683 | value = cfg_get_value( cfg_index, section, keyword ) ; 684 | break ; 685 | case TYPE_ARRAY: 686 | values = cfg_get_values( cfg_index, section, keyword ) ; 687 | for ( vp = values ; *vp ; vp++ ) { 688 | //printf( "\t\t\'%s\'\n", *vp ) ; 689 | } 690 | break ; 691 | case TYPE_HASH: 692 | hash_keys = cfg_get_hash_keys( cfg_index, section, keyword ) ; 693 | for ( hkp = hash_keys ; *hkp ; hkp++ ) { 694 | hash_key = *hkp ; 695 | value = cfg_get_hash_value( cfg_index, section, keyword, hash_key ) ; 696 | //printf( "\t\t%s = \'%s\'\n", hash_key, value ) ; 697 | } 698 | break ; 699 | } 700 | 701 | if (keyword && value) { 702 | //如果命令行没有指定,就读取配置文件的值 703 | if (!strcmp(keyword, "ip") && !g_ctx.ip_addr) 704 | g_ctx.ip_addr = strdup(value); 705 | else if (!strcmp(keyword, "port") && g_ctx.port == -1) 706 | g_ctx.port = strtoi(value); 707 | else if (!strcmp(keyword, "work_dir") && !g_ctx.work_dir) 708 | g_ctx.work_dir = strdup(value); 709 | else if (!strcmp(keyword, "log_dir") && !g_ctx.log_dir) { 710 | char path[1024]; 711 | if (value[strlen(value)] != '/') { 712 | sprintf(path, "%s/", value); 713 | path[strlen(path)] = '\0'; 714 | } 715 | g_ctx.log_dir = strdup(path); 716 | } else if (!strcmp(keyword, "dummy_file") && !g_ctx.dummy_file_path) 717 | g_ctx.dummy_file_path = strdup(value); 718 | else if (!strcmp(keyword,"send_dummy_interval")) 719 | g_ctx.send_dummy_interval = strtoi(value); 720 | else if (!strcmp(keyword,"start_wait_interval") && 721 | (g_ctx.start_wait_interval != 0)) 722 | g_ctx.start_wait_interval = strtoi(value); 723 | } 724 | } 725 | } 726 | if (g_ctx.ip_addr == NULL || 727 | g_ctx.port == -1 || g_ctx.work_dir == NULL) { 728 | printf("ERROR! usage sample:\n"); 729 | printf("./udp -i 192.168.10.18 -p 8888 -t 10000 -w /home \n"); 730 | } 731 | if (g_ctx.log_dir) { 732 | log_init(g_ctx.log_dir, LOGGER_ROTATE_BY_SIZE | LOGGER_ROTATE_PER_HOUR, 64); 733 | log_debug("init log succeed in %s", g_ctx.log_dir); 734 | } else 735 | log_init(".udpproxy", LOGGER_ROTATE_BY_SIZE | LOGGER_ROTATE_PER_HOUR, 64); 736 | 737 | //dupm info 738 | log_debug("[-----------------dump config begin-----------------------------]"); 739 | if (g_ctx.work_dir) log_debug("work_dir:%s", g_ctx.work_dir); 740 | if (g_ctx.log_dir) log_debug("log_dir:%s", g_ctx.log_dir); 741 | if (g_ctx.ip_addr && g_ctx.port) 742 | log_debug("send to ip:%s,port:%d", g_ctx.ip_addr, g_ctx.port); 743 | log_debug("start_wait_interval=%d",g_ctx.start_wait_interval); 744 | log_debug("send_dummy_interval=%d",g_ctx.send_dummy_interval); 745 | log_debug("[-----------------dump config end-----------------------------]"); 746 | if (pthread_create(&req_tid, NULL, (void *)event_loop, NULL) != 0) { 747 | close(g_ctx.sock_fd); 748 | return -2; 749 | } 750 | 751 | if (pthread_create(&res_tid, NULL, (void *)on_request, NULL) != 0) { 752 | close(g_ctx.sock_fd); 753 | return -2; 754 | } 755 | 756 | pthread_join(req_tid, NULL); 757 | pthread_join(res_tid, NULL); 758 | 759 | udp_destroy(); 760 | } 761 | 762 | -------------------------------------------------------------------------------- /udp.mak: -------------------------------------------------------------------------------- 1 | # SlickEdit generated file. Do not edit this file except in designated areas. 2 | 3 | # Make command to use for dependencies 4 | MAKE=make 5 | RM=rm 6 | MKDIR=mkdir 7 | 8 | # -----Begin user-editable area----- 9 | 10 | # -----End user-editable area----- 11 | 12 | # If no configuration is specified, "Debug" will be used 13 | ifndef CFG 14 | CFG=Debug 15 | endif 16 | 17 | # 18 | # Configuration: Debug 19 | # 20 | ifeq "$(CFG)" "Debug" 21 | OUTDIR=Debug 22 | OUTFILE=$(OUTDIR)/udp 23 | CFG_INC= 24 | CFG_LIB=-lpthread 25 | CFG_OBJ= 26 | COMMON_OBJ=$(OUTDIR)/config.o $(OUTDIR)/logger.o $(OUTDIR)/udp.o 27 | OBJ=$(COMMON_OBJ) $(CFG_OBJ) 28 | ALL_OBJ=$(OUTDIR)/config.o $(OUTDIR)/logger.o $(OUTDIR)/udp.o \ 29 | -lpthread 30 | 31 | COMPILE=gcc -c -g -o "$(OUTDIR)/$(*F).o" $(CFG_INC) $< 32 | LINK=gcc -g -o "$(OUTFILE)" $(ALL_OBJ) 33 | 34 | # Pattern rules 35 | $(OUTDIR)/%.o : %.c 36 | $(COMPILE) 37 | 38 | # Build rules 39 | all: $(OUTFILE) 40 | 41 | $(OUTFILE): $(OUTDIR) $(OBJ) 42 | $(LINK) 43 | 44 | $(OUTDIR): 45 | $(MKDIR) -p "$(OUTDIR)" 46 | 47 | # Rebuild this project 48 | rebuild: cleanall all 49 | 50 | # Clean this project 51 | clean: 52 | $(RM) -f $(OUTFILE) 53 | $(RM) -f $(OBJ) 54 | 55 | # Clean this project and all dependencies 56 | cleanall: clean 57 | endif 58 | 59 | # 60 | # Configuration: Release 61 | # 62 | ifeq "$(CFG)" "Release" 63 | OUTDIR=Release 64 | OUTFILE=$(OUTDIR)/udp 65 | CFG_INC= 66 | CFG_LIB=-lpthread 67 | CFG_OBJ= 68 | COMMON_OBJ=$(OUTDIR)/config.o $(OUTDIR)/logger.o $(OUTDIR)/udp.o 69 | OBJ=$(COMMON_OBJ) $(CFG_OBJ) 70 | ALL_OBJ=$(OUTDIR)/config.o $(OUTDIR)/logger.o $(OUTDIR)/udp.o \ 71 | -lpthread 72 | 73 | COMPILE=gcc -c -o "$(OUTDIR)/$(*F).o" $(CFG_INC) $< 74 | LINK=gcc -o "$(OUTFILE)" $(ALL_OBJ) 75 | 76 | # Pattern rules 77 | $(OUTDIR)/%.o : %.c 78 | $(COMPILE) 79 | 80 | # Build rules 81 | all: $(OUTFILE) 82 | 83 | $(OUTFILE): $(OUTDIR) $(OBJ) 84 | $(LINK) 85 | 86 | $(OUTDIR): 87 | $(MKDIR) -p "$(OUTDIR)" 88 | 89 | # Rebuild this project 90 | rebuild: cleanall all 91 | 92 | # Clean this project 93 | clean: 94 | $(RM) -f $(OUTFILE) 95 | $(RM) -f $(OBJ) 96 | 97 | # Clean this project and all dependencies 98 | cleanall: clean 99 | endif 100 | -------------------------------------------------------------------------------- /udp.vpj: -------------------------------------------------------------------------------- 1 | 2 | 9 | 17 | 18 | 27 | 28 | 29 | 38 | 39 | 40 | 47 | 48 | 49 | 56 | 57 | 58 | 67 | 70 | 71 | 80 | 83 | 84 | 88 | 89 | 90 | 96 | 99 | 100 | 101 | 102 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 119 | 120 | 129 | 130 | 131 | 140 | 141 | 142 | 149 | 150 | 151 | 158 | 159 | 160 | 169 | 172 | 173 | 182 | 185 | 186 | 190 | 191 | 192 | 198 | 201 | 202 | 203 | 204 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 227 | 230 | 233 | 236 | 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /udp.vpw: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /udp.vpwhistu: -------------------------------------------------------------------------------- 1 | [Global] 2 | CurrentProject=udp.vpj 3 | [ProjectDates] 4 | udp.vpj=20150208102951000 5 | [ActiveConfig] 6 | udp.vpj=,Debug 7 | [TreeExpansion2] 8 | -@ udp.vpj 9 | + Source Files 10 | + Header Files 11 | + Resource Files 12 | + Bitmaps 13 | + Other Files 14 | scroll:0 15 | [State] 16 | SCREEN: 1360 768 87 67 1173 645 0 0 M 0 0 0 0 1337 662 0 17 | CWD: ../udpproxy 18 | BUFFER: BN="config.c" 19 | BI: MA=1 74 1 TABS=1 5 WWS=1 IWT=1 ST=8 IN=2 BW=0 US=32000 RO=0 SE=1 SN=0 BIN=0 MN=C/C++ HM=0 MF=119603808 TL=0 MLL=0 ASE=0 LNL=1 LCF=6 CAPS=0 E=70 ESBU2=71 CL="" SC="" SCE= SCU= 20 | VIEW: LN=.47157 CL=1 LE=0 CX=1 CY=12 WI=5 BI=19 HT=0 HN=0 HF=0 HC=4 21 | BUFFER: BN="config.h" 22 | BI: MA=1 74 1 TABS=1 5 WWS=1 IWT=1 ST=8 IN=2 BW=0 US=32000 RO=0 SE=1 SN=0 BIN=0 MN=C/C++ HM=0 MF=33554432 TL=0 MLL=0 ASE=0 LNL=1 LCF=6 CAPS=0 E=70 ESBU2=71 CL="" SC="" SCE= SCU= 23 | VIEW: LN=.0 CL=1 LE=0 CX=1 CY=0 WI=5 BI=18 HT=0 HN=0 HF=0 HC=4 24 | BUFFER: BN="logger.c" 25 | BI: MA=1 74 1 TABS=1 5 WWS=1 IWT=1 ST=8 IN=2 BW=0 US=32000 RO=0 SE=1 SN=0 BIN=0 MN=C/C++ HM=0 MF=119603296 TL=0 MLL=0 ASE=0 LNL=1 LCF=6 CAPS=0 E=70 ESBU2=71 CL="" SC="" SCE= SCU= 26 | VIEW: LN=.1869 CL=14 LE=0 CX=14 CY=13 WI=5 BI=20 HT=0 HN=0 HF=0 HC=4 27 | BUFFER: BN="udp.c" 28 | BI: MA=1 74 1 TABS=1 5 WWS=1 IWT=1 ST=8 IN=2 BW=0 US=32000 RO=0 SE=1 SN=0 BIN=0 MN=C/C++ HM=0 MF=120652384 TL=0 MLL=0 ASE=0 LNL=1 LCF=6 CAPS=0 E=70 ESBU2=71 CL="" SC="" SCE= SCU= 29 | VIEW: LN=.19448 CL=1 LE=0 CX=1 CY=18 WI=5 BI=21 HT=0 HN=0 HF=0 HC=4 30 | BUFFER: BN="list.h" 31 | BI: MA=1 74 1 TABS=1 5 WWS=1 IWT=0 ST=8 IN=2 BW=0 US=32000 RO=0 SE=1 SN=0 BIN=0 MN=C/C++ HM=0 MF=117440608 TL=0 MLL=0 ASE=0 LNL=1 LCF=6 CAPS=0 E=71 ESBU2=1 CL="" SC="" SCE= SCU= 32 | VIEW: LN=.6630 CL=22 LE=0 CX=22 CY=20 WI=5 BI=17 HT=0 HN=0 HF=0 HC=4 33 | WINDOW: 1 24 1335 637 0 0 N WF=0 WT=5 ",,," DID=0 34 | BUFFER: BN="logger.c" 35 | VIEW: LN=.1869 CL=23 LE=0 CX=28 CY=13 WI=113 BI=20 HT=0 HN=0 HF=0 HC=4 36 | WINDOW: 1 24 1335 565 0 0 N WF=0 WT=6 "DejaVu Sans Mono,12,0,1" DID=0 37 | BUFFER: BN="list.h" 38 | VIEW: LN=.6630 CL=22 LE=0 CX=27 CY=20 WI=115 BI=17 HT=0 HN=0 HF=0 HC=4 39 | WINDOW: 1 24 1335 420 0 0 N WF=0 WT=2 ",,," DID=0 40 | BUFFER: BN="config.h" 41 | VIEW: LN=.0 CL=1 LE=0 CX=5 CY=0 WI=111 BI=18 HT=0 HN=0 HF=0 HC=4 42 | WINDOW: 1 24 1145 529 0 0 N WF=0 WT=4 ",,," DID=0 43 | BUFFER: BN="config.c" 44 | VIEW: LN=.47157 CL=1 LE=0 CX=7 CY=12 WI=109 BI=19 HT=0 HN=0 HF=0 HC=4 45 | WINDOW: 1 24 1335 637 0 0 N WF=0 WT=3 ",,," DID=0 46 | BUFFER: BN="udp.c" 47 | VIEW: LN=.19448 CL=1 LE=0 CX=5 CY=18 WI=118 BI=21 HT=0 HN=0 HF=0 HC=4 48 | MDISTATE: 1 49 | AAAAAAAAAAHaAAAAAAAAACAAAAVPAAACzOoAAAAC+gAAAAUAAAAFAAAABAAAAAAAAAADAAAAAgAAAAH6AAAABQAAAAUAAAAEAAAAAwAAAAAAAAABAAAAAvwAAABWADQAOgA6ADoALwBoAG8AbQBlAC8AcwBoAGEAawBpAG4ALwB3AG8AcgBrAC8AcwByAGMALwB1AGQAcABwAHIAbwB4AHkALwBjAG8AbgBmAGkAZwAuAGMAAP////8AAPwAAABWADIAOgA6ADoALwBoAG8AbQBlAC8AcwBoAGEAawBpAG4ALwB3AG8AcgBrAC8AcwByAGMALwB1AGQAcABwAHIAbwB4AHkALwBjAG8AbgBmAGkAZwAuAGgAAP////8AAPwAAABSADYAOgA6ADoALwBoAG8AbQBlAC8AcwBoAGEAawBpAG4ALwB3AG8AcgBrAC8AcwByAGMALwB1AGQAcABwAHIAbwB4AHkALwBsAGkAcwB0AC4AaAAA/////wAA/AAAAFYANQA6ADoAOgAvAGgAbwBtAGUALwBzAGgAYQBrAGkAbgAvAHcAbwByAGsALwBzAHIAYwAvAHUAZABwAHAAcgBvAHgAeQAvAGwAbwBnAGcAZQByAC4AYwAA/////wAA/AAAAFAAMwA6ADoAOgAvAGgAbwBtAGUALwBzAGgAYQBrAGkAbgAvAHcAbwByAGsALwBzAHIAYwAvAHUAZABwAHAAcgBvAHgAeQAvAHUAZABwAC4AYwEA/////wAB 50 | FILEHIST: 9 51 | log.c 52 | /usr/include/dirent.h 53 | ../../gitrepo/ffmpeg/doc/examples/transcoding.c 54 | config.h 55 | logger.c 56 | list.h 57 | /usr/include/pthread.h 58 | config.c 59 | udp.c 60 | TBCLASS: 0 0 61 | DEBUG: 13 0 3 0 3 0 gdb 62 | 1tmp->timestamp0 63 | 1g_ctx.head0 64 | 1file_info->timestamp / 10000000 65 | 1gContext.head->next->next->next0 66 | 1g_ctx.file_list_mutex0 67 | 1current_timestamp0 68 | 1file_timestamp0 69 | 1tmp->timestamp0 70 | 1event->name0 71 | 3g_ctx0 72 | 2g_ctx.head0 73 | 1file_item->timestamp0 74 | 1filename0 75 | 00on_requestudp.c59900 76 | 00strtoiudp.c27700 77 | 00mainudp.c64810x000000000040729a0 78 | printf 79 | strcpy 80 | std::* 81 | DEBUG2: 3 82 | 00on_requestudp.c5990045 83 | break; 84 | } 85 | pthread_mutex_unlock(&g_ctx.file_list_mutex); 86 | } 87 | } 88 |  89 | intmain(intargc,char*argv[]){ 90 | inti; 91 | structtimevalt_new; 92 | pthread_treq_tid; 93 | 00strtoiudp.c2770045 94 | intstrtoi(constchar*s){ 95 | if(!s)return; 96 | int64_tnum,i; 97 | charch; 98 | num=0; 99 | for(i=0;i<strlen(s);i++){ 100 | ch=s[i]; 101 | if(ch<'0'||ch>'9')break; 102 | num=num*10+(ch-'0'); 103 | } 104 | 00mainudp.c64810x000000000040729a034 105 | } 106 |  107 | //config解析 108 | cfg_index=cfg_read_config_file("udpproxy.conf"); 109 |  110 | if(cfg_error_msg(cfg_index)){ 111 | error=cfg_error_msg(cfg_index); 112 | } 113 | -------------------------------------------------------------------------------- /udp.vtg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcksk/udpproxy/a631af34ea6028d36f389f02fb16f8fb187e12c9/udp.vtg -------------------------------------------------------------------------------- /udpproxy.conf: -------------------------------------------------------------------------------- 1 | #Configure network information 2 | network: 3 | ip = 192.168.1.100 4 | port = 8999 5 | #Working directory settings 6 | directory: 7 | #FTP upload file path 8 | work_dir =/home/shakin/work/contents2 9 | #Log output path 10 | log_dir = /home/shakin/work/src/udpproxy/log 11 | #If the network delay increases, specify the dummy data to send 12 | dummy_file = /home/shakin/work/dummy.ts 13 | time: 14 | #send the data specified by dummy_file if there is no data after 15 | #send_dummy_interval seconds 16 | send_dummy_interval = 30 17 | # Start playing after buffering the data of start_wait_interval seconds 18 | start_wait_interval = 600 19 | --------------------------------------------------------------------------------