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