/*
* 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
* 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
*/
/*
*/
#include "libdevinfo.h"
#include "device_info.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <regex.h>
#include <strings.h>
#include <libsysevent.h>
/* define aspects of record fields */
#define F_product_id 0
#define CRO_REC_FNM { \
}
/* implementation of a field, with indexed values, in a record */
typedef struct rec_f {
} rec_f_t;
/* implementation of opaque interface datatypes */
struct di_cro_rec {
void *r_priv;
};
struct di_cro_reca {
int ra_nrec;
};
struct di_cro_hdl {
int h_nrec;
char *h_db_file;
char *h_date;
char *h_server_id;
char *h_product_id;
char *h_chassis_id;
char *h_fletcher;
};
struct di_cromk_hdl {
int mk_nrec;
char *mk_date;
char *mk_server_id;
char *mk_product_id;
char *mk_chassis_id;
};
#define RF0_REASSIGN(r, f, v) { \
if (RF0(r, f)) \
RF0(r, f) = v; \
}
/* ARGSUSED */
static int
{
int i, j;
for (i = 0; i < CRO_REC_FN; i++) {
}
}
}
free(r);
return (DI_WALK_CONTINUE);
}
/*
* NOTE: At some point, we may need to improve clean. At that time, consider:
*
* o topo_cleanup_auth_str
* o escape via http://en.wikipedia.org/wiki/URI
* o compact white space.
*/
char *
{
char *s = s0;
if (s == NULL)
return (NULL);
/* for now, simple clean-in-place */
for (; s && *s; s++) {
if (*s != (*s & 0x7F))
*s = *s & 0x7F;
if (*s == ' ')
*s = '_';
if (doslash && (*s == '/'))
*s = '_';
if (doperiod && (*s == '.'))
*s = '_';
}
return (s0);
}
/* add "/devices/" prefix (if needed) */
static char *
{
char *s = s0;
char *ps;
int len;
if ((s == NULL) || (*s == 0))
return (s0);
return (s0);
while (*s && (*s == '/'))
s++;
if (*s == 0)
return (s0);
return (s0);
return (ps);
}
/*
* place. This interface is used both to form the 'standard' devchassis_path
* representation the CRO database, and by devchassisd(1M) to construct the
* namespace so that the 'standard' form references resolve (this includes
* the 'raw' path form and any 'alias' symlinks).
*
* NOTE: internal is only defined for DI_CRODC_REC_LINKINFO_STD.
*/
int
{
int len;
char *s;
if (ppath)
if (plink)
switch (info) {
/*
* Form the 'standard' devchassis_path:
*
* [/<occupant_type>[<occupant_instance>]]
*
* using <alias_id> when possible (unless caller is asking
* for RAW). NOTE: if the <alias_id> is "SYS" then we have
* already added the well-known SYS prefix to the
* <receptacle_name>, so don't add the <alias_id>.
*
* The 'raw' form has not symlinks - so it inserts
* a alias specified, and does not use the alias in the
* path.
*
* Need record with product_id, chassis_id, and
* receptacle_name to do anything.
*/
if (!(ppath && r &&
RF0_HASINFO(r, product_id) &&
RF0_HASINFO(r, chassis_id) &&
RF0_HASINFO(r, receptacle_name)))
goto fail;
if ((info == DI_CRODC_REC_LINKINFO_RAW) &&
RF0_HASINFO(r, alias_id))
if ((info == DI_CRODC_REC_LINKINFO_STD) &&
RF0_HASINFO(r, alias_id)) {
if (!internal)
} else
if (RF0_HASINFO(r, occupant_type))
if (RF0_HASINFO(r, occupant_instance))
goto fail;
s += strlen(s);
if ((info == DI_CRODC_REC_LINKINFO_RAW) &&
RF0_HASINFO(r, alias_id)) {
s += strlen(s);
}
if ((info == DI_CRODC_REC_LINKINFO_STD) &&
RF0_HASINFO(r, alias_id)) {
if (!internal)
RF0(r, receptacle_name));
else
RF0(r, receptacle_name));
} else
RF0(r, receptacle_name));
s += strlen(s);
if (RF0_HASINFO(r, occupant_type)) {
s += strlen(s);
}
if (RF0_HASINFO(r, occupant_instance)) {
RF0(r, occupant_instance));
s += strlen(s);
}
goto fail;
}
break;
/*
* Return information to construct a alias based link.
* We need a alias, and the caller needs to be interested
* in links to return a result. NOTE: the well-known SYS
*/
if (!(ppath && r &&
RF0_HASINFO(r, product_id) &&
RF0_HASINFO(r, chassis_id) &&
RF0_HASINFO(r, alias_id) &&
goto fail;
goto fail;
goto fail;
break;
goto fail;
goto fail;
break;
/*
* Return information to construct a well-knonw SYS link.
* Caller needs to be interested in links and provide
* h->h_product_id and h->h_chassis_id.
*/
h->h_product_id && h->h_chassis_id))
goto fail;
goto fail;
goto fail;
break;
default:
goto fail;
}
if (path)
if (plink)
return (1); /* success */
if (link)
return (0);
}
/* Compare two records for qsort */
static int
{
char *p1;
char *p2;
char *b1;
char *b2;
int n1;
int n2;
/*
* NOTE: this code matches that used by format(1M).
*
* Put internal "/SYS/" disks first, and among internal
* disks, put "/BOOT" disks first.
*/
return (-1);
return (1);
}
return (-1);
return (1);
for (;;) {
break;
}
break;
} else {
s1++;
s2++;
}
}
}
return (-1);
return (1);
return (0);
}
static void
{
di_cro_rec_t r;
/* Allocate memory for array of pointers to records. */
return;
/* Form array of pointers to records. */
*rap = r;
/* Sort the array. */
/* Rebuild list of records from sorted array. */
*rp = r;
}
}
/* Enhance an record before it gets written to the database */
/*ARGSUSED*/
static void
{
int i, j;
char *pc;
int len;
char *devchassis_path;
int internal = 0;
char *new_alias_id;
/* clean up the fields */
for (i = 0; i < CRO_REC_FN; i++)
/* clean '/' from single-component path fields */
internal = 1;
/* look for <product_id>.<chassis_id> alias */
if (!RF0_HASINFO(r, alias_id) &&
}
if (pca_r)
else
/* If no alias, use well-known SYS alias for internal */
if (alias_id) {
} /* else strdup err, not much to do with that err */
}
}
/* If internal well-known SYS prefix is missing, add it. */
if (RF0_HASINFO(r, receptacle_name)) {
while (*rn == '/')
rn++;
} else
}
}
if (!RF0_HASINFO(r, devchassis_path) &&
}
/* ensure "/devices/" prefix */
}
/*
* This is a local copy of the fletcher_4 algorithm used by ZFS.
* This code is cribbed from usr/src/common/zfs/zfs_fletcher.c.
* If this code is ever available in a 'convient' non-zfs library, like
* libmd, then the implementation here should be removed.
*/
static char *
{
uint64_t a, b, c, d;
int fletcher_str_len;
char *fletcher;
a += ip[0];
b += a;
c += b;
d += c;
}
/* format fletcher_4 the same way ZFS does */
if (fletcher)
"%llx:%llx:%llx:%llx", a, b, c, d);
return (fletcher);
}
/*ARGSUSED*/
static int
{
di_cro_rec_t r;
int i, j;
char *fletcher_c;
/*
* Since topo snapshots take a long time, take the ProductChassisAlias
* snapshot here instead of in di_cromk_init.
*/
/* Allocate and initialize the db_nvl nvlist (the entire db file). */
goto fail;
goto empty;
/* Allocate array of nvlist pointers for db records */
goto fail;
/* Walk the mk records, "enhancing" the records */
/* Sort the enhanced records */
/* Walk the mk records, allocating and initializing record nvlist */
goto fail;
for (j = 0; j < CRO_REC_FN; j++) {
if (nvlist_add_string_array(ra_nvl[i],
break;
}
if (j < CRO_REC_FN)
break;
break;
}
/* Sanity test - we must be at the end of the list. */
if (r)
goto fail;
/* Pack all the records so we can compute their fletcher checksum. */
NV_ENCODE_NATIVE, 0))
goto fail;
goto fail;
/*
* Get fletcher from a header-only snapshot of the current database to
* see if we need to update the cro database.
*/
if (dch) {
/*
* With fletcher already in sync, we are not going to
* signal devchassisd, but we still have di_pca_sync
* waiting... so indicate finish here.
*/
(void) sysevent_post_event(EC_CRO,
rval = 0; /* success */
goto out; /* no database update needed */
}
}
/* Add the checksum and all the records to the database nvlist. */
goto fail;
/* Pack the database nvlist and write it to the database file. */
goto fail;
goto fail;
goto fail;
db_fd = -1;
/* rotate files to put new one in place */
/* Signal cro database update with sysevent */
goto fail;
if (db_buf)
if (fletcher)
if (fletcher_buf)
if (fletcher_nvl)
if (ra_nvl) {
nvlist_free(ra_nvl[i]);
}
if (db_nvl)
return (rval);
}
static int
{
int version;
int magic;
int ra_nrec;
char **s;
int i, j, k;
h->h_nrec = 0;
goto out;
goto out;
goto out;
goto out;
goto out;
/* required with specific value */
(version != DB_VERSION_VALUE) ||
(magic != DB_MAGIC_VALUE) ||
(h->h_nrec < 0))
goto out;
/* required */
goto out;
/* optional */
/* The header outlives the nvlist, so dup strings. */
if (date)
if (server_id)
if (product_id)
if (chassis_id)
if (fletcher)
(server_id && !h->h_server_id) ||
(product_id && !h->h_product_id) ||
(chassis_id && !h->h_chassis_id) ||
(fletcher && !h->h_fletcher))
goto out; /* memory alloc problem above */
goto empty;
goto out;
/* Form in-memory records from nvlist (preserve file order) */
h->h_nrec = 0;
goto out;
*rp = r;
for (j = 0; j < CRO_REC_FN; j++) {
/* ensure null for non-existent fields */
if (nvlist_lookup_string_array(ra_nvl[i],
cro_rec_fnm[j], &s, &nele)) {
s = NULL;
nele = 0;
}
if (s && nele) {
/* Error with calloc */
goto out;
}
for (k = 0; k < nele; k++, s++) {
strdup(*s ? *s : "")) ==
NULL) {
/* Error with strdup */
goto out;
}
}
} else {
/* Error with calloc */
goto out;
} else {
NULL)
goto out;
}
}
}
(void) nvlist_lookup_uint32(ra_nvl[i],
DB_REC_FLAG, &r->r_rec_flag);
}
if (db_nvl)
if (db_buf)
return (rval);
}
/*ARGSUSED*/
{
di_cro_hdl_t h = NULL;
goto out;
== NULL) {
/* Error with strdup */
free(h);
h = NULL;
goto out;
}
if (_crodb_read(h, flags)) {
di_cro_fini(h);
h = NULL;
}
out: return (h);
}
{
}
char *
{
return (h ? h->h_fletcher : NULL);
}
char *
{
}
char *
{
return (h ? h->h_server_id : NULL);
}
char *
{
return (h ? h->h_product_id : NULL);
}
char *
{
return (h ? h->h_chassis_id : NULL);
}
void
{
if (h == NULL)
return;
if (h->h_db_file)
if (h->h_date)
if (h->h_server_id)
free(h->h_server_id);
if (h->h_product_id)
free(h->h_product_id);
if (h->h_chassis_id)
free(h->h_chassis_id);
if (h->h_fletcher)
free(h->h_fletcher);
free(h);
}
/* check to see if this record is from in internal device */
static int
{
int internal = 0;
if (h_product_id && h_chassis_id &&
internal = 1;
}
return (internal);
}
/*ARGSUSED*/
static int
{
char *h_product_id;
char *h_chassis_id;
int i, j, m;
int internal;
if (h == NULL)
return (NULL);
for (i = 0; i < CRO_REC_FN; i++)
break;
if (i < CRO_REC_FN) {
/* regfree what worked... */
for (j = 0; j < i; j++)
return (-1);
}
if ((r->r_rec_flag & DI_CRO_REC_FLAG_PRIV) &&
!(rec_flag & DI_CRO_REC_FLAG_PRIV))
continue; /* skip private records */
for (i = 0; i < CRO_REC_FN; i++) {
0, NULL, 0) == 0))
m++; /* match */
}
/*
* when a head also has an administered,
* non-"SYS", alias.
*/
if ((i == F_alias_id) && internal &&
0, NULL, 0) == 0))
m++; /* match */
if (m == 0)
break; /* mismatch */
}
}
if (i < CRO_REC_FN)
continue; /* mismatch */
if (wt == DI_WALK_TERMINATE)
break;
}
for (i = 0; i < CRO_REC_FN; i++)
return (0);
}
/* add new record to end of record array */
static int
{
return (DI_WALK_CONTINUE);
} else {
return (DI_WALK_CONTINUE);
}
return (DI_WALK_CONTINUE);
}
char *re_product_id,
char *re_chassis_id,
char *re_alias_id,
char *re_receptacle_name,
char *re_receptacle_type,
char *re_receptacle_fmri,
char *re_occupant_type,
char *re_occupant_instance,
char *re_devchassis_path,
char *re_occupant_devices,
char *re_occupant_paths,
char *re_occupant_compdev,
char *re_occupant_devid,
char *re_occupant_mfg,
char *re_occupant_model,
char *re_occupant_part,
char *re_occupant_serial,
char *re_occupant_firm,
char *re_occupant_misc_1,
char *re_occupant_misc_2,
char *re_occupant_misc_3)
{
int i;
if (h == NULL)
return (NULL);
for (i = 0; i < CRO_REC_FN; i++)
/* Gather RE arguments into array form. */
return (ra);
}
/*
* To protect consumers from any future addition of fields, we provide a
* string-based query mechanism as an alternative to di_cro_reca_create.
* The format of the query string uses JSON-like (json.org) syntax
*
* name : "string" [, name : "string" ]*
*
* for example
*
* char *query = "alias-id:\"SYS\",receptacle-type:\"bay\"";
*
* A NULL query value will return all records.
*/
{
int i;
char *s;
if (h == NULL)
return (NULL);
for (i = 0; i < CRO_REC_FN; i++)
goto walk; /* all records */
return (NULL);
/* parse: [ \t] ns [ \t] : [ \t] "vs" [ \t] [, <repeat>] */
for (s = s0; *s; ) {
if (*s != ':') /* expect ':' */
goto err;
s++;
if (*s != '"') /* expect quoted string */
goto err;
s++;
for (vs = s; *s; s++) {
if ((*s == '\\') && *(s + 1))
s++; /* skip \" in quoted string */
else if (*s == '"')
break;
}
if (*s != '"') /* expect end quoted string */
goto err;
vse = s++; /* end of vs */
if (*s) { /* expect null or ',' */
if (*s == ',')
s++;
else
goto err;
}
*nse = 0; /* terminate ns */
*vse = 0; /* terminate vs */
for (i = 0; i < CRO_REC_FN; i++) {
break;
}
}
/* consider an unsupported field-name an error. */
if (i >= CRO_REC_FN) {
s = ns;
goto err;
}
}
return (ra);
/*
* As a debug aid, we dup the string at the point we are unable
* to parse.
*/
goto done;
}
{
int n;
return (NULL);
if (r == NULL)
break; /* found last record */
n++; /* advance to next */
else
return (NULL); /* end with NULL */
}
void
{
return;
}
/*
* libdevinfo cro snapshot interfaces: record get field functions
*
* Return indexed field value. If index out of bounds, return dflt.
* If morep is non-null, set morep to indicate if another call will
* will retrieve new data.
*/
#define DI_CRO_REC_FGETI(f) \
char *di_cro_rec_fgeti_##f(di_cro_rec_t r, \
{ \
int n; \
char *v; \
\
if (morep) \
*morep = 0; \
if (r == NULL) \
return (dflt); \
if (i >= n) \
return (dflt); \
if (morep && ((i + 1) < n)) \
*morep = 1; \
return ((v && *v) ? v : dflt); \
}
void *
{
if (r == NULL)
return (NULL);
return (r->r_priv);
}
void
{
if (r)
}
/*ARGSUSED*/
{
time_t t;
char *s;
goto out;
/* timestamp is at beginning */
(void) time(&t);
s = ctime(&t);
/* Error with strdup */
}
}
char *product_id,
char *chassis_id,
char *alias_id,
char *receptacle_name,
char *receptacle_type,
char *receptacle_fmri,
char *occupant_type,
char *occupant_instance,
char *devchassis_path,
char **occupant_devices, int n_occupant_devices,
char **occupant_paths, int n_occupant_paths,
char **occupant_compdev, int n_occupant_compdev,
char *occupant_devid,
char *occupant_mfg,
char *occupant_model,
char *occupant_part,
char *occupant_serial,
char *occupant_firm,
char **occupant_misc_1, int n_occupant_misc_1,
char **occupant_misc_2, int n_occupant_misc_2,
char **occupant_misc_3, int n_occupant_misc_3)
{
di_cro_rec_t r;
int i, j;
char **s;
return (NULL);
/* Gather arguments into array form. */
#define R_F_SET1(f) \
#define R_F_SETN(f) \
if (f && n_##f) { \
} else { \
}
/* verify that required fields are specified */
(receptacle_name == NULL))
return (NULL);
return (NULL);
for (i = 0; i < CRO_REC_FN; i++) {
/* Error with calloc, release memory */
(void) _cro_rec_free(r, NULL);
return (NULL);
}
/* Error with strdup, release memory */
(void) _cro_rec_free(r, NULL);
return (NULL);
}
}
}
r->r_rec_flag = rec_flag;
return (r);
}
/*ARGSUSED*/
void
{
return;
if (!(flags & DI_CROMK_END_COMMIT))
goto out;
(void) _cro_rec_free(r, NULL);
}
}
/*
* This interface is called as the process responsible for
* CRO dataset updates is exiting (i.e. fmd(1M)). Since that
* process is no longer around to keep things up to date, move
* the CRO dataset aside (if it exists).
*/
void
{
/* rotate current db file to old (result: no db file) */
}