OILS / vendor / souffle / utility / FileUtil.h View on Github | oilshell.org

363 lines, 181 significant
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
53inline 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
68namespace souffle {
69
70// The separator in the PATH variable
71#ifdef _MSC_VER
72const char PATHdelimiter = ';';
73const char pathSeparator = '/';
74#else
75const char PATHdelimiter = ':';
76const char pathSeparator = '/';
77#endif
78
79inline 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
85inline 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 */
93inline 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 */
115inline 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
129inline 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
134inline 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 */
142inline 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 */
171inline 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 */
203inline 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 */
212inline 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 */
231inline 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 */
249inline 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 */
269inline 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 */
288inline 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 */
307inline 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
325inline 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
343inline std::stringstream execStdOut(std::string const& cmd) {
344 return execStdOut(cmd.c_str());
345}
346
347class TempFileStream : public std::fstream {
348 std::string fileName;
349
350public:
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