15184Sek110237 /*
25184Sek110237 * CDDL HEADER START
35184Sek110237 *
45184Sek110237 * The contents of this file are subject to the terms of the
55184Sek110237 * Common Development and Distribution License (the "License").
65184Sek110237 * You may not use this file except in compliance with the License.
75184Sek110237 *
85184Sek110237 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
95184Sek110237 * or http://www.opensolaris.org/os/licensing.
105184Sek110237 * See the License for the specific language governing permissions
115184Sek110237 * and limitations under the License.
125184Sek110237 *
135184Sek110237 * When distributing Covered Code, include this CDDL HEADER in each
145184Sek110237 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
155184Sek110237 * If applicable, add the following below this CDDL HEADER, with the
165184Sek110237 * fields enclosed by brackets "[]" replaced with your own identifying
175184Sek110237 * information: Portions Copyright [yyyy] [name of copyright owner]
185184Sek110237 *
195184Sek110237 * CDDL HEADER END
205184Sek110237 */
215184Sek110237 /*
228615SAndrew.W.Wilson@sun.com * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
235184Sek110237 * Use is subject to license terms.
245184Sek110237 */
255184Sek110237
265184Sek110237 #include <stdio.h>
275184Sek110237 #include <fcntl.h>
285184Sek110237 #include <limits.h>
295184Sek110237 #include <time.h>
305184Sek110237 #include <libgen.h>
315184Sek110237 #include <unistd.h>
325184Sek110237 #include <strings.h>
335184Sek110237 #include "filebench.h"
345184Sek110237 #include "ipc.h"
355184Sek110237 #include "eventgen.h"
365184Sek110237 #include "utils.h"
378615SAndrew.W.Wilson@sun.com #include "fsplug.h"
388615SAndrew.W.Wilson@sun.com
398615SAndrew.W.Wilson@sun.com /* File System functions vector */
408615SAndrew.W.Wilson@sun.com fsplug_func_t *fs_functions_vec;
415184Sek110237
425184Sek110237 /*
435184Sek110237 * Routines to access high resolution system time, initialize and
446212Saw148015 * shutdown filebench, log filebench run progress and errors, and
456212Saw148015 * access system information strings.
465184Sek110237 */
475184Sek110237
485184Sek110237 #if !defined(sun) && defined(USE_RDTSC)
495184Sek110237 /*
505184Sek110237 * Lets us use the rdtsc instruction to get highres time.
515184Sek110237 * Thanks to libmicro
525184Sek110237 */
535184Sek110237 uint64_t cpu_hz = 0;
545184Sek110237
555184Sek110237 /*
565184Sek110237 * Uses the rdtsc instruction to get high resolution (cpu
575184Sek110237 * clock ticks) time. Only used for non Sun compiles.
585184Sek110237 */
595184Sek110237 __inline__ long long
rdtsc(void)605184Sek110237 rdtsc(void)
615184Sek110237 {
625184Sek110237 unsigned long long x;
635184Sek110237 __asm__ volatile(".byte 0x0f, 0x31" : "=A" (x));
645184Sek110237 return (x);
655184Sek110237 }
665184Sek110237
675184Sek110237 /*
685184Sek110237 * Get high resolution time in nanoseconds. This is the version
695184Sek110237 * used when not compiled for Sun systems. It uses rdtsc call to
705184Sek110237 * get clock ticks and converts to nanoseconds
715184Sek110237 */
725184Sek110237 uint64_t
gethrtime(void)735184Sek110237 gethrtime(void)
745184Sek110237 {
755184Sek110237 uint64_t hrt;
765184Sek110237
775184Sek110237 /* convert to nanosecs and return */
785184Sek110237 hrt = 1000000000UL * rdtsc() / cpu_hz;
795184Sek110237 return (hrt);
805184Sek110237 }
815184Sek110237
825184Sek110237 /*
835184Sek110237 * Gets CPU clock frequency in MHz from cpuinfo file.
845184Sek110237 * Converts to cpu_hz and stores in cpu_hz global uint64_t.
855184Sek110237 * Only used for non Sun compiles.
865184Sek110237 */
875184Sek110237 static uint64_t
parse_cpu_hz(void)885184Sek110237 parse_cpu_hz(void)
895184Sek110237 {
905184Sek110237 /*
915184Sek110237 * Parse the following from /proc/cpuinfo.
925184Sek110237 * cpu MHz : 2191.563
935184Sek110237 */
945184Sek110237 FILE *cpuinfo;
955184Sek110237 double hertz = -1;
965184Sek110237 uint64_t hz;
975184Sek110237
985184Sek110237 if ((cpuinfo = fopen("/proc/cpuinfo", "r")) == NULL) {
995184Sek110237 filebench_log(LOG_ERROR, "open /proc/cpuinfo failed: %s",
1005184Sek110237 strerror(errno));
1015184Sek110237 filebench_shutdown(1);
1025184Sek110237 }
1035184Sek110237 while (!feof(cpuinfo)) {
1045184Sek110237 char buffer[80];
1055184Sek110237
1065184Sek110237 fgets(buffer, 80, cpuinfo);
1075184Sek110237 if (strlen(buffer) == 0) continue;
1085184Sek110237 if (strncasecmp(buffer, "cpu MHz", 7) == 0) {
1095184Sek110237 char *token = strtok(buffer, ":");
1105184Sek110237
1115184Sek110237 if (token != NULL) {
1125184Sek110237 token = strtok((char *)NULL, ":");
1135184Sek110237 hertz = strtod(token, NULL);
1145184Sek110237 }
1155184Sek110237 break;
1165184Sek110237 }
1175184Sek110237 }
1185184Sek110237 hz = hertz * 1000000;
1195184Sek110237
1205184Sek110237 return (hz);
1215184Sek110237 }
1225184Sek110237
1235184Sek110237 #elif !defined(sun)
1245184Sek110237
1255184Sek110237 /*
1265184Sek110237 * Get high resolution time in nanoseconds. This is the version
1275184Sek110237 * used if compiled for Sun systems. It calls gettimeofday
1285184Sek110237 * to get current time and converts it to nanoseconds.
1295184Sek110237 */
1305184Sek110237 uint64_t
gethrtime(void)1315184Sek110237 gethrtime(void)
1325184Sek110237 {
1335184Sek110237 struct timeval tv;
1345184Sek110237 uint64_t hrt;
1355184Sek110237
1365184Sek110237 gettimeofday(&tv, NULL);
1375184Sek110237
1385184Sek110237 hrt = (uint64_t)tv.tv_sec * 1000000000UL +
1395184Sek110237 (uint64_t)tv.tv_usec * 1000UL;
1405184Sek110237 return (hrt);
1415184Sek110237 }
1425184Sek110237 #endif
1435184Sek110237
1445184Sek110237 /*
1455184Sek110237 * Main filebench initialization. Opens the random number
1465184Sek110237 * "device" file or shuts down the run if one is not found.
1475184Sek110237 * Sets the cpu clock frequency variable or shuts down the
1485184Sek110237 * run if one is not found.
1495184Sek110237 */
1505184Sek110237 void
filebench_init(void)1515184Sek110237 filebench_init(void)
1525184Sek110237 {
1536212Saw148015 fb_random_init();
1546212Saw148015
1555184Sek110237 #if defined(USE_RDTSC) && (LINUX_PORT)
1565184Sek110237 cpu_hz = parse_cpu_hz();
1575184Sek110237 if (cpu_hz <= 0) {
1585184Sek110237 filebench_log(LOG_ERROR, "Error getting CPU Mhz: %s",
1595184Sek110237 strerror(errno));
1605184Sek110237 filebench_shutdown(1);
1615184Sek110237 }
1625184Sek110237 #endif /* USE_RDTSC */
1635184Sek110237 }
1645184Sek110237
1655184Sek110237 extern int lex_lineno;
1665184Sek110237
1675184Sek110237 /*
1685184Sek110237 * Writes a message consisting of information formated by
1695184Sek110237 * "fmt" to the log file, dump file or stdout. The supplied
1705184Sek110237 * "level" argument determines which file to write to and
1715184Sek110237 * what other actions to take. The level LOG_LOG writes to
1725184Sek110237 * the "log" file, and will open the file on the first
1735184Sek110237 * invocation. The level LOG_DUMP writes to the "dump" file,
1745184Sek110237 * and will open it on the first invocation. Other levels
1755184Sek110237 * print to the stdout device, with the amount of information
1765184Sek110237 * dependent on the error level and the current error level
1776391Saw148015 * setting in filebench_shm->shm_debug_level.
1785184Sek110237 */
filebench_log(int level,const char * fmt,...)1795184Sek110237 void filebench_log
1805184Sek110237 __V((int level, const char *fmt, ...))
1815184Sek110237 {
1825184Sek110237 va_list args;
1835184Sek110237 hrtime_t now;
1845184Sek110237 char line[131072];
1855184Sek110237 char buf[131072];
1865184Sek110237
1875184Sek110237 if (level == LOG_FATAL)
1885184Sek110237 goto fatal;
1895184Sek110237
1905184Sek110237 /* open logfile if not already open and writing to it */
1915184Sek110237 if ((level == LOG_LOG) &&
1926391Saw148015 (filebench_shm->shm_log_fd < 0)) {
1935184Sek110237 char path[MAXPATHLEN];
1945184Sek110237 char *s;
1955184Sek110237
1966391Saw148015 (void) strcpy(path, filebench_shm->shm_fscriptname);
1975184Sek110237 if ((s = strstr(path, ".f")))
1985184Sek110237 *s = 0;
1995184Sek110237 else
2005184Sek110237 (void) strcpy(path, "filebench");
2015184Sek110237
2025184Sek110237 (void) strcat(path, ".csv");
2035184Sek110237
2046391Saw148015 filebench_shm->shm_log_fd =
2055184Sek110237 open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
2065184Sek110237 }
2075184Sek110237
2085184Sek110237 /*
2095184Sek110237 * if logfile still not open, switch to LOG_ERROR level so
2105184Sek110237 * it gets reported to stdout
2115184Sek110237 */
2125184Sek110237 if ((level == LOG_LOG) &&
2136391Saw148015 (filebench_shm->shm_log_fd < 0)) {
2145184Sek110237 (void) snprintf(line, sizeof (line), "Open logfile failed: %s",
2155184Sek110237 strerror(errno));
2165184Sek110237 level = LOG_ERROR;
2175184Sek110237 }
2185184Sek110237
2195184Sek110237 /* open dumpfile if not already open and writing to it */
2205184Sek110237 if ((level == LOG_DUMP) &&
2216391Saw148015 (*filebench_shm->shm_dump_filename == 0))
2225184Sek110237 return;
2235184Sek110237
2245184Sek110237 if ((level == LOG_DUMP) &&
2256391Saw148015 (filebench_shm->shm_dump_fd < 0)) {
2265184Sek110237
2276391Saw148015 filebench_shm->shm_dump_fd =
2286391Saw148015 open(filebench_shm->shm_dump_filename,
2295184Sek110237 O_RDWR | O_CREAT | O_TRUNC, 0666);
2305184Sek110237 }
2315184Sek110237
2325184Sek110237 if ((level == LOG_DUMP) &&
2336391Saw148015 (filebench_shm->shm_dump_fd < 0)) {
2345184Sek110237 (void) snprintf(line, sizeof (line), "Open logfile failed: %s",
2355184Sek110237 strerror(errno));
2365184Sek110237 level = LOG_ERROR;
2375184Sek110237 }
2385184Sek110237
2396084Saw148015 /* Quit if this is a LOG_ERROR messages and they are disabled */
2406084Saw148015 if ((filebench_shm->shm_1st_err) && (level == LOG_ERROR))
2416084Saw148015 return;
2426084Saw148015
2436084Saw148015 if (level == LOG_ERROR1) {
2446084Saw148015 if (filebench_shm->shm_1st_err)
2456084Saw148015 return;
2466084Saw148015
2476084Saw148015 /* A LOG_ERROR1 temporarily disables LOG_ERROR messages */
2486084Saw148015 filebench_shm->shm_1st_err = 1;
2496084Saw148015 level = LOG_ERROR;
2506084Saw148015 }
2516084Saw148015
2525184Sek110237 /* Only log greater than debug setting */
2535184Sek110237 if ((level != LOG_DUMP) && (level != LOG_LOG) &&
2546391Saw148015 (level > filebench_shm->shm_debug_level))
2555184Sek110237 return;
2565184Sek110237
2575184Sek110237 now = gethrtime();
2585184Sek110237
2595184Sek110237 fatal:
2605184Sek110237
2615184Sek110237 #ifdef __STDC__
2625184Sek110237 va_start(args, fmt);
2635184Sek110237 #else
2645184Sek110237 char *fmt;
2655184Sek110237 va_start(args);
2665184Sek110237 fmt = va_arg(args, char *);
2675184Sek110237 #endif
2685184Sek110237
2695184Sek110237 (void) vsprintf(line, fmt, args);
2705184Sek110237
2715184Sek110237 va_end(args);
2725184Sek110237
2735184Sek110237 if (level == LOG_FATAL) {
2746305Saw148015 (void) fprintf(stderr, "%s\n", line);
2755184Sek110237 return;
2765184Sek110237 }
2775184Sek110237
2785184Sek110237 /* Serialize messages to log */
2796391Saw148015 (void) ipc_mutex_lock(&filebench_shm->shm_msg_lock);
2805184Sek110237
2815184Sek110237 if (level == LOG_LOG) {
2826391Saw148015 if (filebench_shm->shm_log_fd > 0) {
2835184Sek110237 (void) snprintf(buf, sizeof (buf), "%s\n", line);
2846391Saw148015 (void) write(filebench_shm->shm_log_fd, buf,
2856391Saw148015 strlen(buf));
2866391Saw148015 (void) fsync(filebench_shm->shm_log_fd);
2876391Saw148015 (void) ipc_mutex_unlock(&filebench_shm->shm_msg_lock);
2886305Saw148015 return;
2895184Sek110237 }
2905184Sek110237
2915184Sek110237 } else if (level == LOG_DUMP) {
2926391Saw148015 if (filebench_shm->shm_dump_fd != -1) {
2935184Sek110237 (void) snprintf(buf, sizeof (buf), "%s\n", line);
2946391Saw148015 (void) write(filebench_shm->shm_dump_fd, buf,
2956391Saw148015 strlen(buf));
2966391Saw148015 (void) fsync(filebench_shm->shm_dump_fd);
2976391Saw148015 (void) ipc_mutex_unlock(&filebench_shm->shm_msg_lock);
2986305Saw148015 return;
2995184Sek110237 }
3005184Sek110237
3016391Saw148015 } else if (filebench_shm->shm_debug_level > LOG_INFO) {
3026305Saw148015 if (level < LOG_INFO)
3036305Saw148015 (void) fprintf(stderr, "%5d: ", (int)my_pid);
3046305Saw148015 else
3056305Saw148015 (void) fprintf(stdout, "%5d: ", (int)my_pid);
3066305Saw148015 }
3076305Saw148015
3086305Saw148015 if (level < LOG_INFO) {
3096305Saw148015 (void) fprintf(stderr, "%4.3f: %s",
3106391Saw148015 (now - filebench_shm->shm_epoch) / FSECS,
3115184Sek110237 line);
3126305Saw148015
3136305Saw148015 if (my_procflow == NULL)
3146305Saw148015 (void) fprintf(stderr, " on line %d", lex_lineno);
3156305Saw148015
3166305Saw148015 (void) fprintf(stderr, "\n");
3176305Saw148015 (void) fflush(stderr);
3185184Sek110237 } else {
3195184Sek110237 (void) fprintf(stdout, "%4.3f: %s",
3206391Saw148015 (now - filebench_shm->shm_epoch) / FSECS,
3215184Sek110237 line);
3225184Sek110237 (void) fprintf(stdout, "\n");
3235184Sek110237 (void) fflush(stdout);
3245184Sek110237 }
3255184Sek110237
3266391Saw148015 (void) ipc_mutex_unlock(&filebench_shm->shm_msg_lock);
3275184Sek110237 }
3285184Sek110237
3295184Sek110237 /*
3305184Sek110237 * Stops the run and exits filebench. If filebench is
3315184Sek110237 * currently running a workload, calls procflow_shutdown()
3325184Sek110237 * to stop the run. Also closes and deletes shared memory.
3335184Sek110237 */
3345184Sek110237 void
filebench_shutdown(int error)3355184Sek110237 filebench_shutdown(int error) {
3366391Saw148015
3376391Saw148015 if (error) {
338*9801SAndrew.W.Wilson@sun.com filebench_log(LOG_DEBUG_IMPL, "Shutdown on error %d", error);
3399326SAndrew.W.Wilson@sun.com (void) ipc_mutex_lock(&filebench_shm->shm_procflow_lock);
3409326SAndrew.W.Wilson@sun.com if (filebench_shm->shm_f_abort == FILEBENCH_ABORT_FINI) {
3419326SAndrew.W.Wilson@sun.com (void) ipc_mutex_unlock(
3429326SAndrew.W.Wilson@sun.com &filebench_shm->shm_procflow_lock);
3439326SAndrew.W.Wilson@sun.com return;
3449326SAndrew.W.Wilson@sun.com }
3456391Saw148015 filebench_shm->shm_f_abort = FILEBENCH_ABORT_ERROR;
3469326SAndrew.W.Wilson@sun.com (void) ipc_mutex_unlock(&filebench_shm->shm_procflow_lock);
3476391Saw148015 } else {
3486391Saw148015 filebench_log(LOG_DEBUG_IMPL, "Shutdown");
3496391Saw148015 }
3506391Saw148015
3516701Saw148015 procflow_shutdown();
3526391Saw148015
3536391Saw148015 (void) unlink("/tmp/filebench_shm");
3545184Sek110237 ipc_ismdelete();
3555184Sek110237 exit(error);
3565184Sek110237 }
3575184Sek110237
3585184Sek110237 /*
3595184Sek110237 * Put the hostname in ${hostname}. The system supplied
3605184Sek110237 * host name string is copied into an allocated string and
3615184Sek110237 * the pointer to the string is placed in the supplied
3626212Saw148015 * variable "var". If var->var_val.string already points to
3635184Sek110237 * a string, the string is freed. The routine always
3645184Sek110237 * returns zero (0).
3655184Sek110237 */
3665184Sek110237 var_t *
host_var(var_t * var)3675184Sek110237 host_var(var_t *var)
3685184Sek110237 {
3695184Sek110237 char hoststr[128];
3706212Saw148015 char *strptr;
3715184Sek110237
3725184Sek110237 (void) gethostname(hoststr, 128);
3736212Saw148015 if (VAR_HAS_STRING(var) && var->var_val.string)
3746212Saw148015 free(var->var_val.string);
3756212Saw148015
3766212Saw148015 if ((strptr = fb_stralloc(hoststr)) == NULL) {
3776212Saw148015 filebench_log(LOG_ERROR,
3786212Saw148015 "unable to allocate string for host name");
3796212Saw148015 return (NULL);
3806212Saw148015 }
3816212Saw148015
3826212Saw148015 VAR_SET_STR(var, strptr);
3835184Sek110237 return (0);
3845184Sek110237 }
3855184Sek110237
3865184Sek110237 /*
3875184Sek110237 * Put the date string in ${date}. The system supplied date is
3885184Sek110237 * copied into an allocated string and the pointer to the string
3896212Saw148015 * is placed in the supplied var_t's var_val.string. If
3906212Saw148015 * var->var_val.string already points to a string, the string
3916212Saw148015 * is freed. The routine returns a pointer to the supplied var_t,
3926212Saw148015 * unless it is unable to allocate string for the date, in which
3936212Saw148015 * case it returns NULL.
3945184Sek110237 */
3955184Sek110237 var_t *
date_var(var_t * var)3965184Sek110237 date_var(var_t *var)
3975184Sek110237 {
3985184Sek110237 char datestr[128];
3996212Saw148015 char *strptr;
4005184Sek110237 #ifdef HAVE_CFTIME
4015184Sek110237 time_t t = time(NULL);
4025184Sek110237 #else
4035184Sek110237 struct tm t;
4045184Sek110237 #endif
4055184Sek110237
4065184Sek110237 #ifdef HAVE_CFTIME
4075184Sek110237 cftime(datestr, "%y%m%d%H" "%M", &t);
4085184Sek110237 #else
4095184Sek110237 (void) strftime(datestr, sizeof (datestr), "%y%m%d%H %M", &t);
4105184Sek110237 #endif
4115184Sek110237
4126212Saw148015 if (VAR_HAS_STRING(var) && var->var_val.string)
4136212Saw148015 free(var->var_val.string);
4146212Saw148015
4156212Saw148015 if ((strptr = fb_stralloc(datestr)) == NULL) {
4166212Saw148015 filebench_log(LOG_ERROR,
4176212Saw148015 "unable to allocate string for date");
4186212Saw148015 return (NULL);
4196212Saw148015 }
4206212Saw148015
4216212Saw148015 VAR_SET_STR(var, strptr);
4225184Sek110237
4235184Sek110237 return (var);
4245184Sek110237 }
4255184Sek110237
4265184Sek110237 extern char *fscriptname;
4275184Sek110237
4285184Sek110237 /*
4295184Sek110237 * Put the script name in ${script}. The path name of the script
4305184Sek110237 * used with this filebench run trimmed of the trailing ".f" and
4315184Sek110237 * all leading subdirectories. The remaining script name is
4326212Saw148015 * copied into the var_val.string field of the supplied variable
4336212Saw148015 * "var". The routine returns a pointer to the supplied var_t,
4346212Saw148015 * unless it is unable to allocate string space, in which case it
4356212Saw148015 * returns NULL.
4365184Sek110237 */
4375184Sek110237 var_t *
script_var(var_t * var)4385184Sek110237 script_var(var_t *var)
4395184Sek110237 {
4405184Sek110237 char *scriptstr;
4415184Sek110237 char *f = fb_stralloc(fscriptname);
4426212Saw148015 char *strptr;
4435184Sek110237
4445184Sek110237 /* Trim the .f suffix */
4455184Sek110237 for (scriptstr = f + strlen(f) - 1; scriptstr != f; scriptstr--) {
4465184Sek110237 if (*scriptstr == '.') {
4475184Sek110237 *scriptstr = 0;
4485184Sek110237 break;
4495184Sek110237 }
4505184Sek110237 }
4515184Sek110237
4526212Saw148015 if ((strptr = fb_stralloc(basename(f))) == NULL) {
4536212Saw148015 filebench_log(LOG_ERROR,
4546212Saw148015 "unable to allocate string for script name");
4556212Saw148015 free(f);
4566212Saw148015 return (NULL);
4576212Saw148015 }
4586212Saw148015
4596212Saw148015 VAR_SET_STR(var, strptr);
4605184Sek110237 free(f);
4615184Sek110237
4625184Sek110237 return (var);
4635184Sek110237 }
4648615SAndrew.W.Wilson@sun.com
4658615SAndrew.W.Wilson@sun.com void fb_lfs_funcvecinit(void);
4668615SAndrew.W.Wilson@sun.com
4678615SAndrew.W.Wilson@sun.com /*
4688615SAndrew.W.Wilson@sun.com * Initialize any "plug-in" I/O function vectors. Called by each
4698615SAndrew.W.Wilson@sun.com * filebench process that is forked, as the vector is relative to
4708615SAndrew.W.Wilson@sun.com * its image.
4718615SAndrew.W.Wilson@sun.com */
4728615SAndrew.W.Wilson@sun.com void
filebench_plugin_funcvecinit(void)4738615SAndrew.W.Wilson@sun.com filebench_plugin_funcvecinit(void)
4748615SAndrew.W.Wilson@sun.com {
4758615SAndrew.W.Wilson@sun.com
4768615SAndrew.W.Wilson@sun.com switch (filebench_shm->shm_filesys_type) {
4778615SAndrew.W.Wilson@sun.com case LOCAL_FS_PLUG:
4788615SAndrew.W.Wilson@sun.com fb_lfs_funcvecinit();
4798615SAndrew.W.Wilson@sun.com break;
4808615SAndrew.W.Wilson@sun.com
4818615SAndrew.W.Wilson@sun.com case NFS3_PLUG:
4828615SAndrew.W.Wilson@sun.com case NFS4_PLUG:
4838615SAndrew.W.Wilson@sun.com case CIFS_PLUG:
4848615SAndrew.W.Wilson@sun.com break;
4858615SAndrew.W.Wilson@sun.com default:
4868615SAndrew.W.Wilson@sun.com filebench_log(LOG_ERROR,
4878615SAndrew.W.Wilson@sun.com "filebench_plugin_funcvecinit: unknown file system");
4888615SAndrew.W.Wilson@sun.com break;
4898615SAndrew.W.Wilson@sun.com }
4908615SAndrew.W.Wilson@sun.com }
491