Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / lib / tt / bin / tttrace / tttrace.C
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 //%%  (c) Copyright 1993, 1994 Hewlett-Packard Company                  
24 //%%  (c) Copyright 1993, 1994 International Business Machines Corp.    
25 //%%  (c) Copyright 1993, 1994 Sun Microsystems, Inc.                   
26 //%%  (c) Copyright 1993, 1994 Novell, Inc.                             
27 //%%  $TOG: tttrace.C /main/9 1999/10/14 18:38:47 mgreess $                                                     
28 /*
29  * @(#)tttrace.C        1.29 95/05/02
30  *
31  * Copyright (c) 1993 by Sun Microsystems, Inc.
32  */
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <errno.h>
37 #include <locale.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <signal.h>
41 #if defined(linux)
42 # include <sys/poll.h>
43 #else
44 # include <poll.h>
45 #endif
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <fcntl.h>
49 #include "api/c/tt_c.h"
50 #include "util/tt_string.h"
51 #include "util/tt_port.h"
52 #include "util/tt_gettext.h"
53 #include "tttrace_objs.h"
54 #include "tt_options.h"
55
56 #define SESSION_TRACE_OP        "Session_Trace"
57
58 static _Tt_string       progname = "tttrace";
59 pid_t                   forked_pid = (pid_t) -1;
60 int                     makequit = 0;
61 int                     poll_timeout = -1;
62 int                     exit_status = 0;
63 int                     ttfd = 0;
64
65 static void     install_signal_handler();
66 static void     sig_handler(int);
67 static pid_t    do_fork(_Tt_trace_optobj&);
68 static void     send_session_trace(_Tt_trace_optobj&);
69 static int      tail_pipe(int, _Tt_string&, pid_t, int);
70 static int      open_pipe(_Tt_string&, int*);
71 static void     send_on_exit();
72
73
74 \f
75 int main(int argc, char **argv)
76 {
77         void                    print_usage_and_exit();
78         int                     status = 0;
79         int                     pipe_fd = -1;
80         _Tt_string              temp_name;      // name of FIFO file
81         _Tt_trace_optobj        myopts;         // processes command-line options
82         setlocale( LC_ALL, "" );
83
84         install_signal_handler();
85         
86         //
87         // parse command-line options
88         //
89         
90         switch(myopts.getopts(argc, argv)) {
91             case 0:
92                 break;
93             case 1:
94                 print_usage_and_exit();
95                 break;
96             case 2:
97                 exit(2);
98             default:
99                 break;
100         }
101
102         // Open the pipe for reading, if requested
103
104         if (myopts.pipe_name().len()) {
105                 status = open_pipe(myopts.pipe_name(), &pipe_fd);
106         }
107
108         if (status == 0) {
109         
110                 // Do fork, or send session_trace requests
111                                 
112                 pid_t child_pid = 0;
113                 int sink;
114                 switch(myopts.operation_mode()) {
115                     case FORK_COMMAND:
116                         (void) putenv(myopts.envstr());
117                         child_pid = do_fork(myopts);
118                         sink = STDERR_FILENO;
119                         break;
120                     case SESSION_TRACE:
121                         send_session_trace(myopts);
122                         sink = STDOUT_FILENO;
123                         break;
124                     default:
125                         exit(2);
126                         break;
127                 }
128                         
129                 _Tt_string outfile;
130                 if (myopts.outfile( outfile ) && (outfile == "-")) {
131                         sink = STDOUT_FILENO;
132                 }
133
134                 status = tail_pipe(pipe_fd, myopts.pipe_name(), child_pid, sink);
135
136                 if (pipe_fd > 0) {
137                         
138                         if (unlink( myopts.pipe_name()) != 0) {
139                                 _Tt_string msg = progname.cat( ": " );
140                                 msg = msg.cat( myopts.pipe_name() );
141                                 perror( msg );
142                         }
143                 }
144         }
145         
146         exit( status );
147 }
148
149 // 
150 // Install sig_handler as the handler for all the signals it handles.
151 //
152 static void
153 install_signal_handler()
154 {
155         _Tt_string err;
156         err = err.cat( ": _tt_sigset(SIG" );
157         if (_tt_sigset(SIGHUP, &sig_handler) == 0) {
158                 perror( (char *) err.cat("HUP)") );
159         }
160         if (_tt_sigset(SIGTERM, &sig_handler) == 0) {
161                 perror( (char *) err.cat("TERM)") );
162         }
163         if (_tt_sigset(SIGINT, &sig_handler) == 0) {
164                 perror( (char *) err.cat("INT)") );
165         }
166         if (_tt_sigset(SIGUSR1, &sig_handler) == 0) {
167                 perror( (char *) err.cat("USR1)") );
168         }
169         if (_tt_sigset(SIGUSR2, &sig_handler) == 0) {
170                 perror( (char *) err.cat("USR2)") );
171         }
172         if (_tt_sigset(SIGCHLD, &sig_handler) == 0) {
173                 perror( (char *) err.cat("CHLD)") );
174         }
175         if (_tt_sigset(SIGPIPE, &sig_handler) == 0) {
176                 perror( (char *) err.cat("PIPE)") );
177         }
178 }
179
180 // 
181 // Prints out a usage string and exits.
182 //
183 void
184 print_usage_and_exit()
185 {
186         fprintf(stderr,
187                 catgets(_ttcatd, 9, 2,
188                         "Usage: %s [-0FCa][-o outfile] [-S session | command [options]]\n"
189                         "       %s [-e script | -f scriptfile][-S session | command [options]]\n"
190                         " -0            Turn off message tracing in session, or run command\n"
191                         "               without message tracing (i.e. only API tracing)\n"
192                         " -F            Follow all children forked by command or subsequently\n"
193                         "               started in session by ttsession(1)\n"
194                         " -C            Do not trace ToolTalk API calls\n"
195                         " -a            Print all attributes, arguments, and context slots of\n"
196                         "               traced messages.  Default is single-line summary.\n"
197                         " -e script     Read tttracefile(4) settings from script\n"
198                         " -f scriptfile Read tttracefile(4) settings from scriptfile. \"-\": stdin.\n"
199                         " -o outfile    Output. \"-\": stdout. default: stdout for session tracing,\n"
200                         "               stderr (of tttrace) for command tracing\n"
201                         " -S session    Session to trace.  default: see tt_default_session()\n"
202                         " command       ToolTalk client command to invoke and trace\n"),
203                 (char *) progname, (char *) progname);
204         exit(1);
205 }
206
207 // 
208 // Global signal handler for tttrace. All signals are handled by this
209 // function (ie. no signal handlers should be defined in any other files)
210 //
211 static void
212 sig_handler(int sig)
213 {
214         int status;
215         pid_t child;
216         switch (sig) {
217             case SIGPIPE:
218             case SIGHUP:
219                 break;
220             case SIGCHLD:
221                 child = waitpid( -1, &status, WNOHANG );
222                 if ((child > 0) && (WIFEXITED(status))) {
223                         exit_status = WEXITSTATUS(status);
224                         poll_timeout = 2;
225                         makequit = 1;
226                 }
227                 break;
228             case SIGTERM:
229             case SIGINT:
230                 makequit = 1;
231                 break;
232         }
233 }
234
235 static pid_t do_fork(_Tt_trace_optobj& myopts)
236 {
237         int             i;
238         int             _tt_getdtablesize(void);
239         int             _tt_restoredtablesize(void);
240         int             maxfds;   // max TT fd's
241         _Tt_string      cmd;
242         
243         switch(forked_pid = fork()) {
244             case -1:
245                 fprintf(stderr,"%s: fork(): %s\n", (char *)progname,
246                         strerror(errno));
247                 exit(2);
248             case 0:             // child
249                 maxfds = _tt_getdtablesize();
250                 for (i = 3; i < maxfds; i++) {
251                         close(i);
252                 }
253                 _tt_restoredtablesize();
254                 signal(SIGHUP, SIG_IGN);
255                 (void) myopts.command(cmd); // existence of cmd already checked
256                 execvp((char *) cmd, (char * const*)myopts.cargv());
257                 perror((char *) progname);
258                 exit(1);
259             default:            // parent -- wait on child process
260                 break;
261         }
262         return forked_pid;
263 }
264
265 static void send_session_trace(_Tt_trace_optobj& myopts)
266 {
267         int                     timeout = 180; // 3-minute timeout
268         int                     script_stat;
269         size_t                  num_fd = 1;
270         struct pollfd           fds[1];
271         _Tt_string              tmp;
272         Tt_callback_action      tttrace_callback(Tt_message, Tt_pattern);
273         
274         Tt_message              msg;
275         
276         int mark = tt_mark();
277         
278         // We must send the message to the specified session.  This routine
279         // is only called when there is an explicit session in the
280         // options list.
281                                 
282         myopts.session(tmp);
283         
284         ttfd = tt_fd();
285         
286         tt_session_join((char *) tmp);
287         msg = tt_prequest_create(TT_SESSION, SESSION_TRACE_OP);
288         
289         tt_message_arg_add(msg, TT_IN, "string", (char *) 0);
290         script_stat = myopts.script(tmp);
291         if (script_stat == 1) {                 // inline script
292                 tt_message_arg_val_set(msg, 0, (char *) tmp);
293         }
294         else if (script_stat == 2) {            // script is in filename
295                 tt_message_file_set(msg, (char *) tmp);
296         }
297         
298         tt_message_callback_add(msg, tttrace_callback);
299         Tt_status mstat = tt_message_send(msg);
300         
301         if (mstat != TT_OK) {
302                 fprintf(stderr, "%s: tt_message_send(): %s\n",
303                         (char *) progname, tt_status_message(mstat));
304                 exit(2);
305         }
306         
307         fds[0].fd = ttfd;
308         fds[0].events = POLLIN | POLLPRI;
309         fds[0].revents = 0;
310         int rcode = poll(fds, num_fd, timeout);
311         if (rcode == -1) {
312                 fprintf(stderr, "%s: Session_Trace: %s\n",
313                         (char *) progname, strerror(ETIMEDOUT));
314                 exit(2);
315         }
316         
317         Tt_message inmsg = tt_message_receive();
318         
319         // Make sure the stop-tracing message is sent upon exit
320                 
321         send_on_exit();
322         
323         tt_release(mark);
324 }
325
326 Tt_callback_action tttrace_callback(Tt_message msg, Tt_pattern)
327 {
328         Tt_status mstat = (Tt_status) tt_message_status(msg);
329         if (mstat == TT_ERR_NO_MATCH) {
330                 
331                 // ttsession does not recognize this message, which
332                 // means an incompatible version of ttsession is being
333                 // used
334                                         
335                 fprintf(stderr, catgets(_ttcatd, 9, 3,
336                                         "%s: session <%s> does not support "
337                                         "Session_Trace.  Use kill -USR1 instead. "
338                                         "See ttsession(1).\n"),
339                         (char *) progname, tt_message_session(msg) );
340                 exit(5);
341         }
342         else if (mstat != TT_OK) {
343                 fprintf(stderr, "%s: Session_Trace: %s\n",
344                         (char *) progname, tt_status_message(mstat));
345                 exit(2);
346         }
347         
348         return TT_CALLBACK_PROCESSED;
349 }
350
351 static void send_on_exit()
352 {
353         Tt_message msg = tt_prequest_create(TT_SESSION, SESSION_TRACE_OP);
354         tt_message_arg_add(msg, TT_IN, "string",
355                            "version 1; states none; functions none" );
356         tt_message_send_on_exit(msg);
357 }
358
359 #define MAXLINE 255
360
361 static int open_pipe(_Tt_string& pipe_name, int* fd)
362 {
363         int quit = 0;
364         int exit_status = 0;
365         
366         while ((! quit) && (*fd <= 0)) {
367                 *fd = open((char *) pipe_name, O_RDONLY | O_NDELAY);
368                 if ((*fd < 0) && (errno != EINTR)) {
369                         _Tt_string msg = progname.cat( ": " ).cat(pipe_name);
370                         perror(msg);
371                         exit_status = 2;
372                 }
373                 quit = 1;
374         }
375         
376         return exit_status;
377 }
378
379
380 // No silly cracks about this routine's name, please!
381
382 static int tail_pipe(int fd, _Tt_string& pipenm, pid_t child_pid, int sink)
383 {
384         int             bufsize = MAXLINE;
385         int             nbytes;
386         int             quit = 0;
387         struct pollfd   fds[2];
388         char            buf[MAXLINE];
389         
390         if (pipenm.len() == 0) {
391                 int done = 0;
392                 while (!done) {
393                         pid_t child_waited_for = waitpid( child_pid, 0, 0 );
394                         if (child_waited_for < 0 && errno == EINTR) continue;
395                         if (child_waited_for < 0 && errno == ECHILD) break;
396                         if (child_waited_for < 0) {
397                                 fprintf(stderr, "%s: waitpid(): ",
398                                         (char *) progname);
399                                 perror(0);
400                                 if(errno == ECHILD){
401                                         exit(2);
402                                 }
403                         }
404                         done = 1;
405                 }
406         } else {
407                 
408                 while (!quit) {
409                         fds[0].fd = fd;
410                         fds[0].events = POLLIN;
411                         fds[0].revents = 0;
412                         fds[1].fd = ttfd;
413                         fds[1].events = POLLIN;
414                         fds[1].revents = 0;
415                         
416                         int numfds = poll(fds, ttfd ? 2 : 1, poll_timeout);
417                         if (numfds < 0) {
418                                 if (errno != EINTR) {
419                                         _Tt_string msg =
420                                                 progname.cat( ": poll()" );
421                                         perror( msg );
422                                         exit_status = 2;
423                                         quit = 1;
424                                 }
425                                 else if (makequit) {
426                                         quit = 1;
427                                 }
428                                 continue;
429                         }
430                         else if (numfds == 0) {
431                                 quit = 1;
432                                 continue;
433                         }
434                         
435                         if (fds[ 0 ].revents & POLLHUP) {
436                                 if (makequit) {
437                                         
438                                         // Child has exited -- do a last read
439                                         while ((nbytes = read(fd,
440                                                               buf,
441                                                               bufsize)) > 0) {
442                                                 write(sink, buf, nbytes);
443                                         }
444                                         quit = 1;
445                                 }
446                                 else {
447                                         pid_t child_waited_for = waitpid(child_pid,
448                                                                          0, 0 );
449                                         if (child_waited_for == child_pid) {
450
451                                                 quit = 1;
452                                         }
453                                         else {
454                                         
455                                                 // Tracing is temporarily stopped.
456                                                 // Periodically wake up to see if it
457                                                 // has started again.
458                                                                 
459                                                 sleep( 1 );
460                                         }
461                                 }
462                         }
463                         else if (fds[ 0 ].revents & POLLIN) {
464                                 if (makequit) {
465                                         quit = 1;
466                                 }
467                                 else {
468                                         nbytes = read(fd, buf, bufsize);
469                                         if (nbytes > 0) {
470                                                 write(sink, buf, nbytes);
471                                         }
472                                 }
473                         }
474                         if (fds[ 1 ].revents & POLLIN) {
475                                 Tt_message msg = tt_message_receive();
476                                 if (tt_ptr_error(msg) == TT_ERR_NOMP) {
477                                         quit = 1;
478                                 }
479                         }
480                 }
481                 if (fd > 0) {
482                         close(fd);
483                 }
484         }
485         return exit_status;
486 }