1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #pragma ident   "%Z%%M% %I%     %E% SMI"
  27 
  28 /*
  29  * Disk status library
  30  *
  31  * This library is responsible for querying health and other status information
  32  * from disk drives.  It is intended to be a generic interface, however only
  33  * SCSI (and therefore SATA) disks are currently supported.  The library is
  34  * capable of detecting the following status conditions:
  35  *
  36  *      - Predictive failure
  37  *      - Overtemp
  38  *      - Self-test failure
  39  */
  40 
  41 #include <assert.h>
  42 #include <errno.h>
  43 #include <fcntl.h>
  44 #include <libdevinfo.h>
  45 #include <libdiskstatus.h>
  46 #include <stdlib.h>
  47 #include <string.h>
  48 #include <sys/fm/io/scsi.h>
  49 #include <sys/stat.h>
  50 #include <unistd.h>
  51 
  52 #include "ds_impl.h"
  53 #include "ds_scsi.h"
  54 
  55 static ds_transport_t *ds_transports[] = {
  56         &ds_scsi_sim_transport,
  57         &ds_scsi_uscsi_transport
  58 };
  59 
  60 #define NTRANSPORTS     (sizeof (ds_transports) / sizeof (ds_transports[0]))
  61 
  62 /*
  63  * Open a handle to a disk.  This will fail if the device cannot be opened, or
  64  * if no suitable transport exists for communicating with the device.
  65  */
  66 disk_status_t *
  67 disk_status_open(const char *path, int *error)
  68 {
  69         disk_status_t *dsp;
  70         ds_transport_t *t;
  71         int i;
  72 
  73         if ((dsp = calloc(sizeof (disk_status_t), 1)) == NULL) {
  74                 *error = EDS_NOMEM;
  75                 return (NULL);
  76         }
  77 
  78         if ((dsp->ds_fd = open(path, O_RDWR)) < 0) {
  79                 *error = EDS_CANT_OPEN;
  80                 free(dsp);
  81                 return (NULL);
  82         }
  83 
  84         if ((dsp->ds_path = strdup(path)) == NULL) {
  85                 *error = EDS_NOMEM;
  86                 disk_status_close(dsp);
  87                 return (NULL);
  88         }
  89 
  90         for (i = 0; i < NTRANSPORTS; i++) {
  91                 t = ds_transports[i];
  92 
  93                 dsp->ds_transport = t;
  94 
  95                 nvlist_free(dsp->ds_state);
  96                 dsp->ds_state = NULL;
  97                 if (nvlist_alloc(&dsp->ds_state, NV_UNIQUE_NAME, 0) != 0) {
  98                         *error = EDS_NOMEM;
  99                         disk_status_close(dsp);
 100                         return (NULL);
 101                 }
 102 
 103                 if ((dsp->ds_data = t->dt_open(dsp)) == NULL) {
 104                         if (dsp->ds_error != EDS_NO_TRANSPORT) {
 105                                 *error = dsp->ds_error;
 106                                 disk_status_close(dsp);
 107                                 return (NULL);
 108                         }
 109                 } else {
 110                         dsp->ds_error = 0;
 111                         break;
 112                 }
 113         }
 114 
 115         if (dsp->ds_error == EDS_NO_TRANSPORT) {
 116                 *error = dsp->ds_error;
 117                 disk_status_close(dsp);
 118                 return (NULL);
 119         }
 120 
 121         return (dsp);
 122 }
 123 
 124 /*
 125  * Close a handle to a disk.
 126  */
 127 void
 128 disk_status_close(disk_status_t *dsp)
 129 {
 130         nvlist_free(dsp->ds_state);
 131         nvlist_free(dsp->ds_predfail);
 132         nvlist_free(dsp->ds_overtemp);
 133         nvlist_free(dsp->ds_testfail);
 134         if (dsp->ds_data)
 135                 dsp->ds_transport->dt_close(dsp->ds_data);
 136         (void) close(dsp->ds_fd);
 137         free(dsp->ds_path);
 138         free(dsp);
 139 }
 140 
 141 void
 142 disk_status_set_debug(boolean_t value)
 143 {
 144         ds_debug = value;
 145 }
 146 
 147 /*
 148  * Query basic information
 149  */
 150 const char *
 151 disk_status_path(disk_status_t *dsp)
 152 {
 153         return (dsp->ds_path);
 154 }
 155 
 156 int
 157 disk_status_errno(disk_status_t *dsp)
 158 {
 159         return (dsp->ds_error);
 160 }
 161 
 162 nvlist_t *
 163 disk_status_get(disk_status_t *dsp)
 164 {
 165         nvlist_t *nvl = NULL;
 166         nvlist_t *faults = NULL;
 167         int err;
 168 
 169         /*
 170          * Scan (or rescan) the current device.
 171          */
 172         nvlist_free(dsp->ds_testfail);
 173         nvlist_free(dsp->ds_predfail);
 174         nvlist_free(dsp->ds_overtemp);
 175         dsp->ds_testfail = dsp->ds_overtemp = dsp->ds_predfail = NULL;
 176         dsp->ds_faults = 0;
 177 
 178         /*
 179          * Even if there is an I/O failure when trying to scan the device, we
 180          * can still return the current state.
 181          */
 182         if (dsp->ds_transport->dt_scan(dsp->ds_data) != 0 &&
 183             dsp->ds_error != EDS_IO)
 184                 return (NULL);
 185 
 186         if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
 187                 goto nverror;
 188 
 189         if ((err = nvlist_add_string(nvl, "protocol", "scsi")) != 0 ||
 190             (err = nvlist_add_nvlist(nvl, "status", dsp->ds_state)) != 0)
 191                 goto nverror;
 192 
 193         /*
 194          * Construct the list of faults.
 195          */
 196         if ((err = nvlist_alloc(&faults, NV_UNIQUE_NAME, 0)) != 0)
 197                 goto nverror;
 198 
 199         if (dsp->ds_predfail != NULL) {
 200                 if ((err = nvlist_add_boolean_value(faults,
 201                     FM_EREPORT_SCSI_PREDFAIL,
 202                     (dsp->ds_faults & DS_FAULT_PREDFAIL) != 0)) != 0 ||
 203                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_PREDFAIL,
 204                     dsp->ds_predfail)) != 0)
 205                         goto nverror;
 206         }
 207 
 208         if (dsp->ds_testfail != NULL) {
 209                 if ((err = nvlist_add_boolean_value(faults,
 210                     FM_EREPORT_SCSI_TESTFAIL,
 211                     (dsp->ds_faults & DS_FAULT_TESTFAIL) != 0)) != 0 ||
 212                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_TESTFAIL,
 213                     dsp->ds_testfail)) != 0)
 214                         goto nverror;
 215         }
 216 
 217         if (dsp->ds_overtemp != NULL) {
 218                 if ((err = nvlist_add_boolean_value(faults,
 219                     FM_EREPORT_SCSI_OVERTEMP,
 220                     (dsp->ds_faults & DS_FAULT_OVERTEMP) != 0)) != 0 ||
 221                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_OVERTEMP,
 222                     dsp->ds_overtemp)) != 0)
 223                         goto nverror;
 224         }
 225 
 226         if ((err = nvlist_add_nvlist(nvl, "faults", faults)) != 0)
 227                 goto nverror;
 228 
 229         nvlist_free(faults);
 230         return (nvl);
 231 
 232 nverror:
 233         assert(err == ENOMEM);
 234         nvlist_free(nvl);
 235         nvlist_free(faults);
 236         (void) ds_set_errno(dsp, EDS_NOMEM);
 237         return (NULL);
 238 }