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