1 /* 2 * Copyright 2004-2013 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Andrew Bachmann 8 * John Scipione, jscipione@gmail.com 9 */ 10 11 12 #include "AddOnMonitorHandler.h" 13 14 #include <string.h> 15 #include <strings.h> 16 17 #include <Autolock.h> 18 #include <Directory.h> 19 #include <FindDirectory.h> 20 #include <Path.h> 21 22 #include <driver_settings.h> 23 #include <safemode_defs.h> 24 #include <syscalls.h> 25 26 27 #ifndef ADD_ON_STABLE_SECONDS 28 # define ADD_ON_STABLE_SECONDS 1 29 #endif 30 31 32 AddOnMonitorHandler::AddOnMonitorHandler(const char* name) 33 : 34 NodeMonitorHandler(name != NULL ? name : "AddOnMonitorHandler") 35 { 36 } 37 38 39 AddOnMonitorHandler::~AddOnMonitorHandler() 40 { 41 // TODO: Actually calling watch_node() here should be too late, since we 42 // are likely not attached to a looper anymore, and thus consitute no valid 43 // BMessenger at this time. 44 DirectoryList::iterator it = fDirectories.begin(); 45 for (; it != fDirectories.end(); it++) { 46 EntryList::iterator eiter = it->entries.begin(); 47 for (; eiter != it->entries.end(); eiter++) 48 watch_node(&eiter->addon_nref, B_STOP_WATCHING, this); 49 watch_node(&it->nref, B_STOP_WATCHING, this); 50 } 51 } 52 53 54 void 55 AddOnMonitorHandler::MessageReceived(BMessage* msg) 56 { 57 if (msg->what == B_PULSE) 58 _HandlePendingEntries(); 59 60 inherited::MessageReceived(msg); 61 } 62 63 64 status_t 65 AddOnMonitorHandler::AddDirectory(const node_ref* nref, bool sync) 66 { 67 // Keep the looper thread locked, since this method is likely to be called 68 // in a thread other than the looper thread. Otherwise we may access the 69 // lists concurrently with the looper thread, when node monitor 70 // notifications arrive while we are still adding initial entries from the 71 // directory, or (much more likely) if the looper thread handles a pulse 72 // message and wants to process pending entries while we are still adding 73 // them. 74 BAutolock _(Looper()); 75 76 // ignore directories added twice 77 DirectoryList::iterator it = fDirectories.begin(); 78 for (; it != fDirectories.end(); it++) { 79 if (it->nref == *nref) 80 return B_OK; 81 } 82 83 BDirectory directory(nref); 84 status_t status = directory.InitCheck(); 85 if (status != B_OK) 86 return status; 87 88 status = watch_node(nref, B_WATCH_DIRECTORY, this); 89 if (status != B_OK) 90 return status; 91 92 add_on_directory_info dirInfo; 93 dirInfo.nref = *nref; 94 fDirectories.push_back(dirInfo); 95 96 add_on_entry_info entryInfo; 97 entryInfo.dir_nref = *nref; 98 99 BEntry entry; 100 while (directory.GetNextEntry(&entry) == B_OK) { 101 if (entry.GetName(entryInfo.name) != B_OK 102 || entry.GetNodeRef(&entryInfo.nref) != B_OK) { 103 continue; 104 } 105 106 fPendingEntries.push_back(entryInfo); 107 } 108 109 if (sync) 110 _HandlePendingEntries(); 111 112 return B_OK; 113 } 114 115 116 status_t 117 AddOnMonitorHandler::AddAddOnDirectories(const char* leafPath) 118 { 119 char parameter[32]; 120 size_t parameterLength = sizeof(parameter); 121 uint32 start = 0; 122 123 const directory_which addOnDirectories[] = { 124 B_USER_NONPACKAGED_ADDONS_DIRECTORY, 125 B_USER_ADDONS_DIRECTORY, 126 B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY, 127 B_SYSTEM_ADDONS_DIRECTORY 128 }; 129 130 if (_kern_get_safemode_option(B_SAFEMODE_DISABLE_USER_ADD_ONS, parameter, 131 ¶meterLength) == B_OK) { 132 if (!strcasecmp(parameter, "enabled") || !strcasecmp(parameter, "on") 133 || !strcasecmp(parameter, "true") || !strcasecmp(parameter, "yes") 134 || !strcasecmp(parameter, "enable") || !strcmp(parameter, "1")) { 135 // skip user add on directories 136 start = 2; 137 } 138 } 139 140 if (_kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, parameter, 141 ¶meterLength) == B_OK) { 142 if (!strcasecmp(parameter, "enabled") || !strcasecmp(parameter, "on") 143 || !strcasecmp(parameter, "true") || !strcasecmp(parameter, "yes") 144 || !strcasecmp(parameter, "enable") || !strcmp(parameter, "1")) { 145 // safe mode, only B_SYSTEM_ADDONS_DIRECTORY is used 146 start = 3; 147 } 148 } 149 150 for (uint32 i = start; 151 i < sizeof(addOnDirectories) / sizeof(directory_which); i++) { 152 BDirectory directory; 153 node_ref nodeRef; 154 BPath path; 155 if (find_directory(addOnDirectories[i], &path) == B_OK 156 && path.Append(leafPath) == B_OK 157 && directory.SetTo(path.Path()) == B_OK 158 && directory.GetNodeRef(&nodeRef) == B_OK) { 159 status_t result = this->AddDirectory(&nodeRef); 160 if (result != B_OK) 161 return result; 162 } 163 } 164 165 return B_OK; 166 } 167 168 169 // #pragma mark - AddOnMonitorHandler hooks 170 171 172 void 173 AddOnMonitorHandler::AddOnCreated(const add_on_entry_info* entryInfo) 174 { 175 176 } 177 178 179 void 180 AddOnMonitorHandler::AddOnEnabled(const add_on_entry_info* entryInfo) 181 { 182 } 183 184 185 void 186 AddOnMonitorHandler::AddOnDisabled(const add_on_entry_info* entryInfo) 187 { 188 } 189 190 191 void 192 AddOnMonitorHandler::AddOnRemoved(const add_on_entry_info* entryInfo) 193 { 194 } 195 196 197 // #pragma mark - NodeMonitorHandler hooks 198 199 200 void 201 AddOnMonitorHandler::EntryCreated(const char* name, ino_t directory, 202 dev_t device, ino_t node) 203 { 204 add_on_entry_info entryInfo; 205 strlcpy(entryInfo.name, name, sizeof(entryInfo.name)); 206 make_node_ref(device, node, &entryInfo.nref); 207 make_node_ref(device, directory, &entryInfo.dir_nref); 208 fPendingEntries.push_back(entryInfo); 209 } 210 211 212 void 213 AddOnMonitorHandler::EntryRemoved(const char* name, ino_t directory, 214 dev_t device, ino_t node) 215 { 216 node_ref entryNodeRef; 217 make_node_ref(device, node, &entryNodeRef); 218 219 // Search pending entries first, which can simply be discarded 220 // We might have this entry in the pending list multiple times, 221 // so we search entire list through, even after finding one. 222 EntryList::iterator eiter = fPendingEntries.begin(); 223 while (eiter != fPendingEntries.end()) { 224 if (eiter->nref == entryNodeRef) 225 eiter = fPendingEntries.erase(eiter); 226 else 227 eiter++; 228 } 229 230 // Find the directory of the entry. 231 DirectoryList::iterator diter = fDirectories.begin(); 232 if (!_FindDirectory(directory, device, diter)) { 233 // If it was not found, we're done 234 return; 235 } 236 237 eiter = diter->entries.begin(); 238 if (!_FindEntry(entryNodeRef, diter->entries, eiter)) { 239 // This must be the directory, but we didn't find the entry. 240 return; 241 } 242 243 add_on_entry_info info = *eiter; 244 watch_node(&entryNodeRef, B_STOP_WATCHING, this); 245 diter->entries.erase(eiter); 246 247 // Start at the top again, and search until the directory we found 248 // the old add-on in. If we find an add-on with the same name then 249 // the old add-on was not enabled. So we deallocate the old add-on and 250 // return. 251 DirectoryList::iterator diter2 = fDirectories.begin(); 252 for (; diter2 != diter; diter2++) { 253 if (_HasEntry(info.name, diter2->entries)) { 254 AddOnRemoved(&info); 255 return; 256 } 257 } 258 259 // An active add-on was removed. We need to disable and then subsequently 260 // deallocate it. 261 AddOnDisabled(&info); 262 AddOnRemoved(&info); 263 264 // Continue searching for an add-on below us. If we find an add-on 265 // with the same name, we must enable it. 266 for (diter++; diter != fDirectories.end(); diter++) { 267 eiter = diter->entries.begin(); 268 if (_FindEntry(info.name, diter->entries, eiter)) { 269 AddOnEnabled(&*eiter); 270 break; 271 } 272 } 273 } 274 275 276 void 277 AddOnMonitorHandler::EntryMoved(const char* name, const char* fromName, 278 ino_t fromDirectory, ino_t toDirectory, dev_t device, ino_t node, 279 dev_t nodeDevice) 280 { 281 node_ref toNodeRef; 282 make_node_ref(device, toDirectory, &toNodeRef); 283 284 // Search the "from" and "to" directory in the known directories 285 DirectoryList::iterator fromIter = fDirectories.begin(); 286 bool watchingFromDirectory = _FindDirectory(fromDirectory, device, 287 fromIter); 288 289 DirectoryList::iterator toIter = fDirectories.begin(); 290 bool watchingToDirectory = _FindDirectory(toNodeRef, toIter); 291 292 if (!watchingFromDirectory && !watchingToDirectory) { 293 // It seems the notification was for a directory we are not 294 // actually watching (at least not by intention). 295 return; 296 } 297 298 add_on_entry_info info; 299 300 node_ref entryNodeRef; 301 make_node_ref(device, node, &entryNodeRef); 302 303 if (!watchingToDirectory) { 304 // moved out of our view 305 EntryList::iterator eiter = fromIter->entries.begin(); 306 if (!_FindEntry(entryNodeRef, fromIter->entries, eiter)) { 307 // we don't know anything about this entry yet.. ignore it 308 return; 309 } 310 311 // save the info and remove the entry 312 info = *eiter; 313 watch_node(&entryNodeRef, B_STOP_WATCHING, this); 314 fromIter->entries.erase(eiter); 315 316 // Start at the top again, and search until the from directory. 317 // If we find a add-on with the same name then the moved add-on 318 // was not enabled. So we are done. 319 DirectoryList::iterator diter = fDirectories.begin(); 320 for (; diter != fromIter; diter++) { 321 eiter = diter->entries.begin(); 322 if (_FindEntry(info.name, diter->entries, eiter)) 323 return; 324 } 325 326 // finally disable the add-on 327 AddOnDisabled(&info); 328 329 // Continue searching for a add-on below us. If we find a add-on 330 // with the same name, we must enable it. 331 for (fromIter++; fromIter != fDirectories.end(); fromIter++) { 332 eiter = fromIter->entries.begin(); 333 if (_FindEntry(info.name, fromIter->entries, eiter)) { 334 AddOnEnabled(&*eiter); 335 return; 336 } 337 } 338 339 // finally destroy the addon 340 AddOnRemoved(&info); 341 342 // done 343 return; 344 } 345 346 if (!watchingFromDirectory) { 347 // moved into our view 348 349 // update the info 350 strlcpy(info.name, name, sizeof(info.name)); 351 info.nref = entryNodeRef; 352 info.dir_nref = toNodeRef; 353 354 AddOnCreated(&info); 355 356 // Start at the top again, and search until the to directory. 357 // If we find an add-on with the same name then the moved add-on 358 // is not to be enabled. So we are done. 359 DirectoryList::iterator diter = fDirectories.begin(); 360 for (; diter != toIter; diter++) { 361 if (_HasEntry(info.name, diter->entries)) { 362 // The new add-on is being shadowed. 363 return; 364 } 365 } 366 367 // The new add-on should be enabled, but first we check to see 368 // if there is an add-on below us. If we find one, we disable it. 369 for (diter++ ; diter != fDirectories.end(); diter++) { 370 EntryList::iterator eiter = diter->entries.begin(); 371 if (_FindEntry(info.name, diter->entries, eiter)) { 372 AddOnDisabled(&*eiter); 373 break; 374 } 375 } 376 377 // enable the new add-on 378 AddOnEnabled(&info); 379 380 // put the new entry into the target directory 381 _AddNewEntry(toIter->entries, info); 382 383 // done 384 return; 385 } 386 387 // The add-on was renamed, or moved within our hierarchy. 388 389 EntryList::iterator eiter = fromIter->entries.begin(); 390 if (_FindEntry(entryNodeRef, fromIter->entries, eiter)) { 391 // save the old info and remove the entry 392 info = *eiter; 393 } else { 394 // If an entry moved from one watched directory into another watched 395 // directory, there will be two notifications, and this may be the 396 // second. We have handled everything in the first. In that case the 397 // entry was already removed from the fromDirectory and added in the 398 // toDirectory list. 399 return; 400 } 401 402 if (strcmp(info.name, name) == 0) { 403 // It should be impossible for the name to stay the same, unless the 404 // node moved in the watched hierarchy. Handle this case by removing 405 // the entry and readding it. TODO: This can temporarily enable add-ons 406 // which should in fact stay hidden (moving add-on from home to common 407 // folder or vice versa, the system add-on should remain hidden). 408 EntryRemoved(name, fromDirectory, device, node); 409 info.dir_nref = toNodeRef; 410 _EntryCreated(info); 411 } else { 412 // Erase the entry 413 fromIter->entries.erase(eiter); 414 415 // check to see if it was formerly enabled 416 bool wasEnabled = true; 417 DirectoryList::iterator oldIter = fDirectories.begin(); 418 for (; oldIter != fromIter; oldIter++) { 419 if (_HasEntry(info.name, oldIter->entries)) { 420 wasEnabled = false; 421 break; 422 } 423 } 424 425 // If it was enabled, disable it and enable the one under us, if it 426 // exists. 427 if (wasEnabled) { 428 AddOnDisabled(&info); 429 for (; oldIter != fDirectories.end(); oldIter++) { 430 eiter = oldIter->entries.begin(); 431 if (_FindEntry(info.name, oldIter->entries, eiter)) { 432 AddOnEnabled(&*eiter); 433 break; 434 } 435 } 436 } 437 438 // kaboom! 439 AddOnRemoved(&info); 440 441 // set up new addon info 442 strlcpy(info.name, name, sizeof(info.name)); 443 info.dir_nref = toNodeRef; 444 445 // presto! 446 AddOnCreated(&info); 447 448 // check to see if we are newly enabled 449 bool isEnabled = true; 450 DirectoryList::iterator newIter = fDirectories.begin(); 451 for (; newIter != toIter; newIter++) { 452 if (_HasEntry(info.name, newIter->entries)) { 453 isEnabled = false; 454 break; 455 } 456 } 457 458 // if it is newly enabled, check under us for an enabled one, and 459 // disable that first 460 if (isEnabled) { 461 for (; newIter != fDirectories.end(); newIter++) { 462 eiter = newIter->entries.begin(); 463 if (_FindEntry(info.name, newIter->entries, eiter)) { 464 AddOnDisabled(&*eiter); 465 break; 466 } 467 } 468 AddOnEnabled(&info); 469 } 470 // put the new entry into the target directory 471 toIter->entries.push_back(info); 472 } 473 } 474 475 476 void 477 AddOnMonitorHandler::StatChanged(ino_t node, dev_t device, int32 statFields) 478 { 479 // This notification is received for the add-ons themselves. 480 481 // TODO: Add the entry to the pending list, disable/enable it 482 // when the modification time remains stable. 483 484 node_ref entryNodeRef; 485 make_node_ref(device, node, &entryNodeRef); 486 487 DirectoryList::iterator diter = fDirectories.begin(); 488 for (; diter != fDirectories.end(); diter++) { 489 EntryList::iterator eiter = diter->entries.begin(); 490 for (; eiter != diter->entries.end(); eiter++) { 491 if (eiter->addon_nref == entryNodeRef) { 492 // Trigger reloading of the add-on 493 const add_on_entry_info* info = &*eiter; 494 AddOnDisabled(info); 495 AddOnRemoved(info); 496 AddOnCreated(info); 497 AddOnEnabled(info); 498 return; 499 } 500 } 501 } 502 } 503 504 505 // #pragma mark - private 506 507 508 //! Process pending entries. 509 void 510 AddOnMonitorHandler::_HandlePendingEntries() 511 { 512 BDirectory directory; 513 EntryList::iterator iter = fPendingEntries.begin(); 514 while (iter != fPendingEntries.end()) { 515 add_on_entry_info info = *iter; 516 517 // Initialize directory, or re-use from previous iteration, if 518 // directory node_ref remained the same from the last pending entry. 519 node_ref dirNodeRef; 520 if (directory.GetNodeRef(&dirNodeRef) != B_OK 521 || dirNodeRef != info.dir_nref) { 522 if (directory.SetTo(&info.dir_nref) != B_OK) { 523 // invalid directory, discard this pending entry 524 iter = fPendingEntries.erase(iter); 525 continue; 526 } 527 dirNodeRef = info.dir_nref; 528 } 529 530 struct stat st; 531 if (directory.GetStatFor(info.name, &st) != B_OK) { 532 // invalid file name, discard this pending entry 533 iter = fPendingEntries.erase(iter); 534 continue; 535 } 536 537 // stat units are seconds, real_time_clock units are seconds 538 if (real_time_clock() - st.st_mtime < ADD_ON_STABLE_SECONDS) { 539 // entry not stable, skip the entry for this pulse 540 iter++; 541 continue; 542 } 543 544 // we are going to deal with the stable entry, so remove it 545 iter = fPendingEntries.erase(iter); 546 547 _EntryCreated(info); 548 } 549 } 550 551 552 void 553 AddOnMonitorHandler::_EntryCreated(add_on_entry_info& info) 554 { 555 // put the new entry into the directory info 556 DirectoryList::iterator diter = fDirectories.begin(); 557 for (; diter != fDirectories.end(); diter++) { 558 if (diter->nref == info.dir_nref) { 559 _AddNewEntry(diter->entries, info); 560 break; 561 } 562 } 563 564 // report it 565 AddOnCreated(&info); 566 567 // Start at the top again, and search until the directory we put 568 // the new add-on in. If we find an add-on with the same name then 569 // the new add-on should not be enabled. 570 bool enabled = true; 571 DirectoryList::iterator diter2 = fDirectories.begin(); 572 for (; diter2 != diter; diter2++) { 573 if (_HasEntry(info.name, diter2->entries)) { 574 enabled = false; 575 break; 576 } 577 } 578 if (!enabled) 579 return; 580 581 // The new add-on should be enabled, but first we check to see 582 // if there is an add-on shadowed by the new one. If we find one, 583 // we disable it. 584 for (diter++ ; diter != fDirectories.end(); diter++) { 585 EntryList::iterator eiter = diter->entries.begin(); 586 if (_FindEntry(info.name, diter->entries, eiter)) { 587 AddOnDisabled(&*eiter); 588 break; 589 } 590 } 591 592 // enable the new entry 593 AddOnEnabled(&info); 594 } 595 596 597 bool 598 AddOnMonitorHandler::_FindEntry(const node_ref& entry, const EntryList& list, 599 EntryList::iterator& it) const 600 { 601 for (; EntryList::const_iterator(it) != list.end(); it++) { 602 if (it->nref == entry) 603 return true; 604 } 605 return false; 606 } 607 608 609 bool 610 AddOnMonitorHandler::_FindEntry(const char* name, const EntryList& list, 611 EntryList::iterator& it) const 612 { 613 for (; EntryList::const_iterator(it) != list.end(); it++) { 614 if (strcmp(it->name, name) == 0) 615 return true; 616 } 617 return false; 618 } 619 620 621 bool 622 AddOnMonitorHandler::_HasEntry(const node_ref& entry, EntryList& list) const 623 { 624 EntryList::iterator it = list.begin(); 625 return _FindEntry(entry, list, it); 626 } 627 628 629 bool 630 AddOnMonitorHandler::_HasEntry(const char* name, EntryList& list) const 631 { 632 EntryList::iterator it = list.begin(); 633 return _FindEntry(name, list, it); 634 } 635 636 637 bool 638 AddOnMonitorHandler::_FindDirectory(ino_t directory, dev_t device, 639 DirectoryList::iterator& it) const 640 { 641 node_ref nodeRef; 642 make_node_ref(device, directory, &nodeRef); 643 return _FindDirectory(nodeRef, it, fDirectories.end()); 644 } 645 646 647 bool 648 AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef, 649 DirectoryList::iterator& it) const 650 { 651 return _FindDirectory(directoryNodeRef, it, fDirectories.end()); 652 } 653 654 655 bool 656 AddOnMonitorHandler::_FindDirectory(ino_t directory, dev_t device, 657 DirectoryList::iterator& it, 658 const DirectoryList::const_iterator& end) const 659 { 660 node_ref nodeRef; 661 make_node_ref(device, directory, &nodeRef); 662 return _FindDirectory(nodeRef, it, end); 663 } 664 665 666 bool 667 AddOnMonitorHandler::_FindDirectory(const node_ref& directoryNodeRef, 668 DirectoryList::iterator& it, 669 const DirectoryList::const_iterator& end) const 670 { 671 for (; DirectoryList::const_iterator(it) != end; it++) { 672 if (it->nref == directoryNodeRef) 673 return true; 674 } 675 return false; 676 } 677 678 679 void 680 AddOnMonitorHandler::_AddNewEntry(EntryList& list, add_on_entry_info& info) 681 { 682 BDirectory directory(&info.dir_nref); 683 BEntry entry(&directory, info.name, true); 684 685 node_ref addOnRef; 686 if (entry.GetNodeRef(&addOnRef) == B_OK) { 687 watch_node(&addOnRef, B_WATCH_STAT, this); 688 info.addon_nref = addOnRef; 689 } else 690 info.addon_nref = info.nref; 691 692 list.push_back(info); 693 } 694