usr/src/lib/libdiskmgt/common/entry.c
author sjelinek
Thu, 27 Oct 2005 09:01:18 -0700
changeset 767 0aa24dfb5d87
parent 556 5afbbcbc4e13
child 1107 1f78db36dee1
permissions -rw-r--r--
PSARC 2004/776 device checking for fs utilities PSARC 2005/461 Device in use checking environment variables 5084421 libdiskmgt needs to detect in use ZFS data 5085739 remove workaround for bug 4725434 6194015 Device in use checking for Solaris utilities-PSARC/2004/776 6261853 libdiskmgt does not work correctly in all cases on amd64 6268374 libdiskmgt allocates incorrect size for readdir_r() dirent argument 6291309 PSARC/2005/461 - libdiskmgt should enable bypassing of inuse checking 6301815 PSARC/2005/461-Need Sun private libdiskmgt flag for use during install to disable sysevent starting

/*
 * 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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <fcntl.h>
#include <libdevinfo.h>
#include <stdio.h>
#include <sys/sunddi.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <libintl.h>
#include <locale.h>
#include <sys/debug.h>

#include "libdiskmgt.h"
#include "disks_private.h"
#include "partition.h"

extern	char	*getfullblkname();


extern dm_desc_type_t drive_assoc_types[];
extern dm_desc_type_t bus_assoc_types[];
extern dm_desc_type_t controller_assoc_types[];
extern dm_desc_type_t media_assoc_types[];
extern dm_desc_type_t slice_assoc_types[];
extern dm_desc_type_t partition_assoc_types[];
extern dm_desc_type_t path_assoc_types[];
extern dm_desc_type_t alias_assoc_types[];


static dm_descriptor_t *ptr_array_to_desc_array(descriptor_t **ptrs, int *errp);
static descriptor_t **desc_array_to_ptr_array(dm_descriptor_t *da, int *errp);
static int build_usage_string(char *dname, char *by, char *data, char **use,
	int *found, int *errp);

void
dm_free_descriptor(dm_descriptor_t desc)
{
	descriptor_t	*dp;

	if (desc == NULL) {
	    return;
	}
	dp = (descriptor_t *)(uintptr_t)desc;

	cache_wlock();
	cache_free_descriptor(dp);
	cache_unlock();
}

void
dm_free_descriptors(dm_descriptor_t *desc_list)
{
	descriptor_t	**dp;
	int		error;

	if (desc_list == NULL) {
	    return;
	}
	dp = desc_array_to_ptr_array(desc_list, &error);
	if (error != 0) {
	    free(desc_list);
	    return;
	}

	cache_wlock();
	cache_free_descriptors(dp);
	cache_unlock();
}

/*ARGSUSED*/
void
dm_free_name(char *name)
{
	free(name);
}

dm_descriptor_t *
dm_get_associated_descriptors(dm_descriptor_t desc, dm_desc_type_t type,
    int *errp)
{
	descriptor_t **descs = NULL;
	descriptor_t  *dp;


	dp = (descriptor_t *)(uintptr_t)desc;

	cache_wlock();

	if (!cache_is_valid_desc(dp)) {
	    cache_unlock();
	    *errp = EBADF;
	    return (NULL);
	}

	/* verify that the descriptor is still valid */
	if (dp->p.generic == NULL) {
	    cache_unlock();
	    *errp = ENODEV;
	    return (NULL);
	}

	switch (dp->type) {
	case DM_DRIVE:
	    descs = drive_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_BUS:
	    descs = bus_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_CONTROLLER:
	    descs = controller_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_MEDIA:
	    descs = media_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_SLICE:
	    descs = slice_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_PARTITION:
	    descs = partition_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_PATH:
	    descs = path_get_assoc_descriptors(dp, type, errp);
	    break;
	case DM_ALIAS:
	    descs = alias_get_assoc_descriptors(dp, type, errp);
	    break;
	default:
	    *errp = EINVAL;
	    break;
	}

	cache_unlock();

	return (ptr_array_to_desc_array(descs, errp));
}

