1 /* @(#) udpxy server: main module
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/>.
21 #include "osdef.h" /* os-specific definitions */
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <arpa/inet.h>
30 #include <netinet/in.h>
33 /* #include <sys/select.h> */
61 /* external globals */
63 extern const char CMD_UDP
[];
64 extern const char CMD_STATUS
[];
65 extern const char CMD_RESTART
[];
66 extern const char CMD_RTP
[];
68 extern const size_t CMD_UDP_LEN
;
69 extern const size_t CMD_STATUS_LEN
;
70 extern const size_t CMD_RESTART_LEN
;
71 extern const size_t CMD_RTP_LEN
;
73 extern const char IPv4_ALL
[];
75 extern const char UDPXY_COPYRIGHT_NOTICE
[];
76 extern const char UDPXY_CONTACT
[];
77 extern const char COMPILE_MODE
[];
78 extern const char VERSION
[];
79 extern const int BUILDNUM
;
80 extern const char BUILD_TYPE
[];
83 extern volatile sig_atomic_t g_quit
;
94 struct udpxy_opt g_uopt
;
99 static volatile sig_atomic_t g_childexit
= 0;
101 static const int PID_RESET
= 1;
103 /*********************************************************/
105 /* handler for signals to perform a graceful exit
108 handle_quitsigs(int signo
)
110 g_quit
= (sig_atomic_t)1;
113 TRACE( (void)tmfprintf( g_flog
, "*** Caught SIGNAL %d ***\n", signo
) );
118 /* return 1 if the application must gracefully quit
121 must_quit() { return g_quit
; }
127 handle_sigchld(int signo
)
130 g_childexit
= (sig_atomic_t)1;
132 TRACE( (void)tmfprintf( g_flog
, "*** Caught SIGCHLD (%d) ***\n", signo
) );
137 static int get_childexit() { return g_childexit; }
140 /* clear SIGCHLD flag and adjust context if needed
143 wait_children( struct server_ctx
* ctx
, int options
)
149 if (0 == g_childexit
) {
150 TRACE( (void)tmfputs ("No children exited since last check\n",
157 TRACE( (void)tmfputs ("Waiting on exited children\n", g_flog
) );
158 while( 0 < (pid
= waitpid( -1, &status
, options
)) ) {
159 TRACE( (void)tmfprintf( g_flog
, "Client [%d] has exited.\n", pid
) );
160 delete_client( ctx
, pid
);
164 if( (-1 == pid
) && ( ECHILD
!= errno
) ) {
165 mperror(g_flog
, errno
, "%s: waitpid", __func__
);
169 TRACE( (void)tmfprintf (g_flog
, "Cleaned up %d children, "
170 "%ld still running\n", n
, (long)(ctx
->clmax
- ctx
->clfree
)) );
177 /* wait for all children to quit
180 wait_all( struct server_ctx
* ctx
) { wait_children( ctx
, 0 ); }
183 /* wait for the children who already terminated
186 wait_terminated( struct server_ctx
* ctx
) { wait_children( ctx
, WNOHANG
); }
189 /* print out array of accepted socket fd's */
191 print_fds (FILE* fp
, const char* msg
, tmfd_t
* asock
, size_t len
)
195 (void) fprintf (fp
, "%s [%ld]: ", msg
, (long)len
);
196 for (i
= 0; i
< len
; ++i
) {
197 (void) fprintf (fp
, "%s%d", (i
? "," : ""), asock
[i
].fd
);
205 /* read HTTP request from sockfd, parse it into command
206 * and its parameters (for instance, command='udp' and
207 * parameters being '192.168.0.1:5002')
210 read_command( int sockfd
, char* cmd
, size_t clen
,
211 char* param
, size_t plen
)
213 #define DBUF_SZ 2048 /* max size for raw data with HTTP request */
214 #define RBUF_SZ 512 /* max size for url-derived request */
215 char httpbuf
[ DBUF_SZ
] = "\0", request
[ RBUF_SZ
] = "\0";
220 assert( (sockfd
> 0) && cmd
&& clen
&& param
&& plen
);
221 cmd
[0] = '\0'; /* forget previous command */
223 TRACE( (void)tmfprintf( g_flog
, "Reading command from socket [%d]\n",
225 hlen
= recv( sockfd
, httpbuf
, sizeof(httpbuf
), 0 );
228 mperror(g_flog
, rc
, "%s - recv (%d)", __func__
, rc
);
232 (void) tmfprintf (g_flog
, "%s: client closed socket [%d]\n",
237 /* DEBUG - re-enable if needed */
238 TRACE( (void)tmfprintf( g_flog
, "HTTP buffer [%ld bytes] received\n%s", (long)hlen
, httpbuf
) );
239 /* TRACE( (void) save_buffer( httpbuf, hlen, "/tmp/httpbuf.dat" ) ); */
241 rlen
= sizeof(request
);
242 rc
= get_request( httpbuf
, (size_t)hlen
, request
, &rlen
);
245 TRACE( (void)tmfprintf( g_flog
, "Request=[%s], length=[%lu]\n",
246 request
, (u_long
)rlen
) );
248 rc
= parse_param( request
, rlen
, cmd
, clen
, param
, plen
);
250 TRACE( (void)tmfprintf( g_flog
, "Command [%s] with params [%s]"
251 " read from socket=[%d]\n", cmd
, param
, sockfd
) );
258 /* terminate the client process
261 terminate( pid_t pid
)
263 TRACE( (void)tmfprintf( g_flog
, "Forcing client process [%d] to QUIT\n",
266 if( pid
<= 0 ) return 0;
268 if( 0 != kill( pid
, SIGQUIT
) ) {
269 if( ESRCH
!= errno
) {
270 mperror(g_flog
, errno
, "%s - kill", __func__
);
273 /* ESRCH could mean client has quit already;
274 * if so we should wait for it */
276 TRACE( (void)tmfprintf( g_flog
, "Process [%d] is not running.\n",
284 /* terminate all clients
287 terminate_all_clients( struct server_ctx
* ctx
)
292 for( i
= 0; i
< ctx
->clmax
; ++i
) {
293 pid
= ctx
->cl
[i
].pid
;
294 if( pid
> 0 ) (void) terminate( pid
);
301 /* send HTTP response to socket
304 send_http_response( int sockfd
, int code
, const char* reason
, ... )
306 static char msg
[ 3072 ];
309 static const char CONTENT_TYPE
[] = "Content-Type:application/octet-stream";
311 assert( (sockfd
> 0) && code
&& reason
);
315 if ((200 == code
) && g_uopt
.h200_ftr
[0]) {
316 msglen
= snprintf( msg
, sizeof(msg
) - 1, "HTTP/1.1 %d %s\n%s\n%s\n\n",
317 code
, reason
, CONTENT_TYPE
, g_uopt
.h200_ftr
);
320 msglen
= snprintf( msg
, sizeof(msg
) - 1, "HTTP/1.1 %d %s\n%s\n\n",
321 code
, reason
, CONTENT_TYPE
);
323 if( msglen
<= 0 ) return ERR_INTERNAL
;
325 nsent
= send( sockfd
, msg
, msglen
, 0 );
327 mperror(g_flog
, errno
, "%s - send", __func__
);
331 TRACE( (void)tmfprintf( g_flog
, "Sent HTTP response code=[%d], "
332 "reason=[%s] to socket=[%d]\n%s\n",
333 code
, reason
, sockfd
, msg
) );
338 /* renew multicast subscription if g_uopt.mcast_refresh seconds
339 * have passed since the last renewal
342 check_mcast_refresh( int msockfd
, time_t* last_tm
,
343 const struct in_addr
* mifaddr
)
347 if( NULL
!= g_uopt
.srcfile
) /* reading from file */
350 assert( (msockfd
> 0) && last_tm
&& mifaddr
);
353 if( difftime( now
, *last_tm
) >= (double)g_uopt
.mcast_refresh
) {
354 (void) renew_multicast( msockfd
, mifaddr
);
362 /* analyze return value of I/O routines (read_data/write_data)
363 * and the pause-time marker to determine if we are in a
367 pause_detect( int ntrans
, time_t* p_pause
)
370 const double MAX_PAUSE_SEC
= 5.0;
374 /* timeshift: detect PAUSE by would-block error */
375 if (IO_BLK
== ntrans
) {
379 if( difftime(now
, *p_pause
) > MAX_PAUSE_SEC
) {
380 TRACE( (void)tmfprintf( g_flog
,
381 "PAUSE timed out after [%.0f] seconds\n",
388 TRACE( (void)tmfprintf( g_flog
, "PAUSE started\n" ) );
394 TRACE( (void)tmfprintf( g_flog
, "PAUSE ended\n" ) );
402 /* calculate values for:
403 * 1. number of messages to fit into data buffer
404 * 2. recommended (minimal) size of socket buffer
405 * (to read into the data buffer)
408 calc_buf_settings( ssize_t
* bufmsgs
, size_t* sock_buflen
)
410 ssize_t nmsgs
= -1, max_buf_used
= -1, env_snd_buflen
= -1;
413 /* how many messages should we process? */
414 nmsgs
= (g_uopt
.rbuf_msgs
> 0) ? g_uopt
.rbuf_msgs
:
415 (int)g_uopt
.rbuf_len
/ ETHERNET_MTU
;
417 /* how many bytes could be written at once
418 * to the send socket */
419 max_buf_used
= (g_uopt
.rbuf_msgs
> 0)
420 ? (ssize_t
)(nmsgs
* ETHERNET_MTU
) : g_uopt
.rbuf_len
;
421 if (max_buf_used
> g_uopt
.rbuf_len
) {
422 max_buf_used
= g_uopt
.rbuf_len
;
425 assert( max_buf_used
>= 0 );
427 env_snd_buflen
= get_sizeval( "UDPXY_SOCKBUF_LEN", 0);
428 buflen
= (env_snd_buflen
> 0) ? (size_t)env_snd_buflen
: (size_t)max_buf_used
;
430 if (buflen
< (size_t) MIN_SOCKBUF_LEN
) {
431 buflen
= (size_t) MIN_SOCKBUF_LEN
;
434 /* cannot go below the size of effective usage */
435 if( buflen
< (size_t)max_buf_used
) {
436 buflen
= (size_t)max_buf_used
;
439 if (bufmsgs
) *bufmsgs
= nmsgs
;
440 if (sock_buflen
) *sock_buflen
= buflen
;
442 TRACE( (void)tmfprintf( g_flog
,
443 "min socket buffer = [%ld], "
444 "max space to use = [%ld], "
446 (long)buflen
, (long)max_buf_used
, (long)nmsgs
) );
452 /* make send-socket (dsockfd) buffer size no less
453 * than that of read-socket (ssockfd)
456 sync_dsockbuf_len( int ssockfd
, int dsockfd
)
458 size_t curr_sendbuf_len
= 0, curr_rcvbuf_len
= 0;
461 if ( 0 != g_uopt
.nosync_dbuf
) {
462 TRACE( (void)tmfprintf( g_flog
,
463 "Must not adjust buffer size "
464 "for send socket [%d]\n", dsockfd
) );
468 assert( ssockfd
&& dsockfd
);
470 rc
= get_sendbuf( dsockfd
, &curr_sendbuf_len
);
471 if (0 != rc
) return rc
;
473 rc
= get_rcvbuf( ssockfd
, &curr_rcvbuf_len
);
474 if (0 != rc
) return rc
;
476 if ( curr_rcvbuf_len
> curr_sendbuf_len
) {
477 rc
= set_sendbuf( dsockfd
, curr_rcvbuf_len
);
478 if (0 != rc
) return rc
;
485 /* relay traffic from source to destination socket
489 relay_traffic( int ssockfd
, int dsockfd
, struct server_ctx
* ctx
,
490 int dfilefd
, const struct in_addr
* mifaddr
)
492 volatile sig_atomic_t quit
= 0;
496 ssize_t nrcv
= 0, nsent
= 0, nwr
= 0,
499 size_t data_len
= g_uopt
.rbuf_len
;
500 struct rdata_opt ropt
;
501 time_t pause_time
= 0, rfr_tm
= time(NULL
);
504 const int ALLOW_PAUSES
= get_flagval( "UDPXY_ALLOW_PAUSES", 0 );
506 /* permissible variation in data-packet size */
507 static const ssize_t t_delta
= 0x20;
509 struct dstream_ctx ds
;
511 static const int SET_PID
= 1;
514 assert( ctx
&& mifaddr
);
516 (void) sigemptyset (&ubset
);
517 sigaddset (&ubset
, SIGINT
);
518 sigaddset (&ubset
, SIGQUIT
);
519 sigaddset (&ubset
, SIGTERM
);
521 /* restore the ability to receive *quit* signals */
522 rc
= sigprocmask (SIG_UNBLOCK
, &ubset
, NULL
);
524 mperror (g_flog
, errno
, "%s: sigprocmask", __func__
);
528 /* NOPs to eliminate warnings in lean version */
529 lrcv
= t_delta
- t_delta
+ lsent
;
531 check_fragments( NULL
, 0, 0, 0, 0, g_flog
);
536 rc
= calc_buf_settings( &nmsgs
, NULL
);
537 if (0 != rc
) return -1;
539 TRACE( (void)tmfprintf( g_flog
, "Data buffer will hold up to "
540 "[%d] messages\n", nmsgs
) );
542 rc
= init_dstream_ctx( &ds
, ctx
->cmd
, g_uopt
.srcfile
, nmsgs
);
543 if( 0 != rc
) return -1;
545 (void) set_nice( g_uopt
.nice_incr
, g_flog
);
548 if( NULL
== g_uopt
.srcfile
) {
549 rc
= set_timeouts( ssockfd
, dsockfd
,
556 rc
= sync_dsockbuf_len( ssockfd
, dsockfd
);
559 rc
= send_http_response( dsockfd
, 200, "OK" );
562 /* timeshift: to detect PAUSE make destination
563 * socket non-blocking, otherwise make it blocking
564 * (since it might have been set unblocking earlier)
566 rc
= set_nblock( dsockfd
, (ALLOW_PAUSES
? 1 : 0) );
570 data
= malloc(data_len
);
572 mperror( g_flog
, errno
, "%s: malloc", __func__
);
576 if( g_uopt
.cl_tpstat
)
577 tpstat_init( &tps
, SET_PID
);
580 TRACE( (void)tmfprintf( g_flog
, "Relaying traffic from socket[%d] "
581 "to socket[%d], buffer size=[%d], Rmsgs=[%d], pauses=[%d]\n",
582 ssockfd
, dsockfd
, data_len
, g_uopt
.rbuf_msgs
, ALLOW_PAUSES
) );
586 ropt
.max_frgs
= g_uopt
.rbuf_msgs
;
587 ropt
.buf_tmout
= g_uopt
.dhold_tmout
;
591 while( (0 == rc
) && !(quit
= must_quit()) ) {
592 if( g_uopt
.mcast_refresh
> 0 ) {
593 check_mcast_refresh( ssockfd
, &rfr_tm
, mifaddr
);
596 nrcv
= read_data( &ds
, ssockfd
, data
, data_len
, &ropt
);
597 if( -1 == nrcv
) break;
599 TRACE( check_fragments( "received new", data_len
,
600 lrcv
, nrcv
, t_delta
, g_flog
) );
603 if( dsockfd
&& (nrcv
> 0) ) {
604 nsent
= write_data( &ds
, data
, nrcv
, dsockfd
);
605 if( -1 == nsent
) break;
608 if ( !ALLOW_PAUSES
) break;
609 if ( 0 != pause_detect( nsent
, &pause_time
) ) break;
612 TRACE( check_fragments("sent", nrcv
,
613 lsent
, nsent
, t_delta
, g_flog
) );
617 if( (dfilefd
> 0) && (nrcv
> 0) ) {
618 nwr
= write_data( &ds
, data
, nrcv
, dfilefd
);
621 TRACE( check_fragments( "wrote to file",
622 nrcv
, lsent
, nwr
, t_delta
, g_flog
) );
626 if( ds
.flags
& F_SCATTERED
) reset_pkt_registry( &ds
);
628 if( uf_TRUE
== g_uopt
.cl_tpstat
)
629 tpstat_update( ctx
, &tps
, nsent
);
631 } /* end of RELAY LOOP */
635 TRACE( (void)tmfprintf( g_flog
, "Exited relay loop: received=[%ld], "
636 "sent=[%ld], quit=[%ld]\n", (long)nrcv
, (long)nsent
, (long)quit
) );
638 free_dstream_ctx( &ds
);
639 if( NULL
!= data
) free( data
);
641 if( 0 != (quit
= must_quit()) ) {
642 TRACE( (void)tmfprintf( g_flog
, "Child process=[%d] must quit\n",
650 /* process command to relay udp traffic
654 udp_relay( int sockfd
, const char* param
, size_t plen
,
655 const struct in_addr
* mifaddr
,
656 struct server_ctx
* ctx
)
658 char mcast_addr
[ IPADDR_STR_SIZE
];
659 struct sockaddr_in addr
;
664 int msockfd
= -1, sfilefd
= -1,
665 dfilefd
= -1, srcfd
= -1;
666 char dfile_name
[ MAXPATHLEN
];
667 size_t rcvbuf_len
= 0;
669 assert( (sockfd
> 0) && param
&& plen
&& ctx
);
671 TRACE( (void)tmfprintf( g_flog
, "udp_relay : new_socket=[%d] param=[%s]\n",
674 rc
= parse_udprelay( param
, plen
, mcast_addr
, IPADDR_STR_SIZE
, &port
);
676 (void) tmfprintf( g_flog
, "Error [%d] parsing parameters [%s]\n",
681 if( 1 != inet_aton(mcast_addr
, &addr
.sin_addr
) ) {
682 (void) tmfprintf( g_flog
, "Invalid address: [%s]\n", mcast_addr
);
687 addr
.sin_family
= AF_INET
;
688 addr
.sin_port
= htons( (short)port
);
693 (void) send_http_response( sockfd
, 500, "Service error" );
697 /* start the (new) process to relay traffic */
699 if( 0 != (new_pid
= fork()) ) {
700 rc
= add_client( ctx
, new_pid
, mcast_addr
, port
, sockfd
);
701 return rc
; /* parent returns */
706 TRACE( (void)tmfprintf( g_flog
, "Client process=[%d] started "
707 "for socket=[%d]\n", getpid(), sockfd
) );
709 (void) get_pidstr( PID_RESET
, "c" );
711 (void)close( ctx
->lsockfd
);
713 /* close the reading end of the comm. pipe */
714 (void)close( ctx
->cpipe
[0] );
718 /* make write end of pipe non-blocking (we don't want to
719 * block on pipe write while relaying traffic)
721 if( -1 == (flags
= fcntl( ctx
->cpipe
[1], F_GETFL
)) ||
722 -1 == fcntl( ctx
->cpipe
[1], F_SETFL
, flags
| O_NONBLOCK
) ) {
723 mperror( g_flog
, errno
, "%s: fcntl", __func__
);
728 if( NULL
!= g_uopt
.dstfile
) {
729 (void) snprintf( dfile_name
, MAXPATHLEN
- 1,
730 "%s.%d", g_uopt
.dstfile
, getpid() );
731 dfilefd
= creat( dfile_name
, S_IRUSR
| S_IWUSR
| S_IRGRP
);
732 if( -1 == dfilefd
) {
733 mperror( g_flog
, errno
, "%s: g_uopt.dstfile open", __func__
);
738 TRACE( (void)tmfprintf( g_flog
,
739 "Dest file [%s] opened as fd=[%d]\n",
740 dfile_name
, dfilefd
) );
744 if( NULL
!= g_uopt
.srcfile
) {
745 sfilefd
= open( g_uopt
.srcfile
, O_RDONLY
| O_NOCTTY
);
746 if( -1 == sfilefd
) {
747 mperror( g_flog
, errno
, "%s: g_uopt.srcfile open", __func__
);
751 TRACE( (void) tmfprintf( g_flog
, "Source file [%s] opened\n",
757 rc
= calc_buf_settings( NULL
, &rcvbuf_len
);
759 rc
= setup_mcast_listener( &addr
, mifaddr
, &msockfd
,
760 (g_uopt
.nosync_sbuf
? 0 : rcvbuf_len
) );
766 rc
= relay_traffic( srcfd
, sockfd
, ctx
, dfilefd
, mifaddr
);
772 close_mcast_listener( msockfd
, mifaddr
);
775 (void) close( sfilefd
);
776 TRACE( (void) tmfprintf( g_flog
, "Source file [%s] closed\n",
780 (void) close( dfilefd
);
781 TRACE( (void) tmfprintf( g_flog
, "Dest file [%s] closed\n",
786 (void) send_http_response( sockfd
, 500, "Service error" );
789 (void) close( sockfd
);
790 free_server_ctx( ctx
);
794 TRACE( (void)tmfprintf( g_flog
, "Child process=[%d] exits with rc=[%d]\n",
797 if( g_flog
&& (stderr
!= g_flog
) ) {
798 (void) fclose(g_flog
);
801 free_uopt( &g_uopt
);
803 rc
= ( 0 != rc
) ? ERR_INTERNAL
: rc
;
804 exit(rc
); /* child exits */
810 /* send server status as HTTP response to the given socket
813 report_status( int sockfd
, const struct server_ctx
* ctx
, int options
)
818 size_t nlen
= 0, bufsz
, i
;
820 static size_t BYTES_HDR
= 2048;
821 static size_t BYTES_PER_CLI
= 512;
823 assert( (sockfd
> 0) && ctx
);
825 for (bufsz
=BYTES_HDR
, i
=0; i
< ctx
->clmax
; ++i
) {
826 bufsz
+=BYTES_PER_CLI
;
830 mperror(g_flog
, ENOMEM
, "malloc for %ld bytes for HTTP buffer "
831 "failed in %s", (long)bufsz
, __func__
);
835 (void) memset( buf
, 0, sizeof(bufsz
) );
838 rc
= mk_status_page( ctx
, buf
, &nlen
, options
| MSO_HTTP_HEADER
);
840 for( n
= nsent
= 0; (0 == rc
) && (nsent
< (ssize_t
)nlen
); ) {
842 n
= send( sockfd
, buf
, (int)nlen
, 0 );
844 if( (-1 == n
) && (EINTR
!= errno
) ) {
845 mperror(g_flog
, errno
, "%s: send", __func__
);
854 TRACE( (void)tmfprintf( g_flog
, "Error generating status report\n" ) );
858 TRACE( (void)tmfprintf( g_flog, "Saved status buffer to file\n" ) );
859 TRACE( (void)save_buffer(buf, nlen, "/tmp/status-udpxy.html") );
868 /* process command within a request
871 process_command( int new_sockfd
, struct server_ctx
* ctx
,
872 const char* param
, size_t plen
)
875 const int STAT_OPTIONS
= 0;
876 const int RESTART_OPTIONS
= MSO_SKIP_CLIENTS
| MSO_RESTART
;
878 assert( (new_sockfd
> 0) && ctx
&& param
);
880 if( 0 == strncmp( ctx
->cmd
, CMD_UDP
, sizeof(ctx
->cmd
) ) ||
881 0 == strncmp( ctx
->cmd
, CMD_RTP
, sizeof(ctx
->cmd
) ) ) {
883 rc
= udp_relay( new_sockfd
, param
, plen
,
884 &(ctx
->mcast_inaddr
), ctx
);
887 send_http_response( new_sockfd
, 401, "Bad request" );
888 (void)tmfprintf( g_flog
, "Client limit [%d] has been reached.\n",
892 else if( 0 == strncmp( ctx
->cmd
, CMD_STATUS
, sizeof(ctx
->cmd
) ) ) {
893 rc
= report_status( new_sockfd
, ctx
, STAT_OPTIONS
);
895 else if( 0 == strncmp( ctx
->cmd
, CMD_RESTART
, sizeof(ctx
->cmd
) ) ) {
896 (void) report_status( new_sockfd
, ctx
, RESTART_OPTIONS
);
898 terminate_all_clients( ctx
);
902 TRACE( (void)tmfprintf( g_flog
, "Unrecognized command [%s]"
903 " - ignoring.\n", ctx
->cmd
) );
904 send_http_response( new_sockfd
, 401, "Unrecognized request" );
912 accept_requests (int sockfd
, tmfd_t
* asock
, size_t* alen
)
914 int rc
= 0, new_sockfd
= -1, err
= 0, peer_port
= -1,
915 wmark
= g_uopt
.ss_rlwmark
;
916 size_t nmax
= *alen
, naccepted
= 0;
917 struct sockaddr_in cliaddr
;
918 a_socklen_t addrlen
= sizeof (cliaddr
);
919 char peer_addr
[128] = "#undef#";
921 while (naccepted
< nmax
) {
922 TRACE( (void)tmfputs ("Accepting new connection\n", g_flog
) );
924 new_sockfd
= accept (sockfd
, (struct sockaddr
*)&cliaddr
, &addrlen
);
925 if (-1 == new_sockfd
) {
927 if ((EWOULDBLOCK
== err
) || (EAGAIN
== err
)) {
928 TRACE((void)tmfputs ("Nothing more to accept\n", g_flog
));
931 if ((ECONNABORTED
== err
) || (ECONNRESET
== err
) || (EPROTO
== err
)) {
932 TRACE( (void)tmfprintf (g_flog
, "Connection aborted/reset "
933 "at accept point, errno=%d\n", err
) );
937 mperror(g_flog
, err
, "%s: accept", __func__
);
941 if (0 != set_nblock (new_sockfd
, 1)) {
942 (void) close (new_sockfd
); /* TODO: error-aware close */
946 if (0 != set_timeouts(new_sockfd, new_sockfd, g_uopt.sr_tmout, 0,
947 g_uopt.sw_tmout, 0)) {
948 (void) close (new_sockfd);
953 if (0 != setsockopt (new_sockfd
, SOL_SOCKET
, SO_RCVLOWAT
,
954 (char*)&wmark
, sizeof(wmark
))) {
955 mperror (g_flog
, errno
, "%s: setsockopt SO_RCVLOWAT [%d]",
957 (void) close (new_sockfd
); /* TODO: error-aware close */
960 TRACE( (void)tmfprintf (g_flog
, "Receive LOW WATERMARK [%d] appleid "
961 "to newly-accepted socket [%d]\n", wmark
, new_sockfd
) );
965 asock
[naccepted
].fd
= new_sockfd
;
966 asock
[naccepted
].atime
= time (NULL
);
969 (void) get_peerinfo (new_sockfd
, peer_addr
,
970 sizeof(peer_addr
)-1, &peer_port
);
972 TRACE( (void)tmfprintf( g_flog
, "Accepted socket=[%d] from %s:%d "
973 "n=%ld/nmax=%ld\n", new_sockfd
, peer_addr
, peer_port
,
974 (long)naccepted
, (long)nmax
) );
977 if (naccepted
>= nmax
) {
978 (void)tmfprintf (g_flog
, "Accept limit max=[%d] reached, "
979 "%ld already accepted", (long)nmax
, (long)naccepted
);
983 TRACE( (void)tmfprintf (g_flog
, "%s: Sockets accepted: [%ld]\n",
984 __func__
, (long)naccepted
));
990 shrink_asock (tmfd_t
* asock
, size_t* alen
, size_t nserved
)
997 /* uncomment to DEBUG
998 TRACE( print_fds (g_flog, "unshrunk accepted sockets", asock, *alen) );
1003 for (; j
< (long)nmax
; ++j
) {
1004 if (-1 == asock
[j
].fd
) {
1010 asock
[j
- v
].fd
= asock
[j
].fd
;
1011 asock
[j
- v
].atime
= asock
[j
].atime
;
1016 assert ((long)nserved
== v
);
1017 *alen
= nmax
- (size_t)v
;
1019 TRACE ( (void)tmfprintf (g_flog
, "%s: %ld shrunk, was %ld now %ld\n",
1020 __func__
, (long)v
, (long)nmax
, (long)*alen
) );
1022 /* uncomment to DEBUG
1023 TRACE( print_fds (g_flog, "remaining accepted sockets", asock, *alen) );
1029 tmout_requests (tmfd_t
* asock
, size_t *alen
)
1031 size_t nmax
= *alen
, i
= 0, nout
= 0;
1032 time_t now
= time (NULL
);
1034 TRACE( (void)tmfprintf (g_flog
, "%s: BEGIN with %ld sockets\n",
1035 __func__
, (long)*alen
) );
1037 for (; i
< nmax
; ++i
) {
1038 assert ((asock
[i
].fd
>= 0) && asock
[i
].atime
);
1039 if ((asock
[i
].atime
+ g_uopt
.ssel_tmout
) < now
) {
1040 TRACE( (void)tmfprintf (g_flog
, "%s: timed out socket #%d [%d], "
1041 "atime/now/tmout=%ld/%ld/%ld\n", __func__
, (i
+1), asock
[i
].fd
,
1042 (long)asock
[i
].atime
, (long)now
, g_uopt
.ssel_tmout
) );
1044 (void) close (asock
[i
].fd
);
1049 shrink_asock (asock
, alen
, nout
); /* will adjust alen */
1051 TRACE( (void)tmfprintf (g_flog
, "%s: END with %ld sockets\n",
1052 __func__
, (long)*alen
) );
1057 process_requests (tmfd_t
* asock
, size_t *alen
, fd_set
* rset
, struct server_ctx
* srv
)
1059 size_t nmax
= *alen
, i
= 0, nserved
= 0;
1060 int rc
= 0, served
= 0;
1061 char param
[ 128 ] = "\0";
1062 time_t now
= time (NULL
);
1064 /* uncomment to DEBUG */
1065 TRACE( print_fds (g_flog
, "pre-process sockets", asock
, nmax
) );
1067 for (; i
< nmax
; ++i
, served
= 0) {
1068 assert (asock
[i
].fd
>= 0);
1069 assert (asock
[i
].atime
> 0);
1072 /* not selected - yet try to time it out */
1073 if (!FD_ISSET(asock
[i
].fd
, rset
)) {
1074 if ((asock
[i
].atime
+ g_uopt
.ssel_tmout
) < now
) {
1075 TRACE( (void)tmfprintf (g_flog
,
1076 "%s: accepted socket [%ld] timed out\n",
1077 __func__
, (long)asock
[i
].fd
) );
1078 ++served
; /* timed out - must close */
1084 TRACE( (void)tmfprintf (g_flog
, "acting on accepted socket "
1085 "[%d] (%d/%d)\n", asock
[i
].fd
, i
+1, nmax
) );
1087 ++served
; /* selected - must close regardless */
1088 rc
= read_command( asock
[i
].fd
, srv
->cmd
, sizeof(srv
->cmd
),
1089 param
, sizeof(param
) );
1090 if( 0 != rc
) break;
1092 rc
= process_command (asock
[i
].fd
, srv
, param
, sizeof(param
) );
1096 TRACE( (void)tmfprintf (g_flog
, "error [%d] processing "
1097 "client socket [%d]\n", rc
, asock
[i
]));
1100 TRACE( (void)tmfprintf (g_flog
, "%s: %s accepted "
1101 "socket [%d]\n", __func__
, (served
? "closing" : "skipping"),
1105 (void) close (asock
[i
].fd
);
1111 TRACE( (void)tmfprintf (g_flog
, "Processed [%ld/%ld] accepted sockets\n",
1112 (long)nserved
, (long)nmax
) );
1113 TRACE( print_fds (g_flog
, "newly-accepted sockets", asock
, nmax
) );
1115 if (nserved
>= nmax
) {
1117 TRACE( (void)tmfputs ("All accepted sockets processed\n", g_flog
) );
1120 shrink_asock (asock
, alen
, nserved
);
1127 /* process client requests
1130 server_loop( const char* ipaddr
, int port
,
1131 const char* mcast_addr
)
1133 int rc
, maxfd
, err
, nrdy
, i
;
1134 struct in_addr mcast_inaddr
;
1135 struct server_ctx srv
;
1137 struct timespec tmout
, *ptmout
= NULL
;
1138 tmfd_t
*asock
= NULL
;
1139 size_t n
= 0, nasock
= 0, max_nasock
= LQ_BACKLOG
;
1140 sigset_t oset
, bset
;
1142 assert( (port
> 0) && mcast_addr
&& ipaddr
);
1144 (void)tmfprintf( g_flog
, "Server is starting up, max clients = [%u]\n",
1145 g_uopt
.max_clients
);
1146 asock
= calloc (max_nasock
, sizeof(*asock
));
1148 mperror (g_flog
, ENOMEM
, "%s: calloc", __func__
);
1149 return ERR_INTERNAL
;
1152 if( 1 != inet_aton(mcast_addr
, &mcast_inaddr
) ) {
1153 mperror(g_flog
, errno
, "%s: inet_aton", __func__
);
1154 return ERR_INTERNAL
;
1157 init_server_ctx( &srv
, g_uopt
.max_clients
,
1158 (ipaddr
[0] ? ipaddr
: "0.0.0.0") , (uint16_t)port
, mcast_addr
);
1160 srv
.rcv_tmout
= (u_short
)g_uopt
.rcv_tmout
;
1161 srv
.snd_tmout
= RLY_SOCK_TIMEOUT
;
1162 srv
.mcast_inaddr
= mcast_inaddr
;
1164 /* NB: server socket is non-blocking! */
1165 if( 0 != (rc
= setup_listener( ipaddr
, port
, &srv
.lsockfd
,
1166 g_uopt
.lq_backlog
)) ) {
1170 sigemptyset (&bset
);
1171 sigaddset (&bset
, SIGINT
);
1172 sigaddset (&bset
, SIGQUIT
);
1173 sigaddset (&bset
, SIGCHLD
);
1174 sigaddset (&bset
, SIGTERM
);
1176 tmout
.tv_sec
= g_uopt
.ssel_tmout
;
1180 TRACE( (void)tmfprintf (g_flog
, "select() timeout set to "
1181 "[%ld] seconds\n", tmout
.tv_sec
) );
1184 (void) sigprocmask (SIG_BLOCK
, &bset
, &oset
);
1186 TRACE( (void)tmfprintf( g_flog
, "Entering server loop\n") );
1189 FD_SET( srv
.lsockfd
, &rset
);
1190 FD_SET( srv
.cpipe
[0], &rset
);
1192 maxfd
= (srv
.lsockfd
> srv
.cpipe
[0] ) ? srv
.lsockfd
: srv
.cpipe
[0];
1193 for (i
= 0; (size_t)i
< nasock
; ++i
) {
1194 assert (asock
[i
].fd
>= 0);
1195 FD_SET (asock
[i
].fd
, &rset
);
1196 if (asock
[i
].fd
> maxfd
) maxfd
= asock
[i
].fd
;
1199 /* if there are accepted sockets - apply specified time-out
1201 ptmout
= ((nasock
> 0) && (g_uopt
.ssel_tmout
> 0)) ? &tmout
: NULL
;
1203 TRACE( (void)tmfprintf( g_flog
, "Waiting for input from [%ld] fd's, "
1204 "%s timeout\n", (long)(2 + nasock
), (ptmout
? "with" : "NO")));
1206 nrdy
= pselect (maxfd
+ 1, &rset
, NULL
, NULL
, ptmout
, &oset
);
1210 TRACE( (void)tmfputs( "Must quit now\n", g_flog
) );
1213 wait_terminated( &srv
);
1217 TRACE( (void)tmfputs ("INTERRUPTED, yet "
1218 "will continue.\n", g_flog
) );
1222 mperror( g_flog
, err
, "%s: select", __func__
);
1226 TRACE( (void)tmfprintf (g_flog
, "Got %ld requests\n", (long)nrdy
) );
1227 if (0 == nrdy
) { /* time-out */
1228 tmout_requests (asock
, &nasock
);
1232 if( FD_ISSET(srv
.cpipe
[0], &rset
) ) {
1233 (void) tpstat_read( &srv
);
1234 if (--nrdy
<= 0) continue;
1238 (0 < (nrdy
- (FD_ISSET(srv
.lsockfd
, &rset
) ? 1 : 0)))) {
1239 process_requests (asock
, &nasock
, &rset
, &srv
);
1240 /* n now contains # (yet) unprocessed accepted sockets */
1243 if (FD_ISSET(srv
.lsockfd
, &rset
)) {
1244 if (nasock
>= max_nasock
) {
1245 (void) tmfprintf (g_flog
, "Cannot accept sockets beyond "
1246 "the limit [%ld/%ld], skipping\n",
1247 (long)nasock
, (long)max_nasock
);
1250 n
= max_nasock
- nasock
; /* append asock */
1251 accept_requests (srv
.lsockfd
, &(asock
[nasock
]), &n
);
1258 TRACE( (void)tmfprintf( g_flog
, "Exited server loop\n") );
1260 for (i
= 0; (size_t)i
< nasock
; ++i
) {
1261 if (asock
[i
].fd
> 0) (void) close (asock
[i
].fd
);
1265 /* receive additional (blocked signals) */
1266 (void) sigprocmask (SIG_SETMASK
, &oset
, NULL
);
1267 wait_terminated( &srv
);
1268 terminate_all_clients( &srv
);
1271 if (0 != close( srv
.lsockfd
)) {
1272 mperror (g_flog
, errno
, "server socket close");
1275 free_server_ctx( &srv
);
1277 (void)tmfprintf( g_flog
, "Server exits with rc=[%d]\n", rc
);
1283 usage( const char* app
, FILE* fp
)
1285 (void) fprintf (fp
, "%s %s (%s %d) %s [%s]\n", app
, VERSION
, BUILD_TYPE
, BUILDNUM
,
1286 COMPILE_MODE
, get_sysinfo(NULL
));
1287 (void) fprintf (fp
, "usage: %s [-vTS] [-a listenaddr] -p port "
1288 "[-m mcast_ifc_addr] [-c clients] [-l logfile] "
1289 "[-B sizeK] [-n nice_incr]\n", app
);
1291 "\t-v : enable verbose output [default = disabled]\n"
1292 "\t-S : enable client statistics [default = disabled]\n"
1293 "\t-T : do NOT run as a daemon [default = daemon if root]\n"
1294 "\t-a : (IPv4) address/interface to listen on [default = %s]\n"
1295 "\t-p : port to listen on\n"
1296 "\t-m : (IPv4) address/interface of (multicast) "
1297 "source [default = %s]\n"
1298 "\t-c : max clients to serve [default = %d, max = %d]\n",
1299 IPv4_ALL
, IPv4_ALL
, DEFAULT_CLIENT_COUNT
, MAX_CLIENT_COUNT
);
1301 "\t-l : log output to file [default = stderr]\n"
1302 "\t-B : buffer size (65536, 32Kb, 1Mb) for inbound (multicast) data [default = %ld bytes]\n"
1303 "\t-R : maximum messages to store in buffer (-1 = all) "
1305 "\t-H : maximum time (sec) to hold data in buffer "
1306 "(-1 = unlimited) [default = %d]\n"
1307 "\t-n : nice value increment [default = %d]\n"
1308 "\t-M : periodically renew multicast subscription (skip if 0 sec) [default = %d sec]\n",
1309 (long)DEFAULT_CACHE_LEN
, g_uopt
.rbuf_msgs
, DHOLD_TIMEOUT
, g_uopt
.nice_incr
,
1310 (int)g_uopt
.mcast_refresh
);
1311 (void) fprintf( fp
, "Examples:\n"
1313 "\tlisten for HTTP requests on port 4022, all network interfaces\n"
1314 " %s -a lan0 -p 4022 -m lan1\n"
1315 "\tlisten for HTTP requests on interface lan0, port 4022;\n"
1316 "\tsubscribe to multicast groups on interface lan1\n",
1318 (void) fprintf( fp
, "\n %s\n", UDPXY_COPYRIGHT_NOTICE
);
1319 (void) fprintf( fp
, " %s\n\n", UDPXY_CONTACT
);
1324 extern int udpxy_main( int argc
, char* const argv
[] );
1327 udpxy_main( int argc
, char* const argv
[] )
1329 int rc
= 0, ch
= 0, port
= -1,
1330 custom_log
= 0, no_daemon
= 0;
1332 char ipaddr
[IPADDR_STR_SIZE
] = "\0",
1333 mcast_addr
[IPADDR_STR_SIZE
] = "\0";
1335 char pidfile
[ MAXPATHLEN
] = "\0";
1336 u_short MIN_MCAST_REFRESH
= 0, MAX_MCAST_REFRESH
= 0;
1337 char udpxy_finfo
[ 80 ] = {0};
1339 /* support for -r -w (file read/write) option is disabled by default;
1340 * those features are experimental and for dev debugging ONLY
1343 static const char UDPXY_OPTMASK
[] = "TvSa:l:p:m:c:B:n:R:r:w:H:M:";
1345 static const char UDPXY_OPTMASK
[] = "TvSa:l:p:m:c:B:n:R:H:M:";
1348 struct sigaction qact
, iact
, cact
, oldact
;
1350 extern const char g_udpxy_app
[];
1352 (void) get_pidstr( PID_RESET
, "S" );
1354 rc
= init_uopt( &g_uopt
);
1355 while( (0 == rc
) && (-1 != (ch
= getopt(argc
, argv
, UDPXY_OPTMASK
))) ) {
1357 case 'v': set_verbose( &g_uopt
.is_verbose
);
1359 case 'T': no_daemon
= 1;
1361 case 'S': g_uopt
.cl_tpstat
= uf_TRUE
;
1364 rc
= get_ipv4_address( optarg
, ipaddr
, sizeof(ipaddr
) );
1366 (void) fprintf( stderr
, "Invalid address: [%s]\n",
1373 port
= atoi( optarg
);
1375 (void) fprintf( stderr
, "Invalid port number: [%d]\n",
1382 rc
= get_ipv4_address( optarg
, mcast_addr
,
1383 sizeof(mcast_addr
) );
1385 (void) fprintf( stderr
, "Invalid multicast address: "
1392 g_uopt
.max_clients
= atoi( optarg
);
1393 if( (g_uopt
.max_clients
< MIN_CLIENT_COUNT
) ||
1394 (g_uopt
.max_clients
> MAX_CLIENT_COUNT
) ) {
1395 (void) fprintf( stderr
,
1396 "Client count should be between %d and %d\n",
1397 MIN_CLIENT_COUNT
, MAX_CLIENT_COUNT
);
1403 g_flog
= fopen( optarg
, "a" );
1404 if( NULL
== g_flog
) {
1406 (void) fprintf( stderr
, "Error opening logfile "
1408 optarg
, strerror(rc
) );
1413 Setlinebuf( g_flog
);
1418 rc
= a2size(optarg
, &g_uopt
.rbuf_len
);
1420 (void) fprintf( stderr
, "Invalid buffer size: [%s]\n",
1424 else if( (g_uopt
.rbuf_len
< MIN_MCACHE_LEN
) ||
1425 (g_uopt
.rbuf_len
> MAX_MCACHE_LEN
) ) {
1426 fprintf(stderr
, "Buffer size "
1427 "must be within [%ld-%ld] bytes\n",
1428 (long)MIN_MCACHE_LEN
, (long)MAX_MCACHE_LEN
);
1434 g_uopt
.nice_incr
= atoi( optarg
);
1435 if( 0 == g_uopt
.nice_incr
) {
1436 (void) fprintf( stderr
,
1437 "Invalid nice-value increment: [%s]\n", optarg
);
1444 g_uopt
.rbuf_msgs
= atoi( optarg
);
1445 if( (g_uopt
.rbuf_msgs
<= 0) && (-1 != g_uopt
.rbuf_msgs
) ) {
1446 (void) fprintf( stderr
,
1447 "Invalid Rmsgs size: [%s]\n", optarg
);
1454 g_uopt
.dhold_tmout
= (time_t)atoi( optarg
);
1455 if( (0 == g_uopt
.dhold_tmout
) ||
1456 ((g_uopt
.dhold_tmout
) < 0 && (-1 != g_uopt
.dhold_tmout
)) ) {
1457 (void) fprintf( stderr
, "Invalid value for max time "
1458 "to hold buffered data: [%s]\n", optarg
);
1466 if( 0 != access(optarg
, R_OK
) ) {
1467 perror("source file - access");
1472 g_uopt
.srcfile
= strdup( optarg
);
1476 g_uopt
.dstfile
= strdup( optarg
);
1478 #endif /* UDPXY_FILEIO */
1480 g_uopt
.mcast_refresh
= (u_short
)atoi( optarg
);
1482 MIN_MCAST_REFRESH
= 30;
1483 MAX_MCAST_REFRESH
= 64000;
1484 if( g_uopt
.mcast_refresh
&&
1485 (g_uopt
.mcast_refresh
< MIN_MCAST_REFRESH
||
1486 g_uopt
.mcast_refresh
> MAX_MCAST_REFRESH
)) {
1487 (void) fprintf( stderr
,
1488 "Invalid multicast refresh period [%d] seconds, "
1489 "min=[%d] sec, max=[%d] sec\n",
1490 (int)g_uopt
.mcast_refresh
,
1491 (int)MIN_MCAST_REFRESH
, (int)MAX_MCAST_REFRESH
);
1498 (void) fprintf( stderr
,
1499 "Option [-%c] requires an argument\n",
1504 (void) fprintf( stderr
,
1505 "Unrecognized option: [-%c]\n", optopt
);
1510 usage( argv
[0], stderr
);
1514 } /* while getopt */
1517 free_uopt( &g_uopt
);
1521 openlog( g_udpxy_app
, LOG_CONS
| LOG_PID
, LOG_LOCAL0
);
1524 if( (argc
< 2) || (port
<= 0) || (rc
!= 0) ) {
1525 usage( argv
[0], stderr
);
1526 rc
= ERR_PARAM
; break;
1529 if( '\0' == mcast_addr
[0] ) {
1530 (void) strncpy( mcast_addr
, IPv4_ALL
, sizeof(mcast_addr
) - 1 );
1534 /* in debug mode output goes to stderr, otherwise to /dev/null */
1535 g_flog
= ((uf_TRUE
== g_uopt
.is_verbose
)
1537 : fopen( "/dev/null", "a" ));
1538 if( NULL
== g_flog
) {
1540 rc
= ERR_INTERNAL
; break;
1544 if( 0 == geteuid() ) {
1546 if( stderr
== g_flog
) {
1547 (void) fprintf( stderr
,
1548 "Logfile must be specified to run "
1549 "in verbose mode in background\n" );
1550 rc
= ERR_PARAM
; break;
1553 if( 0 != (rc
= daemonize(0, g_flog
)) ) {
1554 rc
= ERR_INTERNAL
; break;
1558 rc
= set_pidfile( g_udpxy_app
, port
, pidfile
, sizeof(pidfile
) );
1560 mperror( g_flog
, errno
, "set_pidfile" );
1561 rc
= ERR_INTERNAL
; break;
1564 if( 0 != (rc
= make_pidfile( pidfile
, getpid(), g_flog
)) )
1568 qact
.sa_handler
= handle_quitsigs
;
1569 sigemptyset(&qact
.sa_mask
);
1572 if( (sigaction(SIGTERM
, &qact
, &oldact
) < 0) ||
1573 (sigaction(SIGQUIT
, &qact
, &oldact
) < 0) ||
1574 (sigaction(SIGINT
, &qact
, &oldact
) < 0)) {
1575 perror("sigaction-quit");
1576 rc
= ERR_INTERNAL
; break;
1579 iact
.sa_handler
= SIG_IGN
;
1580 sigemptyset(&iact
.sa_mask
);
1583 if( (sigaction(SIGPIPE
, &iact
, &oldact
) < 0) ) {
1584 perror("sigaction-ignore");
1585 rc
= ERR_INTERNAL
; break;
1588 cact
.sa_handler
= handle_sigchld
;
1589 sigemptyset(&cact
.sa_mask
);
1592 if( sigaction(SIGCHLD
, &cact
, &oldact
) < 0 ) {
1593 perror("sigaction-sigchld");
1594 rc
= ERR_INTERNAL
; break;
1597 (void) snprintf( udpxy_finfo
, sizeof(udpxy_finfo
),
1598 "%s %s (%s %d) %s [%s]", g_udpxy_app
, VERSION
,
1599 BUILD_TYPE
, BUILDNUM
,
1600 COMPILE_MODE
, get_sysinfo(NULL
) );
1602 syslog( LOG_NOTICE
, "%s is starting\n",udpxy_finfo
);
1603 TRACE( printcmdln( g_flog
, udpxy_finfo
, argc
, argv
) );
1605 rc
= server_loop( ipaddr
, port
, mcast_addr
);
1607 syslog( LOG_NOTICE
, "%s is exiting with rc=[%d]\n",
1609 TRACE( tmfprintf( g_flog
, "%s is exiting with rc=[%d]\n",
1610 g_udpxy_app
, rc
) );
1611 TRACE( printcmdln( g_flog
, udpxy_finfo
, argc
, argv
) );
1614 if( '\0' != pidfile
[0] ) {
1615 if( -1 == unlink(pidfile
) ) {
1616 mperror( g_flog
, errno
, "unlink [%s]", pidfile
);
1620 if( g_flog
&& (stderr
!= g_flog
) ) {
1621 (void) fclose(g_flog
);
1625 free_uopt( &g_uopt
);