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 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <alloca.h>
27#include <assert.h>
28#include <errno.h>
29#include <libintl.h>
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <ctype.h>
35#include <wchar.h>
36#include <wctype.h>
37
38#include "fru_tag.h"
39#include "libfrup.h"
40#include "libfrureg.h"
41
42
43#define	NUM_ITER_BYTES 4
44
45#define	HEAD_ITER 0
46#define	TAIL_ITER 1	/*  not used  */
47#define	NUM_ITER  2
48#define	MAX_ITER  3
49
50#define	INDENT 3
51#define	TIMESTRINGLEN 128
52#define	TEMPERATURE_OFFSET 73
53#define	MIN_VERSION 17
54#define	GMT "%a, %b %d %Y %H:%M:%S GMT"
55
56typedef struct
57{
58    uint8_t value;
59    char *data;
60} Status_CurrentR;
61
62Status_CurrentR Status_CurrentR_status[] = {
63	{ 0x00, "OK"},
64	{ 0x04, "DEEMED FAULTY"},
65	{ 0x08, "FRU DETECTED"},
66	{ 0x0c, "FRU DETECTED, DEEMED FAULTY"},
67	{ 0x10, "PROXIED FAULT"},
68	{ 0x14, "DEEMED FAULTY.  Also PROXIED FAULT"},
69	{ 0x18, "FRU DETECTED.  Also PROXIED FAULT"},
70	{ 0x1c, "FRU DETECTED, DEEMED FAULTY.  Also PROXIED FAULT"},
71	{ 0x20, "SUSPECT"},
72	{ 0x24, "SUSPECT, DEEMED FAULTY"},
73	{ 0x28, "SUSPECT, FRU DETECTED"},
74	{ 0x2c, "SUSPECT, FRU DETECTED, DEEMED FAULTY"},
75	{ 0x30, "SUSPECT.  Also PROXIED FAULT"},
76	{ 0x34, "SUSPECT, DEEMED FAULTY.  Also PROXIED FAULT"},
77	{ 0x38, "SUSPECT, FRU DETECTED.  Also PROXIED FAULT"},
78	{ 0x3c, "SUSPECT, FRU DETECTED, DEEMED FAULTY.  Also PROXIED FAULT"},
79	{ 0x40, "MAINTENANCE REQUIRED"},
80	{ 0x44, "MAINTENANCE REQUIRED, DEEMED FAULTY"},
81	{ 0x48, "MAINTENANCE REQUIRED, FRU DETECTED"},
82	{ 0x4c, "MAINTENANCE REQUIRED, FRU DETECTED, DEEMED FAULTY"},
83	{ 0x50, "MAINTENANCE REQUIRED.  Also PROXIED FAULT"},
84	{ 0x54, "MAINTENANCE REQUIRED, DEEMED FAULTY.  Also PROXIED FAULT"},
85	{ 0x58, "MAINTENANCE REQUIRED, FRU DETECTED.  Also PROXIED FAULT"},
86	{ 0x5c, "MAINTENANCE REQUIRED, FRU DETECTED, DEEMED FAULTY. \
87	    Also PROXIED FAULT"},
88	{ 0x60, "MAINTENANCE REQUIRED, SUSPECT"},
89	{ 0x64, "MAINTENANCE REQUIRED, SUSPECT, DEEMED FAULTY"},
90	{ 0x68, "MAINTENANCE REQUIRED, SUSPECT, FRU DETECTED"},
91	{ 0x6c, "MAINTENANCE REQUIRED, SUSPECT, FRU DETECTED, DEEMED FAULTY"},
92	{ 0x70, "MAINTENANCE REQUIRED, SUSPECT.  Also PROXIED FAULT"},
93	{ 0x74, "MAINTENANCE REQUIRED, SUSPECT, DEEMED FAULTY.\
94	    Also PROXIED FAULT"},
95	{ 0x78, "MAINTENANCE REQUIRED, SUSPECT, FRU DETECTED. \
96	    Also PROXIED FAULT"},
97	{ 0x7c, "MAINTENANCE REQUIRED, SUSPECT, FRU DETECTED, \
98	    DEEMED FAULTY.  Also PROXIED FAULT"},
99	{ 0x80, "DISABLED"},
100	{ 0x84, "DISABLED, DEEMED FAULTY"},
101	{ 0x88, "DISABLED, FRU DETECTED"},
102	{ 0x8c, "DISABLED, FRU DETECTED, DEEMED FAULTY"},
103	{ 0x90, "DISABLED.  Also PROXIED FAULT"},
104	{ 0x94, "DISABLED, DEEMED FAULTY.  Also PROXIED FAULT"},
105	{ 0x98, "DISABLED, FRU DETECTED.  Also PROXIED FAULT"},
106	{ 0x9c, "DISABLED, FRU DETECTED, DEEMED FAULTY.  Also PROXIED FAULT"},
107	{ 0xa0, "DISABLED, SUSPECT"},
108	{ 0xa4, "DISABLED, SUSPECT, DEEMED FAULTY"},
109	{ 0xa8, "DISABLED, SUSPECT, FRU DETECTED"},
110	{ 0xac, "DISABLED, SUSPECT, FRU DETECTED, DEEMED FAULTY"},
111	{ 0xb0, "DISABLED, SUSPECT.  Also PROXIED FAULT"},
112	{ 0xb4, "DISABLED, SUSPECT, DEEMED FAULTY.  Also PROXIED FAULT"},
113	{ 0xb8, "DISABLED, SUSPECT, FRU DETECTED.  Also PROXIED FAULT"},
114	{ 0xbc, "DISABLED, SUSPECT, FRU DETECTED, \
115	    DEEMED FAULTY.  Also PROXIED FAULT"},
116	{ 0xc0, "DISABLED, MAINTENANCE REQUIRED"},
117	{ 0xc4, "DISABLED, MAINTENANCE REQUIRED, DEEMED FAULTY"},
118	{ 0xc8, "DISABLED, MAINTENANCE REQUIRED, FRU DETECTED"},
119	{ 0xcc, "DISABLED, MAINTENANCE REQUIRED, FRU DETECTED, DEEMED FAULTY"},
120	{ 0xd0, "DISABLED, MAINTENANCE REQUIRED.  Also PROXIED FAULT"},
121	{ 0xd4, "DISABLED, MAINTENANCE REQUIRED, \
122	    DEEMED FAULTY.  Also PROXIED FAULT"},
123	{ 0xd8, "DISABLED, MAINTENANCE REQUIRED, \
124	    FRU DETECTED.  Also PROXIED FAULT"},
125	{ 0xdc, "DISABLED, MAINTENANCE REQUIRED, FRU DETECTED, \
126	    DEEMED FAULTY.  Also PROXIED FAULT"},
127	{ 0xe0, "DISABLED, MAINTENANCE REQUIRED, SUSPECT"},
128	{ 0xe4, "DISABLED, MAINTENANCE REQUIRED, SUSPECT, DEEMED FAULTY"},
129	{ 0xe8, "DISABLED, MAINTENANCE REQUIRED, SUSPECT, FRU DETECTED"},
130	{ 0xec, "DISABLED, MAINTENANCE REQUIRED, SUSPECT, \
131	    FRU DETECTED, DEEMED FAULTY"},
132	{ 0xf0, "DISABLED, MAINTENANCE REQUIRED, SUSPECT.  Also PROXIED FAULT"},
133	{ 0xf4, "DISABLED, MAINTENANCE REQUIRED, SUSPECT, \
134	    DEEMED FAULTY.  Also PROXIED FAULT"},
135	{ 0xf8, "DISABLED, MAINTENANCE REQUIRED, SUSPECT, \
136	    FRU DETECTED.  Also PROXIED FAULT"},
137	{ 0xfc, "DISABLED, MAINTENANCE REQUIRED, SUSPECT, \
138	    FRU DETECTED, DEEMED FAULTY.  Also PROXIED FAULT"},
139	{ 0xff, "RETIRED"}
140};
141
142static void	(*print_node)(fru_node_t fru_type, const char *path,
143				const char *name, end_node_fp_t *end_node,
144				void **end_args);
145
146static void	print_element(const uint8_t *data, const fru_regdef_t *def,
147const char *parent_path, int indent);
148
149static char	tagname[sizeof ("?_0123456789_0123456789_0123456789")];
150
151static int	containers_only = 0, list_only = 0, saved_status = 0, xml = 0;
152
153static FILE	*errlog;
154
155int iterglobal = 0;
156int FMAmessageR = -1;
157int Fault_Install_DataR_flag = 0;
158int Power_On_DataR_flag = 0;
159int spd_memtype = 0;
160int spd_revision = 0;
161/*
162 * Definition for data elements found in devices but not found in
163 * the system's version of libfrureg
164 */
165static fru_regdef_t  unknown = {
166	REGDEF_VERSION,
167	tagname,
168	-1,
169	-1,
170	-1,
171	-1,
172	FDTYPE_ByteArray,
173	FDISP_Hex,
174	FRU_WHICH_UNDEFINED,
175	FRU_WHICH_UNDEFINED,
176	0,
177	NULL,
178	0,
179	FRU_NOT_ITERATED,
180	NULL
181};
182
183
184/*
185 * Write message to standard error and possibly the error log buffer
186 */
187static void
188error(const char *format, ...)
189{
190	va_list	args;
191
192
193	/* make relevant output appear before error message */
194	if (fflush(stdout) == EOF) {
195		(void) fprintf(stderr, "Error flushing output:  %s\n",
196		    strerror(errno));
197		exit(1);
198	}
199
200	va_start(args, format);
201	if (vfprintf(stderr, format, args) < 0) exit(1);
202	if (errlog && (vfprintf(errlog, format, args) < 0)) exit(1);
203}
204
205/*
206 * Write message to standard output
207 */
208static void
209output(const char *format, ...)
210{
211	va_list   args;
212
213
214	va_start(args, format);
215	if (vfprintf(stdout, format, args) < 0) {
216		error(gettext("Error writing output:  %s\n"),
217		    strerror(errno));
218		exit(1);
219	}
220}
221
222/*
223 * Safe wrapper for putchar()
224 */
225static void
226voidputchar(int c)
227{
228	if (putchar(c) == EOF) {
229		error(gettext("Error writing output:  %s\n"),
230		    strerror(errno));
231		exit(1);
232	}
233}
234
235static void  (*safeputchar)(int c) = voidputchar;
236
237/*
238 * Safe wrapper for puts()
239 */
240static void
241voidputs(const char *s)
242{
243	if (fputs(s, stdout) == EOF) {
244		error(gettext("Error writing output:  %s\n"),
245		    strerror(errno));
246		exit(1);
247	}
248}
249
250static void  (*safeputs)(const char *s) = voidputs;
251
252/*
253 * XML-safe wrapper for putchar():  quotes XML-special characters
254 */
255static void
256xputchar(int c)
257{
258	switch (c) {
259	case '<':
260		c = fputs("&lt;", stdout);
261		break;
262	case '>':
263		c = fputs("&gt;", stdout);
264		break;
265	case '&':
266		c = fputs("&amp;", stdout);
267		break;
268	case '"':
269		c = fputs("&quot;", stdout);
270		break;
271	default:
272		c = putchar(c);
273		break;
274	}
275
276	if (c == EOF) {
277		error(gettext("Error writing output:  %s\n"),
278		    strerror(errno));
279		exit(1);
280	}
281}
282
283/*
284 * XML-safe analog of puts():  quotes XML-special characters
285 */
286static void
287xputs(const char *s)
288{
289	char c;
290
291	for (/* */; ((c = *s) != 0); s++)
292		xputchar(c);
293}
294
295/*
296 * Output the XML DTD derived from the registry provided by libfrureg
297 */
298int
299output_dtd(void)
300{
301	char			**element;
302
303	unsigned int		i, j, num_elements = 0;
304
305	uint8_t			*tagged;
306
307	const fru_regdef_t	*def;
308
309
310	if (((element = fru_reg_list_entries(&num_elements)) == NULL) ||
311	    (num_elements == 0)) {
312		error(gettext("No FRU ID Registry elements"));
313		return (1);
314	}
315
316	if ((tagged = calloc(num_elements, sizeof (*tagged))) == NULL) {
317		error(gettext("Unable to get memory for tagged element list"),
318		    strerror(errno));
319		return (1);
320	}
321
322	/*
323	 * Output the DTD preamble
324	 */
325	output("<!ELEMENT FRUID_XML_Tree (Parameter*, "
326	    "(Fru | Location | Container)*,\n"
327	    "                          Parameter*, ErrorLog?, Parameter*)>\n"
328	    "<!ATTLIST FRUID_XML_Tree>\n"
329	    "\n"
330	    "<!ELEMENT Parameter EMPTY>\n"
331	    "<!ATTLIST Parameter type CDATA #REQUIRED>\n"
332	    "<!ATTLIST Parameter name CDATA #REQUIRED>\n"
333	    "<!ATTLIST Parameter value CDATA #REQUIRED>\n"
334	    "\n"
335	    "<!ELEMENT Fru (Fru | Location | Container)*>\n"
336	    "<!ATTLIST Fru name CDATA #REQUIRED>\n"
337	    "\n"
338	    "<!ELEMENT Location (Fru | Location | Container)*>\n"
339	    "<!ATTLIST Location\n"
340	    "	name CDATA #IMPLIED\n"
341	    "	value CDATA #IMPLIED\n"
342	    ">\n"
343	    "\n"
344	    "<!ELEMENT Container (ContainerData?, "
345	    "(Fru | Location | Container)*)>\n"
346	    "<!ATTLIST Container name CDATA #REQUIRED>\n"
347	    "<!ATTLIST Container imagefile CDATA #IMPLIED>\n"
348	    "\n"
349	    "<!ELEMENT ContainerData (Segment*)>\n"
350	    "<!ATTLIST ContainerData>\n"
351	    "\n"
352	    "<!ATTLIST Segment name CDATA #REQUIRED>\n"
353	    "\n"
354	    "<!ELEMENT Index EMPTY>\n"
355	    "<!ATTLIST Index value CDATA #REQUIRED>\n"
356	    "\n"
357	    "<!ELEMENT ErrorLog (#PCDATA)>\n"
358	    "<!ATTLIST ErrorLog>\n"
359	    "\n");
360
361	/*
362	 * Output the definition for each element
363	 */
364	for (i = 0; i < num_elements; i++) {
365		assert(element[i] != NULL);
366		/* Prevent incompatible duplicate defn. from FRUID Registry. */
367		if ((strcmp("Location", element[i])) == 0) continue;
368		if ((def = fru_reg_lookup_def_by_name(element[i])) == NULL) {
369			error(gettext("Error looking up registry "
370			    "definition for \"%s\"\n"),
371			    element[i]);
372			return (1);
373		}
374
375		if (def->tagType != FRU_X) tagged[i] = 1;
376
377		if (def->dataType == FDTYPE_Record) {
378			if (def->iterationType == FRU_NOT_ITERATED)
379				output("<!ELEMENT %s (%s", element[i],
380				    def->enumTable[0].text);
381			else
382				output("<!ELEMENT %s (Index_%s*)>\n"
383				    "<!ATTLIST Index_%s>\n"
384				    "<!ELEMENT Index_%s (%s",
385				    element[i], element[i], element[i],
386				    element[i], def->enumTable[0].text);
387
388			for (j = 1; j < def->enumCount; j++)
389				output(",\n\t%s", def->enumTable[j].text);
390
391			output(")>\n");
392		} else if (def->iterationType == FRU_NOT_ITERATED) {
393			output("<!ELEMENT %s EMPTY>\n"
394			    "<!ATTLIST %s value CDATA #REQUIRED>\n",
395			    element[i], element[i]);
396
397			if (def->dataType == FDTYPE_Enumeration) {
398				output("<!-- %s valid enumeration values\n");
399				for (j = 0; j < def->enumCount; j++) {
400					output("\t\"");
401					xputs(def->enumTable[j].text);
402					output("\"\n");
403				}
404				output("-->\n");
405			}
406		}
407		else
408			output("<!ELEMENT %s (Index*)>\n", element[i]);
409
410		output("\n");
411	}
412
413	/* Provide for returning the tag for an "unknown" element */
414	output("<!ATTLIST UNKNOWN tag CDATA \"UNKNOWN\">\n\n");
415
416
417	/*
418	 * List all data elements as possible members of "Segment"
419	 */
420	output("<!ELEMENT Segment ((UNKNOWN");
421	for (i = 0; i < num_elements; i++) {
422		if (tagged[i]) output("\n\t| %s", element[i]);
423		free(element[i]);
424	}
425	output(")*)>\n");
426	free(element);
427	free(tagged);
428
429	return (0);
430}
431/*
432 * Function to convert bcd to binary to correct the SPD_Manufacturer_Week
433 *
434 */
435static void convertbcdtobinary(int *val)
436{
437	unsigned int newval = (unsigned int)*val, tmpval = 0;
438	while (newval > 0) {
439		tmpval = (tmpval << 4) | (newval & 0xF);
440		newval >>= 4;
441	}
442	while (tmpval > 0) {
443		newval = (newval * 10) + (tmpval & 0xF);
444		tmpval >>= 4;
445	}
446	*val = newval;
447}
448
449/*
450 * Checking UTF-8 printable charecter
451 */
452static int check_utf_char(const uint8_t *field, int len)
453{
454	int i, status = 0;
455	char tmp[128], tmp1[128], tmp2[128];
456	(void) sprintf(tmp, " (Invalid Data");
457	(void) sprintf(tmp2, "0x");
458	for (i = 0; i < len && field[i]; i++) {
459		(void) sprintf(tmp1, "%2.2X", field[i]);
460		(void) strcat(tmp2, tmp1);
461		if (iswprint(field[i]) == 0) {
462			status = 1;
463			(void) sprintf(tmp1, " : 0x%2.2X", field[i]);
464			(void) strcat(tmp, tmp1);
465		}
466	}
467	if (status) {
468		(void) sprintf(tmp1, ")");
469		(void) strcat(tmp, tmp1);
470		(void) strcat(tmp2, tmp);
471		output("%s", tmp2);
472	}
473	return (status);
474}
475
476/*
477 * Safely pretty-print the value of a field
478 */
479static void
480print_field(const uint8_t *field, const fru_regdef_t *def)
481{
482	char		*errmsg = NULL, timestring[TIMESTRINGLEN], path[16384];
483
484	int		i, valueint;
485
486	uint64_t	value;
487
488	time_t		timefield;
489
490	struct tm	*tm;
491
492	uchar_t		first_byte, data[128];
493
494	const fru_regdef_t	*new_def;
495
496	const char 	*elem_name = NULL;
497	const char	*parent_path;
498	switch (def->dataType) {
499	case FDTYPE_Binary:
500		assert(def->payloadLen <= sizeof (value));
501		switch (def->dispType) {
502		case FDISP_Binary:
503			for (i = 0; i < def->payloadLen; i++)
504				output("%c%c%c%c%c%c%c%c",
505				    ((field[i] & 0x80) ? '1' : '0'),
506				    ((field[i] & 0x40) ? '1' : '0'),
507				    ((field[i] & 0x20) ? '1' : '0'),
508				    ((field[i] & 0x10) ? '1' : '0'),
509				    ((field[i] & 0x08) ? '1' : '0'),
510				    ((field[i] & 0x04) ? '1' : '0'),
511				    ((field[i] & 0x02) ? '1' : '0'),
512				    ((field[i] & 0x01) ? '1' : '0'));
513			return;
514		case FDISP_Octal:
515		case FDISP_Decimal:
516			value = 0;
517			valueint = 0;
518			(void) memcpy((((uint8_t *)&value) +
519			    sizeof (value) - def->payloadLen),
520			    field, def->payloadLen);
521			if ((value != 0) &&
522			    (strcmp(def->name, "SPD_Manufacture_Week") == 0)) {
523				valueint = (int)value;
524				if (spd_memtype && spd_revision) {
525					convertbcdtobinary(&valueint);
526					spd_memtype = 0;
527					spd_revision = 0;
528				}
529				output("%d", valueint);
530				return;
531			}
532			if ((value != 0) &&
533			    ((strcmp(def->name, "Lowest") == 0) ||
534			    (strcmp(def->name, "Highest") == 0) ||
535			    (strcmp(def->name, "Latest") == 0)))
536				output((def->dispType == FDISP_Octal) ?
537				"%llo" : "%lld (%lld degrees C)",
538				    value, (value - TEMPERATURE_OFFSET));
539			else
540				output((def->dispType == FDISP_Octal) ?
541				"%llo" : "%lld", value);
542			return;
543		case FDISP_Time:
544			if (def->payloadLen > sizeof (timefield)) {
545				errmsg = "time value too large for formatting";
546				break;
547			}
548			timefield = 0;
549			(void) memcpy((((uint8_t *)&timefield) +
550			    sizeof (timefield) - def->payloadLen),
551			    field, def->payloadLen);
552			if (timefield == 0) {
553				errmsg = "No Value Recorded";
554				break;
555			}
556			if ((tm = gmtime(&timefield)) == NULL) {
557				errmsg = "cannot convert time value";
558				break;
559			}
560			if (strftime(timestring, sizeof (timestring), GMT, tm)
561			    == 0) {
562				errmsg = "formatted time would overflow buffer";
563				break;
564			}
565			safeputs(timestring);
566			return;
567		}
568		break;
569	case FDTYPE_ASCII:
570		if (!xml) {
571			if (strcmp(def->name, "Message") == 0) {
572				if (FMAmessageR == 0)
573					elem_name = "FMA_Event_DataR";
574				else if (FMAmessageR == 1)
575					elem_name = "FMA_MessageR";
576				if (elem_name != NULL) {
577					(void) memcpy(data, field,
578					    def->payloadLen);
579					new_def =
580					    fru_reg_lookup_def_by_name
581					    (elem_name);
582					(void) snprintf(path, sizeof (path),
583					"/Status_EventsR[%d]/Message(FMA)",
584					    iterglobal);
585					parent_path = path;
586					output("\n");
587					print_element(data, new_def,
588					    parent_path, 2*INDENT);
589					return;
590				}
591			}
592		}
593		if (strcmp(def->name, "Fru_Path") == 0) {
594			if (check_utf_char(field, def->payloadLen) == 1)
595				return;
596		}
597		for (i = 0; i < def->payloadLen && field[i]; i++)
598			safeputchar(field[i]);
599		return;
600	case FDTYPE_Enumeration:
601		value = 0;
602		(void) memcpy((((uint8_t *)&value) + sizeof (value)
603		    - def->payloadLen),
604		    field, def->payloadLen);
605		for (i = 0; i < def->enumCount; i++)
606			if (def->enumTable[i].value == value) {
607				if (strcmp(def->name, "Event_Code") == 0) {
608					if (strcmp(def->enumTable[i].text,
609"FMA Message R") == 0)
610						FMAmessageR = 1;
611				else
612					if (strcmp(def->enumTable[i].text,
613"FMA Event Data R") == 0)
614						FMAmessageR = 0;
615				}
616				if (strcmp(def->name,
617"SPD_Fundamental_Memory_Type") == 0) {
618					if (strcmp(def->enumTable[i].text,
619"DDR II SDRAM") == 0)
620						spd_memtype = 1;
621				}
622				safeputs(def->enumTable[i].text);
623				return;
624			}
625
626		errmsg = "unrecognized value";
627		break;
628	}
629
630	/* If nothing matched above, print the field in hex */
631	switch (def->dispType) {
632		case FDISP_MSGID:
633			(void) memcpy((uchar_t *)&first_byte, field, 1);
634			if (isprint(first_byte)) {
635				for (i = 0; i < def->payloadLen && field[i];
636				    i++)
637					safeputchar(field[i]);
638			}
639			break;
640		case FDISP_UUID:
641			for (i = 0; i < def->payloadLen; i++) {
642				if ((i == 4) || (i == 6) ||
643				    (i == 8) || (i == 10))
644				output("-");
645				output("%2.2x", field[i]);
646			}
647			break;
648		default:
649			if ((strcmp(def->name, "Status") == 0) ||
650			    (strcmp(def->name, "Old_Status") == 0) ||
651			    (strcmp(def->name, "New_Status") == 0)) {
652				int status_length = \
653				    sizeof (Status_CurrentR_status) / \
654				    sizeof (*(Status_CurrentR_status));
655				i = 0;
656				do {
657					if (Status_CurrentR_status[i].value == \
658					    *(field))
659						break;
660					i++;
661				} while (i < status_length);
662				if (i < status_length)
663					output("0x%2.2X (%s)", *(field),
664					    Status_CurrentR_status[i].data);
665				else
666					output("0x%2.2X (UNKNOWN)", *(field));
667				break;
668			}
669			if (strcmp(def->name,
670			"SPD_Data_Revision_Code") == 0) {
671				value = 0;
672				valueint = 0;
673				(void) memcpy((((uint8_t *)&value)
674				    + sizeof (value) - def->payloadLen),
675				    field, def->payloadLen);
676				valueint = (int)value;
677				if ((valueint >= MIN_VERSION) && (spd_memtype))
678					spd_revision = 1;
679			}
680			for (i = 0; i < def->payloadLen; i++)
681				output("%2.2X", field[i]);
682			break;
683	}
684
685	/* Safely print any error message associated with the field */
686	if (errmsg) {
687		if (strcmp(def->name, "Fault_Diag_Secs") != 0) {
688			output(" (");
689			safeputs(errmsg);
690			output(")");
691		}
692	}
693}
694
695/*
696 * Recursively print the contents of a data element
697 */
698static void
699print_element(const uint8_t *data, const fru_regdef_t *def,
700    const char *parent_path, int indent)
701{
702	char	*path;
703	size_t	len;
704
705	int	bytes = 0, i;
706
707
708	indent = (xml) ? (indent + INDENT) : (2*INDENT);
709	if (strcmp(def->name, "Sun_SPD_DataR") == 0) {
710		Fault_Install_DataR_flag = indent;
711		Power_On_DataR_flag = indent;
712	}
713	/*
714	 * Construct the path, or, for XML, the name, for the current
715	 * data element
716	 */
717	if ((def->iterationCount == 0) &&
718	    (def->iterationType != FRU_NOT_ITERATED)) {
719		if (xml) {
720			if (def->dataType == FDTYPE_Record) {
721				len = strlen("Index_") + strlen(def->name) + 1;
722				path = alloca(len);
723				(void) snprintf(path, len,
724				    "Index_%s", def->name);
725			}
726			else
727				path = "Index";
728		}
729		else
730			path = (char *)parent_path;
731	} else {
732		if (xml)
733			path = (char *)def->name;
734		else {
735			len = strlen(parent_path) + sizeof ("/") +
736			    strlen(def->name) +
737			    (def->iterationCount ? sizeof ("[255]") : 0);
738			path = alloca(len);
739			bytes = snprintf(path, len,
740			    "%s/%s", parent_path, def->name);
741		}
742	}
743
744	if ((Fault_Install_DataR_flag) &&
745	    (strcmp(path, "E_1_46") == 0) || (strcmp(path, "/E_1_46") == 0)) {
746		int cnt;
747		char timestring[128];
748		time_t timefield = 0;
749		struct tm *tm;
750		indent = Fault_Install_DataR_flag;
751		(void) memcpy((uint8_t *)&timefield, data, 4);
752		if (timefield == 0) {
753			(void) sprintf(timestring,
754			    "00000000 (No Value Recorded)\"");
755		} else {
756			if ((tm = gmtime(&timefield)) == NULL)
757				(void) sprintf(timestring,
758				    "cannot convert time value");
759			if (strftime(timestring,
760			    sizeof (timestring), GMT, tm) == 0)
761				(void) sprintf(timestring,
762				    "formatted time would overflow buffer");
763		}
764		if (xml) {
765			(void) sprintf(path, "Fault_Install_DataR");
766			output("%*s<%s>\n", indent, "", path);
767			indent = Fault_Install_DataR_flag + INDENT;
768			(void) sprintf(path, "UNIX_Timestamp32");
769			output("%*s<%s value=\"", indent, "", path);
770			/*CSTYLED*/
771			output("%s\"/>\n", timestring);
772			(void) sprintf(path, "MACADDR");
773			output("%*s<%s value=\"", indent, "", path);
774			for (cnt = 4; cnt < 4 + 6; cnt++) {
775				output("%2.2x", data[cnt]);
776				if (cnt < 4 + 6 - 1)
777					output(":");
778			}
779			/*CSTYLED*/
780			output("\"/>\n");
781			(void) sprintf(path, "Status");
782			output("%*s<%s value=\"", indent, "", path);
783			/*CSTYLED*/
784			output("%2.2x\"/>\n", data[10]);
785			(void) sprintf(path, "Initiator");
786			output("%*s<%s value=\"", indent, "", path);
787			/*CSTYLED*/
788			output("%2.2x\"/>\n", data[11]);
789			(void) sprintf(path, "Message_Type");
790			output("%*s<%s value=\"", indent, "", path);
791			/*CSTYLED*/
792			output("%2.2x\"/>\n", data[12]);
793			(void) sprintf(path, "Message_32");
794			output("%*s<%s value=\"", indent, "", path);
795			for (cnt = 13; cnt < 13 + 32; cnt++)
796				output("%2.2x", data[cnt]);
797			/*CSTYLED*/
798			output("\"/>\n");
799			indent = Fault_Install_DataR_flag;
800			(void) sprintf(path, "Fault_Install_DataR");
801			output("%*s</%s>\n", indent, "", path);
802		} else {
803			(void) sprintf(path, "/Fault_Install_DataR");
804			output("%*s%s\n", indent, "", path);
805			(void) sprintf(path,
806			    "/Fault_Install_DataR/UNIX_Timestamp32");
807			output("%*s%s: ", indent, "", path);
808			output("%s\n", timestring);
809			(void) sprintf(path, "/Fault_Install_DataR/MACADDR");
810			output("%*s%s: ", indent, "", path);
811			for (cnt = 4; cnt < 4 + 6; cnt++) {
812				output("%2.2x", data[cnt]);
813				if (cnt < 4 + 6 - 1)
814					output(":");
815			}
816			output("\n");
817			(void) sprintf(path, "/Fault_Install_DataR/Status");
818			output("%*s%s: ", indent, "", path);
819			output("%2.2x\n", data[10]);
820			(void) sprintf(path, "/Fault_Install_DataR/Initiator");
821			output("%*s%s: ", indent, "", path);
822			output("%2.2x\n", data[11]);
823			(void) sprintf(path,
824			    "/Fault_Install_DataR/Message_Type");
825			output("%*s%s: ", indent, "", path);
826			output("%2.2x\n", data[12]);
827			(void) sprintf(path, "/Fault_Install_DataR/Message_32");
828			output("%*s%s: ", indent, "", path);
829			for (cnt = 13; cnt < 13 + 32; cnt++)
830				output("%2.2x", data[cnt]);
831			output("\n");
832		}
833		Fault_Install_DataR_flag = 0;
834		return;
835	} else if ((Power_On_DataR_flag) && (
836	    strcmp(path, "C_10_8") == 0 ||
837	    (strcmp(path, "/C_10_8") == 0))) {
838		int cnt;
839		char timestring[128];
840		time_t timefield = 0;
841		struct tm *tm;
842		indent = Power_On_DataR_flag;
843		(void) memcpy((uint8_t *)&timefield, data, 4);
844		if (timefield == 0) {
845			(void) sprintf(timestring,
846			    "00000000 (No Value Recorded)");
847		} else {
848			if ((tm = gmtime(&timefield)) == NULL)
849				(void) sprintf(timestring,
850				    "cannot convert time value");
851			if (strftime(timestring,
852			    sizeof (timestring), GMT, tm) == 0)
853				(void) sprintf(timestring,
854				    "formatted time would overflow buffer");
855		}
856		if (xml) {
857			(void) sprintf(path, "Power_On_DataR");
858			output("%*s<%s>\n", indent, "", path);
859			indent = Power_On_DataR_flag + INDENT;
860			(void) sprintf(path, "UNIX_Timestamp32");
861			output("%*s<%s value=\"", indent, "", path);
862			/*CSTYLED*/
863			output("%s\"/>\n", timestring);
864			(void) sprintf(path, "Power_On_Minutes");
865			output("%*s<%s value=\"", indent, "", path);
866			for (cnt = 4; cnt < 4 + 4; cnt++)
867				output("%2.2x", data[cnt]);
868			/*CSTYLED*/
869			output("\"/>\n");
870			indent = Power_On_DataR_flag;
871			(void) sprintf(path, "Power_On_DataR");
872			output("%*s</%s>\n", indent, "", path);
873		} else {
874			(void) sprintf(path, "/Power_On_DataR");
875			output("%*s%s\n", indent, "", path);
876			(void) sprintf(path,
877			    "/Power_On_DataR/UNIX_Timestamp32");
878			output("%*s%s: ", indent, "", path);
879			output("%s\n", timestring);
880			(void) sprintf(path,
881			    "/Power_On_DataR/Power_On_Minutes");
882			output("%*s%s: ", indent, "", path);
883			for (cnt = 4; cnt < 4 + 4; cnt++)
884				output("%2.2x", data[cnt]);
885			output("\n");
886		}
887		Power_On_DataR_flag = 0;
888		return;
889	}
890	/*
891	 * Handle the various categories of data elements:  iteration,
892	 * record, and field
893	 */
894	if (def->iterationCount) {
895		int		iterlen = (def->payloadLen - NUM_ITER_BYTES)/
896		    def->iterationCount,
897		    n, valid = 1;
898
899		uint8_t		head, num;
900
901		fru_regdef_t	newdef;
902
903
904		/*
905		 * Make a new element definition to describe the components
906		 * of the iteration
907		 */
908		(void) memcpy(&newdef, def, sizeof (newdef));
909		newdef.iterationCount = 0;
910		newdef.payloadLen = iterlen;
911
912		/*
913		 * Validate the contents of the iteration control bytes
914		 */
915		if (data[HEAD_ITER] >= def->iterationCount) {
916			valid = 0;
917			error(gettext("%s:  Invalid iteration head:  %d "
918			    "(should be less than %d)\n"),
919			    path, data[HEAD_ITER], def->iterationCount);
920		}
921
922		if (data[NUM_ITER] > def->iterationCount) {
923			valid = 0;
924			error(gettext("%s:  Invalid iteration count:  %d "
925			    "(should not be greater than %d)\n"),
926			    path, data[NUM_ITER], def->iterationCount);
927		}
928
929		if (data[MAX_ITER] != def->iterationCount) {
930			valid = 0;
931			error(gettext("%s:  Invalid iteration maximum:  %d "
932			    "(should equal %d)\n"),
933			    path, data[MAX_ITER], def->iterationCount);
934		}
935
936		if (valid) {
937			head = data[HEAD_ITER];
938			num  = data[NUM_ITER];
939		} else {
940			head = 0;
941			num  = def->iterationCount;
942			error(gettext("%s:  Showing all iterations\n"), path);
943		}
944
945		if (xml)
946			output("%*s<%s>\n", indent, "", path);
947		else
948			output("%*s%s (%d iterations)\n", indent, "", path,
949			    num);
950
951		/*
952		 * Print each component of the iteration
953		 */
954		for (i = head, n = 0, data += 4;
955		    n < num;
956		    i = ((i + 1) % def->iterationCount), n++) {
957			if (!xml) (void) sprintf((path + bytes), "[%d]", n);
958			iterglobal = n;
959			print_element((data + i*iterlen), &newdef, path,
960			    indent);
961		}
962
963		if (xml) output("%*s</%s>\n", indent, "", path);
964
965	} else if (def->dataType == FDTYPE_Record) {
966		const fru_regdef_t  *component;
967
968		if (xml)
969			output("%*s<%s>\n", indent, "", path);
970		else
971			output("%*s%s\n", indent, "", path);
972
973		/*
974		 * Print each component of the record
975		 */
976		for (i = 0; i < def->enumCount;
977		    i++, data += component->payloadLen) {
978			component = fru_reg_lookup_def_by_name(
979			    def->enumTable[i].text);
980			assert(component != NULL);
981			print_element(data, component, path, indent);
982		}
983
984		if (xml) output("%*s</%s>\n", indent, "", path);
985	} else if (xml) {
986		/*
987		 * Base case:  print the field formatted for XML
988		 */
989		char  *format = ((def == &unknown)
990		    ? "%*s<UNKNOWN tag=\"%s\" value=\""
991		    : "%*s<%s value=\"");
992
993		output(format, indent, "", path);
994		print_field(data, def);
995		/*CSTYLED*/
996		output("\"/>\n");	/* \" confuses cstyle */
997
998		if ((strcmp(def->name, "Message") == 0) &&
999		    ((FMAmessageR == 0) || (FMAmessageR == 1))) {
1000			const char	*elem_name = NULL;
1001			const char	*parent_path;
1002			uchar_t		tmpdata[128];
1003			char		path[16384];
1004			const fru_regdef_t	*new_def;
1005
1006			if (FMAmessageR == 0)
1007				elem_name = "FMA_Event_DataR";
1008			else if (FMAmessageR == 1)
1009				elem_name = "FMA_MessageR";
1010			if (elem_name != NULL) {
1011				(void) memcpy(tmpdata, data, def->payloadLen);
1012				new_def = fru_reg_lookup_def_by_name(elem_name);
1013				(void) snprintf(path, sizeof (path),
1014				"/Status_EventsR[%d]/Message(FMA)", iterglobal);
1015				parent_path = path;
1016				print_element(tmpdata, new_def,
1017				    parent_path, 2*INDENT);
1018				FMAmessageR = -1;
1019			}
1020		}
1021
1022	} else {
1023		/*
1024		 * Base case:  print the field
1025		 */
1026		output("%*s%s: ", indent, "", path);
1027		print_field(data, def);
1028		output("\n");
1029	}
1030}
1031
1032/*
1033 * Print the contents of a packet (i.e., a tagged data element)
1034 */
1035/* ARGSUSED */
1036static int
1037print_packet(fru_tag_t *tag, uint8_t *payload, size_t length, void *args)
1038{
1039	int			tag_type = get_tag_type(tag);
1040
1041	size_t			payload_length = 0;
1042
1043	const fru_regdef_t	*def;
1044
1045	/*
1046	 * Build a definition for unrecognized tags (e.g., not in libfrureg)
1047	 */
1048	if ((tag_type == -1) ||
1049	    ((payload_length = get_payload_length(tag)) != length)) {
1050		def = &unknown;
1051
1052		unknown.tagType    = -1;
1053		unknown.tagDense   = -1;
1054		unknown.payloadLen = length;
1055		unknown.dataLength = unknown.payloadLen;
1056
1057		if (tag_type == -1)
1058			(void) snprintf(tagname, sizeof (tagname), "INVALID");
1059		else
1060			(void) snprintf(tagname, sizeof (tagname),
1061			    "%s_%u_%u_%u", get_tagtype_str(tag_type),
1062			    get_tag_dense(tag), payload_length, length);
1063	} else if ((def = fru_reg_lookup_def_by_tag(*tag)) == NULL) {
1064		def = &unknown;
1065
1066		unknown.tagType    = tag_type;
1067		unknown.tagDense   = get_tag_dense(tag);
1068		unknown.payloadLen = payload_length;
1069		unknown.dataLength = unknown.payloadLen;
1070
1071		(void) snprintf(tagname, sizeof (tagname), "%s_%u_%u",
1072		    get_tagtype_str(unknown.tagType),
1073		    unknown.tagDense, payload_length);
1074	}
1075
1076
1077	/*
1078	 * Print the defined element
1079	 */
1080	print_element(payload, def, "", INDENT);
1081
1082	return (FRU_SUCCESS);
1083}
1084
1085/*
1086 * Print a segment's name and the contents of each data element in the segment
1087 */
1088static int
1089print_packets_in_segment(fru_seghdl_t segment, void *args)
1090{
1091	char	*name;
1092
1093	int	status;
1094
1095
1096	if ((status = fru_get_segment_name(segment, &name)) != FRU_SUCCESS) {
1097		saved_status = status;
1098		name = "";
1099		error(gettext("Error getting segment name:  %s\n"),
1100		    fru_strerror(status));
1101	}
1102
1103
1104	if (xml)
1105		output("%*s<Segment name=\"%s\">\n", INDENT, "", name);
1106	else
1107		output("%*sSEGMENT: %s\n", INDENT, "", name);
1108
1109	if (strcmp(name, "ED") == 0) {
1110		if (xml) output("%*s</Segment>\n", INDENT, "");
1111		free(name);
1112		return (FRU_SUCCESS);
1113	}
1114	/* Iterate over the packets in the segment, printing the contents */
1115	if ((status = fru_for_each_packet(segment, print_packet, args))
1116	    != FRU_SUCCESS) {
1117		saved_status = status;
1118		error(gettext("Error processing data in segment \"%s\":  %s\n"),
1119		    name, fru_strerror(status));
1120	}
1121
1122	if (xml) output("%*s</Segment>\n", INDENT, "");
1123
1124	free(name);
1125
1126	return (FRU_SUCCESS);
1127}
1128
1129/* ARGSUSED */
1130static void
1131print_node_path(fru_node_t fru_type, const char *path, const char *name,
1132    end_node_fp_t *end_node, void **end_args)
1133{
1134	output("%s%s\n", path,
1135	    ((fru_type == FRU_NODE_CONTAINER) ? " (container)"
1136	    : ((fru_type == FRU_NODE_FRU) ? " (fru)" : "")));
1137}
1138
1139/*
1140 * Close the XML element for a "location" node
1141 */
1142/* ARGSUSED */
1143static void
1144end_location_xml(fru_nodehdl_t node, const char *path, const char *name,
1145    void *args)
1146{
1147	assert(args != NULL);
1148	output("</Location> <!-- %s -->\n", args);
1149}
1150
1151/*
1152 * Close the XML element for a "fru" node
1153 */
1154/* ARGSUSED */
1155static void
1156end_fru_xml(fru_nodehdl_t node, const char *path, const char *name, void *args)
1157{
1158	assert(args != NULL);
1159	output("</Fru> <!-- %s -->\n", args);
1160}
1161
1162/*
1163 * Close the XML element for a "container" node
1164 */
1165/* ARGSUSED */
1166static void
1167end_container_xml(fru_nodehdl_t node, const char *path, const char *name,
1168    void *args)
1169{
1170	assert(args != NULL);
1171	output("</Container> <!-- %s -->\n", args);
1172}
1173
1174/*
1175 * Introduce a node in XML and set the appropriate node-closing function
1176 */
1177/* ARGSUSED */
1178static void
1179print_node_xml(fru_node_t fru_type, const char *path, const char *name,
1180    end_node_fp_t *end_node, void **end_args)
1181{
1182	switch (fru_type) {
1183	case FRU_NODE_FRU:
1184		output("<Fru name=\"%s\">\n", name);
1185		*end_node = end_fru_xml;
1186		break;
1187	case FRU_NODE_CONTAINER:
1188		output("<Container name=\"%s\">\n", name);
1189		*end_node = end_container_xml;
1190		break;
1191	default:
1192		output("<Location name=\"%s\">\n", name);
1193		*end_node = end_location_xml;
1194		break;
1195	}
1196
1197	*end_args = (void *) name;
1198}
1199
1200/*
1201 * Print node info and, where appropriate, node contents
1202 */
1203/* ARGSUSED */
1204static fru_errno_t
1205process_node(fru_nodehdl_t node, const char *path, const char *name,
1206		void *args, end_node_fp_t *end_node, void **end_args)
1207{
1208	int		status;
1209
1210	fru_node_t	fru_type = FRU_NODE_UNKNOWN;
1211
1212
1213	if ((status = fru_get_node_type(node, &fru_type)) != FRU_SUCCESS) {
1214		saved_status = status;
1215		error(gettext("Error getting node type:  %s\n"),
1216		    fru_strerror(status));
1217	}
1218
1219	if (containers_only) {
1220		if (fru_type != FRU_NODE_CONTAINER)
1221			return (FRU_SUCCESS);
1222		name = path;
1223	}
1224
1225	/* Introduce the node */
1226	assert(print_node != NULL);
1227	print_node(fru_type, path, name, end_node, end_args);
1228
1229	if (list_only)
1230		return (FRU_SUCCESS);
1231
1232	/* Print the contents of each packet in each segment of a container */
1233	if (fru_type == FRU_NODE_CONTAINER) {
1234		if (xml) output("<ContainerData>\n");
1235		if ((status =
1236		    fru_for_each_segment(node, print_packets_in_segment,
1237		    NULL))
1238		    != FRU_SUCCESS) {
1239			saved_status = status;
1240			error(gettext("Error  processing node \"%s\":  %s\n"),
1241			    name, fru_strerror(status));
1242		}
1243		if (xml) output("</ContainerData>\n");
1244	}
1245
1246	return (FRU_SUCCESS);
1247}
1248
1249/*
1250 * Process the node if its path matches the search path in "args"
1251 */
1252/* ARGSUSED */
1253static fru_errno_t
1254process_matching_node(fru_nodehdl_t node, const char *path, const char *name,
1255    void *args, end_node_fp_t *end_node, void **end_args)
1256	{
1257	int  status;
1258
1259
1260	if (!fru_pathmatch(path, args))
1261		return (FRU_SUCCESS);
1262
1263	status = process_node(node, path, path, args, end_node, end_args);
1264
1265	return ((status == FRU_SUCCESS) ? FRU_WALK_TERMINATE : status);
1266}
1267
1268/*
1269 * Write the trailer required for well-formed DTD-compliant XML
1270 */
1271static void
1272terminate_xml()
1273{
1274	errno = 0;
1275	if (ftell(errlog) > 0) {
1276		char  c;
1277
1278		output("<ErrorLog>\n");
1279		rewind(errlog);
1280		if (!errno)
1281			while ((c = getc(errlog)) != EOF)
1282				xputchar(c);
1283		output("</ErrorLog>\n");
1284	}
1285
1286	if (errno) {
1287		/*NOTREACHED*/
1288		errlog = NULL;
1289		error(gettext("Error copying error messages to \"ErrorLog\""),
1290		    strerror(errno));
1291	}
1292
1293	output("</FRUID_XML_Tree>\n");
1294}
1295
1296/*
1297 * Print available FRU ID information
1298 */
1299int
1300prtfru(const char *searchpath, int containers_only_flag, int list_only_flag,
1301	int xml_flag)
1302{
1303	fru_errno_t    status;
1304
1305	fru_nodehdl_t  frutree = 0;
1306
1307
1308	/* Copy parameter flags to global flags */
1309	containers_only	= containers_only_flag;
1310	list_only	= list_only_flag;
1311	xml		= xml_flag;
1312
1313
1314	/* Help arrange for correct, efficient interleaving of output */
1315	(void) setvbuf(stderr, NULL, _IOLBF, 0);
1316
1317
1318	/* Initialize for XML--or not */
1319	if (xml) {
1320		safeputchar = xputchar;
1321		safeputs    = xputs;
1322
1323		print_node  = print_node_xml;
1324
1325		if ((errlog = tmpfile()) == NULL) {
1326			(void) fprintf(stderr,
1327			    "Error creating error log file:  %s\n",
1328			    strerror(errno));
1329			return (1);
1330		}
1331
1332		/* Output the XML preamble */
1333		output("<?xml version=\"1.0\" ?>\n"
1334		    "<!--\n"
1335		    " Copyright 2000-2002 Sun Microsystems, Inc.  "
1336		    "All rights reserved.\n"
1337		    " Use is subject to license terms.\n"
1338		    "-->\n\n"
1339		    "<!DOCTYPE FRUID_XML_Tree SYSTEM \"prtfrureg.dtd\">\n\n"
1340		    "<FRUID_XML_Tree>\n");
1341
1342		/* Arrange to always properly terminate XML */
1343		if (atexit(terminate_xml))
1344			error(gettext("Warning:  XML will not be terminated:  "
1345			    "%s\n"), strerror(errno));
1346	} else
1347		print_node = print_node_path;
1348
1349
1350	/* Get the root node */
1351	if ((status = fru_get_root(&frutree)) == FRU_NODENOTFOUND) {
1352		error(gettext("This system does not support PICL "
1353		    "infrastructure to provide FRUID data\n"
1354		    "Please use the platform SP to access the FRUID "
1355		    "information\n"));
1356		return (1);
1357	} else if (status != FRU_SUCCESS) {
1358		error(gettext("Unable to access FRU ID data:  %s\n"),
1359		    fru_strerror(status));
1360		return (1);
1361	}
1362
1363	/* Process the tree */
1364	if (searchpath == NULL) {
1365		status = fru_walk_tree(frutree, "", process_node, NULL);
1366	} else {
1367		status = fru_walk_tree(frutree, "", process_matching_node,
1368		    (void *)searchpath);
1369		if (status == FRU_WALK_TERMINATE) {
1370			status = FRU_SUCCESS;
1371		} else if (status == FRU_SUCCESS) {
1372			error(gettext("\"%s\" not found\n"), searchpath);
1373			return (1);
1374		}
1375	}
1376
1377	if (status != FRU_SUCCESS)
1378		error(gettext("Error processing FRU tree:  %s\n"),
1379		    fru_strerror(status));
1380
1381	return (((status == FRU_SUCCESS) && (saved_status == 0)) ? 0 : 1);
1382}
1383