Line data Source code
1 : /* Copyright (C) 2021 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 : /*
24 : * look up directories/files by traversing path components.
25 : */
26 :
27 : #include "precompiled.h"
28 : #include "lib/file/vfs/vfs_lookup.h"
29 :
30 : #include "lib/external_libraries/suppress_boost_warnings.h"
31 :
32 : #include "lib/sysdep/filesystem.h"
33 : #include "lib/file/file.h"
34 : #include "lib/file/vfs/vfs.h" // error codes
35 : #include "lib/file/vfs/vfs_tree.h"
36 : #include "lib/file/vfs/vfs_populate.h"
37 :
38 : #include "lib/timer.h"
39 :
40 :
41 116 : static Status CreateDirectory(const OsPath& path)
42 : {
43 : {
44 116 : const mode_t mode = S_IRWXU; // 0700 as prescribed by XDG basedir
45 116 : const int ret = wmkdir(path, mode);
46 116 : if(ret == 0) // success
47 116 : return INFO::OK;
48 : }
49 :
50 : // Failed because the directory already exists.
51 : // Return 'success' to attach the existing directory.
52 0 : if(errno == EEXIST)
53 : {
54 : // But first ensure it's really a directory
55 : // (otherwise, a file is "in the way" and needs to be deleted).
56 : struct stat s;
57 0 : const int ret = wstat(path, &s);
58 0 : ENSURE(ret == 0); // (wmkdir said it existed)
59 0 : ENSURE(S_ISDIR(s.st_mode));
60 0 : return INFO::OK;
61 : }
62 :
63 0 : if (errno == EACCES)
64 0 : return ERR::FILE_ACCESS;
65 :
66 : // unexpected failure
67 0 : debug_printf("wmkdir failed with errno=%d\n", errno);
68 0 : DEBUG_WARN_ERR(ERR::LOGIC);
69 0 : WARN_RETURN(StatusFromErrno());
70 : }
71 :
72 :
73 7937 : Status vfs_Lookup(const VfsPath& pathname, VfsDirectory* startDirectory, VfsDirectory*& directory, VfsFile** pfile, size_t flags)
74 : {
75 : // extract and validate flags (ensure no unknown bits are set)
76 7937 : const bool addMissingDirectories = (flags & VFS_LOOKUP_ADD) != 0;
77 7937 : const bool skipPopulate = (flags & VFS_LOOKUP_SKIP_POPULATE) != 0;
78 7937 : const bool realPath = (flags & VFS_LOOKUP_REAL_PATH) != 0;
79 7937 : ENSURE((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE|VFS_LOOKUP_REAL_PATH)) == 0);
80 :
81 7937 : directory = startDirectory;
82 7937 : if (pfile)
83 7406 : *pfile = 0;
84 :
85 7937 : if (!skipPopulate)
86 7752 : RETURN_STATUS_IF_ERR(vfs_Populate(directory));
87 :
88 : // early-out for pathname == "" when mounting into VFS root
89 7937 : if (pathname.empty()) // (prevent iterator error in loop end condition)
90 : {
91 : // Preserve a guarantee that if pfile then we either return an error or set *pfile,
92 : // and if looking for a real path ensure an associated directory.
93 91 : if (pfile || (realPath && !directory->AssociatedDirectory()))
94 3 : return ERR::VFS_FILE_NOT_FOUND;
95 : else
96 88 : return INFO::OK;
97 : }
98 :
99 : // for each directory component:
100 7846 : size_t pos = 0; // (needed outside of loop)
101 : for(;;)
102 : {
103 23727 : const size_t nextSlash = pathname.string().find_first_of('/', pos);
104 23727 : if (nextSlash == VfsPath::String::npos)
105 7316 : break;
106 32292 : const VfsPath subdirectoryName = pathname.string().substr(pos, nextSlash-pos);
107 16411 : pos = nextSlash+1;
108 :
109 16411 : VfsDirectory* subdirectory = directory->GetSubdirectory(subdirectoryName);
110 16411 : if (!subdirectory)
111 : {
112 671 : if (addMissingDirectories)
113 141 : subdirectory = directory->AddSubdirectory(subdirectoryName);
114 : else
115 530 : return ERR::VFS_DIR_NOT_FOUND; // NOWARN
116 : }
117 : // When looking for a real path, we need to keep the path of the highest priority subdirectory.
118 : // If the current directory has an associated directory, and the subdir does not / is lower priority,
119 : // we will overwrite it.
120 31762 : PRealDirectory realDir = directory->AssociatedDirectory();
121 16304 : if (realPath && realDir &&
122 504 : (!subdirectory->AssociatedDirectory() ||
123 197 : realDir->Priority() > subdirectory->AssociatedDirectory()->Priority()))
124 : {
125 232 : OsPath currentPath = directory->AssociatedDirectory()->Path();
126 116 : currentPath = currentPath / subdirectoryName / "";
127 :
128 : // Only actually create the directory if we're in LOOKUP_ADD mode.
129 116 : if (addMissingDirectories)
130 116 : RETURN_STATUS_IF_ERR(CreateDirectory(currentPath));
131 0 : else if (!DirectoryExists(currentPath))
132 0 : return ERR::VFS_DIR_NOT_FOUND;
133 :
134 : // Propagate priority and flags to the subdirectory.
135 : // If it already existed, it will be replaced & the memory freed.
136 : PRealDirectory realDirectory(new RealDirectory(currentPath,
137 116 : realDir ? realDir->Priority() : 0,
138 116 : realDir ? realDir->Flags() : 0)
139 232 : );
140 116 : RETURN_STATUS_IF_ERR(vfs_Attach(subdirectory, realDirectory));
141 : }
142 :
143 15881 : if (!skipPopulate)
144 15839 : RETURN_STATUS_IF_ERR(vfs_Populate(subdirectory));
145 :
146 15881 : directory = subdirectory;
147 15881 : }
148 :
149 7423 : if (realPath && !directory->AssociatedDirectory())
150 0 : return ERR::VFS_DIR_NOT_FOUND;
151 :
152 7316 : if (pfile)
153 : {
154 6900 : ENSURE(!pathname.IsDirectory());
155 13010 : const VfsPath filename = pathname.string().substr(pos);
156 6900 : *pfile = directory->GetFile(filename);
157 6900 : if (!*pfile)
158 790 : return ERR::VFS_FILE_NOT_FOUND; // NOWARN
159 : }
160 :
161 6526 : return INFO::OK;
162 3 : }
|