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
24namespace 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