1 /* @(#) implementation of utility functions for udpxy
3 * Copyright 2008-2011 Pavel V. Cherenkov (pcherenkov@gmail.com)
5 * This file is part of udpxy.
7 * udpxy is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * udpxy is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with udpxy. If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/utsname.h>
26 #include <sys/types.h>
45 extern const char COMPILE_MODE
[];
46 extern const char VERSION
[];
47 extern const int BUILDNUM
;
48 extern const char BUILD_TYPE
[];
49 extern const int PATCH
;
51 static char s_sysinfo
[80] = "\0";
53 extern struct udpxy_opt g_uopt
;
55 /* write buffer to a file
59 save_buffer( const void* buf
, size_t len
, const char* filename
)
65 assert( buf
&& len
&& filename
);
69 (mode_t
)(S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
) );
75 for( p
= (const char*)buf
, nw
= 0, left
= len
; left
> 0; ) {
76 nw
= write( fd
, p
, left
);
91 return (rc
? -1 : ((ssize_t
)len
- left
));
95 /* read text file into a buffer
99 txtf_read (const char* fpath
, char* dst
, size_t maxlen
, FILE* log
)
103 ssize_t left
= maxlen
- 1;
106 assert (fpath
&& dst
&& maxlen
);
107 fd
= open (fpath
, O_RDONLY
, 0);
109 mperror (log
, errno
, "%s open %s", __func__
, fpath
);
114 n
= read (fd
, p
, maxlen
- 1);
119 mperror (log
, errno
, "%s read %s", __func__
, fpath
);
129 if (-1 == close (fd
)) {
130 mperror (log
, errno
, "%s close %s", __func__
, fpath
);
132 return (rc
? -1 : ((ssize_t
)maxlen
- left
- 1));
136 /* make current process run as a daemon
139 daemonize(int options
, FILE* log
)
146 if( (pid
= fork()) < 0 ) {
148 "%s: fork", __func__
);
151 else if( 0 != pid
) {
156 if( -1 == (rc
= setsid()) ) {
158 "%s: setsid", __func__
);
162 if( -1 == (rc
= chdir("/")) ) {
163 mperror( log
, errno
, "%s: chdir", __func__
);
169 if( !(options
& DZ_STDIO_OPEN
) ) {
170 for( fh
= 0; fh
< 3; ++fh
)
171 if( -1 == (rc
= close(fh
)) ) {
172 mperror( log
, errno
, "%s: close", __func__
);
177 if( SIG_ERR
== signal(SIGHUP
, SIG_IGN
) ) {
178 mperror( log
, errno
, "%s: signal", __func__
);
185 if( 0 != rc
) return rc
;
187 /* child exits to avoid session leader's re-acquiring
188 * control terminal */
189 if( (pid
= fork()) < 0 ) {
190 mperror( log
, errno
, "%s: fork", __func__
);
200 /* multiplex error output to custom log
204 mperror( FILE* fp
, int err
, const char* format
, ... )
206 char buf
[ 256 ] = { '\0' };
212 va_start( ap
, format
);
213 n
= vsnprintf( buf
, sizeof(buf
) - 1, format
, ap
);
216 if( n
<= 0 || n
>= ((int)sizeof(buf
) - 1) ) return;
218 snprintf( buf
+ n
, sizeof(buf
) - n
- 1, ": %s",
221 syslog( LOG_ERR
| LOG_LOCAL0
, "%s", buf
);
222 if( fp
) (void) tmfprintf( fp
, "%s\n", buf
);
228 /* write-lock on a file handle
235 lck
.l_type
= F_WRLCK
;
237 lck
.l_whence
= SEEK_SET
;
240 return fcntl( fd
, F_SETLK
, &lck
);
243 /* create and lock file with process's ID
246 make_pidfile( const char* fpath
, pid_t pid
, FILE* log
)
248 int fd
= -1, rc
= 0, n
= -1;
251 #define LLONG_MAX_DIGITS 21
252 char buf
[ LLONG_MAX_DIGITS
+ 1 ];
254 mode_t fmode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
;
256 assert( (NULL
!= fpath
) && pid
);
260 fd
= open( fpath
, O_CREAT
| O_WRONLY
| O_NOCTTY
, fmode
);
262 mperror(log
, errno
, "make_pidfile - open");
267 rc
= wlock_file( fd
);
269 if( (EACCES
== errno
) || (EAGAIN
== errno
) ) {
270 (void) fprintf( stderr
, "File [%s] is locked "
271 "(another instance of daemon must be running)\n",
276 mperror(log
, errno
, "wlock_file");
280 rc
= ftruncate( fd
, 0 );
282 mperror(log
, errno
, "make_pidfile - ftruncate");
286 n
= snprintf( buf
, sizeof(buf
) - 1, "%d", pid
);
288 mperror(log
, errno
, "make_pidfile - snprintf");
293 nwr
= write( fd
, buf
, n
);
294 if( (ssize_t
)n
!= nwr
) {
295 mperror( log
, errno
, "make_pidfile - write");
302 if( (0 != rc
) && (fd
> 0) ) {
310 /* write path to application's pidfile into the the buffer
311 * (fail if destination directory is not writable)
314 set_pidfile( const char* appname
, int port
, char* buf
, size_t len
)
318 assert( appname
&& buf
&& len
);
320 if( -1 == access(PIDFILE_DIR
, W_OK
) )
323 n
= snprintf( buf
, len
, "%s/%s%d.pid", PIDFILE_DIR
, appname
, port
);
324 if( n
< 0 ) return EXIT_FAILURE
;
326 buf
[ len
- 1 ] = '\0';
335 return (EAGAIN
== err
) || (EWOULDBLOCK
== err
);
342 return (EPIPE
== err
) || (ECONNRESET
== err
) || would_block(err
);
346 /* write buffer to designated socket/file
349 write_buf( int fd
, const char* data
, const ssize_t len
, FILE* log
)
351 ssize_t n
= 0, nwr
= 0, error
= IO_ERR
;
354 for( n
= 0; errno
= 0, n
< len
; ) {
355 nwr
= write( fd
, &(data
[n
]), len
- n
);
359 TRACE( (void)tmfprintf( log
,
360 "%s interrupted\n", __func__
) );
364 if( would_block(err
) )
375 TRACE( (void)tmfprintf( log
,
376 "Fragment written %s[%ld:%ld]/[%ld] bytes\n",
377 (len
> n
? "P" : "F"), (long)nwr
, (long)n
, (long)len
) );
386 (void)tmfprintf( log
, "%s: socket time-out on write", __func__
);
387 else if( !no_fault(err
) || g_uopt
.is_verbose
)
388 mperror( log
, errno
, "%s: write", __func__
);
398 /* read data chunk of designated size (or less) into buffer
399 * (will *NOT* attempt to re-read if read less than expected
403 read_buf( int fd
, char* data
, const ssize_t len
, FILE* log
)
405 ssize_t n
= 0, nrd
= 0, err
= 0;
407 for( n
= 0; errno
= 0, n
< len
; ) {
408 nrd
= read( fd
, &(data
[n
]), len
- n
);
412 TRACE( (void)tmfprintf( log
,
413 "%s interrupted\n", __func__
) );
426 TRACE( (void)tmfprintf( log,
427 "Fragment read [%ld]/[%ld] bytes\n",
428 (long)nrd, (long)len ) );
433 /* we only read as much as we can read at once (uninterrupted) */
439 if( would_block(err
) )
440 (void)tmfprintf( log
, "%s: socket time-out on read", __func__
);
441 else if( !no_fault(err
) || g_uopt
.is_verbose
)
442 mperror( log
, errno
, "%s: read", __func__
);
450 /* output hex dump of a memory fragment
453 hex_dump( const char* msg
, const char* data
, size_t len
, FILE* log
)
457 assert( data
&& (len
> (size_t)0) && log
);
459 if( msg
) (void)fprintf( log
, "%s: ", msg
);
461 for( i
= 0; i
< len
; ++i
)
462 (void)fprintf( log
, "%02x ", data
[i
] & 0xFF );
464 (void) fputs("\n", log
);
469 /* check for expected size, complain if not matched
472 sizecheck( const char* msg
, ssize_t expct
, ssize_t len
,
473 FILE* log
, const char* func
)
475 assert( msg
&& log
&& func
);
476 if( expct
== len
) return 0;
478 (void) (msg
&& log
&& func
); /* NOP to eliminate warnings */
480 TRACE( (void)tmfprintf( log
, "%s: %s - read only [%ld] "
481 "bytes out of [%ld]\n",
482 func
, msg
, (long)len
, (long)expct
) );
488 /* check for a potential buffer overrun by
489 * evaluating target buffer and the portion
490 * of that buffer to be accessed
493 buf_overrun( const char* buf
, size_t buflen
,
494 size_t offset
, size_t dlen
,
497 const char* tgt
= buf
+ offset
+ dlen
;
498 if( tgt
> (buf
+ buflen
) ) {
500 TRACE( (void)tmfprintf( log
, "+++ BUFFER OVERRUN at: "
501 "buf=[%p], size=[%lu] - intending to access [%p]: "
502 "offset=[%lu], data_length=[%lu]\n",
503 buf
, (u_long
)buflen
, tgt
, (u_long
)offset
,
513 /* create timestamp string in YYYY-mm-dd HH24:MI:SS.MSEC from struct timeval
516 mk_tvstamp( const struct timeval
* tv
, char* buf
, size_t* len
,
519 const char tmfmt_TZ
[] = "%Y-%m-%d %H:%M:%S.%%06ld %Z";
521 char tfmt_ms
[ 80 ] = { '\0' };
523 struct tm src_tm
, *p_tm
;
526 assert( tv
&& buf
&& len
);
530 p_tm
= (flags
& TVSTAMP_GMT
)
531 ? gmtime_r( &clock
, &src_tm
)
532 : localtime_r( &clock
, &src_tm
);
534 perror("gmtime_r/localtime_r");
538 n
= strftime( tfmt_ms
, sizeof(tfmt_ms
) - 1, tmfmt_TZ
, &src_tm
);
540 perror( "strftime" );
544 n
= snprintf( buf
, *len
, tfmt_ms
, (long)tv
->tv_usec
);
546 perror( "snprintf" );
556 /* write timestamp-prepended formatted message to file
559 tmfprintf( FILE* stream
, const char* format
, ... )
562 int n
= -1, total
= 0, rc
= 0, NO_RESET
= 0;
563 char tstamp
[ 80 ] = {'\0'};
564 size_t ts_len
= sizeof(tstamp
) - 1;
565 struct timeval tv_now
;
566 const char* pidstr
= get_pidstr( NO_RESET
, NULL
);
568 (void)gettimeofday( &tv_now
, NULL
);
572 rc
= mk_tvstamp( &tv_now
, tstamp
, &ts_len
, 0 );
575 n
= fprintf( stream
, "%s\t%s\t", tstamp
, pidstr
);
579 va_start( ap
, format
);
580 n
= vfprintf( stream
, format
, ap
);
589 perror( "fprintf/vfprintf" );
593 return (0 != rc
) ? -1 : total
;
597 /* write timestamp-prepended message to file
600 tmfputs( const char* s
, FILE* stream
)
602 int n
= -1, rc
= 0, NO_RESET
= 0;
603 char tstamp
[ 80 ] = {'\0'};
604 size_t ts_len
= sizeof(tstamp
) - 1;
605 struct timeval tv_now
;
606 const char* pidstr
= get_pidstr( NO_RESET
, NULL
);
608 (void)gettimeofday( &tv_now
, NULL
);
612 rc
= mk_tvstamp( &tv_now
, tstamp
, &ts_len
, 0 );
615 if( (n
= fputs( tstamp
, stream
)) < 0 ||
616 (n
= fputs( "\t", stream
)) < 0 ||
617 (n
= fputs( pidstr
, stream
)) < 0 ||
618 (n
= fputs( "\t", stream
)) < 0 )
621 if( (n
= fputs( s
, stream
)) < 0 )
625 if( n
< 0 && errno
) {
629 return (0 != rc
) ? -1 : n
;
633 /* print out command-line
636 printcmdln( FILE* stream
, const char* msg
,
637 int argc
, char* const argv
[] )
644 (void)tmfprintf( stream
, "%s: ", msg
);
646 (void)tmfputs( "", stream
);
648 for( i
= 0; i
< argc
; ++i
)
649 (void)fprintf( stream
, "%s ", argv
[i
] );
650 (void)fputc( '\n', stream
);
654 /* convert timespec to time_t
656 * timespec format: [+|-]dd:hh24:mi.ss
658 * @return 0 if success, n>0 if errno > 0,
659 * n < 0 otherwise (see ERR_ codes)
662 a2time( const char* str
, time_t* t
, time_t from
)
664 int field
[ 4 ] = {0};
665 const size_t field_LEN
= sizeof(field
)/sizeof(field
[0]);
668 int i_sec
, i_min
, i_hour
, i_day
;
669 int n
= 0, fi
= 0, i
= 0, new_field
= 0, has_seconds
= 0;
671 time_t tgt_time
= (time_t)-1, offset_tm
= (time_t)0;
674 static const int ERR_HSEC
= -2;
675 static const int ERR_FIELDLEN
= -3;
676 static const int ERR_NONDIGIT
= -4;
680 /* check if timespec is an offset (to current time) */
682 if( ('-' == str
[n
]) || ('+' == str
[n
]) ) {
683 is_offset
= ('-' == str
[n
]) ? -1 : 1;
687 /* read every field into an array element
689 for( fi
= 0; (size_t)fi
< field_LEN
; ++n
) {
690 if( '\0' == str
[n
] ) break;
692 /* seconds has to be the last field in timespec
693 * if seconds field has already been processed
695 if( (':' == str
[n
]) && has_seconds
) return ERR_HSEC
;
697 /* delimiter - move on to the next timespec field */
698 if( ':' == str
[n
] || ('.' == str
[n
]) ) {
700 if( '.' == str
[n
] ) has_seconds
= 1;
704 if( !isdigit( str
[n
] ) ) return ERR_NONDIGIT
;
712 field
[ fi
] += (str
[n
] - '0');
714 if( (fi
<= 0) || (size_t)fi
>= field_LEN
) return ERR_FIELDLEN
;
716 /* last field is seconds, the one before is hours, etc. */
718 i_sec
= has_seconds
? i
-- : -1;
723 if( NULL
== localtime_r( &now
, &stm
) ) {
728 (void) fprintf( stderr, "sec[%d], min[%d], hour[%d], day[%d]\n",
729 stm.tm_sec, stm.tm_min, stm.tm_hour, stm.tm_mday );
730 (void) fprintf( stderr, "i_sec[%d], i_min[%d], i_hour[%d], i_day[%d]\n",
731 i_sec, i_min, i_hour, i_day );
735 /* hours and days default to current value */
737 stm
.tm_hour
= field
[ i_hour
];
740 stm
.tm_mday
= field
[ i_day
];
742 stm
.tm_sec
= (i_sec
< 0) ? 0 : field
[ i_sec
];
743 stm
.tm_min
= (i_min
< 0) ? 0 : field
[ i_min
];
746 (void) fprintf( stderr, "sec[%d], min[%d], hour[%d], day[%d]\n",
747 stm.tm_sec, stm.tm_min, stm.tm_hour, stm.tm_mday );
750 tgt_time
= mktime( &stm
);
754 (void) fprintf( stderr, "sec[%d], min[%d], hour[%d], day[%d]\n",
755 (i_sec < 0 ? 0 : field[i_sec]), (i_min < 0 ? 0 : field[i_min]),
756 (i_hour < 0 ? 0 : field[i_hour]),(i_day < 0 ? 0 : field[i_day]) );
759 offset_tm
= ( (i_sec
< 0 ? 0 : field
[ i_sec
]) +
760 60 * (i_min
< 0 ? 0 : field
[ i_min
]) +
761 3600 * (i_hour
< 0 ? 0 : field
[ i_hour
]) +
762 (3600*24) * (i_day
< 0 ? 0 : field
[ i_day
])
764 tgt_time
= now
+ is_offset
* offset_tm
;
770 return ((time_t)-1 == tgt_time
) ? -1 : 0;
776 /* convert ASCII size spec (positive) into numeric value
778 * num[modifier], where num is an ASCII representation of
779 * any positive integer and
780 * modifier ::= [Kb|K|Mb|M|Gb|G]
783 a2double( const char* str
, double* pval
)
786 const char* p
= NULL
;
790 static const int ERR_OVERRUN
= -3;
791 static const int ERR_NUMFMT
= -4;
792 static const int ERR_BADMULT
= -5;
795 char buf
[ MAX_SZLEN
] = {0};
799 /* skip to the first */
800 for( p
= str
; *p
&& !isalpha(*p
); ++p
);
803 /* there is a modifier, calculate multiplication
805 if( 0 == strncasecmp( p
, "Kb", MAX_SZLEN
) ||
806 0 == strncasecmp( p
, "K", MAX_SZLEN
))
808 else if( 0 == strncasecmp( p
, "Mb", MAX_SZLEN
) ||
809 0 == strncasecmp( p
, "M", MAX_SZLEN
) )
810 mult
= (1024 * 1024);
811 else if( 0 == strncasecmp( p
, "Gb", MAX_SZLEN
) ||
812 0 == strncasecmp( p
, "G", MAX_SZLEN
) )
813 mult
= (1024 * 1024 * 1024);
819 if( numsz
>= (size_t)MAX_SZLEN
) return ERR_OVERRUN
;
820 (void) memcpy( buf
, str
, numsz
);
822 /*(void)fprintf( stderr, "buf=[%s]\n", buf);*/
825 dval
= strtod( buf
, (char**)NULL
);
826 if( errno
) return ERR_NUMFMT
;
828 /* apply the modifier-induced multiplication factor */
833 dval
= strtod( str
, (char**)NULL
);
834 if( errno
) return ERR_NUMFMT
;
838 /*fprintf( stderr, "dval = [%f]\n", dval );*/
848 a2size( const char* str
, ssize_t
* pval
)
852 static const int ERR_OVFLW
= -2;
854 if( 0 != (rc
= a2double( str
, &dval
)) )
857 if( dval
> LONG_MAX
|| dval
< LONG_MIN
)
861 *pval
= (ssize_t
)dval
;
868 a2int64( const char* str
, int64_t* pval
)
870 /* NB: use LLONG_MAX and LLONG_MIN recommended when
871 * compiling with C99 compliance;
873 * we still (yet) complie under C89, so LLONGMAX64,
874 * LLONGMIN64 are introduced instead to avoid C99-related
878 # define LLONGMAX64 9223372036854775807.0
879 # define LLONGMIN64 (-LLONGMAX64 - 1.0)
883 static const int ERR_OVFLW
= -2;
885 if( 0 != (rc
= a2double( str
, &dval
)) )
888 if( dval
> LLONGMAX64
|| dval
< LLONGMIN64
)
892 *pval
= (int64_t)dval
;
898 /* returns asctime w/o CR character at the end
901 Zasctime( const struct tm
* tm
)
906 static char buf
[ ASCBUF_SZ
], *p
= NULL
;
909 p
= asctime_r( tm
, buf
);
910 if( NULL
== p
) return p
;
912 buf
[ ASCBUF_SZ
- 1 ] = '\0';
914 if( (n
> (size_t)1) && ('\n' == buf
[ n
- 1 ]) ) {
922 /* adjust nice value if needed
925 set_nice( int val
, FILE* log
)
932 newval
= nice( val
);
933 if( (-1 == newval
) && (0 != errno
) ) {
934 mperror( log
, errno
, "%s: nice", __func__
);
938 TRACE( (void)tmfprintf( log
, "Nice value incremented by %d\n",
947 /* check and report a deviation in values: n_was and n_is are not supposed
948 * to differ more than by delta
951 check_fragments( const char* action
, ssize_t total
, ssize_t n_was
, ssize_t n_is
,
952 ssize_t delta
, FILE* log
)
954 if( NULL
== action
) return;
956 if( (n_is
< (n_was
- delta
)) || (n_is
> (n_was
+ delta
)) ) {
957 (void)tmfprintf( log
,
958 "%s [%ld] bytes out of [%ld], last=[%ld]\n",
959 action
, (long)n_is
, (long)total
, (long)n_was
);
964 /* retrieve UNIX time value from given environment
965 * variable, otherwise return default */
967 get_timeval( const char* envar
, const time_t deflt
)
969 char *str
= NULL
, *eptr
= NULL
;
973 if( NULL
== (str
= getenv( envar
)) )
977 tval
= (time_t) strtol( str
, &eptr
, 10 );
978 if( errno
) return deflt
;
980 if ( eptr
&& (0 == *eptr
) ) return tval
;
986 /* retrieve flag value as 1 = true, 0 = false
987 * from an environment variable set in the form
988 * of 0|1|'true'|'false'|'yes'|'no'
991 get_flagval( const char* envar
, const int deflt
)
993 long lval
= (long)deflt
;
994 char *str
= NULL
, *eptr
= NULL
;
997 static char* ON_sym
[] = { "true", "yes", "on" };
998 static char* OFF_sym
[] = { "false", "no", "off" };
1001 str
= getenv( envar
);
1006 lval
= strtol( str
, &eptr
, 10 );
1007 if( errno
) return deflt
;
1009 if ( eptr
&& (0 == *eptr
) ) return lval
;
1011 for( i
= 0; i
< sizeof(ON_sym
)/sizeof(ON_sym
[0]); ++i
)
1012 if( 0 == strcasecmp(str
, ON_sym
[i
]) ) return 1;
1014 for( i
= 0; i
< sizeof(OFF_sym
)/sizeof(OFF_sym
[0]); ++i
)
1015 if( 0 == strcasecmp(str
, OFF_sym
[i
]) ) return 0;
1021 /* retrieve SSIZE value from given environment
1022 * variable, otherwise return default */
1024 get_sizeval( const char* envar
, const ssize_t deflt
)
1026 char *str
= NULL
, *eptr
= NULL
;
1030 if( NULL
== (str
= getenv( envar
)) )
1034 lval
= strtol( str
, &eptr
, 10 );
1035 if( errno
) return deflt
;
1037 if ( eptr
&& (0 == *eptr
) ) return lval
;
1044 /* retrieve/reset string representation of pid
1047 get_pidstr( int reset
, const char* pfx
)
1049 static char pidstr
[ 24 ];
1050 static pid_t pid
= 0;
1052 if( 1 == reset
|| (0 == pid
) ) {
1055 (void) snprintf (pidstr
, sizeof(pidstr
)-1, "%s(%d)", pfx
, pid
);
1057 (void) snprintf (pidstr
, sizeof(pidstr
)-1, "%d", pid
);
1059 pidstr
[sizeof(pidstr
)-1] = '\0';
1066 /* retrieve system info string
1069 get_sysinfo (int* perr
)
1074 if (s_sysinfo
[0]) return s_sysinfo
;
1076 (void) memset (&uts
, 0, sizeof(uts
));
1077 errno
= 0; rc
= uname (&uts
);
1078 if (perr
) *perr
= errno
;
1081 s_sysinfo
[sizeof(s_sysinfo
)-1] = '\0';
1082 (void) snprintf (s_sysinfo
, sizeof(s_sysinfo
)-1, "%s %s %s",
1083 uts
.sysname
, uts
.release
, uts
.machine
);
1090 mk_app_info(const char *appname
, char *info
, size_t infolen
)
1093 if ('\0' == *info
) {
1094 (void) snprintf(info
, infolen
,
1095 "%s %s-%d.%d (%s) %s [%s]", appname
, VERSION
,
1096 BUILDNUM
, PATCH
, BUILD_TYPE
,
1097 COMPILE_MODE
, get_sysinfo(NULL
) );