dm_desc_type_t *
dm_get_associated_types(dm_desc_type_t type)
{
	switch (type) {
	case DM_DRIVE:
	    return (drive_assoc_types);
	case DM_BUS:
	    return (bus_assoc_types);
	case DM_CONTROLLER:
	    return (controller_assoc_types);
	case DM_MEDIA:
	    return (media_assoc_types);
	case DM_SLICE:
	    return (slice_assoc_types);
	case DM_PARTITION:
	    return (partition_assoc_types);
	case DM_PATH:
	    return (path_assoc_types);
	case DM_ALIAS:
	    return (alias_assoc_types);
	}

	return (NULL);
}

nvlist_t *
dm_get_attributes(dm_descriptor_t desc, int *errp)
{
	descriptor_t	*dp;
	nvlist_t	*attrs = NULL;


	dp = (descriptor_t *)(uintptr_t)desc;

	cache_rlock();

	if (!cache_is_valid_desc(dp)) {
	    cache_unlock();
	    *errp = EBADF;
	    return (NULL);
	}

	/* verify that the descriptor is still valid */
	if (dp->p.generic == NULL) {
	    cache_unlock();
	    *errp = ENODEV;
	    return (NULL);
	}

	switch (dp->type) {
	case DM_DRIVE:
	    attrs = drive_get_attributes(dp, errp);
	    break;
	case DM_BUS:
	    attrs = bus_get_attributes(dp, errp);
	    break;
	case DM_CONTROLLER:
	    attrs = controller_get_attributes(dp, errp);
	    break;
	case DM_MEDIA:
	    attrs = media_get_attributes(dp, errp);
	    break;
	case DM_SLICE:
	    attrs = slice_get_attributes(dp, errp);
	    break;
	case DM_PARTITION:
	    attrs = partition_get_attributes(dp, errp);
	    break;
	case DM_PATH:
	    attrs = path_get_attributes(dp, errp);
	    break;
	case DM_ALIAS:
	    attrs = alias_get_attributes(dp, errp);
	    break;
	default:
	    *errp = EINVAL;
	    break;
	}

	cache_unlock();

	return (attrs);
}

dm_descriptor_t
dm_get_descriptor_by_name(dm_desc_type_t desc_type, char *name, int *errp)
{
	dm_descriptor_t desc = NULL;


	cache_wlock();

	switch (desc_type) {
	case DM_DRIVE:
	    desc = (uintptr_t)drive_get_descriptor_by_name(name, errp);
	    break;
	case DM_BUS:
	    desc = (uintptr_t)bus_get_descriptor_by_name(name, errp);
	    break;
	case DM_CONTROLLER:
	    desc = (uintptr_t)controller_get_descriptor_by_name(name,
		errp);
	    break;
	case DM_MEDIA:
	    desc = (uintptr_t)media_get_descriptor_by_name(name, errp);
	    break;
	case DM_SLICE:
	    desc = (uintptr_t)slice_get_descriptor_by_name(name, errp);
	    break;
	case DM_PARTITION:
	    desc = (uintptr_t)partition_get_descriptor_by_name(name,
		errp);
	    break;
	case DM_PATH:
	    desc = (uintptr_t)path_get_descriptor_by_name(name, errp);
	    break;
	case DM_ALIAS:
	    desc = (uintptr_t)alias_get_descriptor_by_name(name, errp);
	    break;
	default:
	    *errp = EINVAL;
	    break;
	}

	cache_unlock();

	return (desc);
}

dm_descriptor_t *
dm_get_descriptors(dm_desc_type_t type, int filter[], int *errp)
{
	descriptor_t **descs = NULL;


	cache_wlock();

	switch (type) {
	case DM_DRIVE:
	    descs = drive_get_descriptors(filter, errp);
	    break;
	case DM_BUS:
	    descs = bus_get_descriptors(filter, errp);
	    break;
	case DM_CONTROLLER:
	    descs = controller_get_descriptors(filter, errp);
	    break;
	case DM_MEDIA:
	    descs = media_get_descriptors(filter, errp);
	    break;
	case DM_SLICE:
	    descs = slice_get_descriptors(filter, errp);
	    break;
	case DM_PARTITION:
	    descs = partition_get_descriptors(filter, errp);
	    break;
	case DM_PATH:
	    descs = path_get_descriptors(filter, errp);
	    break;
	case DM_ALIAS:
	    descs = alias_get_descriptors(filter, errp);
	    break;
	default:
	    *errp = EINVAL;
	    break;
	}

	cache_unlock();

	return (ptr_array_to_desc_array(descs, errp));
}

