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