2 * Copyright 2013 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Ingo Weinhold, ingo_weinhold@gmx.de
10 #include "VirtualDirectoryManager.h"
16 #include <Directory.h>
18 #include <StringList.h>
20 #include <AutoDeleter.h>
21 #include <AutoLocker.h>
22 #include <DriverSettings.h>
23 #include <NotOwningEntryRef.h>
26 #include "MimeTypes.h"
32 static const size_t kMaxVirtualDirectoryFileSize
= 10 * 1024;
33 static const char* const kTemporaryDefinitionFileBaseDirectoryPath
34 = "/tmp/tracker/virtual-directories";
37 // #pragma mark - VirtualDirectoryManager::Info
40 class VirtualDirectoryManager::Info
{
42 typedef BObjectList
<Info
> InfoList
;
45 Info(RootInfo
* root
, Info
* parent
, const BString
& path
,
46 const node_ref
& definitionFileNodeRef
,
47 const entry_ref
& definitionFileEntryRef
)
52 fDefinitionFileNodeRef(definitionFileNodeRef
),
53 fDefinitionFileEntryRef(definitionFileEntryRef
),
55 fChildDefinitionsDirectoryRef(-1, -1),
64 RootInfo
* Root() const
74 const char* Name() const
76 return fDefinitionFileEntryRef
.name
;
79 const BString
& Path() const
84 const node_ref
& DefinitionFileNodeRef() const
86 return fDefinitionFileNodeRef
;
89 const entry_ref
& DefinitionFileEntryRef() const
91 return fDefinitionFileEntryRef
;
94 const InfoList
& Children() const
99 const BString
& Id() const
104 void SetId(const BString
& id
)
109 const node_ref
& ChildDefinitionsDirectoryRef() const
111 return fChildDefinitionsDirectoryRef
;
114 void SetChildDefinitionsDirectoryRef(const node_ref
& ref
)
116 fChildDefinitionsDirectoryRef
= ref
;
119 Info
* GetChild(const char* name
) const
121 for (int32 i
= 0; Info
* child
= fChildren
.ItemAt(i
); i
++) {
122 if (strcmp(name
, child
->Name()) == 0)
128 Info
* CreateChild(const node_ref
& definitionFileNodeRef
,
129 const entry_ref
& definitionFileEntryRef
)
132 if (fPath
.IsEmpty()) {
133 path
= definitionFileEntryRef
.name
;
135 path
.SetToFormat("%s/%s", fPath
.String(),
136 definitionFileEntryRef
.name
);
141 Info
* info
= new(std::nothrow
) Info(fRoot
, this, path
,
142 definitionFileNodeRef
, definitionFileEntryRef
);
143 if (info
== NULL
|| !fChildren
.AddItem(info
)) {
150 bool DeleteChild(Info
* info
)
152 return fChildren
.RemoveItem(info
, true);
155 void DeleteChildAt(int32 index
)
157 delete fChildren
.RemoveItemAt(index
);
164 node_ref fDefinitionFileNodeRef
;
165 entry_ref fDefinitionFileEntryRef
;
167 node_ref fChildDefinitionsDirectoryRef
;
172 // #pragma mark - VirtualDirectoryManager::RootInfo
175 class VirtualDirectoryManager::RootInfo
{
177 RootInfo(const node_ref
& definitionFileNodeRef
,
178 const entry_ref
& definitionFileEntryRef
)
181 fInfo(new(std::nothrow
) VirtualDirectoryManager::Info(this, NULL
,
182 BString(), definitionFileNodeRef
, definitionFileEntryRef
)),
193 status_t
InitCheck() const
195 return fInfo
!= NULL
? B_OK
: B_NO_MEMORY
;
198 bigtime_t
FileTime() const
203 bigtime_t
LastChangeTime() const
205 return fLastChangeTime
;
208 const BStringList
& DirectoryPaths() const
210 return fDirectoryPaths
;
213 status_t
ReadDefinition(bool* _changed
= NULL
)
215 // open the definition file
217 status_t error
= file
.SetTo(&fInfo
->DefinitionFileEntryRef(),
223 error
= file
.GetStat(&st
);
227 bigtime_t fileTime
= st
.st_mtim
.tv_sec
;
229 fileTime
+= st
.st_mtim
.tv_nsec
/ 1000;
230 if (fileTime
== fFileTime
) {
231 if (_changed
!= NULL
)
237 if (node_ref(st
.st_dev
, st
.st_ino
) != fInfo
->DefinitionFileNodeRef())
238 return B_ENTRY_NOT_FOUND
;
241 off_t fileSize
= st
.st_size
;
242 if (fileSize
> (off_t
)kMaxVirtualDirectoryFileSize
)
245 char* buffer
= new(std::nothrow
) char[fileSize
+ 1];
248 ArrayDeleter
<char> bufferDeleter(buffer
);
250 ssize_t bytesRead
= file
.ReadAt(0, buffer
, fileSize
);
254 buffer
[bytesRead
] = '\0';
257 BStringList
oldDirectoryPaths(fDirectoryPaths
);
258 fDirectoryPaths
.MakeEmpty();
260 BDriverSettings driverSettings
;
261 error
= driverSettings
.SetToString(buffer
);
265 BDriverParameterIterator it
266 = driverSettings
.ParameterIterator("directory");
267 while (it
.HasNext()) {
268 BDriverParameter parameter
= it
.Next();
269 for (int32 i
= 0; i
< parameter
.CountValues(); i
++)
270 fDirectoryPaths
.Add(parameter
.ValueAt(i
));
273 // update file time and check whether something has changed
274 fFileTime
= fileTime
;
276 bool changed
= fDirectoryPaths
!= oldDirectoryPaths
;
277 if (changed
|| fLastChangeTime
< 0)
278 fLastChangeTime
= fFileTime
;
280 if (_changed
!= NULL
)
286 VirtualDirectoryManager::Info
* Info() const
292 typedef std::map
<BString
, VirtualDirectoryManager::Info
*> InfoMap
;
295 BStringList fDirectoryPaths
;
296 VirtualDirectoryManager::Info
* fInfo
;
298 // actual file modified time
299 bigtime_t fLastChangeTime
;
300 // last time something actually changed
304 // #pragma mark - VirtualDirectoryManager
307 VirtualDirectoryManager::VirtualDirectoryManager()
309 fLock("virtual directory manager")
314 /*static*/ VirtualDirectoryManager
*
315 VirtualDirectoryManager::Instance()
317 static VirtualDirectoryManager
* manager
318 = new(std::nothrow
) VirtualDirectoryManager
;
324 VirtualDirectoryManager::ResolveDirectoryPaths(
325 const node_ref
& definitionFileNodeRef
,
326 const entry_ref
& definitionFileEntryRef
, BStringList
& _directoryPaths
,
327 node_ref
* _definitionFileNodeRef
, entry_ref
* _definitionFileEntryRef
)
329 Info
* info
= _InfoForNodeRef(definitionFileNodeRef
);
331 status_t error
= _ResolveUnknownDefinitionFile(definitionFileNodeRef
,
332 definitionFileEntryRef
, info
);
337 const BString
& subDirectory
= info
->Path();
338 const BStringList
& rootDirectoryPaths
= info
->Root()->DirectoryPaths();
339 if (subDirectory
.IsEmpty()) {
340 _directoryPaths
= rootDirectoryPaths
;
342 _directoryPaths
.MakeEmpty();
343 int32 count
= rootDirectoryPaths
.CountStrings();
344 for (int32 i
= 0; i
< count
; i
++) {
345 BString path
= rootDirectoryPaths
.StringAt(i
);
346 _directoryPaths
.Add(path
<< '/' << subDirectory
);
350 if (_definitionFileEntryRef
!= NULL
) {
351 *_definitionFileEntryRef
= info
->DefinitionFileEntryRef();
352 if (_definitionFileEntryRef
->name
== NULL
)
356 if (_definitionFileNodeRef
!= NULL
)
357 *_definitionFileNodeRef
= info
->DefinitionFileNodeRef();
364 VirtualDirectoryManager::GetDefinitionFileChangeTime(
365 const node_ref
& definitionFileRef
, bigtime_t
& _time
) const
367 Info
* info
= _InfoForNodeRef(definitionFileRef
);
371 _time
= info
->Root()->LastChangeTime();
377 VirtualDirectoryManager::GetRootDefinitionFile(
378 const node_ref
& definitionFileRef
, node_ref
& _rootDefinitionFileRef
)
380 Info
* info
= _InfoForNodeRef(definitionFileRef
);
384 _rootDefinitionFileRef
= info
->Root()->Info()->DefinitionFileNodeRef();
390 VirtualDirectoryManager::GetSubDirectoryDefinitionFile(
391 const node_ref
& baseDefinitionRef
, const char* subDirName
,
392 entry_ref
& _entryRef
, node_ref
& _nodeRef
)
394 Info
* parentInfo
= _InfoForNodeRef(baseDefinitionRef
);
395 if (parentInfo
== NULL
)
398 Info
* info
= parentInfo
->GetChild(subDirName
);
402 _entryRef
= info
->DefinitionFileEntryRef();
403 _nodeRef
= info
->DefinitionFileNodeRef();
404 return _entryRef
.name
!= NULL
;
409 VirtualDirectoryManager::GetParentDirectoryDefinitionFile(
410 const node_ref
& subDirDefinitionRef
, entry_ref
& _entryRef
,
413 Info
* info
= _InfoForNodeRef(subDirDefinitionRef
);
417 Info
* parentInfo
= info
->Parent();
418 if (parentInfo
== NULL
)
421 _entryRef
= parentInfo
->DefinitionFileEntryRef();
422 _nodeRef
= parentInfo
->DefinitionFileNodeRef();
423 return _entryRef
.name
!= NULL
;
428 VirtualDirectoryManager::TranslateDirectoryEntry(
429 const node_ref
& definitionFileRef
, dirent
* buffer
)
431 NotOwningEntryRef
entryRef(buffer
->d_pdev
, buffer
->d_pino
, buffer
->d_name
);
432 node_ref
nodeRef(buffer
->d_dev
, buffer
->d_ino
);
434 status_t result
= TranslateDirectoryEntry(definitionFileRef
, entryRef
,
439 buffer
->d_pdev
= entryRef
.device
;
440 buffer
->d_pino
= entryRef
.directory
;
441 buffer
->d_dev
= nodeRef
.device
;
442 buffer
->d_ino
= nodeRef
.node
;
449 VirtualDirectoryManager::TranslateDirectoryEntry(
450 const node_ref
& definitionFileRef
, entry_ref
& _entryRef
, node_ref
& _nodeRef
)
452 Info
* parentInfo
= _InfoForNodeRef(definitionFileRef
);
453 if (parentInfo
== NULL
)
456 // get the info for the entry
457 Info
* info
= parentInfo
->GetChild(_entryRef
.name
);
459 // If not done yet, create a directory for the parent, where we can
460 // place the new definition file.
461 if (parentInfo
->Id().IsEmpty()) {
462 BString id
= BUuid().SetToRandom().ToString();
466 BPath
path(kTemporaryDefinitionFileBaseDirectoryPath
, id
);
467 status_t error
= path
.InitCheck();
471 error
= create_directory(path
.Path(),
472 S_IRWXU
| S_IRGRP
| S_IXGRP
| S_IROTH
| S_IXOTH
);
477 if (lstat(path
.Path(), &st
) != 0)
480 parentInfo
->SetId(id
);
481 parentInfo
->SetChildDefinitionsDirectoryRef(
482 node_ref(st
.st_dev
, st
.st_ino
));
485 // create the definition file
486 const node_ref
& directoryRef
487 = parentInfo
->ChildDefinitionsDirectoryRef();
488 NotOwningEntryRef
entryRef(directoryRef
, _entryRef
.name
);
490 BFile definitionFile
;
491 status_t error
= definitionFile
.SetTo(&entryRef
,
492 B_WRITE_ONLY
| B_CREATE_FILE
| B_ERASE_FILE
);
497 error
= definitionFile
.GetNodeRef(&nodeRef
);
501 BNodeInfo
nodeInfo(&definitionFile
);
502 error
= nodeInfo
.SetType(kVirtualDirectoryMimeType
);
507 info
= parentInfo
->CreateChild(nodeRef
, entryRef
);
508 if (info
== NULL
|| !_AddInfo(info
))
511 // Write some info into the definition file that helps us to find the
512 // root definition file. This is only necessary when definition file
513 // entry refs are transferred between applications. Then the receiving
514 // application may need to find the root definition file and resolve
515 // the subdirectories.
516 const entry_ref
& rootEntryRef
517 = parentInfo
->Root()->Info()->DefinitionFileEntryRef();
518 BString definitionFileContent
;
519 definitionFileContent
.SetToFormat(
521 " device %" B_PRIdDEV
"\n"
522 " directory %" B_PRIdINO
"\n"
526 rootEntryRef
.device
, rootEntryRef
.directory
, rootEntryRef
.name
,
527 info
->Path().String());
528 // failure is not nice, but not mission critical for this application
529 if (!definitionFileContent
.IsEmpty()) {
530 definitionFile
.WriteAt(0,
531 definitionFileContent
.String(), definitionFileContent
.Length());
535 const entry_ref
& entryRef
= info
->DefinitionFileEntryRef();
536 _nodeRef
= info
->DefinitionFileNodeRef();
537 _entryRef
.device
= entryRef
.device
;
538 _entryRef
.directory
= entryRef
.directory
;
545 VirtualDirectoryManager::DefinitionFileChanged(
546 const node_ref
& definitionFileRef
)
548 Info
* info
= _InfoForNodeRef(definitionFileRef
);
552 _UpdateTree(info
->Root());
554 return _InfoForNodeRef(definitionFileRef
) != NULL
;
559 VirtualDirectoryManager::DirectoryRemoved(const node_ref
& definitionFileRef
)
561 Info
* info
= _InfoForNodeRef(definitionFileRef
);
563 return B_ENTRY_NOT_FOUND
;
565 _RemoveDirectory(info
);
568 if (info
->Parent() == NULL
)
571 info
->Parent()->DeleteChild(info
);
578 VirtualDirectoryManager::GetEntry(const BStringList
& directoryPaths
,
579 const char* name
, entry_ref
* _ref
, struct stat
* _st
)
581 int32 count
= directoryPaths
.CountStrings();
582 for (int32 i
= 0; i
< count
; i
++) {
584 if (path
.SetTo(directoryPaths
.StringAt(i
), name
) != B_OK
)
588 if (lstat(path
.Path(), &st
) == 0) {
590 if (get_ref_for_path(path
.Path(), _ref
) != B_OK
)
603 VirtualDirectoryManager::Info
*
604 VirtualDirectoryManager::_InfoForNodeRef(const node_ref
& nodeRef
) const
606 NodeRefInfoMap::const_iterator it
= fInfos
.find(nodeRef
);
607 return it
!= fInfos
.end() ? it
->second
: NULL
;
612 VirtualDirectoryManager::_AddInfo(Info
* info
)
615 fInfos
[info
->DefinitionFileNodeRef()] = info
;
624 VirtualDirectoryManager::_RemoveInfo(Info
* info
)
626 NodeRefInfoMap::iterator it
= fInfos
.find(info
->DefinitionFileNodeRef());
627 if (it
!= fInfos
.end())
633 VirtualDirectoryManager::_UpdateTree(RootInfo
* root
)
635 bool changed
= false;
636 status_t result
= root
->ReadDefinition(&changed
);
637 if (result
!= B_OK
) {
638 DirectoryRemoved(root
->Info()->DefinitionFileNodeRef());
645 _UpdateTree(root
->Info());
650 VirtualDirectoryManager::_UpdateTree(Info
* info
)
652 const BStringList
& directoryPaths
= info
->Root()->DirectoryPaths();
654 int32 childCount
= info
->Children().CountItems();
655 for (int32 i
= childCount
-1; i
>= 0; i
--) {
656 Info
* childInfo
= info
->Children().ItemAt(i
);
658 if (GetEntry(directoryPaths
, childInfo
->Path(), NULL
, &st
)
659 && S_ISDIR(st
.st_mode
)) {
660 _UpdateTree(childInfo
);
662 _RemoveDirectory(childInfo
);
663 info
->DeleteChildAt(i
);
670 VirtualDirectoryManager::_RemoveDirectory(Info
* info
)
672 // recursively remove the subdirectories
673 for (int32 i
= 0; Info
* child
= info
->Children().ItemAt(i
); i
++)
674 _RemoveDirectory(child
);
676 // remove the directory for the child definition file
677 if (!info
->Id().IsEmpty()) {
678 BPath
path(kTemporaryDefinitionFileBaseDirectoryPath
, info
->Id());
679 if (path
.InitCheck() == B_OK
)
683 // unless this is the root directory, remove the definition file
684 if (info
!= info
->Root()->Info())
685 BEntry(&info
->DefinitionFileEntryRef()).Remove();
692 VirtualDirectoryManager::_ResolveUnknownDefinitionFile(
693 const node_ref
& definitionFileNodeRef
,
694 const entry_ref
& definitionFileEntryRef
, Info
*& _info
)
696 // This is either a root definition file or a subdir definition file
697 // created by another application. We'll just try to read the info from the
698 // file that a subdir definition file would contain. If that fails, we
699 // assume a root definition file.
702 if (_ReadSubDirectoryDefinitionFileInfo(definitionFileEntryRef
, entryRef
,
703 subDirPath
) != B_OK
) {
704 return _CreateRootInfo(definitionFileNodeRef
, definitionFileEntryRef
,
708 if (subDirPath
.IsEmpty())
711 // get the root definition file node ref
713 status_t error
= BEntry(&entryRef
).GetNodeRef(&nodeRef
);
717 // resolve/create the root info
718 Info
* info
= _InfoForNodeRef(nodeRef
);
720 error
= _CreateRootInfo(nodeRef
, entryRef
, info
);
723 } else if (info
->Root()->Info() != info
)
726 const BStringList
& rootDirectoryPaths
= info
->Root()->DirectoryPaths();
728 // now we can traverse the subdir path and resolve all infos along the way
729 int32 nextComponentOffset
= 0;
730 while (nextComponentOffset
< subDirPath
.Length()) {
731 int32 componentEnd
= subDirPath
.FindFirst('/', nextComponentOffset
);
732 if (componentEnd
>= 0) {
733 // skip duplicate '/'s
734 if (componentEnd
== nextComponentOffset
+ 1) {
735 nextComponentOffset
= componentEnd
;
738 nextComponentOffset
= componentEnd
+ 1;
740 componentEnd
= subDirPath
.Length();
741 nextComponentOffset
= componentEnd
;
744 BString
entryPath(subDirPath
, componentEnd
);
745 if (entryPath
.IsEmpty())
749 if (!GetEntry(rootDirectoryPaths
, entryPath
, &entryRef
, &st
))
750 return B_ENTRY_NOT_FOUND
;
752 if (!S_ISDIR(st
.st_mode
))
755 error
= TranslateDirectoryEntry(info
->DefinitionFileNodeRef(), entryRef
,
760 info
= _InfoForNodeRef(nodeRef
);
770 VirtualDirectoryManager::_CreateRootInfo(const node_ref
& definitionFileNodeRef
,
771 const entry_ref
& definitionFileEntryRef
, Info
*& _info
)
773 RootInfo
* root
= new(std::nothrow
) RootInfo(definitionFileNodeRef
,
774 definitionFileEntryRef
);
775 if (root
== NULL
|| root
->InitCheck() != B_OK
) {
779 ObjectDeleter
<RootInfo
> rootDeleter(root
);
781 status_t error
= root
->ReadDefinition();
785 if (!_AddInfo(root
->Info()))
788 rootDeleter
.Detach();
789 _info
= root
->Info();
796 VirtualDirectoryManager::_ReadSubDirectoryDefinitionFileInfo(
797 const entry_ref
& entryRef
, entry_ref
& _rootDefinitionFileEntryRef
,
798 BString
& _subDirPath
)
800 BDriverSettings driverSettings
;
801 status_t error
= driverSettings
.Load(entryRef
);
805 const char* subDirPath
= driverSettings
.GetParameterValue("subdir");
806 if (subDirPath
== NULL
|| subDirPath
[0] == '\0')
809 BDriverParameter rootParameter
;
810 if (!driverSettings
.FindParameter("root", &rootParameter
))
813 const char* name
= rootParameter
.GetParameterValue("name");
814 dev_t device
= rootParameter
.GetInt32ParameterValue("device", -1, -1);
815 ino_t directory
= rootParameter
.GetInt64ParameterValue("directory");
816 if (name
== NULL
|| name
[0] == '\0' || device
< 0)
819 _rootDefinitionFileEntryRef
= entry_ref(device
, directory
, name
);
820 _subDirPath
= subDirPath
;
822 return !_subDirPath
.IsEmpty() && _rootDefinitionFileEntryRef
.name
!= NULL
823 ? B_OK
: B_NO_MEMORY
;
826 } // namespace BPrivate