GCC Code Coverage Report


Directory: ./
File: lib/geogram/basic/file_system.cpp
Date: 2026-09-07 02:37:58
Exec Total Coverage
Lines: 117 518 22.6%
Functions: 24 82 29.3%
Branches: 87 664 13.1%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/basic/file_system.h>
41 #include <geogram/basic/line_stream.h>
42 #include <geogram/basic/logger.h>
43 #include <geogram/basic/string.h>
44
45 #include <iostream>
46 #include <fstream>
47 #include <assert.h>
48
49 #include <sys/stat.h>
50
51 #ifdef GEO_OS_WINDOWS
52 #include <windows.h>
53 #include <io.h>
54 #include <shlobj.h>
55 constexpr char SEPARATOR = '\\';
56 #else
57 #include <sys/types.h>
58 #include <sys/stat.h>
59 #include <fcntl.h>
60 #include <dirent.h>
61 #include <unistd.h>
62 #include <stdio.h>
63 constexpr char SEPARATOR = '/';
64 #endif
65
66 #ifdef GEO_OS_EMSCRIPTEN
67 #include <emscripten.h>
68 #endif
69
70 namespace {
71 using namespace GEO;
72
73 /******************* Windows file system implementation **********************/
74 #ifdef GEO_OS_WINDOWS
75
76 class FileSystemRootNode : public FileSystem::Node {
77 public:
78 bool is_file(const std::string& path) override {
79 WIN32_FIND_DATAA file;
80 HANDLE file_handle = FindFirstFileA(path.c_str(), &file);
81 if(file_handle == INVALID_HANDLE_VALUE) {
82 return false;
83 }
84 FindClose(file_handle);
85 return (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
86 }
87
88 bool is_directory(const std::string& path) override {
89 WIN32_FIND_DATAA file;
90 HANDLE file_handle = FindFirstFileA(path.c_str(), &file);
91 if(file_handle == INVALID_HANDLE_VALUE) {
92 return false;
93 }
94 FindClose(file_handle);
95 return (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
96 }
97
98 bool create_directory(const std::string& path_in) override {
99 std::vector<std::string> path;
100 String::split_string(path_in, '/', path);
101 std::string current;
102 int start_at = 0;
103 if(path_in.at(1) == ':') {
104 current += path_in.at(0);
105 current += path_in.at(1);
106 start_at = 1;
107 }
108 else if(path_in.at(0) != '/' && path_in.at(0) != '\\') {
109 current += get_current_working_directory();
110 }
111 for(size_t i = size_t(start_at); i < path.size(); i++) {
112 current += "/";
113 current += path[i];
114 if(path[i].at(0) == '.' &&
115 path[i].at(1) == '.' &&
116 path[i].length() == 2
117 ) {
118 continue;
119 }
120 if(!is_directory(current)) {
121 if(!::CreateDirectoryA(current.c_str(), nullptr)) {
122 Logger::err("OS")
123 << "Could not create directory "
124 << current << std::endl;
125 return false;
126 }
127 }
128 }
129 return true;
130 }
131
132 bool delete_directory(const std::string& path) override {
133 return ::RemoveDirectoryA(path.c_str()) != FALSE;
134 }
135
136 bool delete_file(const std::string& path) override {
137 return ::DeleteFileA(path.c_str()) != FALSE;
138 }
139
140 bool get_directory_entries(
141 const std::string& path, std::vector<std::string>& result
142 ) override {
143 std::string dirname = path;
144 if(dirname.at(dirname.size() - 1) != '/' &&
145 dirname.at(dirname.size() - 1) != '\\'
146 ) {
147 dirname += '/';
148 }
149
150 std::string current_directory = get_current_working_directory();
151
152 bool dir_found = set_current_working_directory(dirname);
153 if(!dir_found) {
154 return false;
155 }
156
157 WIN32_FIND_DATAA file;
158 HANDLE file_handle = FindFirstFileA("*.*", &file);
159 if(file_handle != INVALID_HANDLE_VALUE) {
160 do {
161 std::string file_name = file.cFileName;
162 if(file_name != "." && file_name != "..") {
163 file_name = dirname + file_name;
164 flip_slashes(file_name);
165 result.push_back(file_name);
166 }
167 } while(FindNextFileA(file_handle, &file));
168 FindClose(file_handle);
169 }
170 set_current_working_directory(current_directory);
171 return true;
172 }
173
174 std::string get_current_working_directory() override {
175 char buf[2048];
176 std::string result = "";
177 if(GetCurrentDirectoryA(sizeof(buf), buf)) {
178 result = buf;
179 flip_slashes(result);
180 }
181 return result;
182 }
183
184 bool set_current_working_directory(
185 const std::string& path_in
186 ) override {
187 std::string path = path_in;
188 if(
189 path.at(path.size() - 1) != '/' &&
190 path.at(path.size() - 1) != '\\') {
191 path += "/";
192 }
193 return SetCurrentDirectoryA(path.c_str()) != -1;
194 }
195
196 bool rename_file(
197 const std::string& old_name, const std::string& new_name
198 ) override {
199 return ::rename(old_name.c_str(), new_name.c_str()) != -1;
200 }
201
202 Numeric::uint64 get_time_stamp(const std::string& path) override {
203 WIN32_FILE_ATTRIBUTE_DATA infos;
204 if(!GetFileAttributesExA(
205 path.c_str(), GetFileExInfoStandard, &infos)
206 ) {
207 return 0;
208 }
209 return infos.ftLastWriteTime.dwLowDateTime;
210 }
211
212 bool set_executable_flag(const std::string& filename) override {
213 geo_argused(filename);
214 return false;
215 }
216
217 bool touch(const std::string& filename) override {
218 HANDLE hfile = CreateFileA(
219 filename.c_str(),
220 GENERIC_READ | GENERIC_WRITE,
221 FILE_SHARE_READ | FILE_SHARE_WRITE,
222 nullptr,
223 OPEN_EXISTING,
224 FILE_ATTRIBUTE_NORMAL,
225 nullptr
226 );
227 if(hfile == INVALID_HANDLE_VALUE) {
228 Logger::err("FileSystem")
229 << "Could not touch file:"
230 << filename
231 << std::endl;
232 return false;
233 }
234 SYSTEMTIME now_system;
235 FILETIME now_file;
236 GetSystemTime(&now_system);
237 SystemTimeToFileTime(&now_system, &now_file);
238 SetFileTime(hfile,nullptr,&now_file,&now_file);
239 CloseHandle(hfile);
240 return true;
241 }
242
243 std::string normalized_path(const std::string& path_in) override {
244 if(path_in == "") {
245 return "";
246 }
247 std::string path = path_in;
248 std::string result;
249 char buffer[MAX_PATH];
250 GetFullPathNameA(path.c_str(), MAX_PATH, buffer, nullptr);
251 result = std::string(buffer);
252 flip_slashes(result);
253 return result;
254 }
255
256
257 std::string home_directory() override {
258 std::string home;
259 wchar_t folder[MAX_PATH+1];
260 HRESULT hr = SHGetFolderPathW(0, CSIDL_PROFILE, 0, 0, folder);
261 if (SUCCEEDED(hr)) {
262 char result[MAX_PATH+1];
263 wcstombs(result, folder, MAX_PATH);
264 home=std::string(result);
265 flip_slashes(home);
266 }
267 return home;
268 }
269
270 std::string documents_directory() override {
271 std::string home;
272 wchar_t folder[MAX_PATH+1];
273 HRESULT hr = SHGetFolderPathW(0, CSIDL_MYDOCUMENTS, 0, 0, folder);
274 if (SUCCEEDED(hr)) {
275 char result[MAX_PATH+1];
276 wcstombs(result, folder, MAX_PATH);
277 home=std::string(result);
278 flip_slashes(home);
279 }
280 return home;
281 }
282 };
283
284 #else
285
286 /***** Unix/Mac/Android/Emscripten file system implementation ****************/
287
288 class FileSystemRootNode : public FileSystem::Node {
289 public:
290 251 bool is_file(const std::string& path) override {
291 // We use 'stat' and not 'lstat' since
292 // we want to be able to follow symbolic
293 // links (required for instance when testing
294 // input path in GOMGEN, there can be some
295 // symlinks in system includes)
296 struct stat buff;
297
1/2
✓ Branch 2 taken 251 times.
✗ Branch 3 not taken.
251 if(stat(path.c_str(), &buff)) {
298 251 return false;
299 }
300 return S_ISREG(buff.st_mode);
301 }
302
303 1057 bool is_directory(const std::string& path) override {
304 // We use 'stat' and not 'lstat' since
305 // we want to be able to follow symbolic
306 // links (required for instance when testing
307 // input path in GOMGEN, there can be some
308 // symlinks in system includes)
309 struct stat buff;
310
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 1057 times.
1057 if(stat(path.c_str(), &buff)) {
311 return false;
312 }
313 1057 return S_ISDIR(buff.st_mode);
314 }
315
316 151 bool create_directory(const std::string& path_in) override {
317 151 std::vector<std::string> path;
318
1/2
✓ Branch 1 taken 151 times.
✗ Branch 2 not taken.
151 String::split_string(path_in, '/', path);
319 151 std::string current;
320
2/2
✓ Branch 1 taken 1057 times.
✓ Branch 2 taken 151 times.
1208 for(size_t i = 0; i < path.size(); i++) {
321
1/2
✓ Branch 1 taken 1057 times.
✗ Branch 2 not taken.
1057 current += "/";
322
1/2
✓ Branch 2 taken 1057 times.
✗ Branch 3 not taken.
1057 current += path[i];
323
2/4
✓ Branch 1 taken 1057 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1057 times.
1057 if(!is_directory(current)) {
324 if(mkdir(current.c_str(), 0755) != 0) {
325 Logger::err("OS")
326 << "Could not create directory "
327 << current << std::endl;
328 return false;
329 }
330 }
331 }
332 151 return true;
333 151 }
334
335 bool delete_directory(const std::string& path) override {
336 return rmdir(path.c_str()) == 0;
337 }
338
339 bool delete_file(const std::string& path) override {
340 return unlink(path.c_str()) == 0;
341 }
342
343 bool get_directory_entries(
344 const std::string& path, std::vector<std::string>& result
345 ) override {
346 std::string dirname = path;
347 if(dirname[dirname.length() - 1] != '/') {
348 dirname += "/";
349 }
350 DIR* dir = opendir(dirname.c_str());
351 if(dir == nullptr) {
352 Logger::err("OS")
353 << "Could not open directory " << dirname
354 << std::endl;
355 #ifdef GEO_OS_ANDROID
356 static bool first_time = true;
357 if(first_time) {
358 Logger::err("OS")
359 << "You may need to grant the \"Storage\" permission"
360 << std::endl
361 << "to this application"
362 << std::endl;
363 Logger::err("OS")
364 << "(see Parameters/Applications"
365 << std::endl
366 << "in Android perferences)"
367 << std::endl;
368 first_time = false;
369 }
370 #endif
371 return false;
372 }
373 struct dirent* entry = readdir(dir);
374 while(entry != nullptr) {
375 std::string current = std::string(entry->d_name);
376 // Ignore . and ..
377 if(current != "." && current != "..") {
378 if(dirname != "./") {
379 current = dirname + current;
380 }
381 // Ignore symbolic links and other special Unix stuff
382 if(is_file(current) || is_directory(current)) {
383 result.push_back(current);
384 }
385 }
386 entry = readdir(dir);
387 }
388 closedir(dir);
389 return true;
390 }
391
392 151 std::string get_current_working_directory() override {
393 char buff[4096];
394
1/2
✓ Branch 2 taken 151 times.
✗ Branch 3 not taken.
302 return std::string(getcwd(buff, 4096));
395 }
396
397 bool set_current_working_directory(const std::string& path) override {
398 return chdir(path.c_str()) == 0;
399 }
400
401 bool rename_file(
402 const std::string& old_name, const std::string& new_name
403 ) override {
404 if(is_file(new_name)) {
405 return false;
406 }
407 return ::rename(old_name.c_str(), new_name.c_str()) == 0;
408 }
409
410 Numeric::uint64 get_time_stamp(const std::string& path) override {
411 struct stat buffer;
412 if(!stat(path.c_str(), &buffer)) {
413 return Numeric::uint64(buffer.st_mtime);
414 }
415 return 0;
416 }
417
418 bool set_executable_flag(const std::string& filename) override {
419 geo_argused(filename);
420 if(::chmod(filename.c_str(), 0755) != 0) {
421 Logger::err("FileSyst")
422 << "Could not change file permissions for:"
423 << filename << std::endl;
424 return false;
425 }
426 return true;
427 }
428
429 bool touch(const std::string& filename) override {
430 #ifdef GEO_OS_APPLE
431 {
432 struct stat buff;
433 int rc = stat(filename.c_str(), &buff);
434 if(rc != 0) { // FABIEN NOT SURE WE GET THE TOUCH
435 Logger::err("FileSystem")
436 << "Could not touch file:"
437 << filename
438 << std::endl;
439 return false;
440 }
441 return true;
442 }
443 #else
444 {
445 int rc = utimensat(
446 AT_FDCWD,
447 filename.c_str(),
448 nullptr,
449 0
450 );
451 if(rc != 0) {
452 Logger::err("FileSystem")
453 << "Could not touch file:"
454 << filename
455 << std::endl;
456 return false;
457 }
458 return true;
459 }
460 #endif
461 }
462
463 302 std::string normalized_path(const std::string& path_in) override {
464
465
2/4
✓ Branch 1 taken 302 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 302 times.
302 if(path_in == "") {
466 return "";
467 }
468
469
1/2
✓ Branch 1 taken 302 times.
✗ Branch 2 not taken.
302 std::string path = path_in;
470 302 std::string result;
471
472 // If this is a relative path, prepend "./"
473
2/4
✓ Branch 1 taken 302 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 302 times.
302 if(path[0] != '/') {
474 path = "./" + path;
475 }
476
477 char buffer[PATH_MAX];
478 302 char* p = realpath(path.c_str(), buffer);
479
2/2
✓ Branch 0 taken 172 times.
✓ Branch 1 taken 130 times.
302 if(p != nullptr) {
480
1/2
✓ Branch 1 taken 172 times.
✗ Branch 2 not taken.
344 result = std::string(p);
481 } else {
482 // realpath() only works for existing paths and existing file,
483 // therefore we attempt calling it on the input path by adding
484 // one component at a time.
485 130 size_t pos = 1;
486
2/2
✓ Branch 0 taken 1044 times.
✓ Branch 1 taken 130 times.
1304 while(pos != std::string::npos) {
487 1044 pos = path.find('/',pos);
488
2/2
✓ Branch 0 taken 914 times.
✓ Branch 1 taken 130 times.
1044 if(pos != std::string::npos) {
489
1/2
✓ Branch 1 taken 914 times.
✗ Branch 2 not taken.
914 std::string path_part = path.substr(0,pos);
490 914 p = realpath(path_part.c_str(), buffer);
491
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 914 times.
914 if(p == nullptr) {
492 break;
493 } else {
494
1/2
✓ Branch 1 taken 914 times.
✗ Branch 2 not taken.
1828 result = std::string(p) +
495
2/4
✓ Branch 2 taken 914 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 914 times.
✗ Branch 6 not taken.
2742 path.substr(pos, path.length()-pos);
496 }
497 914 ++pos;
498
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 914 times.
914 if(pos == path.length()) {
499 break;
500 }
501
1/2
✓ Branch 1 taken 914 times.
✗ Branch 2 not taken.
914 }
502 }
503 }
504
1/2
✓ Branch 1 taken 302 times.
✗ Branch 2 not taken.
302 flip_slashes(result);
505 302 return result;
506 302 }
507
508
509 502 std::string home_directory() override {
510 502 std::string home;
511 #if defined GEO_OS_EMSCRIPTEN
512 home="/";
513 #else
514 502 char* result = getenv("HOME");
515
1/2
✓ Branch 0 taken 502 times.
✗ Branch 1 not taken.
502 if(result != nullptr) {
516
1/2
✓ Branch 1 taken 502 times.
✗ Branch 2 not taken.
502 home=result;
517 }
518 #endif
519 502 return home;
520 }
521
522 std::string documents_directory() override {
523 std::string home;
524 #if defined GEO_OS_EMSCRIPTEN
525 home="/";
526 #elif defined GEO_OS_ANDROID
527 char* result = getenv("EXTERNAL_STORAGE");
528 if(result != nullptr) {
529 home=result;
530 }
531 #else
532 char* result = getenv("HOME");
533 if(result != nullptr) {
534 home=result;
535 }
536 #endif
537 return home;
538 }
539
540 };
541 #endif
542
543 FileSystem::Node_var root_;
544 }
545
546
547 namespace GEO {
548
549 namespace FileSystem {
550
551 251 Node::Node() {
552 251 }
553
554 502 Node::~Node() {
555 502 }
556
557 /******* OS-independent functions *************************************/
558
559 1043 std::string Node::extension(const std::string& path) {
560 1043 size_t len = path.length();
561
1/2
✓ Branch 0 taken 1043 times.
✗ Branch 1 not taken.
1043 if(len != 0) {
562
1/2
✓ Branch 0 taken 4979 times.
✗ Branch 1 not taken.
4979 for(size_t i = len - 1; i != 0; i--) {
563
3/6
✓ Branch 1 taken 4979 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4979 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 4979 times.
4979 if(path[i] == '/' || path[i] == '\\') {
564 break;
565 }
566
2/2
✓ Branch 1 taken 1043 times.
✓ Branch 2 taken 3936 times.
4979 if(path[i] == '.') {
567
2/4
✓ Branch 1 taken 1043 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1043 times.
✗ Branch 5 not taken.
1043 return String::to_lowercase(path.substr(i + 1));
568 }
569 }
570 }
571 return std::string();
572 }
573
574 508 std::string Node::base_name(
575 const std::string& path, bool remove_extension
576 ) {
577 508 long int len = (long int)(path.length());
578
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 508 times.
508 if(len == 0) {
579 return std::string();
580 }
581 508 long int dot_pos = len;
582 long int i;
583
2/2
✓ Branch 0 taken 6176 times.
✓ Branch 1 taken 4 times.
6180 for(i = len - 1; i >= 0; i--) {
584
5/6
✓ Branch 1 taken 5672 times.
✓ Branch 2 taken 504 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5672 times.
✓ Branch 6 taken 504 times.
✓ Branch 7 taken 5672 times.
6176 if(path[size_t(i)] == '/' || path[size_t(i)] == '\\') {
585 504 break;
586 }
587
5/6
✓ Branch 0 taken 5672 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 5668 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 5668 times.
5672 if(remove_extension && path[size_t(i)] == '.') {
588 4 dot_pos = i;
589 }
590 }
591 508 return path.substr(size_t(i + 1), size_t(dot_pos - i - 1));
592 }
593
594 155 std::string Node::dir_name(const std::string& path) {
595 155 size_t len = path.length();
596
1/2
✓ Branch 0 taken 155 times.
✗ Branch 1 not taken.
155 if(len != 0) {
597
2/2
✓ Branch 0 taken 1641 times.
✓ Branch 1 taken 4 times.
1645 for(size_t i = len - 1; i != 0; i--) {
598
5/6
✓ Branch 1 taken 1490 times.
✓ Branch 2 taken 151 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1490 times.
✓ Branch 6 taken 151 times.
✓ Branch 7 taken 1490 times.
1641 if(path[i] == '/' || path[i] == '\\') {
599 151 return path.substr(0, i);
600 }
601 }
602 }
603
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 return ".";
604 }
605
606 void Node::get_directory_entries_recursive(
607 const std::string& path,
608 std::vector<std::string>& result, bool recursive
609 ) {
610 // TODO: seems to be bugged, enters infinite recursion...
611 get_directory_entries(path, result);
612 if(recursive) {
613 for(size_t i = 0; i < result.size(); i++) {
614 if(is_directory(result[i])) {
615 get_directory_entries_recursive(result[i], result, true);
616 }
617 }
618 }
619 }
620
621 void Node::get_files(
622 const std::string& path,
623 std::vector<std::string>& result, bool recursive
624 ) {
625 std::vector<std::string> entries;
626 get_directory_entries_recursive(path, entries, recursive);
627 for(size_t i = 0; i < entries.size(); i++) {
628 if(is_file(entries[i])) {
629 result.push_back(entries[i]);
630 }
631 }
632 }
633
634 void Node::get_subdirectories_recursive(
635 const std::string& path,
636 std::vector<std::string>& result, bool recursive
637 ) {
638 std::vector<std::string> entries;
639 get_directory_entries_recursive(path, entries, recursive);
640 for(size_t i = 0; i < entries.size(); i++) {
641 if(is_directory(entries[i])) {
642 result.push_back(entries[i]);
643 }
644 }
645 }
646
647 302 void Node::flip_slashes(std::string& s) {
648
2/2
✓ Branch 1 taken 19291 times.
✓ Branch 2 taken 302 times.
19593 for(size_t i = 0; i < s.length(); i++) {
649
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 19291 times.
19291 if(s[i] == '\\') {
650 s[i] = '/';
651 }
652 }
653 302 }
654
655 bool Node::copy_file(const std::string& from, const std::string& to) {
656 FILE* fromf = fopen(from.c_str(), "rb");
657 if(fromf == nullptr) {
658 Logger::err("FileSyst")
659 << "Could not open source file:" << from << std::endl;
660 return false;
661 }
662 FILE* tof = fopen(to.c_str(),"wb");
663 if(tof == nullptr) {
664 Logger::err("FileSyst")
665 << "Could not create file:" << to << std::endl;
666 fclose(fromf);
667 return false;
668 }
669
670 bool result = true;
671 const size_t buff_size = 4096;
672 char buff[buff_size];
673 size_t rdsize = 0;
674 do {
675 rdsize = fread(buff, 1, buff_size, fromf);
676 if(fwrite(buff, 1, rdsize, tof) != rdsize) {
677 Logger::err("FileSyst") << "I/O error when writing to file:"
678 << to << std::endl;
679 result = false;
680 break;
681 }
682 } while(rdsize == 4096);
683
684 fclose(fromf);
685 fclose(tof);
686 return result;
687 }
688
689 /*************** OS-dependent functions *******************************/
690
691 bool Node::is_file(const std::string& path) {
692 geo_argused(path);
693 return false;
694 }
695
696 bool Node::is_directory(const std::string& path) {
697 geo_argused(path);
698 return false;
699 }
700
701 bool Node::create_directory(const std::string& path) {
702 geo_argused(path);
703 return false;
704 }
705
706 bool Node::delete_directory(const std::string& path) {
707 geo_argused(path);
708 return false;
709 }
710
711 bool Node::delete_file(const std::string& path) {
712 geo_argused(path);
713 return false;
714 }
715
716 bool Node::get_directory_entries(
717 const std::string& path, std::vector<std::string>& result
718 ) {
719 geo_argused(path);
720 geo_argused(result);
721 return false;
722 }
723
724 std::string Node::get_current_working_directory() {
725 return "/";
726 }
727
728 bool Node::set_current_working_directory(const std::string& path) {
729 geo_argused(path);
730 return false;
731 }
732
733 bool Node::rename_file(
734 const std::string& old_name, const std::string& new_name
735 ) {
736 geo_argused(old_name);
737 geo_argused(new_name);
738 return false;
739 }
740
741 Numeric::uint64 Node::get_time_stamp(const std::string& path) {
742 geo_argused(path);
743 return 0;
744 }
745
746 bool Node::set_executable_flag(const std::string& filename) {
747 geo_argused(filename);
748 return false;
749 }
750
751 bool Node::touch(const std::string& filename) {
752 geo_argused(filename);
753 return false;
754 }
755
756 std::string Node::normalized_path(const std::string& path) {
757 std::vector<std::string> components;
758 String::split_string(path, '/', components);
759 std::vector<std::string> new_components;
760 for(auto c: components) {
761 if(c == ".") {
762 } else if(c == "..") {
763 if(new_components.size() != 0) {
764 new_components.pop_back();
765 }
766 } else {
767 new_components.push_back(c);
768 }
769 }
770 std::string result;
771 for(auto c: new_components) {
772 result += "/";
773 result += c;
774 }
775 return result;
776 }
777
778 std::string Node::home_directory() {
779 return "/";
780 }
781
782 std::string Node::documents_directory() {
783 return "/";
784 }
785
786 std::string Node::load_file_as_string(const std::string& path) {
787 std::string result;
788 FILE* f = fopen(path.c_str(),"rb");
789 if(f != nullptr) {
790 // Get file length
791 fseek(f, 0L, SEEK_END);
792 size_t length = size_t(ftell(f));
793 fseek(f, 0L, SEEK_SET);
794 if(length != 0) {
795 result.resize(length);
796 size_t read_length = fread(&result[0], 1, length, f);
797 if(read_length != length) {
798 Logger::warn("FileSystem")
799 << "Problem occured when reading "
800 << path
801 << std::endl;
802
803 }
804 }
805 fclose(f);
806 }
807 return result;
808 }
809
810 /*********************************************************************/
811
812 bool MemoryNode::copy_file(
813 const std::string& from, const std::string& to
814 ) {
815 const char* contents = get_file_contents(from);
816 if(contents == nullptr) {
817 return false;
818 }
819 return create_file(to, contents);
820 }
821
822 std::string MemoryNode::load_file_as_string(const std::string& path) {
823 std::string result;
824 std::string subdir;
825 std::string rest;
826 split_path(path, subdir, rest);
827 if(subdir == "") {
828 auto it = files_.find(rest);
829 if(it != files_.end()) {
830 result = std::string(it->second);
831 }
832 } else {
833 auto it = subnodes_.find(subdir);
834 if(it != subnodes_.end()) {
835 result = it->second->load_file_as_string(rest);
836 }
837 }
838 return result;
839 }
840
841 bool MemoryNode::is_file(const std::string& path) {
842 std::string result;
843 std::string subdir;
844 std::string rest;
845 split_path(path, subdir, rest);
846 if(subdir == "") {
847 return(files_.find(rest) != files_.end());
848 } else {
849 auto it = subnodes_.find(subdir);
850 if(it == subnodes_.end()) {
851 return false;
852 }
853 return it->second->is_file(rest);
854 }
855 }
856
857 bool MemoryNode::is_directory(const std::string& path) {
858 std::string result;
859 std::string subdir;
860 std::string rest;
861 split_path(path, subdir, rest);
862 if(subdir == "") {
863 return(subnodes_.find(rest) != subnodes_.end());
864 } else {
865 auto it = subnodes_.find(subdir);
866 if(it == subnodes_.end()) {
867 return false;
868 }
869 return it->second->is_directory(rest);
870 }
871 }
872
873 bool MemoryNode::create_directory(const std::string& path) {
874 std::string result;
875 std::string subdir;
876 std::string rest;
877 split_path(path, subdir, rest);
878 if(subdir == "") {
879 if(subnodes_.find(path) != subnodes_.end()) {
880 return false;
881 }
882 subnodes_[path] = new MemoryNode(path_ + path + "/");
883 return true;
884 } else {
885 auto it = subnodes_.find(subdir);
886 if(it == subnodes_.end()) {
887 subnodes_[subdir] = new MemoryNode(path_ + subdir + "/");
888 }
889 return it->second->create_directory(rest);
890 }
891 }
892
893 bool MemoryNode::delete_directory(const std::string& path) {
894 std::string result;
895 std::string subdir;
896 std::string rest;
897 split_path(path, subdir, rest);
898 if(subdir == "") {
899 auto it = subnodes_.find(rest);
900 if(it == subnodes_.end()) {
901 return false;
902 }
903 subnodes_.erase(it);
904 return true;
905 } else {
906 auto it = subnodes_.find(subdir);
907 if(it == subnodes_.end()) {
908 return false;
909 }
910 return it->second->delete_directory(rest);
911 }
912 }
913
914 bool MemoryNode::delete_file(const std::string& path) {
915 std::string result;
916 std::string subdir;
917 std::string rest;
918 split_path(path, subdir, rest);
919 if(subdir == "") {
920 auto it = files_.find(rest);
921 if(it == files_.end()) {
922 return false;
923 }
924 files_.erase(it);
925 return true;
926 } else {
927 auto it = subnodes_.find(subdir);
928 if(it == subnodes_.end()) {
929 return false;
930 }
931 return it->second->delete_file(rest);
932 }
933 }
934
935 bool MemoryNode::get_directory_entries(
936 const std::string& path, std::vector<std::string>& result
937 ) {
938 std::string subdir;
939 std::string rest;
940 split_path(path, subdir, rest);
941 if(subdir == "" && rest == "") {
942 result.clear();
943 for(auto it : subnodes_) {
944 result.push_back(path_ + it.first);
945 }
946 for(auto it : files_) {
947 result.push_back(path_ + it.first);
948 }
949 return true;
950 } else {
951 if(subdir == "") {
952 subdir = rest;
953 rest = "";
954 }
955 auto it = subnodes_.find(subdir);
956 if(it == subnodes_.end()) {
957 return false;
958 }
959 return it->second->get_directory_entries(rest, result);
960 }
961 }
962
963 bool MemoryNode::rename_file(
964 const std::string& from, const std::string& to
965 ) {
966 const char* contents = get_file_contents(from);
967 if(contents == nullptr) {
968 return false;
969 }
970 if(!delete_file(from)) {
971 return false;
972 }
973 return create_file(to, contents);
974 }
975
976 const char* MemoryNode::get_file_contents(const std::string& path) {
977 std::string subdir;
978 std::string rest;
979 split_path(path, subdir, rest);
980 const char* result = nullptr;
981 if(subdir == "") {
982 auto it = files_.find(rest);
983 if(it != files_.end()) {
984 result = it->second;
985 }
986 } else {
987 auto it = subnodes_.find(subdir);
988 if(it != subnodes_.end()) {
989 result = it->second->get_file_contents(rest);
990 }
991 }
992 return result;
993 }
994
995 bool MemoryNode::create_file(
996 const std::string& path, const char* content
997 ) {
998 std::string subdir;
999 std::string rest;
1000 split_path(path, subdir, rest);
1001 if(subdir == "") {
1002 if(files_.find(rest) != files_.end()) {
1003 return false;
1004 }
1005 files_[rest] = content;
1006 return true;
1007 } else {
1008 SmartPointer<MemoryNode>& n = subnodes_[subdir];
1009 if(n.is_null()) {
1010 n = new MemoryNode(path_ + subdir + "/");
1011 }
1012 return n->create_file(rest, content);
1013 }
1014 }
1015
1016 void MemoryNode::split_path(
1017 const std::string& path, std::string& leadingsubdir,
1018 std::string& rest
1019 ) {
1020 leadingsubdir = "";
1021 rest = "";
1022 std::vector<std::string> components;
1023 String::split_string(path, '/', components);
1024 if(components.size() == 0) {
1025 return;
1026 } else if(components.size() == 1) {
1027 leadingsubdir = "";
1028 rest = components[0];
1029 } else {
1030 leadingsubdir = components[0];
1031 for(size_t i=1; i<components.size(); ++i) {
1032 if(i != 1) {
1033 rest += "/";
1034 }
1035 rest += components[i];
1036 }
1037 }
1038 }
1039
1040 /*********************************************************************/
1041
1042 251 void initialize() {
1043
3/8
✓ Branch 2 taken 251 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 251 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 251 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
251 root_ = new FileSystemRootNode;
1044 251 }
1045
1046 251 void terminate() {
1047 251 root_.reset();
1048 251 }
1049
1050 251 bool is_file(const std::string& path) {
1051 251 return root_->is_file(path);
1052 }
1053
1054 bool is_directory(const std::string& path) {
1055 return root_->is_directory(path);
1056 }
1057
1058
1059 bool can_read_directory(const std::string& path) {
1060 const std::string abs_path = absolute_path(path);
1061 #ifdef GEO_OS_WINDOWS
1062 return is_directory(abs_path); // TODO: check permissions
1063 #else
1064 struct stat stats;
1065 if( ::stat(abs_path.c_str(), &stats) == 0 ) {
1066 return (stats.st_mode & S_IRUSR) != 0;
1067 }
1068 return false;
1069 #endif
1070 }
1071
1072 151 bool can_write_directory(
1073 const std::string& path, bool create_missing_directories
1074 ) {
1075
1/2
✓ Branch 1 taken 151 times.
✗ Branch 2 not taken.
151 std::string abs_path = absolute_path(path);
1076
1/2
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
151 if( create_missing_directories ) {
1077
2/4
✓ Branch 1 taken 151 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 151 times.
151 if( !FileSystem::create_directory(abs_path) ) {
1078 return false;
1079 }
1080 }
1081 #ifdef GEO_OS_WINDOWS
1082 return is_directory(abs_path); // TODO: check permissions
1083 #else
1084 struct stat stats;
1085
1/2
✓ Branch 2 taken 151 times.
✗ Branch 3 not taken.
151 if( ::stat(abs_path.c_str(), &stats) == 0 ) {
1086 151 return (stats.st_mode & S_IWUSR) != 0;
1087 }
1088 return false;
1089 #endif
1090 151 }
1091
1092 151 bool create_directory(const std::string& path) {
1093 151 return root_->create_directory(path);
1094 }
1095
1096 bool delete_directory(const std::string& path) {
1097 return root_->delete_directory(path);
1098 }
1099
1100 bool delete_file(const std::string& path) {
1101 return root_->delete_file(path);
1102 }
1103
1104 bool get_directory_entries(
1105 const std::string& path, std::vector<std::string>& result
1106 ) {
1107 return root_->get_directory_entries(path, result);
1108 }
1109
1110 151 std::string get_current_working_directory() {
1111 151 return root_->get_current_working_directory();
1112 }
1113
1114 bool set_current_working_directory(
1115 const std::string& path
1116 ) {
1117 return root_->set_current_working_directory(path);
1118 }
1119
1120 bool rename_file(
1121 const std::string& old_name, const std::string& new_name
1122 ) {
1123 return root_->rename_file(old_name, new_name);
1124 }
1125
1126 Numeric::uint64 get_time_stamp(const std::string& path) {
1127 return root_->get_time_stamp(path);
1128 }
1129
1130 1043 std::string extension(const std::string& path) {
1131 1043 return root_->extension(path);
1132 }
1133
1134 508 std::string base_name(
1135 const std::string& path, bool remove_extension
1136 ) {
1137 508 return root_->base_name(path, remove_extension);
1138 }
1139
1140 155 std::string dir_name(const std::string& path) {
1141 155 return root_->dir_name(path);
1142 }
1143
1144 void get_directory_entries_recursive(
1145 const std::string& path,
1146 std::vector<std::string>& result, bool recursive
1147 ) {
1148 return root_->get_directory_entries_recursive(
1149 path, result, recursive
1150 );
1151 }
1152
1153 void get_files(
1154 const std::string& path,
1155 std::vector<std::string>& result, bool recursive
1156 ) {
1157 return root_->get_files(path, result, recursive);
1158 }
1159
1160 void get_subdirectories(
1161 const std::string& path,
1162 std::vector<std::string>& result, bool recursive
1163 ) {
1164 return root_->get_subdirectories_recursive(path, result, recursive);
1165 }
1166
1167 void flip_slashes(std::string& path) {
1168 return root_->flip_slashes(path);
1169 }
1170
1171 bool copy_file(
1172 const std::string& from, const std::string& to
1173 ) {
1174 return root_->copy_file(from, to);
1175 }
1176
1177 bool set_executable_flag(const std::string& filename) {
1178 return root_->set_executable_flag(filename);
1179 }
1180
1181 bool touch(const std::string& filename) {
1182 return root_->touch(filename);
1183 }
1184
1185 302 std::string normalized_path(const std::string& path) {
1186 302 return root_->normalized_path(path);
1187 }
1188
1189 302 std::string absolute_path(const std::string& path) {
1190
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 302 times.
302 if( path.empty() ) {
1191 return path;
1192 }
1193
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 298 times.
302 if( path[0] == '.' ) {
1194 return FileSystem::normalized_path(
1195
3/6
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
8 FileSystem::get_current_working_directory() + '/' + path
1196
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 );
1197 }
1198
2/2
✓ Branch 1 taken 151 times.
✓ Branch 2 taken 147 times.
298 if( path[0] == '/' ) {
1199 151 return FileSystem::normalized_path(path);
1200 }
1201 #ifdef GEO_OS_WINDOWS
1202 if(
1203 path.size() > 2 && path[1] == ':' &&
1204 (path[2] == '\\' || path[2] == '/')
1205 ) {
1206 return FileSystem::normalized_path(path);
1207 }
1208 #endif
1209 // relative path
1210 return FileSystem::normalized_path(
1211
3/6
✓ Branch 1 taken 147 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 147 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 147 times.
✗ Branch 8 not taken.
294 FileSystem::get_current_working_directory() + SEPARATOR + path
1212
1/2
✓ Branch 1 taken 147 times.
✗ Branch 2 not taken.
147 );
1213 }
1214
1215 502 std::string home_directory() {
1216 502 return root_->home_directory();
1217 }
1218
1219 std::string documents_directory() {
1220 return root_->documents_directory();
1221 }
1222
1223 void get_root(Node*& root) {
1224 root = root_;
1225 }
1226 } // end namespace FileSystem
1227 } // end namespace GEO
1228
1229
1230
1231 #ifdef GEO_OS_EMSCRIPTEN
1232
1233 namespace GEO {
1234 namespace FileSystem {
1235 static void (*file_system_changed_callback_)() = nullptr;
1236 void set_file_system_changed_callback(void(*callback)()) {
1237 file_system_changed_callback_ = callback;
1238 }
1239 }
1240 }
1241
1242 extern "C" {
1243 void file_system_changed_callback();
1244 }
1245
1246 EMSCRIPTEN_KEEPALIVE void file_system_changed_callback() {
1247 if(GEO::FileSystem::file_system_changed_callback_ != nullptr) {
1248 (*GEO::FileSystem::file_system_changed_callback_)();
1249 }
1250 }
1251
1252 #endif
1253