char *
dm_get_name(dm_descriptor_t desc, int *errp)
{
	descriptor_t	*dp;
	char		*nm = NULL;
	char		*name = NULL;

	dp = (descriptor_t *)(uintptr_t)desc;

	cache_rlock();

	if (!cache_is_valid_desc(dp)) {
	    cache_unlock();
	    *errp = EBADF;
	    return (NULL);
	}

	/* verify that the descriptor is still valid */
	if (dp->p.generic == NULL) {
	    cache_unlock();
	    *errp = ENODEV;
	    return (NULL);
	}

	switch (dp->type) {
	case DM_DRIVE:
	    nm = (drive_get_name(dp));
	    break;
	case DM_BUS:
	    nm = (bus_get_name(dp));
	    break;
	case DM_CONTROLLER:
	    nm = (controller_get_name(dp));
	    break;
	case DM_MEDIA:
	    nm = (media_get_name(dp));
	    break;
	case DM_SLICE:
	    nm = (slice_get_name(dp));
	    break;
	case DM_PARTITION:
	    nm = (partition_get_name(dp));
	    break;
	case DM_PATH:
	    nm = (path_get_name(dp));
	    break;
	case DM_ALIAS:
	    nm = (alias_get_name(dp));
	    break;
	}

	cache_unlock();

	*errp = 0;
	if (nm != NULL) {
	    name = strdup(nm);
	    if (name == NULL) {
		*errp = ENOMEM;
		return (NULL);
	    }
	    return (name);
	}
	return (NULL);
}

nvlist_t *
dm_get_stats(dm_descriptor_t desc, int stat_type, int *errp)
{
	descriptor_t  *dp;
	nvlist_t	*stats = NULL;


	dp = (descriptor_t *)(uintptr_t)desc;

	cache_rlock();

	if (!cache_is_valid_desc(dp)) {
		cache_unlock();
		*errp = EBADF;
		return (NULL);
	}

	/* verify that the descriptor is still valid */
	if (dp->p.generic == NULL) {
		cache_unlock();
		*errp = ENODEV;
		return (NULL);
	}

	switch (dp->type) {
	case DM_DRIVE:
		stats = drive_get_stats(dp, stat_type, errp);
		break;
	case DM_BUS:
		stats = bus_get_stats(dp, stat_type, errp);
		break;
	case DM_CONTROLLER:
		stats = controller_get_stats(dp, stat_type, errp);
		break;
	case DM_MEDIA:
		stats = media_get_stats(dp, stat_type, errp);
		break;
	case DM_SLICE:
		if (stat_type == DM_SLICE_STAT_USE) {
			/*
			 * If NOINUSE_CHECK is set, we do not perform
			 * the in use checking if the user has set stat_type
			 * DM_SLICE_STAT_USE
			 */
			if (getenv("NOINUSE_CHECK") != NULL) {
				stats = NULL;
				break;
			}
		}
		stats = slice_get_stats(dp, stat_type, errp);
		break;
	case DM_PARTITION:
		stats = partition_get_stats(dp, stat_type, errp);
		break;
	case DM_PATH:
		stats = path_get_stats(dp, stat_type, errp);
		break;
	case DM_ALIAS:
		stats = alias_get_stats(dp, stat_type, errp);
		break;
	default:
		*errp = EINVAL;
		break;
	}

	cache_unlock();

	return (stats);
}

dm_desc_type_t
dm_get_type(dm_descriptor_t desc)
{
	descriptor_t  *dp;

	dp = (descriptor_t *)(uintptr_t)desc;

	cache_rlock();

	if (!cache_is_valid_desc(dp)) {
	    cache_unlock();
	    return (-1);
	}

	cache_unlock();

	return (dp->type);
}
/*
 * Returns, via slices paramater, a dm_descriptor_t list of
 * slices for the named disk drive.
 */
