vfs: check userland buffers before reading them.
[haiku.git] / src / servers / input / AddOnManager.cpp
blob757ccbf6a3a6d2540daadcee15651a3b10288124
1 /*
2 * Copyright 2004-2013 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Jérôme Duval
8 * Marcus Overhagen
9 * John Scipione, jscipione@gmail.com
13 //! Manager for input_server add-ons (devices, filters, methods)
16 #include "AddOnManager.h"
18 #include <stdio.h>
19 #include <string.h>
21 #include <Autolock.h>
22 #include <Deskbar.h>
23 #include <Directory.h>
24 #include <Entry.h>
25 #include <image.h>
26 #include <Path.h>
27 #include <Roster.h>
28 #include <String.h>
30 #include <PathMonitor.h>
32 #include "InputServer.h"
33 #include "InputServerTypes.h"
34 #include "MethodReplicant.h"
37 #undef TRACE
38 //#define TRACE_ADD_ON_MONITOR
39 #ifdef TRACE_ADD_ON_MONITOR
40 # define TRACE(x...) debug_printf(x)
41 # define ERROR(x...) debug_printf(x)
42 #else
43 # define TRACE(x...) ;
44 // TODO: probably better to the syslog
45 # define ERROR(x...) debug_printf(x)
46 #endif
50 class AddOnManager::MonitorHandler : public AddOnMonitorHandler {
51 public:
52 MonitorHandler(AddOnManager* manager)
54 fManager = manager;
57 virtual void AddOnEnabled(const add_on_entry_info* entryInfo)
59 CALLED();
60 entry_ref ref;
61 make_entry_ref(entryInfo->dir_nref.device, entryInfo->dir_nref.node,
62 entryInfo->name, &ref);
63 BEntry entry(&ref, false);
65 fManager->_RegisterAddOn(entry);
68 virtual void AddOnDisabled(const add_on_entry_info* entryInfo)
70 CALLED();
71 entry_ref ref;
72 make_entry_ref(entryInfo->dir_nref.device, entryInfo->dir_nref.node,
73 entryInfo->name, &ref);
74 BEntry entry(&ref, false);
76 fManager->_UnregisterAddOn(entry);
79 private:
80 AddOnManager* fManager;
84 // #pragma mark -
87 template<class T> T*
88 instantiate_add_on(image_id image, const char* path, const char* type)
90 T* (*instantiateFunction)();
92 BString functionName = "instantiate_input_";
93 functionName += type;
95 if (get_image_symbol(image, functionName.String(), B_SYMBOL_TYPE_TEXT,
96 (void**)&instantiateFunction) < B_OK) {
97 ERROR("AddOnManager::_RegisterAddOn(): can't find %s() in \"%s\"\n",
98 functionName.String(), path);
99 return NULL;
102 T* addOn = (*instantiateFunction)();
103 if (addOn == NULL) {
104 ERROR("AddOnManager::_RegisterAddOn(): %s() in \"%s\" returned "
105 "NULL\n", functionName.String(), path);
106 return NULL;
109 status_t status = addOn->InitCheck();
110 if (status != B_OK) {
111 ERROR("AddOnManager::_RegisterAddOn(): InitCheck() in \"%s\" "
112 "returned %s\n", path, strerror(status));
113 delete addOn;
114 return NULL;
117 return addOn;
121 // #pragma mark - AddOnManager
124 AddOnManager::AddOnManager()
126 AddOnMonitor(),
127 fHandler(new(std::nothrow) MonitorHandler(this))
129 SetHandler(fHandler);
133 AddOnManager::~AddOnManager()
135 delete fHandler;
139 void
140 AddOnManager::MessageReceived(BMessage* message)
142 CALLED();
144 BMessage reply;
145 status_t status;
147 TRACE("%s what: %.4s\n", __PRETTY_FUNCTION__, (char*)&message->what);
149 switch (message->what) {
150 case IS_FIND_DEVICES:
151 status = _HandleFindDevices(message, &reply);
152 break;
153 case IS_WATCH_DEVICES:
154 status = _HandleWatchDevices(message, &reply);
155 break;
156 case IS_IS_DEVICE_RUNNING:
157 status = _HandleIsDeviceRunning(message, &reply);
158 break;
159 case IS_START_DEVICE:
160 status = _HandleStartStopDevices(message, &reply);
161 break;
162 case IS_STOP_DEVICE:
163 status = _HandleStartStopDevices(message, &reply);
164 break;
165 case IS_CONTROL_DEVICES:
166 status = _HandleControlDevices(message, &reply);
167 break;
168 case SYSTEM_SHUTTING_DOWN:
169 status = _HandleSystemShuttingDown(message, &reply);
170 break;
171 case IS_METHOD_REGISTER:
172 status = _HandleMethodReplicant(message, &reply);
173 break;
175 case B_PATH_MONITOR:
176 _HandleDeviceMonitor(message);
177 return;
179 default:
180 AddOnMonitor::MessageReceived(message);
181 return;
184 reply.AddInt32("status", status);
185 message->SendReply(&reply);
189 void
190 AddOnManager::LoadState()
192 _RegisterAddOns();
196 void
197 AddOnManager::SaveState()
199 CALLED();
200 _UnregisterAddOns();
204 status_t
205 AddOnManager::StartMonitoringDevice(DeviceAddOn* addOn, const char* device)
207 CALLED();
209 BString path;
210 if (device[0] != '/')
211 path = "/dev/";
212 path += device;
214 TRACE("AddOnMonitor::StartMonitoringDevice(%s)\n", path.String());
216 bool newPath;
217 status_t status = _AddDevicePath(addOn, path.String(), newPath);
218 if (status == B_OK && newPath) {
219 status = BPathMonitor::StartWatching(path.String(),
220 B_WATCH_FILES_ONLY | B_WATCH_RECURSIVELY, this);
221 if (status != B_OK) {
222 bool lastPath;
223 _RemoveDevicePath(addOn, path.String(), lastPath);
227 return status;
231 status_t
232 AddOnManager::StopMonitoringDevice(DeviceAddOn* addOn, const char *device)
234 CALLED();
236 BString path;
237 if (device[0] != '/')
238 path = "/dev/";
239 path += device;
241 TRACE("AddOnMonitor::StopMonitoringDevice(%s)\n", path.String());
243 bool lastPath;
244 status_t status = _RemoveDevicePath(addOn, path.String(), lastPath);
245 if (status == B_OK && lastPath)
246 BPathMonitor::StopWatching(path.String(), this);
248 return status;
252 // #pragma mark -
255 void
256 AddOnManager::_RegisterAddOns()
258 CALLED();
259 BAutolock locker(this);
261 fHandler->AddAddOnDirectories("input_server/devices");
262 fHandler->AddAddOnDirectories("input_server/filters");
263 fHandler->AddAddOnDirectories("input_server/methods");
267 void
268 AddOnManager::_UnregisterAddOns()
270 BAutolock locker(this);
272 // We have to stop manually the add-ons because the monitor doesn't
273 // disable them on exit
275 while (device_info* info = fDeviceList.RemoveItemAt(0)) {
276 gInputServer->StartStopDevices(*info->add_on, false);
277 delete info;
280 // TODO: what about the filters/methods lists in the input_server?
282 while (filter_info* info = fFilterList.RemoveItemAt(0)) {
283 delete info;
286 while (method_info* info = fMethodList.RemoveItemAt(0)) {
287 delete info;
292 bool
293 AddOnManager::_IsDevice(const char* path) const
295 return strstr(path, "input_server/devices") != 0;
299 bool
300 AddOnManager::_IsFilter(const char* path) const
302 return strstr(path, "input_server/filters") != 0;
306 bool
307 AddOnManager::_IsMethod(const char* path) const
309 return strstr(path, "input_server/methods") != 0;
313 status_t
314 AddOnManager::_RegisterAddOn(BEntry& entry)
316 BPath path(&entry);
318 entry_ref ref;
319 status_t status = entry.GetRef(&ref);
320 if (status < B_OK)
321 return status;
323 TRACE("AddOnManager::RegisterAddOn(): trying to load \"%s\"\n",
324 path.Path());
326 image_id image = load_add_on(path.Path());
327 if (image < B_OK) {
328 ERROR("load addon %s failed\n", path.Path());
329 return image;
332 status = B_ERROR;
334 if (_IsDevice(path.Path())) {
335 BInputServerDevice* device = instantiate_add_on<BInputServerDevice>(
336 image, path.Path(), "device");
337 if (device != NULL)
338 status = _RegisterDevice(device, ref, image);
339 } else if (_IsFilter(path.Path())) {
340 BInputServerFilter* filter = instantiate_add_on<BInputServerFilter>(
341 image, path.Path(), "filter");
342 if (filter != NULL)
343 status = _RegisterFilter(filter, ref, image);
344 } else if (_IsMethod(path.Path())) {
345 BInputServerMethod* method = instantiate_add_on<BInputServerMethod>(
346 image, path.Path(), "method");
347 if (method != NULL)
348 status = _RegisterMethod(method, ref, image);
349 } else {
350 ERROR("AddOnManager::_RegisterAddOn(): addon type not found for "
351 "\"%s\" \n", path.Path());
354 if (status != B_OK)
355 unload_add_on(image);
357 return status;
361 status_t
362 AddOnManager::_UnregisterAddOn(BEntry& entry)
364 BPath path(&entry);
366 entry_ref ref;
367 status_t status = entry.GetRef(&ref);
368 if (status < B_OK)
369 return status;
371 TRACE("AddOnManager::UnregisterAddOn(): trying to unload \"%s\"\n",
372 path.Path());
374 BAutolock _(this);
376 if (_IsDevice(path.Path())) {
377 for (int32 i = fDeviceList.CountItems(); i-- > 0;) {
378 device_info* info = fDeviceList.ItemAt(i);
379 if (!strcmp(info->ref.name, ref.name)) {
380 gInputServer->StartStopDevices(*info->add_on, false);
381 delete fDeviceList.RemoveItemAt(i);
382 break;
385 } else if (_IsFilter(path.Path())) {
386 for (int32 i = fFilterList.CountItems(); i-- > 0;) {
387 filter_info* info = fFilterList.ItemAt(i);
388 if (!strcmp(info->ref.name, ref.name)) {
389 BAutolock locker(InputServer::gInputFilterListLocker);
390 InputServer::gInputFilterList.RemoveItem(info->add_on);
391 delete fFilterList.RemoveItemAt(i);
392 break;
395 } else if (_IsMethod(path.Path())) {
396 BInputServerMethod* method = NULL;
398 for (int32 i = fMethodList.CountItems(); i-- > 0;) {
399 method_info* info = fMethodList.ItemAt(i);
400 if (!strcmp(info->ref.name, ref.name)) {
401 BAutolock locker(InputServer::gInputMethodListLocker);
402 InputServer::gInputMethodList.RemoveItem(info->add_on);
403 method = info->add_on;
404 // this will only be used as a cookie, and not referenced
405 // anymore
406 delete fMethodList.RemoveItemAt(i);
407 break;
411 if (fMethodList.CountItems() <= 0) {
412 // we remove the method replicant
413 BDeskbar().RemoveItem(REPLICANT_CTL_NAME);
414 gInputServer->SetMethodReplicant(NULL);
415 } else if (method != NULL) {
416 BMessage msg(IS_REMOVE_METHOD);
417 msg.AddInt32("cookie", method->fOwner->Cookie());
418 if (gInputServer->MethodReplicant())
419 gInputServer->MethodReplicant()->SendMessage(&msg);
423 return B_OK;
427 //! Takes over ownership of the \a device, regardless of success.
428 status_t
429 AddOnManager::_RegisterDevice(BInputServerDevice* device, const entry_ref& ref,
430 image_id addOnImage)
432 BAutolock locker(this);
434 for (int32 i = fDeviceList.CountItems(); i-- > 0;) {
435 device_info* info = fDeviceList.ItemAt(i);
436 if (!strcmp(info->ref.name, ref.name)) {
437 // we already know this device
438 delete device;
439 return B_NAME_IN_USE;
443 TRACE("AddOnManager::RegisterDevice, name %s\n", ref.name);
445 device_info* info = new(std::nothrow) device_info;
446 if (info == NULL) {
447 delete device;
448 return B_NO_MEMORY;
451 info->ref = ref;
452 info->add_on = device;
454 if (!fDeviceList.AddItem(info)) {
455 delete info;
456 return B_NO_MEMORY;
459 info->image = addOnImage;
461 return B_OK;
465 //! Takes over ownership of the \a filter, regardless of success.
466 status_t
467 AddOnManager::_RegisterFilter(BInputServerFilter* filter, const entry_ref& ref,
468 image_id addOnImage)
470 BAutolock _(this);
472 for (int32 i = fFilterList.CountItems(); i-- > 0;) {
473 filter_info* info = fFilterList.ItemAt(i);
474 if (strcmp(info->ref.name, ref.name) == 0) {
475 // we already know this ref
476 delete filter;
477 return B_NAME_IN_USE;
481 TRACE("%s, name %s\n", __PRETTY_FUNCTION__, ref.name);
483 filter_info* info = new(std::nothrow) filter_info;
484 if (info == NULL) {
485 delete filter;
486 return B_NO_MEMORY;
489 info->ref = ref;
490 info->add_on = filter;
492 if (!fFilterList.AddItem(info)) {
493 delete info;
494 return B_NO_MEMORY;
497 BAutolock locker(InputServer::gInputFilterListLocker);
498 if (!InputServer::gInputFilterList.AddItem(filter)) {
499 fFilterList.RemoveItem(info, false);
500 delete info;
501 return B_NO_MEMORY;
504 info->image = addOnImage;
506 return B_OK;
510 //! Takes over ownership of the \a method, regardless of success.
511 status_t
512 AddOnManager::_RegisterMethod(BInputServerMethod* method, const entry_ref& ref,
513 image_id addOnImage)
515 BAutolock _(this);
517 for (int32 i = fMethodList.CountItems(); i-- > 0;) {
518 method_info* info = fMethodList.ItemAt(i);
519 if (!strcmp(info->ref.name, ref.name)) {
520 // we already know this ref
521 delete method;
522 return B_NAME_IN_USE;
526 TRACE("%s, name %s\n", __PRETTY_FUNCTION__, ref.name);
528 method_info* info = new(std::nothrow) method_info;
529 if (info == NULL) {
530 delete method;
531 return B_NO_MEMORY;
534 info->ref = ref;
535 info->add_on = method;
537 if (!fMethodList.AddItem(info)) {
538 delete info;
539 return B_NO_MEMORY;
542 BAutolock locker(InputServer::gInputMethodListLocker);
543 if (!InputServer::gInputMethodList.AddItem(method)) {
544 fMethodList.RemoveItem(info);
545 delete info;
546 return B_NO_MEMORY;
549 info->image = addOnImage;
551 if (gInputServer->MethodReplicant() == NULL) {
552 _LoadReplicant();
554 if (gInputServer->MethodReplicant()) {
555 _BMethodAddOn_ *addon = InputServer::gKeymapMethod.fOwner;
556 addon->AddMethod();
560 if (gInputServer->MethodReplicant() != NULL) {
561 _BMethodAddOn_ *addon = method->fOwner;
562 addon->AddMethod();
565 return B_OK;
569 // #pragma mark -
572 void
573 AddOnManager::_UnloadReplicant()
575 BDeskbar().RemoveItem(REPLICANT_CTL_NAME);
579 void
580 AddOnManager::_LoadReplicant()
582 CALLED();
583 app_info info;
584 be_app->GetAppInfo(&info);
586 status_t err = BDeskbar().AddItem(&info.ref);
587 if (err != B_OK)
588 ERROR("Deskbar refuses to add method replicant: %s\n", strerror(err));
590 BMessage request(B_GET_PROPERTY);
591 BMessenger to;
592 BMessenger status;
594 request.AddSpecifier("Messenger");
595 request.AddSpecifier("Shelf");
597 // In the Deskbar the Shelf is in the View "Status" in Window "Deskbar"
598 request.AddSpecifier("View", "Status");
599 request.AddSpecifier("Window", "Deskbar");
600 to = BMessenger("application/x-vnd.Be-TSKB", -1);
602 BMessage reply;
604 if (to.SendMessage(&request, &reply) == B_OK
605 && reply.FindMessenger("result", &status) == B_OK) {
606 // enum replicant in Status view
607 int32 index = 0;
608 int32 uid;
609 while ((uid = _GetReplicantAt(status, index++)) >= B_OK) {
610 BMessage replicantInfo;
611 if (_GetReplicantName(status, uid, &replicantInfo) != B_OK)
612 continue;
614 const char *name;
615 if (replicantInfo.FindString("result", &name) == B_OK
616 && !strcmp(name, REPLICANT_CTL_NAME)) {
617 BMessage replicant;
618 if (_GetReplicantView(status, uid, &replicant) == B_OK) {
619 BMessenger result;
620 if (replicant.FindMessenger("result", &result) == B_OK) {
621 gInputServer->SetMethodReplicant(new BMessenger(result));
628 if (!gInputServer->MethodReplicant()) {
629 ERROR("LoadReplicant(): Method replicant not found!\n");
634 int32
635 AddOnManager::_GetReplicantAt(BMessenger target, int32 index) const
637 // So here we want to get the Unique ID of the replicant at the given index
638 // in the target Shelf.
640 BMessage request(B_GET_PROPERTY);// We're getting the ID property
641 BMessage reply;
642 status_t err;
644 request.AddSpecifier("ID");// want the ID
645 request.AddSpecifier("Replicant", index);// of the index'th replicant
647 if ((err = target.SendMessage(&request, &reply)) != B_OK)
648 return err;
650 int32 uid;
651 if ((err = reply.FindInt32("result", &uid)) != B_OK)
652 return err;
654 return uid;
658 status_t
659 AddOnManager::_GetReplicantName(BMessenger target, int32 uid,
660 BMessage* reply) const
662 // We send a message to the target shelf, asking it for the Name of the
663 // replicant with the given unique id.
665 BMessage request(B_GET_PROPERTY);
666 BMessage uid_specifier(B_ID_SPECIFIER);// specifying via ID
667 status_t err;
668 status_t e;
670 request.AddSpecifier("Name");// ask for the Name of the replicant
672 // IDs are specified using code like the following 3 lines:
673 uid_specifier.AddInt32("id", uid);
674 uid_specifier.AddString("property", "Replicant");
675 request.AddSpecifier(&uid_specifier);
677 if ((err = target.SendMessage(&request, reply)) != B_OK)
678 return err;
680 if (((err = reply->FindInt32("error", &e)) != B_OK) || (e != B_OK))
681 return err ? err : e;
683 return B_OK;
687 status_t
688 AddOnManager::_GetReplicantView(BMessenger target, int32 uid,
689 BMessage* reply) const
691 // We send a message to the target shelf, asking it for the Name of the
692 // replicant with the given unique id.
694 BMessage request(B_GET_PROPERTY);
695 BMessage uid_specifier(B_ID_SPECIFIER);
696 // specifying via ID
697 status_t err;
698 status_t e;
700 request.AddSpecifier("View");
701 // ask for the Name of the replicant
703 // IDs are specified using code like the following 3 lines:
704 uid_specifier.AddInt32("id", uid);
705 uid_specifier.AddString("property", "Replicant");
706 request.AddSpecifier(&uid_specifier);
708 if ((err = target.SendMessage(&request, reply)) != B_OK)
709 return err;
711 if (((err = reply->FindInt32("error", &e)) != B_OK) || (e != B_OK))
712 return err ? err : e;
714 return B_OK;
718 status_t
719 AddOnManager::_HandleStartStopDevices(BMessage* message, BMessage* reply)
721 const char *name = NULL;
722 int32 type = 0;
723 if (!((message->FindInt32("type", &type) != B_OK)
724 ^ (message->FindString("device", &name) != B_OK)))
725 return B_ERROR;
727 return gInputServer->StartStopDevices(name, (input_device_type)type,
728 message->what == IS_START_DEVICE);
732 status_t
733 AddOnManager::_HandleFindDevices(BMessage* message, BMessage* reply)
735 CALLED();
736 const char *name = NULL;
737 input_device_type type;
738 if (message->FindString("device", &name) == B_OK) {
739 if (gInputServer->GetDeviceInfo(name, &type) != B_OK)
740 return B_NAME_NOT_FOUND;
741 reply->AddString("device", name);
742 reply->AddInt32("type", type);
743 } else {
744 gInputServer->GetDeviceInfos(reply);
746 return B_OK;
750 status_t
751 AddOnManager::_HandleWatchDevices(BMessage* message, BMessage* reply)
753 // TODO
754 return B_OK;
758 status_t
759 AddOnManager::_HandleIsDeviceRunning(BMessage* message, BMessage* reply)
761 const char* name;
762 bool running;
763 if (message->FindString("device", &name) != B_OK
764 || gInputServer->GetDeviceInfo(name, NULL, &running) != B_OK)
765 return B_NAME_NOT_FOUND;
767 return running ? B_OK : B_ERROR;
771 status_t
772 AddOnManager::_HandleControlDevices(BMessage* message, BMessage* reply)
774 CALLED();
775 const char *name = NULL;
776 int32 type = 0;
777 if (!((message->FindInt32("type", &type) != B_OK)
778 ^ (message->FindString("device", &name) != B_OK)))
779 return B_BAD_VALUE;
781 uint32 code = 0;
782 BMessage controlMessage;
783 bool hasMessage = true;
784 if (message->FindInt32("code", (int32*)&code) != B_OK)
785 return B_BAD_VALUE;
786 if (message->FindMessage("message", &controlMessage) != B_OK)
787 hasMessage = false;
789 return gInputServer->ControlDevices(name, (input_device_type)type,
790 code, hasMessage ? &controlMessage : NULL);
794 status_t
795 AddOnManager::_HandleSystemShuttingDown(BMessage* message, BMessage* reply)
797 CALLED();
799 for (int32 i = 0; i < fDeviceList.CountItems(); i++) {
800 device_info* info = fDeviceList.ItemAt(i);
801 info->add_on->SystemShuttingDown();
804 return B_OK;
808 status_t
809 AddOnManager::_HandleMethodReplicant(BMessage* message, BMessage* reply)
811 CALLED();
813 if (InputServer::gInputMethodList.CountItems() == 0) {
814 _UnloadReplicant();
815 return B_OK;
818 _LoadReplicant();
820 BAutolock lock(InputServer::gInputMethodListLocker);
822 if (gInputServer->MethodReplicant()) {
823 _BMethodAddOn_* addon = InputServer::gKeymapMethod.fOwner;
824 addon->AddMethod();
826 for (int32 i = 0; i < InputServer::gInputMethodList.CountItems(); i++) {
827 BInputServerMethod* method
828 = (BInputServerMethod*)InputServer::gInputMethodList.ItemAt(i);
830 _BMethodAddOn_* addon = method->fOwner;
831 addon->AddMethod();
835 return B_OK;
839 void
840 AddOnManager::_HandleDeviceMonitor(BMessage* message)
842 int32 opcode;
843 if (message->FindInt32("opcode", &opcode) != B_OK)
844 return;
846 switch (opcode) {
847 case B_ENTRY_CREATED:
848 case B_ENTRY_REMOVED:
850 const char* path;
851 const char* watchedPath;
852 if (message->FindString("watched_path", &watchedPath) != B_OK
853 || message->FindString("path", &path) != B_OK) {
854 #if DEBUG
855 char string[1024];
856 sprintf(string, "message does not contain all fields - "
857 "watched_path: %d, path: %d\n",
858 message->HasString("watched_path"),
859 message->HasString("path"));
860 debugger(string);
861 #endif
862 return;
865 // Notify all watching devices
867 for (int32 i = 0; i < fDeviceAddOns.CountItems(); i++) {
868 DeviceAddOn* addOn = fDeviceAddOns.ItemAt(i);
869 if (!addOn->HasPath(watchedPath))
870 continue;
872 addOn->Device()->Control(NULL, NULL, B_NODE_MONITOR, message);
874 break;
880 status_t
881 AddOnManager::_AddDevicePath(DeviceAddOn* addOn, const char* path,
882 bool& newPath)
884 newPath = !fDevicePaths.HasPath(path);
886 status_t status = fDevicePaths.AddPath(path);
887 if (status == B_OK) {
888 status = addOn->AddPath(path);
889 if (status == B_OK) {
890 if (!fDeviceAddOns.HasItem(addOn)
891 && !fDeviceAddOns.AddItem(addOn)) {
892 addOn->RemovePath(path);
893 status = B_NO_MEMORY;
895 } else
896 fDevicePaths.RemovePath(path);
899 return status;
903 status_t
904 AddOnManager::_RemoveDevicePath(DeviceAddOn* addOn, const char* path,
905 bool& lastPath)
907 if (!fDevicePaths.HasPath(path) || !addOn->HasPath(path))
908 return B_ENTRY_NOT_FOUND;
910 fDevicePaths.RemovePath(path);
912 lastPath = !fDevicePaths.HasPath(path);
914 addOn->RemovePath(path);
915 if (addOn->CountPaths() == 0)
916 fDeviceAddOns.RemoveItem(addOn);
918 return B_OK;