1 /* @(#) udpxrec utility: main module
2  *
3  * Copyright 2008-2011 Pavel V. Cherenkov (pcherenkov@gmail.com)
4  *
5  *  This file is part of udpxy.
6  *
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.
11  *
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.
16  *
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/>.
19  */
20 
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <strings.h>
25 #include <signal.h>
26 #include <errno.h>
27 #include <assert.h>
28 #include <stdlib.h>
29 
30 #include <sys/socket.h>
31 #include <arpa/inet.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 
35 #ifndef __USE_LARGEFILE64
36     #define __USE_LARGEFILE64
37 #endif
38 
39 #ifndef __USE_FILE_OFFSET64
40     #define __USE_FILE_OFFSET64
41 #endif
42 
43 #include <fcntl.h>
44 
45 #include "osdef.h"  /* os-specific definitions */
46 #include "udpxy.h"
47 #include "util.h"
48 #include "uopt.h"
49 #include "ctx.h"
50 #include "dpkt.h"
51 #include "mtrace.h"
52 #include "netop.h"
53 #include "ifaddr.h"
54 
55 
56 /* external globals */
57 
58 extern FILE*  g_flog;
59 extern volatile sig_atomic_t g_quit;
60 extern const char g_udpxrec_app[];
61 
62 static volatile sig_atomic_t g_alarm = 0;
63 
64 /* globals
65  */
66 
67 struct udpxrec_opt g_recopt;
68 static char g_app_info[ 80 ] = {0};
69 
70 /* handler for signals requestin application exit
71  */
72 static void
73 handle_quitsigs(int signo)
74 {
75     g_quit = 1;
76     if( SIGALRM == signo ) g_alarm = 1;
77 
78     TRACE( (void)tmfprintf( g_flog,
79                 "*** Caught SIGNAL [%d] in process=[%d] ***\n",
80                 signo, getpid()) );
81     return;
82 }
83 
84 
85 /* return 1 if the application must gracefully quit
86  */
87 static sig_atomic_t
88 must_quit() { return g_quit; }
89 
90 
91 static void
92 usage( const char* app, FILE* fp )
93 {
94     extern const char  UDPXY_COPYRIGHT_NOTICE[];
95     extern const char  UDPXY_CONTACT[];
96 
97     (void) fprintf(fp, "%s\n", g_app_info);
98     (void) fprintf(fp, "usage: %s [-v] [-b begin_time] [-e end_time] "
99             "[-M maxfilesize] [-p pidfile] [-B bufsizeK] [-n nice_incr] "
100             "[-m mcast_ifc_addr] [-l logfile] "
101             "-c src_addr:port dstfile\n",
102             app );
103 
104     (void) fprintf(fp,
105             "\t-v : enable verbose output [default = disabled]\n"
106             "\t-b : begin recording at [+]dd:hh24:mi.ss\n"
107             "\t-e : stop recording at [+]dd:hh24:mi.ss\n"
108             "\t-M : maximum size of destination file\n"
109             "\t-p : pidfile for this process [MUST be specified if daemon]\n"
110             "\t-B : buffer size for inbound (multicast) data [default = %ld bytes]\n"
111             "\t-R : maximum messages to store in buffer (-1 = all) "
112                     "[default = -1]\n",
113             (long)DEFAULT_CACHE_LEN );
114     (void)fprintf(fp,
115             "\t-T : do NOT run as a daemon [default = daemon if root]\n"
116             "\t-n : nice value increment [default = %d]\n"
117             "\t-m : name or address of multicast interface to read from\n"
118             "\t-c : multicast channel to record - ipv4addr:port\n"
119             "\t-l : write output into the logfile\n"
120             "\t-u : seconds to wait before updating on how long till recording starts\n",
121             g_recopt.nice_incr );
122 
123     (void) fprintf( fp, "Examples:\n"
124             "  %s -b 15:45.00 -e +2:00.00 -M 1.5Gb -n 2 -B 64K -c 224.0.11.31:5050 "
125             " /opt/video/tv5.mpg \n"
126             "\tbegin recording multicast channel 224.0.11.31:5050 at 15:45 today,\n"
127             "\tfinish recording in two hours or if destination file size >= 1.5 Gb;\n"
128             "\tset socket buffer to 64Kb; increment nice value by 2;\n"
129             "\twrite captured video to /opt/video/tv5.mpg\n",
130             g_udpxrec_app );
131     (void) fprintf( fp, "\n  %s\n", UDPXY_COPYRIGHT_NOTICE );
132     (void) fprintf( fp, "  %s\n\n", UDPXY_CONTACT );
133     return;
134 }
135 
136 
137 /* update wait status
138  *
139  */
140 static void
141 update_waitstat( FILE* log, time_t end_time )
142 {
143     /* TODO: make sure to distinguish if log is a terminal
144      * terminal gets the progress bar, etc. */
145 
146     double tdiff = difftime( end_time, time(NULL) );
147     (void) tmfprintf( log, "%.0f\tseconds till recording begins\n",
148             tdiff );
149 }
150 
151 
152 /* wait till the given time, update wait status
153  * every update_sec seconds
154  *
155  */
156 static int
157 wait_till( time_t endtime, int update_sec )
158 {
159     u_int sec2wait = 0;
160     u_int unslept = 0;
161     time_t now = time(NULL);
162     sig_atomic_t quit = 0;
163 
164     TRACE( (void)tmfprintf( g_flog, "%s: waiting till time=[%ld], now=[%ld]\n",
165                 __func__, (long)endtime, (long)now ) );
166 
167     if( now >= endtime ) return 0;
168 
169     (void) tmfprintf( g_flog, "[%ld] seconds before recording begins\n",
170                         (long)(endtime - now) );
171 
172     if( (update_sec <= 0) ||
173         ((now + update_sec) > endtime) ) {
174         unslept = sleep( endtime - now );
175     }
176     else {
177         while( !(quit = must_quit()) && (now < endtime) ) {
178             sec2wait = (now + update_sec) <= endtime
179                         ? update_sec : (endtime - now);
180             update_waitstat( g_flog, endtime );
181 
182             TRACE( (void)tmfprintf( g_flog, "Waiting for [%u] more seconds.\n",
183                         sec2wait ) );
184 
185             unslept = sleep( sec2wait );
186             now = time(NULL);
187         }
188     }
189 
190     TRACE( (void)tmfprintf( g_flog, "[%u] seconds unslept, quit=[%d]\n", unslept,
191                 (long)quit ) );
192     return (unslept ? ERR_INTERNAL : 0);
193 }
194 
195 
196 static int
197 calc_buf_settings( ssize_t* bufmsgs, size_t* sock_buflen )
198 {
199     ssize_t nmsgs = -1, max_buf_used = -1, env_snd_buflen = -1;
200     size_t buflen = 0;
201 
202     /* how many messages should we process? */
203     nmsgs = (g_recopt.rbuf_msgs > 0) ? g_recopt.rbuf_msgs :
204              (int)g_recopt.bufsize / ETHERNET_MTU;
205 
206     /* how many bytes could be written at once
207         * to the send socket */
208     max_buf_used = (g_recopt.rbuf_msgs > 0)
209         ? (ssize_t)(nmsgs * ETHERNET_MTU) : g_recopt.bufsize;
210     if (max_buf_used > g_recopt.bufsize) {
211         max_buf_used = g_recopt.bufsize;
212     }
213 
214     assert( max_buf_used >= 0 );
215 
216     env_snd_buflen = get_sizeval( "UDPXREC_SOCKBUF_LEN", 0);
217     buflen = (env_snd_buflen > 0) ? (size_t)env_snd_buflen : (size_t)max_buf_used;
218 
219     if (buflen < (size_t) MIN_SOCKBUF_LEN) {
220         buflen = (size_t) MIN_SOCKBUF_LEN;
221     }
222 
223     /* cannot go below the size of effective usage */
224     if( buflen < (size_t)max_buf_used ) {
225         buflen = (size_t)max_buf_used;
226     }
227 
228     if (bufmsgs) *bufmsgs = nmsgs;
229     if (sock_buflen) *sock_buflen = buflen;
230 
231     TRACE( (void)tmfprintf( g_flog,
232                 "min socket buffer = [%ld], "
233                 "max space to use = [%ld], "
234                 "Rmsgs = [%ld]\n",
235                 (long)buflen, (long)max_buf_used, (long)nmsgs ) );
236 
237     return 0;
238 }
239 
240 
241 
242 /* subscribe to the (configured) multicast channel
243  */
244 static int
245 subscribe( int* sockfd, struct in_addr* mcast_inaddr )
246 {
247     struct sockaddr_in sa;
248     const char* ipaddr = g_recopt.rec_channel;
249     size_t rcvbuf_len = 0;
250     int rc = 0;
251 
252     assert( sockfd && mcast_inaddr );
253 
254     if( 1 != inet_aton( ipaddr, &sa.sin_addr ) ) {
255         mperror( g_flog, errno,
256                 "%s: Invalid subscription [%s:%d]: inet_aton",
257                 __func__, ipaddr, g_recopt.rec_port );
258         return -1;
259     }
260 
261     sa.sin_family = AF_INET;
262     sa.sin_port = htons( (uint16_t)g_recopt.rec_port );
263 
264     if( 1 != inet_aton( g_recopt.mcast_addr, mcast_inaddr ) ) {
265         mperror( g_flog, errno,
266                 "%s: Invalid multicast interface: [%s]: inet_aton",
267                 __func__, g_recopt.mcast_addr );
268         return -1;
269     }
270 
271     rc = calc_buf_settings( NULL, &rcvbuf_len );
272     if (0 != rc) return rc;
273 
274     return setup_mcast_listener( &sa, mcast_inaddr,
275             sockfd, (g_recopt.nosync_sbuf ? 0 : rcvbuf_len) );
276 }
277 
278 
279 /* record network stream as per spec in opt
280  */
281 static int
282 record()
283 {
284     int rsock = -1, destfd = -1, rc = 0, wtime_sec = 0;
285     struct in_addr raddr;
286     struct timeval rtv;
287     struct dstream_ctx ds;
288     ssize_t nmsgs = 0;
289     ssize_t nrcv = -1, lrcv = -1, t_delta = 0;
290     uint64_t n_total = 0;
291     ssize_t nwr = -1, lwr = -1;
292     sig_atomic_t quit = 0;
293     struct rdata_opt ropt;
294     int oflags = 0;
295 
296     char* data = NULL;
297 
298     static const u_short RSOCK_TIMEOUT  = 5;
299     extern const char CMD_UDP[];
300 
301     /* NOPs to eliminate warnings in lean version */
302     (void)&t_delta; (void)&lrcv;
303     t_delta = lrcv = lwr = 0; quit=0;
304 
305     check_fragments( NULL, 0, 0, 0, 0, g_flog );
306 
307     /* init */
308     do {
309         data = malloc( g_recopt.bufsize );
310         if( NULL == data ) {
311             mperror(g_flog, errno, "%s: cannot allocate [%ld] bytes",
312                     __func__, (long)g_recopt.bufsize );
313             rc = ERR_INTERNAL;
314             break;
315         }
316 
317         rc = subscribe( &rsock, &raddr );
318         if( 0 != rc ) break;
319 
320         rtv.tv_sec = RSOCK_TIMEOUT;
321         rtv.tv_usec = 0;
322 
323         rc = setsockopt( rsock, SOL_SOCKET, SO_RCVTIMEO, &rtv, sizeof(rtv) );
324         if( -1 == rc ) {
325             mperror(g_flog, errno, "%s: setsockopt - SO_RCVTIMEO",
326                     __func__);
327             rc = ERR_INTERNAL;
328             break;
329         }
330 
331         oflags = O_CREAT | O_TRUNC | O_WRONLY |
332                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
333         # if defined(O_LARGEFILE)
334             /* O_LARGEFILE is not defined under FreeBSD ??-7.1 */
335             oflags |= O_LARGEFILE;
336         # endif
337         destfd = open( g_recopt.dstfile, oflags,
338                 (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
339         if( -1 == destfd ) {
340             mperror( g_flog, errno, "%s: cannot create destination file [%s]",
341                      __func__, g_recopt.dstfile );
342             rc = ERR_INTERNAL;
343             break;
344         }
345 
346         rc = calc_buf_settings( &nmsgs, NULL );
347         if (0 != rc) return -1;
348 
349         if( nmsgs < (ssize_t)1 ) {
350             (void) tmfprintf( g_flog, "Buffer for inbound data is too small [%ld] bytes; "
351                     "the minimum size is [%ld] bytes\n",
352                     (long)g_recopt.bufsize, (long)ETHERNET_MTU );
353             rc = ERR_PARAM;
354             break;
355         }
356 
357         TRACE( (void)tmfprintf( g_flog, "Inbound buffer set to "
358                         "[%d] messages\n", nmsgs ) );
359 
360         rc = init_dstream_ctx( &ds, CMD_UDP, NULL, nmsgs );
361         if( 0 != rc ) return -1;
362 
363         (void) set_nice( g_recopt.nice_incr, g_flog );
364 
365         /* set up alarm to break main loop */
366         if( 0 != g_recopt.end_time ) {
367             wtime_sec = (int)difftime( g_recopt.end_time, time(NULL) );
368             assert( wtime_sec >= 0 );
369 
370             (void) alarm( wtime_sec );
371 
372             (void)tmfprintf( g_flog, "Recording will end in [%d] seconds\n",
373                     wtime_sec );
374         }
375     } while(0);
376 
377     /* record loop */
378     ropt.max_frgs = g_recopt.rbuf_msgs;
379     ropt.buf_tmout = -1;
380 
381     for( n_total = 0; (0 == rc) && !(quit = must_quit()); ) {
382         nrcv = read_data( &ds, rsock, data, g_recopt.bufsize, &ropt );
383         if( -1 == nrcv ) { rc = ERR_INTERNAL; break; }
384 
385         if( 0 == n_total ) {
386             (void) tmfprintf( g_flog, "Recording to file=[%s] started.\n",
387                     g_recopt.dstfile );
388         }
389 
390         TRACE( check_fragments( "received new", g_recopt.bufsize,
391                     lrcv, nrcv, t_delta, g_flog ) );
392         lrcv = nrcv;
393 
394         if( nrcv > 0 ) {
395             if( g_recopt.max_fsize &&
396                 ((int64_t)(n_total + nrcv) >= g_recopt.max_fsize) ) {
397                 break;
398             }
399 
400             nwr = write_data( &ds, data, nrcv, destfd );
401             if( -1 == nwr ) { rc = ERR_INTERNAL; break; }
402 
403             n_total += (size_t)nwr;
404             /*
405             TRACE( tmfprintf( g_flog, "Wrote [%ld] to file, total=[%ld]\n",
406                         (long)nwr, (long)n_total ) );
407             */
408 
409             TRACE( check_fragments( "wrote to file",
410                     nrcv, lwr, nwr, t_delta, g_flog ) );
411             lwr = nwr;
412         }
413 
414         if( ds.flags & F_SCATTERED ) reset_pkt_registry( &ds );
415 
416     } /* record loop */
417 
418     (void) tmfprintf( g_flog, "Recording to file=[%s] stopped at filesize=[%lu] bytes\n",
419                       g_recopt.dstfile, (u_long)n_total );
420 
421     /* CLEANUP
422      */
423     (void) alarm(0);
424 
425     TRACE( (void)tmfprintf( g_flog, "Exited record loop: wrote [%lu] bytes to file [%s], "
426                     "rc=[%d], alarm=[%ld], quit=[%ld]\n",
427                     (u_long)n_total, g_recopt.dstfile, rc, g_alarm, (long)quit ) );
428 
429     free_dstream_ctx( &ds );
430     if( data ) free( data );
431 
432     close_mcast_listener( rsock, &raddr );
433     if( destfd >= 0 ) (void) close( destfd );
434 
435     if( quit )
436         TRACE( (void)tmfprintf( g_flog, "%s process must quit\n",
437                         g_udpxrec_app ) );
438 
439     return rc;
440 }
441 
442 
443 /* make sure the channel is valid: subscribe/close
444  */
445 static int
446 verify_channel()
447 {
448     struct in_addr mcast_inaddr;
449     int sockfd = -1, rc = -1;
450     char buf[16];
451     ssize_t nrd = -1;
452     struct timeval rtv;
453 
454     static const time_t MSOCK_TMOUT_SEC = 2;
455 
456     rc = subscribe( &sockfd, &mcast_inaddr );
457     do {
458         if( rc ) break;
459 
460         rtv.tv_sec = MSOCK_TMOUT_SEC;
461         rtv.tv_usec = 0;
462         rc = setsockopt( sockfd, SOL_SOCKET, SO_RCVTIMEO, &rtv, sizeof(rtv) );
463         if( -1 == rc ) {
464             mperror(g_flog, errno, "%s: setsockopt - SO_RCVTIMEO",
465                 __func__);
466             rc = ERR_INTERNAL; break;
467         }
468 
469         /* attempt to read from the socket to
470          * make sure the channel is alive
471          */
472         nrd = read( sockfd, buf, sizeof(buf) );
473         if( nrd <= 0 ) {
474             rc = errno;
475             mperror( g_flog, errno, "channel read" );
476             if( EAGAIN == rc ) {
477                 (void) tmfprintf( g_flog,
478                         "failed to read from [%s:%d]\n",
479                         g_recopt.rec_channel, g_recopt.rec_port );
480             }
481             rc = ERR_INTERNAL; break;
482         }
483 
484         TRACE( (void)tmfprintf( g_flog, "%s: read [%ld] bytes "
485                     "from source channel\n", __func__, nrd ) );
486     } while(0);
487 
488     if( sockfd >= 0 ) {
489         close_mcast_listener( sockfd, &mcast_inaddr );
490     }
491 
492     return rc;
493 }
494 
495 
496 /* set up signal handling
497  */
498 static int
499 setup_signals()
500 {
501     struct sigaction qact, oldact;
502 
503     qact.sa_handler = handle_quitsigs;
504     sigemptyset(&qact.sa_mask);
505     qact.sa_flags = 0;
506 
507     if( (sigaction(SIGTERM, &qact, &oldact) < 0) ||
508         (sigaction(SIGQUIT, &qact, &oldact) < 0) ||
509         (sigaction(SIGINT,  &qact, &oldact) < 0) ||
510         (sigaction(SIGALRM, &qact, &oldact) < 0) ||
511         (sigaction(SIGPIPE, &qact, &oldact) < 0)) {
512         perror("sigaction-quit");
513 
514         return ERR_INTERNAL;
515     }
516 
517     return 0;
518 }
519 
520 
521 extern int udpxrec_main( int argc, char* const argv[] );
522 
523 /* main() for udpxrec module
524  */
525 int udpxrec_main( int argc, char* const argv[] )
526 {
527     int rc = 0, ch = 0, custom_log = 0, no_daemon = 0;
528     static const char OPTMASK[] = "vb:e:M:p:B:n:m:l:c:R:u:T";
529     time_t now = time(NULL);
530     char now_buf[ 32 ] = {0}, sel_buf[ 32 ] = {0}, app_finfo[80] = {0};
531 
532     extern int optind, optopt;
533     extern const char IPv4_ALL[];
534 
535     mk_app_info(g_udpxrec_app, g_app_info, sizeof(g_app_info) - 1);
536 
537     if( argc < 2 ) {
538         usage( argv[0], stderr );
539         return ERR_PARAM;
540     }
541 
542     rc = init_recopt( &g_recopt );
543     while( (0 == rc) && (-1 != (ch = getopt( argc, argv, OPTMASK ))) ) {
544         switch(ch) {
545             case 'T':   no_daemon = 1; break;
546             case 'v':   set_verbose( &g_recopt.is_verbose ); break;
547             case 'b':
548                         if( (time_t)0 != g_recopt.end_time ) {
549                             (void) fprintf( stderr, "Cannot specify start-recording "
550                                     "time after end-recording time has been set\n" );
551                         }
552 
553                         rc = a2time( optarg, &g_recopt.bg_time, time(NULL) );
554                         if( 0 != rc ) {
555                             (void) fprintf( stderr, "Invalid time: [%s]\n", optarg );
556                             rc = ERR_PARAM;
557                         }
558                         else {
559                             if( g_recopt.bg_time < now ) {
560                                 (void)strncpy( now_buf, Zasctime(localtime( &now )),
561                                         sizeof(now_buf) );
562                                 (void)strncpy( sel_buf,
563                                         Zasctime(localtime( &g_recopt.bg_time )),
564                                         sizeof(sel_buf) );
565 
566                                 (void) fprintf( stderr,
567                                         "Selected %s time is in the past, "
568                                         "now=[%s], selected=[%s]\n", "start",
569                                         now_buf, sel_buf );
570                                 rc = ERR_PARAM;
571                             }
572                         }
573 
574                         break;
575             case 'e':
576                         if( (time_t)0 == g_recopt.bg_time ) {
577                             g_recopt.bg_time = time(NULL);
578                             (void)fprintf( stderr,
579                                     "Start-recording time defaults to now [%s]\n",
580                                     Zasctime( localtime( &g_recopt.bg_time ) ) );
581                         }
582 
583                         rc = a2time( optarg, &g_recopt.end_time, g_recopt.bg_time );
584                         if( 0 != rc ) {
585                             (void) fprintf( stderr, "Invalid time: [%s]\n", optarg );
586                             rc = ERR_PARAM;
587                         }
588                         else {
589                             if( g_recopt.end_time < now ) {
590                                 (void)strncpy( now_buf, Zasctime(localtime( &now )),
591                                         sizeof(now_buf) );
592                                 (void)strncpy( sel_buf,
593                                         Zasctime(localtime( &g_recopt.end_time )),
594                                         sizeof(sel_buf) );
595 
596                                 (void) fprintf( stderr,
597                                         "Selected %s time is in the past, "
598                                         "now=[%s], selected=[%s]\n", "end",
599                                         now_buf, sel_buf );
600                                 rc = ERR_PARAM;
601                             }
602                         }
603                         break;
604 
605             case 'M':
606                         rc = a2int64( optarg, &g_recopt.max_fsize );
607                         if( 0 != rc ) {
608                             (void) fprintf( stderr, "Invalid file size: [%s]\n",
609                                     optarg );
610                             rc = ERR_PARAM;
611                         }
612                         break;
613             case 'p':
614                         g_recopt.pidfile = strdup(optarg);
615                         break;
616 
617             case 'B':
618                         rc = a2size( optarg, &g_recopt.bufsize );
619                         if( 0 != rc ) {
620                             (void) fprintf( stderr, "Invalid buffer size: [%s]\n",
621                                     optarg );
622                             rc = ERR_PARAM;
623                         }
624                         else if( (g_recopt.bufsize < MIN_MCACHE_LEN) ||
625                                  (g_recopt.bufsize > MAX_MCACHE_LEN)) {
626                             (void) fprintf( stderr,
627                                 "Buffer size must be in [%ld-%ld] bytes range\n",
628                                 (long)MIN_MCACHE_LEN, (long)MAX_MCACHE_LEN );
629                             rc = ERR_PARAM;
630                         }
631 
632                         break;
633             case 'n':
634                       g_recopt.nice_incr = atoi( optarg );
635                       if( 0 == g_recopt.nice_incr ) {
636                         (void) fprintf( stderr,
637                             "Invalid nice-value increment: [%s]\n", optarg );
638                         rc = ERR_PARAM;
639                       }
640                       break;
641             case 'm':
642                       rc = get_ipv4_address( optarg, g_recopt.mcast_addr,
643                               sizeof(g_recopt.mcast_addr) );
644                       if( 0 != rc ) {
645                         (void) fprintf( stderr, "Invalid multicast address: [%s]\n",
646                                         optarg );
647                           rc = ERR_PARAM;
648                       }
649                       break;
650             case 'l':
651                       g_flog = fopen( optarg, "a" );
652                       if( NULL == g_flog ) {
653                         rc = errno;
654                         (void) fprintf( stderr, "Error opening logfile [%s]: %s\n",
655                                 optarg, strerror(rc) );
656                         rc = ERR_PARAM; break;
657                       }
658 
659                       Setlinebuf( g_flog );
660                       custom_log = 1;
661                       break;
662 
663             case 'c':
664                       rc = get_addrport( optarg, g_recopt.rec_channel,
665                                          sizeof( g_recopt.rec_channel ),
666                                          &g_recopt.rec_port );
667                       if( 0 != rc ) rc = ERR_PARAM;
668                       break;
669 
670             case 'R':
671                       g_recopt.rbuf_msgs = atoi( optarg );
672                       if( (g_recopt.rbuf_msgs <= 0) && (-1 != g_recopt.rbuf_msgs) ) {
673                         (void) fprintf( stderr,
674                                 "Invalid rcache size: [%s]\n", optarg );
675                         rc = ERR_PARAM;
676                       }
677                       break;
678 
679             case 'u':
680                       g_recopt.waitupd_sec = atoi(optarg);
681                       if( g_recopt.waitupd_sec <= 0 ) {
682                           (void) fprintf( stderr, "Invalid wait-update value [%s] "
683                                   "(must be a number > 0)\n", optarg );
684                           rc = ERR_PARAM;
685                       }
686                       break;
687 
688             case ':':
689                       (void) fprintf( stderr, "Option [-%c] requires an argument\n",
690                               optopt );
691                       rc = ERR_PARAM; break;
692             case '?':
693                       (void) fprintf( stderr, "Unrecognized option: [-%c]\n", optopt );
694                       rc = ERR_PARAM; break;
695             default:
696                       usage( argv[0], stderr );
697                       rc = ERR_PARAM; break;
698 
699         } /* switch */
700     } /* while getopt */
701 
702     if( 0 == rc ) {
703         if( optind >= argc ) {
704             (void) fputs( "Missing destination file parameter\n", stderr );
705             rc = ERR_PARAM;
706         }
707         else {
708             g_recopt.dstfile = strdup( argv[optind] );
709         }
710 
711         if( !(g_recopt.max_fsize > 0 || g_recopt.end_time)  ) {
712             (void) fputs( "Must specify either max file [-M] size "
713                     "or end time [-e]\n", stderr );
714             rc = ERR_PARAM;
715         }
716 
717         if( !g_recopt.rec_channel[0] || !g_recopt.rec_port ) {
718             (void) fputs( "Must specify multicast channel to record from\n",
719                     stderr );
720             rc = ERR_PARAM;
721         }
722     }
723 
724     if( rc ) {
725         free_recopt( &g_recopt );
726         return rc;
727     }
728 
729     do {
730         if( '\0' == g_recopt.mcast_addr[0] ) {
731             (void) strncpy( g_recopt.mcast_addr, IPv4_ALL,
732                     sizeof(g_recopt.mcast_addr) - 1 );
733         }
734 
735         if( !custom_log ) {
736             /* in debug mode output goes to stderr, otherwise to /dev/null */
737             g_flog = ((uf_TRUE == g_recopt.is_verbose)
738                     ? stderr
739                     : fopen( "/dev/null", "a" ));
740             if( NULL == g_flog ) {
741                 perror("fopen");
742                 rc = ERR_INTERNAL; break;
743             }
744         }
745 
746         if( 0 == geteuid() ) {
747             if( !no_daemon ) {
748                 if( stderr == g_flog ) {
749                     (void) fprintf( stderr,
750                         "Logfile must be specified to run "
751                         "in verbose mode in background\n" );
752                     rc = ERR_PARAM; break;
753                 }
754 
755                 if( NULL == g_recopt.pidfile ) {
756                     (void) fprintf( stderr, "pidfile must be specified "
757                             "to run as daemon\n" );
758                     rc = ERR_PARAM; break;
759                 }
760 
761                 if( 0 != (rc = daemonize(0, g_flog)) ) {
762                     rc = ERR_INTERNAL; break;
763                 }
764             }
765 
766         } /* 0 == geteuid() */
767 
768         if( NULL != g_recopt.pidfile ) {
769             rc = make_pidfile( g_recopt.pidfile, getpid(), g_flog );
770             if( 0 != rc ) break;
771         }
772 
773         (void) set_nice( g_recopt.nice_incr, g_flog );
774 
775         if( 0 != (rc = setup_signals()) ) break;
776 
777         TRACE( fprint_recopt( g_flog, &g_recopt ) );
778 
779         TRACE( printcmdln( g_flog, g_app_info, argc, argv ) );
780 
781         if( g_recopt.bg_time ) {
782             if( 0 != (rc = verify_channel()) || g_quit )
783                 break;
784 
785             rc = wait_till( g_recopt.bg_time, g_recopt.waitupd_sec );
786             if( rc || g_quit ) break;
787         }
788 
789         rc = record();
790 
791         if( NULL != g_recopt.pidfile ) {
792             if( -1 == unlink(g_recopt.pidfile) ) {
793                 mperror( g_flog, errno, "unlink [%s]", g_recopt.pidfile );
794             }
795         }
796     }
797     while(0);
798 
799     if( g_flog ) {
800         (void)tmfprintf( g_flog, "%s is exiting with rc=[%d]\n",
801                 app_finfo, rc );
802     }
803 
804     if( g_flog && (stderr != g_flog) ) {
805         (void) fclose(g_flog);
806     }
807 
808     free_recopt( &g_recopt );
809 
810     return rc;
811 }
812 
813 
814 /* __EOF__ */
815 
816