1/****************************************************************************
2 * Copyright (C) 2013 Woboq UG (haftungsbeschraenkt)
3 * Olivier Goffart <contact at woboq.com>
4 * https://woboq.com/codebrowser.html
5 *
6 * This file is part of the Woboq Code Browser.
7 *
8 * Commercial License Usage:
9 * Licensees holding valid commercial licenses provided by Woboq may use
10 * this file in accordance with the terms contained in a written agreement
11 * between the licensee and Woboq.
12 * For further information see https://woboq.com/codebrowser.html
13 *
14 * Alternatively, this work may be used under a Creative Commons
15 * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License.
16 * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US
17 * This license does not allow you to use the code browser to assist the
18 * development of your commercial software. If you intent to do so, consider
19 * purchasing a commercial licence.
20 ****************************************************************************/
21
22#include "filesystem.h"
23
24#include <clang/Basic/Version.h>
25#include <llvm/ADT/Twine.h>
26#include <llvm/ADT/SmallString.h>
27#include <llvm/Support/FileSystem.h>
28#include <llvm/Support/Path.h>
29
30#include <iostream>
31
32#ifndef _WIN32
33#include <unistd.h>
34#include <sys/stat.h>
35#else
36#include <windows.h> // MAX_PATH
37#endif
38
39void make_forward_slashes(char *str)
40{
41 char *c = strchr(str, '\\');
42 while (c) {
43 *c = '/';
44 c = strchr(c + 1, '\\');
45 }
46}
47void make_forward_slashes(std::string &str)
48{
49 std::replace(str.begin(), str.end(), '\\', '/');
50}
51
52// ATTENTION: Keep in sync with ECMAScript function of the same name in .js files and `escapeAttrForFilename` generator.cpp
53void replace_invalid_filename_chars(std::string &str)
54{
55 std::replace(str.begin(), str.end(), ':', '.');
56}
57
58std::error_code canonicalize(const llvm::Twine &path, llvm::SmallVectorImpl<char> &result) {
59 std::string p = path.str();
60#if CLANG_VERSION_MAJOR>=5
61 llvm::sys::fs::real_path(path, result);
62#else
63#ifdef PATH_MAX
64 int path_max = PATH_MAX;
65#elif defined(MAX_PATH)
66 unsigned int path_max = MAX_PATH;
67#else
68 int path_max = pathconf(p.c_str(), _PC_PATH_MAX);
69 if (path_max <= 0)
70 path_max = 4096;
71#endif
72
73 result.resize(path_max);
74 realpath(p.c_str(), result.data());
75
76 result.resize(strlen(result.data()));
77#endif
78
79#ifdef _WIN32
80 // Make sure we use forward slashes to make sure folder detection works as expected everywhere
81 make_forward_slashes(result.data());
82#endif
83
84 return {};
85}
86
87#if (CLANG_VERSION_MAJOR>=3 && CLANG_VERSION_MINOR>=8) || CLANG_VERSION_MAJOR>3
88std::error_code create_directories(const llvm::Twine& path)
89{
90 using namespace llvm::sys::fs;
91 auto defaultPerms = perms::all_all
92 & ~perms::group_write
93 & ~perms::others_write;
94
95 return llvm::sys::fs::create_directories(path, true, defaultPerms);
96}
97#else
98/* Based on the one from Support/Unix/PathV2 but with different default rights */
99static std::error_code create_directory(const llvm::Twine& path)
100{
101 using namespace llvm;
102 SmallString<128> path_storage;
103 StringRef p = path.toNullTerminatedStringRef(path_storage);
104
105 if (::mkdir(p.begin(), 0755) == -1) {
106 if (errno != static_cast<int>(std::errc::file_exists))
107 return {errno, std::system_category()};
108 }
109 return {};
110}
111
112std::error_code create_directories(const llvm::Twine& path)
113{
114 using namespace llvm;
115 using namespace llvm::sys;
116 SmallString<128> path_storage;
117 StringRef p = path.toStringRef(path_storage);
118 StringRef parent = path::parent_path(p);
119 if (!parent.empty()) {
120 bool parent_exists;
121#if CLANG_VERSION_MAJOR==3 && CLANG_VERSION_MINOR<=5
122 if (auto ec = fs::exists(parent, parent_exists)) {
123#if CLANG_VERSION_MAJOR==3 && CLANG_VERSION_MINOR<=4
124 return std::error_code(ec.value(), std::system_category());
125#else
126 return ec;
127#endif
128 }
129#else
130 parent_exists = fs::exists(parent);
131#endif
132
133 if (!parent_exists)
134 if (auto ec = create_directories(parent)) return ec;
135 }
136
137 return create_directory(p);
138}
139#endif
140
141/**
142 * https://svn.boost.org/trac/boost/ticket/1976#comment:2
143 *
144 * "The idea: uncomplete(/foo/new, /foo/bar) => ../new
145 * The use case for this is any time you get a full path (from an open dialog, perhaps)
146 * and want to store a relative path so that the group of files can be moved to a different
147 * directory without breaking the paths. An IDE would be a simple example, so that the
148 * project file could be safely checked out of subversion."
149 *
150 * ALGORITHM:
151 * iterate path and base
152 * compare all elements so far of path and base
153 * whilst they are the same, no write to output
154 * when they change, or one runs out:
155 * write to output, ../ times the number of remaining elements in base
156 * write to output, the remaining elements in path
157 */
158std::string naive_uncomplete(llvm::StringRef base, llvm::StringRef path) {
159 using namespace llvm;
160 if (sys::path::has_root_path(path)){
161 if (sys::path::root_path(path) != sys::path::root_path(base)) {
162 return path;
163 } else {
164 return naive_uncomplete(sys::path::relative_path(base), sys::path::relative_path(path));
165 }
166 } else {
167 if (sys::path::has_root_path(base)) {
168 std::cerr << "naive_uncomplete(" << base.str() << "," << path.str()
169 << "): cannot uncomplete a path relative path from a rooted base" << std::endl;
170 return path;
171 } else {
172 auto path_it = sys::path::begin(path);
173 auto path_it_end = sys::path::end(path);
174 auto base_it = sys::path::begin(base);
175 auto base_it_end = sys::path::end(base);
176 while ( path_it != path_it_end && base_it != base_it_end ) {
177 if (*path_it != *base_it) break;
178 ++path_it; ++base_it;
179 }
180 llvm::SmallString<128> result;
181 for (; base_it != base_it_end; ++base_it) {
182 sys::path::append(result, "..");
183 }
184 sys::path::append(result, path_it, path_it_end);
185 return result.str();
186 }
187 }
188}
189