xref: /illumos-gate/usr/src/cmd/fm/fmdump/common/fmdump.c (revision bc37da3a)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <alloca.h>
29 #include <unistd.h>
30 #include <limits.h>
31 #include <strings.h>
32 #include <stdlib.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <time.h>
37 #include <ctype.h>
38 
39 #include <fmdump.h>
40 
41 #define	FMDUMP_EXIT_SUCCESS	0
42 #define	FMDUMP_EXIT_FATAL	1
43 #define	FMDUMP_EXIT_USAGE	2
44 #define	FMDUMP_EXIT_ERROR	3
45 
46 const char *g_pname;
47 ulong_t g_errs;
48 ulong_t g_recs;
49 char *g_root;
50 struct topo_hdl *g_thp;
51 
52 /*PRINTFLIKE2*/
53 void
54 fmdump_printf(FILE *fp, const char *format, ...)
55 {
56 	va_list ap;
57 
58 	va_start(ap, format);
59 
60 	if (vfprintf(fp, format, ap) < 0) {
61 		(void) fprintf(stderr, "%s: failed to print record: %s\n",
62 		    g_pname, strerror(errno));
63 		g_errs++;
64 	}
65 
66 	va_end(ap);
67 }
68 
69 void
70 fmdump_vwarn(const char *format, va_list ap)
71 {
72 	int err = errno;
73 
74 	(void) fprintf(stderr, "%s: warning: ", g_pname);
75 	(void) vfprintf(stderr, format, ap);
76 
77 	if (strchr(format, '\n') == NULL)
78 		(void) fprintf(stderr, ": %s\n", strerror(err));
79 
80 	g_errs++;
81 }
82 
83 /*PRINTFLIKE1*/
84 void
85 fmdump_warn(const char *format, ...)
86 {
87 	va_list ap;
88 
89 	va_start(ap, format);
90 	fmdump_vwarn(format, ap);
91 	va_end(ap);
92 }
93 
94 char *
95 fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp)
96 {
97 	if (rp->rec_sec > LONG_MAX) {
98 		fmdump_warn("record time is too large for 32-bit utility\n");
99 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
100 	} else {
101 		time_t tod = (time_t)rp->rec_sec;
102 		time_t now = time(NULL);
103 		if (tod > now+60 ||
104 		    tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */
105 			(void) strftime(buf, len, "%b %d %Y %T",
106 			    localtime(&tod));
107 		} else {
108 			size_t sz;
109 			sz = strftime(buf, len, "%b %d %T", localtime(&tod));
110 			(void) snprintf(buf + sz, len - sz, ".%4.4llu",
111 			    rp->rec_nsec / (NANOSEC / 10000));
112 		}
113 	}
114 
115 	return (buf);
116 }
117 
118 char *
119 fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp)
120 {
121 #ifdef _ILP32
122 	if (rp->rec_sec > LONG_MAX) {
123 		fmdump_warn("record time is too large for 32-bit utility\n");
124 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
125 	} else {
126 #endif
127 		time_t tod = (time_t)rp->rec_sec;
128 		(void) strftime(buf, len, "%b %d %Y %T", localtime(&tod));
129 #ifdef _ILP32
130 	}
131 #endif
132 	return (buf);
133 }
134 
135 static int
136 usage(FILE *fp)
137 {
138 	(void) fprintf(fp, "Usage: %s [-efvV] [-c class] [-R root] [-t time] "
139 	    "[-T time] [-u uuid] [file]\n", g_pname);
140 
141 	(void) fprintf(fp,
142 	    "\t-c  select events that match the specified class\n"
143 	    "\t-e  display error log content instead of fault log content\n"
144 	    "\t-f  follow growth of log file by waiting for additional data\n"
145 	    "\t-R  set root directory for pathname expansions\n"
146 	    "\t-t  select events that occurred after the specified time\n"
147 	    "\t-T  select events that occurred before the specified time\n"
148 	    "\t-u  select events that match the specified uuid\n"
149 	    "\t-v  set verbose mode: display additional event detail\n"
150 	    "\t-V  set very verbose mode: display complete event contents\n");
151 
152 	return (FMDUMP_EXIT_USAGE);
153 }
154 
155 /*ARGSUSED*/
156 static int
157 error(fmd_log_t *lp, void *private)
158 {
159 	fmdump_warn("skipping record: %s\n",
160 	    fmd_log_errmsg(lp, fmd_log_errno(lp)));
161 	return (0);
162 }
163 
164 /*
165  * Yet another disgusting argument parsing function (TM).  We attempt to parse
166  * a time argument in a variety of strptime(3C) formats, in which case it is
167  * interpreted as a local time and is converted to a timeval using mktime(3C).
168  * If those formats fail, we look to see if the time is a decimal integer
169  * followed by one of our magic suffixes, in which case the time is interpreted
170  * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago").
171  */
172 static struct timeval *
173 gettimeopt(const char *arg)
174 {
175 	const struct {
176 		const char *name;
177 		hrtime_t mul;
178 	} suffix[] = {
179 		{ "ns", 	NANOSEC / NANOSEC },
180 		{ "nsec",	NANOSEC / NANOSEC },
181 		{ "us",		NANOSEC / MICROSEC },
182 		{ "usec",	NANOSEC / MICROSEC },
183 		{ "ms",		NANOSEC / MILLISEC },
184 		{ "msec",	NANOSEC / MILLISEC },
185 		{ "s",		NANOSEC / SEC },
186 		{ "sec",	NANOSEC / SEC },
187 		{ "m",		NANOSEC * (hrtime_t)60 },
188 		{ "min",	NANOSEC * (hrtime_t)60 },
189 		{ "h",		NANOSEC * (hrtime_t)(60 * 60) },
190 		{ "hour",	NANOSEC * (hrtime_t)(60 * 60) },
191 		{ "d",		NANOSEC * (hrtime_t)(24 * 60 * 60) },
192 		{ "day",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
193 		{ NULL }
194 	};
195 
196 	struct timeval *tvp = malloc(sizeof (struct timeval));
197 	struct timeval tod;
198 	struct tm tm;
199 	char *p;
200 
201 	if (tvp == NULL) {
202 		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
203 		    g_pname, strerror(errno));
204 		exit(FMDUMP_EXIT_FATAL);
205 	}
206 
207 	if (gettimeofday(&tod, NULL) != 0) {
208 		(void) fprintf(stderr, "%s: failed to get tod: %s\n",
209 		    g_pname, strerror(errno));
210 		exit(FMDUMP_EXIT_FATAL);
211 	}
212 
213 	/*
214 	 * First try a variety of strptime() calls.  If these all fail, we'll
215 	 * try parsing an integer followed by one of our suffix[] strings.
216 	 * NOTE: any form using %y must appear *before* the equivalent %Y form;
217 	 * otherwise %Y will accept the two year digits but infer century zero.
218 	 * Any form ending in %y must additionally check isdigit(*p) to ensure
219 	 * that it does not inadvertently match 2 digits of a 4-digit year.
220 	 *
221 	 * Beware: Any strptime() sequence containing consecutive %x sequences
222 	 * may fall victim to SCCS expanding it as a keyword!  If this happens
223 	 * we use separate string constant that ANSI C will concatenate.
224 	 */
225 	if ((p = strptime(arg, "%m/%d/%y" "%t" "%H:%M:%S", &tm)) == NULL &&
226 	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
227 	    (p = strptime(arg, "%m/%d/%y" "%t" "%H:%M", &tm)) == NULL &&
228 	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M", &tm)) == NULL &&
229 	    ((p = strptime(arg, "%m/%d/%y", &tm)) == NULL || isdigit(*p)) &&
230 	    (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL &&
231 	    (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL &&
232 	    (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL &&
233 	    (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL &&
234 	    (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL &&
235 	    (p = strptime(arg, "%y-%m-%d", &tm)) == NULL &&
236 	    (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL &&
237 	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M:%S", &tm)) == NULL &&
238 	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
239 	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M", &tm)) == NULL &&
240 	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M", &tm)) == NULL &&
241 	    ((p = strptime(arg, "%d%b%y", &tm)) == NULL || isdigit(*p)) &&
242 	    (p = strptime(arg, "%d%b%Y", &tm)) == NULL &&
243 	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
244 	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
245 	    (p = strptime(arg, "%H:%M:%S", &tm)) == NULL &&
246 	    (p = strptime(arg, "%H:%M", &tm)) == NULL) {
247 
248 		hrtime_t nsec;
249 		int i;
250 
251 		errno = 0;
252 		nsec = strtol(arg, (char **)&p, 10);
253 
254 		if (errno != 0 || nsec == 0 || p == arg || *p == '\0') {
255 			(void) fprintf(stderr, "%s: illegal time "
256 			    "format -- %s\n", g_pname, arg);
257 			exit(FMDUMP_EXIT_USAGE);
258 		}
259 
260 		for (i = 0; suffix[i].name != NULL; i++) {
261 			if (strcasecmp(suffix[i].name, p) == 0) {
262 				nsec *= suffix[i].mul;
263 				break;
264 			}
265 		}
266 
267 		if (suffix[i].name == NULL) {
268 			(void) fprintf(stderr, "%s: illegal time "
269 			    "format -- %s\n", g_pname, arg);
270 			exit(FMDUMP_EXIT_USAGE);
271 		}
272 
273 		tvp->tv_sec = nsec / NANOSEC;
274 		tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC);
275 
276 		if (tvp->tv_sec > tod.tv_sec) {
277 			(void) fprintf(stderr, "%s: time delta precedes "
278 			    "UTC time origin -- %s\n", g_pname, arg);
279 			exit(FMDUMP_EXIT_USAGE);
280 		}
281 
282 		tvp->tv_sec = tod.tv_sec - tvp->tv_sec;
283 
284 	} else if (*p == '\0' || *p == '.') {
285 		/*
286 		 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use
287 		 * the result of localtime(&tod.tv_sec) to fill in the rest.
288 		 */
289 		if (tm.tm_year == 0) {
290 			int h = tm.tm_hour;
291 			int m = tm.tm_min;
292 			int s = tm.tm_sec;
293 			int b = tm.tm_mon;
294 			int d = tm.tm_mday;
295 
296 			bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm));
297 			tm.tm_isdst = 0; /* see strptime(3C) and below */
298 
299 			if (d > 0) {
300 				tm.tm_mon = b;
301 				tm.tm_mday = d;
302 			}
303 
304 			tm.tm_hour = h;
305 			tm.tm_min = m;
306 			tm.tm_sec = s;
307 		}
308 
309 		errno = 0;
310 		tvp->tv_sec = mktime(&tm);
311 		tvp->tv_usec = 0;
312 
313 		if (tvp->tv_sec == -1L && errno != 0) {
314 			(void) fprintf(stderr, "%s: failed to compose "
315 			    "time %s: %s\n", g_pname, arg, strerror(errno));
316 			exit(FMDUMP_EXIT_ERROR);
317 		}
318 
319 		/*
320 		 * If our mktime() set tm_isdst, adjust the result for DST by
321 		 * subtracting the offset between the main and alternate zones.
322 		 */
323 		if (tm.tm_isdst)
324 			tvp->tv_sec -= timezone - altzone;
325 
326 		if (p[0] == '.') {
327 			arg = p;
328 			errno = 0;
329 			tvp->tv_usec =
330 			    (suseconds_t)(strtod(arg, &p) * (double)MICROSEC);
331 
332 			if (errno != 0 || p == arg || *p != '\0') {
333 				(void) fprintf(stderr, "%s: illegal time "
334 				    "suffix -- .%s\n", g_pname, arg);
335 				exit(FMDUMP_EXIT_USAGE);
336 			}
337 		}
338 
339 	} else {
340 		(void) fprintf(stderr, "%s: unexpected suffix after "
341 		    "time %s -- %s\n", g_pname, arg, p);
342 		exit(FMDUMP_EXIT_USAGE);
343 	}
344 
345 	return (tvp);
346 }
347 
348 /*
349  * If the -u option is specified in combination with the -e option, we iterate
350  * over each record in the fault log with a matching UUID finding xrefs to the
351  * error log, and then use this function to iterate over every xref'd record.
352  */
353 int
354 xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
355 {
356 	const fmd_log_record_t *xrp = rp->rec_xrefs;
357 	fmdump_arg_t *dap = arg;
358 	int i, rv = 0;
359 
360 	for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) {
361 		if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp))
362 			rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp);
363 	}
364 
365 	return (rv);
366 }
367 
368 int
369 xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
370 {
371 	fmdump_lyr_t *dyp = arg;
372 
373 	fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off);
374 	return (dyp->dy_func(lp, rp, dyp->dy_arg));
375 }
376 
377 /*
378  * If the -a option is not present, filter out fault records that correspond
379  * to events that the producer requested not be messaged for administrators.
380  */
381 /*ARGSUSED*/
382 int
383 log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
384 {
385 	boolean_t msg;
386 
387 	return (nvlist_lookup_boolean_value(rp->rec_nvl,
388 	    FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0);
389 }
390 
391 int
392 main(int argc, char *argv[])
393 {
394 	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0;
395 	int opt_u = 0, opt_v = 0, opt_V = 0;
396 
397 	char ifile[PATH_MAX] = "";
398 	int iflags = 0;
399 
400 	fmdump_arg_t arg;
401 	fmdump_lyr_t lyr;
402 	const fmdump_ops_t *ops;
403 	fmd_log_filter_t *filtv;
404 	uint_t filtc;
405 
406 	fmd_log_filter_t *errfv, *fltfv, *allfv;
407 	uint_t errfc = 0, fltfc = 0, allfc = 0;
408 
409 	fmd_log_header_t log;
410 	fmd_log_rec_f *func;
411 	void *farg;
412 	fmd_log_t *lp;
413 	int c, err;
414 	off64_t off = 0;
415 
416 	g_pname = argv[0];
417 
418 	errfv = alloca(sizeof (fmd_log_filter_t) * argc);
419 	fltfv = alloca(sizeof (fmd_log_filter_t) * argc);
420 	allfv = alloca(sizeof (fmd_log_filter_t) * argc);
421 
422 	while (optind < argc) {
423 		while ((c = getopt(argc, argv, "ac:efHO:R:t:T:u:vV")) != EOF) {
424 			switch (c) {
425 			case 'a':
426 				opt_a++;
427 				break;
428 			case 'c':
429 				errfv[errfc].filt_func = fmd_log_filter_class;
430 				errfv[errfc].filt_arg = optarg;
431 				allfv[allfc++] = errfv[errfc++];
432 				break;
433 			case 'e':
434 				opt_e++;
435 				break;
436 			case 'f':
437 				opt_f++;
438 				break;
439 			case 'H':
440 				opt_H++;
441 				break;
442 			case 'O':
443 				off = strtoull(optarg, NULL, 16);
444 				iflags |= FMD_LOG_XITER_OFFS;
445 				break;
446 			case 'R':
447 				g_root = optarg;
448 				break;
449 			case 't':
450 				errfv[errfc].filt_func = fmd_log_filter_after;
451 				errfv[errfc].filt_arg = gettimeopt(optarg);
452 				allfv[allfc++] = errfv[errfc++];
453 				break;
454 			case 'T':
455 				errfv[errfc].filt_func = fmd_log_filter_before;
456 				errfv[errfc].filt_arg = gettimeopt(optarg);
457 				allfv[allfc++] = errfv[errfc++];
458 				break;
459 			case 'u':
460 				fltfv[fltfc].filt_func = fmd_log_filter_uuid;
461 				fltfv[fltfc].filt_arg = optarg;
462 				allfv[allfc++] = fltfv[fltfc++];
463 				opt_u++;
464 				opt_a++; /* -u implies -a */
465 				break;
466 			case 'v':
467 				opt_v++;
468 				break;
469 			case 'V':
470 				opt_V++;
471 				break;
472 			default:
473 				return (usage(stderr));
474 			}
475 		}
476 
477 		if (optind < argc) {
478 			if (*ifile != '\0') {
479 				(void) fprintf(stderr, "%s: illegal "
480 				    "argument -- %s\n", g_pname, argv[optind]);
481 				return (FMDUMP_EXIT_USAGE);
482 			} else {
483 				(void) strlcpy(ifile,
484 				    argv[optind++], sizeof (ifile));
485 			}
486 		}
487 	}
488 
489 	if (*ifile == '\0') {
490 		(void) snprintf(ifile, sizeof (ifile), "%s/var/fm/fmd/%slog",
491 		    g_root ? g_root : "", opt_e && !opt_u ? "err" : "flt");
492 	} else if (g_root != NULL) {
493 		(void) fprintf(stderr, "%s: -R option is not appropriate "
494 		    "when file operand is present\n", g_pname);
495 		return (FMDUMP_EXIT_USAGE);
496 	}
497 
498 	if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) {
499 		(void) fprintf(stderr, "%s: failed to open %s: %s\n",
500 		    g_pname, ifile, fmd_log_errmsg(NULL, err));
501 		return (FMDUMP_EXIT_FATAL);
502 	}
503 
504 	if (opt_H) {
505 		fmd_log_header(lp, &log);
506 
507 		(void) printf("EXD_CREATOR = %s\n", log.log_creator);
508 		(void) printf("EXD_HOSTNAME = %s\n", log.log_hostname);
509 		(void) printf("EXD_FMA_LABEL = %s\n", log.log_label);
510 		(void) printf("EXD_FMA_VERSION = %s\n", log.log_version);
511 		(void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease);
512 		(void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion);
513 		(void) printf("EXD_FMA_PLAT = %s\n", log.log_platform);
514 		(void) printf("EXD_FMA_UUID = %s\n", log.log_uuid);
515 
516 		return (FMDUMP_EXIT_SUCCESS);
517 	}
518 
519 	if (off != 0 && fmd_log_seek(lp, off) != 0) {
520 		(void) fprintf(stderr, "%s: failed to seek %s: %s\n",
521 		    g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp)));
522 		return (FMDUMP_EXIT_FATAL);
523 	}
524 
525 	if (opt_e && opt_u)
526 		ops = &fmdump_err_ops;
527 	else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0)
528 		ops = &fmdump_flt_ops;
529 	else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0)
530 		ops = &fmdump_asru_ops;
531 	else
532 		ops = &fmdump_err_ops;
533 
534 	if (!opt_a && ops == &fmdump_flt_ops) {
535 		fltfv[fltfc].filt_func = log_filter_silent;
536 		fltfv[fltfc].filt_arg = NULL;
537 		allfv[allfc++] = fltfv[fltfc++];
538 	}
539 
540 	if (opt_V) {
541 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB2];
542 		iflags |= FMD_LOG_XITER_REFS;
543 	} else if (opt_v) {
544 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB1];
545 	} else
546 		arg.da_fmt = &ops->do_formats[FMDUMP_SHORT];
547 
548 	arg.da_fv = errfv;
549 	arg.da_fc = errfc;
550 	arg.da_fp = stdout;
551 
552 	if (iflags & FMD_LOG_XITER_OFFS)
553 		fmdump_printf(arg.da_fp, "%16s ", "OFFSET");
554 
555 	if (arg.da_fmt->do_hdr)
556 		fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr);
557 
558 	if (opt_e && opt_u) {
559 		iflags |= FMD_LOG_XITER_REFS;
560 		func = xref_iter;
561 		farg = &arg;
562 		filtc = fltfc;
563 		filtv = fltfv;
564 	} else {
565 		func = arg.da_fmt->do_func;
566 		farg = arg.da_fp;
567 		filtc = allfc;
568 		filtv = allfv;
569 	}
570 
571 	if (iflags & FMD_LOG_XITER_OFFS) {
572 		lyr.dy_func = func;
573 		lyr.dy_arg = farg;
574 		lyr.dy_fp = arg.da_fp;
575 		func = xoff_iter;
576 		farg = &lyr;
577 	}
578 
579 	do {
580 		if (fmd_log_xiter(lp, iflags, filtc, filtv,
581 		    func, error, farg, &g_recs) != 0) {
582 			(void) fprintf(stderr,
583 			    "%s: failed to dump %s: %s\n", g_pname, ifile,
584 			    fmd_log_errmsg(lp, fmd_log_errno(lp)));
585 			g_errs++;
586 		}
587 
588 		if (opt_f)
589 			(void) sleep(1);
590 
591 	} while (opt_f);
592 
593 	if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO))
594 		(void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile);
595 
596 	fmd_log_close(lp);
597 	return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS);
598 }
599