1 /*
2 path.h -- A simple class for manipulating paths on Linux/Windows/Mac OS
3
4 Copyright (c) 2015 Wenzel Jakob <wenzel@inf.ethz.ch>
5
6 All rights reserved. Use of this source code is governed by a
7 BSD-style license that can be found in the LICENSE file.
8 */
9
10 #pragma once
11
12 #include "fwd.h"
13 #include <string>
14 #include <vector>
15 #include <stdexcept>
16 #include <sstream>
17 #include <cctype>
18 #include <cstdlib>
19 #include <cerrno>
20 #include <cstring>
21
22 #if defined(_WIN32)
23 # include <windows.h>
24 #else
25 # include <unistd.h>
26 #endif
27 #include <sys/stat.h>
28
29 #if (defined(__linux__) || defined(__linux))
30 # include <linux/limits.h>
31 #endif
32
33 #if defined(__APPLE__) || defined(__FreeBSD__)
34 # include <limits.h>
35 #endif
36
37 #ifdef __FreeBSD__
38 # include <sys/syslimits.h>
39 #endif
40
41
NAMESPACE_BEGIN(filesystem)42 NAMESPACE_BEGIN(filesystem)
43
44 /**
45 * \brief Simple class for manipulating paths on Linux/Windows/Mac OS
46 *
47 * This class is just a temporary workaround to avoid the heavy boost
48 * dependency until boost::filesystem is integrated into the standard template
49 * library at some point in the future.
50 */
51 class path {
52 public:
53 enum path_type {
54 windows_path = 0,
55 posix_path = 1,
56 #if defined(_WIN32)
57 native_path = windows_path
58 #else
59 native_path = posix_path
60 #endif
61 };
62
63 path() : m_type(native_path), m_absolute(false) { }
64
65 path(const path &path)
66 : m_type(path.m_type), m_path(path.m_path), m_absolute(path.m_absolute) {}
67
68 path(path &&path)
69 : m_type(path.m_type), m_path(std::move(path.m_path)),
70 m_absolute(path.m_absolute) {}
71
72 path(const char *string) { set(string); }
73
74 path(const std::string &string) { set(string); }
75
76 #if defined(_WIN32)
77 path(const std::wstring &wstring) { set(wstring); }
78 path(const wchar_t *wstring) { set(wstring); }
79 #endif
80
81 size_t length() const { return m_path.size(); }
82
83 bool empty() const { return m_path.empty(); }
84
85 bool is_absolute() const { return m_absolute; }
86
87 path make_absolute() const {
88 #if !defined(_WIN32)
89 char temp[PATH_MAX];
90 if (realpath(str().c_str(), temp) == NULL)
91 throw std::runtime_error("Internal error in realpath(): " + std::string(strerror(errno)));
92 return path(temp);
93 #else
94 std::wstring value = wstr(), out(MAX_PATH, '\0');
95 DWORD length = GetFullPathNameW(value.c_str(), MAX_PATH, &out[0], NULL);
96 if (length == 0)
97 throw std::runtime_error("Internal error in realpath(): " + std::to_string(GetLastError()));
98 return path(out.substr(0, length));
99 #endif
100 }
101
102 bool exists() const {
103 #if defined(_WIN32)
104 return GetFileAttributesW(wstr().c_str()) != INVALID_FILE_ATTRIBUTES;
105 #else
106 struct stat sb;
107 return stat(str().c_str(), &sb) == 0;
108 #endif
109 }
110
111 size_t file_size() const {
112 #if defined(_WIN32)
113 struct _stati64 sb;
114 if (_wstati64(wstr().c_str(), &sb) != 0)
115 throw std::runtime_error("path::file_size(): cannot stat file \"" + str() + "\"!");
116 #else
117 struct stat sb;
118 if (stat(str().c_str(), &sb) != 0)
119 throw std::runtime_error("path::file_size(): cannot stat file \"" + str() + "\"!");
120 #endif
121 return (size_t) sb.st_size;
122 }
123
124 bool is_directory() const {
125 #if defined(_WIN32)
126 DWORD result = GetFileAttributesW(wstr().c_str());
127 if (result == INVALID_FILE_ATTRIBUTES)
128 return false;
129 return (result & FILE_ATTRIBUTE_DIRECTORY) != 0;
130 #else
131 struct stat sb;
132 if (stat(str().c_str(), &sb))
133 return false;
134 return S_ISDIR(sb.st_mode);
135 #endif
136 }
137
138 bool is_file() const {
139 #if defined(_WIN32)
140 DWORD attr = GetFileAttributesW(wstr().c_str());
141 return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0);
142 #else
143 struct stat sb;
144 if (stat(str().c_str(), &sb))
145 return false;
146 return S_ISREG(sb.st_mode);
147 #endif
148 }
149
150 std::string extension() const {
151 const std::string &name = filename();
152 size_t pos = name.find_last_of(".");
153 if (pos == std::string::npos)
154 return "";
155 return name.substr(pos+1);
156 }
157
158 std::string filename() const {
159 if (empty())
160 return "";
161 const std::string &last = m_path[m_path.size()-1];
162 return last;
163 }
164
165 //// Radu: add function to extract stem (basename)
166 //// by stripping extension from filename
167 std::string stem() const {
168 std::string name = filename();
169 if (name.empty())
170 return "";
171 return name.substr(0, name.find_last_of("."));
172 }
173
174 path parent_path() const {
175 path result;
176 result.m_absolute = m_absolute;
177
178 if (m_path.empty()) {
179 if (!m_absolute)
180 result.m_path.push_back("..");
181 } else {
182 size_t until = m_path.size() - 1;
183 for (size_t i = 0; i < until; ++i)
184 result.m_path.push_back(m_path[i]);
185 }
186 return result;
187 }
188
189 path operator/(const path &other) const {
190 if (other.m_absolute)
191 throw std::runtime_error("path::operator/(): expected a relative path!");
192 if (m_type != other.m_type)
193 throw std::runtime_error("path::operator/(): expected a path of the same type!");
194
195 path result(*this);
196
197 for (size_t i=0; i<other.m_path.size(); ++i)
198 result.m_path.push_back(other.m_path[i]);
199
200 return result;
201 }
202
203 std::string str(path_type type = native_path) const {
204 std::ostringstream oss;
205
206 if (m_type == posix_path && m_absolute)
207 oss << "/";
208
209 for (size_t i=0; i<m_path.size(); ++i) {
210 oss << m_path[i];
211 if (i+1 < m_path.size()) {
212 if (type == posix_path)
213 oss << '/';
214 else
215 oss << '\\';
216 }
217 }
218
219 return oss.str();
220 }
221
222 void set(const std::string &str, path_type type = native_path) {
223 m_type = type;
224 if (type == windows_path) {
225 m_path = tokenize(str, "/\\");
226 m_absolute = str.size() >= 2 && std::isalpha(str[0]) && str[1] == ':';
227 } else {
228 m_path = tokenize(str, "/");
229 m_absolute = !str.empty() && str[0] == '/';
230 }
231 }
232
233 path &operator=(const path &path) {
234 m_type = path.m_type;
235 m_path = path.m_path;
236 m_absolute = path.m_absolute;
237 return *this;
238 }
239
240 path &operator=(path &&path) {
241 if (this != &path) {
242 m_type = path.m_type;
243 m_path = std::move(path.m_path);
244 m_absolute = path.m_absolute;
245 }
246 return *this;
247 }
248
249 friend std::ostream &operator<<(std::ostream &os, const path &path) {
250 os << path.str();
251 return os;
252 }
253
254 bool remove_file() {
255 #if !defined(_WIN32)
256 return std::remove(str().c_str()) == 0;
257 #else
258 return DeleteFileW(wstr().c_str()) != 0;
259 #endif
260 }
261
262 bool resize_file(size_t target_length) {
263 #if !defined(_WIN32)
264 return ::truncate(str().c_str(), (off_t) target_length) == 0;
265 #else
266 HANDLE handle = CreateFileW(wstr().c_str(), GENERIC_WRITE, 0, nullptr, 0, FILE_ATTRIBUTE_NORMAL, nullptr);
267 if (handle == INVALID_HANDLE_VALUE)
268 return false;
269 LARGE_INTEGER size;
270 size.QuadPart = (LONGLONG) target_length;
271 if (SetFilePointerEx(handle, size, NULL, FILE_BEGIN) == 0) {
272 CloseHandle(handle);
273 return false;
274 }
275 if (SetEndOfFile(handle) == 0) {
276 CloseHandle(handle);
277 return false;
278 }
279 CloseHandle(handle);
280 return true;
281 #endif
282 }
283
284 static path getcwd() {
285 #if !defined(_WIN32)
286 char temp[PATH_MAX];
287 if (::getcwd(temp, PATH_MAX) == NULL)
288 throw std::runtime_error("Internal error in getcwd(): " + std::string(strerror(errno)));
289 return path(temp);
290 #else
291 std::wstring temp(MAX_PATH, '\0');
292 if (!_wgetcwd(&temp[0], MAX_PATH))
293 throw std::runtime_error("Internal error in getcwd(): " + std::to_string(GetLastError()));
294 return path(temp.c_str());
295 #endif
296 }
297
298 #if defined(_WIN32)
299 std::wstring wstr(path_type type = native_path) const {
300 std::string temp = str(type);
301 int size = MultiByteToWideChar(CP_UTF8, 0, &temp[0], (int)temp.size(), NULL, 0);
302 std::wstring result(size, 0);
303 MultiByteToWideChar(CP_UTF8, 0, &temp[0], (int)temp.size(), &result[0], size);
304 return result;
305 }
306
307
308 void set(const std::wstring &wstring, path_type type = native_path) {
309 std::string string;
310 if (!wstring.empty()) {
311 int size = WideCharToMultiByte(CP_UTF8, 0, &wstring[0], (int)wstring.size(),
312 NULL, 0, NULL, NULL);
313 string.resize(size, 0);
314 WideCharToMultiByte(CP_UTF8, 0, &wstring[0], (int)wstring.size(),
315 &string[0], size, NULL, NULL);
316 }
317 set(string, type);
318 }
319
320 path &operator=(const std::wstring &str) { set(str); return *this; }
321 #endif
322
323 bool operator==(const path &p) const { return p.m_path == m_path; }
324 bool operator!=(const path &p) const { return p.m_path != m_path; }
325
326 protected:
327 static std::vector<std::string> tokenize(const std::string &string, const std::string &delim) {
328 std::string::size_type lastPos = 0, pos = string.find_first_of(delim, lastPos);
329 std::vector<std::string> tokens;
330
331 while (lastPos != std::string::npos) {
332 if (pos != lastPos)
333 tokens.push_back(string.substr(lastPos, pos - lastPos));
334 lastPos = pos;
335 if (lastPos == std::string::npos || lastPos + 1 == string.length())
336 break;
337 pos = string.find_first_of(delim, ++lastPos);
338 }
339
340 return tokens;
341 }
342
343 protected:
344 path_type m_type;
345 std::vector<std::string> m_path;
346 bool m_absolute;
347 };
348
create_directory(const path & p)349 inline bool create_directory(const path& p) {
350 //// Radu: Do nothing if directory already exists.
351 //// This is consistent with std::experimental::filesystem::create_directory in C++17
352 if (p.is_directory())
353 return true;
354
355 #if defined(_WIN32)
356 return CreateDirectoryW(p.wstr().c_str(), NULL) != 0;
357 #else
358 return mkdir(p.str().c_str(), S_IRUSR | S_IWUSR | S_IXUSR) == 0;
359 #endif
360 }
361
362 //// Radu: function to create a hierarchy of directories
create_subdirectory(const path & p)363 inline bool create_subdirectory(const path& p) {
364 auto tmp = p;
365 std::vector<path> hierarchy;
366 while (!tmp.empty()) {
367 hierarchy.push_back(tmp);
368 tmp = tmp.parent_path();
369 }
370
371 for (auto ancestor = hierarchy.rbegin(); ancestor != hierarchy.rend(); ++ancestor) {
372 if (ancestor->exists())
373 continue;
374 if (!create_directory(*ancestor))
375 return false;
376 }
377
378 return true;
379 }
380
381 NAMESPACE_END(filesystem)
382