Mercurial > code
comparison modules/fs/fs.cpp @ 519:d6dad57e9e6b
Fs: import
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 01 Jun 2016 16:35:01 +0200 |
parents | |
children | ecf5fb9319da |
comparison
equal
deleted
inserted
replaced
518:78f296a7b2e5 | 519:d6dad57e9e6b |
---|---|
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 |