519
|
1 /* |
|
2 * fs.cpp -- filesystem operations |
|
3 * |
|
4 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> |
|
5 * |
|
6 * Permission to use, copy, modify, and/or distribute this software for any |
|
7 * purpose with or without fee is hereby granted, provided that the above |
|
8 * copyright notice and this permission notice appear in all copies. |
|
9 * |
|
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
17 */ |
|
18 |
|
19 #if defined(_WIN32) |
|
20 # define _CRT_SECURE_NO_WARNINGS |
|
21 #endif |
|
22 |
|
23 #include <algorithm> |
|
24 #include <cassert> |
|
25 #include <cerrno> |
|
26 #include <cstdio> |
|
27 #include <cstring> |
|
28 #include <sstream> |
|
29 #include <stdexcept> |
|
30 |
|
31 #if defined(_WIN32) |
|
32 # include <direct.h> |
|
33 # include <Windows.h> |
|
34 # include <Shlwapi.h> |
|
35 #else |
|
36 # include <sys/types.h> |
|
37 # include <dirent.h> |
|
38 # include <unistd.h> |
|
39 #endif |
|
40 |
|
41 #include "fs.hpp" |
|
42 |
|
43 namespace fs { |
|
44 |
|
45 namespace { |
|
46 |
|
47 /* |
|
48 * error. |
|
49 * ------------------------------------------------------------------ |
|
50 * |
|
51 * Function to retrieve system error in Windows API. |
|
52 */ |
|
53 #if defined(_WIN32) |
|
54 |
|
55 std::string error() |
|
56 { |
|
57 LPSTR error = nullptr; |
|
58 std::string errmsg = "Unknown error"; |
|
59 |
|
60 FormatMessageA( |
|
61 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, |
|
62 nullptr, |
|
63 GetLastError(), |
|
64 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
|
65 (LPSTR)&error, 0, nullptr); |
|
66 |
|
67 if (error) { |
|
68 errmsg = std::string(error); |
|
69 LocalFree(error); |
|
70 } |
|
71 |
|
72 return errmsg; |
|
73 } |
|
74 |
|
75 #endif |
|
76 |
|
77 /* |
|
78 * hasAccess. |
|
79 * ------------------------------------------------------------------ |
|
80 * |
|
81 * Check if we have access to the file specified, mode is the same used as std::fopen. |
|
82 */ |
|
83 bool hasAccess(const std::string &path, const std::string &mode) |
|
84 { |
|
85 assert(mode.length() == 1); |
|
86 assert(mode[0] == 'r' || mode[0] == 'w'); |
|
87 |
|
88 auto fp = std::fopen(path.c_str(), mode.c_str()); |
|
89 |
|
90 if (fp == nullptr) |
|
91 return false; |
|
92 |
|
93 std::fclose(fp); |
|
94 |
|
95 return true; |
|
96 } |
|
97 |
|
98 /* |
|
99 * typeOf. |
|
100 * ------------------------------------------------------------------ |
|
101 * |
|
102 * Get the type of the specified file. |
|
103 * |
|
104 * Use GetFileAttributesA on Windows and stat if available. |
|
105 * |
|
106 * Receives the object as predicate parameter and return true on success. |
|
107 */ |
|
108 #if defined(_WIN32) |
|
109 |
|
110 template <typename Predicate> |
|
111 bool typeOf(const std::string &path, Predicate &&predicate) |
|
112 { |
|
113 DWORD result = GetFileAttributesA(path.c_str()); |
|
114 |
|
115 if (result == INVALID_FILE_ATTRIBUTES) |
|
116 return false; |
|
117 |
|
118 return predicate(result); |
|
119 } |
|
120 |
|
121 #elif defined(FS_HAVE_STAT) |
|
122 |
|
123 template <typename Predicate> |
|
124 bool typeOf(const std::string &path, Predicate &&predicate) noexcept |
|
125 { |
|
126 struct stat st; |
|
127 |
|
128 if (::stat(path.c_str(), &st) < 0) |
|
129 return false; |
|
130 |
|
131 return predicate(st); |
|
132 } |
|
133 |
|
134 #else |
|
135 |
|
136 template <typename Predicate> |
|
137 bool typeOf(const std::string &path, Predicate &&predicate) noexcept |
|
138 { |
|
139 throw std::runtime_error(std::strerror(ENOSYS)); |
|
140 } |
|
141 |
|
142 #endif |
|
143 |
|
144 } // !namespace |
|
145 |
|
146 /* |
|
147 * clean. |
|
148 * ------------------------------------------------------------------ |
|
149 */ |
|
150 std::string clean(std::string input) |
|
151 { |
|
152 if (input.empty()) |
|
153 return input; |
|
154 |
|
155 /* First, remove any duplicates */ |
|
156 input.erase(std::unique(input.begin(), input.end(), [&] (char c1, char c2) { |
|
157 return c1 == c2 && (c1 == '/' || c1 == '\\'); |
|
158 }), input.end()); |
|
159 |
|
160 /* Add a trailing / or \\ */ |
|
161 char c = input[input.length() - 1]; |
|
162 |
|
163 if (c != '/' && c != '\\') |
|
164 input += separator(); |
|
165 |
|
166 /* Now converts all / to \\ for Windows and the opposite for Unix */ |
|
167 #if defined(_WIN32) |
|
168 std::replace(input.begin(), input.end(), '/', '\\'); |
|
169 #else |
|
170 std::replace(input.begin(), input.end(), '\\', '/'); |
|
171 #endif |
|
172 |
|
173 return input; |
|
174 } |
|
175 |
|
176 /* |
|
177 * baseName. |
|
178 * ------------------------------------------------------------------ |
|
179 */ |
|
180 std::string baseName(std::string path) |
|
181 { |
|
182 auto pos = path.find_last_of("\\/"); |
|
183 |
|
184 if (pos != std::string::npos) |
|
185 path = path.substr(pos + 1); |
|
186 |
|
187 return path; |
|
188 } |
|
189 |
|
190 /* |
|
191 * dirName. |
|
192 * ------------------------------------------------------------------ |
|
193 */ |
|
194 std::string dirName(std::string path) |
|
195 { |
|
196 auto pos = path.find_last_of("\\/"); |
|
197 |
|
198 if (pos == std::string::npos) |
|
199 path = "."; |
|
200 else |
|
201 path = path.substr(0, pos); |
|
202 |
|
203 return path; |
|
204 } |
|
205 |
|
206 /* |
|
207 * isAbsolute. |
|
208 * ------------------------------------------------------------------ |
|
209 */ |
|
210 bool isAbsolute(const std::string &path) noexcept |
|
211 { |
|
212 #if defined(_WIN32) |
|
213 return !isRelative(path); |
|
214 #else |
|
215 return path.size() > 0 && path[0] == '/'; |
|
216 #endif |
|
217 } |
|
218 |
|
219 /* |
|
220 * isRelative. |
|
221 * ------------------------------------------------------------------ |
|
222 */ |
|
223 bool isRelative(const std::string &path) noexcept |
|
224 { |
|
225 #if defined(_WIN32) |
|
226 return PathIsRelativeA(path.c_str()) == 1; |
|
227 #else |
|
228 return !isAbsolute(path); |
|
229 #endif |
|
230 } |
|
231 |
|
232 /* |
|
233 * isReadable. |
|
234 * ------------------------------------------------------------------ |
|
235 */ |
|
236 bool isReadable(const std::string &path) noexcept |
|
237 { |
|
238 return hasAccess(path, "r"); |
|
239 } |
|
240 |
|
241 /* |
|
242 * isWritable. |
|
243 * ------------------------------------------------------------------ |
|
244 */ |
|
245 bool isWritable(const std::string &path) noexcept |
|
246 { |
|
247 return hasAccess(path, "w"); |
|
248 } |
|
249 |
|
250 /* |
|
251 * isFile. |
|
252 * ------------------------------------------------------------------ |
|
253 */ |
|
254 bool isFile(const std::string &path) |
|
255 { |
|
256 return typeOf(path, [] (const auto &object) { |
|
257 #if defined(_WIN32) |
|
258 return (object & FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE; |
|
259 #elif defined(FS_HAVE_STAT) |
|
260 return S_ISREG(object.st_mode); |
|
261 #endif |
|
262 }); |
|
263 } |
|
264 |
|
265 /* |
|
266 * isDirectory. |
|
267 * ------------------------------------------------------------------ |
|
268 */ |
|
269 bool isDirectory(const std::string &path) |
|
270 { |
|
271 return typeOf(path, [] (const auto &object) { |
|
272 #if defined(_WIN32) |
|
273 return (object & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY; |
|
274 #elif defined(FS_HAVE_STAT) |
|
275 return S_ISDIR(object.st_mode); |
|
276 #endif |
|
277 }); |
|
278 } |
|
279 |
|
280 /* |
|
281 * isSymlink. |
|
282 * ------------------------------------------------------------------ |
|
283 */ |
|
284 bool isSymlink(const std::string &path) |
|
285 { |
|
286 return typeOf(path, [] (const auto &object) { |
|
287 #if defined(_WIN32) |
|
288 return (object & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT; |
|
289 #elif defined(FS_HAVE_STAT) |
|
290 return S_ISLNK(object.st_mode); |
|
291 #endif |
|
292 }); |
|
293 } |
|
294 |
|
295 /* |
|
296 * stat. |
|
297 * ------------------------------------------------------------------ |
|
298 */ |
|
299 #if defined(FS_HAVE_STAT) |
|
300 |
|
301 struct stat stat(const std::string &path) |
|
302 { |
|
303 struct stat st; |
|
304 |
|
305 if (::stat(path.c_str(), &st) < 0) |
|
306 throw std::runtime_error(std::strerror(errno)); |
|
307 |
|
308 return st; |
|
309 } |
|
310 |
|
311 #endif |
|
312 |
|
313 /* |
|
314 * exists. |
|
315 * ------------------------------------------------------------------ |
|
316 */ |
|
317 bool exists(const std::string &path) noexcept |
|
318 { |
|
319 #if defined(FS_HAVE_STAT) |
|
320 struct stat st; |
|
321 |
|
322 return ::stat(path.c_str(), &st) == 0; |
|
323 #else |
|
324 return hasAccess(path, "r"); |
|
325 #endif |
|
326 } |
|
327 |
|
328 /* |
|
329 * readdir. |
|
330 * ------------------------------------------------------------------ |
|
331 */ |
|
332 std::vector<Entry> readdir(const std::string &path, int flags) |
|
333 { |
|
334 std::vector<Entry> entries; |
|
335 |
|
336 #if defined(_WIN32) |
|
337 std::ostringstream oss; |
|
338 HANDLE handle; |
|
339 WIN32_FIND_DATA fdata; |
|
340 |
|
341 oss << path << "\\*"; |
|
342 handle = FindFirstFile(oss.str().c_str(), &fdata); |
|
343 |
|
344 if (handle == nullptr) |
|
345 throw std::runtime_error(error()); |
|
346 |
|
347 do { |
|
348 Entry entry; |
|
349 |
|
350 entry.name = fdata.cFileName; |
|
351 |
|
352 if (entry.name == "." && !(flags & Dot)) |
|
353 continue; |
|
354 if (entry.name == ".." && !(flags & DotDot)) |
|
355 continue; |
|
356 |
|
357 switch (fdata.dwFileAttributes) { |
|
358 case FILE_ATTRIBUTE_DIRECTORY: |
|
359 entry.type = Entry::Dir; |
|
360 break; |
|
361 case FILE_ATTRIBUTE_NORMAL: |
|
362 entry.type = Entry::File; |
|
363 break; |
|
364 case FILE_ATTRIBUTE_REPARSE_POINT: |
|
365 entry.type = Entry::Link; |
|
366 break; |
|
367 default: |
|
368 break; |
|
369 } |
|
370 |
|
371 entries.push_back(std::move(entry)); |
|
372 } while (FindNextFile(handle, &fdata) != 0); |
|
373 |
|
374 FindClose(handle); |
|
375 #else |
|
376 DIR *dp; |
|
377 struct dirent *ent; |
|
378 |
|
379 if ((dp = opendir(path.c_str())) == nullptr) |
|
380 throw std::runtime_error(std::strerror(errno)); |
|
381 |
|
382 while ((ent = readdir(dp)) != nullptr) { |
|
383 Entry entry; |
|
384 |
|
385 entry.name = ent->d_name; |
|
386 if (entry.name == "." && !(flags & Dot)) |
|
387 continue; |
|
388 if (entry.name == ".." && !(flags & DotDot)) |
|
389 continue; |
|
390 |
|
391 switch (ent->d_type) { |
|
392 case DT_DIR: |
|
393 entry.type = Entry::Dir; |
|
394 break; |
|
395 case DT_REG: |
|
396 entry.type = Entry::File; |
|
397 break; |
|
398 case DT_LNK: |
|
399 entry.type = Entry::Link; |
|
400 break; |
|
401 default: |
|
402 break; |
|
403 } |
|
404 |
|
405 entries.push_back(std::move(entry)); |
|
406 } |
|
407 |
|
408 closedir(dp); |
|
409 #endif |
|
410 |
|
411 return entries; |
|
412 } |
|
413 |
|
414 /* |
|
415 * mkdir. |
|
416 * ------------------------------------------------------------------ |
|
417 */ |
|
418 void mkdir(const std::string &path, int mode) |
|
419 { |
|
420 std::string::size_type next = 0; |
|
421 std::string part; |
|
422 |
|
423 for (;;) { |
|
424 next = path.find_first_of("\\/", next); |
|
425 part = path.substr(0, next); |
|
426 |
|
427 if (!part.empty()) { |
|
428 #if defined(_WIN32) |
|
429 (void)mode; |
|
430 |
|
431 if (::_mkdir(part.c_str()) < 0 && errno != EEXIST) |
|
432 throw std::runtime_error(std::strerror(errno)); |
|
433 #else |
|
434 if (::mkdir(part.c_str(), mode) < 0 && errno != EEXIST) |
|
435 throw std::runtime_error(std::strerror(errno)); |
|
436 #endif |
|
437 } |
|
438 |
|
439 if (next++ == std::string::npos) |
|
440 break; |
|
441 } |
|
442 } |
|
443 |
|
444 /* |
|
445 * rmdir. |
|
446 * ------------------------------------------------------------------ |
|
447 */ |
|
448 void rmdir(const std::string &base) noexcept |
|
449 { |
|
450 try { |
|
451 for (const auto &entry : readdir(base)) { |
|
452 std::string path = base + separator() + entry.name; |
|
453 |
|
454 if (entry.type == Entry::Dir) |
|
455 rmdir(path); |
|
456 else |
|
457 remove(path.c_str()); |
|
458 } |
|
459 } catch (...) { |
|
460 /* Silently discard to remove as much as possible */ |
|
461 } |
|
462 |
|
463 #if defined(_WIN32) |
|
464 RemoveDirectoryA(base.c_str()); |
|
465 #else |
|
466 remove(base.c_str()); |
|
467 #endif |
|
468 } |
|
469 |
|
470 /* |
|
471 * cwd. |
|
472 * ------------------------------------------------------------------ |
|
473 */ |
|
474 std::string cwd() |
|
475 { |
|
476 #if defined(_WIN32) |
|
477 char path[MAX_PATH]; |
|
478 |
|
479 if (!GetCurrentDirectoryA(sizeof (path), path)) |
|
480 throw std::runtime_error("failed to get current working directory"); |
|
481 |
|
482 return path; |
|
483 #else |
|
484 char path[PATH_MAX]; |
|
485 |
|
486 if (getcwd(path, sizeof (path)) == nullptr) |
|
487 throw std::runtime_error{std::strerror(errno)}; |
|
488 |
|
489 return path; |
|
490 #endif |
|
491 } |
|
492 |
|
493 } // !fs |