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