1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #if defined(POSIX) || defined(PLAYSTATION3) || defined(PSP2) || defined(__DS__)
24
25 // Re-enable some forbidden symbols to avoid clashes with stat.h and unistd.h.
26 // Also with clock() in sys/time.h in some Mac OS X SDKs.
27 #define FORBIDDEN_SYMBOL_EXCEPTION_time_h
28 #define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
29 #define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
30 #define FORBIDDEN_SYMBOL_EXCEPTION_getenv
31 #define FORBIDDEN_SYMBOL_EXCEPTION_exit //Needed for IRIX's unistd.h
32 #define FORBIDDEN_SYMBOL_EXCEPTION_random
33 #define FORBIDDEN_SYMBOL_EXCEPTION_srandom
34
35 #include "backends/fs/posix/posix-fs.h"
36 #include "backends/fs/posix/posix-iostream.h"
37 #include "common/algorithm.h"
38
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #ifdef MACOSX
42 #include <sys/types.h>
43 #endif
44 #ifdef PSP2
45 #define mkdir sceIoMkdir
46 #endif
47 #include <dirent.h>
48 #include <stdio.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <unistd.h>
52
53 #ifdef __OS2__
54 #define INCL_DOS
55 #include <os2.h>
56 #endif
57
58 #if defined(ANDROID_PLAIN_PORT)
59 #include "backends/platform/android/jni-android.h"
60 #endif
61
exists() const62 bool POSIXFilesystemNode::exists() const {
63 return access(_path.c_str(), F_OK) == 0;
64 }
65
isReadable() const66 bool POSIXFilesystemNode::isReadable() const {
67 return access(_path.c_str(), R_OK) == 0;
68 }
69
isWritable() const70 bool POSIXFilesystemNode::isWritable() const {
71 bool retVal = access(_path.c_str(), W_OK) == 0;
72 #if defined(ANDROID_PLAIN_PORT)
73 if (!retVal) {
74 // Update return value if going through Android's SAF grants the permission
75 retVal = JNI::isDirectoryWritableWithSAF(_path);
76 }
77 #endif // ANDROID_PLAIN_PORT
78 return retVal;
79 }
80
setFlags()81 void POSIXFilesystemNode::setFlags() {
82 struct stat st;
83
84 _isValid = (0 == stat(_path.c_str(), &st));
85 _isDirectory = _isValid ? S_ISDIR(st.st_mode) : false;
86 }
87
POSIXFilesystemNode(const Common::String & p)88 POSIXFilesystemNode::POSIXFilesystemNode(const Common::String &p) {
89 assert(p.size() > 0);
90
91 // Expand "~/" to the value of the HOME env variable
92 if (p.hasPrefix("~/") || p == "~") {
93 const char *home = getenv("HOME");
94 if (home != NULL && strlen(home) < MAXPATHLEN) {
95 _path = home;
96 // Skip over the tilda.
97 if (p.size() > 1)
98 _path += p.c_str() + 1;
99 }
100 } else {
101 _path = p;
102 }
103
104 #ifdef __OS2__
105 // On OS/2, 'X:/' is a root of drive X, so we should not remove that last
106 // slash.
107 if (!(_path.size() == 3 && _path.hasSuffix(":/")))
108 #endif
109 // Normalize the path (that is, remove unneeded slashes etc.)
110 _path = Common::normalizePath(_path, '/');
111 _displayName = Common::lastPathComponent(_path, '/');
112
113 // TODO: should we turn relative paths into absolute ones?
114 // Pro: Ensures the "getParent" works correctly even for relative dirs.
115 // Contra: The user may wish to use (and keep!) relative paths in his
116 // config file, and converting relative to absolute paths may hurt him...
117 //
118 // An alternative approach would be to change getParent() to work correctly
119 // if "_path" is the empty string.
120 #if 0
121 if (!_path.hasPrefix("/")) {
122 char buf[MAXPATHLEN+1];
123 getcwd(buf, MAXPATHLEN);
124 strcat(buf, "/");
125 _path = buf + _path;
126 }
127 #endif
128 // TODO: Should we enforce that the path is absolute at this point?
129 //assert(_path.hasPrefix("/"));
130
131 setFlags();
132 }
133
getChild(const Common::String & n) const134 AbstractFSNode *POSIXFilesystemNode::getChild(const Common::String &n) const {
135 assert(!_path.empty());
136 assert(_isDirectory);
137
138 // Make sure the string contains no slashes
139 assert(!n.contains('/'));
140
141 // We assume here that _path is already normalized (hence don't bother to call
142 // Common::normalizePath on the final path).
143 Common::String newPath(_path);
144 if (_path.lastChar() != '/')
145 newPath += '/';
146 newPath += n;
147
148 return makeNode(newPath);
149 }
150
getChildren(AbstractFSList & myList,ListMode mode,bool hidden) const151 bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const {
152 assert(_isDirectory);
153
154 #ifdef __OS2__
155 if (_path == "/") {
156 // Special case for the root dir: List all DOS drives
157 ULONG ulDrvNum;
158 ULONG ulDrvMap;
159
160 DosQueryCurrentDisk(&ulDrvNum, &ulDrvMap);
161
162 for (int i = 0; i < 26; i++) {
163 if (ulDrvMap & 1) {
164 char drive_root[] = "A:/";
165 drive_root[0] += i;
166
167 POSIXFilesystemNode *entry = new POSIXFilesystemNode();
168 entry->_isDirectory = true;
169 entry->_isValid = true;
170 entry->_path = drive_root;
171 entry->_displayName = "[" + Common::String(drive_root, 2) + "]";
172 myList.push_back(entry);
173 }
174
175 ulDrvMap >>= 1;
176 }
177
178 return true;
179 }
180 #endif
181
182 #if defined(ANDROID_PLAIN_PORT)
183 if (_path == "/") {
184 Common::Array<Common::String> list = JNI::getAllStorageLocations();
185 for (Common::Array<Common::String>::const_iterator it = list.begin(), end = list.end(); it != end; ++it) {
186 POSIXFilesystemNode *entry = new POSIXFilesystemNode();
187
188 entry->_isDirectory = true;
189 entry->_isValid = true;
190 entry->_displayName = *it;
191 ++it;
192 entry->_path = *it;
193 myList.push_back(entry);
194 }
195 return true;
196 }
197 #endif
198
199 DIR *dirp = opendir(_path.c_str());
200 struct dirent *dp;
201
202 if (dirp == NULL)
203 return false;
204
205 // loop over dir entries using readdir
206 while ((dp = readdir(dirp)) != NULL) {
207 // Skip 'invisible' files if necessary
208 if (dp->d_name[0] == '.' && !hidden) {
209 continue;
210 }
211 // Skip '.' and '..' to avoid cycles
212 if ((dp->d_name[0] == '.' && dp->d_name[1] == 0) || (dp->d_name[0] == '.' && dp->d_name[1] == '.')) {
213 continue;
214 }
215
216 // Start with a clone of this node, with the correct path set
217 POSIXFilesystemNode entry(*this);
218 entry._displayName = dp->d_name;
219 if (_path.lastChar() != '/')
220 entry._path += '/';
221 entry._path += entry._displayName;
222
223 #if defined(SYSTEM_NOT_SUPPORTING_D_TYPE)
224 /* TODO: d_type is not part of POSIX, so it might not be supported
225 * on some of our targets. For those systems where it isn't supported,
226 * add this #elif case, which tries to use stat() instead.
227 *
228 * The d_type method is used to avoid costly recurrent stat() calls in big
229 * directories.
230 */
231 entry.setFlags();
232 #else
233 if (dp->d_type == DT_UNKNOWN) {
234 // Fall back to stat()
235 entry.setFlags();
236 } else {
237 entry._isValid = (dp->d_type == DT_DIR) || (dp->d_type == DT_REG) || (dp->d_type == DT_LNK);
238 if (dp->d_type == DT_LNK) {
239 struct stat st;
240 if (stat(entry._path.c_str(), &st) == 0)
241 entry._isDirectory = S_ISDIR(st.st_mode);
242 else
243 entry._isDirectory = false;
244 } else {
245 entry._isDirectory = (dp->d_type == DT_DIR);
246 }
247 }
248 #endif
249
250 // Skip files that are invalid for some reason (e.g. because we couldn't
251 // properly stat them).
252 if (!entry._isValid)
253 continue;
254
255 // Honor the chosen mode
256 if ((mode == Common::FSNode::kListFilesOnly && entry._isDirectory) ||
257 (mode == Common::FSNode::kListDirectoriesOnly && !entry._isDirectory))
258 continue;
259
260 myList.push_back(new POSIXFilesystemNode(entry));
261 }
262 closedir(dirp);
263
264 return true;
265 }
266
getParent() const267 AbstractFSNode *POSIXFilesystemNode::getParent() const {
268 if (_path == "/")
269 return 0; // The filesystem root has no parent
270
271 #ifdef __OS2__
272 if (_path.size() == 3 && _path.hasSuffix(":/"))
273 // This is a root directory of a drive
274 return makeNode("/"); // return a virtual root for a list of drives
275 #endif
276
277 const char *start = _path.c_str();
278 const char *end = start + _path.size();
279
280 // Strip of the last component. We make use of the fact that at this
281 // point, _path is guaranteed to be normalized
282 while (end > start && *(end-1) != '/')
283 end--;
284
285 if (end == start) {
286 // This only happens if we were called with a relative path, for which
287 // there simply is no parent.
288 // TODO: We could also resolve this by assuming that the parent is the
289 // current working directory, and returning a node referring to that.
290 return 0;
291 }
292
293 return makeNode(Common::String(start, end));
294 }
295
createReadStream()296 Common::SeekableReadStream *POSIXFilesystemNode::createReadStream() {
297 return PosixIoStream::makeFromPath(getPath(), false);
298 }
299
createWriteStream()300 Common::SeekableWriteStream *POSIXFilesystemNode::createWriteStream() {
301 return PosixIoStream::makeFromPath(getPath(), true);
302 }
303
createDirectory()304 bool POSIXFilesystemNode::createDirectory() {
305 if (mkdir(_path.c_str(), 0755) == 0)
306 setFlags();
307 #if defined(ANDROID_PLAIN_PORT)
308 else {
309 // TODO eventually android specific stuff should be moved to an Android backend for fs
310 // peterkohaut already has some work on that in his fork (moving the port to more native code)
311 // However, I have not found a way to do this Storage Access Framework stuff natively yet.
312 if (JNI::createDirectoryWithSAF(_path)) {
313 setFlags();
314 }
315 }
316 #endif // ANDROID_PLAIN_PORT
317
318
319 return _isValid && _isDirectory;
320 }
321
322 namespace Posix {
323
assureDirectoryExists(const Common::String & dir,const char * prefix)324 bool assureDirectoryExists(const Common::String &dir, const char *prefix) {
325 struct stat sb;
326
327 // Check whether the prefix exists if one is supplied.
328 if (prefix) {
329 if (stat(prefix, &sb) != 0) {
330 return false;
331 } else if (!S_ISDIR(sb.st_mode)) {
332 return false;
333 }
334 }
335
336 // Obtain absolute path.
337 Common::String path;
338 if (prefix) {
339 path = prefix;
340 path += '/';
341 path += dir;
342 } else {
343 path = dir;
344 }
345
346 path = Common::normalizePath(path, '/');
347
348 const Common::String::iterator end = path.end();
349 Common::String::iterator cur = path.begin();
350 if (*cur == '/')
351 ++cur;
352
353 do {
354 if (cur + 1 != end) {
355 if (*cur != '/') {
356 continue;
357 }
358
359 // It is kind of ugly and against the purpose of Common::String to
360 // insert 0s inside, but this is just for a local string and
361 // simplifies the code a lot.
362 *cur = '\0';
363 }
364
365 if (mkdir(path.c_str(), 0755) != 0) {
366 if (errno == EEXIST) {
367 if (stat(path.c_str(), &sb) != 0) {
368 return false;
369 } else if (!S_ISDIR(sb.st_mode)) {
370 return false;
371 }
372 } else {
373 return false;
374 }
375 }
376
377 *cur = '/';
378 } while (cur++ != end);
379
380 return true;
381 }
382
383 } // End of namespace Posix
384
385 #endif //#if defined(POSIX)
386