]> git.localhorst.tv Git - blank.git/blob - src/io/filesystem.cpp
move common exceptions to app/error
[blank.git] / src / io / filesystem.cpp
1 #include "filesystem.hpp"
2
3 #include "../app/error.hpp"
4
5 #include <cerrno>
6 #include <cstdio>
7 #include <cstdlib>
8 #include <cstring>
9 #include <iostream>
10 #include <stdexcept>
11 #ifdef _WIN32
12 #  include <conio.h>
13 #  include <direct.h>
14 #  include <windows.h>
15 #else
16 #  include <dirent.h>
17 #  include <sys/types.h>
18 #endif
19 #include <sys/stat.h>
20
21 using namespace std;
22
23
24 namespace blank {
25
26 namespace {
27 #ifdef _WIN32
28         using Stat = struct _stat;
29         int do_stat(const char *path, Stat &info) {
30                 return _stat(path, &info);
31         }
32         bool is_dir(const Stat &info) {
33                 return (info.st_mode & _S_IFDIR) != 0;
34         }
35         bool is_file(const Stat &info) {
36                 return (info.st_mode & _S_IFEG) != 0;
37         }
38 #else
39         using Stat = struct stat;
40         int do_stat(const char *path, Stat &info) {
41                 return stat(path, &info);
42         }
43         bool is_dir(const Stat &info) {
44                 return S_ISDIR(info.st_mode);
45         }
46         bool is_file(const Stat &info) {
47                 return S_ISREG(info.st_mode);
48         }
49 #endif
50         time_t get_mtime(const Stat &info) {
51 #ifdef __APPLE__
52                 return info.st_mtimespec.tv_sec;
53 #else
54         return info.st_mtime;
55 #endif
56         }
57 }
58
59 bool is_dir(const char *path) {
60         Stat info;
61         if (do_stat(path, info) != 0) {
62                 return false;
63         }
64         return is_dir(info);
65 }
66
67 bool is_file(const char *path) {
68         Stat info;
69         if (do_stat(path, info) != 0) {
70                 return false;
71         }
72         return is_file(info);
73 }
74
75 time_t file_mtime(const char *path) {
76         Stat info;
77         if (do_stat(path, info) != 0) {
78                 return 0;
79         }
80         return get_mtime(info);
81 }
82
83
84 bool make_dir(const char *path) {
85 #ifdef _WIN32
86         int ret = _mkdir(path);
87 #else
88         int ret = mkdir(path, 0777);
89 #endif
90         return ret == 0;
91 }
92
93
94 bool make_dirs(const string &path) {
95         if (make_dir(path)) {
96                 return true;
97         }
98
99         switch (errno) {
100
101                 case ENOENT:
102                         // missing component
103                         {
104 #ifdef _WIN32
105                                 auto pos = path.find_last_of("\\/");
106 #else
107                                 auto pos = path.find_last_of('/');
108 #endif
109                                 if (pos == string::npos) {
110                                         return false;
111                                 }
112                                 if (pos == path.length() - 1) {
113                                         // trailing separator, would make final make_dir fail
114 #ifdef _WIN32
115                                          pos = path.find_last_of("\\/", pos - 1);
116 #else
117                                          pos = path.find_last_of('/', pos - 1);
118 #endif
119                                         if (pos == string::npos) {
120                                                 return false;
121                                         }
122                                 }
123                                 if (!make_dirs(path.substr(0, pos))) {
124                                         return false;
125                                 }
126                         }
127                         // try again
128                         return make_dir(path);
129
130                 case EEXIST:
131                         // something's there, check if it's a dir and we're good
132                         return is_dir(path);
133
134                 default:
135                         // whatever else went wrong, it can't be good
136                         return false;
137
138         }
139 }
140
141
142 bool remove_file(const string &path) {
143         return remove(path.c_str()) == 0;
144 }
145
146
147 bool remove_dir(const string &path) {
148 #ifdef _WIN32
149
150         // shamelessly stolen from http://www.codeguru.com/forum/showthread.php?t=239271
151         const string pattern = path + "\\*.*";
152         WIN32_FIND_DATA info;
153         HANDLE file = FindFirstFile(pattern.c_str(), &info);
154         if (file == INVALID_HANDLE_VALUE) {
155                 // already non-existing
156                 return true;
157         }
158
159         do {
160                 if (
161                         strncmp(info.cFileName, ".", 2) == 0 ||
162                         strncmp(info.cFileName, "..", 3) == 0
163                 ) {
164                         continue;
165                 }
166                 const string sub_path = path + '\\' + info.cFileName;
167                 if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
168                         if (!remove_dir(sub_path)) {
169                                 return false;
170                         }
171                 } else {
172                         if (!SetFileAttributes(sub_path.c_str(), FILE_ATTRIBUTE_NORMAL)) {
173                                 return false;
174                         }
175                         if (!remove_file(sub_path)) {
176                                 return false;
177                         }
178                 }
179         } while (FindNextFile(file, &info));
180         FindClose(file);
181
182         DWORD error = GetLastError();
183         if (error != ERROR_NO_MORE_FILES) {
184                 return false;
185         }
186         // is this (NORMAL vs DIRECTORY) really correct?
187         if (!SetFileAttributes(path.c_str(), FILE_ATTRIBUTE_NORMAL)) {
188                 return false;
189         }
190         return RemoveDirectory(path.c_str());
191
192 #else
193
194         DIR *dir = opendir(path.c_str());
195         for (dirent *entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
196                 if (
197                         strncmp(entry->d_name, ".", 2) == 0 ||
198                         strncmp(entry->d_name, "..", 3) == 0
199                 ) {
200                         continue;
201                 }
202                 const string sub_path = path + '/' + entry->d_name;
203                 if (is_dir(sub_path)) {
204                         if (!remove_dir(sub_path)) {
205                                 return false;
206                         }
207                 } else {
208                         if (!remove_file(sub_path)) {
209                                 return false;
210                         }
211                 }
212         }
213         return remove(path.c_str()) == 0;
214
215 #endif
216 }
217
218
219 TempDir::TempDir() {
220 #if _DEFAULT_SOURCE || _BSD_SOURCE || _POSIX_C_SOURCE >= 200809L
221         char tmpl[] = "blank.XXXXXX";
222         const char *name = mkdtemp(tmpl);
223         if (!name) {
224                 throw SysError("unable to create temporary directory");
225         }
226         path = name;
227 #else
228         char name[L_tmpnam];
229         tmpnam(name);
230         constexpr int max_tries = 10;
231         int tries = 0;
232         while (!make_dirs(name) && tries < max_tries) {
233                 tmpnam(name);
234                 ++tries;
235         }
236         if (tries == max_tries) {
237                 throw runtime_error("unable to create temporary directory");
238         }
239 #endif
240         path = name;
241 }
242
243 TempDir::~TempDir() {
244         try {
245                 remove_dir(path);
246         } catch (...) {
247                 cerr << "warning: could not remove temp dir " << path << endl;
248         }
249 }
250
251 }