/* * CDE - Common Desktop Environment * * Copyright (c) 1993-2012, The Open Group. All rights reserved. * * These libraries and programs are free software; you can * redistribute them and/or modify them under the terms of the GNU * Lesser General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * These libraries and programs are distributed in the hope that * they will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with these libraries and programs; if not, write * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301 USA */ //%% (c) Copyright 1993, 1994 Hewlett-Packard Company //%% (c) Copyright 1993, 1994 International Business Machines Corp. //%% (c) Copyright 1993, 1994 Sun Microsystems, Inc. //%% (c) Copyright 1993, 1994 Novell, Inc. //%% $TOG: tttrace.C /main/9 1999/10/14 18:38:47 mgreess $ /* * @(#)tttrace.C 1.29 95/05/02 * * Copyright (c) 1993 by Sun Microsystems, Inc. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #if HAVE_SYS_POLL_H # include #elif HAVE_POLL_H # include #endif #include #include #include #include "Tt/tt_c.h" #include "util/tt_string.h" #include "util/tt_port.h" #include "util/tt_gettext.h" #include "tttrace_objs.h" #include "tt_options.h" #define SESSION_TRACE_OP "Session_Trace" static _Tt_string progname = "tttrace"; pid_t forked_pid = (pid_t) -1; int makequit = 0; int poll_timeout = -1; int exit_status = 0; int ttfd = 0; static void install_signal_handler(); static void sig_handler(int); static pid_t do_fork(_Tt_trace_optobj&); static void send_session_trace(_Tt_trace_optobj&); static int tail_pipe(int, _Tt_string&, pid_t, int); static int open_pipe(_Tt_string&, int*); static void send_on_exit(); int main(int argc, char **argv) { void print_usage_and_exit(); int status = 0; int pipe_fd = -1; _Tt_string temp_name; // name of FIFO file _Tt_trace_optobj myopts; // processes command-line options setlocale( LC_ALL, "" ); install_signal_handler(); // // parse command-line options // switch(myopts.getopts(argc, argv)) { case 0: break; case 1: print_usage_and_exit(); break; case 2: exit(2); default: break; } // Open the pipe for reading, if requested if (myopts.pipe_name().len()) { status = open_pipe(myopts.pipe_name(), &pipe_fd); } if (status == 0) { // Do fork, or send session_trace requests pid_t child_pid = 0; int sink; switch(myopts.operation_mode()) { case FORK_COMMAND: (void) putenv(myopts.envstr()); child_pid = do_fork(myopts); sink = STDERR_FILENO; break; case SESSION_TRACE: send_session_trace(myopts); sink = STDOUT_FILENO; break; default: exit(2); break; } _Tt_string outfile; if (myopts.outfile( outfile ) && (outfile == "-")) { sink = STDOUT_FILENO; } status = tail_pipe(pipe_fd, myopts.pipe_name(), child_pid, sink); if (pipe_fd > 0) { if (unlink( myopts.pipe_name()) != 0) { _Tt_string msg = progname.cat( ": " ); msg = msg.cat( myopts.pipe_name() ); perror( msg ); } } } exit( status ); } // // Install sig_handler as the handler for all the signals it handles. // static void install_signal_handler() { _Tt_string err; err = err.cat( ": _tt_sigset(SIG" ); if (_tt_sigset(SIGHUP, &sig_handler) == 0) { perror( (char *) err.cat("HUP)") ); } if (_tt_sigset(SIGTERM, &sig_handler) == 0) { perror( (char *) err.cat("TERM)") ); } if (_tt_sigset(SIGINT, &sig_handler) == 0) { perror( (char *) err.cat("INT)") ); } if (_tt_sigset(SIGUSR1, &sig_handler) == 0) { perror( (char *) err.cat("USR1)") ); } if (_tt_sigset(SIGUSR2, &sig_handler) == 0) { perror( (char *) err.cat("USR2)") ); } if (_tt_sigset(SIGCHLD, &sig_handler) == 0) { perror( (char *) err.cat("CHLD)") ); } if (_tt_sigset(SIGPIPE, &sig_handler) == 0) { perror( (char *) err.cat("PIPE)") ); } } // // Prints out a usage string and exits. // void print_usage_and_exit() { fprintf(stderr, catgets(_ttcatd, 9, 2, "Usage: %s [-0FCa][-o outfile] [-S session | command [options]]\n" " %s [-e script | -f scriptfile][-S session | command [options]]\n" " -0 Turn off message tracing in session, or run command\n" " without message tracing (i.e. only API tracing)\n" " -F Follow all children forked by command or subsequently\n" " started in session by ttsession(1)\n" " -C Do not trace ToolTalk API calls\n" " -a Print all attributes, arguments, and context slots of\n" " traced messages. Default is single-line summary.\n" " -e script Read tttracefile(4) settings from script\n" " -f scriptfile Read tttracefile(4) settings from scriptfile. \"-\": stdin.\n" " -o outfile Output. \"-\": stdout. default: stdout for session tracing,\n" " stderr (of tttrace) for command tracing\n" " -S session Session to trace. default: see tt_default_session()\n" " command ToolTalk client command to invoke and trace\n"), (char *) progname, (char *) progname); exit(1); } // // Global signal handler for tttrace. All signals are handled by this // function (ie. no signal handlers should be defined in any other files) // static void sig_handler(int sig) { int status; pid_t child; switch (sig) { case SIGPIPE: case SIGHUP: break; case SIGCHLD: child = waitpid( -1, &status, WNOHANG ); if ((child > 0) && (WIFEXITED(status))) { exit_status = WEXITSTATUS(status); poll_timeout = 2; makequit = 1; } break; case SIGTERM: case SIGINT: makequit = 1; break; } } static pid_t do_fork(_Tt_trace_optobj& myopts) { int i; int _tt_getdtablesize(void); int _tt_restoredtablesize(void); int maxfds; // max TT fd's _Tt_string cmd; switch(forked_pid = fork()) { case -1: fprintf(stderr,"%s: fork(): %s\n", (char *)progname, strerror(errno)); exit(2); case 0: // child maxfds = _tt_getdtablesize(); for (i = 3; i < maxfds; i++) { close(i); } _tt_restoredtablesize(); signal(SIGHUP, SIG_IGN); (void) myopts.command(cmd); // existence of cmd already checked execvp((char *) cmd, (char * const*)myopts.cargv()); perror((char *) progname); exit(1); default: // parent -- wait on child process break; } return forked_pid; } static void send_session_trace(_Tt_trace_optobj& myopts) { int timeout = 180; // 3-minute timeout int script_stat; size_t num_fd = 1; struct pollfd fds[1]; _Tt_string tmp; Tt_callback_action tttrace_callback(Tt_message, Tt_pattern); Tt_message msg; int mark = tt_mark(); // We must send the message to the specified session. This routine // is only called when there is an explicit session in the // options list. myopts.session(tmp); ttfd = tt_fd(); tt_session_join((char *) tmp); msg = tt_prequest_create(TT_SESSION, SESSION_TRACE_OP); tt_message_arg_add(msg, TT_IN, "string", (char *) 0); script_stat = myopts.script(tmp); if (script_stat == 1) { // inline script tt_message_arg_val_set(msg, 0, (char *) tmp); } else if (script_stat == 2) { // script is in filename tt_message_file_set(msg, (char *) tmp); } tt_message_callback_add(msg, tttrace_callback); Tt_status mstat = tt_message_send(msg); if (mstat != TT_OK) { fprintf(stderr, "%s: tt_message_send(): %s\n", (char *) progname, tt_status_message(mstat)); exit(2); } fds[0].fd = ttfd; fds[0].events = POLLIN | POLLPRI; fds[0].revents = 0; int rcode = poll(fds, num_fd, timeout); if (rcode == -1) { fprintf(stderr, "%s: Session_Trace: %s\n", (char *) progname, strerror(ETIMEDOUT)); exit(2); } Tt_message inmsg = tt_message_receive(); // Make sure the stop-tracing message is sent upon exit send_on_exit(); tt_release(mark); } Tt_callback_action tttrace_callback(Tt_message msg, Tt_pattern) { Tt_status mstat = (Tt_status) tt_message_status(msg); if (mstat == TT_ERR_NO_MATCH) { // ttsession does not recognize this message, which // means an incompatible version of ttsession is being // used fprintf(stderr, catgets(_ttcatd, 9, 3, "%s: session <%s> does not support " "Session_Trace. Use kill -USR1 instead. " "See ttsession(1).\n"), (char *) progname, tt_message_session(msg) ); exit(5); } else if (mstat != TT_OK) { fprintf(stderr, "%s: Session_Trace: %s\n", (char *) progname, tt_status_message(mstat)); exit(2); } return TT_CALLBACK_PROCESSED; } static void send_on_exit() { Tt_message msg = tt_prequest_create(TT_SESSION, SESSION_TRACE_OP); tt_message_arg_add(msg, TT_IN, "string", "version 1; states none; functions none" ); tt_message_send_on_exit(msg); } #define MAXLINE 255 static int open_pipe(_Tt_string& pipe_name, int* fd) { int quit = 0; int exit_status = 0; while ((! quit) && (*fd <= 0)) { *fd = open((char *) pipe_name, O_RDONLY | O_NDELAY); if ((*fd < 0) && (errno != EINTR)) { _Tt_string msg = progname.cat( ": " ).cat(pipe_name); perror(msg); exit_status = 2; } quit = 1; } return exit_status; } // No silly cracks about this routine's name, please! static int tail_pipe(int fd, _Tt_string& pipenm, pid_t child_pid, int sink) { int bufsize = MAXLINE; int nbytes; int quit = 0; struct pollfd fds[2]; char buf[MAXLINE]; if (pipenm.len() == 0) { int done = 0; while (!done) { pid_t child_waited_for = waitpid( child_pid, 0, 0 ); if (child_waited_for < 0 && errno == EINTR) continue; if (child_waited_for < 0 && errno == ECHILD) break; if (child_waited_for < 0) { fprintf(stderr, "%s: waitpid(): ", (char *) progname); perror(0); if(errno == ECHILD){ exit(2); } } done = 1; } } else { while (!quit) { fds[0].fd = fd; fds[0].events = POLLIN; fds[0].revents = 0; fds[1].fd = ttfd; fds[1].events = POLLIN; fds[1].revents = 0; int numfds = poll(fds, ttfd ? 2 : 1, poll_timeout); if (numfds < 0) { if (errno != EINTR) { _Tt_string msg = progname.cat( ": poll()" ); perror( msg ); exit_status = 2; quit = 1; } else if (makequit) { quit = 1; } continue; } else if (numfds == 0) { quit = 1; continue; } if (fds[ 0 ].revents & POLLHUP) { if (makequit) { // Child has exited -- do a last read while ((nbytes = read(fd, buf, bufsize)) > 0) { write(sink, buf, nbytes); } quit = 1; } else { pid_t child_waited_for = waitpid(child_pid, 0, 0 ); if (child_waited_for == child_pid) { quit = 1; } else { // Tracing is temporarily stopped. // Periodically wake up to see if it // has started again. sleep( 1 ); } } } else if (fds[ 0 ].revents & POLLIN) { if (makequit) { quit = 1; } else { nbytes = read(fd, buf, bufsize); if (nbytes > 0) { write(sink, buf, nbytes); } } } if (fds[ 1 ].revents & POLLIN) { Tt_message msg = tt_message_receive(); if (tt_ptr_error(msg) == TT_ERR_NOMP) { quit = 1; } } } if (fd > 0) { close(fd); } } return exit_status; }