1 | /*
|
2 | * Souffle - A Datalog Compiler
|
3 | * Copyright (c) 2021, The Souffle Developers. All rights reserved
|
4 | * Licensed under the Universal Permissive License v 1.0 as shown at:
|
5 | * - https://opensource.org/licenses/UPL
|
6 | * - <souffle root>/licenses/SOUFFLE-UPL.txt
|
7 | */
|
8 |
|
9 | /************************************************************************
|
10 | *
|
11 | * @file FileUtil.h
|
12 | *
|
13 | * @brief Datalog project utilities
|
14 | *
|
15 | ***********************************************************************/
|
16 |
|
17 | #pragma once
|
18 |
|
19 | #include <algorithm>
|
20 | #include <array>
|
21 | #include <climits>
|
22 | #include <cstdio>
|
23 | #include <cstdlib>
|
24 | #include <filesystem>
|
25 | #include <fstream>
|
26 | #include <map>
|
27 | #include <optional>
|
28 | #include <sstream>
|
29 | #include <string>
|
30 | #include <utility>
|
31 | #include <sys/stat.h>
|
32 |
|
33 | // -------------------------------------------------------------------------------
|
34 | // File Utils
|
35 | // -------------------------------------------------------------------------------
|
36 |
|
37 | #ifndef _WIN32
|
38 | #include <unistd.h>
|
39 | #else
|
40 | #define NOMINMAX
|
41 | #define NOGDI
|
42 | #include <fcntl.h>
|
43 | #include <io.h>
|
44 | #include <stdlib.h>
|
45 | #include <windows.h>
|
46 |
|
47 | // -------------------------------------------------------------------------------
|
48 | // Windows
|
49 | // -------------------------------------------------------------------------------
|
50 |
|
51 | #define PATH_MAX 260
|
52 |
|
53 | inline char* realpath(const char* path, char* resolved_path) {
|
54 | return _fullpath(resolved_path, path, PATH_MAX);
|
55 | }
|
56 |
|
57 | /**
|
58 | * Define an alias for the popen and pclose functions on windows
|
59 | */
|
60 | #define popen _popen
|
61 | #define pclose _pclose
|
62 | #endif
|
63 |
|
64 | // -------------------------------------------------------------------------------
|
65 | // All systems
|
66 | // -------------------------------------------------------------------------------
|
67 |
|
68 | namespace souffle {
|
69 |
|
70 | // The separator in the PATH variable
|
71 | #ifdef _MSC_VER
|
72 | const char PATHdelimiter = ';';
|
73 | const char pathSeparator = '/';
|
74 | #else
|
75 | const char PATHdelimiter = ':';
|
76 | const char pathSeparator = '/';
|
77 | #endif
|
78 |
|
79 | inline std::string& makePreferred(std::string& name) {
|
80 | std::replace(name.begin(), name.end(), '\\', '/');
|
81 | // std::replace(name.begin(), name.end(), '/', pathSeparator);
|
82 | return name;
|
83 | }
|
84 |
|
85 | inline bool isAbsolute(const std::string& path) {
|
86 | std::filesystem::path P(path);
|
87 | return P.is_absolute();
|
88 | }
|
89 |
|
90 | /**
|
91 | * Check whether a file exists in the file system
|
92 | */
|
93 | inline bool existFile(const std::string& name) {
|
94 | static std::map<std::string, bool> existFileCache{};
|
95 | auto it = existFileCache.find(name);
|
96 | if (it != existFileCache.end()) {
|
97 | return it->second;
|
98 | }
|
99 | std::filesystem::path P(name);
|
100 | bool result = std::filesystem::exists(P);
|
101 | /*bool result = false;
|
102 | struct stat buffer = {};
|
103 | if (stat(P.native().c_str(), &buffer) == 0) {
|
104 | if ((buffer.st_mode & S_IFMT) != 0) {
|
105 | result = true;
|
106 | }
|
107 | }*/
|
108 | existFileCache[name] = result;
|
109 | return result;
|
110 | }
|
111 |
|
112 | /**
|
113 | * Check whether a directory exists in the file system
|
114 | */
|
115 | inline bool existDir(const std::string& name) {
|
116 | struct stat buffer = {};
|
117 | if (stat(name.c_str(), &buffer) == 0) {
|
118 | if ((buffer.st_mode & S_IFDIR) != 0) {
|
119 | return true;
|
120 | }
|
121 | }
|
122 | return false;
|
123 | }
|
124 |
|
125 | /**
|
126 | * Check whether a given file exists and it is an executable
|
127 | */
|
128 | #ifdef _WIN32
|
129 | inline bool isExecutable(const std::string& name) {
|
130 | return existFile(
|
131 | name); // there is no EXECUTABLE bit on Windows, so theoretically any file may be executable
|
132 | }
|
133 | #else
|
134 | inline bool isExecutable(const std::string& name) {
|
135 | return existFile(name) && (access(name.c_str(), X_OK) == 0);
|
136 | }
|
137 | #endif
|
138 |
|
139 | /**
|
140 | * Simple implementation of a which tool
|
141 | */
|
142 | inline std::string which(const std::string& name) {
|
143 | // Check if name has path components in it and if so return it immediately
|
144 | std::filesystem::path P(name);
|
145 | if (P.has_parent_path()) {
|
146 | return name;
|
147 | }
|
148 | // Get PATH from environment, if it exists.
|
149 | const char* syspath = ::getenv("PATH");
|
150 | if (syspath == nullptr) {
|
151 | return "";
|
152 | }
|
153 | char buf[PATH_MAX];
|
154 | std::stringstream sstr;
|
155 | sstr << syspath;
|
156 | std::string sub;
|
157 |
|
158 | // Check for existence of a binary called 'name' in PATH
|
159 | while (std::getline(sstr, sub, PATHdelimiter)) {
|
160 | std::string path = sub + pathSeparator + name;
|
161 | if ((::realpath(path.c_str(), buf) != nullptr) && isExecutable(path) && !existDir(path)) {
|
162 | return buf;
|
163 | }
|
164 | }
|
165 | return "";
|
166 | }
|
167 |
|
168 | /**
|
169 | * C++-style dirname
|
170 | */
|
171 | inline std::string dirName(const std::string& name) {
|
172 | if (name.empty()) {
|
173 | return ".";
|
174 | }
|
175 |
|
176 | std::filesystem::path P(name);
|
177 | if (P.has_parent_path()) {
|
178 | return P.parent_path().string();
|
179 | } else {
|
180 | return ".";
|
181 | }
|
182 |
|
183 | std::size_t lastNotSlash = name.find_last_not_of(pathSeparator);
|
184 | // All '/'
|
185 | if (lastNotSlash == std::string::npos) {
|
186 | return "/";
|
187 | }
|
188 | std::size_t leadingSlash = name.find_last_of(pathSeparator, lastNotSlash);
|
189 | // No '/'
|
190 | if (leadingSlash == std::string::npos) {
|
191 | return ".";
|
192 | }
|
193 | // dirname is '/'
|
194 | if (leadingSlash == 0) {
|
195 | return std::string(1, pathSeparator);
|
196 | }
|
197 | return name.substr(0, leadingSlash);
|
198 | }
|
199 |
|
200 | /**
|
201 | * C++-style realpath
|
202 | */
|
203 | inline std::string absPath(const std::string& path) {
|
204 | char buf[PATH_MAX];
|
205 | char* res = realpath(path.c_str(), buf);
|
206 | return (res == nullptr) ? "" : std::string(buf);
|
207 | }
|
208 |
|
209 | /**
|
210 | * Join two paths together; note that this does not resolve overlaps or relative paths.
|
211 | */
|
212 | inline std::string pathJoin(const std::string& first, const std::string& second) {
|
213 | return (std::filesystem::path(first) / std::filesystem::path(second)).string();
|
214 |
|
215 | /*unsigned firstPos = static_cast<unsigned>(first.size()) - 1;
|
216 | while (first.at(firstPos) == pathSeparator) {
|
217 | firstPos--;
|
218 | }
|
219 | unsigned secondPos = 0;
|
220 | while (second.at(secondPos) == pathSeparator) {
|
221 | secondPos++;
|
222 | }
|
223 | return first.substr(0, firstPos + 1) + pathSeparator + second.substr(secondPos);*/
|
224 | }
|
225 |
|
226 | /*
|
227 | * Find out if an executable given by @p tool exists in the path given @p path
|
228 | * relative to the directory given by @ base. A path here refers a
|
229 | * colon-separated list of directories.
|
230 | */
|
231 | inline std::optional<std::string> findTool(
|
232 | const std::string& tool, const std::string& base, const std::string& path) {
|
233 | std::filesystem::path dir(dirName(base));
|
234 | std::stringstream sstr(path);
|
235 | std::string sub;
|
236 |
|
237 | while (std::getline(sstr, sub, ':')) {
|
238 | auto subpath = (dir / sub / tool);
|
239 | if (std::filesystem::exists(subpath)) {
|
240 | return absPath(subpath.string());
|
241 | }
|
242 | }
|
243 | return {};
|
244 | }
|
245 |
|
246 | /*
|
247 | * Get the basename of a fully qualified filename
|
248 | */
|
249 | inline std::string baseName(const std::string& filename) {
|
250 | if (filename.empty()) {
|
251 | return ".";
|
252 | }
|
253 |
|
254 | std::size_t lastNotSlash = filename.find_last_not_of(pathSeparator);
|
255 | if (lastNotSlash == std::string::npos) {
|
256 | return std::string(1, pathSeparator);
|
257 | }
|
258 |
|
259 | std::size_t lastSlashBeforeBasename = filename.find_last_of(pathSeparator, lastNotSlash - 1);
|
260 | if (lastSlashBeforeBasename == std::string::npos) {
|
261 | lastSlashBeforeBasename = static_cast<std::size_t>(-1);
|
262 | }
|
263 | return filename.substr(lastSlashBeforeBasename + 1, lastNotSlash - lastSlashBeforeBasename);
|
264 | }
|
265 |
|
266 | /**
|
267 | * File name, with extension removed.
|
268 | */
|
269 | inline std::string simpleName(const std::string& path) {
|
270 | std::string name = baseName(path);
|
271 | const std::size_t lastDot = name.find_last_of('.');
|
272 | // file has no extension
|
273 | if (lastDot == std::string::npos) {
|
274 | return name;
|
275 | }
|
276 | const std::size_t lastSlash = name.find_last_of(pathSeparator);
|
277 | // last slash occurs after last dot, so no extension
|
278 | if (lastSlash != std::string::npos && lastSlash > lastDot) {
|
279 | return name;
|
280 | }
|
281 | // last dot after last slash, or no slash
|
282 | return name.substr(0, lastDot);
|
283 | }
|
284 |
|
285 | /**
|
286 | * File extension, with all else removed.
|
287 | */
|
288 | inline std::string fileExtension(const std::string& path) {
|
289 | std::string name = path;
|
290 | const std::size_t lastDot = name.find_last_of('.');
|
291 | // file has no extension
|
292 | if (lastDot == std::string::npos) {
|
293 | return std::string();
|
294 | }
|
295 | const std::size_t lastSlash = name.find_last_of(pathSeparator);
|
296 | // last slash occurs after last dot, so no extension
|
297 | if (lastSlash != std::string::npos && lastSlash > lastDot) {
|
298 | return std::string();
|
299 | }
|
300 | // last dot after last slash, or no slash
|
301 | return name.substr(lastDot + 1);
|
302 | }
|
303 |
|
304 | /**
|
305 | * Generate temporary file.
|
306 | */
|
307 | inline std::string tempFile() {
|
308 | #ifdef _WIN32
|
309 | char ctempl[L_tmpnam];
|
310 | std::string templ;
|
311 | std::FILE* f = nullptr;
|
312 | while (f == nullptr) {
|
313 | templ = std::tmpnam(ctempl);
|
314 | f = fopen(templ.c_str(), "wx");
|
315 | }
|
316 | fclose(f);
|
317 | return templ;
|
318 | #else
|
319 | char templ[40] = "./souffleXXXXXX";
|
320 | close(mkstemp(templ));
|
321 | return std::string(templ);
|
322 | #endif
|
323 | }
|
324 |
|
325 | inline std::stringstream execStdOut(char const* cmd) {
|
326 | std::stringstream data;
|
327 | std::shared_ptr<FILE> command_pipe(popen(cmd, "r"), pclose);
|
328 |
|
329 | if (command_pipe.get() == nullptr) {
|
330 | return data;
|
331 | }
|
332 |
|
333 | std::array<char, 256> buffer;
|
334 | while (!feof(command_pipe.get())) {
|
335 | if (fgets(buffer.data(), 256, command_pipe.get()) != nullptr) {
|
336 | data << buffer.data();
|
337 | }
|
338 | }
|
339 |
|
340 | return data;
|
341 | }
|
342 |
|
343 | inline std::stringstream execStdOut(std::string const& cmd) {
|
344 | return execStdOut(cmd.c_str());
|
345 | }
|
346 |
|
347 | class TempFileStream : public std::fstream {
|
348 | std::string fileName;
|
349 |
|
350 | public:
|
351 | TempFileStream(std::string fileName = tempFile())
|
352 | : std::fstream(fileName), fileName(std::move(fileName)) {}
|
353 | ~TempFileStream() override {
|
354 | close();
|
355 | remove(fileName.c_str());
|
356 | }
|
357 |
|
358 | std::string const& getFileName() const {
|
359 | return fileName;
|
360 | }
|
361 | };
|
362 |
|
363 | } // namespace souffle
|