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