/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define FMDUMP_EXIT_SUCCESS 0 #define FMDUMP_EXIT_FATAL 1 #define FMDUMP_EXIT_USAGE 2 #define FMDUMP_EXIT_ERROR 3 const char *g_pname; ulong_t g_errs; ulong_t g_recs; char *g_root; struct topo_hdl *g_thp; fmd_msg_hdl_t *g_msg; /*PRINTFLIKE2*/ void fmdump_printf(FILE *fp, const char *format, ...) { va_list ap; va_start(ap, format); if (vfprintf(fp, format, ap) < 0) { (void) fprintf(stderr, "%s: failed to print record: %s\n", g_pname, strerror(errno)); g_errs++; } va_end(ap); } void fmdump_vwarn(const char *format, va_list ap) { int err = errno; (void) fprintf(stderr, "%s: warning: ", g_pname); (void) vfprintf(stderr, format, ap); if (strchr(format, '\n') == NULL) (void) fprintf(stderr, ": %s\n", strerror(err)); g_errs++; } /*PRINTFLIKE1*/ void fmdump_warn(const char *format, ...) { va_list ap; va_start(ap, format); fmdump_vwarn(format, ap); va_end(ap); } char * fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp) { if (rp->rec_sec > LONG_MAX) { fmdump_warn("record time is too large for 32-bit utility\n"); (void) snprintf(buf, len, "0x%llx", rp->rec_sec); } else { time_t tod = (time_t)rp->rec_sec; time_t now = time(NULL); if (tod > now+60 || tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */ (void) strftime(buf, len, "%b %d %Y %T", localtime(&tod)); } else { size_t sz; sz = strftime(buf, len, "%b %d %T", localtime(&tod)); (void) snprintf(buf + sz, len - sz, ".%4.4llu", rp->rec_nsec / (NANOSEC / 10000)); } } return (buf); } char * fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp) { #ifdef _ILP32 if (rp->rec_sec > LONG_MAX) { fmdump_warn("record time is too large for 32-bit utility\n"); (void) snprintf(buf, len, "0x%llx", rp->rec_sec); } else { #endif time_t tod = (time_t)rp->rec_sec; (void) strftime(buf, len, "%b %d %Y %T", localtime(&tod)); #ifdef _ILP32 } #endif return (buf); } static int usage(FILE *fp) { (void) fprintf(fp, "Usage: %s [-efmvV] [-c class] [-R root] [-t time] " "[-T time] [-u uuid]\n\t\t[-n name[.name]*[=value]] [file]\n", g_pname); (void) fprintf(fp, "\t-c select events that match the specified class\n" "\t-e display error log content instead of fault log content\n" "\t-f follow growth of log file by waiting for additional data\n" "\t-m display human-readable messages for the fault log\n" "\t-R set root directory for pathname expansions\n" "\t-t select events that occurred after the specified time\n" "\t-T select events that occurred before the specified time\n" "\t-u select events that match the specified uuid\n" "\t-n select events containing named nvpair " "(with matching value)\n" "\t-v set verbose mode: display additional event detail\n" "\t-V set very verbose mode: display complete event contents\n"); return (FMDUMP_EXIT_USAGE); } /*ARGSUSED*/ static int error(fmd_log_t *lp, void *private) { fmdump_warn("skipping record: %s\n", fmd_log_errmsg(lp, fmd_log_errno(lp))); return (0); } /* * Yet another disgusting argument parsing function (TM). We attempt to parse * a time argument in a variety of strptime(3C) formats, in which case it is * interpreted as a local time and is converted to a timeval using mktime(3C). * If those formats fail, we look to see if the time is a decimal integer * followed by one of our magic suffixes, in which case the time is interpreted * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago"). */ static struct timeval * gettimeopt(const char *arg) { const struct { const char *name; hrtime_t mul; } suffix[] = { { "ns", NANOSEC / NANOSEC }, { "nsec", NANOSEC / NANOSEC }, { "us", NANOSEC / MICROSEC }, { "usec", NANOSEC / MICROSEC }, { "ms", NANOSEC / MILLISEC }, { "msec", NANOSEC / MILLISEC }, { "s", NANOSEC / SEC }, { "sec", NANOSEC / SEC }, { "m", NANOSEC * (hrtime_t)60 }, { "min", NANOSEC * (hrtime_t)60 }, { "h", NANOSEC * (hrtime_t)(60 * 60) }, { "hour", NANOSEC * (hrtime_t)(60 * 60) }, { "d", NANOSEC * (hrtime_t)(24 * 60 * 60) }, { "day", NANOSEC * (hrtime_t)(24 * 60 * 60) }, { NULL } }; struct timeval *tvp = malloc(sizeof (struct timeval)); struct timeval tod; struct tm tm; char *p; if (tvp == NULL) { (void) fprintf(stderr, "%s: failed to allocate memory: %s\n", g_pname, strerror(errno)); exit(FMDUMP_EXIT_FATAL); } if (gettimeofday(&tod, NULL) != 0) { (void) fprintf(stderr, "%s: failed to get tod: %s\n", g_pname, strerror(errno)); exit(FMDUMP_EXIT_FATAL); } /* * First try a variety of strptime() calls. If these all fail, we'll * try parsing an integer followed by one of our suffix[] strings. * NOTE: any form using %y must appear *before* the equivalent %Y form; * otherwise %Y will accept the two year digits but infer century zero. * Any form ending in %y must additionally check isdigit(*p) to ensure * that it does not inadvertently match 2 digits of a 4-digit year. * * Beware: Any strptime() sequence containing consecutive %x sequences * may fall victim to SCCS expanding it as a keyword! If this happens * we use separate string constant that ANSI C will concatenate. */ if ((p = strptime(arg, "%m/%d/%y" "%t" "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%m/%d/%y" "%t" "%H:%M", &tm)) == NULL && (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M", &tm)) == NULL && ((p = strptime(arg, "%m/%d/%y", &tm)) == NULL || isdigit(*p)) && (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL && (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL && (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL && (p = strptime(arg, "%y-%m-%d", &tm)) == NULL && (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL && (p = strptime(arg, "%d%b%y" "%t" "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%d%b%Y" "%t" "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%d%b%y" "%t" "%H:%M", &tm)) == NULL && (p = strptime(arg, "%d%b%Y" "%t" "%H:%M", &tm)) == NULL && ((p = strptime(arg, "%d%b%y", &tm)) == NULL || isdigit(*p)) && (p = strptime(arg, "%d%b%Y", &tm)) == NULL && (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%H:%M:%S", &tm)) == NULL && (p = strptime(arg, "%H:%M", &tm)) == NULL) { hrtime_t nsec; int i; errno = 0; nsec = strtol(arg, (char **)&p, 10); if (errno != 0 || nsec == 0 || p == arg || *p == '\0') { (void) fprintf(stderr, "%s: illegal time " "format -- %s\n", g_pname, arg); exit(FMDUMP_EXIT_USAGE); } for (i = 0; suffix[i].name != NULL; i++) { if (strcasecmp(suffix[i].name, p) == 0) { nsec *= suffix[i].mul; break; } } if (suffix[i].name == NULL) { (void) fprintf(stderr, "%s: illegal time " "format -- %s\n", g_pname, arg); exit(FMDUMP_EXIT_USAGE); } tvp->tv_sec = nsec / NANOSEC; tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC); if (tvp->tv_sec > tod.tv_sec) { (void) fprintf(stderr, "%s: time delta precedes " "UTC time origin -- %s\n", g_pname, arg); exit(FMDUMP_EXIT_USAGE); } tvp->tv_sec = tod.tv_sec - tvp->tv_sec; } else if (*p == '\0' || *p == '.') { /* * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use * the result of localtime(&tod.tv_sec) to fill in the rest. */ if (tm.tm_year == 0) { int h = tm.tm_hour; int m = tm.tm_min; int s = tm.tm_sec; int b = tm.tm_mon; int d = tm.tm_mday; bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm)); tm.tm_isdst = 0; /* see strptime(3C) and below */ if (d > 0) { tm.tm_mon = b; tm.tm_mday = d; } tm.tm_hour = h; tm.tm_min = m; tm.tm_sec = s; } errno = 0; tvp->tv_sec = mktime(&tm); tvp->tv_usec = 0; if (tvp->tv_sec == -1L && errno != 0) { (void) fprintf(stderr, "%s: failed to compose " "time %s: %s\n", g_pname, arg, strerror(errno)); exit(FMDUMP_EXIT_ERROR); } /* * If our mktime() set tm_isdst, adjust the result for DST by * subtracting the offset between the main and alternate zones. */ if (tm.tm_isdst) tvp->tv_sec -= timezone - altzone; if (p[0] == '.') { arg = p; errno = 0; tvp->tv_usec = (suseconds_t)(strtod(arg, &p) * (double)MICROSEC); if (errno != 0 || p == arg || *p != '\0') { (void) fprintf(stderr, "%s: illegal time " "suffix -- .%s\n", g_pname, arg); exit(FMDUMP_EXIT_USAGE); } } } else { (void) fprintf(stderr, "%s: unexpected suffix after " "time %s -- %s\n", g_pname, arg, p); exit(FMDUMP_EXIT_USAGE); } return (tvp); } /* * If the -u option is specified in combination with the -e option, we iterate * over each record in the fault log with a matching UUID finding xrefs to the * error log, and then use this function to iterate over every xref'd record. */ int xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) { const fmd_log_record_t *xrp = rp->rec_xrefs; fmdump_arg_t *dap = arg; int i, rv = 0; for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) { if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp)) rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp); } return (rv); } int xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) { fmdump_lyr_t *dyp = arg; fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off); return (dyp->dy_func(lp, rp, dyp->dy_arg)); } /* * Initialize fmd_log_filter_nvarg_t from -n name=value argument string. */ static fmd_log_filter_nvarg_t * setupnamevalue(char *namevalue) { fmd_log_filter_nvarg_t *argt; char *value; regex_t *value_regex = NULL; char errstr[128]; int rv; if ((value = strchr(namevalue, '=')) == NULL) { value_regex = NULL; } else { *value++ = '\0'; /* separate name and value string */ /* * Skip white space before value to facilitate direct * cut/paste from previous fmdump output. */ while (isspace(*value)) value++; if ((value_regex = malloc(sizeof (regex_t))) == NULL) { (void) fprintf(stderr, "%s: failed to allocate memory: " "%s\n", g_pname, strerror(errno)); exit(FMDUMP_EXIT_FATAL); } /* compile regular expression for possible string match */ if ((rv = regcomp(value_regex, value, REG_NOSUB|REG_NEWLINE)) != 0) { (void) regerror(rv, value_regex, errstr, sizeof (errstr)); (void) fprintf(stderr, "unexpected regular expression " "in %s: %s\n", value, errstr); free(value_regex); exit(FMDUMP_EXIT_USAGE); } } if ((argt = malloc(sizeof (fmd_log_filter_nvarg_t))) == NULL) { (void) fprintf(stderr, "%s: failed to allocate memory: %s\n", g_pname, strerror(errno)); exit(FMDUMP_EXIT_FATAL); } argt->nvarg_name = namevalue; /* now just name */ argt->nvarg_value = value; argt->nvarg_value_regex = value_regex; return (argt); } /* * If the -a option is not present, filter out fault records that correspond * to events that the producer requested not be messaged for administrators. */ /*ARGSUSED*/ int log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) { boolean_t msg; return (nvlist_lookup_boolean_value(rp->rec_nvl, FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0); } struct loglink { char *path; long suffix; struct loglink *next; }; static void addlink(struct loglink **llp, char *dirname, char *logname, long suffix) { struct loglink *newp; size_t len; char *str; newp = malloc(sizeof (struct loglink)); len = strlen(dirname) + strlen(logname) + 2; str = malloc(len); if (newp == NULL || str == NULL) { (void) fprintf(stderr, "%s: failed to allocate memory: %s\n", g_pname, strerror(errno)); exit(FMDUMP_EXIT_FATAL); } (void) snprintf(str, len, "%s/%s", dirname, logname); newp->path = str; newp->suffix = suffix; while (*llp != NULL && suffix < (*llp)->suffix) llp = &(*llp)->next; newp->next = *llp; *llp = newp; } /* * Find and return all the rotated logs. */ static struct loglink * get_rotated_logs(char *logpath) { char dirname[PATH_MAX], *logname, *endptr; DIR *dirp; struct dirent *dp; long len, suffix; struct loglink *head = NULL; (void) strlcpy(dirname, logpath, sizeof (dirname)); logname = strrchr(dirname, '/'); *logname++ = '\0'; len = strlen(logname); if ((dirp = opendir(dirname)) == NULL) { (void) fprintf(stderr, "%s: failed to opendir `%s': %s\n", g_pname, dirname, strerror(errno)); return (NULL); } while ((dp = readdir(dirp)) != NULL) { /* * Search the log directory for logs named ".0", * ".1", etc and add to the link in the * reverse numeric order. */ if (strlen(dp->d_name) < len + 2 || strncmp(dp->d_name, logname, len) != 0 || dp->d_name[len] != '.') continue; /* * "*.0-" file normally should not be seen. It may * exist when user manually run 'fmadm rotate'. * In such case, we put it at the end of the list so * it'll be dumped after all the rotated logs, before * the current one. */ if (strcmp(dp->d_name + len + 1, "0-") == 0) addlink(&head, dirname, dp->d_name, -1); else if ((suffix = strtol(dp->d_name + len + 1, &endptr, 10)) >= 0 && *endptr == '\0') addlink(&head, dirname, dp->d_name, suffix); } (void) closedir(dirp); return (head); } int main(int argc, char *argv[]) { int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0, opt_m = 0; int opt_u = 0, opt_v = 0, opt_V = 0; char ifile[PATH_MAX] = ""; int iflags = 0; fmdump_arg_t arg; fmdump_lyr_t lyr; const fmdump_ops_t *ops; fmd_log_filter_t *filtv; uint_t filtc; fmd_log_filter_t *errfv, *fltfv, *allfv; uint_t errfc = 0, fltfc = 0, allfc = 0; fmd_log_header_t log; fmd_log_rec_f *func; void *farg; fmd_log_t *lp; int c, err; off64_t off = 0; ulong_t recs; struct loglink *rotated_logs = NULL, *llp; g_pname = argv[0]; errfv = alloca(sizeof (fmd_log_filter_t) * argc); fltfv = alloca(sizeof (fmd_log_filter_t) * argc); allfv = alloca(sizeof (fmd_log_filter_t) * argc); while (optind < argc) { while ((c = getopt(argc, argv, "ac:efHmn:O:R:t:T:u:vV")) != EOF) { switch (c) { case 'a': opt_a++; break; case 'c': errfv[errfc].filt_func = fmd_log_filter_class; errfv[errfc].filt_arg = optarg; allfv[allfc++] = errfv[errfc++]; break; case 'e': opt_e++; break; case 'f': opt_f++; break; case 'H': opt_H++; break; case 'm': opt_m++; break; case 'O': off = strtoull(optarg, NULL, 16); iflags |= FMD_LOG_XITER_OFFS; break; case 'R': g_root = optarg; break; case 't': errfv[errfc].filt_func = fmd_log_filter_after; errfv[errfc].filt_arg = gettimeopt(optarg); allfv[allfc++] = errfv[errfc++]; break; case 'T': errfv[errfc].filt_func = fmd_log_filter_before; errfv[errfc].filt_arg = gettimeopt(optarg); allfv[allfc++] = errfv[errfc++]; break; case 'u': fltfv[fltfc].filt_func = fmd_log_filter_uuid; fltfv[fltfc].filt_arg = optarg; allfv[allfc++] = fltfv[fltfc++]; opt_u++; opt_a++; /* -u implies -a */ break; case 'n': { fltfv[fltfc].filt_func = fmd_log_filter_nv; fltfv[fltfc].filt_arg = setupnamevalue(optarg); allfv[allfc++] = fltfv[fltfc++]; break; } case 'v': opt_v++; break; case 'V': opt_V++; break; default: return (usage(stderr)); } } if (optind < argc) { if (*ifile != '\0') { (void) fprintf(stderr, "%s: illegal " "argument -- %s\n", g_pname, argv[optind]); return (FMDUMP_EXIT_USAGE); } else { (void) strlcpy(ifile, argv[optind++], sizeof (ifile)); } } } if (*ifile == '\0') { (void) snprintf(ifile, sizeof (ifile), "%s/var/fm/fmd/%slog", g_root ? g_root : "", opt_e && !opt_u ? "err" : "flt"); /* * logadm may rotate the logs. When no input file is specified, * we try to dump all the rotated logs as well in the right * order. */ if (!opt_H && off == 0) rotated_logs = get_rotated_logs(ifile); } else if (g_root != NULL) { (void) fprintf(stderr, "%s: -R option is not appropriate " "when file operand is present\n", g_pname); return (FMDUMP_EXIT_USAGE); } if ((g_msg = fmd_msg_init(g_root, FMD_MSG_VERSION)) == NULL) { (void) fprintf(stderr, "%s: failed to initialize " "libfmd_msg: %s\n", g_pname, strerror(errno)); return (FMDUMP_EXIT_FATAL); } if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) { (void) fprintf(stderr, "%s: failed to open %s: %s\n", g_pname, ifile, fmd_log_errmsg(NULL, err)); return (FMDUMP_EXIT_FATAL); } if (opt_H) { fmd_log_header(lp, &log); (void) printf("EXD_CREATOR = %s\n", log.log_creator); (void) printf("EXD_HOSTNAME = %s\n", log.log_hostname); (void) printf("EXD_FMA_LABEL = %s\n", log.log_label); (void) printf("EXD_FMA_VERSION = %s\n", log.log_version); (void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease); (void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion); (void) printf("EXD_FMA_PLAT = %s\n", log.log_platform); (void) printf("EXD_FMA_UUID = %s\n", log.log_uuid); return (FMDUMP_EXIT_SUCCESS); } if (off != 0 && fmd_log_seek(lp, off) != 0) { (void) fprintf(stderr, "%s: failed to seek %s: %s\n", g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp))); return (FMDUMP_EXIT_FATAL); } if (opt_e && opt_u) ops = &fmdump_err_ops; else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0) ops = &fmdump_flt_ops; else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0) ops = &fmdump_asru_ops; else ops = &fmdump_err_ops; if (!opt_a && ops == &fmdump_flt_ops) { fltfv[fltfc].filt_func = log_filter_silent; fltfv[fltfc].filt_arg = NULL; allfv[allfc++] = fltfv[fltfc++]; } if (opt_V) { arg.da_fmt = &ops->do_formats[FMDUMP_VERB2]; iflags |= FMD_LOG_XITER_REFS; } else if (opt_v) { arg.da_fmt = &ops->do_formats[FMDUMP_VERB1]; } else if (opt_m) { arg.da_fmt = &ops->do_formats[FMDUMP_MSG]; } else arg.da_fmt = &ops->do_formats[FMDUMP_SHORT]; if (opt_m && arg.da_fmt->do_func == NULL) { (void) fprintf(stderr, "%s: -m mode is not supported for " "log of type %s: %s\n", g_pname, fmd_log_label(lp), ifile); return (FMDUMP_EXIT_USAGE); } arg.da_fv = errfv; arg.da_fc = errfc; arg.da_fp = stdout; if (iflags & FMD_LOG_XITER_OFFS) fmdump_printf(arg.da_fp, "%16s ", "OFFSET"); if (arg.da_fmt->do_hdr) fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr); if (opt_e && opt_u) { iflags |= FMD_LOG_XITER_REFS; func = xref_iter; farg = &arg; filtc = fltfc; filtv = fltfv; } else { func = arg.da_fmt->do_func; farg = arg.da_fp; filtc = allfc; filtv = allfv; } if (iflags & FMD_LOG_XITER_OFFS) { lyr.dy_func = func; lyr.dy_arg = farg; lyr.dy_fp = arg.da_fp; func = xoff_iter; farg = &lyr; } for (llp = rotated_logs; llp != NULL; llp = llp->next) { fmd_log_t *rlp; if ((rlp = fmd_log_open(FMD_LOG_VERSION, llp->path, &err)) == NULL) { (void) fprintf(stderr, "%s: failed to open %s: %s\n", g_pname, llp->path, fmd_log_errmsg(NULL, err)); g_errs++; continue; } recs = 0; if (fmd_log_xiter(rlp, iflags, filtc, filtv, func, error, farg, &recs) != 0) { (void) fprintf(stderr, "%s: failed to dump %s: %s\n", g_pname, llp->path, fmd_log_errmsg(rlp, fmd_log_errno(rlp))); g_errs++; } g_recs += recs; fmd_log_close(rlp); } do { recs = 0; if (fmd_log_xiter(lp, iflags, filtc, filtv, func, error, farg, &recs) != 0) { (void) fprintf(stderr, "%s: failed to dump %s: %s\n", g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp))); g_errs++; } g_recs += recs; if (opt_f) (void) sleep(1); } while (opt_f); if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO)) (void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile); if (g_thp != NULL) topo_close(g_thp); fmd_log_close(lp); fmd_msg_fini(g_msg); return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS); }