1*0Sstevel@tonic-gate /* 2*0Sstevel@tonic-gate * CDDL HEADER START 3*0Sstevel@tonic-gate * 4*0Sstevel@tonic-gate * The contents of this file are subject to the terms of the 5*0Sstevel@tonic-gate * Common Development and Distribution License, Version 1.0 only 6*0Sstevel@tonic-gate * (the "License"). You may not use this file except in compliance 7*0Sstevel@tonic-gate * with the License. 8*0Sstevel@tonic-gate * 9*0Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10*0Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 11*0Sstevel@tonic-gate * See the License for the specific language governing permissions 12*0Sstevel@tonic-gate * and limitations under the License. 13*0Sstevel@tonic-gate * 14*0Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 15*0Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16*0Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 17*0Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 18*0Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 19*0Sstevel@tonic-gate * 20*0Sstevel@tonic-gate * CDDL HEADER END 21*0Sstevel@tonic-gate */ 22*0Sstevel@tonic-gate /* 23*0Sstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24*0Sstevel@tonic-gate * Use is subject to license terms. 25*0Sstevel@tonic-gate */ 26*0Sstevel@tonic-gate 27*0Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 28*0Sstevel@tonic-gate 29*0Sstevel@tonic-gate #include <stdio.h> 30*0Sstevel@tonic-gate #include <stdlib.h> 31*0Sstevel@tonic-gate #include <unistd.h> 32*0Sstevel@tonic-gate #include <string.h> 33*0Sstevel@tonic-gate #include <errno.h> 34*0Sstevel@tonic-gate #include <sys/wait.h> 35*0Sstevel@tonic-gate #include <apptrace.h> 36*0Sstevel@tonic-gate #include <libintl.h> 37*0Sstevel@tonic-gate #include <locale.h> 38*0Sstevel@tonic-gate 39*0Sstevel@tonic-gate #ifdef TRUE 40*0Sstevel@tonic-gate #undef TRUE 41*0Sstevel@tonic-gate #endif 42*0Sstevel@tonic-gate #ifdef FALSE 43*0Sstevel@tonic-gate #undef FALSE 44*0Sstevel@tonic-gate #endif 45*0Sstevel@tonic-gate #define TRUE 1 46*0Sstevel@tonic-gate #define FALSE 0 47*0Sstevel@tonic-gate 48*0Sstevel@tonic-gate /* Various list pointers */ 49*0Sstevel@tonic-gate static char *fromlist; 50*0Sstevel@tonic-gate static char *fromexcl; 51*0Sstevel@tonic-gate static char *tolist; 52*0Sstevel@tonic-gate static char *toexcl; 53*0Sstevel@tonic-gate 54*0Sstevel@tonic-gate static char *iflist; 55*0Sstevel@tonic-gate static char *ifexcl; 56*0Sstevel@tonic-gate static char *viflist; 57*0Sstevel@tonic-gate static char *vifexcl; 58*0Sstevel@tonic-gate 59*0Sstevel@tonic-gate /* The supported options */ 60*0Sstevel@tonic-gate static char const *optlet = "F:fo:T:t:v:"; 61*0Sstevel@tonic-gate /* basename(argv[0]) */ 62*0Sstevel@tonic-gate static char const *command; 63*0Sstevel@tonic-gate 64*0Sstevel@tonic-gate /* The environment variables that'll get picked up by apptrace.so.1 */ 65*0Sstevel@tonic-gate static char const *APPTRACE_BINDTO = "APPTRACE_BINDTO="; 66*0Sstevel@tonic-gate static char const *APPTRACE_BINDTO_EXCLUDE = "APPTRACE_BINDTO_EXCLUDE="; 67*0Sstevel@tonic-gate static char const *APPTRACE_BINDFROM = "APPTRACE_BINDFROM="; 68*0Sstevel@tonic-gate static char const *APPTRACE_BINDFROM_EXCLUDE = "APPTRACE_BINDFROM_EXCLUDE="; 69*0Sstevel@tonic-gate static char const *APPTRACE_OUTPUT = "APPTRACE_OUTPUT="; 70*0Sstevel@tonic-gate static char const *APPTRACE_PID = "APPTRACE_PID="; 71*0Sstevel@tonic-gate static char const *APPTRACE_INTERFACES = "APPTRACE_INTERFACES="; 72*0Sstevel@tonic-gate static char const *APPTRACE_INTERFACES_EXCLUDE = "APPTRACE_INTERFACES_EXCLUDE="; 73*0Sstevel@tonic-gate static char const *APPTRACE_VERBOSE = "APPTRACE_VERBOSE="; 74*0Sstevel@tonic-gate static char const *APPTRACE_VERBOSE_EXCLUDE = "APPTRACE_VERBOSE_EXCLUDE="; 75*0Sstevel@tonic-gate 76*0Sstevel@tonic-gate /* Some default values for the above */ 77*0Sstevel@tonic-gate static char *LD_AUDIT = "LD_AUDIT=/usr/lib/abi/apptrace.so.1"; 78*0Sstevel@tonic-gate #if defined(sparc) || defined(__sparcv9) 79*0Sstevel@tonic-gate static char *LD_AUDIT_64 = 80*0Sstevel@tonic-gate "LD_AUDIT_64=/usr/lib/abi/sparcv9/apptrace.so.1"; 81*0Sstevel@tonic-gate #elif defined(i386) || defined(__amd64) 82*0Sstevel@tonic-gate static char *LD_AUDIT_64 = 83*0Sstevel@tonic-gate "LD_AUDIT_64=/usr/lib/abi/amd64/apptrace.so.1"; 84*0Sstevel@tonic-gate #else 85*0Sstevel@tonic-gate #error Unsupported Platform 86*0Sstevel@tonic-gate #endif 87*0Sstevel@tonic-gate 88*0Sstevel@tonic-gate static char const *one = "1"; 89*0Sstevel@tonic-gate 90*0Sstevel@tonic-gate /* The local support functions */ 91*0Sstevel@tonic-gate static void usage(char const *); 92*0Sstevel@tonic-gate static void stuffenv(char const *, char const *); 93*0Sstevel@tonic-gate static char *buildlist(char **, char const *); 94*0Sstevel@tonic-gate 95*0Sstevel@tonic-gate int 96*0Sstevel@tonic-gate main(int argc, char **argv) 97*0Sstevel@tonic-gate { 98*0Sstevel@tonic-gate int opt; 99*0Sstevel@tonic-gate int fflag = FALSE; 100*0Sstevel@tonic-gate int errflg = FALSE; 101*0Sstevel@tonic-gate char *outfile = NULL; 102*0Sstevel@tonic-gate int stat_loc; 103*0Sstevel@tonic-gate pid_t wret, pid; 104*0Sstevel@tonic-gate 105*0Sstevel@tonic-gate (void) setlocale(LC_ALL, ""); 106*0Sstevel@tonic-gate #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 107*0Sstevel@tonic-gate #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 108*0Sstevel@tonic-gate #endif 109*0Sstevel@tonic-gate (void) textdomain(TEXT_DOMAIN); 110*0Sstevel@tonic-gate 111*0Sstevel@tonic-gate 112*0Sstevel@tonic-gate /* Squirrel the basename of the command name away. */ 113*0Sstevel@tonic-gate if ((command = strrchr(argv[0], '/')) != NULL) 114*0Sstevel@tonic-gate command++; 115*0Sstevel@tonic-gate else 116*0Sstevel@tonic-gate command = argv[0]; 117*0Sstevel@tonic-gate 118*0Sstevel@tonic-gate while ((opt = getopt(argc, argv, optlet)) != EOF) { 119*0Sstevel@tonic-gate switch (opt) { 120*0Sstevel@tonic-gate case 'F': 121*0Sstevel@tonic-gate if (*optarg == '!') 122*0Sstevel@tonic-gate (void) buildlist(&fromexcl, optarg + 1); 123*0Sstevel@tonic-gate else 124*0Sstevel@tonic-gate (void) buildlist(&fromlist, optarg); 125*0Sstevel@tonic-gate break; 126*0Sstevel@tonic-gate case 'f': 127*0Sstevel@tonic-gate fflag = TRUE; 128*0Sstevel@tonic-gate break; 129*0Sstevel@tonic-gate case 'o': 130*0Sstevel@tonic-gate outfile = optarg; 131*0Sstevel@tonic-gate break; 132*0Sstevel@tonic-gate case 'T': 133*0Sstevel@tonic-gate if (*optarg == '!') 134*0Sstevel@tonic-gate (void) buildlist(&toexcl, optarg + 1); 135*0Sstevel@tonic-gate else 136*0Sstevel@tonic-gate (void) buildlist(&tolist, optarg); 137*0Sstevel@tonic-gate break; 138*0Sstevel@tonic-gate case 't': 139*0Sstevel@tonic-gate if (*optarg == '!') 140*0Sstevel@tonic-gate (void) buildlist(&ifexcl, optarg + 1); 141*0Sstevel@tonic-gate else 142*0Sstevel@tonic-gate (void) buildlist(&iflist, optarg); 143*0Sstevel@tonic-gate break; 144*0Sstevel@tonic-gate case 'v': 145*0Sstevel@tonic-gate if (*optarg == '!') 146*0Sstevel@tonic-gate (void) buildlist(&vifexcl, optarg + 1); 147*0Sstevel@tonic-gate else 148*0Sstevel@tonic-gate (void) buildlist(&viflist, optarg); 149*0Sstevel@tonic-gate break; 150*0Sstevel@tonic-gate default: 151*0Sstevel@tonic-gate errflg = TRUE; 152*0Sstevel@tonic-gate break; 153*0Sstevel@tonic-gate } 154*0Sstevel@tonic-gate } 155*0Sstevel@tonic-gate 156*0Sstevel@tonic-gate /* 157*0Sstevel@tonic-gate * Whack the argument vector so that the remainder will be 158*0Sstevel@tonic-gate * ready for passing to exec 159*0Sstevel@tonic-gate */ 160*0Sstevel@tonic-gate argc -= optind; 161*0Sstevel@tonic-gate argv += optind; 162*0Sstevel@tonic-gate 163*0Sstevel@tonic-gate /* 164*0Sstevel@tonic-gate * If there was a problem with the options, or there was no command 165*0Sstevel@tonic-gate * to be run, then give the usage message and bugout. 166*0Sstevel@tonic-gate */ 167*0Sstevel@tonic-gate if (errflg || argc <= 0) { 168*0Sstevel@tonic-gate usage(command); 169*0Sstevel@tonic-gate exit(EXIT_FAILURE); 170*0Sstevel@tonic-gate } 171*0Sstevel@tonic-gate 172*0Sstevel@tonic-gate /* 173*0Sstevel@tonic-gate * This is where the environment gets setup. 174*0Sstevel@tonic-gate */ 175*0Sstevel@tonic-gate if (fflag == TRUE) 176*0Sstevel@tonic-gate stuffenv(APPTRACE_PID, one); 177*0Sstevel@tonic-gate 178*0Sstevel@tonic-gate if (fromexcl != NULL) 179*0Sstevel@tonic-gate stuffenv(APPTRACE_BINDFROM_EXCLUDE, fromexcl); 180*0Sstevel@tonic-gate if (fromlist != NULL) 181*0Sstevel@tonic-gate stuffenv(APPTRACE_BINDFROM, fromlist); 182*0Sstevel@tonic-gate 183*0Sstevel@tonic-gate if (tolist != NULL) 184*0Sstevel@tonic-gate stuffenv(APPTRACE_BINDTO, tolist); 185*0Sstevel@tonic-gate if (toexcl != NULL) 186*0Sstevel@tonic-gate stuffenv(APPTRACE_BINDTO_EXCLUDE, toexcl); 187*0Sstevel@tonic-gate 188*0Sstevel@tonic-gate if (iflist != NULL) 189*0Sstevel@tonic-gate stuffenv(APPTRACE_INTERFACES, iflist); 190*0Sstevel@tonic-gate if (ifexcl != NULL) 191*0Sstevel@tonic-gate stuffenv(APPTRACE_INTERFACES_EXCLUDE, ifexcl); 192*0Sstevel@tonic-gate 193*0Sstevel@tonic-gate if (viflist != NULL) 194*0Sstevel@tonic-gate stuffenv(APPTRACE_VERBOSE, viflist); 195*0Sstevel@tonic-gate if (vifexcl != NULL) 196*0Sstevel@tonic-gate stuffenv(APPTRACE_VERBOSE_EXCLUDE, vifexcl); 197*0Sstevel@tonic-gate 198*0Sstevel@tonic-gate if (outfile != NULL) 199*0Sstevel@tonic-gate stuffenv(APPTRACE_OUTPUT, outfile); 200*0Sstevel@tonic-gate 201*0Sstevel@tonic-gate /* 202*0Sstevel@tonic-gate * It is the setting of the LD_AUDIT environment variable 203*0Sstevel@tonic-gate * that tells ld.so.1 to enable link auditing when the child 204*0Sstevel@tonic-gate * is exec()ed. 205*0Sstevel@tonic-gate */ 206*0Sstevel@tonic-gate (void) putenv(LD_AUDIT); 207*0Sstevel@tonic-gate (void) putenv(LD_AUDIT_64); 208*0Sstevel@tonic-gate 209*0Sstevel@tonic-gate /* 210*0Sstevel@tonic-gate * The environment is now all setup. 211*0Sstevel@tonic-gate * For those about to rock, we salute you! 212*0Sstevel@tonic-gate */ 213*0Sstevel@tonic-gate pid = fork(); 214*0Sstevel@tonic-gate switch (pid) { 215*0Sstevel@tonic-gate /* Error */ 216*0Sstevel@tonic-gate case -1: 217*0Sstevel@tonic-gate (void) fprintf(stderr, gettext("%s: fork failed: %s\n"), 218*0Sstevel@tonic-gate command, strerror(errno)); 219*0Sstevel@tonic-gate exit(EXIT_FAILURE); 220*0Sstevel@tonic-gate break; 221*0Sstevel@tonic-gate /* Child */ 222*0Sstevel@tonic-gate case 0: 223*0Sstevel@tonic-gate /* 224*0Sstevel@tonic-gate * Usual failure is argv[0] does not exist or is 225*0Sstevel@tonic-gate * not executable. 226*0Sstevel@tonic-gate */ 227*0Sstevel@tonic-gate if (execvp(argv[0], argv)) { 228*0Sstevel@tonic-gate (void) fprintf(stderr, gettext("%s: %s: %s\n"), 229*0Sstevel@tonic-gate command, argv[0], strerror(errno)); 230*0Sstevel@tonic-gate _exit(EXIT_FAILURE); 231*0Sstevel@tonic-gate } 232*0Sstevel@tonic-gate break; 233*0Sstevel@tonic-gate /* Parent */ 234*0Sstevel@tonic-gate default: 235*0Sstevel@tonic-gate wret = waitpid(pid, &stat_loc, 0); 236*0Sstevel@tonic-gate if (wret == -1) { 237*0Sstevel@tonic-gate (void) fprintf(stderr, 238*0Sstevel@tonic-gate gettext("%s: waitpid failed: %s\n"), 239*0Sstevel@tonic-gate command, strerror(errno)); 240*0Sstevel@tonic-gate exit(EXIT_FAILURE); 241*0Sstevel@tonic-gate } 242*0Sstevel@tonic-gate 243*0Sstevel@tonic-gate if (wret != pid) { 244*0Sstevel@tonic-gate (void) fprintf(stderr, 245*0Sstevel@tonic-gate gettext("%s: " 246*0Sstevel@tonic-gate "waitpid returned %ld when child pid was %ld\n"), 247*0Sstevel@tonic-gate command, wret, pid); 248*0Sstevel@tonic-gate exit(EXIT_FAILURE); 249*0Sstevel@tonic-gate } 250*0Sstevel@tonic-gate 251*0Sstevel@tonic-gate if (WIFSIGNALED(stat_loc)) { 252*0Sstevel@tonic-gate (void) fprintf(stderr, gettext("\n%s: %s: %s"), 253*0Sstevel@tonic-gate command, argv[0], strsignal(WTERMSIG(stat_loc))); 254*0Sstevel@tonic-gate if (WCOREDUMP(stat_loc)) { 255*0Sstevel@tonic-gate (void) fputs(gettext("(Core dump)"), stderr); 256*0Sstevel@tonic-gate #ifdef DEBUG 257*0Sstevel@tonic-gate (void) fputs(gettext("\nRunning pstack:\n"), 258*0Sstevel@tonic-gate stderr); 259*0Sstevel@tonic-gate (void) putenv("LD_AUDIT="); 260*0Sstevel@tonic-gate (void) putenv("LD_AUDIT_64="); 261*0Sstevel@tonic-gate (void) system("/usr/proc/bin/pstack core"); 262*0Sstevel@tonic-gate #endif 263*0Sstevel@tonic-gate } 264*0Sstevel@tonic-gate (void) putc('\n', stderr); 265*0Sstevel@tonic-gate } 266*0Sstevel@tonic-gate 267*0Sstevel@tonic-gate /* Normal return from main() */ 268*0Sstevel@tonic-gate return (WEXITSTATUS(stat_loc)); 269*0Sstevel@tonic-gate } 270*0Sstevel@tonic-gate /* NOTREACHED */ 271*0Sstevel@tonic-gate } 272*0Sstevel@tonic-gate 273*0Sstevel@tonic-gate /* 274*0Sstevel@tonic-gate * Take a string in the form "VAR=" and another in the 275*0Sstevel@tonic-gate * form "value" and paste them together. 276*0Sstevel@tonic-gate */ 277*0Sstevel@tonic-gate static void 278*0Sstevel@tonic-gate stuffenv(char const *var, char const *val) 279*0Sstevel@tonic-gate { 280*0Sstevel@tonic-gate int lenvar, lenval; 281*0Sstevel@tonic-gate char *stuff; 282*0Sstevel@tonic-gate 283*0Sstevel@tonic-gate lenvar = strlen(var); 284*0Sstevel@tonic-gate lenval = strlen(val); 285*0Sstevel@tonic-gate 286*0Sstevel@tonic-gate if ((stuff = malloc(lenvar + lenval + 1)) == NULL) { 287*0Sstevel@tonic-gate (void) fprintf(stderr, gettext("%s: malloc failed\n"), command); 288*0Sstevel@tonic-gate exit(EXIT_FAILURE); 289*0Sstevel@tonic-gate } 290*0Sstevel@tonic-gate (void) sprintf(stuff, "%s%s", var, val); 291*0Sstevel@tonic-gate (void) putenv(stuff); 292*0Sstevel@tonic-gate } 293*0Sstevel@tonic-gate 294*0Sstevel@tonic-gate /* 295*0Sstevel@tonic-gate * If *dst is empty, use strdup to duplicate src. 296*0Sstevel@tonic-gate * Otherwise: dst = dst + "," + src; 297*0Sstevel@tonic-gate */ 298*0Sstevel@tonic-gate static char * 299*0Sstevel@tonic-gate buildlist(char **dst, char const *src) 300*0Sstevel@tonic-gate { 301*0Sstevel@tonic-gate int len; 302*0Sstevel@tonic-gate char *p; 303*0Sstevel@tonic-gate 304*0Sstevel@tonic-gate /* 305*0Sstevel@tonic-gate * If dst is still empty then dup, 306*0Sstevel@tonic-gate * if dup succeeds set dst. 307*0Sstevel@tonic-gate */ 308*0Sstevel@tonic-gate if (*dst == NULL) { 309*0Sstevel@tonic-gate p = strdup(src); 310*0Sstevel@tonic-gate if (p == NULL) 311*0Sstevel@tonic-gate goto error; 312*0Sstevel@tonic-gate *dst = p; 313*0Sstevel@tonic-gate return (p); 314*0Sstevel@tonic-gate } 315*0Sstevel@tonic-gate 316*0Sstevel@tonic-gate len = strlen(*dst); 317*0Sstevel@tonic-gate 318*0Sstevel@tonic-gate /* +2 because of the comma we add below */ 319*0Sstevel@tonic-gate if ((p = realloc(*dst, len + strlen(src) + 2)) == NULL) 320*0Sstevel@tonic-gate goto error; 321*0Sstevel@tonic-gate 322*0Sstevel@tonic-gate *dst = p; 323*0Sstevel@tonic-gate 324*0Sstevel@tonic-gate *(*dst + len) = ','; 325*0Sstevel@tonic-gate (void) strcpy((*dst + len + 1), src); 326*0Sstevel@tonic-gate 327*0Sstevel@tonic-gate return (*dst); 328*0Sstevel@tonic-gate 329*0Sstevel@tonic-gate error: 330*0Sstevel@tonic-gate (void) fprintf(stderr, gettext("%s: allocation failed: %s\n"), 331*0Sstevel@tonic-gate command, strerror(errno)); 332*0Sstevel@tonic-gate exit(EXIT_FAILURE); 333*0Sstevel@tonic-gate /* NOTREACHED */ 334*0Sstevel@tonic-gate } 335*0Sstevel@tonic-gate 336*0Sstevel@tonic-gate static void 337*0Sstevel@tonic-gate usage(char const *prog) 338*0Sstevel@tonic-gate { 339*0Sstevel@tonic-gate (void) fprintf(stderr, gettext("Usage: %s [-f][-F [!]tracefromlist]" 340*0Sstevel@tonic-gate "[-T [!]tracetolist][-o outputfile]\n" 341*0Sstevel@tonic-gate " [-t calls][-v calls] prog [prog arguments]\n" 342*0Sstevel@tonic-gate 343*0Sstevel@tonic-gate " -F <bindfromlist>\n" 344*0Sstevel@tonic-gate " A comma separated list of libraries that are to be\n" 345*0Sstevel@tonic-gate " traced. Only calls from these libraries will be\n" 346*0Sstevel@tonic-gate " traced. The default is to trace calls from the\n" 347*0Sstevel@tonic-gate " main executable.\n" 348*0Sstevel@tonic-gate " If <bindfromlist> begins with a ! then it defines\n" 349*0Sstevel@tonic-gate " a list of libraries to exclude from the trace.\n" 350*0Sstevel@tonic-gate " -T <bindtolist>\n" 351*0Sstevel@tonic-gate " A comma separated list of libraries that are to be\n" 352*0Sstevel@tonic-gate " traced. Only calls to these libraries will be\n" 353*0Sstevel@tonic-gate " traced. The default is to trace all calls.\n" 354*0Sstevel@tonic-gate " If <bindtolist> begins with a ! then it defines\n" 355*0Sstevel@tonic-gate " a list of libraries to exclude from the trace.\n" 356*0Sstevel@tonic-gate " -o <outputfile>\n" 357*0Sstevel@tonic-gate " %s output will be directed to 'outputfile'.\n" 358*0Sstevel@tonic-gate " by default it is placed on stderr\n" 359*0Sstevel@tonic-gate " -f\n" 360*0Sstevel@tonic-gate " Follow all children created by fork() and also\n" 361*0Sstevel@tonic-gate " print apptrace output for the children. This also\n" 362*0Sstevel@tonic-gate " causes a 'pid' to be added to each output line\n" 363*0Sstevel@tonic-gate " -t <tracelist>\n" 364*0Sstevel@tonic-gate " A comma separated list of interfaces to trace.\n" 365*0Sstevel@tonic-gate " A list preceded by ! is an exlusion list.\n" 366*0Sstevel@tonic-gate " -v <verboselist>\n" 367*0Sstevel@tonic-gate " A comman separated list of interfaces to trace\n" 368*0Sstevel@tonic-gate " verbosely.\n" 369*0Sstevel@tonic-gate " A list preceded by ! is an exclusion list.\n" 370*0Sstevel@tonic-gate " Interfaces matched in -v do not also need to be\n" 371*0Sstevel@tonic-gate " named by -t\n" 372*0Sstevel@tonic-gate " All lists may use shell style wild cards.\n" 373*0Sstevel@tonic-gate " Leading path components or suffixes are not required when\n" 374*0Sstevel@tonic-gate " listing libraries (ie. libc will match /usr/lib/libc.so.1).\n"), 375*0Sstevel@tonic-gate prog, prog); 376*0Sstevel@tonic-gate } 377