cdesktopenv/cde/lib/tt/bin/tttrace/tttrace.C

490 lines
12 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <cde_config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <locale.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#if HAVE_POLL_H
# include <poll.h>
#elif HAVE_SYS_POLL_H
# include <sys/poll.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#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;
}