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