1 /*
2 * virscsi.c: helper APIs for managing host SCSI devices
3 *
4 * Copyright (C) 2013-2014 Red Hat, Inc.
5 * Copyright (C) 2013 Fujitsu, Inc.
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 <dirent.h>
25 #include <fcntl.h>
26 #include <inttypes.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30
31 #include "virlog.h"
32 #include "virscsi.h"
33 #include "virfile.h"
34 #include "virstring.h"
35 #include "virerror.h"
36 #include "viralloc.h"
37
38 #define SYSFS_SCSI_DEVICES "/sys/bus/scsi/devices"
39
40 #define VIR_FROM_THIS VIR_FROM_NONE
41
42 VIR_LOG_INIT("util.scsi");
43
44 struct _virUsedByInfo {
45 char *drvname; /* which driver */
46 char *domname; /* which domain */
47 };
48 typedef struct _virUsedByInfo virUsedByInfo;
49
50 struct _virSCSIDevice {
51 unsigned int adapter;
52 unsigned int bus;
53 unsigned int target;
54 unsigned long long unit;
55
56 char *name; /* adapter:bus:target:unit */
57 char *id; /* model:vendor */
58 char *sg_path; /* e.g. /dev/sg2 */
59 virUsedByInfo **used_by; /* driver:domain(s) using this dev */
60 size_t n_used_by; /* how many domains are using this dev */
61
62 bool readonly;
63 bool shareable;
64 };
65
66 struct _virSCSIDeviceList {
67 virObjectLockable parent;
68 size_t count;
69 virSCSIDevice **devs;
70 };
71
72 static virClass *virSCSIDeviceListClass;
73
74 static void virSCSIDeviceListDispose(void *obj);
75
76 static int
virSCSIOnceInit(void)77 virSCSIOnceInit(void)
78 {
79 if (!VIR_CLASS_NEW(virSCSIDeviceList, virClassForObjectLockable()))
80 return -1;
81
82 return 0;
83 }
84
85 VIR_ONCE_GLOBAL_INIT(virSCSI);
86
87 static int
virSCSIDeviceGetAdapterId(const char * adapter,unsigned int * adapter_id)88 virSCSIDeviceGetAdapterId(const char *adapter,
89 unsigned int *adapter_id)
90 {
91 if (STRPREFIX(adapter, "scsi_host") &&
92 virStrToLong_ui(adapter + strlen("scsi_host"),
93 NULL, 0, adapter_id) == 0)
94 return 0;
95 virReportError(VIR_ERR_INTERNAL_ERROR,
96 _("Cannot parse adapter '%s'"), adapter);
97 return -1;
98 }
99
100 char *
virSCSIDeviceGetSgName(const char * sysfs_prefix,const char * adapter,unsigned int bus,unsigned int target,unsigned long long unit)101 virSCSIDeviceGetSgName(const char *sysfs_prefix,
102 const char *adapter,
103 unsigned int bus,
104 unsigned int target,
105 unsigned long long unit)
106 {
107 g_autoptr(DIR) dir = NULL;
108 struct dirent *entry;
109 g_autofree char *path = NULL;
110 unsigned int adapter_id;
111 const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_DEVICES;
112
113 if (virSCSIDeviceGetAdapterId(adapter, &adapter_id) < 0)
114 return NULL;
115
116 path = g_strdup_printf("%s/%d:%u:%u:%llu/scsi_generic", prefix, adapter_id,
117 bus, target, unit);
118
119 if (virDirOpen(&dir, path) < 0)
120 return NULL;
121
122 /* Assume a single directory entry */
123 if (virDirRead(dir, &entry, path) > 0)
124 return g_strdup(entry->d_name);
125
126 return NULL;
127 }
128
129 /* Returns device name (e.g. "sdc") on success, or NULL
130 * on failure.
131 */
132 char *
virSCSIDeviceGetDevName(const char * sysfs_prefix,const char * adapter,unsigned int bus,unsigned int target,unsigned long long unit)133 virSCSIDeviceGetDevName(const char *sysfs_prefix,
134 const char *adapter,
135 unsigned int bus,
136 unsigned int target,
137 unsigned long long unit)
138 {
139 g_autoptr(DIR) dir = NULL;
140 struct dirent *entry;
141 g_autofree char *path = NULL;
142 unsigned int adapter_id;
143 const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_DEVICES;
144
145 if (virSCSIDeviceGetAdapterId(adapter, &adapter_id) < 0)
146 return NULL;
147
148 path = g_strdup_printf("%s/%d:%u:%u:%llu/block", prefix, adapter_id, bus,
149 target, unit);
150
151 if (virDirOpen(&dir, path) < 0)
152 return NULL;
153
154 if (virDirRead(dir, &entry, path) > 0)
155 return g_strdup(entry->d_name);
156
157 return NULL;
158 }
159
160 virSCSIDevice *
virSCSIDeviceNew(const char * sysfs_prefix,const char * adapter,unsigned int bus,unsigned int target,unsigned long long unit,bool readonly,bool shareable)161 virSCSIDeviceNew(const char *sysfs_prefix,
162 const char *adapter,
163 unsigned int bus,
164 unsigned int target,
165 unsigned long long unit,
166 bool readonly,
167 bool shareable)
168 {
169 g_autoptr(virSCSIDevice) dev = NULL;
170 g_autofree char *sg = NULL;
171 g_autofree char *vendor_path = NULL;
172 g_autofree char *model_path = NULL;
173 g_autofree char *vendor = NULL;
174 g_autofree char *model = NULL;
175 const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_DEVICES;
176
177 dev = g_new0(virSCSIDevice, 1);
178
179 dev->bus = bus;
180 dev->target = target;
181 dev->unit = unit;
182 dev->readonly = readonly;
183 dev->shareable = shareable;
184
185 if (!(sg = virSCSIDeviceGetSgName(prefix, adapter, bus, target, unit)))
186 return NULL;
187
188 if (virSCSIDeviceGetAdapterId(adapter, &dev->adapter) < 0)
189 return NULL;
190
191 dev->name = g_strdup_printf("%d:%u:%u:%llu", dev->adapter,
192 dev->bus, dev->target, dev->unit);
193 dev->sg_path = g_strdup_printf("%s/%s",
194 sysfs_prefix ? sysfs_prefix : "/dev", sg);
195
196 if (!virFileExists(dev->sg_path)) {
197 virReportSystemError(errno,
198 _("SCSI device '%s': could not access %s"),
199 dev->name, dev->sg_path);
200 return NULL;
201 }
202
203 vendor_path = g_strdup_printf("%s/%s/vendor", prefix, dev->name);
204 model_path = g_strdup_printf("%s/%s/model", prefix, dev->name);
205
206 if (virFileReadAll(vendor_path, 1024, &vendor) < 0)
207 return NULL;
208
209 if (virFileReadAll(model_path, 1024, &model) < 0)
210 return NULL;
211
212 virTrimSpaces(vendor, NULL);
213 virTrimSpaces(model, NULL);
214
215 dev->id = g_strdup_printf("%s:%s", vendor, model);
216
217 return g_steal_pointer(&dev);
218 }
219
220 static void
virSCSIDeviceUsedByInfoFree(virUsedByInfo * used_by)221 virSCSIDeviceUsedByInfoFree(virUsedByInfo *used_by)
222 {
223 g_free(used_by->drvname);
224 g_free(used_by->domname);
225 g_free(used_by);
226 }
227 G_DEFINE_AUTOPTR_CLEANUP_FUNC(virUsedByInfo, virSCSIDeviceUsedByInfoFree);
228
229 void
virSCSIDeviceFree(virSCSIDevice * dev)230 virSCSIDeviceFree(virSCSIDevice *dev)
231 {
232 size_t i;
233
234 if (!dev)
235 return;
236
237 g_free(dev->id);
238 g_free(dev->name);
239 g_free(dev->sg_path);
240 for (i = 0; i < dev->n_used_by; i++)
241 virSCSIDeviceUsedByInfoFree(dev->used_by[i]);
242 g_free(dev->used_by);
243 g_free(dev);
244 }
245
246 int
virSCSIDeviceSetUsedBy(virSCSIDevice * dev,const char * drvname,const char * domname)247 virSCSIDeviceSetUsedBy(virSCSIDevice *dev,
248 const char *drvname,
249 const char *domname)
250 {
251 g_autoptr(virUsedByInfo) copy = NULL;
252
253 copy = g_new0(virUsedByInfo, 1);
254 copy->drvname = g_strdup(drvname);
255 copy->domname = g_strdup(domname);
256
257 VIR_APPEND_ELEMENT(dev->used_by, dev->n_used_by, copy);
258
259 return 0;
260 }
261
262 bool
virSCSIDeviceIsAvailable(virSCSIDevice * dev)263 virSCSIDeviceIsAvailable(virSCSIDevice *dev)
264 {
265 return dev->n_used_by == 0;
266 }
267
268 const char *
virSCSIDeviceGetName(virSCSIDevice * dev)269 virSCSIDeviceGetName(virSCSIDevice *dev)
270 {
271 return dev->name;
272 }
273
274 const char *
virSCSIDeviceGetPath(virSCSIDevice * dev)275 virSCSIDeviceGetPath(virSCSIDevice *dev)
276 {
277 return dev->sg_path;
278 }
279
280 unsigned int
virSCSIDeviceGetAdapter(virSCSIDevice * dev)281 virSCSIDeviceGetAdapter(virSCSIDevice *dev)
282 {
283 return dev->adapter;
284 }
285
286 unsigned int
virSCSIDeviceGetBus(virSCSIDevice * dev)287 virSCSIDeviceGetBus(virSCSIDevice *dev)
288 {
289 return dev->bus;
290 }
291
292 unsigned int
virSCSIDeviceGetTarget(virSCSIDevice * dev)293 virSCSIDeviceGetTarget(virSCSIDevice *dev)
294 {
295 return dev->target;
296 }
297
298 unsigned long long
virSCSIDeviceGetUnit(virSCSIDevice * dev)299 virSCSIDeviceGetUnit(virSCSIDevice *dev)
300 {
301 return dev->unit;
302 }
303
304 bool
virSCSIDeviceGetReadonly(virSCSIDevice * dev)305 virSCSIDeviceGetReadonly(virSCSIDevice *dev)
306 {
307 return dev->readonly;
308 }
309
310 bool
virSCSIDeviceGetShareable(virSCSIDevice * dev)311 virSCSIDeviceGetShareable(virSCSIDevice *dev)
312 {
313 return dev->shareable;
314 }
315
316 int
virSCSIDeviceFileIterate(virSCSIDevice * dev,virSCSIDeviceFileActor actor,void * opaque)317 virSCSIDeviceFileIterate(virSCSIDevice *dev,
318 virSCSIDeviceFileActor actor,
319 void *opaque)
320 {
321 return (actor)(dev, dev->sg_path, opaque);
322 }
323
324 virSCSIDeviceList *
virSCSIDeviceListNew(void)325 virSCSIDeviceListNew(void)
326 {
327 virSCSIDeviceList *list;
328
329 if (virSCSIInitialize() < 0)
330 return NULL;
331
332 if (!(list = virObjectLockableNew(virSCSIDeviceListClass)))
333 return NULL;
334
335 return list;
336 }
337
338 static void
virSCSIDeviceListDispose(void * obj)339 virSCSIDeviceListDispose(void *obj)
340 {
341 virSCSIDeviceList *list = obj;
342 size_t i;
343
344 for (i = 0; i < list->count; i++)
345 virSCSIDeviceFree(list->devs[i]);
346
347 g_free(list->devs);
348 }
349
350 int
virSCSIDeviceListAdd(virSCSIDeviceList * list,virSCSIDevice * dev)351 virSCSIDeviceListAdd(virSCSIDeviceList *list,
352 virSCSIDevice *dev)
353 {
354 if (virSCSIDeviceListFind(list, dev)) {
355 virReportError(VIR_ERR_INTERNAL_ERROR,
356 _("Device %s already exists"),
357 dev->name);
358 return -1;
359 }
360
361 VIR_APPEND_ELEMENT(list->devs, list->count, dev);
362
363 return 0;
364 }
365
366 virSCSIDevice *
virSCSIDeviceListGet(virSCSIDeviceList * list,int idx)367 virSCSIDeviceListGet(virSCSIDeviceList *list, int idx)
368 {
369 if (idx >= list->count || idx < 0)
370 return NULL;
371
372 return list->devs[idx];
373 }
374
375 size_t
virSCSIDeviceListCount(virSCSIDeviceList * list)376 virSCSIDeviceListCount(virSCSIDeviceList *list)
377 {
378 return list->count;
379 }
380
381 virSCSIDevice *
virSCSIDeviceListSteal(virSCSIDeviceList * list,virSCSIDevice * dev)382 virSCSIDeviceListSteal(virSCSIDeviceList *list,
383 virSCSIDevice *dev)
384 {
385 virSCSIDevice *ret = NULL;
386 size_t i;
387
388 for (i = 0; i < list->count; i++) {
389 if (list->devs[i]->adapter == dev->adapter &&
390 list->devs[i]->bus == dev->bus &&
391 list->devs[i]->target == dev->target &&
392 list->devs[i]->unit == dev->unit) {
393 ret = list->devs[i];
394 VIR_DELETE_ELEMENT(list->devs, i, list->count);
395 break;
396 }
397 }
398
399 return ret;
400 }
401
402 void
virSCSIDeviceListDel(virSCSIDeviceList * list,virSCSIDevice * dev,const char * drvname,const char * domname)403 virSCSIDeviceListDel(virSCSIDeviceList *list,
404 virSCSIDevice *dev,
405 const char *drvname,
406 const char *domname)
407 {
408 size_t i;
409
410 for (i = 0; i < dev->n_used_by; i++) {
411 if (STREQ_NULLABLE(dev->used_by[i]->drvname, drvname) &&
412 STREQ_NULLABLE(dev->used_by[i]->domname, domname)) {
413 if (dev->n_used_by > 1) {
414 virSCSIDeviceUsedByInfoFree(dev->used_by[i]);
415 VIR_DELETE_ELEMENT(dev->used_by, i, dev->n_used_by);
416 } else {
417 virSCSIDeviceFree(virSCSIDeviceListSteal(list, dev));
418 }
419 break;
420 }
421 }
422 }
423
424 virSCSIDevice *
virSCSIDeviceListFind(virSCSIDeviceList * list,virSCSIDevice * dev)425 virSCSIDeviceListFind(virSCSIDeviceList *list,
426 virSCSIDevice *dev)
427 {
428 size_t i;
429
430 for (i = 0; i < list->count; i++) {
431 if (list->devs[i]->adapter == dev->adapter &&
432 list->devs[i]->bus == dev->bus &&
433 list->devs[i]->target == dev->target &&
434 list->devs[i]->unit == dev->unit)
435 return list->devs[i];
436 }
437
438 return NULL;
439 }
440