1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #include "FileSystem/BsFileSystem.h" |
4 | |
5 | #include "Error/BsException.h" |
6 | #include "FileSystem/BsDataStream.h" |
7 | #include "Debug/BsDebug.h" |
8 | |
9 | #include <dirent.h> |
10 | #include <errno.h> |
11 | #include <sys/stat.h> |
12 | #include <sys/types.h> |
13 | #include <unistd.h> |
14 | |
15 | #include <climits> |
16 | #include <cstring> |
17 | #include <cstdio> |
18 | #include <cstdlib> |
19 | #include <fstream> |
20 | |
21 | #define HANDLE_PATH_ERROR(path__, errno__) \ |
22 | LOGERR(String(__FUNCTION__) + ": " + (path__) + ": " + (strerror(errno__))); |
23 | |
24 | namespace bs |
25 | { |
26 | bool unix_pathExists(const String& path) |
27 | { |
28 | struct stat st_buf; |
29 | if (stat(path.c_str(), &st_buf) == 0) |
30 | return true; |
31 | else |
32 | if (errno == ENOENT) // No such file or directory |
33 | return false; |
34 | else |
35 | { |
36 | HANDLE_PATH_ERROR(path, errno); |
37 | return false; |
38 | } |
39 | } |
40 | |
41 | bool unix_stat(const String& path, struct stat *st_buf) |
42 | { |
43 | if (stat(path.c_str(), st_buf) != 0) |
44 | { |
45 | HANDLE_PATH_ERROR(path, errno); |
46 | return false; |
47 | } |
48 | return true; |
49 | } |
50 | |
51 | bool unix_isFile(const String& path) |
52 | { |
53 | struct stat st_buf; |
54 | if (unix_stat(path, &st_buf)) |
55 | return S_ISREG(st_buf.st_mode); |
56 | |
57 | return false; |
58 | } |
59 | |
60 | bool unix_isDirectory(const String& path) |
61 | { |
62 | struct stat st_buf; |
63 | if (unix_stat(path, &st_buf)) |
64 | return S_ISDIR(st_buf.st_mode); |
65 | |
66 | return false; |
67 | } |
68 | |
69 | bool unix_createDirectory(const String& path) |
70 | { |
71 | if (unix_pathExists(path) && unix_isDirectory(path)) |
72 | return false; |
73 | |
74 | if (mkdir(path.c_str(), 0755)) |
75 | { |
76 | HANDLE_PATH_ERROR(path, errno); |
77 | return false; |
78 | } |
79 | |
80 | return true; |
81 | } |
82 | |
83 | void FileSystem::removeFile(const Path& path) |
84 | { |
85 | String pathStr = path.toString(); |
86 | if (unix_isDirectory(pathStr)) |
87 | { |
88 | if (rmdir(pathStr.c_str())) |
89 | HANDLE_PATH_ERROR(pathStr, errno); |
90 | } |
91 | else |
92 | { |
93 | if (unlink(pathStr.c_str())) |
94 | HANDLE_PATH_ERROR(pathStr, errno); |
95 | } |
96 | } |
97 | |
98 | void FileSystem::copyFile(const Path& source, const Path& destination) |
99 | { |
100 | std::ifstream sourceStream(source.toString().c_str(), std::ios::binary); |
101 | std::ofstream destinationStream(destination.toString().c_str(), std::ios::binary); |
102 | |
103 | destinationStream << sourceStream.rdbuf(); |
104 | sourceStream.close(); |
105 | destinationStream.close(); |
106 | } |
107 | |
108 | void FileSystem::moveFile(const Path& oldPath, const Path& newPath) |
109 | { |
110 | String oldPathStr = oldPath.toString(); |
111 | String newPathStr = newPath.toString(); |
112 | if (std::rename(oldPathStr.c_str(), newPathStr.c_str()) == -1) |
113 | { |
114 | // Cross-filesystem copy is likely needed (for example, /tmp to Banshee install dir while copying assets) |
115 | std::ifstream src(oldPathStr.c_str(), std::ios::binary); |
116 | std::ofstream dst(newPathStr.c_str(), std::ios::binary); |
117 | dst << src.rdbuf(); // First, copy |
118 | |
119 | // Error handling |
120 | src.close(); |
121 | if (!src) |
122 | { |
123 | LOGERR(String(__FUNCTION__) + ": renaming " + oldPathStr + " to " + newPathStr + |
124 | ": " + strerror(errno)); |
125 | return; // Do not remove source if we failed! |
126 | } |
127 | |
128 | // Then, remove source file (hopefully succeeds) |
129 | if (std::remove(oldPathStr.c_str()) == -1) |
130 | { |
131 | LOGERR(String(__FUNCTION__) + ": renaming " + oldPathStr + " to " + newPathStr + |
132 | ": " + strerror(errno)); |
133 | } |
134 | } |
135 | } |
136 | |
137 | SPtr<DataStream> FileSystem::openFile(const Path& path, bool readOnly) |
138 | { |
139 | String pathString = path.toString(); |
140 | |
141 | DataStream::AccessMode accessMode = DataStream::READ; |
142 | if (!readOnly) |
143 | accessMode = (DataStream::AccessMode)((UINT32)accessMode | (UINT32)DataStream::WRITE); |
144 | |
145 | return bs_shared_ptr_new<FileDataStream>(path, accessMode, true); |
146 | } |
147 | |
148 | SPtr<DataStream> FileSystem::createAndOpenFile(const Path& path) |
149 | { |
150 | return bs_shared_ptr_new<FileDataStream>(path, DataStream::AccessMode::WRITE, true); |
151 | } |
152 | |
153 | UINT64 FileSystem::getFileSize(const Path& path) |
154 | { |
155 | struct stat st_buf; |
156 | |
157 | if (stat(path.toString().c_str(), &st_buf) == 0) |
158 | { |
159 | return (UINT64)st_buf.st_size; |
160 | } |
161 | else |
162 | { |
163 | HANDLE_PATH_ERROR(path.toString(), errno); |
164 | return (UINT64)-1; |
165 | } |
166 | } |
167 | |
168 | bool FileSystem::exists(const Path& path) |
169 | { |
170 | return unix_pathExists(path.toString()); |
171 | } |
172 | |
173 | bool FileSystem::isFile(const Path& path) |
174 | { |
175 | String pathStr = path.toString(); |
176 | return unix_pathExists(pathStr) && unix_isFile(pathStr); |
177 | } |
178 | |
179 | bool FileSystem::isDirectory(const Path& path) |
180 | { |
181 | String pathStr = path.toString(); |
182 | return unix_pathExists(pathStr) && unix_isDirectory(pathStr); |
183 | } |
184 | |
185 | void FileSystem::createDir(const Path& path) |
186 | { |
187 | Path parentPath = path; |
188 | while (!exists(parentPath) && parentPath.getNumDirectories() > 0) |
189 | { |
190 | parentPath = parentPath.getParent(); |
191 | } |
192 | |
193 | for (UINT32 i = parentPath.getNumDirectories(); i < path.getNumDirectories(); i++) |
194 | { |
195 | parentPath.append(path[i]); |
196 | unix_createDirectory(parentPath.toString()); |
197 | } |
198 | |
199 | // Last "file" entry is also considered a directory |
200 | if(!parentPath.equals(path)) |
201 | unix_createDirectory(path.toString()); |
202 | } |
203 | |
204 | void FileSystem::getChildren(const Path& dirPath, Vector<Path>& files, Vector<Path>& directories) |
205 | { |
206 | const String pathStr = dirPath.toString(); |
207 | |
208 | if (unix_isFile(pathStr)) |
209 | return; |
210 | |
211 | DIR *dp = opendir(pathStr.c_str()); |
212 | if (dp == NULL) |
213 | { |
214 | HANDLE_PATH_ERROR(pathStr, errno); |
215 | return; |
216 | } |
217 | |
218 | struct dirent *ep; |
219 | while ( (ep = readdir(dp)) ) |
220 | { |
221 | const String filename(ep->d_name); |
222 | if (filename != "." && filename != ".." ) |
223 | { |
224 | if (unix_isDirectory(pathStr + "/" + filename)) |
225 | directories.push_back(dirPath + (filename + "/" )); |
226 | else |
227 | files.push_back(dirPath + filename); |
228 | } |
229 | } |
230 | closedir(dp); |
231 | } |
232 | |
233 | std::time_t FileSystem::getLastModifiedTime(const Path& path) |
234 | { |
235 | struct stat st_buf; |
236 | stat(path.toString().c_str(), &st_buf); |
237 | std::time_t time = st_buf.st_mtime; |
238 | |
239 | return time; |
240 | } |
241 | |
242 | Path FileSystem::getWorkingDirectoryPath() |
243 | { |
244 | char *buffer = bs_newN<char>(PATH_MAX); |
245 | |
246 | String wd; |
247 | if (getcwd(buffer, PATH_MAX) != nullptr) |
248 | wd = buffer; |
249 | else |
250 | LOGERR(String("Error when calling getcwd(): " ) + strerror(errno)); |
251 | |
252 | bs_free(buffer); |
253 | return Path(wd); |
254 | } |
255 | |
256 | bool FileSystem::iterate(const Path& dirPath, std::function<bool(const Path&)> fileCallback, |
257 | std::function<bool(const Path&)> dirCallback, bool recursive) |
258 | { |
259 | String pathStr = dirPath.toString(); |
260 | |
261 | if (unix_isFile(pathStr)) |
262 | return false; |
263 | |
264 | DIR* dirHandle = opendir(pathStr.c_str()); |
265 | if (dirHandle == nullptr) |
266 | { |
267 | HANDLE_PATH_ERROR(pathStr, errno); |
268 | return false; |
269 | } |
270 | |
271 | dirent* entry; |
272 | while((entry = readdir(dirHandle))) |
273 | { |
274 | String filename(entry->d_name); |
275 | if (filename == "." || filename == ".." ) |
276 | continue; |
277 | |
278 | Path fullPath = dirPath; |
279 | if (unix_isDirectory(pathStr + "/" + filename)) |
280 | { |
281 | Path childDir = fullPath.append(filename + "/" ); |
282 | if (dirCallback != nullptr) |
283 | { |
284 | if (!dirCallback(childDir)) |
285 | { |
286 | closedir(dirHandle); |
287 | return false; |
288 | } |
289 | } |
290 | |
291 | if (recursive) |
292 | { |
293 | if (!iterate(childDir, fileCallback, dirCallback, recursive)) |
294 | { |
295 | closedir(dirHandle); |
296 | return false; |
297 | } |
298 | } |
299 | } |
300 | else |
301 | { |
302 | Path filePath = fullPath.append(filename); |
303 | if (fileCallback != nullptr) |
304 | { |
305 | if (!fileCallback(filePath)) |
306 | { |
307 | closedir(dirHandle); |
308 | return false; |
309 | } |
310 | } |
311 | } |
312 | } |
313 | closedir(dirHandle); |
314 | |
315 | return true; |
316 | } |
317 | |
318 | Path FileSystem::getTempDirectoryPath() |
319 | { |
320 | String tmpdir; |
321 | |
322 | // Try different things: |
323 | // 1) If defined, honor the TMPDIR environnement variable |
324 | char* TMPDIR = getenv("TMPDIR" ); |
325 | if (TMPDIR != nullptr) |
326 | tmpdir = TMPDIR; |
327 | else |
328 | { |
329 | // 2) If defined, honor the P_tmpdir macro |
330 | #ifdef P_tmpdir |
331 | tmpdir = String(P_tmpdir); |
332 | #else |
333 | // 3) If everything else fails, simply default to /tmp |
334 | tmpdir = String("/tmp" ); |
335 | #endif |
336 | } |
337 | |
338 | tmpdir.append("/bsf-XXXXXX" ); |
339 | |
340 | // null terminated, modifiable tmpdir name template |
341 | Vector<char> nameTemplate(tmpdir.c_str(), tmpdir.c_str() + tmpdir.size() + 1); |
342 | char *directoryName = mkdtemp(nameTemplate.data()); |
343 | |
344 | if (directoryName == nullptr) |
345 | { |
346 | LOGERR(String(__FUNCTION__) + ": " + strerror(errno)); |
347 | return Path(StringUtil::BLANK); |
348 | } |
349 | |
350 | return Path(String(directoryName) + "/" ); |
351 | } |
352 | } |
353 | |