/[svn]/linuxsampler/trunk/src/common/stacktrace.c
ViewVC logotype

Annotation of /linuxsampler/trunk/src/common/stacktrace.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 361 - (hide annotations) (download)
Wed Feb 9 01:22:18 2005 UTC (19 years, 2 months ago) by schoenebeck
File MIME type: text/plain
File size: 18172 byte(s)
* bunch of fixes for OSX (patch by Stephane Letz)

1 schoenebeck 270 /*************************************************************************
2     *
3 schoenebeck 361 * $Id: stacktrace.c,v 1.2 2005-02-09 01:22:17 schoenebeck Exp $
4 schoenebeck 270 *
5     * Copyright (c) 1998 by Bjorn Reese <breese@mail1.stofanet.dk>
6     *
7     * Permission to use, copy, modify, and distribute this software for any
8     * purpose with or without fee is hereby granted, provided that the above
9     * copyright notice and this permission notice appear in all copies.
10     *
11     * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
12     * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
13     * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND
14     * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER.
15     *
16     ************************************************************************
17     *
18     * 1999/08/19 - breese
19     * - Cleaned up the interface
20     *
21     * 1999/08/06 - breese
22     * - Added U_STACK_TRACE for HP/UX
23     *
24     * 1999/01/24 - breese
25     * - Added GCC_DumpStack
26     *
27     * 1998/12/21 - breese
28     * - Fixed include files and arguments for waitpid()
29     * - Made an AIX workaround for waitpid() exiting with EINTR
30     * - Added a missing 'quit' command for OSF dbx
31     *
32     ************************************************************************/
33    
34     #if defined(unix) || defined(__unix) || defined(__xlC__)
35     # define PLATFORM_UNIX
36     #elif defined(WIN32) || defined(_WIN32)
37     # define PLATFORM_WIN32
38     #endif
39    
40     #if defined(_AIX) || defined(__xlC__)
41     # define PLATFORM_AIX
42     #elif defined(__FreeBSD__)
43     # define PLATFORM_FREEBSD
44     #elif defined(hpux) || defined(__hpux) || defined(_HPUX_SOURCE)
45     # define PLATFORM_HPUX
46     #elif defined(sgi) || defined(mips) || defined(_SGI_SOURCE)
47     # define PLATFORM_IRIX
48     #elif defined(__osf__)
49     # define PLATFORM_OSF
50     #elif defined(M_I386) || defined(_SCO_DS) || defined(_SCO_C_DIALECT)
51     # define PLATFORM_SCO
52     #elif defined(sun) || defined(__sun__) || defined(__SUNPRO_C)
53     # if defined(__SVR4) || defined(__svr4__)
54     # define PLATFORM_SOLARIS
55     # endif
56     #endif
57    
58     /* ANSI C includes */
59     #include <stdio.h>
60     #include <stdlib.h>
61     #include <stdarg.h>
62     #include <string.h>
63     #include <limits.h>
64     #include <errno.h>
65     #include <signal.h>
66 schoenebeck 361 #include <unistd.h>
67 schoenebeck 270 #if defined(PLATFORM_UNIX)
68     # include <unistd.h>
69     # include <sys/types.h>
70     # include <sys/wait.h>
71     # if defined(PLATFORM_IRIX) && defined(USE_BUILTIN)
72     /* Compile with -DUSE_BUILTIN and -lexc */
73     # include <libexc.h>
74     # elif defined(PLATFORM_HPUX) && defined(USE_BUILTIN)
75     /* Compile with -DUSE_BUILTIN and -lcl */
76     extern void U_STACK_TRACE(void);
77     # endif
78     #endif
79    
80     #include "stacktrace.h"
81    
82     #ifndef FALSE
83     # define FALSE (0 == 1)
84     # define TRUE (! FALSE)
85     #endif
86    
87     #define SYS_ERROR -1
88    
89     #ifndef EXIT_SUCCESS
90     # define EXIT_SUCCESS 0
91     #endif
92     #ifndef EXIT_FAILURE
93     # define EXIT_FAILURE 1
94     #endif
95    
96     #define MAX_BUFFER_SIZE 512
97     #if defined(__GNUC__)
98     /* Change the code if ADDRESSLIST_SIZE is increased */
99     # define ADDRESSLIST_SIZE 20
100     #endif
101    
102     /*************************************************************************
103     * Globals
104     *
105     * We cannot pass custom arguments to signal handlers so we store
106     * them as global variables (but limit their scope to this file.)
107     */
108     static const char *global_progname;
109     static int global_output = STDOUT_FILENO;
110    
111    
112     #if defined(PLATFORM_UNIX)
113     /*************************************************************************
114     * my_pclose [private]
115     */
116     static void my_pclose(int fd, int pid)
117     {
118     close(fd);
119     /* Make sure the the child process has terminated */
120     (void)kill(pid, SIGTERM);
121     }
122    
123     /*************************************************************************
124     * my_popen [private]
125     */
126     static int my_popen(const char *command, pid_t *pid)
127     {
128     int rc;
129     //int pipefd[2];
130    
131     //FIXME: deactivated separate piping of debugger output, as it caused problems in conjunction with threads
132     //rc = pipe(pipefd);
133     rc = SYS_ERROR;
134     //if (SYS_ERROR != rc)
135     {
136     *pid = fork();
137     switch (*pid)
138     {
139     case SYS_ERROR:
140     //rc = SYS_ERROR;
141     //close(pipefd[0]);
142     //close(pipefd[1]);
143     break;
144    
145     case 0: /* Child */
146     //close(pipefd[0]);
147     //close(STDOUT_FILENO);
148     //close(STDERR_FILENO);
149     //dup2(pipefd[1], STDOUT_FILENO);
150     //dup2(pipefd[1], STDERR_FILENO);
151     /*
152     * The System() call assumes that /bin/sh is
153     * always available, and so will we.
154     */
155     execl("/bin/sh", "/bin/sh", "-c", command, NULL);
156     _exit(EXIT_FAILURE);
157     break;
158    
159     default: /* Parent */
160     //close(pipefd[1]);
161     //rc = pipefd[0];
162     break;
163     } /* switch */
164     }
165     return rc;
166     }
167    
168     /*************************************************************************
169     * my_getline [private]
170     */
171     static int my_getline(int fd, char *buffer, int max)
172     {
173     char c;
174     int i = 0;
175    
176     do {
177     if (read(fd, &c, 1) < 1)
178     return 0;
179     if (i < max)
180     buffer[i++] = c;
181     } while (c != '\n');
182     buffer[i] = (char)0;
183     return i;
184     }
185    
186     /*************************************************************************
187     * GCC_DumpStack [private]
188     *
189     *
190     * This code is still experimental.
191     *
192     * Stackbased arrays are used to prevent allocating from the heap.
193     * Warning: sprintf/sscanf are not ASync safe. They were used for
194     * convenience.
195     *
196     * 'nm' is used because it is most widespread
197     * GNU: nm [-B]
198     * Solaris: nm -x -p
199     * IRIX: nm -x -B (but __builtin_return_address() always returns NULL)
200     * AIX: nm -x -B
201     * OSF/1: nm -B
202     * SCO/OpenServer: nm -x -p
203     * HP/UX nm -x -p
204     */
205    
206     #if defined(__GNUC__) && defined(USE_BUILTIN)
207    
208     typedef struct {
209     unsigned long realAddress;
210     unsigned long closestAddress;
211     char name[MAX_BUFFER_SIZE + 1];
212     char type;
213     } address_T;
214    
215    
216     static void GCC_DumpStack(void)
217     {
218     int i;
219     void *p = &p; /* dummy start value */
220     address_T syms[ADDRESSLIST_SIZE + 1];
221     char buffer[MAX_BUFFER_SIZE];
222     int fd;
223     pid_t pid;
224     unsigned long addr;
225     unsigned long highestAddress;
226     unsigned long lowestAddress;
227     char type;
228     char *pname;
229     char name[MAX_BUFFER_SIZE];
230     int number;
231    
232     for (i = 0; p; i++)
233     {
234     /*
235     * This is based on code by Steve Coleman <steve.colemanjhuapl.edu>
236     *
237     * __builtin_return_address() only accepts a constant as argument.
238     */
239     switch (i)
240     {
241     case 0:
242     p = __builtin_return_address(0);
243     break;
244     case 1:
245     p = __builtin_return_address(1);
246     break;
247     case 2:
248     p = __builtin_return_address(2);
249     break;
250     case 3:
251     p = __builtin_return_address(3);
252     break;
253     case 4:
254     p = __builtin_return_address(4);
255     break;
256     case 5:
257     p = __builtin_return_address(5);
258     break;
259     case 6:
260     p = __builtin_return_address(6);
261     break;
262     case 7:
263     p = __builtin_return_address(7);
264     break;
265     case 8:
266     p = __builtin_return_address(8);
267     break;
268     case 9:
269     p = __builtin_return_address(9);
270     break;
271     case 10:
272     p = __builtin_return_address(10);
273     break;
274     case 11:
275     p = __builtin_return_address(11);
276     break;
277     case 12:
278     p = __builtin_return_address(12);
279     break;
280     case 13:
281     p = __builtin_return_address(13);
282     break;
283     case 14:
284     p = __builtin_return_address(14);
285     break;
286     case 15:
287     p = __builtin_return_address(15);
288     break;
289     case 16:
290     p = __builtin_return_address(16);
291     break;
292     case 17:
293     p = __builtin_return_address(17);
294     break;
295     case 18:
296     p = __builtin_return_address(18);
297     break;
298     case 19:
299     p = __builtin_return_address(19);
300     break;
301     default:
302     /* Change ADDRESSLIST_SIZE if more are added */
303     p = NULL;
304     break;
305     }
306     if ((p) && (i < ADDRESSLIST_SIZE))
307     {
308     syms[i].realAddress = (unsigned long)p;
309     syms[i].closestAddress = 0;
310     syms[i].name[0] = (char)0;
311     syms[i].type = ' ';
312     }
313     else
314     {
315     syms[i].realAddress = 0;
316     break; /* for */
317     }
318     } /* for */
319    
320    
321     /* First find out if we are using GNU or vendor nm */
322     number = 0;
323     strcpy(buffer, "nm -V 2>/dev/null | grep GNU | wc -l");
324     fd = my_popen(buffer, &pid);
325     if (SYS_ERROR != fd)
326     {
327     if (my_getline(fd, buffer, sizeof(buffer)))
328     {
329     sscanf(buffer, "%d", &number);
330     }
331     my_pclose(fd, pid);
332     }
333     if (number == 0) /* vendor nm */
334     {
335     # if defined(PLATFORM_SOLARIS) || defined(PLATFORM_SCO) || defined(PLATFORM_HPUX)
336     strcpy(buffer, "nm -x -p ");
337     # elif defined(PLATFORM_AIX) || defined(PLATFORM_IRIX) || defined(PLATFORM_OSF)
338     strcpy(buffer, "nm -x -B ");
339     # else
340     strcpy(buffer, "nm -B ");
341     # endif
342     }
343     else /* GNU nm */
344     strcpy(buffer, "nm -B ");
345     strcat(buffer, global_progname);
346    
347     lowestAddress = ULONG_MAX;
348     highestAddress = 0;
349     fd = my_popen(buffer, &pid);
350     if (SYS_ERROR != fd)
351     {
352     while (my_getline(fd, buffer, sizeof(buffer)))
353     {
354     if (buffer[0] == '\n')
355     continue;
356     if (3 == sscanf(buffer, "%lx %c %s", &addr, &type, name))
357     {
358     if ((type == 't') || type == 'T')
359     {
360     if (addr == 0)
361     continue; /* while */
362     if (addr < lowestAddress)
363     lowestAddress = addr;
364     if (addr > highestAddress)
365     highestAddress = addr;
366     for (i = 0; syms[i].realAddress != 0; i++)
367     {
368     if ((addr <= syms[i].realAddress) &&
369     (addr > syms[i].closestAddress))
370     {
371     syms[i].closestAddress = addr;
372     strncpy(syms[i].name, name, MAX_BUFFER_SIZE);
373     syms[i].name[MAX_BUFFER_SIZE] = (char)0;
374     syms[i].type = type;
375     }
376     }
377     }
378     }
379     }
380     my_pclose(fd, pid);
381    
382     for (i = 0; syms[i].realAddress != 0; i++)
383     {
384     if ((syms[i].name[0] == (char)0) ||
385     (syms[i].realAddress <= lowestAddress) ||
386     (syms[i].realAddress >= highestAddress))
387     {
388     sprintf(buffer, "[%d] 0x%08lx ???\n", i, syms[i].realAddress);
389     }
390     else
391     {
392     sprintf(buffer, "[%d] 0x%08lx <%s + 0x%lx> %c\n",
393     i,
394     syms[i].realAddress,
395     syms[i].name,
396     syms[i].realAddress - syms[i].closestAddress,
397     syms[i].type);
398     }
399     write(global_output, buffer, strlen(buffer));
400     }
401     }
402     }
403     #endif
404    
405     /*************************************************************************
406     * DumpStack [private]
407     */
408     static int DumpStack(char *format, ...)
409     {
410     int gotSomething = FALSE;
411     int fd;
412     pid_t pid;
413     int status = EXIT_FAILURE;
414     int rc;
415     va_list args;
416     char *buffer;
417     char cmd[MAX_BUFFER_SIZE];
418     char buf[MAX_BUFFER_SIZE];
419    
420     /*
421     * Please note that vsprintf() is not ASync safe (ie. cannot safely
422     * be used from a signal handler.) If this proves to be a problem
423     * then the cmd string can be built by more basic functions such as
424     * strcpy, strcat, and a homemade integer-to-ascii function.
425     */
426     va_start(args, format);
427     vsprintf(cmd, format, args);
428     va_end(args);
429    
430     fd = my_popen(cmd, &pid);
431     if (SYS_ERROR != fd)
432     {
433     /*
434     * Wait for the child to exit. This must be done
435     * to make the debugger attach successfully.
436     * The output from the debugger is buffered on
437     * the pipe.
438     *
439     * AIX needs the looping hack
440     */
441     do
442     {
443     rc = waitpid(pid, &status, 0);
444     }
445     while ((SYS_ERROR == rc) && (EINTR == errno));
446    
447     if ((WIFEXITED(status)) && (WEXITSTATUS(status) == EXIT_SUCCESS))
448     {
449     while (my_getline(fd, buf, sizeof(buf)))
450     {
451     buffer = buf;
452     if (! gotSomething)
453     {
454     write(global_output, "Output from ",
455     strlen("Output from "));
456     strtok(cmd, " ");
457     write(global_output, cmd, strlen(cmd));
458     write(global_output, "\n", strlen("\n"));
459     gotSomething = TRUE;
460     }
461     if ('\n' == buf[strlen(buf)-1])
462     {
463     buf[strlen(buf)-1] = (char)0;
464     }
465     write(global_output, buffer, strlen(buffer));
466     write(global_output, "\n", strlen("\n"));
467     }
468     }
469     my_pclose(fd, pid);
470     }
471     return gotSomething;
472     }
473     #endif /* PLATFORM_UNIX */
474    
475     /*************************************************************************
476     * StackTrace
477     */
478     void StackTrace(void)
479     {
480     #if defined(PLATFORM_UNIX)
481     /*
482     * In general dbx seems to do a better job than gdb.
483     *
484     * Different dbx implementations require different flags/commands.
485     */
486    
487     # if defined(PLATFORM_AIX)
488    
489     if (DumpStack("dbx -a %d 2>/dev/null <<EOF\n"
490     "where\n"
491     "detach\n"
492     "EOF\n",
493     (int)getpid()))
494     return;
495    
496     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
497     "set prompt\n"
498     "where\n"
499     "detach\n"
500     "quit\n"
501     "EOF\n",
502     global_progname, (int)getpid()))
503     return;
504    
505     # elif defined(PLATFORM_FREEBSD)
506    
507     /*
508     * FreeBSD insists on sending a SIGSTOP to the process we
509     * attach to, so we let the debugger send a SIGCONT to that
510     * process after we have detached.
511     */
512     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
513     "set prompt\n"
514     "where\n"
515     "detach\n"
516     "shell kill -CONT %d\n"
517     "quit\n"
518     "EOF\n",
519     global_progname, (int)getpid(), (int)getpid()))
520     return;
521    
522     # elif defined(PLATFORM_HPUX)
523    
524     /*
525     * HP decided to call their debugger xdb.
526     *
527     * This does not seem to work properly yet. The debugger says
528     * "Note: Stack traces may not be possible until you are
529     * stopped in user code." on HP-UX 09.01
530     *
531     * -L = line-oriented interface.
532     * "T [depth]" gives a stacktrace with local variables.
533     * The final "y" is confirmation to the quit command.
534     */
535    
536     if (DumpStack("xdb -P %d -L %s 2>&1 <<EOF\n"
537     "T 50\n"
538     "q\ny\n"
539     "EOF\n",
540     (int)getpid(), global_progname))
541     return;
542    
543     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
544     "set prompt\n"
545     "where\n"
546     "detach\n"
547     "quit\n"
548     "EOF\n",
549     global_progname, (int)getpid()))
550     return;
551    
552     # if defined(PLATFORM_HPUX) && defined(USE_BUILTIN)
553     U_STACK_TRACE();
554     return;
555     # endif
556    
557     # elif defined(PLATFORM_IRIX)
558    
559     /*
560     * "set $page=0" drops hold mode
561     * "dump ." displays the contents of the variables
562     */
563     if (DumpStack("dbx -p %d 2>/dev/null <<EOF\n"
564     "set \\$page=0\n"
565     "where\n"
566     # if !defined(__GNUC__)
567     /* gcc does not generate this information */
568     "dump .\n"
569     # endif
570     "detach\n"
571     "EOF\n",
572     (int)getpid()))
573     return;
574    
575     # if defined(USE_BUILTIN)
576     if (trace_back_stack_and_print())
577     return;
578     # endif
579    
580     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
581     "set prompt\n"
582     "echo --- Stacktrace\\n\n"
583     "where\n"
584     "echo --- Symbols\\n\n"
585     "frame 5\n" /* Skip signal handler frames */
586     "set \\$x = 50\n"
587     "while (\\$x)\n" /* Print local variables for each frame */
588     "info locals\n"
589     "up\n"
590     "set \\$x--\n"
591     "end\n"
592     "echo ---\\n\n"
593     "detach\n"
594     "quit\n"
595     "EOF\n",
596     global_progname, (int)getpid()))
597     return;
598    
599     # elif defined(PLATFORM_OSF)
600    
601     if (DumpStack("dbx -pid %d %s 2>/dev/null <<EOF\n"
602     "where\n"
603     "detach\n"
604     "quit\n"
605     "EOF\n",
606     (int)getpid(), global_progname))
607     return;
608    
609     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
610     "set prompt\n"
611     "where\n"
612     "detach\n"
613     "quit\n"
614     "EOF\n",
615     global_progname, (int)getpid()))
616     return;
617    
618     # elif defined(PLATFORM_SCO)
619    
620     /*
621     * SCO OpenServer dbx is like a catch-22. The 'detach' command
622     * depends on whether ptrace(S) support detaching or not. If it
623     * is supported then 'detach' must be used, otherwise the process
624     * will be killed upon dbx exit. If it isn't supported then 'detach'
625     * will cause the process to be killed. We do not want it to be
626     * killed.
627     *
628     * Out of two evils, the omission of 'detach' was chosen because
629     * it worked on our system.
630     */
631     if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
632     "where\n"
633     "quit\nEOF\n",
634     global_progname, (int)getpid()))
635     return;
636    
637     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
638     "set prompt\n"
639     "where\n"
640     "detach\n"
641     "quit\n"
642     "EOF\n",
643     global_progname, (int)getpid()))
644     return;
645    
646     # elif defined(PLATFORM_SOLARIS)
647    
648     if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
649     "where\n"
650     "detach\n"
651     "EOF\n",
652     global_progname, (int)getpid()))
653     return;
654    
655     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
656     "set prompt\n"
657     "echo --- Stacktrace\\n\n"
658     "where\n"
659     "echo --- Symbols\\n\n"
660     "frame 5\n" /* Skip signal handler frames */
661     "set \\$x = 50\n"
662     "while (\\$x)\n" /* Print local variables for each frame */
663     "info locals\n"
664     "up\n"
665     "set \\$x--\n"
666     "end\n"
667     "echo ---\\n\n"
668     "detach\n"
669     "quit\n"
670     "EOF\n",
671     global_progname, (int)getpid()))
672     return;
673    
674     if (DumpStack("/usr/proc/bin/pstack %d",
675     (int)getpid()))
676     return;
677    
678     /*
679     * Other Unices (AIX, HPUX, SCO) also have adb, but
680     * they seem unable to attach to a running process.
681     */
682     if (DumpStack("adb %s 2>&1 <<EOF\n"
683     "0t%d:A\n" /* Attach to pid */
684     "\\$c\n" /* print stacktrace */
685     ":R\n" /* Detach */
686     "\\$q\n" /* Quit */
687     "EOF\n",
688     global_progname, (int)getpid()))
689     return;
690    
691     # else /* All other Unix platforms */
692    
693     /*
694     * TODO: SCO/UnixWare 7 must be something like (not tested)
695     * debug -i c <pid> <<EOF\nstack -f 4\nquit\nEOF\n
696     */
697    
698     # if !defined(__GNUC__)
699     if (DumpStack("dbx %s %d 2>/dev/null <<EOF\n"
700     "where\n"
701     "detach\n"
702     "EOF\n",
703     global_progname, (int)getpid()))
704     return;
705     # endif
706    
707     if (DumpStack("gdb -q %s %d 2>/dev/null <<EOF\n"
708     "set prompt\n"
709     "echo --- Stacktrace\\n\n"
710     "where\n"
711     "echo --- Symbols\\n\n"
712     "frame 4\n"
713     "set \\$x = 50\n"
714     "while (\\$x)\n" /* Print local variables for each frame */
715     "info locals\n"
716     "up\n"
717     "set \\$x--\n"
718     "end\n"
719     "echo ---\\n\n"
720     "detach\n"
721     "quit\n"
722     "EOF\n",
723     global_progname, (int)getpid()))
724     return;
725    
726     # endif
727    
728     # if defined(__GNUC__) && defined(USE_BUILTIN)
729    
730     GCC_DumpStack();
731    
732     # endif
733    
734     write(global_output,
735     "No debugger found\n", strlen("No debugger found\n"));
736    
737     #elif defined(PLATFORM_WIN32)
738     /* Use StackWalk() */
739     #endif
740     }
741    
742     /*************************************************************************
743     * StackTraceInit
744     */
745     void StackTraceInit(const char *in_name, int in_handle)
746     {
747     global_progname = in_name;
748     global_output = (in_handle == -1) ? STDOUT_FILENO : in_handle;
749     }
750    
751    
752     /*************************************************************************
753     * test
754     */
755     #if defined(STANDALONE)
756    
757     void CrashHandler(int sig)
758     {
759     /* Reinstall default handler to prevent race conditions */
760     signal(sig, SIG_DFL);
761     /* Print the stack trace */
762     StackTrace();
763     /* And exit because we may have corrupted the internal
764     * memory allocation lists. Use abort() if we want to
765     * generate a core dump. */
766     _exit(EXIT_FAILURE);
767     }
768    
769    
770     void Crash(void)
771     {
772     /* Force a crash */
773     strcpy(NULL, "");
774     }
775    
776     int main(int argc, char *argv[])
777     {
778     struct sigaction sact;
779    
780     StackTraceInit(argv[0], -1);
781    
782     sigemptyset(&sact.sa_mask);
783     sact.sa_flags = 0;
784     sact.sa_handler = CrashHandler;
785     sigaction(SIGSEGV, &sact, NULL);
786     sigaction(SIGBUS, &sact, NULL);
787    
788     Crash();
789     return EXIT_SUCCESS;
790     }
791     #endif

  ViewVC Help
Powered by ViewVC