static FILE *filter( const char *program, const char *channel, const char *suffix ) { size_t n = strlen(channel) + strlen(suffix) + 1; char buf[n]; strcpy(buf, channel); strcat(buf, suffix); FILE *fout = fopen( buf, "w"); if( NULL == fout ) { ACH_DIE( "Could not open log file %s: %s\n", buf, strerror(errno) ); } int oldout = dup(STDOUT_FILENO); if( oldout < 0 ) { ACH_DIE( "Could not dup stdout: %s\n", strerror(errno) ); } if( dup2(fileno(fout), STDOUT_FILENO) < 0 ) { ACH_DIE( "Could not dup output: %s\n", strerror(errno) ); } FILE *f = popen(program, "w"); if( NULL == f ) { ACH_DIE( "Could not popen `%s': %s\n", program, strerror(errno) ); } if( dup2(oldout, STDOUT_FILENO) < 0 ) { ACH_DIE( "Could not dup stdout back: %s\n", strerror(errno) ); } if( close(oldout) ) { ACH_LOG( LOG_WARNING, "Could not close dup'ed stdout: %s\n", strerror(errno) ); } if( fclose(fout) ) { ACH_LOG( LOG_WARNING, "Could not close dup'ed output file: %s\n", strerror(errno) ); } return f; }
int ach_sig_wait( const int *sigs ) { ACH_LOG( LOG_DEBUG, "pid %d waiting for signal\n", getpid() ); sigset_t waitset; ach_sig_mask( sigs, &waitset ); int sig; if( sigwait(&waitset, &sig) ) { ACH_DIE("sigwait failed: %s\n", strerror(errno)); } ACH_LOG( LOG_DEBUG, "pid %d signalled: '%s' %d\n", getpid(), strsignal(sig), sig ); return sig; }
void ach_notify(int sig) { if( ach_pid_notify > 0 ) { if( kill(ach_pid_notify, sig) ) { ACH_LOG( LOG_ERR, "Could not notify pid %d of failure: %s\n", ach_pid_notify, strerror(errno) ); } ach_pid_notify = 0; } }
void ach_sig_dfl_unblock( const int *sig ) { size_t i; for( i = 0; sig[i]; i ++ ) { /* Default Disposition */ if( SIG_ERR == signal(sig[i], SIG_DFL) ) { ACH_LOG( LOG_ERR, "Couldn't set default signal disposition for %s (%d): %s", strsignal(sig[i]), sig[i], strerror(errno) ); } } /* Unblock Signal */ { sigset_t blockmask; ach_sig_mask( sig, &blockmask ); if( sigprocmask(SIG_UNBLOCK, &blockmask, NULL) ) { ACH_DIE( "sigprocmask failed: %s\n", strerror(errno) ); } } }
void ach_sig_block_dummy( const int *sig ) { /* Block Signal */ { sigset_t blockmask; ach_sig_mask( sig, &blockmask ); if( sigprocmask(SIG_BLOCK, &blockmask, NULL) ) { ACH_DIE( "sigprocmask failed: %s\n", strerror(errno) ); } } /* Install Dummy Handler */ size_t i; for( i = 0; sig[i]; i ++ ) { struct sigaction act; memset( &act, 0, sizeof(act) ); act.sa_handler = &ach_sigdummy; if (sigaction(sig[i], &act, NULL) < 0) { ACH_LOG( LOG_ERR, "Couldn't install signal handler: %s", strerror(errno) ); } } }
enum ach_status achd_printf(int fd, const char fmt[], ...) { int n = ACHD_LINE_LENGTH-1; do { char buf[n+1]; va_list ap; va_start(ap, fmt); n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end( ap ); if( n < 0 ) { ACH_LOG( LOG_CRIT, "Error in printf\n"); return ACH_BUG; } else if( (size_t)n < sizeof(buf) ) { if ( (ssize_t)n == achd_write( fd, buf, (size_t)n ) ) { return ACH_OK; } else { return ACH_FAILED_SYSCALL; } } /* else continue */ } while(!cx.sig_received); return ACH_FAILED_SYSCALL; }
pid_t ach_detach ( unsigned timeout ) { pid_t gp_pid = getpid(); ACH_LOG( LOG_DEBUG, "detach grandparent: %d\n", gp_pid ); /* Block signals for child status notification */ const int sigs[] = {ACH_SIG_OK, ACH_SIG_FAIL, SIGALRM, 0}; ach_sig_block_dummy(sigs); /* fork */ pid_t pid1 = fork(); if( pid1 < 0 ) { ACH_DIE( "First fork failed: %s\n", strerror(errno) ); } else if ( pid1 ) { /* parent */ /* wait for a signal */ alarm( timeout ); int sig = ach_sig_wait(sigs); ACH_LOG( LOG_DEBUG, "Detach grandparent got: '%s' (%d)\n", strsignal(sig), sig ); switch( sig ) { case SIGALRM: ACH_LOG( LOG_ERR, "Detached child failed on timeout\n" ); exit( EXIT_FAILURE ); case SIGUSR2: ACH_LOG( LOG_ERR, "Detached child reported failure\n" ); exit( EXIT_FAILURE ); case SIGUSR1: ACH_LOG( LOG_DEBUG, "Detached child OK\n" ); exit(EXIT_SUCCESS); default: ACH_LOG( LOG_ERR, "Unexpected signal in detach: %s (%d)\n", strsignal(sig), sig ); exit( EXIT_FAILURE ); } assert(0); } /* else child */ /* Unblock signals that were blocked in the parent */ ach_sig_dfl_unblock( sigs ); /* set session id to lose our controlling terminal */ if( setsid() < 0 ) { ACH_LOG( LOG_ERR, "Couldn't set sid: %s\n", strerror(errno) ); } /* refork to prevent future controlling ttys */ pid_t pid2 = fork(); if( pid2 < 0 ) { ACH_LOG( LOG_ERR, "Second fork failed: %s\n", strerror(errno) ); /* Don't give up */ } else if ( pid2 ) { /* parent */ ACH_LOG( LOG_DEBUG, "detach parent: %d\n", getpid() ); exit(EXIT_SUCCESS); } /* else child */ ACH_LOG( LOG_DEBUG, "detach child: %d\n", getpid() ); /* ignore sighup */ if( SIG_ERR == signal(SIGHUP, SIG_IGN) ) { ACH_LOG( LOG_ERR, "Couldn't ignore SIGHUP: %s", strerror(errno) ); } /* cd to root */ if( chdir("/") ) { ACH_LOG( LOG_ERR, "Couldn't cd to /: %s", strerror(errno) ); } /* close stdin */ if( close(STDIN_FILENO) ) { ACH_LOG( LOG_ERR, "Couldn't close stdin: %s", strerror(errno) ); } return gp_pid; }
static void *worker( void *arg ) { struct log_desc *desc = (struct log_desc*)arg; /* write header */ fprintf( desc->fout, "ACHLOG\n" "channel-name: %s\n" "log-version: 0\n" "log-time-ach: %lu.%09lu\n" "log-time-real: %lu.%09lu # %s" "local-host: %s\n", desc->name, now_ach.tv_sec, now_ach.tv_nsec, now_real.tv_sec, now_real.tv_nsec, now_real_str, host ); if( passwd ) { fprintf( desc->fout, "user: %s # %s\n", passwd->pw_name, passwd->pw_gecos ); } fputs( ".\n", desc->fout ); if( fflush(desc->fout) ) { ACH_LOG( LOG_ERR, "Could not flush file %s: %s\n", desc->name, strerror(errno) ); } size_t max = 512; ach_pipe_frame_t *frame = ach_pipe_alloc( max ); /* get frames */ int canceled = 0; while( ! canceled ) { /* push the data */ size_t frame_size; ach_status_t r = ach_get( &desc->chan, frame->data, max, &frame_size, NULL, ACH_O_WAIT | ((opt_last ) ? ACH_O_LAST : 0) ); switch(r) { case ACH_OVERFLOW: /* enlarge buffer and retry on overflow */ assert(frame_size > max ); max = frame_size; free(frame); frame = ach_pipe_alloc( max ); continue; case ACH_MISSED_FRAME: case ACH_OK: { ach_pipe_set_size( frame, frame_size ); size_t size = sizeof(ach_pipe_frame_t) - 1 + frame_size; size_t s = fwrite( frame, 1, size, desc->fout ); if( s != size ) { ACH_LOG( LOG_ERR, "Could not write frame to %s, %"PRIuPTR" written instead of %"PRIuPTR"\n", desc->name, s, size ); canceled = 1; } } break; case ACH_CANCELED: canceled = 1; break; default: ACH_LOG( LOG_ERR, "Could not get frame from %s: %s\n", desc->name, strerror(errno) ); canceled = 1; break; } } /* sync */ if( fflush(desc->fout) ) { ACH_LOG( LOG_ERR, "Could not flush file %s: %s\n", desc->name, strerror(errno) ); } return arg; }
int main( int argc, char **argv ) { /* Check if we're running under achcop */ if( getenv("ACHCOP") ) { ach_pid_notify = getppid(); } int c; while( (c = getopt( argc, argv, "zlnh?V")) != -1 ) { switch(c) { case 'v': ach_verbosity ++; break; case 'l': opt_last = 1; break; case 'n': opt_last = 0; break; case 'z': opt_gzip = 1; break; /* case 'f': */ /* opt_freq = atof(optarg); */ /* break; */ case 'V': /* version */ ach_print_version("achpipe.bin"); exit(EXIT_SUCCESS); case '?': case 'h': case 'H': puts( "Usage: achlog [OPTIONS] channels...\n" "Log ach channels to files" "\n" "Options:\n" " -?, Show help\n" " -z, Filter output through gzip\n" "\n" "Examples:\n" " achlog foo bar Log channels foo and bar\n" "\n" "Report bugs to <*****@*****.**>" ); exit(EXIT_SUCCESS); default: posarg(optarg); } } while( optind < argc ) { posarg(argv[optind++]); } if( 0 == n_log ) ACH_DIE("No channels to log\n"); /* Block Signals */ /* Have to block these before forking so ctrl-C doesn't kill the * gzip */ int sigs[] = {SIGTERM, SIGINT, 0}; ach_sig_block_dummy( sigs ); /* Open Channels */ size_t i; for( i = 0; i < n_log; i ++ ) { ach_status_t r = ach_open(&log_desc[i].chan, log_desc[i].name, NULL); if( ACH_OK != r ) { ACH_DIE( "Could not open channel %s: %s\n", log_desc[i].name, ach_result_to_string(r) ); } /* Open log file */ if( opt_gzip ) { log_desc[i].fout = filter( "gzip -c", log_desc[i].name, ".gz" ); } else { log_desc[i].fout = fopen(log_desc[i].name, "w"); } if( NULL == log_desc[i].fout ) { ACH_DIE( "Could not open log file for %s: %s\n", log_desc[i].name, strerror(errno) ); } } /* get some data */ if( clock_gettime(ACH_DEFAULT_CLOCK, &now_ach ) || clock_gettime(CLOCK_REALTIME, &now_real ) ) { ACH_DIE( "Could not get time: %s\n", strerror(errno) ); } if( gethostname( host, sizeof(host) ) ) { ACH_LOG(LOG_ERR, "Could not get host name: %s\n", strerror(errno)); } host[sizeof(host)-1] = '\0'; passwd = getpwuid(getuid()); if( passwd ) { strtok(passwd->pw_gecos, ","); } now_real_str = ctime( &now_real.tv_sec ); /* Create Workers */ pthread_t thread[n_log]; for( i = 0; i < n_log; i ++ ) { int r = pthread_create( thread+i, NULL, worker, (void*)(log_desc+i) ); if( r ) ACH_DIE( "Couldn't start worker thread: %s\n", strerror(r) ); } ach_notify(ACH_SIG_OK); /* Wait for Signal */ ach_sig_wait( sigs ); /* Cancel workers */ ach_cancel_attr_t cattr; ach_cancel_attr_init( &cattr ); cattr.async_unsafe = 1; for( i = 0; i < n_log; i ++ ) { ach_cancel( &log_desc[i].chan, &cattr ); } /* Join worker threads */ for( i = 0; i < n_log; i ++ ) { int r = pthread_join( thread[i], NULL ); if( r ) ACH_DIE( "Couldn't join worker thread: %s\n", strerror(r) ); if( opt_gzip ) { if( pclose(log_desc[i].fout) < 0 ) { ACH_LOG( LOG_ERR, "Could not pclose output for %s: %s\n", log_desc[i].name, strerror(errno) ); } } else { fclose(log_desc[i].fout); } } return 0; }