]>
Commit | Line | Data |
---|---|---|
428870ff BB |
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 | /* | |
23 | * Copyright 2009 Sun Microsystems, Inc. All rights reserved. | |
24 | * Use is subject to license terms. | |
25 | */ | |
26 | ||
27 | #include <dlfcn.h> | |
28 | #include <errno.h> | |
29 | #include <libintl.h> | |
30 | #include <link.h> | |
31 | #include <pthread.h> | |
32 | #include <strings.h> | |
33 | #include <unistd.h> | |
34 | ||
35 | #include <libzfs.h> | |
36 | ||
6b003d7c BB |
37 | #if defined(HAVE_LIBTOPO) |
38 | ||
428870ff BB |
39 | #include <fm/libtopo.h> |
40 | #include <sys/fm/protocol.h> | |
41 | #include <sys/systeminfo.h> | |
42 | ||
43 | #include "libzfs_impl.h" | |
44 | ||
45 | /* | |
46 | * This file is responsible for determining the relationship between I/O | |
47 | * devices paths and physical locations. In the world of MPxIO and external | |
48 | * enclosures, the device path is not synonymous with the physical location. | |
49 | * If you remove a drive and insert it into a different slot, it will end up | |
50 | * with the same path under MPxIO. If you recable storage enclosures, the | |
51 | * device paths may change. All of this makes it difficult to implement the | |
52 | * 'autoreplace' property, which is supposed to automatically manage disk | |
53 | * replacement based on physical slot. | |
54 | * | |
55 | * In order to work around these limitations, we have a per-vdev FRU property | |
56 | * that is the libtopo path (minus disk-specific authority information) to the | |
57 | * physical location of the device on the system. This is an optional | |
58 | * property, and is only needed when using the 'autoreplace' property or when | |
59 | * generating FMA faults against vdevs. | |
60 | */ | |
61 | ||
62 | /* | |
63 | * Because the FMA packages depend on ZFS, we have to dlopen() libtopo in case | |
64 | * it is not present. We only need this once per library instance, so it is | |
65 | * not part of the libzfs handle. | |
66 | */ | |
67 | static void *_topo_dlhandle; | |
68 | static topo_hdl_t *(*_topo_open)(int, const char *, int *); | |
69 | static void (*_topo_close)(topo_hdl_t *); | |
70 | static char *(*_topo_snap_hold)(topo_hdl_t *, const char *, int *); | |
71 | static void (*_topo_snap_release)(topo_hdl_t *); | |
72 | static topo_walk_t *(*_topo_walk_init)(topo_hdl_t *, const char *, | |
73 | topo_walk_cb_t, void *, int *); | |
74 | static int (*_topo_walk_step)(topo_walk_t *, int); | |
75 | static void (*_topo_walk_fini)(topo_walk_t *); | |
76 | static void (*_topo_hdl_strfree)(topo_hdl_t *, char *); | |
77 | static char *(*_topo_node_name)(tnode_t *); | |
78 | static int (*_topo_prop_get_string)(tnode_t *, const char *, const char *, | |
79 | char **, int *); | |
80 | static int (*_topo_node_fru)(tnode_t *, nvlist_t **, nvlist_t *, int *); | |
81 | static int (*_topo_fmri_nvl2str)(topo_hdl_t *, nvlist_t *, char **, int *); | |
82 | static int (*_topo_fmri_strcmp_noauth)(topo_hdl_t *, const char *, | |
83 | const char *); | |
84 | ||
85 | #define ZFS_FRU_HASH_SIZE 257 | |
86 | ||
87 | static size_t | |
88 | fru_strhash(const char *key) | |
89 | { | |
90 | ulong_t g, h = 0; | |
91 | const char *p; | |
92 | ||
93 | for (p = key; *p != '\0'; p++) { | |
94 | h = (h << 4) + *p; | |
95 | ||
96 | if ((g = (h & 0xf0000000)) != 0) { | |
97 | h ^= (g >> 24); | |
98 | h ^= g; | |
99 | } | |
100 | } | |
101 | ||
102 | return (h % ZFS_FRU_HASH_SIZE); | |
103 | } | |
104 | ||
105 | static int | |
106 | libzfs_fru_gather(topo_hdl_t *thp, tnode_t *tn, void *arg) | |
107 | { | |
108 | libzfs_handle_t *hdl = arg; | |
109 | nvlist_t *fru; | |
110 | char *devpath, *frustr; | |
111 | int err; | |
112 | libzfs_fru_t *frup; | |
113 | size_t idx; | |
114 | ||
115 | /* | |
116 | * If this is the chassis node, and we don't yet have the system | |
117 | * chassis ID, then fill in this value now. | |
118 | */ | |
119 | if (hdl->libzfs_chassis_id[0] == '\0' && | |
120 | strcmp(_topo_node_name(tn), "chassis") == 0) { | |
121 | if (_topo_prop_get_string(tn, FM_FMRI_AUTHORITY, | |
122 | FM_FMRI_AUTH_CHASSIS, &devpath, &err) == 0) | |
123 | (void) strlcpy(hdl->libzfs_chassis_id, devpath, | |
124 | sizeof (hdl->libzfs_chassis_id)); | |
125 | } | |
126 | ||
127 | /* | |
128 | * Skip non-disk nodes. | |
129 | */ | |
130 | if (strcmp(_topo_node_name(tn), "disk") != 0) | |
131 | return (TOPO_WALK_NEXT); | |
132 | ||
133 | /* | |
134 | * Get the devfs path and FRU. | |
135 | */ | |
136 | if (_topo_prop_get_string(tn, "io", "devfs-path", &devpath, &err) != 0) | |
137 | return (TOPO_WALK_NEXT); | |
138 | ||
139 | if (libzfs_fru_lookup(hdl, devpath) != NULL) { | |
140 | _topo_hdl_strfree(thp, devpath); | |
141 | return (TOPO_WALK_NEXT); | |
142 | } | |
143 | ||
144 | if (_topo_node_fru(tn, &fru, NULL, &err) != 0) { | |
145 | _topo_hdl_strfree(thp, devpath); | |
146 | return (TOPO_WALK_NEXT); | |
147 | } | |
148 | ||
149 | /* | |
150 | * Convert the FRU into a string. | |
151 | */ | |
152 | if (_topo_fmri_nvl2str(thp, fru, &frustr, &err) != 0) { | |
153 | nvlist_free(fru); | |
154 | _topo_hdl_strfree(thp, devpath); | |
155 | return (TOPO_WALK_NEXT); | |
156 | } | |
157 | ||
158 | nvlist_free(fru); | |
159 | ||
160 | /* | |
161 | * Finally, we have a FRU string and device path. Add it to the hash. | |
162 | */ | |
163 | if ((frup = calloc(sizeof (libzfs_fru_t), 1)) == NULL) { | |
164 | _topo_hdl_strfree(thp, devpath); | |
165 | _topo_hdl_strfree(thp, frustr); | |
166 | return (TOPO_WALK_NEXT); | |
167 | } | |
168 | ||
169 | if ((frup->zf_device = strdup(devpath)) == NULL || | |
170 | (frup->zf_fru = strdup(frustr)) == NULL) { | |
171 | free(frup->zf_device); | |
172 | free(frup); | |
173 | _topo_hdl_strfree(thp, devpath); | |
174 | _topo_hdl_strfree(thp, frustr); | |
175 | return (TOPO_WALK_NEXT); | |
176 | } | |
177 | ||
178 | _topo_hdl_strfree(thp, devpath); | |
179 | _topo_hdl_strfree(thp, frustr); | |
180 | ||
181 | idx = fru_strhash(frup->zf_device); | |
182 | frup->zf_chain = hdl->libzfs_fru_hash[idx]; | |
183 | hdl->libzfs_fru_hash[idx] = frup; | |
184 | frup->zf_next = hdl->libzfs_fru_list; | |
185 | hdl->libzfs_fru_list = frup; | |
186 | ||
187 | return (TOPO_WALK_NEXT); | |
188 | } | |
189 | ||
190 | /* | |
191 | * Called during initialization to setup the dynamic libtopo connection. | |
192 | */ | |
193 | #pragma init(libzfs_init_fru) | |
194 | static void | |
195 | libzfs_init_fru(void) | |
196 | { | |
197 | char path[MAXPATHLEN]; | |
198 | char isa[257]; | |
199 | ||
200 | #if defined(_LP64) | |
201 | if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0) | |
202 | isa[0] = '\0'; | |
203 | #else | |
204 | isa[0] = '\0'; | |
205 | #endif | |
206 | (void) snprintf(path, sizeof (path), | |
207 | "/usr/lib/fm/%s/libtopo.so", isa); | |
208 | ||
209 | if ((_topo_dlhandle = dlopen(path, RTLD_LAZY)) == NULL) | |
210 | return; | |
211 | ||
212 | _topo_open = (topo_hdl_t *(*)()) | |
213 | dlsym(_topo_dlhandle, "topo_open"); | |
214 | _topo_close = (void (*)()) | |
215 | dlsym(_topo_dlhandle, "topo_close"); | |
216 | _topo_snap_hold = (char *(*)()) | |
217 | dlsym(_topo_dlhandle, "topo_snap_hold"); | |
218 | _topo_snap_release = (void (*)()) | |
219 | dlsym(_topo_dlhandle, "topo_snap_release"); | |
220 | _topo_walk_init = (topo_walk_t *(*)()) | |
221 | dlsym(_topo_dlhandle, "topo_walk_init"); | |
222 | _topo_walk_step = (int (*)()) | |
223 | dlsym(_topo_dlhandle, "topo_walk_step"); | |
224 | _topo_walk_fini = (void (*)()) | |
225 | dlsym(_topo_dlhandle, "topo_walk_fini"); | |
226 | _topo_hdl_strfree = (void (*)()) | |
227 | dlsym(_topo_dlhandle, "topo_hdl_strfree"); | |
228 | _topo_node_name = (char *(*)()) | |
229 | dlsym(_topo_dlhandle, "topo_node_name"); | |
230 | _topo_prop_get_string = (int (*)()) | |
231 | dlsym(_topo_dlhandle, "topo_prop_get_string"); | |
232 | _topo_node_fru = (int (*)()) | |
233 | dlsym(_topo_dlhandle, "topo_node_fru"); | |
234 | _topo_fmri_nvl2str = (int (*)()) | |
235 | dlsym(_topo_dlhandle, "topo_fmri_nvl2str"); | |
236 | _topo_fmri_strcmp_noauth = (int (*)()) | |
237 | dlsym(_topo_dlhandle, "topo_fmri_strcmp_noauth"); | |
238 | ||
239 | if (_topo_open == NULL || _topo_close == NULL || | |
240 | _topo_snap_hold == NULL || _topo_snap_release == NULL || | |
241 | _topo_walk_init == NULL || _topo_walk_step == NULL || | |
242 | _topo_walk_fini == NULL || _topo_hdl_strfree == NULL || | |
243 | _topo_node_name == NULL || _topo_prop_get_string == NULL || | |
244 | _topo_node_fru == NULL || _topo_fmri_nvl2str == NULL || | |
245 | _topo_fmri_strcmp_noauth == NULL) { | |
246 | (void) dlclose(_topo_dlhandle); | |
247 | _topo_dlhandle = NULL; | |
248 | } | |
249 | } | |
250 | ||
251 | /* | |
252 | * Refresh the mappings from device path -> FMRI. We do this by walking the | |
253 | * hc topology looking for disk nodes, and recording the io/devfs-path and FRU. | |
254 | * Note that we strip out the disk-specific authority information (serial, | |
255 | * part, revision, etc) so that we are left with only the identifying | |
256 | * characteristics of the slot (hc path and chassis-id). | |
257 | */ | |
258 | void | |
259 | libzfs_fru_refresh(libzfs_handle_t *hdl) | |
260 | { | |
261 | int err; | |
262 | char *uuid; | |
263 | topo_hdl_t *thp; | |
264 | topo_walk_t *twp; | |
265 | ||
266 | if (_topo_dlhandle == NULL) | |
267 | return; | |
268 | ||
269 | /* | |
270 | * Clear the FRU hash and initialize our basic structures. | |
271 | */ | |
272 | libzfs_fru_clear(hdl, B_FALSE); | |
273 | ||
274 | if ((hdl->libzfs_topo_hdl = _topo_open(TOPO_VERSION, | |
275 | NULL, &err)) == NULL) | |
276 | return; | |
277 | ||
278 | thp = hdl->libzfs_topo_hdl; | |
279 | ||
280 | if ((uuid = _topo_snap_hold(thp, NULL, &err)) == NULL) | |
281 | return; | |
282 | ||
283 | _topo_hdl_strfree(thp, uuid); | |
284 | ||
285 | if (hdl->libzfs_fru_hash == NULL && | |
286 | (hdl->libzfs_fru_hash = | |
cae5b340 | 287 | calloc(ZFS_FRU_HASH_SIZE, sizeof (void *))) == NULL) |
428870ff BB |
288 | return; |
289 | ||
290 | /* | |
291 | * We now have a topo snapshot, so iterate over the hc topology looking | |
292 | * for disks to add to the hash. | |
293 | */ | |
294 | twp = _topo_walk_init(thp, FM_FMRI_SCHEME_HC, | |
295 | libzfs_fru_gather, hdl, &err); | |
296 | if (twp != NULL) { | |
297 | (void) _topo_walk_step(twp, TOPO_WALK_CHILD); | |
298 | _topo_walk_fini(twp); | |
299 | } | |
300 | } | |
301 | ||
302 | /* | |
303 | * Given a devfs path, return the FRU for the device, if known. This will | |
304 | * automatically call libzfs_fru_refresh() if it hasn't already been called by | |
305 | * the consumer. The string returned is valid until the next call to | |
306 | * libzfs_fru_refresh(). | |
307 | */ | |
308 | const char * | |
309 | libzfs_fru_lookup(libzfs_handle_t *hdl, const char *devpath) | |
310 | { | |
311 | size_t idx = fru_strhash(devpath); | |
312 | libzfs_fru_t *frup; | |
313 | ||
314 | if (hdl->libzfs_fru_hash == NULL) | |
315 | libzfs_fru_refresh(hdl); | |
316 | ||
317 | if (hdl->libzfs_fru_hash == NULL) | |
318 | return (NULL); | |
319 | ||
320 | for (frup = hdl->libzfs_fru_hash[idx]; frup != NULL; | |
321 | frup = frup->zf_chain) { | |
322 | if (strcmp(devpath, frup->zf_device) == 0) | |
323 | return (frup->zf_fru); | |
324 | } | |
325 | ||
326 | return (NULL); | |
327 | } | |
328 | ||
329 | /* | |
330 | * Given a fru path, return the device path. This will automatically call | |
331 | * libzfs_fru_refresh() if it hasn't already been called by the consumer. The | |
332 | * string returned is valid until the next call to libzfs_fru_refresh(). | |
333 | */ | |
334 | const char * | |
335 | libzfs_fru_devpath(libzfs_handle_t *hdl, const char *fru) | |
336 | { | |
337 | libzfs_fru_t *frup; | |
338 | size_t idx; | |
339 | ||
340 | if (hdl->libzfs_fru_hash == NULL) | |
341 | libzfs_fru_refresh(hdl); | |
342 | ||
343 | if (hdl->libzfs_fru_hash == NULL) | |
344 | return (NULL); | |
345 | ||
346 | for (idx = 0; idx < ZFS_FRU_HASH_SIZE; idx++) { | |
347 | for (frup = hdl->libzfs_fru_hash[idx]; frup != NULL; | |
348 | frup = frup->zf_next) { | |
349 | if (_topo_fmri_strcmp_noauth(hdl->libzfs_topo_hdl, | |
350 | fru, frup->zf_fru)) | |
351 | return (frup->zf_device); | |
352 | } | |
353 | } | |
354 | ||
355 | return (NULL); | |
356 | } | |
357 | ||
358 | /* | |
359 | * Change the stored FRU for the given vdev. | |
360 | */ | |
361 | int | |
362 | zpool_fru_set(zpool_handle_t *zhp, uint64_t vdev_guid, const char *fru) | |
363 | { | |
a08ee875 | 364 | zfs_cmd_t zc = {"\0"}; |
428870ff BB |
365 | |
366 | (void) strncpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name)); | |
367 | (void) strncpy(zc.zc_value, fru, sizeof (zc.zc_value)); | |
368 | zc.zc_guid = vdev_guid; | |
369 | ||
370 | if (zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_VDEV_SETFRU, &zc) != 0) | |
371 | return (zpool_standard_error_fmt(zhp->zpool_hdl, errno, | |
372 | dgettext(TEXT_DOMAIN, "cannot set FRU"))); | |
373 | ||
374 | return (0); | |
375 | } | |
376 | ||
377 | /* | |
378 | * Compare to two FRUs, ignoring any authority information. | |
379 | */ | |
380 | boolean_t | |
381 | libzfs_fru_compare(libzfs_handle_t *hdl, const char *a, const char *b) | |
382 | { | |
383 | if (hdl->libzfs_fru_hash == NULL) | |
384 | libzfs_fru_refresh(hdl); | |
385 | ||
386 | if (hdl->libzfs_fru_hash == NULL) | |
387 | return (strcmp(a, b) == 0); | |
388 | ||
389 | return (_topo_fmri_strcmp_noauth(hdl->libzfs_topo_hdl, a, b)); | |
390 | } | |
391 | ||
392 | /* | |
393 | * This special function checks to see whether the FRU indicates it's supposed | |
394 | * to be in the system chassis, but the chassis-id doesn't match. This can | |
395 | * happen in a clustered case, where both head nodes have the same logical | |
396 | * disk, but opening the device on the other head node is meaningless. | |
397 | */ | |
398 | boolean_t | |
399 | libzfs_fru_notself(libzfs_handle_t *hdl, const char *fru) | |
400 | { | |
401 | const char *chassisid; | |
402 | size_t len; | |
403 | ||
404 | if (hdl->libzfs_fru_hash == NULL) | |
405 | libzfs_fru_refresh(hdl); | |
406 | ||
407 | if (hdl->libzfs_chassis_id[0] == '\0') | |
408 | return (B_FALSE); | |
409 | ||
410 | if (strstr(fru, "/chassis=0/") == NULL) | |
411 | return (B_FALSE); | |
412 | ||
413 | if ((chassisid = strstr(fru, ":chassis-id=")) == NULL) | |
414 | return (B_FALSE); | |
415 | ||
416 | chassisid += 12; | |
417 | len = strlen(hdl->libzfs_chassis_id); | |
418 | if (strncmp(chassisid, hdl->libzfs_chassis_id, len) == 0 && | |
419 | (chassisid[len] == '/' || chassisid[len] == ':')) | |
420 | return (B_FALSE); | |
421 | ||
422 | return (B_TRUE); | |
423 | } | |
424 | ||
425 | /* | |
426 | * Clear memory associated with the FRU hash. | |
427 | */ | |
428 | void | |
429 | libzfs_fru_clear(libzfs_handle_t *hdl, boolean_t final) | |
430 | { | |
431 | libzfs_fru_t *frup; | |
432 | ||
433 | while ((frup = hdl->libzfs_fru_list) != NULL) { | |
434 | hdl->libzfs_fru_list = frup->zf_next; | |
435 | free(frup->zf_device); | |
436 | free(frup->zf_fru); | |
437 | free(frup); | |
438 | } | |
439 | ||
440 | hdl->libzfs_fru_list = NULL; | |
441 | ||
442 | if (hdl->libzfs_topo_hdl != NULL) { | |
443 | _topo_snap_release(hdl->libzfs_topo_hdl); | |
444 | _topo_close(hdl->libzfs_topo_hdl); | |
445 | hdl->libzfs_topo_hdl = NULL; | |
446 | } | |
447 | ||
448 | if (final) { | |
449 | free(hdl->libzfs_fru_hash); | |
450 | } else if (hdl->libzfs_fru_hash != NULL) { | |
451 | bzero(hdl->libzfs_fru_hash, | |
452 | ZFS_FRU_HASH_SIZE * sizeof (void *)); | |
453 | } | |
454 | } | |
6b003d7c BB |
455 | |
456 | #else /* HAVE_LIBTOPO */ | |
457 | ||
cae5b340 AX |
458 | /* |
459 | * Compare to two FRUs, ignoring any authority information. | |
460 | */ | |
461 | boolean_t | |
462 | libzfs_fru_compare(libzfs_handle_t *hdl, const char *a, const char *b) | |
463 | { | |
464 | return (B_FALSE); | |
465 | } | |
466 | ||
6b003d7c BB |
467 | /* |
468 | * Clear memory associated with the FRU hash. | |
469 | */ | |
470 | void | |
471 | libzfs_fru_clear(libzfs_handle_t *hdl, boolean_t final) | |
472 | { | |
6b003d7c BB |
473 | } |
474 | ||
475 | #endif /* HAVE_LIBTOPO */ |