/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #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 (NOINUSE_SET) { 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); } } /* * Checks for overlapping slices. If the given device is a slice, and it * overlaps with any non-backup slice on the disk, return true with a detailed * description similar to dm_inuse(). */ int dm_isoverlapping(char *slicename, char **overlaps_with, int *errp) { dm_descriptor_t slice = NULL; dm_descriptor_t *media = NULL; dm_descriptor_t *slices = NULL; int i = 0; uint32_t in_snum; uint64_t start_block = 0; uint64_t end_block = 0; uint64_t media_size = 0; uint64_t size = 0; nvlist_t *media_attrs = NULL; nvlist_t *slice_attrs = NULL; int ret = 0; slice = dm_get_descriptor_by_name(DM_SLICE, slicename, errp); if (slice == NULL) goto out; /* * Get the list of slices be fetching the associated media, and then all * associated slices. */ media = dm_get_associated_descriptors(slice, DM_MEDIA, errp); if (media == NULL || *media == NULL || *errp != 0) goto out; slices = dm_get_associated_descriptors(*media, DM_SLICE, errp); if (slices == NULL || *slices == NULL || *errp != 0) goto out; media_attrs = dm_get_attributes(*media, errp); if (media_attrs == NULL || *errp) goto out; *errp = nvlist_lookup_uint64(media_attrs, DM_NACCESSIBLE, &media_size); if (*errp != 0) goto out; slice_attrs = dm_get_attributes(slice, errp); if (slice_attrs == NULL || *errp != 0) goto out; *errp = nvlist_lookup_uint64(slice_attrs, DM_START, &start_block); if (*errp != 0) goto out; *errp = nvlist_lookup_uint64(slice_attrs, DM_SIZE, &size); if (*errp != 0) goto out; *errp = nvlist_lookup_uint32(slice_attrs, DM_INDEX, &in_snum); if (*errp != 0) goto out; end_block = (start_block + size) - 1; for (i = 0; slices[i]; i ++) { uint64_t other_start; uint64_t other_end; uint64_t other_size; uint32_t snum; nvlist_t *other_attrs = dm_get_attributes(slices[i], errp); if (other_attrs == NULL) continue; if (*errp != 0) goto out; *errp = nvlist_lookup_uint64(other_attrs, DM_START, &other_start); if (*errp) { nvlist_free(other_attrs); goto out; } *errp = nvlist_lookup_uint64(other_attrs, DM_SIZE, &other_size); if (*errp) { nvlist_free(other_attrs); ret = -1; goto out; } other_end = (other_size + other_start) - 1; *errp = nvlist_lookup_uint32(other_attrs, DM_INDEX, &snum); if (*errp) { nvlist_free(other_attrs); ret = -1; goto out; } /* * Check to see if there are > 2 overlapping regions * on this media in the same region as this slice. * This is done by assuming the following: * Slice 2 is the backup slice if it is the size * of the whole disk * If slice 2 is the overlap and slice 2 is the size of * the whole disk, continue. If another slice is found * that overlaps with our slice, return it. * There is the potential that there is more than one slice * that our slice overlaps with, however, we only return * the first overlapping slice we find. * */ if (start_block >= other_start && start_block <= other_end) { if ((snum == 2 && (other_size == media_size)) || snum == in_snum) { continue; } else { char *str = dm_get_name(slices[i], errp); if (*errp != 0) { nvlist_free(other_attrs); ret = -1; goto out; } *overlaps_with = strdup(str); dm_free_name(str); nvlist_free(other_attrs); ret = 1; goto out; } } else if (other_start >= start_block && other_start <= end_block) { if ((snum == 2 && (other_size == media_size)) || snum == in_snum) { continue; } else { char *str = dm_get_name(slices[i], errp); if (*errp != 0) { nvlist_free(other_attrs); ret = -1; goto out; } *overlaps_with = strdup(str); dm_free_name(str); nvlist_free(other_attrs); ret = 1; goto out; } } nvlist_free(other_attrs); } out: if (media_attrs) nvlist_free(media_attrs); if (slice_attrs) nvlist_free(slice_attrs); if (slices) dm_free_descriptors(slices); if (media) dm_free_descriptors(media); if (slice) dm_free_descriptor(slice); return (ret); } /* * 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; /* * If the user doesn't want to do in use checking, return. */ if (NOINUSE_SET) return (0); 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 || strcmp(by, DM_USE_EXPORTED_ZPOOL) == 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 || strcmp(by, DM_USE_EXPORTED_ZPOOL) == 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 || strcmp(by, DM_USE_EXPORTED_ZPOOL) == 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 || strcmp(by, DM_USE_EXPORTED_ZPOOL) == 0) break; if (build_usage_string(dname, by, data, msg, &found, errp) != 0) { if (*errp) { goto out; } } break; case DM_WHO_ZPOOL_FORCE: if (strcmp(by, DM_USE_FS) == 0 || strcmp(by, DM_USE_EXPORTED_ZPOOL) == 0) break; /* FALLTHROUGH */ case DM_WHO_ZPOOL: 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, "%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_EXPORTED_ZPOOL) == 0) { *usage_string = dgettext(TEXT_DOMAIN, "%s is part of exported or potentially active ZFS pool %s. " "Please see zpool(1M).\n"); } else if (strcmp(what, DM_USE_ACTIVE_ZPOOL) == 0) { *usage_string = dgettext(TEXT_DOMAIN, "%s is part of active ZFS pool %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); }