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  * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  25  */
  26 
  27 /*
  28  * Disk status library
  29  *
  30  * This library is responsible for querying health and other status information
  31  * from disk drives.  It is intended to be a generic interface, however only
  32  * SCSI (and therefore SATA) disks are currently supported.  The library is
  33  * capable of detecting the following status conditions:
  34  *
  35  *      - Predictive failure
  36  *      - Overtemp
  37  *      - Self-test failure
  38  *      - Solid State Media wearout
  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         nvlist_free(dsp->ds_ssmwearout);
 135         if (dsp->ds_data)
 136                 dsp->ds_transport->dt_close(dsp->ds_data);
 137         (void) close(dsp->ds_fd);
 138         free(dsp->ds_path);
 139         free(dsp);
 140 }
 141 
 142 void
 143 disk_status_set_debug(boolean_t value)
 144 {
 145         ds_debug = value;
 146 }
 147 
 148 /*
 149  * Query basic information
 150  */
 151 const char *
 152 disk_status_path(disk_status_t *dsp)
 153 {
 154         return (dsp->ds_path);
 155 }
 156 
 157 int
 158 disk_status_errno(disk_status_t *dsp)
 159 {
 160         return (dsp->ds_error);
 161 }
 162 
 163 nvlist_t *
 164 disk_status_get(disk_status_t *dsp)
 165 {
 166         nvlist_t *nvl = NULL;
 167         nvlist_t *faults = NULL;
 168         int err;
 169 
 170         /*
 171          * Scan (or rescan) the current device.
 172          */
 173         nvlist_free(dsp->ds_testfail);
 174         nvlist_free(dsp->ds_predfail);
 175         nvlist_free(dsp->ds_overtemp);
 176         nvlist_free(dsp->ds_ssmwearout);
 177         dsp->ds_ssmwearout = NULL;
 178         dsp->ds_testfail = dsp->ds_overtemp = dsp->ds_predfail = NULL;
 179         dsp->ds_faults = 0;
 180 
 181         /*
 182          * Even if there is an I/O failure when trying to scan the device, we
 183          * can still return the current state.
 184          */
 185         if (dsp->ds_transport->dt_scan(dsp->ds_data) != 0 &&
 186             dsp->ds_error != EDS_IO)
 187                 return (NULL);
 188 
 189         if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
 190                 goto nverror;
 191 
 192         if ((err = nvlist_add_string(nvl, "protocol", "scsi")) != 0 ||
 193             (err = nvlist_add_nvlist(nvl, "status", dsp->ds_state)) != 0)
 194                 goto nverror;
 195 
 196         /*
 197          * Construct the list of faults.
 198          */
 199         if ((err = nvlist_alloc(&faults, NV_UNIQUE_NAME, 0)) != 0)
 200                 goto nverror;
 201 
 202         if (dsp->ds_predfail != NULL) {
 203                 if ((err = nvlist_add_boolean_value(faults,
 204                     FM_EREPORT_SCSI_PREDFAIL,
 205                     (dsp->ds_faults & DS_FAULT_PREDFAIL) != 0)) != 0 ||
 206                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_PREDFAIL,
 207                     dsp->ds_predfail)) != 0)
 208                         goto nverror;
 209         }
 210 
 211         if (dsp->ds_testfail != NULL) {
 212                 if ((err = nvlist_add_boolean_value(faults,
 213                     FM_EREPORT_SCSI_TESTFAIL,
 214                     (dsp->ds_faults & DS_FAULT_TESTFAIL) != 0)) != 0 ||
 215                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_TESTFAIL,
 216                     dsp->ds_testfail)) != 0)
 217                         goto nverror;
 218         }
 219 
 220         if (dsp->ds_overtemp != NULL) {
 221                 if ((err = nvlist_add_boolean_value(faults,
 222                     FM_EREPORT_SCSI_OVERTEMP,
 223                     (dsp->ds_faults & DS_FAULT_OVERTEMP) != 0)) != 0 ||
 224                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_OVERTEMP,
 225                     dsp->ds_overtemp)) != 0)
 226                         goto nverror;
 227         }
 228 
 229         if (dsp->ds_ssmwearout != NULL) {
 230                 if ((err = nvlist_add_boolean_value(faults,
 231                     FM_EREPORT_SCSI_SSMWEAROUT,
 232                     (dsp->ds_faults & DS_FAULT_SSMWEAROUT) != 0)) != 0 ||
 233                     (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_SSMWEAROUT,
 234                     dsp->ds_ssmwearout)) != 0)
 235                         goto nverror;
 236         }
 237 
 238         if ((err = nvlist_add_nvlist(nvl, "faults", faults)) != 0)
 239                 goto nverror;
 240 
 241         nvlist_free(faults);
 242         return (nvl);
 243 
 244 nverror:
 245         assert(err == ENOMEM);
 246         nvlist_free(nvl);
 247         nvlist_free(faults);
 248         (void) ds_set_errno(dsp, EDS_NOMEM);
 249         return (NULL);
 250 }