1 /* Copyright (C) 2017 Wildfire Games.
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "precompiled.h"
24 #include "lib/file/vfs/vfs.h"
25 
26 #include "lib/allocators/shared_ptr.h"
27 #include "lib/posix/posix_pthread.h"
28 #include "lib/file/file_system.h"
29 #include "lib/file/common/file_stats.h"
30 #include "lib/file/common/trace.h"
31 #include "lib/file/archive/archive.h"
32 #include "lib/file/io/io.h"
33 #include "lib/file/vfs/vfs_tree.h"
34 #include "lib/file/vfs/vfs_lookup.h"
35 #include "lib/file/vfs/vfs_populate.h"
36 
37 static const StatusDefinition vfsStatusDefinitions[] = {
38 	{ ERR::VFS_DIR_NOT_FOUND, L"VFS directory not found" },
39 	{ ERR::VFS_FILE_NOT_FOUND, L"VFS file not found" },
40 	{ ERR::VFS_ALREADY_MOUNTED, L"VFS path already mounted" }
41 };
42 STATUS_ADD_DEFINITIONS(vfsStatusDefinitions);
43 
44 static pthread_mutex_t vfs_mutex = PTHREAD_MUTEX_INITIALIZER;
45 namespace {
46 struct ScopedLock
47 {
ScopedLock__anonbaba9e240111::ScopedLock48 	ScopedLock() { pthread_mutex_lock(&vfs_mutex); }
~ScopedLock__anonbaba9e240111::ScopedLock49 	~ScopedLock() { pthread_mutex_unlock(&vfs_mutex); }
50 };
51 } // namespace
52 
53 class VFS : public IVFS
54 {
55 public:
VFS()56 	VFS() : m_trace(CreateDummyTrace(8*MiB))
57 	{
58 	}
59 
Mount(const VfsPath & mountPoint,const OsPath & path,size_t flags,size_t priority)60 	virtual Status Mount(const VfsPath& mountPoint, const OsPath& path, size_t flags /* = 0 */, size_t priority /* = 0 */)
61 	{
62 		ScopedLock s;
63 		if(!DirectoryExists(path))
64 		{
65 			if(flags & VFS_MOUNT_MUST_EXIST)
66 				return ERR::VFS_DIR_NOT_FOUND;	// NOWARN
67 			else
68 				RETURN_STATUS_IF_ERR(CreateDirectories(path, 0700));
69 		}
70 
71 		VfsDirectory* directory;
72 		WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(mountPoint, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE));
73 
74 		PRealDirectory realDirectory(new RealDirectory(path, priority, flags));
75 		RETURN_STATUS_IF_ERR(vfs_Attach(directory, realDirectory));
76 		return INFO::OK;
77 	}
78 
GetFileInfo(const VfsPath & pathname,CFileInfo * pfileInfo) const79 	virtual Status GetFileInfo(const VfsPath& pathname, CFileInfo* pfileInfo) const
80 	{
81 		ScopedLock s;
82 		VfsDirectory* directory;
83 		VfsFile* file;
84 
85 		Status ret = vfs_Lookup(pathname, &m_rootDirectory, directory, &file);
86 		if(!pfileInfo)	// just indicate if the file exists without raising warnings.
87 			return ret;
88 		WARN_RETURN_STATUS_IF_ERR(ret);
89 		*pfileInfo = CFileInfo(file->Name(), file->Size(), file->MTime());
90 		return INFO::OK;
91 	}
92 
GetFilePriority(const VfsPath & pathname,size_t * ppriority) const93 	virtual Status GetFilePriority(const VfsPath& pathname, size_t* ppriority) const
94 	{
95 		ScopedLock s;
96 		VfsDirectory* directory; VfsFile* file;
97 		RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file));
98 		*ppriority = file->Priority();
99 		return INFO::OK;
100 	}
101 
GetDirectoryEntries(const VfsPath & path,CFileInfos * fileInfos,DirectoryNames * subdirectoryNames) const102 	virtual Status GetDirectoryEntries(const VfsPath& path, CFileInfos* fileInfos, DirectoryNames* subdirectoryNames) const
103 	{
104 		ScopedLock s;
105 		VfsDirectory* directory;
106 		RETURN_STATUS_IF_ERR(vfs_Lookup(path, &m_rootDirectory, directory, 0));
107 
108 		if(fileInfos)
109 		{
110 			const VfsDirectory::VfsFiles& files = directory->Files();
111 			fileInfos->clear();
112 			fileInfos->reserve(files.size());
113 			for(VfsDirectory::VfsFiles::const_iterator it = files.begin(); it != files.end(); ++it)
114 			{
115 				const VfsFile& file = it->second;
116 				fileInfos->push_back(CFileInfo(file.Name(), file.Size(), file.MTime()));
117 			}
118 		}
119 
120 		if(subdirectoryNames)
121 		{
122 			const VfsDirectory::VfsSubdirectories& subdirectories = directory->Subdirectories();
123 			subdirectoryNames->clear();
124 			subdirectoryNames->reserve(subdirectories.size());
125 			for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it)
126 				subdirectoryNames->push_back(it->first);
127 		}
128 
129 		return INFO::OK;
130 	}
131 
CreateFile(const VfsPath & pathname,const shared_ptr<u8> & fileContents,size_t size)132 	virtual Status CreateFile(const VfsPath& pathname, const shared_ptr<u8>& fileContents, size_t size)
133 	{
134 		ScopedLock s;
135 		VfsDirectory* directory;
136 		Status st;
137 		st = vfs_Lookup(pathname, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_CREATE_ALWAYS);
138 		if (st == ERR::FILE_ACCESS)
139 			return ERR::FILE_ACCESS;
140 
141 		WARN_RETURN_STATUS_IF_ERR(st);
142 
143 		const PRealDirectory& realDirectory = directory->AssociatedDirectory();
144 		const OsPath name = pathname.Filename();
145 		RETURN_STATUS_IF_ERR(realDirectory->Store(name, fileContents, size));
146 
147 		const VfsFile file(name, size, time(0), realDirectory->Priority(), realDirectory);
148 		directory->AddFile(file);
149 
150 		m_trace->NotifyStore(pathname, size);
151 		return INFO::OK;
152 	}
153 
ReplaceFile(const VfsPath & pathname,const shared_ptr<u8> & fileContents,size_t size)154 	virtual Status ReplaceFile(const VfsPath& pathname, const shared_ptr<u8>& fileContents, size_t size)
155 	{
156 		ScopedLock s;
157 		VfsDirectory* directory;
158 		VfsFile* file;
159 		Status st;
160 		st = vfs_Lookup(pathname, &m_rootDirectory, directory, &file, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE);
161 
162 		// There is no such file, create it.
163 		if (st == ERR::VFS_FILE_NOT_FOUND)
164 		{
165 			s.~ScopedLock();
166 			return CreateFile(pathname, fileContents, size);
167 		}
168 
169 		WARN_RETURN_STATUS_IF_ERR(st);
170 
171 		RealDirectory realDirectory(file->Loader()->Path(), file->Priority(), directory->AssociatedDirectory()->Flags());
172 		RETURN_STATUS_IF_ERR(realDirectory.Store(pathname.Filename(), fileContents, size));
173 
174 		directory->AddFile(*file);
175 
176 		m_trace->NotifyStore(pathname, size);
177 		return INFO::OK;
178 	}
179 
LoadFile(const VfsPath & pathname,shared_ptr<u8> & fileContents,size_t & size)180 	virtual Status LoadFile(const VfsPath& pathname, shared_ptr<u8>& fileContents, size_t& size)
181 	{
182 		ScopedLock s;
183 
184 		VfsDirectory* directory; VfsFile* file;
185 		// per 2010-05-01 meeting, this shouldn't raise 'scary error
186 		// dialogs', which might fail to display the culprit pathname
187 		// instead, callers should log the error, including pathname.
188 		RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file));
189 
190 		fileContents = DummySharedPtr((u8*)0);
191 		size = file->Size();
192 
193 		RETURN_STATUS_IF_ERR(AllocateAligned(fileContents, size, maxSectorSize));
194 		RETURN_STATUS_IF_ERR(file->Loader()->Load(file->Name(), fileContents, file->Size()));
195 
196 		stats_io_user_request(size);
197 		m_trace->NotifyLoad(pathname, size);
198 
199 		return INFO::OK;
200 	}
201 
TextRepresentation() const202 	virtual std::wstring TextRepresentation() const
203 	{
204 		ScopedLock s;
205 		std::wstring textRepresentation;
206 		textRepresentation.reserve(100*KiB);
207 		DirectoryDescriptionR(textRepresentation, m_rootDirectory, 0);
208 		return textRepresentation;
209 	}
210 
GetRealPath(const VfsPath & pathname,OsPath & realPathname)211 	virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname)
212 	{
213 		ScopedLock s;
214 		VfsDirectory* directory; VfsFile* file;
215 		WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file));
216 		realPathname = file->Loader()->Path() / pathname.Filename();
217 		return INFO::OK;
218 	}
219 
GetDirectoryRealPath(const VfsPath & pathname,OsPath & realPathname)220 	virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname)
221 	{
222 		ScopedLock s;
223 		VfsDirectory* directory;
224 		WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, NULL));
225 		realPathname = directory->AssociatedDirectory()->Path();
226 		return INFO::OK;
227 	}
228 
GetVirtualPath(const OsPath & realPathname,VfsPath & pathname)229 	virtual Status GetVirtualPath(const OsPath& realPathname, VfsPath& pathname)
230 	{
231 		ScopedLock s;
232 		const OsPath realPath = realPathname.Parent()/"";
233 		VfsPath path;
234 		RETURN_STATUS_IF_ERR(FindRealPathR(realPath, m_rootDirectory, L"", path));
235 		pathname = path / realPathname.Filename();
236 		return INFO::OK;
237 	}
238 
RemoveFile(const VfsPath & pathname)239 	virtual Status RemoveFile(const VfsPath& pathname)
240 	{
241 		ScopedLock s;
242 
243 		VfsDirectory* directory; VfsFile* file;
244 		RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file));
245 		directory->RemoveFile(file->Name());
246 
247 		return INFO::OK;
248 	}
249 
RepopulateDirectory(const VfsPath & path)250 	virtual Status RepopulateDirectory(const VfsPath& path)
251 	{
252 		ScopedLock s;
253 
254 		VfsDirectory* directory;
255 		RETURN_STATUS_IF_ERR(vfs_Lookup(path, &m_rootDirectory, directory, 0));
256 		directory->RequestRepopulate();
257 
258 		return INFO::OK;
259 	}
260 
Clear()261 	virtual void Clear()
262 	{
263 		ScopedLock s;
264 		m_rootDirectory.Clear();
265 	}
266 
267 private:
FindRealPathR(const OsPath & realPath,const VfsDirectory & directory,const VfsPath & curPath,VfsPath & path)268 	Status FindRealPathR(const OsPath& realPath, const VfsDirectory& directory, const VfsPath& curPath, VfsPath& path)
269 	{
270 		PRealDirectory realDirectory = directory.AssociatedDirectory();
271 		if(realDirectory && realDirectory->Path() == realPath)
272 		{
273 			path = curPath;
274 			return INFO::OK;
275 		}
276 
277 		const VfsDirectory::VfsSubdirectories& subdirectories = directory.Subdirectories();
278 		for(VfsDirectory::VfsSubdirectories::const_iterator it = subdirectories.begin(); it != subdirectories.end(); ++it)
279 		{
280 			const OsPath& subdirectoryName = it->first;
281 			const VfsDirectory& subdirectory = it->second;
282 			Status ret = FindRealPathR(realPath, subdirectory, curPath / subdirectoryName/"", path);
283 			if(ret == INFO::OK)
284 				return INFO::OK;
285 		}
286 
287 		return ERR::PATH_NOT_FOUND;	// NOWARN
288 	}
289 
290 	PITrace m_trace;
291 	mutable VfsDirectory m_rootDirectory;
292 };
293 
294 //-----------------------------------------------------------------------------
295 
CreateVfs()296 PIVFS CreateVfs()
297 {
298 	return PIVFS(new VFS());
299 }
300