1300906Sasomers/*-
2300906Sasomers * Copyright (c) 2011, 2012, 2013, 2014, 2016 Spectra Logic Corporation
3300906Sasomers * All rights reserved.
4300906Sasomers *
5300906Sasomers * Redistribution and use in source and binary forms, with or without
6300906Sasomers * modification, are permitted provided that the following conditions
7300906Sasomers * are met:
8300906Sasomers * 1. Redistributions of source code must retain the above copyright
9300906Sasomers *    notice, this list of conditions, and the following disclaimer,
10300906Sasomers *    without modification.
11300906Sasomers * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12300906Sasomers *    substantially similar to the "NO WARRANTY" disclaimer below
13300906Sasomers *    ("Disclaimer") and any redistribution must be conditioned upon
14300906Sasomers *    including a substantially similar Disclaimer requirement for further
15300906Sasomers *    binary redistribution.
16300906Sasomers *
17300906Sasomers * NO WARRANTY
18300906Sasomers * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19300906Sasomers * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20300906Sasomers * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
21300906Sasomers * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22300906Sasomers * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23300906Sasomers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24300906Sasomers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25300906Sasomers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26300906Sasomers * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27300906Sasomers * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28300906Sasomers * POSSIBILITY OF SUCH DAMAGES.
29300906Sasomers *
30300906Sasomers * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
31300906Sasomers */
32300906Sasomers
33300906Sasomers/**
34300906Sasomers * \file zfsd_event.cc
35300906Sasomers */
36300906Sasomers#include <sys/cdefs.h>
37300906Sasomers#include <sys/time.h>
38300906Sasomers#include <sys/fs/zfs.h>
39326298Sasomers#include <sys/vdev_impl.h>
40300906Sasomers
41300906Sasomers#include <syslog.h>
42300906Sasomers
43300906Sasomers#include <libzfs.h>
44300906Sasomers/*
45300906Sasomers * Undefine flush, defined by cpufunc.h on sparc64, because it conflicts with
46300906Sasomers * C++ flush methods
47300906Sasomers */
48300906Sasomers#undef   flush
49300906Sasomers
50300906Sasomers#include <list>
51300906Sasomers#include <map>
52300906Sasomers#include <sstream>
53300906Sasomers#include <string>
54300906Sasomers
55300906Sasomers#include <devdctl/guid.h>
56300906Sasomers#include <devdctl/event.h>
57300906Sasomers#include <devdctl/event_factory.h>
58300906Sasomers#include <devdctl/exception.h>
59300906Sasomers#include <devdctl/consumer.h>
60300906Sasomers
61300906Sasomers#include "callout.h"
62300906Sasomers#include "vdev_iterator.h"
63300906Sasomers#include "zfsd_event.h"
64300906Sasomers#include "case_file.h"
65300906Sasomers#include "vdev.h"
66300906Sasomers#include "zfsd.h"
67300906Sasomers#include "zfsd_exception.h"
68300906Sasomers#include "zpool_list.h"
69300906Sasomers
70300906Sasomers__FBSDID("$FreeBSD: stable/11/cddl/usr.sbin/zfsd/zfsd_event.cc 330733 2018-03-10 03:34:27Z asomers $");
71300906Sasomers/*============================ Namespace Control =============================*/
72300906Sasomersusing DevdCtl::Event;
73300906Sasomersusing DevdCtl::Guid;
74300906Sasomersusing DevdCtl::NVPairMap;
75300906Sasomersusing std::stringstream;
76300906Sasomers
77300906Sasomers/*=========================== Class Implementations ==========================*/
78300906Sasomers
79330733Sasomers/*-------------------------------- GeomEvent --------------------------------*/
80300906Sasomers
81330733Sasomers//- GeomEvent Static Public Methods -------------------------------------------
82300906SasomersEvent *
83330733SasomersGeomEvent::Builder(Event::Type type,
84330733Sasomers		   NVPairMap &nvPairs,
85330733Sasomers		   const string &eventString)
86300906Sasomers{
87330733Sasomers	return (new GeomEvent(type, nvPairs, eventString));
88300906Sasomers}
89300906Sasomers
90330733Sasomers//- GeomEvent Virtual Public Methods ------------------------------------------
91330733SasomersEvent *
92330733SasomersGeomEvent::DeepCopy() const
93330733Sasomers{
94330733Sasomers	return (new GeomEvent(*this));
95330733Sasomers}
96330733Sasomers
97330733Sasomersbool
98330733SasomersGeomEvent::Process() const
99330733Sasomers{
100330733Sasomers	/*
101330733Sasomers	 * We only use GEOM events to repair damaged pools.  So return early if
102330733Sasomers	 * there are no damaged pools
103330733Sasomers	 */
104330733Sasomers	if (CaseFile::Empty())
105330733Sasomers		return (false);
106330733Sasomers
107330733Sasomers	/*
108330733Sasomers	 * We are only concerned with arrivals and physical path changes,
109330733Sasomers	 * because those can be used to satisfy online and autoreplace
110330733Sasomers	 * operations
111330733Sasomers	 */
112330733Sasomers	if (Value("type") != "GEOM::physpath" && Value("type") != "CREATE")
113330733Sasomers		return (false);
114330733Sasomers
115330733Sasomers	/* Log the event since it is of interest. */
116330733Sasomers	Log(LOG_INFO);
117330733Sasomers
118330733Sasomers	string devPath;
119330733Sasomers	if (!DevPath(devPath))
120330733Sasomers		return (false);
121330733Sasomers
122330733Sasomers	int devFd(open(devPath.c_str(), O_RDONLY));
123330733Sasomers	if (devFd == -1)
124330733Sasomers		return (false);
125330733Sasomers
126330733Sasomers	bool inUse;
127330733Sasomers	bool degraded;
128330733Sasomers	nvlist_t *devLabel(ReadLabel(devFd, inUse, degraded));
129330733Sasomers
130330733Sasomers	string physPath;
131330733Sasomers        bool havePhysPath(PhysicalPath(physPath));
132330733Sasomers
133330733Sasomers	string devName;
134330733Sasomers	DevName(devName);
135330733Sasomers	close(devFd);
136330733Sasomers
137330733Sasomers	if (inUse && devLabel != NULL) {
138330733Sasomers		OnlineByLabel(devPath, physPath, devLabel);
139330733Sasomers	} else if (degraded) {
140330733Sasomers		syslog(LOG_INFO, "%s is marked degraded.  Ignoring "
141330733Sasomers		       "as a replace by physical path candidate.\n",
142330733Sasomers		       devName.c_str());
143330733Sasomers	} else if (havePhysPath) {
144330733Sasomers		/*
145330733Sasomers		 * TODO: attempt to resolve events using every casefile
146330733Sasomers		 * that matches this physpath
147330733Sasomers		 */
148330733Sasomers		CaseFile *caseFile(CaseFile::Find(physPath));
149330733Sasomers		if (caseFile != NULL) {
150330733Sasomers			syslog(LOG_INFO,
151330733Sasomers			       "Found CaseFile(%s:%s:%s) - ReEvaluating\n",
152330733Sasomers			       caseFile->PoolGUIDString().c_str(),
153330733Sasomers			       caseFile->VdevGUIDString().c_str(),
154330733Sasomers			       zpool_state_to_name(caseFile->VdevState(),
155330733Sasomers						   VDEV_AUX_NONE));
156330733Sasomers			caseFile->ReEvaluate(devPath, physPath, /*vdev*/NULL);
157330733Sasomers		}
158330733Sasomers	}
159330733Sasomers	return (false);
160330733Sasomers}
161330733Sasomers
162330733Sasomers//- GeomEvent Protected Methods -----------------------------------------------
163330733SasomersGeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
164330733Sasomers			       const string &eventString)
165330733Sasomers : DevdCtl::GeomEvent(type, nvpairs, eventString)
166330733Sasomers{
167330733Sasomers}
168330733Sasomers
169330733SasomersGeomEvent::GeomEvent(const GeomEvent &src)
170330733Sasomers : DevdCtl::GeomEvent::GeomEvent(src)
171330733Sasomers{
172330733Sasomers}
173330733Sasomers
174300906Sasomersnvlist_t *
175330733SasomersGeomEvent::ReadLabel(int devFd, bool &inUse, bool &degraded)
176300906Sasomers{
177300906Sasomers	pool_state_t poolState;
178300906Sasomers	char        *poolName;
179300906Sasomers	boolean_t    b_inuse;
180326298Sasomers	int          nlabels;
181300906Sasomers
182300906Sasomers	inUse    = false;
183300906Sasomers	degraded = false;
184300906Sasomers	poolName = NULL;
185300906Sasomers	if (zpool_in_use(g_zfsHandle, devFd, &poolState,
186300906Sasomers			 &poolName, &b_inuse) == 0) {
187326298Sasomers		nvlist_t *devLabel = NULL;
188300906Sasomers
189300906Sasomers		inUse = b_inuse == B_TRUE;
190300906Sasomers		if (poolName != NULL)
191300906Sasomers			free(poolName);
192300906Sasomers
193326298Sasomers		nlabels = zpool_read_all_labels(devFd, &devLabel);
194326298Sasomers		/*
195326298Sasomers		 * If we find a disk with fewer than the maximum number of
196326298Sasomers		 * labels, it might be the whole disk of a partitioned disk
197326298Sasomers		 * where ZFS resides on a partition.  In that case, we should do
198326298Sasomers		 * nothing and wait for the partition to appear.  Or, the disk
199326298Sasomers		 * might be damaged.  In that case, zfsd should do nothing and
200326298Sasomers		 * wait for the sysadmin to decide.
201326298Sasomers		 */
202326298Sasomers		if (nlabels != VDEV_LABELS || devLabel == NULL) {
203326298Sasomers			nvlist_free(devLabel);
204300906Sasomers			return (NULL);
205326298Sasomers		}
206300906Sasomers
207300906Sasomers		try {
208300906Sasomers			Vdev vdev(devLabel);
209300906Sasomers			degraded = vdev.State() != VDEV_STATE_HEALTHY;
210300906Sasomers			return (devLabel);
211300906Sasomers		} catch (ZfsdException &exp) {
212300906Sasomers			string devName = fdevname(devFd);
213300906Sasomers			string devPath = _PATH_DEV + devName;
214330733Sasomers			string context("GeomEvent::ReadLabel: "
215300906Sasomers				     + devPath + ": ");
216300906Sasomers
217300906Sasomers			exp.GetString().insert(0, context);
218300906Sasomers			exp.Log();
219326298Sasomers			nvlist_free(devLabel);
220300906Sasomers		}
221300906Sasomers	}
222300906Sasomers	return (NULL);
223300906Sasomers}
224300906Sasomers
225300906Sasomersbool
226330733SasomersGeomEvent::OnlineByLabel(const string &devPath, const string& physPath,
227300906Sasomers			      nvlist_t *devConfig)
228300906Sasomers{
229300906Sasomers	try {
230300906Sasomers		/*
231300906Sasomers		 * A device with ZFS label information has been
232300906Sasomers		 * inserted.  If it matches a device for which we
233300906Sasomers		 * have a case, see if we can solve that case.
234300906Sasomers		 */
235300906Sasomers		syslog(LOG_INFO, "Interrogating VDEV label for %s\n",
236300906Sasomers		       devPath.c_str());
237300906Sasomers		Vdev vdev(devConfig);
238300906Sasomers		CaseFile *caseFile(CaseFile::Find(vdev.PoolGUID(),
239300906Sasomers						  vdev.GUID()));
240300906Sasomers		if (caseFile != NULL)
241300906Sasomers			return (caseFile->ReEvaluate(devPath, physPath, &vdev));
242300906Sasomers
243300906Sasomers	} catch (ZfsdException &exp) {
244330733Sasomers		string context("GeomEvent::OnlineByLabel: " + devPath + ": ");
245300906Sasomers
246300906Sasomers		exp.GetString().insert(0, context);
247300906Sasomers		exp.Log();
248300906Sasomers	}
249300906Sasomers	return (false);
250300906Sasomers}
251300906Sasomers
252300906Sasomers
253300906Sasomers/*--------------------------------- ZfsEvent ---------------------------------*/
254300906Sasomers//- ZfsEvent Static Public Methods ---------------------------------------------
255300906SasomersDevdCtl::Event *
256300906SasomersZfsEvent::Builder(Event::Type type, NVPairMap &nvpairs,
257300906Sasomers		  const string &eventString)
258300906Sasomers{
259300906Sasomers	return (new ZfsEvent(type, nvpairs, eventString));
260300906Sasomers}
261300906Sasomers
262300906Sasomers//- ZfsEvent Virtual Public Methods --------------------------------------------
263300906SasomersEvent *
264300906SasomersZfsEvent::DeepCopy() const
265300906Sasomers{
266300906Sasomers	return (new ZfsEvent(*this));
267300906Sasomers}
268300906Sasomers
269300906Sasomersbool
270300906SasomersZfsEvent::Process() const
271300906Sasomers{
272300906Sasomers	string logstr("");
273300906Sasomers
274300906Sasomers	if (!Contains("class") && !Contains("type")) {
275300906Sasomers		syslog(LOG_ERR,
276300906Sasomers		       "ZfsEvent::Process: Missing class or type data.");
277300906Sasomers		return (false);
278300906Sasomers	}
279300906Sasomers
280300906Sasomers	/* On config syncs, replay any queued events first. */
281300906Sasomers	if (Value("type").find("misc.fs.zfs.config_sync") == 0) {
282300906Sasomers		/*
283300906Sasomers		 * Even if saved events are unconsumed the second time
284300906Sasomers		 * around, drop them.  Any events that still can't be
285300906Sasomers		 * consumed are probably referring to vdevs or pools that
286300906Sasomers		 * no longer exist.
287300906Sasomers		 */
288300906Sasomers		ZfsDaemon::Get().ReplayUnconsumedEvents(/*discard*/true);
289300906Sasomers		CaseFile::ReEvaluateByGuid(PoolGUID(), *this);
290300906Sasomers	}
291300906Sasomers
292300906Sasomers	if (Value("type").find("misc.fs.zfs.") == 0) {
293300906Sasomers		/* Configuration changes, resilver events, etc. */
294300906Sasomers		ProcessPoolEvent();
295300906Sasomers		return (false);
296300906Sasomers	}
297300906Sasomers
298300906Sasomers	if (!Contains("pool_guid") || !Contains("vdev_guid")) {
299300906Sasomers		/* Only currently interested in Vdev related events. */
300300906Sasomers		return (false);
301300906Sasomers	}
302300906Sasomers
303300906Sasomers	CaseFile *caseFile(CaseFile::Find(PoolGUID(), VdevGUID()));
304300906Sasomers	if (caseFile != NULL) {
305300906Sasomers		Log(LOG_INFO);
306300906Sasomers		syslog(LOG_INFO, "Evaluating existing case file\n");
307300906Sasomers		caseFile->ReEvaluate(*this);
308300906Sasomers		return (false);
309300906Sasomers	}
310300906Sasomers
311300906Sasomers	/* Skip events that can't be handled. */
312300906Sasomers	Guid poolGUID(PoolGUID());
313300906Sasomers	/* If there are no replicas for a pool, then it's not manageable. */
314300906Sasomers	if (Value("class").find("fs.zfs.vdev.no_replicas") == 0) {
315300906Sasomers		stringstream msg;
316300906Sasomers		msg << "No replicas available for pool "  << poolGUID;
317300906Sasomers		msg << ", ignoring";
318300906Sasomers		Log(LOG_INFO);
319300906Sasomers		syslog(LOG_INFO, "%s", msg.str().c_str());
320300906Sasomers		return (false);
321300906Sasomers	}
322300906Sasomers
323300906Sasomers	/*
324300906Sasomers	 * Create a case file for this vdev, and have it
325300906Sasomers	 * evaluate the event.
326300906Sasomers	 */
327300906Sasomers	ZpoolList zpl(ZpoolList::ZpoolByGUID, &poolGUID);
328300906Sasomers	if (zpl.empty()) {
329300906Sasomers		stringstream msg;
330300906Sasomers		int priority = LOG_INFO;
331300906Sasomers		msg << "ZfsEvent::Process: Event for unknown pool ";
332300906Sasomers		msg << poolGUID << " ";
333300906Sasomers		msg << "queued";
334300906Sasomers		Log(LOG_INFO);
335300906Sasomers		syslog(priority, "%s", msg.str().c_str());
336300906Sasomers		return (true);
337300906Sasomers	}
338300906Sasomers
339300906Sasomers	nvlist_t *vdevConfig = VdevIterator(zpl.front()).Find(VdevGUID());
340300906Sasomers	if (vdevConfig == NULL) {
341300906Sasomers		stringstream msg;
342300906Sasomers		int priority = LOG_INFO;
343300906Sasomers		msg << "ZfsEvent::Process: Event for unknown vdev ";
344300906Sasomers		msg << VdevGUID() << " ";
345300906Sasomers		msg << "queued";
346300906Sasomers		Log(LOG_INFO);
347300906Sasomers		syslog(priority, "%s", msg.str().c_str());
348300906Sasomers		return (true);
349300906Sasomers	}
350300906Sasomers
351300906Sasomers	Vdev vdev(zpl.front(), vdevConfig);
352300906Sasomers	caseFile = &CaseFile::Create(vdev);
353300906Sasomers	if (caseFile->ReEvaluate(*this) == false) {
354300906Sasomers		stringstream msg;
355300906Sasomers		int priority = LOG_INFO;
356300906Sasomers		msg << "ZfsEvent::Process: Unconsumed event for vdev(";
357300906Sasomers		msg << zpool_get_name(zpl.front()) << ",";
358300906Sasomers		msg << vdev.GUID() << ") ";
359300906Sasomers		msg << "queued";
360300906Sasomers		Log(LOG_INFO);
361300906Sasomers		syslog(priority, "%s", msg.str().c_str());
362300906Sasomers		return (true);
363300906Sasomers	}
364300906Sasomers	return (false);
365300906Sasomers}
366300906Sasomers
367300906Sasomers//- ZfsEvent Protected Methods -------------------------------------------------
368300906SasomersZfsEvent::ZfsEvent(Event::Type type, NVPairMap &nvpairs,
369300906Sasomers			   const string &eventString)
370300906Sasomers : DevdCtl::ZfsEvent(type, nvpairs, eventString)
371300906Sasomers{
372300906Sasomers}
373300906Sasomers
374300906SasomersZfsEvent::ZfsEvent(const ZfsEvent &src)
375300906Sasomers : DevdCtl::ZfsEvent(src)
376300906Sasomers{
377300906Sasomers}
378300906Sasomers
379300906Sasomers/*
380300906Sasomers * Sometimes the kernel won't detach a spare when it is no longer needed.  This
381300906Sasomers * can happen for example if a drive is removed, then either the pool is
382300906Sasomers * exported or the machine is powered off, then the drive is reinserted, then
383300906Sasomers * the machine is powered on or the pool is imported.  ZFSD must detach these
384300906Sasomers * spares itself.
385300906Sasomers */
386300906Sasomersvoid
387300906SasomersZfsEvent::CleanupSpares() const
388300906Sasomers{
389300906Sasomers	Guid poolGUID(PoolGUID());
390300906Sasomers	ZpoolList zpl(ZpoolList::ZpoolByGUID, &poolGUID);
391300906Sasomers	if (!zpl.empty()) {
392300906Sasomers		zpool_handle_t* hdl;
393300906Sasomers
394300906Sasomers		hdl = zpl.front();
395300906Sasomers		VdevIterator(hdl).Each(TryDetach, (void*)hdl);
396300906Sasomers	}
397300906Sasomers}
398300906Sasomers
399300906Sasomersvoid
400300906SasomersZfsEvent::ProcessPoolEvent() const
401300906Sasomers{
402300906Sasomers	bool degradedDevice(false);
403300906Sasomers
404300906Sasomers	/* The pool is destroyed.  Discard any open cases */
405300906Sasomers	if (Value("type") == "misc.fs.zfs.pool_destroy") {
406300906Sasomers		Log(LOG_INFO);
407300906Sasomers		CaseFile::ReEvaluateByGuid(PoolGUID(), *this);
408300906Sasomers		return;
409300906Sasomers	}
410300906Sasomers
411300906Sasomers	CaseFile *caseFile(CaseFile::Find(PoolGUID(), VdevGUID()));
412300906Sasomers	if (caseFile != NULL) {
413300906Sasomers		if (caseFile->VdevState() != VDEV_STATE_UNKNOWN
414300906Sasomers		 && caseFile->VdevState() < VDEV_STATE_HEALTHY)
415300906Sasomers			degradedDevice = true;
416300906Sasomers
417300906Sasomers		Log(LOG_INFO);
418300906Sasomers		caseFile->ReEvaluate(*this);
419300906Sasomers	}
420300906Sasomers	else if (Value("type") == "misc.fs.zfs.resilver_finish")
421300906Sasomers	{
422300906Sasomers		/*
423300906Sasomers		 * It's possible to get a resilver_finish event with no
424300906Sasomers		 * corresponding casefile.  For example, if a damaged pool were
425300906Sasomers		 * exported, repaired, then reimported.
426300906Sasomers		 */
427300906Sasomers		Log(LOG_INFO);
428300906Sasomers		CleanupSpares();
429300906Sasomers	}
430300906Sasomers
431300906Sasomers	if (Value("type") == "misc.fs.zfs.vdev_remove"
432300906Sasomers	 && degradedDevice == false) {
433300906Sasomers
434300906Sasomers		/* See if any other cases can make use of this device. */
435300906Sasomers		Log(LOG_INFO);
436300906Sasomers		ZfsDaemon::RequestSystemRescan();
437300906Sasomers	}
438300906Sasomers}
439300906Sasomers
440300906Sasomersbool
441300906SasomersZfsEvent::TryDetach(Vdev &vdev, void *cbArg)
442300906Sasomers{
443300906Sasomers	/*
444300906Sasomers	 * Outline:
445300906Sasomers	 * if this device is a spare, and its parent includes one healthy,
446300906Sasomers	 * non-spare child, then detach this device.
447300906Sasomers	 */
448300906Sasomers	zpool_handle_t *hdl(static_cast<zpool_handle_t*>(cbArg));
449300906Sasomers
450300906Sasomers	if (vdev.IsSpare()) {
451300906Sasomers		std::list<Vdev> siblings;
452300906Sasomers		std::list<Vdev>::iterator siblings_it;
453300906Sasomers		boolean_t cleanup = B_FALSE;
454300906Sasomers
455300906Sasomers		Vdev parent = vdev.Parent();
456300906Sasomers		siblings = parent.Children();
457300906Sasomers
458300906Sasomers		/* Determine whether the parent should be cleaned up */
459300906Sasomers		for (siblings_it = siblings.begin();
460300906Sasomers		     siblings_it != siblings.end();
461300906Sasomers		     siblings_it++) {
462300906Sasomers			Vdev sibling = *siblings_it;
463300906Sasomers
464300906Sasomers			if (!sibling.IsSpare() &&
465300906Sasomers			     sibling.State() == VDEV_STATE_HEALTHY) {
466300906Sasomers				cleanup = B_TRUE;
467300906Sasomers				break;
468300906Sasomers			}
469300906Sasomers		}
470300906Sasomers
471300906Sasomers		if (cleanup) {
472300906Sasomers			syslog(LOG_INFO, "Detaching spare vdev %s from pool %s",
473300906Sasomers			       vdev.Path().c_str(), zpool_get_name(hdl));
474300906Sasomers			zpool_vdev_detach(hdl, vdev.Path().c_str());
475300906Sasomers		}
476300906Sasomers
477300906Sasomers	}
478300906Sasomers
479300906Sasomers	/* Always return false, because there may be other spares to detach */
480300906Sasomers	return (false);
481300906Sasomers}
482