void
dm_get_slices(char *drive, dm_descriptor_t **slices, int *errp)
{
	dm_descriptor_t alias;
	dm_descriptor_t	*media;
	dm_descriptor_t *disk;

	*slices = NULL;
	*errp = 0;

	if (drive == NULL) {
		return;
	}

	alias = dm_get_descriptor_by_name(DM_ALIAS, drive, errp);

	/*
	 * Errors must be handled by the caller. The dm_descriptor_t *
	 * values will be NULL if an error occured in these calls.
	 */

	if (alias != NULL) {
		disk = dm_get_associated_descriptors(alias, DM_DRIVE, errp);
		dm_free_descriptor(alias);
		if (disk != NULL) {
			media = dm_get_associated_descriptors(*disk,
			    DM_MEDIA, errp);
			dm_free_descriptors(disk);
			if (media != NULL) {
				*slices = dm_get_associated_descriptors(*media,
				    DM_SLICE, errp);
				dm_free_descriptors(media);
			}
		}
	}
}
/*
 * Convenience function to get slice stats
 */
void
dm_get_slice_stats(char *slice, nvlist_t **dev_stats, int *errp)
{
	dm_descriptor_t	devp;

	*dev_stats = NULL;
	*errp = 0;

	if (slice == NULL) {
		return;
	}

	/*
	 * Errors must be handled by the caller. The dm_descriptor_t *
	 * values will be NULL if an error occured in these calls.
	 */
	devp = dm_get_descriptor_by_name(DM_SLICE, slice, errp);
	if (devp != NULL) {
		*dev_stats = dm_get_stats(devp, DM_SLICE_STAT_USE,
		    errp);
		dm_free_descriptor(devp);
	}
}

/*
 * Returns 'in use' details, if found, about a specific dev_name,
 * based on the caller(who). It is important to note that it is possible
 * for there to be more than one 'in use' statistic regarding a dev_name.
 * The **msg parameter returns a list of 'in use' details. This message
 * is formatted via gettext().
 */
int
dm_inuse(char *dev_name, char **msg, dm_who_type_t who, int *errp)
{
	nvlist_t *dev_stats = NULL;
	char *by, *data;
	nvpair_t *nvwhat = NULL;
	nvpair_t *nvdesc = NULL;
	int	found = 0;
	char	*dname = NULL;

	*errp = 0;
	*msg = NULL;

	dname = getfullblkname(dev_name);
	/*
	 * If we cannot find the block name, we cannot check the device
	 * for in use statistics. So, return found, which is == 0.
	 */
	if (dname == NULL || *dname == '\0') {
		return (found);
	}

	dm_get_slice_stats(dname, &dev_stats, errp);
	if (dev_stats == NULL) {
		/*
		 * If there is an error, but it isn't a no device found error
		 * return the error as recorded. Otherwise, with a full
		 * block name, we might not be able to get the slice
		 * associated, and will get an ENODEV error. For example,
		 * an SVM metadevice will return a value from getfullblkname()
		 * but libdiskmgt won't be able to find this device for
		 * statistics gathering. This is expected and we should not
		 * report errnoneous errors.
		 */
		if (*errp) {
			if (*errp == ENODEV) {
				*errp = 0;
			}
		}
		free(dname);
		return (found);
	}

	for (;;) {

		nvwhat = nvlist_next_nvpair(dev_stats, nvdesc);
		nvdesc = nvlist_next_nvpair(dev_stats, nvwhat);

		/*
		 * End of the list found.
		 */
		if (nvwhat == NULL || nvdesc == NULL) {
			break;
		}
		/*
		 * Otherwise, we check to see if this client(who) cares
		 * about this in use scenario
		 */

		ASSERT(strcmp(nvpair_name(nvwhat), DM_USED_BY) == 0);
		ASSERT(strcmp(nvpair_name(nvdesc), DM_USED_NAME) == 0);
		/*
		 * If we error getting the string value continue on
		 * to the next pair(if there is one)
		 */
		if (nvpair_value_string(nvwhat, &by)) {
			continue;
		}
		if (nvpair_value_string(nvdesc, &data)) {
			continue;
		}

		switch (who) {
			case DM_WHO_MKFS:
				/*
				 * mkfs is not in use for these cases.
				 * All others are in use.
				 */
				if (strcmp(by, DM_USE_LU) == 0 ||
				    strcmp(by, DM_USE_FS) == 0) {
					break;
				}
				if (build_usage_string(dname,
				    by, data, msg, &found, errp) != 0) {
					if (*errp) {
						goto out;
					}
				}
				break;
			case DM_WHO_SWAP:
				/*
				 * Not in use for this.
				 */
				if (strcmp(by, DM_USE_DUMP) == 0 ||
				    strcmp(by, DM_USE_FS) == 0) {
					break;
				}

				if (build_usage_string(dname,
				    by, data, msg, &found, errp) != 0) {
					if (*errp) {
						goto out;
					}
				}
				break;
			case DM_WHO_DUMP:
				/*
				 * Not in use for this.
				 */
				if ((strcmp(by, DM_USE_MOUNT) == 0 &&
				    strcmp(data, "swap") == 0) ||
				    strcmp(by, DM_USE_DUMP) == 0 ||
				    strcmp(by, DM_USE_FS) == 0) {
					break;
				}
				if (build_usage_string(dname,
				    by, data, msg, &found, errp)) {
					if (*errp) {
						goto out;
					}
				}
				break;

			case DM_WHO_FORMAT:
				if (strcmp(by, DM_USE_FS) == 0)
					break;
				if (build_usage_string(dname,
				    by, data, msg, &found, errp) != 0) {
					if (*errp) {
						goto out;
					}
				}
				break;
			default:
				/*
				 * nothing found in use for this client
				 * of libdiskmgt. Default is 'not in use'.
				 */
				break;
		}
	}
out:
	if (dname != NULL)
		free(dname);
	if (dev_stats != NULL)
		nvlist_free(dev_stats);

	return (found);
}

