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