1 /*
2  * storage_backend_scsi.c: storage backend for SCSI handling
3  *
4  * Copyright (C) 2007-2008, 2013-2014 Red Hat, Inc.
5  * Copyright (C) 2007-2008 Daniel P. Berrange
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include <unistd.h>
25 #include <fcntl.h>
26 
27 #include "virerror.h"
28 #include "storage_backend_scsi.h"
29 #include "viralloc.h"
30 #include "virlog.h"
31 #include "virfile.h"
32 #include "vircommand.h"
33 #include "virstring.h"
34 #include "storage_util.h"
35 #include "node_device_conf.h"
36 #include "node_device_util.h"
37 #include "driver.h"
38 
39 #define VIR_FROM_THIS VIR_FROM_STORAGE
40 
41 VIR_LOG_INIT("storage.storage_backend_scsi");
42 
43 #define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host"
44 #define LINUX_SYSFS_SCSI_HOST_SCAN_STRING "- - -"
45 
46 typedef struct _virStoragePoolFCRefreshInfo virStoragePoolFCRefreshInfo;
47 struct _virStoragePoolFCRefreshInfo {
48     char *fchost_name;
49     unsigned char pool_uuid[VIR_UUID_BUFLEN];
50 };
51 
52 
53 static int
virStorageBackendSCSITriggerRescan(uint32_t host)54 virStorageBackendSCSITriggerRescan(uint32_t host)
55 {
56     VIR_AUTOCLOSE fd = -1;
57     g_autofree char *path = NULL;
58 
59     VIR_DEBUG("Triggering rescan of host %d", host);
60 
61     path = g_strdup_printf("%s/host%u/scan", LINUX_SYSFS_SCSI_HOST_PREFIX, host);
62 
63     VIR_DEBUG("Scan trigger path is '%s'", path);
64 
65     fd = open(path, O_WRONLY);
66 
67     if (fd < 0) {
68         virReportSystemError(errno,
69                              _("Could not open '%s' to trigger host scan"),
70                              path);
71         return -1;
72     }
73 
74     if (safewrite(fd,
75                   LINUX_SYSFS_SCSI_HOST_SCAN_STRING,
76                   sizeof(LINUX_SYSFS_SCSI_HOST_SCAN_STRING)) < 0) {
77         virReportSystemError(errno,
78                              _("Write to '%s' to trigger host scan failed"),
79                              path);
80         return -1;
81     }
82 
83     VIR_DEBUG("Rescan of host %d complete", host);
84     return 0;
85 }
86 
87 /**
88  * Frees opaque data
89  *
90  * @opaque Data to be freed
91  */
92 static void
virStoragePoolFCRefreshDataFree(void * opaque)93 virStoragePoolFCRefreshDataFree(void *opaque)
94 {
95     virStoragePoolFCRefreshInfo *cbdata = opaque;
96 
97     g_free(cbdata->fchost_name);
98     g_free(cbdata);
99 }
100 
101 /**
102  * Thread to handle the pool refresh after a VPORT_CREATE is done. In this
103  * case the 'udevEventHandleCallback' will be executed asynchronously as a
104  * result of the node device driver callback routine to handle when udev
105  * notices some sort of device change (such as adding a new device). It takes
106  * some amount of time (usually a few seconds) for udev to go through the
107  * process of setting up the new device.  Unfortunately, there is nothing
108  * that says "when" it's done. The immediate virStorageBackendSCSIRefreshPool
109  * done after virStorageBackendSCSIStartPool (and createVport) occurs too
110  * quickly to find any devices.
111  *
112  * So this thread is designed to wait a few seconds (5), then make the query
113  * to find the LUs for the pool.  If none yet exist, we'll try once more
114  * to find the LUs before giving up.
115  *
116  * Attempting to find devices prior to allowing udev to settle down may result
117  * in finding devices that then get deleted.
118  *
119  * @opaque Pool's Refresh Info containing name and pool object pointer
120  */
121 static void
virStoragePoolFCRefreshThread(void * opaque)122 virStoragePoolFCRefreshThread(void *opaque)
123 {
124     virStoragePoolFCRefreshInfo *cbdata = opaque;
125     const char *fchost_name = cbdata->fchost_name;
126     const unsigned char *pool_uuid = cbdata->pool_uuid;
127     virStoragePoolObj *pool = NULL;
128     virStoragePoolDef *def;
129     unsigned int host;
130     int found = 0;
131     int tries = 2;
132 
133     do {
134         sleep(5); /* Give it time */
135 
136         /* Let's see if the pool still exists -  */
137         if (!(pool = virStoragePoolObjFindPoolByUUID(pool_uuid)))
138             break;
139         def = virStoragePoolObjGetDef(pool);
140 
141         /* Return with pool lock, if active, we can get the host number,
142          * successfully, rescan, and find LUN's, then we are happy
143          */
144         VIR_DEBUG("Attempt FC Refresh for pool='%s' name='%s' tries='%d'",
145                   def->name, fchost_name, tries);
146 
147         def->allocation = def->capacity = def->available = 0;
148 
149         if (virStoragePoolObjIsActive(pool) &&
150             virSCSIHostGetNumber(fchost_name, &host) == 0 &&
151             virStorageBackendSCSITriggerRescan(host) == 0) {
152             virStoragePoolObjClearVols(pool);
153             found = virStorageBackendSCSIFindLUs(pool, host);
154         }
155         virStoragePoolObjEndAPI(&pool);
156     } while (!found && --tries);
157 
158     if (pool && !found)
159         VIR_DEBUG("FC Refresh Thread failed to find LU's");
160 
161     virStoragePoolFCRefreshDataFree(cbdata);
162 }
163 
164 static char *
getAdapterName(virStorageAdapter * adapter)165 getAdapterName(virStorageAdapter *adapter)
166 {
167     char *name = NULL;
168 
169     if (adapter->type == VIR_STORAGE_ADAPTER_TYPE_SCSI_HOST) {
170         virStorageAdapterSCSIHost *scsi_host = &adapter->data.scsi_host;
171 
172         if (scsi_host->has_parent) {
173             virPCIDeviceAddress *addr = &scsi_host->parentaddr;
174             unsigned int unique_id = scsi_host->unique_id;
175 
176             if (!(name = virSCSIHostGetNameByParentaddr(addr->domain,
177                                                         addr->bus,
178                                                         addr->slot,
179                                                         addr->function,
180                                                         unique_id)))
181                 return NULL;
182         } else {
183             name = g_strdup(scsi_host->name);
184         }
185     } else if (adapter->type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
186         virStorageAdapterFCHost *fchost = &adapter->data.fchost;
187 
188         if (!(name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {
189             virReportError(VIR_ERR_XML_ERROR,
190                            _("Failed to find SCSI host with wwnn='%s', "
191                              "wwpn='%s'"), fchost->wwnn, fchost->wwpn);
192         }
193     }
194 
195     return name;
196 }
197 
198 
199 /**
200  * @name: Name from a wwnn/wwpn lookup
201  *
202  * Validate that the @name fetched from the wwnn/wwpn is a vHBA
203  * and not an HBA as that should be a configuration error. It's only
204  * possible to use an existing wwnn/wwpn of a vHBA because that's
205  * what someone would have created using the node device create via XML
206  * functionality. Using the HBA "just because" it has a wwnn/wwpn and
207  * the characteristics of a vHBA is just not valid
208  *
209  * Returns true if the @name is OK, false on error
210  */
211 static bool
checkName(const char * name)212 checkName(const char *name)
213 {
214     unsigned int host_num;
215 
216     if (virSCSIHostGetNumber(name, &host_num) &&
217         virVHBAIsVportCapable(NULL, host_num))
218         return true;
219 
220     virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
221                    _("the wwnn/wwpn for '%s' are assigned to an HBA"), name);
222     return false;
223 }
224 
225 
226 /*
227  * Using the host# name found via wwnn/wwpn lookup in the fc_host
228  * sysfs tree to get the parent 'scsi_host#' to ensure it matches.
229  */
230 static bool
checkParent(const char * name,const char * parent_name)231 checkParent(const char *name,
232             const char *parent_name)
233 {
234     unsigned int host_num;
235     bool retval = false;
236     virConnectPtr conn = NULL;
237     g_autofree char *scsi_host_name = NULL;
238     g_autofree char *vhba_parent = NULL;
239 
240     VIR_DEBUG("name=%s, parent_name=%s", name, parent_name);
241 
242     conn = virGetConnectNodeDev();
243     if (!conn)
244         goto cleanup;
245 
246     if (virSCSIHostGetNumber(parent_name, &host_num) < 0) {
247         virReportError(VIR_ERR_XML_ERROR,
248                        _("parent '%s' is not properly formatted"),
249                        parent_name);
250         goto cleanup;
251     }
252 
253     if (!virVHBAPathExists(NULL, host_num)) {
254         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
255                        _("parent '%s' is not an fc_host for the wwnn/wwpn"),
256                        parent_name);
257         goto cleanup;
258     }
259 
260     scsi_host_name = g_strdup_printf("scsi_%s", name);
261 
262     if (!(vhba_parent = virNodeDeviceGetParentName(conn, scsi_host_name)))
263         goto cleanup;
264 
265     if (STRNEQ(parent_name, vhba_parent)) {
266         virReportError(VIR_ERR_XML_ERROR,
267                        _("Parent attribute '%s' does not match parent '%s' "
268                          "determined for the '%s' wwnn/wwpn lookup."),
269                        parent_name, vhba_parent, name);
270         goto cleanup;
271     }
272 
273     retval = true;
274 
275  cleanup:
276     virObjectUnref(conn);
277     return retval;
278 }
279 
280 
281 static int
createVport(virStoragePoolDef * def,const char * configFile,virStorageAdapterFCHost * fchost)282 createVport(virStoragePoolDef *def,
283             const char *configFile,
284             virStorageAdapterFCHost *fchost)
285 {
286     virStoragePoolFCRefreshInfo *cbdata = NULL;
287     virThread thread;
288     g_autofree char *name = NULL;
289 
290     VIR_DEBUG("configFile='%s' parent='%s', wwnn='%s' wwpn='%s'",
291               NULLSTR(configFile), NULLSTR(fchost->parent),
292               fchost->wwnn, fchost->wwpn);
293 
294     /* If we find an existing HBA/vHBA within the fc_host sysfs
295      * using the wwnn/wwpn, then a nodedev is already created for
296      * this pool and we don't have to create the vHBA
297      */
298     if ((name = virVHBAGetHostByWWN(NULL, fchost->wwnn, fchost->wwpn))) {
299         if (!(checkName(name)))
300             return -1;
301 
302         /* If a parent was provided, let's make sure the 'name' we've
303          * retrieved has the same parent. If not this will cause failure. */
304         if (!fchost->parent || checkParent(name, fchost->parent))
305             return 0;
306 
307         return -1;
308     }
309 
310     /* Since we're creating the vHBA, then we need to manage removing it
311      * as well. Since we need this setting to "live" through a libvirtd
312      * restart, we need to save the persistent configuration. So if not
313      * already defined as YES, then force the issue.
314      */
315     if (fchost->managed != VIR_TRISTATE_BOOL_YES) {
316         fchost->managed = VIR_TRISTATE_BOOL_YES;
317         if (configFile) {
318             if (virStoragePoolSaveConfig(configFile, def) < 0)
319                 return -1;
320         }
321     }
322 
323     if (!(name = virNodeDeviceCreateVport(fchost)))
324         return -1;
325 
326     /* Creating our own VPORT didn't leave enough time to find any LUN's,
327      * so, let's create a thread whose job it is to call the FindLU's with
328      * retry logic set to true. If the thread isn't created, then no big
329      * deal since it's still possible to refresh the pool later.
330      */
331     cbdata = g_new0(virStoragePoolFCRefreshInfo, 1);
332     memcpy(cbdata->pool_uuid, def->uuid, VIR_UUID_BUFLEN);
333     cbdata->fchost_name = g_steal_pointer(&name);
334 
335     if (virThreadCreateFull(&thread, false, virStoragePoolFCRefreshThread,
336                             "scsi-refresh", false, cbdata) < 0) {
337         /* Oh well - at least someone can still refresh afterwards */
338         VIR_DEBUG("Failed to create FC Pool Refresh Thread");
339         virStoragePoolFCRefreshDataFree(cbdata);
340     }
341 
342     return 0;
343 }
344 
345 
346 static int
virStorageBackendSCSICheckPool(virStoragePoolObj * pool,bool * isActive)347 virStorageBackendSCSICheckPool(virStoragePoolObj *pool,
348                                bool *isActive)
349 {
350     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
351     unsigned int host;
352     g_autofree char *path = NULL;
353     g_autofree char *name = NULL;
354 
355     *isActive = false;
356 
357     if (!(name = getAdapterName(&def->source.adapter))) {
358         /* It's normal for the pool with "fc_host" type source
359          * adapter fails to get the adapter name, since the vHBA
360          * the adapter based on might be not created yet.
361          */
362         if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
363             virResetLastError();
364             return 0;
365         } else {
366             return -1;
367         }
368     }
369 
370     if (virSCSIHostGetNumber(name, &host) < 0)
371         return -1;
372 
373     path = g_strdup_printf("%s/host%d", LINUX_SYSFS_SCSI_HOST_PREFIX, host);
374 
375     *isActive = virFileExists(path);
376 
377     return 0;
378 }
379 
380 static int
virStorageBackendSCSIRefreshPool(virStoragePoolObj * pool)381 virStorageBackendSCSIRefreshPool(virStoragePoolObj *pool)
382 {
383     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
384     unsigned int host;
385     g_autofree char *name = NULL;
386 
387     def->allocation = def->capacity = def->available = 0;
388 
389     if (!(name = getAdapterName(&def->source.adapter)))
390         return -1;
391 
392     if (virSCSIHostGetNumber(name, &host) < 0)
393         return -1;
394 
395     VIR_DEBUG("Scanning host%u", host);
396 
397     if (virStorageBackendSCSITriggerRescan(host) < 0)
398         return -1;
399 
400     if (virStorageBackendSCSIFindLUs(pool, host) < 0)
401         return -1;
402 
403     return 0;
404 }
405 
406 
407 static int
virStorageBackendSCSIStartPool(virStoragePoolObj * pool)408 virStorageBackendSCSIStartPool(virStoragePoolObj *pool)
409 {
410     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
411     const char *configFile = virStoragePoolObjGetConfigFile(pool);
412 
413     if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST)
414         return createVport(def, configFile,
415                            &def->source.adapter.data.fchost);
416 
417     return 0;
418 }
419 
420 
421 static int
virStorageBackendSCSIStopPool(virStoragePoolObj * pool)422 virStorageBackendSCSIStopPool(virStoragePoolObj *pool)
423 {
424     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
425 
426     if (def->source.adapter.type == VIR_STORAGE_ADAPTER_TYPE_FC_HOST) {
427         virConnectPtr conn;
428         int ret;
429         conn = virGetConnectNodeDev();
430         if (!conn)
431             return -1;
432 
433         ret = virNodeDeviceDeleteVport(conn,
434                                        &def->source.adapter.data.fchost);
435         virObjectUnref(conn);
436         return ret;
437     }
438 
439     return 0;
440 }
441 
442 virStorageBackend virStorageBackendSCSI = {
443     .type = VIR_STORAGE_POOL_SCSI,
444 
445     .checkPool = virStorageBackendSCSICheckPool,
446     .refreshPool = virStorageBackendSCSIRefreshPool,
447     .startPool = virStorageBackendSCSIStartPool,
448     .stopPool = virStorageBackendSCSIStopPool,
449     .uploadVol = virStorageBackendVolUploadLocal,
450     .downloadVol = virStorageBackendVolDownloadLocal,
451     .wipeVol = virStorageBackendVolWipeLocal,
452 };
453 
454 
455 int
virStorageBackendSCSIRegister(void)456 virStorageBackendSCSIRegister(void)
457 {
458     return virStorageBackendRegister(&virStorageBackendSCSI);
459 }
460