void
dm_get_usage_string(char *what, char *how, char **usage_string)
{


	if (usage_string == NULL || what == NULL) {
		return;
	}
	*usage_string = NULL;

	if (strcmp(what, DM_USE_MOUNT) == 0) {
		if (strcmp(how, "swap") == 0) {
			*usage_string = dgettext(TEXT_DOMAIN,
			    "%s is currently used by swap. Please see swap(1M)."
			    "\n");
		} else {
			*usage_string = dgettext(TEXT_DOMAIN,
			    "%s is currently mounted on %s."
			    " Please see umount(1M).\n");
		}
	} else if (strcmp(what, DM_USE_VFSTAB) == 0) {
		*usage_string = dgettext(TEXT_DOMAIN,
		    "%s is normally mounted on %s according to /etc/vfstab. "
		    "Please remove this entry to use this device.\n");
	} else if (strcmp(what, DM_USE_FS) == 0) {
		*usage_string = dgettext(TEXT_DOMAIN,
		    "Warning: %s contains a %s filesystem.\n");
	} else if (strcmp(what, DM_USE_SVM) == 0) {
		if (strcmp(how, "mdb") == 0) {
			*usage_string = dgettext(TEXT_DOMAIN,
			    "%s contains an SVM %s. Please see "
			    "metadb(1M).\n");
		} else {
			*usage_string = dgettext(TEXT_DOMAIN,
			    "%s is part of SVM volume %s. "
			    "Please see metaclear(1M).\n");
		}
	} else if (strcmp(what, DM_USE_VXVM) == 0) {
		*usage_string = dgettext(TEXT_DOMAIN,
		    "%s is part of VxVM volume %s.\n");
	} else if (strcmp(what, DM_USE_LU) == 0) {
		*usage_string = dgettext(TEXT_DOMAIN,
		    "%s is in use for live upgrade %s. Please see ludelete(1M)."
		    "\n");
	} else if (strcmp(what, DM_USE_DUMP) == 0) {
		*usage_string = dgettext(TEXT_DOMAIN,
		    "%s is in use by %s. Please see dumpadm(1M)."
		    "\n");
	} else if (strcmp(what, DM_USE_ZPOOL) == 0) {
		*usage_string = dgettext(TEXT_DOMAIN,
		    "%s is in use by zpool %s. Please see zpool(1M)."
		    "\n");
	}
}
void
libdiskmgt_add_str(nvlist_t *attrs, char *name, char *val, int *errp)
{
	if (*errp == 0) {
		*errp = nvlist_add_string(attrs, name, val);
	}
}

