cfga_cvt.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 1999, 2001-2002 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include "cfga_scsi.h"
typedef struct {
char *dyncomp;
char *devlink;
int l_errno;
scfga_ret_t ret;
} dyn_t;
typedef struct {
scfga_recur_t (*devlink_to_dyncomp_p)(dyn_t *dyntp);
scfga_recur_t (*dyncomp_to_devlink_p)(dyn_t *dyntp);
} dynrules_t;
typedef struct {
dyn_t *dynp;
dynrules_t *rule_array;
int nrules;
} dyncvt_t;
typedef struct {
const char *hba_phys;
const char *dyncomp;
char *path;
int l_errno;
scfga_ret_t ret;
} devpath_t;
/* Function prototypes */
static int drv_to_hba_logid(di_node_t node, di_minor_t minor, void *arg);
static scfga_ret_t drv_dyn_to_devpath(const char *hba_phys,
const char *dyncomp, char **pathpp, int *l_errnop);
static int do_drv_dyn_to_devpath(di_node_t node, void *arg);
static scfga_ret_t devlink_dyn_to_devpath(const char *hba_phys,
const char *dyncomp, char **pathpp, int *l_errnop);
static scfga_recur_t disk_dyncomp_to_devlink(dyn_t *dyntp);
static scfga_recur_t tape_dyncomp_to_devlink(dyn_t *dyntp);
static scfga_recur_t def_dyncomp_to_devlink(dyn_t *dyntp);
static scfga_ret_t devlink_to_dyncomp(char *devlink,
char **dyncompp, int *l_errnop);
static scfga_recur_t disk_devlink_to_dyncomp(dyn_t *dyntp);
static scfga_recur_t tape_devlink_to_dyncomp(dyn_t *dyntp);
static scfga_recur_t def_devlink_to_dyncomp(dyn_t *dyntp);
static scfga_ret_t drv_to_dyncomp(di_node_t node, const char *phys,
char **dyncompp, int *l_errnop);
static scfga_ret_t get_hba_devlink(const char *hba_phys,
char **hba_logpp, int *l_errnop);
/* Globals */
/*
* Rules for converting between a devlink and logical ap_id and vice-versa
* The default rules must be the last entry.
*/
static dynrules_t dyncvt_rules[] = {
{disk_devlink_to_dyncomp, disk_dyncomp_to_devlink},
{tape_devlink_to_dyncomp, tape_dyncomp_to_devlink},
{def_devlink_to_dyncomp, def_dyncomp_to_devlink}
};
#define N_DYNRULES (sizeof (dyncvt_rules)/sizeof (dyncvt_rules[0]))
/*
* Numbering of disk slices is assumed to be 0 through n - 1
*/
typedef struct {
char *prefix;
int nslices;
} slice_t;
static slice_t disk_slices[] = {
{"s", 16},
{"p", 5},
};
#define N_SLICE_TYPES (sizeof (disk_slices) / sizeof (disk_slices[0]))
static const char *tape_modes[] = {
"",
"b", "bn",
"c", "cb", "cbn", "cn",
"h", "hb", "hbn", "hn",
"l", "lb", "lbn", "ln",
"m", "mb", "mbn", "mn",
"n",
"u", "ub", "ubn", "un"
};
#define N_TAPE_MODES (sizeof (tape_modes) / sizeof (tape_modes[0]))
/* Various conversions routines */
/*
* Generates the HBA logical ap_id from physical ap_id.
*/
scfga_ret_t
make_hba_logid(const char *hba_phys, char **hba_logpp, int *l_errnop)
{
walkarg_t u;
pathm_t pmt = {NULL};
scfga_ret_t ret;
if (*hba_logpp != NULL) {
return (SCFGA_ERR);
}
/* A devlink for the HBA may or may not exist */
if (get_hba_devlink(hba_phys, hba_logpp, l_errnop) == SCFGA_OK) {
assert(*hba_logpp != NULL);
return (SCFGA_OK);
}
/*
* No devlink based logical ap_id.
* Try driver name and instance number.
*/
u.minor_args.nodetype = DDI_NT_SCSI_ATTACHMENT_POINT;
u.minor_args.fcn = drv_to_hba_logid;
pmt.phys = (char *)hba_phys;
pmt.ret = SCFGA_APID_NOEXIST;
errno = 0;
ret = walk_tree(pmt.phys, &pmt, DINFOMINOR | DINFOPROP, &u,
SCFGA_WALK_MINOR, &pmt.l_errno);
if (ret == SCFGA_OK && (ret = pmt.ret) == SCFGA_OK) {
assert(pmt.log != NULL);
*hba_logpp = pmt.log;
return (SCFGA_OK);
}
/* failed to create logical ap_id */
if (pmt.log != NULL) {
S_FREE(pmt.log);
}
*l_errnop = pmt.l_errno;
return (ret);
}
static scfga_ret_t
get_hba_devlink(const char *hba_phys, char **hba_logpp, int *l_errnop)
{
size_t len;
scfga_ret_t ret;
int match_minor = 1;
ret = physpath_to_devlink((char *)hba_phys, hba_logpp,
l_errnop, match_minor);
if (ret != SCFGA_OK) {
return (ret);
}
assert(*hba_logpp != NULL);
/* Remove the "/dev/cfg/" prefix */
len = strlen(CFGA_DEV_DIR SLASH);
(void) memmove(*hba_logpp, *hba_logpp + len,
strlen(*hba_logpp + len) + 1);
return (SCFGA_OK);
}
/* Make logical name for HBA based on driver and instance */
static int
drv_to_hba_logid(di_node_t node, di_minor_t minor, void *arg)
{
int inst;
char *drv, *mn, *log;
pathm_t *ptp;
const size_t loglen = MAXPATHLEN;
ptp = (pathm_t *)arg;
errno = 0;
mn = di_minor_name(minor);
drv = di_driver_name(node);
inst = di_instance(node);
log = calloc(1, loglen);
if (mn != NULL && drv != NULL && inst != -1 && log != NULL) {
/* Count does not include terminating NULL */
if (snprintf(log, loglen, "%s%d:%s", drv, inst, mn) < loglen) {
ptp->ret = SCFGA_OK;
ptp->log = log;
return (DI_WALK_TERMINATE);
}
}
S_FREE(log);
return (DI_WALK_CONTINUE);
}
/*
* Given a bus or device ap_id <hba_phys, dyncomp>, returns the physical
* path in pathpp.
* Returns: SCFGA_APID_NOEXIST if the path does not exist.
*/
scfga_ret_t
apid_to_path(
const char *hba_phys,
const char *dyncomp,
char **pathpp,
int *l_errnop)
{
scfga_ret_t ret;
if (*pathpp != NULL) {
return (SCFGA_LIB_ERR);
}
/* If a bus, the physical ap_id is the physical path */
if (dyncomp == NULL) {
if ((*pathpp = strdup(hba_phys)) == NULL) {
*l_errnop = errno;
return (SCFGA_LIB_ERR);
}
return (SCFGA_OK);
}
/* Dynamic component exists, we have a device */
/*
* If the dynamic component has a '/', it was derived from a devlink
* Else it was derived from driver name and instance number.
*/
if (strchr(dyncomp, '/') != NULL) {
ret = devlink_dyn_to_devpath(hba_phys, dyncomp, pathpp,
l_errnop);
} else {
ret = drv_dyn_to_devpath(hba_phys, dyncomp, pathpp, l_errnop);
}
assert(ret != SCFGA_OK || *pathpp != NULL);
return (ret);
}
static scfga_ret_t
drv_dyn_to_devpath(
const char *hba_phys,
const char *dyncomp,
char **pathpp,
int *l_errnop)
{
walkarg_t u;
devpath_t dpt = {NULL};
scfga_ret_t ret;
/* A device MUST have a dynamic component */
if (dyncomp == NULL || *pathpp != NULL) {
return (SCFGA_LIB_ERR);
}
u.node_args.flags = DI_WALK_CLDFIRST;
u.node_args.fcn = do_drv_dyn_to_devpath;
dpt.hba_phys = hba_phys;
dpt.dyncomp = dyncomp;
dpt.ret = SCFGA_APID_NOEXIST;
ret = walk_tree(hba_phys, &dpt, DINFOCPYALL, &u,
SCFGA_WALK_NODE, &dpt.l_errno);
if (ret == SCFGA_OK && (ret = dpt.ret) == SCFGA_OK) {
assert(dpt.path != NULL);
*pathpp = dpt.path;
return (SCFGA_OK);
}
if (dpt.path != NULL) {
S_FREE(dpt.path);
}
*l_errnop = dpt.l_errno;
return (ret);
}
/* Converts a driver and instance number based logid into a physical path */
static int
do_drv_dyn_to_devpath(di_node_t node, void *arg)
{
int inst, rv, match_minor;
devpath_t *dptp;
char *physpath, *drv;
char *drvinst, *devpath;
const size_t drvlen = MAXPATHLEN;
size_t devlen;
dptp = (devpath_t *)arg;
assert(dptp->hba_phys != NULL && dptp->dyncomp != NULL);
assert(dptp->path == NULL);
/*
* Skip stub nodes
*/
if (IS_STUB_NODE(node)) {
return (DI_WALK_CONTINUE);
}
errno = 0;
drv = di_driver_name(node);
inst = di_instance(node);
physpath = di_devfs_path(node);
if (drv == NULL || inst == -1 || physpath == NULL) {
rv = DI_WALK_CONTINUE;
goto out;
}
devlen = strlen(DEVICES_DIR) + strlen(physpath) + 1;
devpath = calloc(1, devlen);
drvinst = calloc(1, drvlen);
if (devpath == NULL || drvinst == NULL) {
dptp->l_errno = errno;
dptp->ret = SCFGA_LIB_ERR;
rv = DI_WALK_TERMINATE;
goto out;
}
(void) snprintf(drvinst, drvlen, "%s%d", drv, inst);
/* Create the physical path */
(void) snprintf(devpath, devlen, "%s%s", DEVICES_DIR, physpath);
/* Skip node if it is the HBA */
match_minor = 0;
if (!dev_cmp(dptp->hba_phys, devpath, match_minor)) {
rv = DI_WALK_CONTINUE;
goto out;
}
/* Compare the base and dynamic components */
if (!hba_dev_cmp(dptp->hba_phys, devpath) &&
strcmp(dptp->dyncomp, drvinst) == 0) {
dptp->ret = SCFGA_OK;
dptp->path = devpath;
rv = DI_WALK_TERMINATE;
} else {
rv = DI_WALK_CONTINUE;
}
/*FALLTHRU*/
out:
S_FREE(drvinst);
if (physpath != NULL) di_devfs_path_free(physpath);
if (dptp->ret != SCFGA_OK) S_FREE(devpath);
return (rv);
}
/* readlink wrapper to ensure proper null termination of the results */
static int
s_readlink(char *link, char *buf, int len)
{
int count;
count = readlink(link, buf, len - 1);
if (count != -1)
buf[count] = '\0';
return (count);
}
/* Converts a devlink based dynamic component to a path */
static scfga_ret_t
devlink_dyn_to_devpath(
const char *hba_phys,
const char *dyncomp,
char **pathpp,
int *l_errnop)
{
dyn_t dynt = {NULL};
int i;
scfga_ret_t ret;
char buf[PATH_MAX], *path;
if (*pathpp != NULL) {
return (SCFGA_LIB_ERR);
}
/* Convert the dynamic component to the corresponding devlink */
dynt.dyncomp = (char *)dyncomp;
dynt.ret = SCFGA_APID_NOEXIST;
for (i = 0; i < N_DYNRULES; i++) {
if (dyncvt_rules[i].dyncomp_to_devlink_p(&dynt)
!= SCFGA_CONTINUE) {
break;
}
}
if (i >= N_DYNRULES) {
dynt.ret = SCFGA_APID_NOEXIST;
}
if (dynt.ret != SCFGA_OK) {
/* No symlink or error */
return (dynt.ret);
}
assert(dynt.devlink != NULL);
/*
* Follow devlink to get the physical path
* Note: Do not use realpath(). It will stat() device
* and stat() fails under devfs if device is offline.
*/
errno = 0;
if ((s_readlink(dynt.devlink, buf, PATH_MAX) == -1) ||
((path = strstr(buf, "/devices/")) == NULL) ||
((*pathpp = strdup(path)) == NULL)) {
*l_errnop = errno;
ret = SCFGA_LIB_ERR;
goto out;
}
/* Compare base components as well */
if (!hba_dev_cmp(hba_phys, path)) {
ret = SCFGA_OK;
} else {
/* Mismatched base and dynamic component */
*l_errnop = 0;
ret = SCFGA_APID_NOEXIST;
}
/*FALLTHRU*/
out:
S_FREE(dynt.devlink);
if (ret != SCFGA_OK) S_FREE(*pathpp);
return (ret);
}
scfga_ret_t
make_dyncomp(
di_node_t node,
const char *physpath,
char **dyncompp,
int *l_errnop)
{
char *devlink = NULL;
scfga_ret_t ret;
di_minor_t minor;
char *path;
char pathbuf[MAXPATHLEN];
int match_minor;
if (*dyncompp != NULL) {
return (SCFGA_LIB_ERR);
}
/* tag on minor name */
minor = di_minor_next(node, DI_MINOR_NIL);
if (minor == DI_MINOR_NIL) {
match_minor = 0;
path = (char *)physpath;
} else {
match_minor = 1;
snprintf(pathbuf, MAXPATHLEN, "%s:%s", physpath,
di_minor_name(minor));
path = pathbuf;
}
/* Get the corresponding devlink from the physical path */
ret = physpath_to_devlink(path, &devlink, l_errnop, match_minor);
if (ret == SCFGA_OK) {
assert(devlink != NULL);
/* Create dynamic component. */
ret = devlink_to_dyncomp(devlink, dyncompp, l_errnop);
S_FREE(devlink);
if (ret == SCFGA_OK) {
assert(*dyncompp != NULL);
return (SCFGA_OK);
}
/*
* Failed to get devlink based dynamic component.
* Try driver and instance
*/
}
ret = drv_to_dyncomp(node, physpath, dyncompp, l_errnop);
assert(ret != SCFGA_OK || *dyncompp != NULL);
return (ret);
}
/*ARGSUSED*/
static scfga_ret_t
drv_to_dyncomp(di_node_t node, const char *phys, char **dyncompp, int *l_errnop)
{
char *drv;
int inst;
const int dynlen = MAXPATHLEN;
scfga_ret_t ret;
*l_errnop = 0;
if ((*dyncompp = calloc(1, dynlen)) == NULL) {
*l_errnop = errno;
return (SCFGA_LIB_ERR);
}
drv = di_driver_name(node);
inst = di_instance(node);
if (drv != NULL && inst != -1) {
if (snprintf(*dyncompp, dynlen, "%s%d", drv, inst) < dynlen) {
return (SCFGA_OK);
} else {
ret = SCFGA_LIB_ERR;
}
} else {
ret = SCFGA_APID_NOEXIST;
}
S_FREE(*dyncompp);
return (ret);
}
/* Get a dynamic component from a physical path if possible */
static scfga_ret_t
devlink_to_dyncomp(char *devlink, char **dyncompp, int *l_errnop)
{
int i;
dyn_t dynt = {NULL};
*l_errnop = 0;
if (*dyncompp != NULL) {
return (SCFGA_LIB_ERR);
}
/* Convert devlink to dynamic component */
dynt.devlink = devlink;
dynt.ret = SCFGA_APID_NOEXIST;
for (i = 0; i < N_DYNRULES; i++) {
if (dyncvt_rules[i].devlink_to_dyncomp_p(&dynt)
!= SCFGA_CONTINUE) {
break;
}
}
if (i >= N_DYNRULES) {
dynt.ret = SCFGA_APID_NOEXIST;
}
if (dynt.ret == SCFGA_OK) {
assert(dynt.dyncomp != NULL);
*dyncompp = dynt.dyncomp;
}
return (dynt.ret);
}
/* For disks remove partition information, (s or p) */
static scfga_recur_t
disk_devlink_to_dyncomp(dyn_t *dyntp)
{
char *cp = NULL, *cp1 = NULL;
assert(dyntp->devlink != NULL);
dyntp->l_errno = 0;
if (dyntp->dyncomp != NULL) {
goto lib_err;
}
/* Check if a disk devlink */
if (strncmp(dyntp->devlink, DEV_DSK SLASH, strlen(DEV_DSK SLASH)) &&
strncmp(dyntp->devlink, DEV_RDSK SLASH, strlen(DEV_RDSK SLASH))) {
return (SCFGA_CONTINUE);
}
cp = dyntp->devlink + strlen(DEV_DIR SLASH);
if ((dyntp->dyncomp = strdup(cp)) == NULL) {
dyntp->l_errno = errno;
goto lib_err;
}
/* Get the leaf component from dsk/cXtYdZsN */
cp1 = strrchr(dyntp->dyncomp, '/');
/* Blank out partition information */
dyntp->ret = SCFGA_OK;
if ((cp = strchr(cp1 + 1, 's')) != NULL) {
*cp = '\0';
} else if ((cp = strchr(cp1 + 1, 'p')) != NULL) {
*cp = '\0';
} else {
S_FREE(dyntp->dyncomp);
dyntp->ret = SCFGA_ERR;
}
return (SCFGA_TERMINATE);
lib_err:
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}
static scfga_recur_t
disk_dyncomp_to_devlink(dyn_t *dyntp)
{
char buf[MAXPATHLEN], *cp = NULL;
int i, j;
size_t len;
struct stat sbuf;
assert(dyntp->dyncomp != NULL);
dyntp->l_errno = 0;
if (dyntp->devlink != NULL) {
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}
/* A disk link can only be from DEV_DSK (ignore /dev/rdsk) */
if (strncmp(dyntp->dyncomp, DSK_DIR SLASH, strlen(DSK_DIR SLASH)) != 0)
return (SCFGA_CONTINUE); /* not a disk link */
(void) snprintf(buf, sizeof (buf), "%s%s", DEV_DIR SLASH,
dyntp->dyncomp);
len = strlen(buf);
cp = buf + len;
len = sizeof (buf) - len;
for (i = 0; i < N_SLICE_TYPES; i++) {
for (j = 0; j < disk_slices[i].nslices; j++) {
if (snprintf(cp, len, "%s%d", disk_slices[i].prefix, j)
>= len) {
continue;
}
if (lstat(buf, &sbuf) != -1 && S_ISLNK(sbuf.st_mode)) {
if ((dyntp->devlink = strdup(buf)) == NULL) {
dyntp->l_errno = errno;
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}
dyntp->ret = SCFGA_OK;
return (SCFGA_TERMINATE);
}
}
}
dyntp->ret = SCFGA_APID_NOEXIST;
return (SCFGA_TERMINATE);
}
/* For tapes, remove mode(minor) information from link */
static scfga_recur_t
tape_devlink_to_dyncomp(dyn_t *dyntp)
{
char *cp = NULL;
assert(dyntp->devlink != NULL);
dyntp->l_errno = 0;
if (dyntp->dyncomp != NULL) {
goto lib_err;
}
if (strncmp(dyntp->devlink, DEV_RMT SLASH, strlen(DEV_RMT SLASH))) {
return (SCFGA_CONTINUE); /* not a tape */
}
cp = dyntp->devlink + strlen(DEV_DIR SLASH);
if ((dyntp->dyncomp = strdup(cp)) == NULL) {
dyntp->l_errno = errno;
goto lib_err;
}
/* Get the leaf component from rmt/xyz */
cp = strrchr(dyntp->dyncomp, '/');
/* Remove the mode part */
while (isdigit(*(++cp)));
*cp = '\0';
dyntp->ret = SCFGA_OK;
return (SCFGA_TERMINATE);
lib_err:
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}
static scfga_recur_t
tape_dyncomp_to_devlink(dyn_t *dyntp)
{
char buf[MAXPATHLEN], *cp = NULL;
int i;
size_t len = 0;
struct stat sbuf;
assert(dyntp->dyncomp != NULL);
dyntp->l_errno = 0;
if (dyntp->devlink != NULL) {
goto lib_err;
}
if (strncmp(dyntp->dyncomp, RMT_DIR SLASH, strlen(RMT_DIR SLASH))) {
return (SCFGA_CONTINUE); /* not a tape */
}
/* A tape device */
(void) snprintf(buf, sizeof (buf), "%s%s", DEV_DIR SLASH,
dyntp->dyncomp);
len = strlen(buf);
cp = buf + len;
len = sizeof (buf) - len;
for (i = 0; i < N_TAPE_MODES; i++) {
(void) snprintf(cp, len, "%s", tape_modes[i]);
if (lstat(buf, &sbuf) != -1 && S_ISLNK(sbuf.st_mode)) {
if ((dyntp->devlink = strdup(buf)) == NULL) {
dyntp->l_errno = errno;
goto lib_err;
}
dyntp->ret = SCFGA_OK;
return (SCFGA_TERMINATE);
}
}
dyntp->ret = SCFGA_APID_NOEXIST;
return (SCFGA_TERMINATE);
lib_err:
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}
/*
* Default rules
*/
static scfga_recur_t
def_devlink_to_dyncomp(dyn_t *dyntp)
{
size_t len = 0;
char *cp = NULL;
assert(dyntp->devlink != NULL);
dyntp->l_errno = 0;
if (dyntp->dyncomp != NULL) {
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}
/* Is it a link in DEV_DIR directory ? */
len = strlen(DEV_DIR SLASH);
if (strncmp(dyntp->devlink, DEV_DIR SLASH, len)) {
return (SCFGA_CONTINUE);
}
/* Check if this is a top level devlink */
if (strchr(dyntp->devlink + len, '/') != NULL) {
/* not top level - Remove DEV_DIR SLASH prefix */
cp = dyntp->devlink + len;
} else {
/* top level, leave DEV_DIR SLASH part in */
cp = dyntp->devlink;
}
if ((dyntp->dyncomp = strdup(cp)) == NULL) {
dyntp->l_errno = errno;
dyntp->ret = SCFGA_LIB_ERR;
} else {
dyntp->ret = SCFGA_OK;
}
return (SCFGA_TERMINATE);
}
static scfga_recur_t
def_dyncomp_to_devlink(dyn_t *dyntp)
{
struct stat sbuf;
int top;
size_t prelen, linklen;
assert(dyntp->dyncomp != NULL);
dyntp->l_errno = 0;
if (dyntp->devlink != NULL) {
goto lib_err;
}
prelen = strlen(DEV_DIR SLASH);
linklen = strlen(dyntp->dyncomp) + 1;
/*
* Check if the dynamic component was derived from a top level entry
* in "/dev"
*/
if (strncmp(dyntp->dyncomp, DEV_DIR SLASH, prelen) == 0) {
top = 1;
} else if (*dyntp->dyncomp != '/' && linklen > 1 &&
strchr(dyntp->dyncomp + 1, '/') != NULL) {
top = 0;
linklen += prelen; /* The "/dev/" needs to be prepended */
} else {
/* Not a dynamic component we handle */
return (SCFGA_CONTINUE);
}
if ((dyntp->devlink = calloc(1, linklen)) == NULL) {
dyntp->l_errno = errno;
goto lib_err;
}
*dyntp->devlink = '\0';
if (!top) {
(void) strcpy(dyntp->devlink, DEV_DIR SLASH);
}
(void) strcat(dyntp->devlink, dyntp->dyncomp);
if (lstat(dyntp->devlink, &sbuf) != -1 && S_ISLNK(sbuf.st_mode)) {
dyntp->ret = SCFGA_OK;
return (SCFGA_TERMINATE);
}
S_FREE(dyntp->devlink);
return (SCFGA_CONTINUE);
lib_err:
dyntp->ret = SCFGA_LIB_ERR;
return (SCFGA_TERMINATE);
}