Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/unix/file_access_unix.cpp
9903 views
1
/**************************************************************************/
2
/* file_access_unix.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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 "file_access_unix.h"
32
33
#if defined(UNIX_ENABLED)
34
35
#include "core/os/os.h"
36
#include "core/string/print_string.h"
37
38
#include <fcntl.h>
39
#include <sys/stat.h>
40
#include <sys/types.h>
41
#include <unistd.h>
42
#include <cerrno>
43
44
#if defined(TOOLS_ENABLED)
45
#include <climits>
46
#include <cstdlib>
47
#endif
48
49
void FileAccessUnix::check_errors(bool p_write) const {
50
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
51
52
last_error = OK;
53
if (ferror(f)) {
54
if (p_write) {
55
last_error = ERR_FILE_CANT_WRITE;
56
} else {
57
last_error = ERR_FILE_CANT_READ;
58
}
59
}
60
if (!p_write && feof(f)) {
61
last_error = ERR_FILE_EOF;
62
}
63
}
64
65
Error FileAccessUnix::open_internal(const String &p_path, int p_mode_flags) {
66
_close();
67
68
path_src = p_path;
69
path = fix_path(p_path);
70
//printf("opening %s, %i\n", path.utf8().get_data(), Memory::get_static_mem_usage());
71
72
ERR_FAIL_COND_V_MSG(f, ERR_ALREADY_IN_USE, "File is already in use.");
73
const char *mode_string;
74
75
if (p_mode_flags == READ) {
76
mode_string = "rb";
77
} else if (p_mode_flags == WRITE) {
78
mode_string = "wb";
79
} else if (p_mode_flags == READ_WRITE) {
80
mode_string = "rb+";
81
} else if (p_mode_flags == WRITE_READ) {
82
mode_string = "wb+";
83
} else {
84
return ERR_INVALID_PARAMETER;
85
}
86
87
/* pretty much every implementation that uses fopen as primary
88
backend (unix-compatible mostly) supports utf8 encoding */
89
90
//printf("opening %s as %s\n", p_path.utf8().get_data(), path.utf8().get_data());
91
struct stat st = {};
92
int err = stat(path.utf8().get_data(), &st);
93
if (!err) {
94
switch (st.st_mode & S_IFMT) {
95
case S_IFLNK:
96
case S_IFREG:
97
break;
98
default:
99
return ERR_FILE_CANT_OPEN;
100
}
101
}
102
103
#if defined(TOOLS_ENABLED)
104
if (p_mode_flags & READ) {
105
String real_path = get_real_path();
106
if (real_path != path) {
107
// Don't warn on symlinks, since they can be used to simply share addons on multiple projects.
108
if (real_path.to_lower() == path.to_lower()) {
109
// The File system is case insensitive, but other platforms can be sensitive to it
110
// To ease cross-platform development, we issue a warning if users try to access
111
// a file using the wrong case (which *works* on Windows and macOS, but won't on other
112
// platforms).
113
WARN_PRINT(vformat("Case mismatch opening requested file '%s', stored as '%s' in the filesystem. This file will not open when exported to other case-sensitive platforms.", path, real_path));
114
}
115
}
116
}
117
#endif
118
119
if (is_backup_save_enabled() && (p_mode_flags == WRITE)) {
120
// Set save path to the symlink target, not the link itself.
121
String link;
122
bool is_link = false;
123
{
124
CharString cs = path.utf8();
125
struct stat lst = {};
126
if (lstat(cs.get_data(), &lst) == 0) {
127
is_link = S_ISLNK(lst.st_mode);
128
}
129
if (is_link) {
130
char buf[PATH_MAX];
131
memset(buf, 0, PATH_MAX);
132
ssize_t len = readlink(cs.get_data(), buf, sizeof(buf));
133
if (len > 0) {
134
link.append_utf8(buf, len);
135
}
136
if (!link.is_absolute_path()) {
137
link = path.get_base_dir().path_join(link);
138
}
139
}
140
}
141
save_path = is_link ? link : path;
142
143
// Create a temporary file in the same directory as the target file.
144
path = path + "-XXXXXX";
145
CharString cs = path.utf8();
146
int fd = mkstemp(cs.ptrw());
147
if (fd == -1) {
148
last_error = ERR_FILE_CANT_OPEN;
149
return last_error;
150
}
151
fchmod(fd, 0644);
152
path = String::utf8(cs.ptr());
153
154
f = fdopen(fd, mode_string);
155
if (f == nullptr) {
156
// Delete temp file and close descriptor if open failed.
157
::unlink(cs.ptr());
158
::close(fd);
159
last_error = ERR_FILE_CANT_OPEN;
160
return last_error;
161
}
162
} else {
163
f = fopen(path.utf8().get_data(), mode_string);
164
}
165
166
if (f == nullptr) {
167
switch (errno) {
168
case ENOENT: {
169
last_error = ERR_FILE_NOT_FOUND;
170
} break;
171
default: {
172
last_error = ERR_FILE_CANT_OPEN;
173
} break;
174
}
175
return last_error;
176
}
177
178
// Set close on exec to avoid leaking it to subprocesses.
179
int fd = fileno(f);
180
181
if (fd != -1) {
182
int opts = fcntl(fd, F_GETFD);
183
fcntl(fd, F_SETFD, opts | FD_CLOEXEC);
184
}
185
186
last_error = OK;
187
flags = p_mode_flags;
188
return OK;
189
}
190
191
void FileAccessUnix::_close() {
192
if (!f) {
193
return;
194
}
195
196
fclose(f);
197
f = nullptr;
198
199
if (close_notification_func) {
200
close_notification_func(path, flags);
201
}
202
203
if (!save_path.is_empty()) {
204
int rename_error = rename(path.utf8().get_data(), save_path.utf8().get_data());
205
206
if (rename_error && close_fail_notify) {
207
close_fail_notify(save_path);
208
}
209
210
save_path = "";
211
ERR_FAIL_COND(rename_error != 0);
212
}
213
}
214
215
bool FileAccessUnix::is_open() const {
216
return (f != nullptr);
217
}
218
219
String FileAccessUnix::get_path() const {
220
return path_src;
221
}
222
223
String FileAccessUnix::get_path_absolute() const {
224
return path;
225
}
226
227
#if defined(TOOLS_ENABLED)
228
String FileAccessUnix::get_real_path() const {
229
char *resolved_path = ::realpath(path.utf8().get_data(), nullptr);
230
231
if (!resolved_path) {
232
return path;
233
}
234
235
String result;
236
Error parse_ok = result.append_utf8(resolved_path);
237
::free(resolved_path);
238
239
if (parse_ok != OK) {
240
return path;
241
}
242
243
return result.simplify_path();
244
}
245
#endif
246
247
void FileAccessUnix::seek(uint64_t p_position) {
248
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
249
250
if (fseeko(f, p_position, SEEK_SET)) {
251
check_errors();
252
}
253
}
254
255
void FileAccessUnix::seek_end(int64_t p_position) {
256
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
257
258
if (fseeko(f, p_position, SEEK_END)) {
259
check_errors();
260
}
261
}
262
263
uint64_t FileAccessUnix::get_position() const {
264
ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
265
266
int64_t pos = ftello(f);
267
if (pos < 0) {
268
check_errors();
269
ERR_FAIL_V(0);
270
}
271
return pos;
272
}
273
274
uint64_t FileAccessUnix::get_length() const {
275
ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
276
277
int64_t pos = ftello(f);
278
ERR_FAIL_COND_V(pos < 0, 0);
279
ERR_FAIL_COND_V(fseeko(f, 0, SEEK_END), 0);
280
int64_t size = ftello(f);
281
ERR_FAIL_COND_V(size < 0, 0);
282
ERR_FAIL_COND_V(fseeko(f, pos, SEEK_SET), 0);
283
284
return size;
285
}
286
287
bool FileAccessUnix::eof_reached() const {
288
return feof(f);
289
}
290
291
uint64_t FileAccessUnix::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
292
ERR_FAIL_NULL_V_MSG(f, -1, "File must be opened before use.");
293
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
294
295
uint64_t read = fread(p_dst, 1, p_length, f);
296
check_errors();
297
298
return read;
299
}
300
301
Error FileAccessUnix::get_error() const {
302
return last_error;
303
}
304
305
Error FileAccessUnix::resize(int64_t p_length) {
306
ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use.");
307
int res = ::ftruncate(fileno(f), p_length);
308
switch (res) {
309
case 0:
310
return OK;
311
case EBADF:
312
return ERR_FILE_CANT_OPEN;
313
case EFBIG:
314
return ERR_OUT_OF_MEMORY;
315
case EINVAL:
316
return ERR_INVALID_PARAMETER;
317
default:
318
return FAILED;
319
}
320
}
321
322
void FileAccessUnix::flush() {
323
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
324
fflush(f);
325
}
326
327
bool FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) {
328
ERR_FAIL_NULL_V_MSG(f, false, "File must be opened before use.");
329
ERR_FAIL_COND_V(!p_src && p_length > 0, false);
330
bool res = fwrite(p_src, 1, p_length, f) == p_length;
331
check_errors(true);
332
return res;
333
}
334
335
bool FileAccessUnix::file_exists(const String &p_path) {
336
struct stat st = {};
337
const CharString filename_utf8 = fix_path(p_path).utf8();
338
339
// Does the name exist at all?
340
if (stat(filename_utf8.get_data(), &st)) {
341
return false;
342
}
343
344
// See if we have access to the file
345
if (access(filename_utf8.get_data(), F_OK)) {
346
return false;
347
}
348
349
// See if this is a regular file
350
switch (st.st_mode & S_IFMT) {
351
case S_IFLNK:
352
case S_IFREG:
353
return true;
354
default:
355
return false;
356
}
357
}
358
359
uint64_t FileAccessUnix::_get_modified_time(const String &p_file) {
360
String file = fix_path(p_file);
361
struct stat st = {};
362
int err = stat(file.utf8().get_data(), &st);
363
364
if (!err) {
365
uint64_t modified_time = 0;
366
if ((st.st_mode & S_IFMT) == S_IFLNK || (st.st_mode & S_IFMT) == S_IFREG || (st.st_mode & S_IFDIR) == S_IFDIR) {
367
modified_time = st.st_mtime;
368
}
369
#ifdef ANDROID_ENABLED
370
// Workaround for GH-101007
371
//FIXME: After saving, all timestamps (st_mtime, st_ctime, st_atime) are set to the same value.
372
// After exporting or after some time, only 'modified_time' resets to a past timestamp.
373
uint64_t created_time = st.st_ctime;
374
if (modified_time < created_time) {
375
modified_time = created_time;
376
}
377
#endif
378
return modified_time;
379
} else {
380
return 0;
381
}
382
}
383
384
uint64_t FileAccessUnix::_get_access_time(const String &p_file) {
385
String file = fix_path(p_file);
386
struct stat st = {};
387
int err = stat(file.utf8().get_data(), &st);
388
389
if (!err) {
390
if ((st.st_mode & S_IFMT) == S_IFLNK || (st.st_mode & S_IFMT) == S_IFREG || (st.st_mode & S_IFDIR) == S_IFDIR) {
391
return st.st_atime;
392
}
393
}
394
ERR_FAIL_V_MSG(0, "Failed to get access time for: " + p_file + "");
395
}
396
397
int64_t FileAccessUnix::_get_size(const String &p_file) {
398
String file = fix_path(p_file);
399
struct stat st = {};
400
int err = stat(file.utf8().get_data(), &st);
401
402
if (!err) {
403
if ((st.st_mode & S_IFMT) == S_IFLNK || (st.st_mode & S_IFMT) == S_IFREG) {
404
return st.st_size;
405
}
406
}
407
ERR_FAIL_V_MSG(-1, "Failed to get size for: " + p_file + "");
408
}
409
410
BitField<FileAccess::UnixPermissionFlags> FileAccessUnix::_get_unix_permissions(const String &p_file) {
411
String file = fix_path(p_file);
412
struct stat status = {};
413
int err = stat(file.utf8().get_data(), &status);
414
415
if (!err) {
416
return status.st_mode & 0xFFF; //only permissions
417
} else {
418
ERR_FAIL_V_MSG(0, "Failed to get unix permissions for: " + p_file + ".");
419
}
420
}
421
422
Error FileAccessUnix::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
423
String file = fix_path(p_file);
424
425
int err = chmod(file.utf8().get_data(), p_permissions);
426
if (!err) {
427
return OK;
428
}
429
430
return FAILED;
431
}
432
433
bool FileAccessUnix::_get_hidden_attribute(const String &p_file) {
434
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
435
String file = fix_path(p_file);
436
437
struct stat st = {};
438
int err = stat(file.utf8().get_data(), &st);
439
ERR_FAIL_COND_V_MSG(err, false, "Failed to get attributes for: " + p_file);
440
441
return (st.st_flags & UF_HIDDEN);
442
#else
443
return false;
444
#endif
445
}
446
447
Error FileAccessUnix::_set_hidden_attribute(const String &p_file, bool p_hidden) {
448
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
449
String file = fix_path(p_file);
450
451
struct stat st = {};
452
int err = stat(file.utf8().get_data(), &st);
453
ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to get attributes for: " + p_file);
454
455
if (p_hidden) {
456
err = chflags(file.utf8().get_data(), st.st_flags | UF_HIDDEN);
457
} else {
458
err = chflags(file.utf8().get_data(), st.st_flags & ~UF_HIDDEN);
459
}
460
ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to set attributes for: " + p_file);
461
return OK;
462
#else
463
return ERR_UNAVAILABLE;
464
#endif
465
}
466
467
bool FileAccessUnix::_get_read_only_attribute(const String &p_file) {
468
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
469
String file = fix_path(p_file);
470
471
struct stat st = {};
472
int err = stat(file.utf8().get_data(), &st);
473
ERR_FAIL_COND_V_MSG(err, false, "Failed to get attributes for: " + p_file);
474
475
return st.st_flags & UF_IMMUTABLE;
476
#else
477
return false;
478
#endif
479
}
480
481
Error FileAccessUnix::_set_read_only_attribute(const String &p_file, bool p_ro) {
482
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
483
String file = fix_path(p_file);
484
485
struct stat st = {};
486
int err = stat(file.utf8().get_data(), &st);
487
ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to get attributes for: " + p_file);
488
489
if (p_ro) {
490
err = chflags(file.utf8().get_data(), st.st_flags | UF_IMMUTABLE);
491
} else {
492
err = chflags(file.utf8().get_data(), st.st_flags & ~UF_IMMUTABLE);
493
}
494
ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to set attributes for: " + p_file);
495
return OK;
496
#else
497
return ERR_UNAVAILABLE;
498
#endif
499
}
500
501
void FileAccessUnix::close() {
502
_close();
503
}
504
505
FileAccessUnix::CloseNotificationFunc FileAccessUnix::close_notification_func = nullptr;
506
507
FileAccessUnix::~FileAccessUnix() {
508
_close();
509
}
510
511
#endif // UNIX_ENABLED
512
513