descriptor_t **
libdiskmgt_empty_desc_array(int *errp)
{
	descriptor_t	**empty;

	empty = (descriptor_t **)calloc(1, sizeof (descriptor_t *));
	if (empty == NULL) {
	    *errp = ENOMEM;
	    return (NULL);
	}
	empty[0] = NULL;

	*errp = 0;
	return (empty);
}

void
libdiskmgt_init_debug()
{
	char	*valp;

	if ((valp = getenv(DM_DEBUG)) != NULL) {
	    dm_debug = atoi(valp);
	}
}

int
libdiskmgt_str_eq(char *nm1, char *nm2)
{
	if (nm1 == NULL) {
	    if (dm_debug) {
		(void) fprintf(stderr, "WARNING: str_eq nm1 NULL\n");
	    }

	    if (nm2 == NULL) {
		return (1);
	    } else {
		return (0);
	    }
	}

	/* nm1 != NULL */

	if (nm2 == NULL) {
	    if (dm_debug) {
		(void) fprintf(stderr, "WARNING: str_eq nm2 NULL\n");
	    }
	    return (0);
	}

	if (strcmp(nm1, nm2) == 0) {
	    return (1);
	}

	return (0);
}

/*ARGSUSED*/
static descriptor_t **
desc_array_to_ptr_array(dm_descriptor_t *descs, int *errp)
{
#ifdef _LP64
	return ((descriptor_t **)descs);
#else
	/* convert the 64 bit descriptors to 32 bit ptrs */
	int	cnt;
	int	i;
	descriptor_t **da;

	for (cnt = 0; descs[cnt]; cnt++);

	da = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *));
	if (da == NULL) {
	    *errp = ENOMEM;
	    return (NULL);
	}

	for (i = 0; descs[i]; i++) {
	    da[i] = (descriptor_t *)(uintptr_t)descs[i];
	}
	*errp = 0;
	free(descs);

	return (da);
#endif
}

/*ARGSUSED*/
static dm_descriptor_t *
ptr_array_to_desc_array(descriptor_t **ptrs, int *errp)
{
#ifdef _LP64
	return ((dm_descriptor_t *)ptrs);
#else
	/* convert the 32 bit ptrs to the 64 bit descriptors */
	int	cnt;
	int	i;
	dm_descriptor_t *da;

	if (*errp != 0 || ptrs == NULL) {
	    return (NULL);
	}

	for (cnt = 0; ptrs[cnt]; cnt++);

	da = (dm_descriptor_t *)calloc(cnt + 1, sizeof (dm_descriptor_t));
	if (da == NULL) {
	    *errp = ENOMEM;
	    return (NULL);
	}

	for (i = 0; ptrs[i]; i++) {
	    da[i] = (uintptr_t)ptrs[i];
	}
	*errp = 0;
	free(ptrs);

	return (da);
#endif
}
/*
 * Build the usage string for the in use data. Return the build string in
 * the msg parameter. This function takes care of reallocing all the memory
 * for this usage string. Usage string is returned already formatted for
 * localization.
 */
static int
build_usage_string(char *dname, char *by, char *data, char **msg,
    int *found, int *errp)
{
	int	len0;
	int	len1;
	char	*use;
	char	*p;

	*errp = 0;

	dm_get_usage_string(by, data, &use);
	if (!use) {
		return (-1);
	}

	if (*msg)
		len0 = strlen(*msg);
	else
		len0 = 0;
	/* LINTED */
	len1 = snprintf(NULL, 0, use, dname, data);

	/*
	 * If multiple in use details they
	 * are listed 1 per line for ease of
	 * reading. dm_find_usage_string
	 * formats these appropriately.
	 */
	if ((p = realloc(*msg, len0 + len1 + 1)) == NULL) {
		*errp = errno;
		free(*msg);
		return (-1);
	}
	*msg = p;

	/* LINTED */
	(void) snprintf(*msg + len0, len1 + 1, use, dname, data);
	(*found)++;
	return (0);
}