summaryrefslogtreecommitdiff
path: root/usr/src/lib/libdiskmgt/common/drive.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libdiskmgt/common/drive.c')
-rw-r--r--usr/src/lib/libdiskmgt/common/drive.c1575
1 files changed, 1575 insertions, 0 deletions
diff --git a/usr/src/lib/libdiskmgt/common/drive.c b/usr/src/lib/libdiskmgt/common/drive.c
new file mode 100644
index 0000000000..00fd979ae3
--- /dev/null
+++ b/usr/src/lib/libdiskmgt/common/drive.c
@@ -0,0 +1,1575 @@
+/*
+ * 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 2004 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 <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/dkio.h>
+#include <sys/sunddi.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <kstat.h>
+#include <errno.h>
+#include <devid.h>
+#include <dirent.h>
+
+/* included for uscsi */
+#include <strings.h>
+#include <sys/stat.h>
+#include <sys/scsi/impl/types.h>
+#include <sys/scsi/impl/uscsi.h>
+#include <sys/scsi/generic/commands.h>
+#include <sys/scsi/impl/commands.h>
+#include <sys/scsi/generic/mode.h>
+#include <sys/byteorder.h>
+
+#include "libdiskmgt.h"
+#include "disks_private.h"
+
+#define KSTAT_CLASS_DISK "disk"
+#define KSTAT_CLASS_ERROR "device_error"
+
+#define SCSIBUFLEN 0xffff
+
+/* byte get macros */
+#define b3(a) (((a)>>24) & 0xFF)
+#define b2(a) (((a)>>16) & 0xFF)
+#define b1(a) (((a)>>8) & 0xFF)
+#define b0(a) (((a)>>0) & 0xFF)
+
+static char *kstat_err_names[] = {
+ "Soft Errors",
+ "Hard Errors",
+ "Transport Errors",
+ "Media Error",
+ "Device Not Ready",
+ "No Device",
+ "Recoverable",
+ "Illegal Request",
+ "Predictive Failure Analysis",
+ NULL
+};
+
+static char *err_attr_names[] = {
+ DM_NSOFTERRS,
+ DM_NHARDERRS,
+ DM_NTRANSERRS,
+ DM_NMEDIAERRS,
+ DM_NDNRERRS,
+ DM_NNODEVERRS,
+ DM_NRECOVERRS,
+ DM_NILLREQERRS,
+ DM_FAILING,
+ NULL
+};
+
+/*
+ * **************** begin uscsi stuff ****************
+ */
+
+#if defined(_BIT_FIELDS_LTOH)
+#elif defined(_BIT_FIELDS_HTOL)
+#else
+#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined
+#endif
+
+struct conf_feature {
+ uchar_t feature[2]; /* common to all */
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t current : 1;
+ uchar_t persist : 1;
+ uchar_t version : 4;
+ uchar_t reserved: 2;
+#else
+ uchar_t reserved: 2;
+ uchar_t version : 4;
+ uchar_t persist : 1;
+ uchar_t current : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t len;
+ union features {
+ struct generic {
+ uchar_t data[1];
+ } gen;
+ uchar_t data[1];
+ struct profile_list {
+ uchar_t profile[2];
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t current_p : 1;
+ uchar_t reserved1 : 7;
+#else
+ uchar_t reserved1 : 7;
+ uchar_t current_p : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t reserved2;
+ } plist[1];
+ struct core {
+ uchar_t phys[4];
+ } core;
+ struct morphing {
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t async : 1;
+ uchar_t reserved1 : 7;
+#else
+ uchar_t reserved1 : 7;
+ uchar_t async : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t reserved[3];
+ } morphing;
+ struct removable {
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t lock : 1;
+ uchar_t resv1 : 1;
+ uchar_t pvnt : 1;
+ uchar_t eject : 1;
+ uchar_t resv2 : 1;
+ uchar_t loading : 3;
+#else
+ uchar_t loading : 3;
+ uchar_t resv2 : 1;
+ uchar_t eject : 1;
+ uchar_t pvnt : 1;
+ uchar_t resv1 : 1;
+ uchar_t lock : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t reserved[3];
+ } removable;
+ struct random_readable {
+ uchar_t lbsize[4];
+ uchar_t blocking[2];
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t pp : 1;
+ uchar_t reserved1 : 7;
+#else
+ uchar_t reserved1 : 7;
+ uchar_t pp : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t reserved;
+ } rread;
+ struct cd_read {
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t cdtext : 1;
+ uchar_t c2flag : 1;
+ uchar_t reserved1 : 6;
+#else
+ uchar_t reserved1 : 6;
+ uchar_t c2flag : 1;
+ uchar_t cdtext : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ } cdread;
+ struct cd_audio {
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t sv : 1;
+ uchar_t scm : 1;
+ uchar_t scan : 1;
+ uchar_t resv : 5;
+#else
+ uchar_t resv : 5;
+ uchar_t scan : 1;
+ uchar_t scm : 1;
+ uchar_t sv : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t reserved;
+ uchar_t numlevels[2];
+ } audio;
+ struct dvd_css {
+ uchar_t reserved[3];
+ uchar_t version;
+ } dvdcss;
+ } features;
+};
+
+#define PROF_NON_REMOVABLE 0x0001
+#define PROF_REMOVABLE 0x0002
+#define PROF_MAGNETO_OPTICAL 0x0003
+#define PROF_OPTICAL_WO 0x0004
+#define PROF_OPTICAL_ASMO 0x0005
+#define PROF_CDROM 0x0008
+#define PROF_CDR 0x0009
+#define PROF_CDRW 0x000a
+#define PROF_DVDROM 0x0010
+#define PROF_DVDR 0x0011
+#define PROF_DVDRAM 0x0012
+#define PROF_DVDRW_REST 0x0013
+#define PROF_DVDRW_SEQ 0x0014
+#define PROF_DVDRW 0x001a
+#define PROF_DDCD_ROM 0x0020
+#define PROF_DDCD_R 0x0021
+#define PROF_DDCD_RW 0x0022
+#define PROF_NON_CONFORMING 0xffff
+
+struct get_configuration {
+ uchar_t len[4];
+ uchar_t reserved[2];
+ uchar_t curprof[2];
+ struct conf_feature feature;
+};
+
+struct capabilities {
+#if defined(_BIT_FIELDS_LTOH)
+ uchar_t pagecode : 6;
+ uchar_t resv1 : 1;
+ uchar_t ps : 1;
+#else
+ uchar_t ps : 1;
+ uchar_t resv1 : 1;
+ uchar_t pagecode : 6;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t pagelen;
+#if defined(_BIT_FIELDS_LTOH)
+ /* read capabilities */
+ uchar_t cdr_read : 1;
+ uchar_t cdrw_read : 1;
+ uchar_t method2 : 1;
+ uchar_t dvdrom_read : 1;
+ uchar_t dvdr_read : 1;
+ uchar_t dvdram_read : 1;
+ uchar_t resv2 : 2;
+#else
+ uchar_t resv2 : 2;
+ uchar_t dvdram_read : 1;
+ uchar_t dvdr_read : 1;
+ uchar_t dvdrom_read : 1;
+ uchar_t method2 : 1;
+ uchar_t cdrw_read : 1;
+ uchar_t cdr_read : 1;
+#endif /* _BIT_FIELDS_LTOH */
+#if defined(_BIT_FIELDS_LTOH)
+ /* write capabilities */
+ uchar_t cdr_write : 1;
+ uchar_t cdrw_write : 1;
+ uchar_t testwrite : 1;
+ uchar_t resv3 : 1;
+ uchar_t dvdr_write : 1;
+ uchar_t dvdram_write : 1;
+ uchar_t resv4 : 2;
+#else
+ /* write capabilities */
+ uchar_t resv4 : 2;
+ uchar_t dvdram_write : 1;
+ uchar_t dvdr_write : 1;
+ uchar_t resv3 : 1;
+ uchar_t testwrite : 1;
+ uchar_t cdrw_write : 1;
+ uchar_t cdr_write : 1;
+#endif /* _BIT_FIELDS_LTOH */
+ uchar_t misc0;
+ uchar_t misc1;
+ uchar_t misc2;
+ uchar_t misc3;
+ uchar_t obsolete0[2];
+ uchar_t numvlevels[2];
+ uchar_t bufsize[2];
+ uchar_t obsolete1[4];
+ uchar_t resv5;
+ uchar_t misc4;
+ uchar_t obsolete2;
+ uchar_t copymgt[2];
+ /* there is more to this page, but nothing we care about */
+};
+
+struct mode_header_g2 {
+ uchar_t modelen[2];
+ uchar_t obsolete;
+ uchar_t reserved[3];
+ uchar_t desclen[2];
+};
+
+/*
+ * Mode sense/select page header information
+ */
+struct scsi_ms_header {
+ struct mode_header mode_header;
+ struct block_descriptor block_descriptor;
+};
+
+#define MODESENSE_PAGE_LEN(p) (((int)((struct mode_page *)p)->length) + \
+ sizeof (struct mode_page))
+
+#define MODE_SENSE_PC_CURRENT (0 << 6)
+#define MODE_SENSE_PC_DEFAULT (2 << 6)
+#define MODE_SENSE_PC_SAVED (3 << 6)
+
+#define MAX_MODE_SENSE_SIZE 255
+#define IMPOSSIBLE_SCSI_STATUS 0xff
+
+/*
+ * ********** end of uscsi stuff ************
+ */
+
+static descriptor_t **apply_filter(descriptor_t **drives, int filter[],
+ int *errp);
+static int check_atapi(int fd);
+static int conv_drive_type(uint_t drive_type);
+static uint64_t convnum(uchar_t *nptr, int len);
+static void fill_command_g1(struct uscsi_cmd *cmd,
+ union scsi_cdb *cdb, caddr_t buff, int blen);
+static void fill_general_page_cdb_g1(union scsi_cdb *cdb,
+ int command, int lun, uchar_t c0, uchar_t c1);
+static void fill_mode_page_cdb(union scsi_cdb *cdb, int page);
+static descriptor_t **get_assoc_alias(disk_t *diskp, int *errp);
+static descriptor_t **get_assoc_controllers(descriptor_t *dp, int *errp);
+static descriptor_t **get_assoc_paths(descriptor_t *dp, int *errp);
+static int get_attrs(disk_t *diskp, int fd, char *opath,
+ nvlist_t *nvp);
+static int get_cdrom_drvtype(int fd);
+static int get_disk_kstats(kstat_ctl_t *kc, char *diskname,
+ char *classname, nvlist_t *stats);
+static void get_drive_type(disk_t *dp, int fd);
+static int get_err_kstats(kstat_ctl_t *kc, char *diskname,
+ nvlist_t *stats);
+static int get_io_kstats(kstat_ctl_t *kc, char *diskname,
+ nvlist_t *stats);
+static int get_kstat_vals(kstat_t *ksp, nvlist_t *stats);
+static char *get_err_attr_name(char *kstat_name);
+static int get_rpm(disk_t *dp, int fd);
+static int update_stat64(nvlist_t *stats, char *attr,
+ uint64_t value);
+static int update_stat32(nvlist_t *stats, char *attr,
+ uint32_t value);
+static int uscsi_mode_sense(int fd, int page_code,
+ int page_control, caddr_t page_data, int page_size,
+ struct scsi_ms_header *header);
+
+descriptor_t **
+drive_get_assoc_descriptors(descriptor_t *dp, dm_desc_type_t type,
+ int *errp)
+{
+ switch (type) {
+ case DM_CONTROLLER:
+ return (get_assoc_controllers(dp, errp));
+ case DM_PATH:
+ return (get_assoc_paths(dp, errp));
+ case DM_ALIAS:
+ return (get_assoc_alias(dp->p.disk, errp));
+ case DM_MEDIA:
+ return (media_get_assocs(dp, errp));
+ }
+
+ *errp = EINVAL;
+ return (NULL);
+}
+
+/*
+ * Get the drive descriptors for the given media/alias/devpath.
+ */
+descriptor_t **
+drive_get_assocs(descriptor_t *desc, int *errp)
+{
+ descriptor_t **drives;
+
+ /* at most one drive is associated with these descriptors */
+
+ drives = (descriptor_t **)calloc(2, sizeof (descriptor_t *));
+ if (drives == NULL) {
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ drives[0] = cache_get_desc(DM_DRIVE, desc->p.disk, NULL, NULL, errp);
+ if (*errp != 0) {
+ cache_free_descriptors(drives);
+ return (NULL);
+ }
+
+ drives[1] = NULL;
+
+ return (drives);
+}
+
+nvlist_t *
+drive_get_attributes(descriptor_t *dp, int *errp)
+{
+ nvlist_t *attrs = NULL;
+ int fd;
+ char opath[MAXPATHLEN];
+
+ if (nvlist_alloc(&attrs, NVATTRS, 0) != 0) {
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ opath[0] = 0;
+ fd = drive_open_disk(dp->p.disk, opath, sizeof (opath));
+
+ if ((*errp = get_attrs(dp->p.disk, fd, opath, attrs)) != 0) {
+ nvlist_free(attrs);
+ attrs = NULL;
+ }
+
+ if (fd >= 0) {
+ (void) close(fd);
+ }
+
+ return (attrs);
+}
+
+/*
+ * Check if we have the drive in our list, based upon the device id.
+ * We got the device id from the dev tree walk. This is encoded
+ * using devid_str_encode(3DEVID). In order to check the device ids we need
+ * to use the devid_compare(3DEVID) function, so we need to decode the
+ * string representation of the device id.
+ */
+descriptor_t *
+drive_get_descriptor_by_name(char *name, int *errp)
+{
+ ddi_devid_t devid;
+ descriptor_t **drives;
+ descriptor_t *drive = NULL;
+ int i;
+
+ if (name == NULL || devid_str_decode(name, &devid, NULL) != 0) {
+ *errp = EINVAL;
+ return (NULL);
+ }
+
+ drives = cache_get_descriptors(DM_DRIVE, errp);
+ if (*errp != 0) {
+ devid_free(devid);
+ return (NULL);
+ }
+
+ /*
+ * We have to loop through all of them, freeing the ones we don't
+ * want. Once drive is set, we don't need to compare any more.
+ */
+ for (i = 0; drives[i]; i++) {
+ if (drive == NULL && drives[i]->p.disk->devid != NULL &&
+ devid_compare(devid, drives[i]->p.disk->devid) == 0) {
+ drive = drives[i];
+
+ } else {
+ /* clean up the unused descriptor */
+ cache_free_descriptor(drives[i]);
+ }
+ }
+ free(drives);
+ devid_free(devid);
+
+ if (drive == NULL) {
+ *errp = ENODEV;
+ }
+
+ return (drive);
+}
+
+descriptor_t **
+drive_get_descriptors(int filter[], int *errp)
+{
+ descriptor_t **drives;
+
+ drives = cache_get_descriptors(DM_DRIVE, errp);
+ if (*errp != 0) {
+ return (NULL);
+ }
+
+ if (filter != NULL && filter[0] != DM_FILTER_END) {
+ descriptor_t **found;
+ found = apply_filter(drives, filter, errp);
+ if (*errp != 0) {
+ drives = NULL;
+ } else {
+ drives = found;
+ }
+ }
+
+ return (drives);
+}
+
+char *
+drive_get_name(descriptor_t *dp)
+{
+ return (dp->p.disk->device_id);
+}
+
+nvlist_t *
+drive_get_stats(descriptor_t *dp, int stat_type, int *errp)
+{
+ disk_t *diskp;
+ nvlist_t *stats;
+
+ diskp = dp->p.disk;
+
+ if (nvlist_alloc(&stats, NVATTRS, 0) != 0) {
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ if (stat_type == DM_DRV_STAT_PERFORMANCE ||
+ stat_type == DM_DRV_STAT_DIAGNOSTIC) {
+
+ alias_t *ap;
+ kstat_ctl_t *kc;
+
+ ap = diskp->aliases;
+ if (ap == NULL || ap->kstat_name == NULL) {
+ nvlist_free(stats);
+ *errp = EACCES;
+ return (NULL);
+ }
+
+ if ((kc = kstat_open()) == NULL) {
+ nvlist_free(stats);
+ *errp = EACCES;
+ return (NULL);
+ }
+
+ while (ap != NULL) {
+ int status;
+
+ if (ap->kstat_name == NULL) {
+ continue;
+ }
+
+ if (stat_type == DM_DRV_STAT_PERFORMANCE) {
+ status = get_io_kstats(kc, ap->kstat_name, stats);
+ } else {
+ status = get_err_kstats(kc, ap->kstat_name, stats);
+ }
+
+ if (status != 0) {
+ nvlist_free(stats);
+ (void) kstat_close(kc);
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ ap = ap->next;
+ }
+
+ (void) kstat_close(kc);
+
+ *errp = 0;
+ return (stats);
+ }
+
+ if (stat_type == DM_DRV_STAT_TEMPERATURE) {
+ int fd;
+
+ if ((fd = drive_open_disk(diskp, NULL, 0)) >= 0) {
+ struct dk_temperature temp;
+
+ if (ioctl(fd, DKIOCGTEMPERATURE, &temp) >= 0) {
+ if (nvlist_add_uint32(stats, DM_TEMPERATURE,
+ temp.dkt_cur_temp) != 0) {
+ *errp = ENOMEM;
+ nvlist_free(stats);
+ return (NULL);
+ }
+ } else {
+ *errp = errno;
+ nvlist_free(stats);
+ return (NULL);
+ }
+ (void) close(fd);
+ } else {
+ *errp = errno;
+ nvlist_free(stats);
+ return (NULL);
+ }
+
+ *errp = 0;
+ return (stats);
+ }
+
+ nvlist_free(stats);
+ *errp = EINVAL;
+ return (NULL);
+}
+
+int
+drive_make_descriptors()
+{
+ int error;
+ disk_t *dp;
+
+ dp = cache_get_disklist();
+ while (dp != NULL) {
+ cache_load_desc(DM_DRIVE, dp, NULL, NULL, &error);
+ if (error != 0) {
+ return (error);
+ }
+ dp = dp->next;
+ }
+
+ return (0);
+}
+
+/*
+ * This function opens the disk generically (any slice).
+ *
+ * Opening the disk could fail because the disk is managed by the volume
+ * manager. Handle this if that is the case. Note that the media APIs don't
+ * always return a device. If the media has slices (e.g. a solaris install
+ * CD-ROM) then media_findname(volname) returns a directory with per slice
+ * devices underneath. We need to open one of those devices in this case.
+ */
+int
+drive_open_disk(disk_t *diskp, char *opath, int len)
+{
+ char rmmedia_devpath[MAXPATHLEN];
+
+ if (diskp->removable && media_get_volm_path(diskp, rmmedia_devpath,
+ sizeof (rmmedia_devpath))) {
+
+ int fd;
+ struct stat buf;
+
+ if (rmmedia_devpath[0] == 0) {
+ /* removable but no media */
+ return (-1);
+ }
+
+ if ((fd = open(rmmedia_devpath, O_RDONLY|O_NDELAY)) < 0) {
+ return (-1);
+ }
+
+ if (fstat(fd, &buf) != 0) {
+ (void) close(fd);
+ return (-1);
+ }
+
+ if (buf.st_mode & S_IFCHR) {
+ /* opened, is device, so done */
+ if (opath != NULL) {
+ (void) strlcpy(opath, rmmedia_devpath, len);
+ }
+ return (fd);
+
+ } else if (buf.st_mode & S_IFDIR) {
+ /* disk w/ slices so handle the directory */
+ DIR *dirp;
+ struct dirent *dentp;
+ int dfd;
+#ifdef _LP64
+ struct dirent *result;
+#endif
+
+ /* each device file in the dir represents a slice */
+
+ if ((dirp = fdopendir(fd)) == NULL) {
+ (void) close(fd);
+ return (-1);
+ }
+
+ if ((dentp = (struct dirent *)malloc(sizeof (struct dirent) +
+ _PC_NAME_MAX + 1)) == NULL) {
+ /* out of memory */
+ (void) close(fd);
+ return (-1);
+ }
+#ifdef _LP64
+ while (readdir_r(dirp, dentp, &result) != NULL) {
+#else
+ while (readdir_r(dirp, dentp) != NULL) {
+#endif
+ char slice_path[MAXPATHLEN];
+
+ if (libdiskmgt_str_eq(".", dentp->d_name) ||
+ libdiskmgt_str_eq("..", dentp->d_name)) {
+ continue;
+ }
+
+ (void) snprintf(slice_path, sizeof (slice_path), "%s/%s",
+ rmmedia_devpath, dentp->d_name);
+
+ if ((dfd = open(slice_path, O_RDONLY|O_NDELAY)) < 0) {
+ continue;
+ }
+
+ if (fstat(dfd, &buf) == 0 && (buf.st_mode & S_IFCHR)) {
+ /* opened, is device, so done */
+ free(dentp);
+ (void) close(fd);
+ if (opath != NULL) {
+ (void) strlcpy(opath, slice_path, len);
+ }
+ return (dfd);
+ }
+
+ /* not a device, keep looking */
+ (void) close(dfd);
+ }
+
+ /* did not find a device under the rmmedia_path */
+ free(dentp);
+ (void) close(fd);
+ }
+
+ /* didn't find a device under volume management control */
+ return (-1);
+ }
+
+ /*
+ * Not removable media under volume management control so just open the
+ * first devpath.
+ */
+ if (diskp->aliases != NULL && diskp->aliases->devpaths != NULL) {
+ if (opath != NULL) {
+ (void) strlcpy(opath, diskp->aliases->devpaths->devpath, len);
+ }
+ return (open(diskp->aliases->devpaths->devpath, O_RDONLY|O_NDELAY));
+ }
+
+ return (-1);
+}
+
+static descriptor_t **
+apply_filter(descriptor_t **drives, int filter[], int *errp)
+{
+ int i;
+ descriptor_t **found;
+ int cnt;
+ int pos;
+
+ /* count the number of drives in the snapshot */
+ for (cnt = 0; drives[cnt]; cnt++);
+
+ found = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *));
+ if (found == NULL) {
+ *errp = ENOMEM;
+ cache_free_descriptors(drives);
+ return (NULL);
+ }
+
+ pos = 0;
+ for (i = 0; drives[i]; i++) {
+ int j;
+ int match;
+
+ /* Make sure the drive type is set */
+ get_drive_type(drives[i]->p.disk, -1);
+
+ match = 0;
+ for (j = 0; filter[j] != DM_FILTER_END; j++) {
+ if (drives[i]->p.disk->drv_type == filter[j]) {
+ found[pos++] = drives[i];
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+ cache_free_descriptor(drives[i]);
+ }
+ }
+ found[pos] = NULL;
+ free(drives);
+
+ *errp = 0;
+ return (found);
+}
+
+static int
+conv_drive_type(uint_t drive_type)
+{
+ switch (drive_type) {
+ case DK_UNKNOWN:
+ return (DM_DT_UNKNOWN);
+ case DK_MO_ERASABLE:
+ return (DM_DT_MO_ERASABLE);
+ case DK_MO_WRITEONCE:
+ return (DM_DT_MO_WRITEONCE);
+ case DK_AS_MO:
+ return (DM_DT_AS_MO);
+ case DK_CDROM:
+ return (DM_DT_CDROM);
+ case DK_CDR:
+ return (DM_DT_CDR);
+ case DK_CDRW:
+ return (DM_DT_CDRW);
+ case DK_DVDROM:
+ return (DM_DT_DVDROM);
+ case DK_DVDR:
+ return (DM_DT_DVDR);
+ case DK_DVDRAM:
+ return (DM_DT_DVDRAM);
+ case DK_FIXED_DISK:
+ return (DM_DT_FIXED);
+ case DK_FLOPPY:
+ return (DM_DT_FLOPPY);
+ case DK_ZIP:
+ return (DM_DT_ZIP);
+ case DK_JAZ:
+ return (DM_DT_JAZ);
+ default:
+ return (DM_DT_UNKNOWN);
+ }
+}
+
+static descriptor_t **
+get_assoc_alias(disk_t *diskp, int *errp)
+{
+ alias_t *aliasp;
+ uint_t cnt;
+ descriptor_t **out_array;
+ int pos;
+
+ *errp = 0;
+
+ aliasp = diskp->aliases;
+ cnt = 0;
+
+ while (aliasp != NULL) {
+ if (aliasp->alias != NULL) {
+ cnt++;
+ }
+ aliasp = aliasp->next;
+ }
+
+ /* set up the new array */
+ out_array = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t));
+ if (out_array == NULL) {
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ aliasp = diskp->aliases;
+ pos = 0;
+ while (aliasp != NULL) {
+ if (aliasp->alias != NULL) {
+ out_array[pos++] = cache_get_desc(DM_ALIAS, diskp,
+ aliasp->alias, NULL, errp);
+ if (*errp != 0) {
+ cache_free_descriptors(out_array);
+ return (NULL);
+ }
+ }
+
+ aliasp = aliasp->next;
+ }
+
+ out_array[pos] = NULL;
+
+ return (out_array);
+}
+
+static descriptor_t **
+get_assoc_controllers(descriptor_t *dp, int *errp)
+{
+ disk_t *diskp;
+ int cnt;
+ descriptor_t **controllers;
+ int i;
+
+ diskp = dp->p.disk;
+
+ /* Count how many we have. */
+ for (cnt = 0; diskp->controllers[cnt]; cnt++);
+
+ /* make the snapshot */
+ controllers = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *));
+ if (controllers == NULL) {
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ for (i = 0; diskp->controllers[i]; i++) {
+ controllers[i] = cache_get_desc(DM_CONTROLLER,
+ diskp->controllers[i], NULL, NULL, errp);
+ if (*errp != 0) {
+ cache_free_descriptors(controllers);
+ return (NULL);
+ }
+ }
+
+ controllers[i] = NULL;
+
+ *errp = 0;
+ return (controllers);
+}
+
+static descriptor_t **
+get_assoc_paths(descriptor_t *dp, int *errp)
+{
+ path_t **pp;
+ int cnt;
+ descriptor_t **paths;
+ int i;
+
+ pp = dp->p.disk->paths;
+
+ /* Count how many we have. */
+ cnt = 0;
+ if (pp != NULL) {
+ for (; pp[cnt]; cnt++);
+ }
+
+ /* make the snapshot */
+ paths = (descriptor_t **)calloc(cnt + 1, sizeof (descriptor_t *));
+ if (paths == NULL) {
+ *errp = ENOMEM;
+ return (NULL);
+ }
+
+ /*
+ * We fill in the name field of the descriptor with the device_id
+ * when we deal with path descriptors originating from a drive.
+ * In that way we can use the device id within the path code to
+ * lookup the path state for this drive.
+ */
+ for (i = 0; i < cnt; i++) {
+ paths[i] = cache_get_desc(DM_PATH, pp[i], dp->p.disk->device_id,
+ NULL, errp);
+ if (*errp != 0) {
+ cache_free_descriptors(paths);
+ return (NULL);
+ }
+ }
+
+ paths[i] = NULL;
+
+ *errp = 0;
+ return (paths);
+}
+
+static int
+get_attrs(disk_t *diskp, int fd, char *opath, nvlist_t *attrs)
+{
+ if (diskp->removable) {
+ struct dk_minfo minfo;
+
+ if (nvlist_add_boolean(attrs, DM_REMOVABLE) != 0) {
+ return (ENOMEM);
+ }
+
+ /* Make sure media is inserted and spun up. */
+ if (fd >= 0 && media_read_info(fd, &minfo)) {
+ if (nvlist_add_boolean(attrs, DM_LOADED) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ /* can't tell diff between dead & no media on removable drives */
+ if (nvlist_add_uint32(attrs, DM_STATUS, DM_DISK_UP) != 0) {
+ return (ENOMEM);
+ }
+
+ get_drive_type(diskp, fd);
+
+ } else {
+ struct dk_minfo minfo;
+
+ /* check if the fixed drive is up or not */
+ if (fd >= 0 && media_read_info(fd, &minfo)) {
+ if (nvlist_add_uint32(attrs, DM_STATUS, DM_DISK_UP) != 0) {
+ return (ENOMEM);
+ }
+ } else {
+ if (nvlist_add_uint32(attrs, DM_STATUS, DM_DISK_DOWN) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ get_drive_type(diskp, fd);
+ }
+
+ if (nvlist_add_uint32(attrs, DM_DRVTYPE, diskp->drv_type) != 0) {
+ return (ENOMEM);
+ }
+
+ if (diskp->product_id != NULL) {
+ if (nvlist_add_string(attrs, DM_PRODUCT_ID, diskp->product_id)
+ != 0) {
+ return (ENOMEM);
+ }
+ }
+ if (diskp->vendor_id != NULL) {
+ if (nvlist_add_string(attrs, DM_VENDOR_ID, diskp->vendor_id) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ if (diskp->sync_speed != -1) {
+ if (nvlist_add_uint32(attrs, DM_SYNC_SPEED, diskp->sync_speed)
+ != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ if (diskp->wide == 1) {
+ if (nvlist_add_boolean(attrs, DM_WIDE) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ if (diskp->rpm == 0) {
+ diskp->rpm = get_rpm(diskp, fd);
+ }
+
+ if (diskp->rpm > 0) {
+ if (nvlist_add_uint32(attrs, DM_RPM, diskp->rpm) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ if (diskp->aliases != NULL && diskp->aliases->cluster) {
+ if (nvlist_add_boolean(attrs, DM_CLUSTERED) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ if (strlen(opath) > 0) {
+ if (nvlist_add_string(attrs, DM_OPATH, opath) != 0) {
+ return (ENOMEM);
+ }
+ }
+
+ return (0);
+}
+
+static int
+get_disk_kstats(kstat_ctl_t *kc, char *diskname, char *classname,
+ nvlist_t *stats)
+{
+ kstat_t *ksp;
+ size_t class_len;
+ int err = 0;
+
+ class_len = strlen(classname);
+ for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+ if (strncmp(ksp->ks_class, classname, class_len) == 0) {
+ char kstat_name[KSTAT_STRLEN];
+ char *dname = kstat_name;
+ char *ename = ksp->ks_name;
+
+ /* names are format: "sd0,err" - copy chars up to comma */
+ while (*ename && *ename != ',') {
+ *dname++ = *ename++;
+ }
+ *dname = NULL;
+
+ if (libdiskmgt_str_eq(diskname, kstat_name)) {
+ (void) kstat_read(kc, ksp, NULL);
+ err = get_kstat_vals(ksp, stats);
+ break;
+ }
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * Getting the drive type depends on if the dev tree walk indicated that the
+ * drive was a CD-ROM or not. The kernal lumps all of the removable multi-media
+ * drives (e.g. CD, DVD, MO, etc.) together as CD-ROMS, so we need to use
+ * a uscsi cmd to check the drive type.
+ */
+static void
+get_drive_type(disk_t *dp, int fd)
+{
+ if (dp->drv_type == DM_DT_UNKNOWN) {
+ int opened_here = 0;
+
+ /* We may have already opened the device. */
+ if (fd < 0) {
+ fd = drive_open_disk(dp, NULL, 0);
+ opened_here = 1;
+ }
+
+ if (fd >= 0) {
+ if (dp->cd_rom) {
+ /* use uscsi to determine drive type */
+ dp->drv_type = get_cdrom_drvtype(fd);
+
+ /* if uscsi fails, just call it a cd-rom */
+ if (dp->drv_type == DM_DT_UNKNOWN) {
+ dp->drv_type = DM_DT_CDROM;
+ }
+
+ } else {
+ struct dk_minfo minfo;
+
+ if (media_read_info(fd, &minfo)) {
+ dp->drv_type = conv_drive_type(minfo.dki_media_type);
+ }
+ }
+
+ if (opened_here) {
+ (void) close(fd);
+ }
+
+ } else {
+ /* couldn't open */
+ if (dp->cd_rom) {
+ dp->drv_type = DM_DT_CDROM;
+ }
+ }
+ }
+}
+
+static char *
+get_err_attr_name(char *kstat_name)
+{
+ int i;
+
+ for (i = 0; kstat_err_names[i] != NULL; i++) {
+ if (libdiskmgt_str_eq(kstat_name, kstat_err_names[i])) {
+ return (err_attr_names[i]);
+ }
+ }
+
+ return (NULL);
+}
+
+static int
+get_err_kstats(kstat_ctl_t *kc, char *diskname, nvlist_t *stats)
+{
+ return (get_disk_kstats(kc, diskname, KSTAT_CLASS_ERROR, stats));
+}
+
+static int
+get_io_kstats(kstat_ctl_t *kc, char *diskname, nvlist_t *stats)
+{
+ return (get_disk_kstats(kc, diskname, KSTAT_CLASS_DISK, stats));
+}
+
+static int
+get_kstat_vals(kstat_t *ksp, nvlist_t *stats)
+{
+ if (ksp->ks_type == KSTAT_TYPE_IO) {
+ kstat_io_t *kiop;
+
+ kiop = KSTAT_IO_PTR(ksp);
+
+ /* see sys/kstat.h kstat_io_t struct for more fields */
+
+ if (update_stat64(stats, DM_NBYTESREAD, kiop->nread) != 0) {
+ return (ENOMEM);
+ }
+ if (update_stat64(stats, DM_NBYTESWRITTEN, kiop->nwritten) != 0) {
+ return (ENOMEM);
+ }
+ if (update_stat64(stats, DM_NREADOPS, kiop->reads) != 0) {
+ return (ENOMEM);
+ }
+ if (update_stat64(stats, DM_NWRITEOPS, kiop->writes) != 0) {
+ return (ENOMEM);
+ }
+
+ } else if (ksp->ks_type == KSTAT_TYPE_NAMED) {
+ kstat_named_t *knp;
+ int i;
+
+ knp = KSTAT_NAMED_PTR(ksp);
+ for (i = 0; i < ksp->ks_ndata; i++) {
+ char *attr_name;
+
+ if (knp[i].name[0] == 0)
+ continue;
+
+ if ((attr_name = get_err_attr_name(knp[i].name)) == NULL) {
+ continue;
+
+ }
+
+ switch (knp[i].data_type) {
+ case KSTAT_DATA_UINT32:
+ if (update_stat32(stats, attr_name, knp[i].value.ui32)
+ != 0) {
+ return (ENOMEM);
+ }
+ break;
+
+ default:
+ /* Right now all of the error types are uint32 */
+ break;
+ }
+ }
+ }
+ return (0);
+}
+
+static int
+update_stat32(nvlist_t *stats, char *attr, uint32_t value)
+{
+ int32_t currval;
+
+ if (nvlist_lookup_int32(stats, attr, &currval) == 0) {
+ value += currval;
+ }
+
+ return (nvlist_add_uint32(stats, attr, value));
+}
+
+/*
+ * There can be more than one kstat value when we have multi-path drives
+ * that are not under mpxio (since there is more than one kstat name for
+ * the drive in this case). So, we may have merge all of the kstat values
+ * to give an accurate set of stats for the drive.
+ */
+static int
+update_stat64(nvlist_t *stats, char *attr, uint64_t value)
+{
+ int64_t currval;
+
+ if (nvlist_lookup_int64(stats, attr, &currval) == 0) {
+ value += currval;
+ }
+ return (nvlist_add_uint64(stats, attr, value));
+}
+
+/*
+ * uscsi function to get the rpm of the drive
+ */
+static int
+get_rpm(disk_t *dp, int fd)
+{
+ int opened_here = 0;
+ int rpm = -1;
+
+ /* We may have already opened the device. */
+ if (fd < 0) {
+ fd = drive_open_disk(dp, NULL, 0);
+ opened_here = 1;
+ }
+
+ if (fd >= 0) {
+ int status;
+ struct mode_geometry *page4;
+ struct scsi_ms_header header;
+ union {
+ struct mode_geometry page4;
+ char rawbuf[MAX_MODE_SENSE_SIZE];
+ } u_page4;
+
+ page4 = &u_page4.page4;
+ (void) memset(&u_page4, 0, sizeof (u_page4));
+
+ status = uscsi_mode_sense(fd, DAD_MODE_GEOMETRY,
+ MODE_SENSE_PC_DEFAULT, (caddr_t)page4, MAX_MODE_SENSE_SIZE,
+ &header);
+
+ if (status) {
+ status = uscsi_mode_sense(fd, DAD_MODE_GEOMETRY,
+ MODE_SENSE_PC_SAVED, (caddr_t)page4, MAX_MODE_SENSE_SIZE,
+ &header);
+ }
+
+ if (status) {
+ status = uscsi_mode_sense(fd, DAD_MODE_GEOMETRY,
+ MODE_SENSE_PC_CURRENT, (caddr_t)page4, MAX_MODE_SENSE_SIZE,
+ &header);
+ }
+
+ if (!status) {
+#ifdef _LITTLE_ENDIAN
+ page4->rpm = ntohs(page4->rpm);
+#endif /* _LITTLE_ENDIAN */
+
+ rpm = page4->rpm;
+ }
+
+ if (opened_here) {
+ (void) close(fd);
+ }
+ }
+
+ return (rpm);
+}
+
+/*
+ * ******** the rest of this is uscsi stuff for the drv type ********
+ */
+
+/*
+ * We try a get_configuration uscsi cmd. If that fails, try a
+ * atapi_capabilities cmd. If both fail then this is an older CD-ROM.
+ */
+static int
+get_cdrom_drvtype(int fd)
+{
+ union scsi_cdb cdb;
+ struct uscsi_cmd cmd;
+ uchar_t buff[SCSIBUFLEN];
+
+ fill_general_page_cdb_g1(&cdb, SCMD_GET_CONFIGURATION, 0,
+ b0(sizeof (buff)), b1(sizeof (buff)));
+ fill_command_g1(&cmd, &cdb, (caddr_t)buff, sizeof (buff));
+
+ if (ioctl(fd, USCSICMD, &cmd) >= 0) {
+ struct get_configuration *config;
+ struct conf_feature *feature;
+ int flen;
+
+ /* The first profile is the preferred one for the drive. */
+ config = (struct get_configuration *)buff;
+ feature = &config->feature;
+ flen = feature->len / sizeof (struct profile_list);
+ if (flen > 0) {
+ int prof_num;
+
+ prof_num = (int)convnum(feature->features.plist[0].profile, 2);
+
+ if (dm_debug > 1) {
+ (void) fprintf(stderr, "INFO: uscsi get_configuration %d\n",
+ prof_num);
+ }
+
+ switch (prof_num) {
+ case PROF_MAGNETO_OPTICAL:
+ return (DM_DT_MO_ERASABLE);
+ case PROF_OPTICAL_WO:
+ return (DM_DT_MO_WRITEONCE);
+ case PROF_OPTICAL_ASMO:
+ return (DM_DT_AS_MO);
+ case PROF_CDROM:
+ return (DM_DT_CDROM);
+ case PROF_CDR:
+ return (DM_DT_CDR);
+ case PROF_CDRW:
+ return (DM_DT_CDRW);
+ case PROF_DVDROM:
+ return (DM_DT_DVDROM);
+ case PROF_DVDRAM:
+ return (DM_DT_DVDRAM);
+ case PROF_DVDRW_REST:
+ return (DM_DT_DVDRW);
+ case PROF_DVDRW_SEQ:
+ return (DM_DT_DVDRW);
+ case PROF_DVDRW:
+ return (DM_DT_DVDRW);
+ case PROF_DDCD_ROM:
+ return (DM_DT_DDCDROM);
+ case PROF_DDCD_R:
+ return (DM_DT_DDCDR);
+ case PROF_DDCD_RW:
+ return (DM_DT_DDCDRW);
+ }
+ }
+ }
+
+ /* see if the atapi capabilities give anything */
+ return (check_atapi(fd));
+}
+
+static int
+check_atapi(int fd)
+{
+ union scsi_cdb cdb;
+ struct uscsi_cmd cmd;
+ uchar_t buff[SCSIBUFLEN];
+
+ fill_mode_page_cdb(&cdb, ATAPI_CAPABILITIES);
+ fill_command_g1(&cmd, &cdb, (caddr_t)buff, sizeof (buff));
+
+ if (ioctl(fd, USCSICMD, &cmd) >= 0) {
+ int bdesclen;
+ struct capabilities *cap;
+ struct mode_header_g2 *mode;
+
+ mode = (struct mode_header_g2 *)buff;
+
+ bdesclen = (int)convnum(mode->desclen, 2);
+ cap = (struct capabilities *)
+ &buff[sizeof (struct mode_header_g2) + bdesclen];
+
+ if (dm_debug > 1) {
+ (void) fprintf(stderr, "INFO: uscsi atapi capabilities\n");
+ }
+
+ /* These are in order of how we want to report the drv type. */
+ if (cap->dvdram_write) {
+ return (DM_DT_DVDRAM);
+ }
+ if (cap->dvdr_write) {
+ return (DM_DT_DVDR);
+ }
+ if (cap->dvdrom_read) {
+ return (DM_DT_DVDROM);
+ }
+ if (cap->cdrw_write) {
+ return (DM_DT_CDRW);
+ }
+ if (cap->cdr_write) {
+ return (DM_DT_CDR);
+ }
+ if (cap->cdr_read) {
+ return (DM_DT_CDROM);
+ }
+ }
+
+ /* everything failed, so this is an older CD-ROM */
+ if (dm_debug > 1) {
+ (void) fprintf(stderr, "INFO: uscsi failed\n");
+ }
+
+ return (DM_DT_CDROM);
+}
+
+static uint64_t
+convnum(uchar_t *nptr, int len)
+{
+ uint64_t value;
+
+ for (value = 0; len > 0; len--, nptr++)
+ value = (value << 8) | *nptr;
+ return (value);
+}
+
+static void
+fill_command_g1(struct uscsi_cmd *cmd, union scsi_cdb *cdb,
+ caddr_t buff, int blen)
+{
+ bzero((caddr_t)cmd, sizeof (struct uscsi_cmd));
+ bzero(buff, blen);
+
+ cmd->uscsi_cdb = (caddr_t)cdb;
+ cmd->uscsi_cdblen = CDB_GROUP1;
+
+ cmd->uscsi_bufaddr = buff;
+ cmd->uscsi_buflen = blen;
+
+ cmd->uscsi_flags = USCSI_DIAGNOSE|USCSI_ISOLATE|USCSI_READ;
+}
+
+static void
+fill_general_page_cdb_g1(union scsi_cdb *cdb, int command, int lun,
+ uchar_t c0, uchar_t c1)
+{
+ bzero((caddr_t)cdb, sizeof (union scsi_cdb));
+ cdb->scc_cmd = command;
+ cdb->scc_lun = lun;
+ cdb->g1_count0 = c0; /* max length for page */
+ cdb->g1_count1 = c1; /* max length for page */
+}
+
+static void
+fill_mode_page_cdb(union scsi_cdb *cdb, int page)
+{
+ /* group 1 mode page */
+ bzero((caddr_t)cdb, sizeof (union scsi_cdb));
+ cdb->scc_cmd = SCMD_MODE_SENSE_G1;
+ cdb->g1_count0 = 0xff; /* max length for mode page */
+ cdb->g1_count1 = 0xff; /* max length for mode page */
+ cdb->g1_addr3 = page;
+}
+
+static int
+uscsi_mode_sense(int fd, int page_code, int page_control, caddr_t page_data,
+ int page_size, struct scsi_ms_header *header)
+{
+ caddr_t mode_sense_buf;
+ struct mode_header *hdr;
+ struct mode_page *pg;
+ int nbytes;
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+ int maximum;
+ char rqbuf[255];
+
+ /*
+ * Allocate a buffer for the mode sense headers
+ * and mode sense data itself.
+ */
+ nbytes = sizeof (struct block_descriptor) +
+ sizeof (struct mode_header) + page_size;
+ nbytes = page_size;
+ if ((mode_sense_buf = malloc((uint_t)nbytes)) == NULL) {
+ return (-1);
+ }
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset(mode_sense_buf, 0, nbytes);
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+
+ cdb.scc_cmd = SCMD_MODE_SENSE;
+ FORMG0COUNT(&cdb, (uchar_t)nbytes);
+ cdb.cdb_opaque[2] = page_control | page_code;
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP0;
+ ucmd.uscsi_bufaddr = mode_sense_buf;
+ ucmd.uscsi_buflen = nbytes;
+
+ ucmd.uscsi_flags |= USCSI_SILENT;
+ ucmd.uscsi_flags |= USCSI_READ;
+ ucmd.uscsi_timeout = 30;
+ ucmd.uscsi_flags |= USCSI_RQENABLE;
+ if (ucmd.uscsi_rqbuf == NULL) {
+ ucmd.uscsi_rqbuf = rqbuf;
+ ucmd.uscsi_rqlen = sizeof (rqbuf);
+ ucmd.uscsi_rqresid = sizeof (rqbuf);
+ }
+ ucmd.uscsi_rqstatus = IMPOSSIBLE_SCSI_STATUS;
+
+ status = ioctl(fd, USCSICMD, &ucmd);
+
+ if (status || ucmd.uscsi_status != 0) {
+ free(mode_sense_buf);
+ return (-1);
+ }
+
+ /*
+ * Verify that the returned data looks reasonabled,
+ * find the actual page data, and copy it into the
+ * user's buffer. Copy the mode_header and block_descriptor
+ * into the header structure, which can then be used to
+ * return the same data to the drive when issuing a mode select.
+ */
+ hdr = (struct mode_header *)mode_sense_buf;
+ (void) memset((caddr_t)header, 0, sizeof (struct scsi_ms_header));
+ if (hdr->bdesc_length != sizeof (struct block_descriptor) &&
+ hdr->bdesc_length != 0) {
+ free(mode_sense_buf);
+ return (-1);
+ }
+ (void) memcpy((caddr_t)header, mode_sense_buf,
+ (int) (sizeof (struct mode_header) + hdr->bdesc_length));
+ pg = (struct mode_page *)((ulong_t)mode_sense_buf +
+ sizeof (struct mode_header) + hdr->bdesc_length);
+ if (pg->code != page_code) {
+ free(mode_sense_buf);
+ return (-1);
+ }
+
+ /*
+ * Accept up to "page_size" bytes of mode sense data.
+ * This allows us to accept both CCS and SCSI-2
+ * structures, as long as we request the greater
+ * of the two.
+ */
+ maximum = page_size - sizeof (struct mode_page) - hdr->bdesc_length;
+ if (((int)pg->length) > maximum) {
+ free(mode_sense_buf);
+ return (-1);
+ }
+
+ (void) memcpy(page_data, (caddr_t)pg, MODESENSE_PAGE_LEN(pg));
+
+ free(mode_sense_buf);
+ return (0);
+}