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