Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/unix/dir_access_unix.cpp
9903 views
1
/**************************************************************************/
2
/* dir_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 "dir_access_unix.h"
32
33
#if defined(UNIX_ENABLED)
34
35
#include "core/os/memory.h"
36
#include "core/os/os.h"
37
#include "core/string/print_string.h"
38
#include "core/templates/list.h"
39
40
#include <fcntl.h>
41
#include <sys/ioctl.h>
42
#include <sys/stat.h>
43
#ifdef __linux__
44
#include <sys/statfs.h>
45
#endif
46
#include <sys/statvfs.h>
47
#include <cerrno>
48
#include <cstdio>
49
#include <cstdlib>
50
51
#if __has_include(<mntent.h>)
52
#include <mntent.h>
53
#endif
54
55
Error DirAccessUnix::list_dir_begin() {
56
list_dir_end(); //close any previous dir opening!
57
58
//char real_current_dir_name[2048]; //is this enough?!
59
//getcwd(real_current_dir_name,2048);
60
//chdir(current_path.utf8().get_data());
61
dir_stream = opendir(current_dir.utf8().get_data());
62
//chdir(real_current_dir_name);
63
if (!dir_stream) {
64
return ERR_CANT_OPEN; //error!
65
}
66
67
return OK;
68
}
69
70
bool DirAccessUnix::file_exists(String p_file) {
71
GLOBAL_LOCK_FUNCTION
72
73
if (p_file.is_relative_path()) {
74
p_file = current_dir.path_join(p_file);
75
}
76
77
p_file = fix_path(p_file);
78
79
struct stat flags = {};
80
bool success = (stat(p_file.utf8().get_data(), &flags) == 0);
81
82
if (success && S_ISDIR(flags.st_mode)) {
83
success = false;
84
}
85
86
return success;
87
}
88
89
bool DirAccessUnix::dir_exists(String p_dir) {
90
GLOBAL_LOCK_FUNCTION
91
92
if (p_dir.is_relative_path()) {
93
p_dir = get_current_dir().path_join(p_dir);
94
}
95
96
p_dir = fix_path(p_dir);
97
98
struct stat flags = {};
99
bool success = (stat(p_dir.utf8().get_data(), &flags) == 0);
100
101
return (success && S_ISDIR(flags.st_mode));
102
}
103
104
bool DirAccessUnix::is_readable(String p_dir) {
105
GLOBAL_LOCK_FUNCTION
106
107
if (p_dir.is_relative_path()) {
108
p_dir = get_current_dir().path_join(p_dir);
109
}
110
111
p_dir = fix_path(p_dir);
112
return (access(p_dir.utf8().get_data(), R_OK) == 0);
113
}
114
115
bool DirAccessUnix::is_writable(String p_dir) {
116
GLOBAL_LOCK_FUNCTION
117
118
if (p_dir.is_relative_path()) {
119
p_dir = get_current_dir().path_join(p_dir);
120
}
121
122
p_dir = fix_path(p_dir);
123
return (access(p_dir.utf8().get_data(), W_OK) == 0);
124
}
125
126
uint64_t DirAccessUnix::get_modified_time(String p_file) {
127
if (p_file.is_relative_path()) {
128
p_file = current_dir.path_join(p_file);
129
}
130
131
p_file = fix_path(p_file);
132
133
struct stat flags = {};
134
bool success = (stat(p_file.utf8().get_data(), &flags) == 0);
135
136
if (success) {
137
return flags.st_mtime;
138
} else {
139
ERR_FAIL_V(0);
140
}
141
return 0;
142
}
143
144
String DirAccessUnix::get_next() {
145
if (!dir_stream) {
146
return "";
147
}
148
149
dirent *entry = readdir(dir_stream);
150
151
if (entry == nullptr) {
152
list_dir_end();
153
return "";
154
}
155
156
String fname = fix_unicode_name(entry->d_name);
157
158
// Look at d_type to determine if the entry is a directory, unless
159
// its type is unknown (the file system does not support it) or if
160
// the type is a link, in that case we want to resolve the link to
161
// known if it points to a directory. stat() will resolve the link
162
// for us.
163
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
164
String f = current_dir.path_join(fname);
165
166
struct stat flags = {};
167
if (stat(f.utf8().get_data(), &flags) == 0) {
168
_cisdir = S_ISDIR(flags.st_mode);
169
} else {
170
_cisdir = false;
171
}
172
} else {
173
_cisdir = (entry->d_type == DT_DIR);
174
}
175
176
_cishidden = is_hidden(fname);
177
178
return fname;
179
}
180
181
bool DirAccessUnix::current_is_dir() const {
182
return _cisdir;
183
}
184
185
bool DirAccessUnix::current_is_hidden() const {
186
return _cishidden;
187
}
188
189
void DirAccessUnix::list_dir_end() {
190
if (dir_stream) {
191
closedir(dir_stream);
192
}
193
dir_stream = nullptr;
194
_cisdir = false;
195
}
196
197
#if __has_include(<mntent.h>) && defined(LINUXBSD_ENABLED)
198
static bool _filter_drive(struct mntent *mnt) {
199
// Ignore devices that don't point to /dev
200
if (strncmp(mnt->mnt_fsname, "/dev", 4) != 0) {
201
return false;
202
}
203
204
// Accept devices mounted at common locations
205
if (strncmp(mnt->mnt_dir, "/media", 6) == 0 ||
206
strncmp(mnt->mnt_dir, "/mnt", 4) == 0 ||
207
strncmp(mnt->mnt_dir, "/home", 5) == 0 ||
208
strncmp(mnt->mnt_dir, "/run/media", 10) == 0) {
209
return true;
210
}
211
212
// Ignore everything else
213
return false;
214
}
215
#endif
216
217
static void _get_drives(List<String> *list) {
218
// Add root.
219
list->push_back("/");
220
221
#if __has_include(<mntent.h>) && defined(LINUXBSD_ENABLED)
222
// Check /etc/mtab for the list of mounted partitions.
223
FILE *mtab = setmntent("/etc/mtab", "r");
224
if (mtab) {
225
struct mntent mnt;
226
char strings[4096];
227
228
while (getmntent_r(mtab, &mnt, strings, sizeof(strings))) {
229
if (mnt.mnt_dir != nullptr && _filter_drive(&mnt)) {
230
// Avoid duplicates
231
String name = String::utf8(mnt.mnt_dir);
232
if (!list->find(name)) {
233
list->push_back(name);
234
}
235
}
236
}
237
238
endmntent(mtab);
239
}
240
#endif
241
242
// Add $HOME.
243
const char *home = getenv("HOME");
244
if (home) {
245
// Only add if it's not a duplicate
246
String home_name = String::utf8(home);
247
if (!list->find(home_name)) {
248
list->push_back(home_name);
249
}
250
251
// Check GTK+3 bookmarks for both XDG locations (Documents, Downloads, etc.)
252
// and potential user-defined bookmarks.
253
char path[1024];
254
snprintf(path, 1024, "%s/.config/gtk-3.0/bookmarks", home);
255
FILE *fd = fopen(path, "r");
256
if (fd) {
257
char string[1024];
258
while (fgets(string, 1024, fd)) {
259
// Parse only file:// links
260
if (strncmp(string, "file://", 7) == 0) {
261
// Strip any unwanted edges on the strings and push_back if it's not a duplicate.
262
String fpath = String::utf8(string + 7).strip_edges().split_spaces()[0].uri_file_decode();
263
if (!list->find(fpath)) {
264
list->push_back(fpath);
265
}
266
}
267
}
268
269
fclose(fd);
270
}
271
272
// Add Desktop dir.
273
String dpath = OS::get_singleton()->get_system_dir(OS::SystemDir::SYSTEM_DIR_DESKTOP);
274
if (dpath.length() > 0 && !list->find(dpath)) {
275
list->push_back(dpath);
276
}
277
}
278
279
list->sort();
280
}
281
282
int DirAccessUnix::get_drive_count() {
283
List<String> list;
284
_get_drives(&list);
285
286
return list.size();
287
}
288
289
String DirAccessUnix::get_drive(int p_drive) {
290
List<String> list;
291
_get_drives(&list);
292
293
ERR_FAIL_INDEX_V(p_drive, list.size(), "");
294
295
return list.get(p_drive);
296
}
297
298
int DirAccessUnix::get_current_drive() {
299
int drive = 0;
300
int max_length = -1;
301
const String path = get_current_dir().to_lower();
302
for (int i = 0; i < get_drive_count(); i++) {
303
const String d = get_drive(i).to_lower();
304
if (max_length < d.length() && path.begins_with(d)) {
305
max_length = d.length();
306
drive = i;
307
}
308
}
309
return drive;
310
}
311
312
bool DirAccessUnix::drives_are_shortcuts() {
313
return true;
314
}
315
316
Error DirAccessUnix::make_dir(String p_dir) {
317
GLOBAL_LOCK_FUNCTION
318
319
if (p_dir.is_relative_path()) {
320
p_dir = get_current_dir().path_join(p_dir);
321
}
322
323
p_dir = fix_path(p_dir);
324
325
bool success = (mkdir(p_dir.utf8().get_data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0);
326
int err = errno;
327
328
if (success) {
329
return OK;
330
}
331
332
if (err == EEXIST) {
333
return ERR_ALREADY_EXISTS;
334
}
335
336
return ERR_CANT_CREATE;
337
}
338
339
Error DirAccessUnix::change_dir(String p_dir) {
340
GLOBAL_LOCK_FUNCTION
341
342
p_dir = fix_path(p_dir);
343
344
// prev_dir is the directory we are changing out of
345
String prev_dir;
346
char real_current_dir_name[2048];
347
ERR_FAIL_NULL_V(getcwd(real_current_dir_name, 2048), ERR_BUG);
348
if (prev_dir.append_utf8(real_current_dir_name) != OK) {
349
prev_dir = real_current_dir_name; //no utf8, maybe latin?
350
}
351
352
// try_dir is the directory we are trying to change into
353
String try_dir = "";
354
if (p_dir.is_relative_path()) {
355
String next_dir = current_dir.path_join(p_dir);
356
next_dir = next_dir.simplify_path();
357
try_dir = next_dir;
358
} else {
359
try_dir = p_dir;
360
}
361
362
bool worked = (chdir(try_dir.utf8().get_data()) == 0); // we can only give this utf8
363
if (!worked) {
364
return ERR_INVALID_PARAMETER;
365
}
366
367
String base = _get_root_path();
368
if (!base.is_empty() && !try_dir.begins_with(base)) {
369
ERR_FAIL_NULL_V(getcwd(real_current_dir_name, 2048), ERR_BUG);
370
String new_dir;
371
new_dir.append_utf8(real_current_dir_name);
372
373
if (!new_dir.begins_with(base)) {
374
try_dir = current_dir; //revert
375
}
376
}
377
378
// the directory exists, so set current_dir to try_dir
379
current_dir = try_dir;
380
ERR_FAIL_COND_V(chdir(prev_dir.utf8().get_data()) != 0, ERR_BUG);
381
return OK;
382
}
383
384
String DirAccessUnix::get_current_dir(bool p_include_drive) const {
385
String base = _get_root_path();
386
if (!base.is_empty()) {
387
String bd = current_dir.replace_first(base, "");
388
if (bd.begins_with("/")) {
389
return _get_root_string() + bd.substr(1);
390
} else {
391
return _get_root_string() + bd;
392
}
393
}
394
return current_dir;
395
}
396
397
Error DirAccessUnix::rename(String p_path, String p_new_path) {
398
if (p_path.is_relative_path()) {
399
p_path = get_current_dir().path_join(p_path);
400
}
401
402
p_path = fix_path(p_path);
403
if (p_path.ends_with("/")) {
404
p_path = p_path.left(-1);
405
}
406
407
if (p_new_path.is_relative_path()) {
408
p_new_path = get_current_dir().path_join(p_new_path);
409
}
410
411
p_new_path = fix_path(p_new_path);
412
if (p_new_path.ends_with("/")) {
413
p_new_path = p_new_path.left(-1);
414
}
415
416
int res = ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data());
417
if (res != 0 && errno == EXDEV) { // Cross-device move, use copy and remove.
418
Error err = OK;
419
err = copy(p_path, p_new_path);
420
if (err != OK) {
421
return err;
422
}
423
return remove(p_path);
424
} else {
425
return (res == 0) ? OK : FAILED;
426
}
427
}
428
429
Error DirAccessUnix::remove(String p_path) {
430
if (p_path.is_relative_path()) {
431
p_path = get_current_dir().path_join(p_path);
432
}
433
434
p_path = fix_path(p_path);
435
if (p_path.ends_with("/")) {
436
p_path = p_path.left(-1);
437
}
438
439
struct stat flags = {};
440
if (lstat(p_path.utf8().get_data(), &flags) != 0) {
441
return FAILED;
442
}
443
444
int err;
445
if (S_ISDIR(flags.st_mode) && !is_link(p_path)) {
446
err = ::rmdir(p_path.utf8().get_data());
447
} else {
448
err = ::unlink(p_path.utf8().get_data());
449
}
450
if (err != 0) {
451
return FAILED;
452
}
453
if (remove_notification_func != nullptr) {
454
remove_notification_func(p_path);
455
}
456
return OK;
457
}
458
459
bool DirAccessUnix::is_link(String p_file) {
460
if (p_file.is_relative_path()) {
461
p_file = get_current_dir().path_join(p_file);
462
}
463
464
p_file = fix_path(p_file);
465
if (p_file.ends_with("/")) {
466
p_file = p_file.left(-1);
467
}
468
469
struct stat flags = {};
470
if (lstat(p_file.utf8().get_data(), &flags) != 0) {
471
return false;
472
}
473
474
return S_ISLNK(flags.st_mode);
475
}
476
477
String DirAccessUnix::read_link(String p_file) {
478
if (p_file.is_relative_path()) {
479
p_file = get_current_dir().path_join(p_file);
480
}
481
482
p_file = fix_path(p_file);
483
if (p_file.ends_with("/")) {
484
p_file = p_file.left(-1);
485
}
486
487
char buf[PATH_MAX];
488
memset(buf, 0, PATH_MAX);
489
ssize_t len = readlink(p_file.utf8().get_data(), buf, sizeof(buf));
490
String link;
491
if (len > 0) {
492
link.append_utf8(buf, len);
493
}
494
return link;
495
}
496
497
Error DirAccessUnix::create_link(String p_source, String p_target) {
498
if (p_target.is_relative_path()) {
499
p_target = get_current_dir().path_join(p_target);
500
}
501
502
p_source = fix_path(p_source);
503
p_target = fix_path(p_target);
504
505
if (symlink(p_source.utf8().get_data(), p_target.utf8().get_data()) == 0) {
506
return OK;
507
} else {
508
return FAILED;
509
}
510
}
511
512
uint64_t DirAccessUnix::get_space_left() {
513
struct statvfs vfs;
514
if (statvfs(current_dir.utf8().get_data(), &vfs) != 0) {
515
return 0;
516
}
517
518
return (uint64_t)vfs.f_bavail * (uint64_t)vfs.f_frsize;
519
}
520
521
String DirAccessUnix::get_filesystem_type() const {
522
#ifdef __linux__
523
struct statfs fs;
524
if (statfs(current_dir.utf8().get_data(), &fs) != 0) {
525
return "";
526
}
527
switch (static_cast<unsigned int>(fs.f_type)) {
528
case 0x0000adf5:
529
return "ADFS";
530
case 0x0000adff:
531
return "AFFS";
532
case 0x5346414f:
533
return "AFS";
534
case 0x00000187:
535
return "AUTOFS";
536
case 0x00c36400:
537
return "CEPH";
538
case 0x73757245:
539
return "CODA";
540
case 0x28cd3d45:
541
return "CRAMFS";
542
case 0x453dcd28:
543
return "CRAMFS";
544
case 0x64626720:
545
return "DEBUGFS";
546
case 0x73636673:
547
return "SECURITYFS";
548
case 0xf97cff8c:
549
return "SELINUX";
550
case 0x43415d53:
551
return "SMACK";
552
case 0x858458f6:
553
return "RAMFS";
554
case 0x01021994:
555
return "TMPFS";
556
case 0x958458f6:
557
return "HUGETLBFS";
558
case 0x73717368:
559
return "SQUASHFS";
560
case 0x0000f15f:
561
return "ECRYPTFS";
562
case 0x00414a53:
563
return "EFS";
564
case 0xe0f5e1e2:
565
return "EROFS";
566
case 0x0000ef53:
567
return "EXTFS";
568
case 0xabba1974:
569
return "XENFS";
570
case 0x9123683e:
571
return "BTRFS";
572
case 0x00003434:
573
return "NILFS";
574
case 0xf2f52010:
575
return "F2FS";
576
case 0xf995e849:
577
return "HPFS";
578
case 0x00009660:
579
return "ISOFS";
580
case 0x000072b6:
581
return "JFFS2";
582
case 0x58465342:
583
return "XFS";
584
case 0x6165676c:
585
return "PSTOREFS";
586
case 0xde5e81e4:
587
return "EFIVARFS";
588
case 0x00c0ffee:
589
return "HOSTFS";
590
case 0x794c7630:
591
return "OVERLAYFS";
592
case 0x65735546:
593
return "FUSE";
594
case 0xca451a4e:
595
return "BCACHEFS";
596
case 0x00004d44:
597
return "FAT32";
598
case 0x2011bab0:
599
return "EXFAT";
600
case 0x0000564c:
601
return "NCP";
602
case 0x00006969:
603
return "NFS";
604
case 0x7461636f:
605
return "OCFS2";
606
case 0x00009fa1:
607
return "OPENPROM";
608
case 0x0000002f:
609
return "QNX4";
610
case 0x68191122:
611
return "QNX6";
612
case 0x6b414653:
613
return "AFS";
614
case 0x52654973:
615
return "REISERFS";
616
case 0x0000517b:
617
return "SMB";
618
case 0xff534d42:
619
return "CIFS";
620
case 0x0027e0eb:
621
return "CGROUP";
622
case 0x63677270:
623
return "CGROUP2";
624
case 0x07655821:
625
return "RDTGROUP";
626
case 0x74726163:
627
return "TRACEFS";
628
case 0x01021997:
629
return "V9FS";
630
case 0x62646576:
631
return "BDEVFS";
632
case 0x64646178:
633
return "DAXFS";
634
case 0x42494e4d:
635
return "BINFMTFS";
636
case 0x00001cd1:
637
return "DEVPTS";
638
case 0x6c6f6f70:
639
return "BINDERFS";
640
case 0x0bad1dea:
641
return "FUTEXFS";
642
case 0x50495045:
643
return "PIPEFS";
644
case 0x00009fa0:
645
return "PROC";
646
case 0x534f434b:
647
return "SOCKFS";
648
case 0x62656572:
649
return "SYSFS";
650
case 0x00009fa2:
651
return "USBDEVICE";
652
case 0x11307854:
653
return "MTD_INODE";
654
case 0x09041934:
655
return "ANON_INODE";
656
case 0x73727279:
657
return "BTRFS";
658
case 0x6e736673:
659
return "NSFS";
660
case 0xcafe4a11:
661
return "BPF_FS";
662
case 0x5a3c69f0:
663
return "AAFS";
664
case 0x5a4f4653:
665
return "ZONEFS";
666
case 0x15013346:
667
return "UDF";
668
case 0x444d4142:
669
return "DMA_BUF";
670
case 0x454d444d:
671
return "DEVMEM";
672
case 0x5345434d:
673
return "SECRETMEM";
674
case 0x50494446:
675
return "PID_FS";
676
default:
677
return "";
678
}
679
#else
680
return ""; //TODO this should be implemented
681
#endif
682
}
683
684
bool DirAccessUnix::is_hidden(const String &p_name) {
685
return p_name != "." && p_name != ".." && p_name.begins_with(".");
686
}
687
688
bool DirAccessUnix::is_case_sensitive(const String &p_path) const {
689
#if defined(LINUXBSD_ENABLED)
690
String f = p_path;
691
if (!f.is_absolute_path()) {
692
f = get_current_dir().path_join(f);
693
}
694
f = fix_path(f);
695
696
int fd = ::open(f.utf8().get_data(), O_RDONLY | O_NONBLOCK);
697
if (fd) {
698
long flags = 0;
699
if (ioctl(fd, _IOR('f', 1, long), &flags) >= 0) {
700
::close(fd);
701
return !(flags & 0x40000000 /* FS_CASEFOLD_FL */);
702
}
703
::close(fd);
704
}
705
#endif
706
return true;
707
}
708
709
bool DirAccessUnix::is_equivalent(const String &p_path_a, const String &p_path_b) const {
710
String f1 = fix_path(p_path_a);
711
struct stat st1 = {};
712
int err = stat(f1.utf8().get_data(), &st1);
713
if (err) {
714
return DirAccess::is_equivalent(p_path_a, p_path_b);
715
}
716
717
String f2 = fix_path(p_path_b);
718
struct stat st2 = {};
719
err = stat(f2.utf8().get_data(), &st2);
720
if (err) {
721
return DirAccess::is_equivalent(p_path_a, p_path_b);
722
}
723
724
return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino);
725
}
726
727
DirAccessUnix::DirAccessUnix() {
728
dir_stream = nullptr;
729
_cisdir = false;
730
731
/* determine drive count */
732
733
// set current directory to an absolute path of the current directory
734
char real_current_dir_name[2048];
735
ERR_FAIL_NULL(getcwd(real_current_dir_name, 2048));
736
current_dir.clear();
737
if (current_dir.append_utf8(real_current_dir_name) != OK) {
738
current_dir = real_current_dir_name;
739
}
740
741
change_dir(current_dir);
742
}
743
744
DirAccessUnix::RemoveNotificationFunc DirAccessUnix::remove_notification_func = nullptr;
745
746
DirAccessUnix::~DirAccessUnix() {
747
list_dir_end();
748
}
749
750
#endif // UNIX_ENABLED
751
752