xref: /haiku/src/kits/debugger/files/FileManager.cpp (revision 7457ccb4b2f4786525d3b7bda42598487d57ab7d)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2011-2017, Rene Gollent, rene@gollent.com.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 #include "FileManager.h"
8 
9 #include <new>
10 
11 #include <AutoDeleter.h>
12 #include <AutoLocker.h>
13 
14 #include "LocatableDirectory.h"
15 #include "LocatableFile.h"
16 #include "SourceFile.h"
17 #include "StringUtils.h"
18 #include "TeamFileManagerSettings.h"
19 
20 
21 // #pragma mark - EntryPath
22 
23 
24 struct FileManager::EntryPath {
25 	const char*	directory;
26 	const char*	name;
27 
28 	EntryPath(const char* directory, const char* name)
29 		:
30 		directory(directory),
31 		name(name)
32 	{
33 	}
34 
35 	EntryPath(const BString& directory, const BString& name)
36 		:
37 		directory(directory.Length() > 0 ? directory.String() : NULL),
38 		name(name.String())
39 	{
40 	}
41 
42 	EntryPath(const LocatableEntry* entry)
43 		:
44 		directory(NULL),
45 		name(entry->Name())
46 	{
47 		LocatableDirectory* parent = entry->Parent();
48 		if (parent != NULL && strlen(parent->Path()) > 0)
49 			directory = parent->Path();
50 	}
51 
52 	EntryPath(const EntryPath& other)
53 		:
54 		directory(other.directory),
55 		name(other.name)
56 	{
57 	}
58 
59 	size_t HashValue() const
60 	{
61 		return StringUtils::HashValue(directory)
62 			^ StringUtils::HashValue(name);
63 	}
64 
65 	bool operator==(const EntryPath& other) const
66 	{
67 		if (directory != other.directory
68 			&& (directory == NULL || other.directory == NULL
69 				|| strcmp(directory, other.directory) != 0)) {
70 			return false;
71 		}
72 
73 		return strcmp(name, other.name) == 0;
74 	}
75 };
76 
77 
78 // #pragma mark - EntryHashDefinition
79 
80 
81 struct FileManager::EntryHashDefinition {
82 	typedef EntryPath		KeyType;
83 	typedef	LocatableEntry	ValueType;
84 
85 	size_t HashKey(const EntryPath& key) const
86 	{
87 		return key.HashValue();
88 	}
89 
90 	size_t Hash(const LocatableEntry* value) const
91 	{
92 		return HashKey(EntryPath(value));
93 	}
94 
95 	bool Compare(const EntryPath& key, const LocatableEntry* value) const
96 	{
97 		return EntryPath(value) == key;
98 	}
99 
100 	LocatableEntry*& GetLink(LocatableEntry* value) const
101 	{
102 		return value->fNext;
103 	}
104 };
105 
106 
107 // #pragma mark - Domain
108 
109 
110 class FileManager::Domain : private LocatableEntryOwner {
111 public:
112 	Domain(FileManager* manager, bool isLocal)
113 		:
114 		fManager(manager),
115 		fIsLocal(isLocal)
116 	{
117 	}
118 
119 	~Domain()
120 	{
121 		LocatableEntry* entry = fEntries.Clear(true);
122 		while (entry != NULL) {
123 			LocatableEntry* next = entry->fNext;
124 			entry->ReleaseReference();
125 			entry = next;
126 		}
127 	}
128 
129 	status_t Init()
130 	{
131 		status_t error = fEntries.Init();
132 		if (error != B_OK)
133 			return error;
134 
135 		return B_OK;
136 	}
137 
138 	LocatableFile* GetFile(const BString& directoryPath,
139 		const BString& relativePath)
140 	{
141 		if (directoryPath.Length() == 0 || relativePath[0] == '/')
142 			return GetFile(relativePath);
143 		return GetFile(BString(directoryPath) << '/' << relativePath);
144 	}
145 
146 	LocatableFile* GetFile(const BString& path)
147 	{
148 		BString directoryPath;
149 		BString name;
150 		_SplitPath(path, directoryPath, name);
151 		LocatableFile* file = _GetFile(directoryPath, name);
152 		if (file == NULL)
153 			return NULL;
154 
155 		// try to auto-locate the file
156 		if (LocatableDirectory* directory = file->Parent()) {
157 			if (directory->State() == LOCATABLE_ENTRY_UNLOCATED) {
158 				// parent not yet located -- try locate with the entry's path
159 				BString path;
160 				file->GetPath(path);
161 				_LocateEntry(file, path, true, true);
162 			} else {
163 				// parent already located -- locate the entry in the parent
164 				BString locatedDirectoryPath;
165 				if (directory->GetLocatedPath(locatedDirectoryPath))
166 					_LocateEntryInParentDir(file, locatedDirectoryPath, true);
167 			}
168 		}
169 
170 		return file;
171 	}
172 
173 	void EntryLocated(const BString& path, const BString& locatedPath)
174 	{
175 		BString directory;
176 		BString name;
177 		_SplitPath(path, directory, name);
178 
179 		LocatableEntry* entry = _LookupEntry(EntryPath(directory, name));
180 		if (entry == NULL)
181 			return;
182 
183 		_LocateEntry(entry, locatedPath, false, true);
184 	}
185 
186 private:
187 	virtual bool Lock()
188 	{
189 		return fManager->Lock();
190 	}
191 
192 	virtual void Unlock()
193 	{
194 		fManager->Unlock();
195 	}
196 
197 	virtual void LocatableEntryUnused(LocatableEntry* entry)
198 	{
199 		AutoLocker<FileManager> lock(fManager);
200 		if (fEntries.Lookup(EntryPath(entry)) == entry)
201 			fEntries.Remove(entry);
202 
203 		LocatableDirectory* parent = entry->Parent();
204 		if (parent != NULL)
205 			parent->RemoveEntry(entry);
206 	}
207 
208 	bool _LocateDirectory(LocatableDirectory* directory,
209 		const BString& locatedPath, bool implicit)
210 	{
211 		if (directory == NULL
212 			|| directory->State() != LOCATABLE_ENTRY_UNLOCATED) {
213 			return false;
214 		}
215 
216 		if (!_LocateEntry(directory, locatedPath, implicit, true))
217 			return false;
218 
219 		_LocateEntries(directory, locatedPath, implicit);
220 
221 		return true;
222 	}
223 
224 	bool _LocateEntry(LocatableEntry* entry, const BString& locatedPath,
225 		bool implicit, bool locateAncestors)
226 	{
227 		if (implicit && entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY)
228 			return false;
229 
230 		struct stat st;
231 		if (stat(locatedPath, &st) != 0)
232 			return false;
233 
234 		if (S_ISDIR(st.st_mode)) {
235 			LocatableDirectory* directory
236 				= dynamic_cast<LocatableDirectory*>(entry);
237 			if (directory == NULL)
238 				return false;
239 			entry->SetLocatedPath(locatedPath, implicit);
240 		} else if (S_ISREG(st.st_mode)) {
241 			LocatableFile* file = dynamic_cast<LocatableFile*>(entry);
242 			if (file == NULL)
243 				return false;
244 			entry->SetLocatedPath(locatedPath, implicit);
245 		}
246 
247 		// locate the ancestor directories, if requested
248 		if (locateAncestors) {
249 			BString locatedDirectory;
250 			BString locatedName;
251 			_SplitPath(locatedPath, locatedDirectory, locatedName);
252 			if (locatedName == entry->Name())
253 				_LocateDirectory(entry->Parent(), locatedDirectory, implicit);
254 		}
255 
256 		return true;
257 	}
258 
259 	bool _LocateEntryInParentDir(LocatableEntry* entry,
260 		const BString& locatedDirectoryPath, bool implicit)
261 	{
262 		// construct the located entry path
263 		BString locatedEntryPath(locatedDirectoryPath);
264 		int32 pathLength = locatedEntryPath.Length();
265 		if (pathLength >= 1 && locatedEntryPath[pathLength - 1] != '/')
266 			locatedEntryPath << '/';
267 		locatedEntryPath << entry->Name();
268 
269 		return _LocateEntry(entry, locatedEntryPath, implicit, false);
270 	}
271 
272 	void _LocateEntries(LocatableDirectory* directory,
273 		const BString& locatedPath, bool implicit)
274 	{
275 		for (LocatableEntryList::ConstIterator it
276 				= directory->Entries().GetIterator();
277 			LocatableEntry* entry = it.Next();) {
278 			if (entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY)
279 				continue;
280 
281 			 if (_LocateEntryInParentDir(entry, locatedPath, implicit)) {
282 				// recurse for directories
283 				if (LocatableDirectory* subDir
284 						= dynamic_cast<LocatableDirectory*>(entry)) {
285 					BString locatedEntryPath;
286 					if (subDir->GetLocatedPath(locatedEntryPath))
287 						_LocateEntries(subDir, locatedEntryPath, implicit);
288 				}
289 			}
290 		}
291 	}
292 
293 	LocatableFile* _GetFile(const BString& directoryPath, const BString& name)
294 	{
295 		BString normalizedDirPath;
296 		_NormalizePath(directoryPath, normalizedDirPath);
297 
298 		// if already known return the file
299 		LocatableEntry* entry = _LookupEntry(EntryPath(normalizedDirPath, name));
300 		if (entry != NULL) {
301 			LocatableFile* file = dynamic_cast<LocatableFile*>(entry);
302 			if (file == NULL)
303 				return NULL;
304 
305 			if (file->AcquireReference() == 0)
306 				fEntries.Remove(file);
307 			else
308 				return file;
309 		}
310 
311 		// no such file yet -- create it
312 		LocatableDirectory* directory = _GetDirectory(normalizedDirPath);
313 		if (directory == NULL)
314 			return NULL;
315 
316 		LocatableFile* file = new(std::nothrow) LocatableFile(this, directory,
317 			name);
318 		if (file == NULL) {
319 			directory->ReleaseReference();
320 			return NULL;
321 		}
322 
323 		directory->AddEntry(file);
324 
325 		fEntries.Insert(file);
326 
327 		return file;
328 	}
329 
330 	LocatableDirectory* _GetDirectory(const BString& path)
331 	{
332 		BString directoryPath;
333 		BString fileName;
334 		_SplitNormalizedPath(path, directoryPath, fileName);
335 
336 		// if already know return the directory
337 		LocatableEntry* entry
338 			= _LookupEntry(EntryPath(directoryPath, fileName));
339 		if (entry != NULL) {
340 			LocatableDirectory* directory
341 				= dynamic_cast<LocatableDirectory*>(entry);
342 			if (directory == NULL)
343 				return NULL;
344 			directory->AcquireReference();
345 			return directory;
346 		}
347 
348 		// get the parent directory
349 		LocatableDirectory* parentDirectory = NULL;
350 		if (directoryPath.Length() > 0) {
351 			parentDirectory = _GetDirectory(directoryPath);
352 			if (parentDirectory == NULL)
353 				return NULL;
354 		}
355 
356 		// create a new directory
357 		LocatableDirectory* directory = new(std::nothrow) LocatableDirectory(
358 			this, parentDirectory, path);
359 		if (directory == NULL) {
360 			parentDirectory->ReleaseReference();
361 			return NULL;
362 		}
363 
364 		// auto-locate, if possible
365 		if (fIsLocal) {
366 			BString dirPath;
367 			directory->GetPath(dirPath);
368 			directory->SetLocatedPath(dirPath, false);
369 		} else if (parentDirectory != NULL
370 			&& parentDirectory->State() != LOCATABLE_ENTRY_UNLOCATED) {
371 			BString locatedDirectoryPath;
372 			if (parentDirectory->GetLocatedPath(locatedDirectoryPath))
373 				_LocateEntryInParentDir(directory, locatedDirectoryPath, true);
374 		}
375 
376 		if (parentDirectory != NULL)
377 			parentDirectory->AddEntry(directory);
378 
379 		fEntries.Insert(directory);
380 		return directory;
381 	}
382 
383 	LocatableEntry* _LookupEntry(const EntryPath& entryPath)
384 	{
385 		LocatableEntry* entry = fEntries.Lookup(entryPath);
386 		if (entry == NULL)
387 			return NULL;
388 
389 		// if already unreferenced, remove it
390 		if (entry->CountReferences() == 0) {
391 			fEntries.Remove(entry);
392 			return NULL;
393 		}
394 
395 		return entry;
396 	}
397 
398 	void _NormalizePath(const BString& path, BString& _normalizedPath)
399 	{
400 		BString normalizedPath;
401 		char* buffer = normalizedPath.LockBuffer(path.Length());
402 		int32 outIndex = 0;
403 		const char* remaining = path.String();
404 
405 		while (*remaining != '\0') {
406 			// collapse repeated slashes
407 			if (*remaining == '/') {
408 				buffer[outIndex++] = '/';
409 				remaining++;
410 				while (*remaining == '/')
411 					remaining++;
412 			}
413 
414 			if (*remaining == '\0') {
415 				// remove trailing slash (unless it's "/" only)
416 				if (outIndex > 1)
417 					outIndex--;
418 				break;
419 			}
420 
421 			// skip "." components
422 			if (*remaining == '.') {
423 				if (remaining[1] == '\0')
424 					break;
425 
426 				if (remaining[1] == '/') {
427 					remaining += 2;
428 					while (*remaining == '/')
429 						remaining++;
430 					continue;
431 				}
432 			}
433 
434 			// copy path component
435 			while (*remaining != '\0' && *remaining != '/')
436 				buffer[outIndex++] = *(remaining++);
437 		}
438 
439 		// If the path didn't change, use the original path (BString's copy on
440 		// write mechanism) rather than the new string.
441 		if (outIndex == path.Length()) {
442 			_normalizedPath = path;
443 		} else {
444 			normalizedPath.UnlockBuffer(outIndex);
445 			_normalizedPath = normalizedPath;
446 		}
447 	}
448 
449 	void _SplitPath(const BString& path, BString& _directory, BString& _name)
450 	{
451 		BString normalized;
452 		_NormalizePath(path, normalized);
453 		_SplitNormalizedPath(normalized, _directory, _name);
454 	}
455 
456 	void _SplitNormalizedPath(const BString& path, BString& _directory,
457 		BString& _name)
458 	{
459 		// handle single component (including root dir) cases
460 		int32 lastSlash = path.FindLast('/');
461 		if (lastSlash < 0 || path.Length() == 1) {
462 			_directory = (const char*)NULL;
463 			_name = path;
464 			return;
465 		}
466 
467 		// handle root dir + one component and multi component cases
468 		if (lastSlash == 0)
469 			_directory = "/";
470 		else
471 			_directory.SetTo(path, lastSlash);
472 		_name = path.String() + (lastSlash + 1);
473 	}
474 
475 private:
476 	FileManager*		fManager;
477 	LocatableEntryTable	fEntries;
478 	bool				fIsLocal;
479 };
480 
481 
482 // #pragma mark - SourceFileEntry
483 
484 
485 struct FileManager::SourceFileEntry : public SourceFileOwner {
486 
487 	FileManager*		manager;
488 	BString				path;
489 	SourceFile*			file;
490 	SourceFileEntry*	next;
491 
492 	SourceFileEntry(FileManager* manager, const BString& path)
493 		:
494 		manager(manager),
495 		path(path),
496 		file(NULL)
497 	{
498 	}
499 
500 	virtual void SourceFileUnused(SourceFile* sourceFile)
501 	{
502 		manager->_SourceFileUnused(this);
503 	}
504 
505 	virtual void SourceFileDeleted(SourceFile* sourceFile)
506 	{
507 		// We have already been removed from the table, so commit suicide.
508 		delete this;
509 	}
510 };
511 
512 
513 // #pragma mark - SourceFileHashDefinition
514 
515 
516 struct FileManager::SourceFileHashDefinition {
517 	typedef BString			KeyType;
518 	typedef	SourceFileEntry	ValueType;
519 
520 	size_t HashKey(const BString& key) const
521 	{
522 		return StringUtils::HashValue(key);
523 	}
524 
525 	size_t Hash(const SourceFileEntry* value) const
526 	{
527 		return HashKey(value->path);
528 	}
529 
530 	bool Compare(const BString& key, const SourceFileEntry* value) const
531 	{
532 		return value->path == key;
533 	}
534 
535 	SourceFileEntry*& GetLink(SourceFileEntry* value) const
536 	{
537 		return value->next;
538 	}
539 };
540 
541 
542 // #pragma mark - FileManager
543 
544 
545 FileManager::FileManager()
546 	:
547 	fLock("file manager"),
548 	fTargetDomain(NULL),
549 	fSourceDomain(NULL),
550 	fSourceFiles(NULL)
551 {
552 }
553 
554 
555 FileManager::~FileManager()
556 {
557 	delete fTargetDomain;
558 	delete fSourceDomain;
559 
560 	SourceFileEntry* entry = fSourceFiles->Clear();
561 	while (entry != NULL) {
562 		SourceFileEntry* next = entry->next;
563 		delete entry;
564 		entry = next;
565 	}
566 	delete fSourceFiles;
567 }
568 
569 
570 status_t
571 FileManager::Init(bool targetIsLocal)
572 {
573 	status_t error = fLock.InitCheck();
574 	if (error != B_OK)
575 		return error;
576 
577 	// create target domain
578 	fTargetDomain = new(std::nothrow) Domain(this, targetIsLocal);
579 	if (fTargetDomain == NULL)
580 		return B_NO_MEMORY;
581 
582 	error = fTargetDomain->Init();
583 	if (error != B_OK)
584 		return error;
585 
586 	// create source domain
587 	fSourceDomain = new(std::nothrow) Domain(this, false);
588 	if (fSourceDomain == NULL)
589 		return B_NO_MEMORY;
590 
591 	error = fSourceDomain->Init();
592 	if (error != B_OK)
593 		return error;
594 
595 	// create source file table
596 	fSourceFiles = new(std::nothrow) SourceFileTable;
597 	if (fSourceFiles == NULL)
598 		return B_NO_MEMORY;
599 
600 	error = fSourceFiles->Init();
601 	if (error != B_OK)
602 		return error;
603 
604 	return B_OK;
605 }
606 
607 
608 LocatableFile*
609 FileManager::GetTargetFile(const BString& directory,
610 	const BString& relativePath)
611 {
612 	AutoLocker<FileManager> locker(this);
613 	return fTargetDomain->GetFile(directory, relativePath);
614 }
615 
616 
617 LocatableFile*
618 FileManager::GetTargetFile(const BString& path)
619 {
620 	AutoLocker<FileManager> locker(this);
621 	return fTargetDomain->GetFile(path);
622 }
623 
624 
625 void
626 FileManager::TargetEntryLocated(const BString& path,
627 	const BString& locatedPath)
628 {
629 	AutoLocker<FileManager> locker(this);
630 	fTargetDomain->EntryLocated(path, locatedPath);
631 }
632 
633 
634 LocatableFile*
635 FileManager::GetSourceFile(const BString& directory,
636 	const BString& relativePath)
637 {
638 	AutoLocker<FileManager> locker(this);
639 	LocatableFile* file = fSourceDomain->GetFile(directory, relativePath);
640 
641 	return file;
642 }
643 
644 
645 LocatableFile*
646 FileManager::GetSourceFile(const BString& path)
647 {
648 	AutoLocker<FileManager> locker(this);
649 	LocatableFile* file = fSourceDomain->GetFile(path);
650 
651 	return file;
652 }
653 
654 
655 status_t
656 FileManager::SourceEntryLocated(const BString& path,
657 	const BString& locatedPath)
658 {
659 	AutoLocker<FileManager> locker(this);
660 
661 	// check if we already have this path mapped. If so,
662 	// first clear the mapping, as the user may be attempting
663 	// to correct an existing entry.
664 	SourceFileEntry* entry = _LookupSourceFile(path);
665 	if (entry != NULL)
666 		_SourceFileUnused(entry);
667 
668 	fSourceDomain->EntryLocated(path, locatedPath);
669 
670 	try {
671 		fSourceLocationMappings[path] = locatedPath;
672 	} catch (...) {
673 		return B_NO_MEMORY;
674 	}
675 
676 	return B_OK;
677 }
678 
679 
680 status_t
681 FileManager::LoadSourceFile(LocatableFile* file, SourceFile*& _sourceFile)
682 {
683 	AutoLocker<FileManager> locker(this);
684 
685 	// get the path
686 	BString path;
687 	BString originalPath;
688 	file->GetPath(originalPath);
689 	if (!file->GetLocatedPath(path)) {
690 		// see if this is a file we have a lazy mapping for.
691 		if (!_LocateFileIfMapped(originalPath, file)
692 			|| !file->GetLocatedPath(path)) {
693 			return B_ENTRY_NOT_FOUND;
694 		}
695 	}
696 
697 	// we might already know the source file
698 	SourceFileEntry* entry = _LookupSourceFile(originalPath);
699 	if (entry != NULL) {
700 		entry->file->AcquireReference();
701 		_sourceFile = entry->file;
702 		return B_OK;
703 	}
704 
705 	// create the hash table entry
706 	entry = new(std::nothrow) SourceFileEntry(this, originalPath);
707 	if (entry == NULL)
708 		return B_NO_MEMORY;
709 
710 	// load the file
711 	SourceFile* sourceFile = new(std::nothrow) SourceFile(entry);
712 	if (sourceFile == NULL) {
713 		delete entry;
714 		return B_NO_MEMORY;
715 	}
716 	ObjectDeleter<SourceFile> sourceFileDeleter(sourceFile);
717 
718 	entry->file = sourceFile;
719 
720 	status_t error = sourceFile->Init(path);
721 	if (error != B_OK)
722 		return error;
723 
724 	fSourceFiles->Insert(entry);
725 
726 	_sourceFile = sourceFileDeleter.Detach();
727 	return B_OK;
728 }
729 
730 
731 status_t
732 FileManager::LoadLocationMappings(TeamFileManagerSettings* settings)
733 {
734 	AutoLocker<FileManager> locker(this);
735 	for (int32 i = 0; i < settings->CountSourceMappings(); i++) {
736 		BString sourcePath;
737 		BString locatedPath;
738 
739 		if (settings->GetSourceMappingAt(i, sourcePath, locatedPath) != B_OK)
740 			return B_NO_MEMORY;
741 
742 		try {
743 			fSourceLocationMappings[sourcePath] = locatedPath;
744 		} catch (...) {
745 			return B_NO_MEMORY;
746 		}
747 	}
748 
749 	return B_OK;
750 }
751 
752 
753 status_t
754 FileManager::SaveLocationMappings(TeamFileManagerSettings* settings)
755 {
756 	AutoLocker<FileManager> locker(this);
757 
758 	for (LocatedFileMap::const_iterator it = fSourceLocationMappings.begin();
759 		it != fSourceLocationMappings.end(); ++it) {
760 		status_t error = settings->AddSourceMapping(it->first, it->second);
761 		if (error != B_OK)
762 			return error;
763 	}
764 
765 	return B_OK;
766 }
767 
768 
769 FileManager::SourceFileEntry*
770 FileManager::_LookupSourceFile(const BString& path)
771 {
772 	SourceFileEntry* entry = fSourceFiles->Lookup(path);
773 	if (entry == NULL)
774 		return NULL;
775 
776 	// the entry might be unused already -- in that case remove it
777 	if (entry->file->CountReferences() == 0) {
778 		fSourceFiles->Remove(entry);
779 		return NULL;
780 	}
781 
782 	return entry;
783 }
784 
785 
786 void
787 FileManager::_SourceFileUnused(SourceFileEntry* entry)
788 {
789 	AutoLocker<FileManager> locker(this);
790 
791 	SourceFileEntry* otherEntry = fSourceFiles->Lookup(entry->path);
792 	if (otherEntry == entry)
793 		fSourceFiles->Remove(entry);
794 }
795 
796 
797 bool
798 FileManager::_LocateFileIfMapped(const BString& sourcePath,
799 	LocatableFile* file)
800 {
801 	// called with lock held
802 
803 	LocatedFileMap::const_iterator it = fSourceLocationMappings.find(
804 		sourcePath);
805 	if (it != fSourceLocationMappings.end()
806 		&& file->State() != LOCATABLE_ENTRY_LOCATED_EXPLICITLY
807 		&& file->State() != LOCATABLE_ENTRY_LOCATED_IMPLICITLY) {
808 		fSourceDomain->EntryLocated(it->first, it->second);
809 		return true;
810 	}
811 
812 	return false;
813 }
814