1 /*************************************************************************/
2 /*  dir_access.cpp                                                       */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 
31 #include "dir_access.h"
32 
33 #include "core/os/file_access.h"
34 #include "core/os/memory.h"
35 #include "core/os/os.h"
36 #include "core/project_settings.h"
37 
_get_root_path() const38 String DirAccess::_get_root_path() const {
39 
40 	switch (_access_type) {
41 
42 		case ACCESS_RESOURCES: return ProjectSettings::get_singleton()->get_resource_path();
43 		case ACCESS_USERDATA: return OS::get_singleton()->get_user_data_dir();
44 		default: return "";
45 	}
46 }
_get_root_string() const47 String DirAccess::_get_root_string() const {
48 
49 	switch (_access_type) {
50 
51 		case ACCESS_RESOURCES: return "res://";
52 		case ACCESS_USERDATA: return "user://";
53 		default: return "";
54 	}
55 }
56 
get_current_drive()57 int DirAccess::get_current_drive() {
58 
59 	String path = get_current_dir().to_lower();
60 	for (int i = 0; i < get_drive_count(); i++) {
61 		String d = get_drive(i).to_lower();
62 		if (path.begins_with(d))
63 			return i;
64 	}
65 
66 	return 0;
67 }
68 
drives_are_shortcuts()69 bool DirAccess::drives_are_shortcuts() {
70 
71 	return false;
72 }
73 
get_current_dir_without_drive()74 String DirAccess::get_current_dir_without_drive() {
75 
76 	return get_current_dir();
77 }
78 
_erase_recursive(DirAccess * da)79 static Error _erase_recursive(DirAccess *da) {
80 
81 	List<String> dirs;
82 	List<String> files;
83 
84 	da->list_dir_begin();
85 	String n = da->get_next();
86 	while (n != String()) {
87 
88 		if (n != "." && n != "..") {
89 
90 			if (da->current_is_dir())
91 				dirs.push_back(n);
92 			else
93 				files.push_back(n);
94 		}
95 
96 		n = da->get_next();
97 	}
98 
99 	da->list_dir_end();
100 
101 	for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
102 
103 		Error err = da->change_dir(E->get());
104 		if (err == OK) {
105 
106 			err = _erase_recursive(da);
107 			if (err) {
108 				da->change_dir("..");
109 				return err;
110 			}
111 			err = da->change_dir("..");
112 			if (err) {
113 				return err;
114 			}
115 			err = da->remove(da->get_current_dir().plus_file(E->get()));
116 			if (err) {
117 				return err;
118 			}
119 		} else {
120 			return err;
121 		}
122 	}
123 
124 	for (List<String>::Element *E = files.front(); E; E = E->next()) {
125 
126 		Error err = da->remove(da->get_current_dir().plus_file(E->get()));
127 		if (err) {
128 			return err;
129 		}
130 	}
131 
132 	return OK;
133 }
134 
erase_contents_recursive()135 Error DirAccess::erase_contents_recursive() {
136 
137 	return _erase_recursive(this);
138 }
139 
make_dir_recursive(String p_dir)140 Error DirAccess::make_dir_recursive(String p_dir) {
141 
142 	if (p_dir.length() < 1) {
143 		return OK;
144 	};
145 
146 	String full_dir;
147 
148 	if (p_dir.is_rel_path()) {
149 		//append current
150 		full_dir = get_current_dir().plus_file(p_dir);
151 
152 	} else {
153 		full_dir = p_dir;
154 	}
155 
156 	full_dir = full_dir.replace("\\", "/");
157 
158 	//int slices = full_dir.get_slice_count("/");
159 
160 	String base;
161 
162 	if (full_dir.begins_with("res://"))
163 		base = "res://";
164 	else if (full_dir.begins_with("user://"))
165 		base = "user://";
166 	else if (full_dir.begins_with("/"))
167 		base = "/";
168 	else if (full_dir.find(":/") != -1) {
169 		base = full_dir.substr(0, full_dir.find(":/") + 2);
170 	} else {
171 		ERR_FAIL_V(ERR_INVALID_PARAMETER);
172 	}
173 
174 	full_dir = full_dir.replace_first(base, "").simplify_path();
175 
176 	Vector<String> subdirs = full_dir.split("/");
177 
178 	String curpath = base;
179 	for (int i = 0; i < subdirs.size(); i++) {
180 
181 		curpath = curpath.plus_file(subdirs[i]);
182 		Error err = make_dir(curpath);
183 		if (err != OK && err != ERR_ALREADY_EXISTS) {
184 
185 			ERR_FAIL_V(err);
186 		}
187 	}
188 
189 	return OK;
190 }
191 
fix_path(String p_path) const192 String DirAccess::fix_path(String p_path) const {
193 
194 	switch (_access_type) {
195 
196 		case ACCESS_RESOURCES: {
197 
198 			if (ProjectSettings::get_singleton()) {
199 				if (p_path.begins_with("res://")) {
200 
201 					String resource_path = ProjectSettings::get_singleton()->get_resource_path();
202 					if (resource_path != "") {
203 
204 						return p_path.replace_first("res:/", resource_path);
205 					};
206 					return p_path.replace_first("res://", "");
207 				}
208 			}
209 
210 		} break;
211 		case ACCESS_USERDATA: {
212 
213 			if (p_path.begins_with("user://")) {
214 
215 				String data_dir = OS::get_singleton()->get_user_data_dir();
216 				if (data_dir != "") {
217 
218 					return p_path.replace_first("user:/", data_dir);
219 				};
220 				return p_path.replace_first("user://", "");
221 			}
222 
223 		} break;
224 		case ACCESS_FILESYSTEM: {
225 
226 			return p_path;
227 		} break;
228 		case ACCESS_MAX: break; // Can't happen, but silences warning
229 	}
230 
231 	return p_path;
232 }
233 
234 DirAccess::CreateFunc DirAccess::create_func[ACCESS_MAX] = { 0, 0, 0 };
235 
create_for_path(const String & p_path)236 DirAccess *DirAccess::create_for_path(const String &p_path) {
237 
238 	DirAccess *da = NULL;
239 	if (p_path.begins_with("res://")) {
240 
241 		da = create(ACCESS_RESOURCES);
242 	} else if (p_path.begins_with("user://")) {
243 
244 		da = create(ACCESS_USERDATA);
245 	} else {
246 
247 		da = create(ACCESS_FILESYSTEM);
248 	}
249 
250 	return da;
251 }
252 
open(const String & p_path,Error * r_error)253 DirAccess *DirAccess::open(const String &p_path, Error *r_error) {
254 
255 	DirAccess *da = create_for_path(p_path);
256 
257 	ERR_FAIL_COND_V_MSG(!da, NULL, "Cannot create DirAccess for path '" + p_path + "'.");
258 	Error err = da->change_dir(p_path);
259 	if (r_error)
260 		*r_error = err;
261 	if (err != OK) {
262 		memdelete(da);
263 		return NULL;
264 	}
265 
266 	return da;
267 }
268 
create(AccessType p_access)269 DirAccess *DirAccess::create(AccessType p_access) {
270 
271 	DirAccess *da = create_func[p_access] ? create_func[p_access]() : NULL;
272 	if (da) {
273 		da->_access_type = p_access;
274 	}
275 
276 	return da;
277 };
278 
get_full_path(const String & p_path,AccessType p_access)279 String DirAccess::get_full_path(const String &p_path, AccessType p_access) {
280 
281 	DirAccess *d = DirAccess::create(p_access);
282 	if (!d)
283 		return p_path;
284 
285 	d->change_dir(p_path);
286 	String full = d->get_current_dir();
287 	memdelete(d);
288 	return full;
289 }
290 
copy(String p_from,String p_to,int p_chmod_flags)291 Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) {
292 
293 	//printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
294 	Error err;
295 	FileAccess *fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
296 
297 	if (err) {
298 		ERR_PRINTS("Failed to open " + p_from);
299 		return err;
300 	}
301 
302 	FileAccess *fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
303 	if (err) {
304 
305 		fsrc->close();
306 		memdelete(fsrc);
307 		ERR_PRINTS("Failed to open " + p_to);
308 		return err;
309 	}
310 
311 	fsrc->seek_end(0);
312 	int size = fsrc->get_position();
313 	fsrc->seek(0);
314 	err = OK;
315 	while (size--) {
316 
317 		if (fsrc->get_error() != OK) {
318 			err = fsrc->get_error();
319 			break;
320 		}
321 		if (fdst->get_error() != OK) {
322 			err = fdst->get_error();
323 			break;
324 		}
325 
326 		fdst->store_8(fsrc->get_8());
327 	}
328 
329 	if (err == OK && p_chmod_flags != -1) {
330 		fdst->close();
331 		err = FileAccess::set_unix_permissions(p_to, p_chmod_flags);
332 		// If running on a platform with no chmod support (i.e., Windows), don't fail
333 		if (err == ERR_UNAVAILABLE)
334 			err = OK;
335 	}
336 
337 	memdelete(fsrc);
338 	memdelete(fdst);
339 
340 	return err;
341 }
342 
343 // Changes dir for the current scope, returning back to the original dir
344 // when scope exits
345 class DirChanger {
346 	DirAccess *da;
347 	String original_dir;
348 
349 public:
DirChanger(DirAccess * p_da,String p_dir)350 	DirChanger(DirAccess *p_da, String p_dir) :
351 			da(p_da),
352 			original_dir(p_da->get_current_dir()) {
353 		p_da->change_dir(p_dir);
354 	}
355 
~DirChanger()356 	~DirChanger() {
357 		da->change_dir(original_dir);
358 	}
359 };
360 
_copy_dir(DirAccess * p_target_da,String p_to,int p_chmod_flags)361 Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags) {
362 	List<String> dirs;
363 
364 	String curdir = get_current_dir();
365 	list_dir_begin();
366 	String n = get_next();
367 	while (n != String()) {
368 
369 		if (n != "." && n != "..") {
370 
371 			if (current_is_dir())
372 				dirs.push_back(n);
373 			else {
374 				const String &rel_path = n;
375 				if (!n.is_rel_path()) {
376 					list_dir_end();
377 					return ERR_BUG;
378 				}
379 				Error err = copy(get_current_dir().plus_file(n), p_to + rel_path, p_chmod_flags);
380 				if (err) {
381 					list_dir_end();
382 					return err;
383 				}
384 			}
385 		}
386 
387 		n = get_next();
388 	}
389 
390 	list_dir_end();
391 
392 	for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
393 		String rel_path = E->get();
394 		String target_dir = p_to + rel_path;
395 		if (!p_target_da->dir_exists(target_dir)) {
396 			Error err = p_target_da->make_dir(target_dir);
397 			ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
398 		}
399 
400 		Error err = change_dir(E->get());
401 		ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + E->get() + "'.");
402 
403 		err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags);
404 		if (err) {
405 			change_dir("..");
406 			ERR_FAIL_V_MSG(err, "Failed to copy recursively.");
407 		}
408 		err = change_dir("..");
409 		ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back.");
410 	}
411 
412 	return OK;
413 }
414 
copy_dir(String p_from,String p_to,int p_chmod_flags)415 Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags) {
416 	ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
417 
418 	DirAccess *target_da = DirAccess::create_for_path(p_to);
419 	ERR_FAIL_COND_V_MSG(!target_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
420 
421 	if (!target_da->dir_exists(p_to)) {
422 		Error err = target_da->make_dir_recursive(p_to);
423 		if (err) {
424 			memdelete(target_da);
425 		}
426 		ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
427 	}
428 
429 	if (!p_to.ends_with("/")) {
430 		p_to = p_to + "/";
431 	}
432 
433 	DirChanger dir_changer(this, p_from);
434 	Error err = _copy_dir(target_da, p_to, p_chmod_flags);
435 	memdelete(target_da);
436 
437 	return err;
438 }
439 
exists(String p_dir)440 bool DirAccess::exists(String p_dir) {
441 
442 	DirAccess *da = DirAccess::create_for_path(p_dir);
443 	bool valid = da->change_dir(p_dir) == OK;
444 	memdelete(da);
445 	return valid;
446 }
447 
DirAccess()448 DirAccess::DirAccess() {
449 
450 	_access_type = ACCESS_FILESYSTEM;
451 }
452 
~DirAccess()453 DirAccess::~DirAccess() {
454 }
455