operations.cpp revision 1.1 1 1.1 jmmv // Copyright 2010 Google Inc.
2 1.1 jmmv // All rights reserved.
3 1.1 jmmv //
4 1.1 jmmv // Redistribution and use in source and binary forms, with or without
5 1.1 jmmv // modification, are permitted provided that the following conditions are
6 1.1 jmmv // met:
7 1.1 jmmv //
8 1.1 jmmv // * Redistributions of source code must retain the above copyright
9 1.1 jmmv // notice, this list of conditions and the following disclaimer.
10 1.1 jmmv // * Redistributions in binary form must reproduce the above copyright
11 1.1 jmmv // notice, this list of conditions and the following disclaimer in the
12 1.1 jmmv // documentation and/or other materials provided with the distribution.
13 1.1 jmmv // * Neither the name of Google Inc. nor the names of its contributors
14 1.1 jmmv // may be used to endorse or promote products derived from this software
15 1.1 jmmv // without specific prior written permission.
16 1.1 jmmv //
17 1.1 jmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 1.1 jmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 1.1 jmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 1.1 jmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 1.1 jmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 1.1 jmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 1.1 jmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 1.1 jmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 1.1 jmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 1.1 jmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 1.1 jmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 1.1 jmmv
29 1.1 jmmv #include "utils/fs/operations.hpp"
30 1.1 jmmv
31 1.1 jmmv #if defined(HAVE_CONFIG_H)
32 1.1 jmmv # include "config.h"
33 1.1 jmmv #endif
34 1.1 jmmv
35 1.1 jmmv extern "C" {
36 1.1 jmmv #include <sys/stat.h>
37 1.1 jmmv
38 1.1 jmmv #include <dirent.h>
39 1.1 jmmv #include <unistd.h>
40 1.1 jmmv }
41 1.1 jmmv
42 1.1 jmmv #include <cerrno>
43 1.1 jmmv #include <cstdlib>
44 1.1 jmmv #include <cstring>
45 1.1 jmmv #include <sstream>
46 1.1 jmmv #include <string>
47 1.1 jmmv
48 1.1 jmmv #include "utils/auto_array.ipp"
49 1.1 jmmv #include "utils/defs.hpp"
50 1.1 jmmv #include "utils/env.hpp"
51 1.1 jmmv #include "utils/format/macros.hpp"
52 1.1 jmmv #include "utils/fs/exceptions.hpp"
53 1.1 jmmv #include "utils/fs/path.hpp"
54 1.1 jmmv #include "utils/logging/macros.hpp"
55 1.1 jmmv #include "utils/optional.ipp"
56 1.1 jmmv #include "utils/sanity.hpp"
57 1.1 jmmv
58 1.1 jmmv namespace fs = utils::fs;
59 1.1 jmmv
60 1.1 jmmv using utils::optional;
61 1.1 jmmv
62 1.1 jmmv
63 1.1 jmmv namespace {
64 1.1 jmmv
65 1.1 jmmv
66 1.1 jmmv /// Stats a file, without following links.
67 1.1 jmmv ///
68 1.1 jmmv /// \param path The file to stat.
69 1.1 jmmv ///
70 1.1 jmmv /// \return The stat structure on success.
71 1.1 jmmv ///
72 1.1 jmmv /// \throw system_error An error on failure.
73 1.1 jmmv static struct ::stat
74 1.1 jmmv safe_stat(const fs::path& path)
75 1.1 jmmv {
76 1.1 jmmv struct ::stat sb;
77 1.1 jmmv if (::lstat(path.c_str(), &sb) == -1) {
78 1.1 jmmv const int original_errno = errno;
79 1.1 jmmv throw fs::system_error(F("Cannot get information about %s") % path,
80 1.1 jmmv original_errno);
81 1.1 jmmv }
82 1.1 jmmv return sb;
83 1.1 jmmv }
84 1.1 jmmv
85 1.1 jmmv
86 1.1 jmmv } // anonymous namespace
87 1.1 jmmv
88 1.1 jmmv
89 1.1 jmmv /// Queries the path to the current directory.
90 1.1 jmmv ///
91 1.1 jmmv /// \return The path to the current directory.
92 1.1 jmmv ///
93 1.1 jmmv /// \throw fs::error If there is a problem querying the current directory.
94 1.1 jmmv fs::path
95 1.1 jmmv fs::current_path(void)
96 1.1 jmmv {
97 1.1 jmmv char* cwd;
98 1.1 jmmv #if defined(HAVE_GETCWD_DYN)
99 1.1 jmmv cwd = ::getcwd(NULL, 0);
100 1.1 jmmv #else
101 1.1 jmmv cwd = ::getcwd(NULL, MAXPATHLEN);
102 1.1 jmmv #endif
103 1.1 jmmv if (cwd == NULL) {
104 1.1 jmmv const int original_errno = errno;
105 1.1 jmmv throw fs::system_error(F("Failed to get current working directory"),
106 1.1 jmmv original_errno);
107 1.1 jmmv }
108 1.1 jmmv
109 1.1 jmmv try {
110 1.1 jmmv const fs::path result(cwd);
111 1.1 jmmv std::free(cwd);
112 1.1 jmmv return result;
113 1.1 jmmv } catch (...) {
114 1.1 jmmv std::free(cwd);
115 1.1 jmmv throw;
116 1.1 jmmv }
117 1.1 jmmv }
118 1.1 jmmv
119 1.1 jmmv
120 1.1 jmmv /// Checks if a file exists.
121 1.1 jmmv ///
122 1.1 jmmv /// Be aware that this is racy in the same way as access(2) is.
123 1.1 jmmv ///
124 1.1 jmmv /// \param path The file to check the existance of.
125 1.1 jmmv ///
126 1.1 jmmv /// \return True if the file exists; false otherwise.
127 1.1 jmmv bool
128 1.1 jmmv fs::exists(const fs::path& path)
129 1.1 jmmv {
130 1.1 jmmv return ::access(path.c_str(), F_OK) == 0;
131 1.1 jmmv }
132 1.1 jmmv
133 1.1 jmmv
134 1.1 jmmv /// Locates a file in the PATH.
135 1.1 jmmv ///
136 1.1 jmmv /// \param name The file to locate.
137 1.1 jmmv ///
138 1.1 jmmv /// \return The path to the located file or none if it was not found. The
139 1.1 jmmv /// returned path is always absolute.
140 1.1 jmmv optional< fs::path >
141 1.1 jmmv fs::find_in_path(const char* name)
142 1.1 jmmv {
143 1.1 jmmv const optional< std::string > current_path = utils::getenv("PATH");
144 1.1 jmmv if (!current_path || current_path.get().empty())
145 1.1 jmmv return none;
146 1.1 jmmv
147 1.1 jmmv std::istringstream path_input(current_path.get() + ":");
148 1.1 jmmv std::string path_component;
149 1.1 jmmv while (std::getline(path_input, path_component, ':').good()) {
150 1.1 jmmv const fs::path candidate = path_component.empty() ?
151 1.1 jmmv fs::path(name) : (fs::path(path_component) / name);
152 1.1 jmmv if (exists(candidate)) {
153 1.1 jmmv if (candidate.is_absolute())
154 1.1 jmmv return utils::make_optional(candidate);
155 1.1 jmmv else
156 1.1 jmmv return utils::make_optional(candidate.to_absolute());
157 1.1 jmmv }
158 1.1 jmmv }
159 1.1 jmmv return none;
160 1.1 jmmv }
161 1.1 jmmv
162 1.1 jmmv
163 1.1 jmmv /// Creates a directory.
164 1.1 jmmv ///
165 1.1 jmmv /// \param dir The path to the directory to create.
166 1.1 jmmv /// \param mode The permissions for the new directory.
167 1.1 jmmv ///
168 1.1 jmmv /// \throw system_error If the call to mkdir(2) fails.
169 1.1 jmmv void
170 1.1 jmmv fs::mkdir(const fs::path& dir, const int mode)
171 1.1 jmmv {
172 1.1 jmmv if (::mkdir(dir.c_str(), static_cast< mode_t >(mode)) == -1) {
173 1.1 jmmv const int original_errno = errno;
174 1.1 jmmv throw fs::system_error(F("Failed to create directory %s") % dir,
175 1.1 jmmv original_errno);
176 1.1 jmmv }
177 1.1 jmmv }
178 1.1 jmmv
179 1.1 jmmv
180 1.1 jmmv /// Creates a directory and any missing parents.
181 1.1 jmmv ///
182 1.1 jmmv /// This is separate from the fs::mkdir function to clearly differentiate the
183 1.1 jmmv /// libc wrapper from the more complex algorithm implemented here.
184 1.1 jmmv ///
185 1.1 jmmv /// \param dir The path to the directory to create.
186 1.1 jmmv /// \param mode The permissions for the new directories.
187 1.1 jmmv ///
188 1.1 jmmv /// \throw system_error If any call to mkdir(2) fails.
189 1.1 jmmv void
190 1.1 jmmv fs::mkdir_p(const fs::path& dir, const int mode)
191 1.1 jmmv {
192 1.1 jmmv try {
193 1.1 jmmv fs::mkdir(dir, mode);
194 1.1 jmmv } catch (const fs::system_error& e) {
195 1.1 jmmv if (e.original_errno() == ENOENT) {
196 1.1 jmmv fs::mkdir_p(dir.branch_path(), mode);
197 1.1 jmmv fs::mkdir(dir, mode);
198 1.1 jmmv } else if (e.original_errno() != EEXIST)
199 1.1 jmmv throw e;
200 1.1 jmmv }
201 1.1 jmmv }
202 1.1 jmmv
203 1.1 jmmv
204 1.1 jmmv /// Creates a temporary directory.
205 1.1 jmmv ///
206 1.1 jmmv /// The temporary directory is created using mkdtemp(3) using the provided
207 1.1 jmmv /// template. This should be most likely used in conjunction with
208 1.1 jmmv /// fs::auto_directory.
209 1.1 jmmv ///
210 1.1 jmmv /// \param path_template The template for the temporary path, which is a
211 1.1 jmmv /// basename that is created within the TMPDIR. Must contain the XXXXXX
212 1.1 jmmv /// pattern, which is atomically replaced by a random unique string.
213 1.1 jmmv ///
214 1.1 jmmv /// \return The generated path for the temporary directory.
215 1.1 jmmv ///
216 1.1 jmmv /// \throw fs::system_error If the call to mkdtemp(3) fails.
217 1.1 jmmv fs::path
218 1.1 jmmv fs::mkdtemp(const std::string& path_template)
219 1.1 jmmv {
220 1.1 jmmv PRE(path_template.find("XXXXXX") != std::string::npos);
221 1.1 jmmv
222 1.1 jmmv const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
223 1.1 jmmv const fs::path full_template = tmpdir / path_template;
224 1.1 jmmv
225 1.1 jmmv utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
226 1.1 jmmv std::strcpy(buf.get(), full_template.c_str());
227 1.1 jmmv if (::mkdtemp(buf.get()) == NULL) {
228 1.1 jmmv const int original_errno = errno;
229 1.1 jmmv throw fs::system_error(F("Cannot create temporary directory using "
230 1.1 jmmv "template %s") % full_template,
231 1.1 jmmv original_errno);
232 1.1 jmmv }
233 1.1 jmmv return fs::path(buf.get());
234 1.1 jmmv }
235 1.1 jmmv
236 1.1 jmmv
237 1.1 jmmv /// Creates a temporary file.
238 1.1 jmmv ///
239 1.1 jmmv /// The temporary file is created using mkstemp(3) using the provided template.
240 1.1 jmmv /// This should be most likely used in conjunction with fs::auto_file.
241 1.1 jmmv ///
242 1.1 jmmv /// \param path_template The template for the temporary path, which is a
243 1.1 jmmv /// basename that is created within the TMPDIR. Must contain the XXXXXX
244 1.1 jmmv /// pattern, which is atomically replaced by a random unique string.
245 1.1 jmmv ///
246 1.1 jmmv /// \return The generated path for the temporary directory.
247 1.1 jmmv ///
248 1.1 jmmv /// \throw fs::system_error If the call to mkstemp(3) fails.
249 1.1 jmmv fs::path
250 1.1 jmmv fs::mkstemp(const std::string& path_template)
251 1.1 jmmv {
252 1.1 jmmv PRE(path_template.find("XXXXXX") != std::string::npos);
253 1.1 jmmv
254 1.1 jmmv const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
255 1.1 jmmv const fs::path full_template = tmpdir / path_template;
256 1.1 jmmv
257 1.1 jmmv utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
258 1.1 jmmv std::strcpy(buf.get(), full_template.c_str());
259 1.1 jmmv if (::mkstemp(buf.get()) == -1) {
260 1.1 jmmv const int original_errno = errno;
261 1.1 jmmv throw fs::system_error(F("Cannot create temporary file using template "
262 1.1 jmmv "%s") % full_template, original_errno);
263 1.1 jmmv }
264 1.1 jmmv return fs::path(buf.get());
265 1.1 jmmv }
266 1.1 jmmv
267 1.1 jmmv
268 1.1 jmmv /// Recursively removes a directory.
269 1.1 jmmv ///
270 1.1 jmmv /// This operation simulates a "rm -r". No effort is made to forcibly delete
271 1.1 jmmv /// files and no attention is paid to mount points.
272 1.1 jmmv ///
273 1.1 jmmv /// \param directory The directory to remove.
274 1.1 jmmv ///
275 1.1 jmmv /// \throw fs::error If there is a problem removing any directory or file.
276 1.1 jmmv void
277 1.1 jmmv fs::rm_r(const fs::path& directory)
278 1.1 jmmv {
279 1.1 jmmv DIR* dirp = ::opendir(directory.c_str());
280 1.1 jmmv if (dirp == NULL) {
281 1.1 jmmv const int original_errno = errno;
282 1.1 jmmv throw fs::system_error(F("Failed to open directory %s") %
283 1.1 jmmv directory.str(), original_errno);
284 1.1 jmmv }
285 1.1 jmmv try {
286 1.1 jmmv ::dirent* dp;
287 1.1 jmmv while ((dp = ::readdir(dirp)) != NULL) {
288 1.1 jmmv const std::string name = dp->d_name;
289 1.1 jmmv if (name == "." || name == "..")
290 1.1 jmmv continue;
291 1.1 jmmv
292 1.1 jmmv const fs::path entry = directory / dp->d_name;
293 1.1 jmmv
294 1.1 jmmv const struct ::stat sb = safe_stat(entry);
295 1.1 jmmv if (S_ISDIR(sb.st_mode)) {
296 1.1 jmmv LD(F("Descending into %s") % entry);
297 1.1 jmmv fs::rm_r(entry);
298 1.1 jmmv } else {
299 1.1 jmmv LD(F("Removing file %s") % entry);
300 1.1 jmmv fs::unlink(entry);
301 1.1 jmmv }
302 1.1 jmmv }
303 1.1 jmmv } catch (...) {
304 1.1 jmmv ::closedir(dirp);
305 1.1 jmmv throw;
306 1.1 jmmv }
307 1.1 jmmv ::closedir(dirp);
308 1.1 jmmv
309 1.1 jmmv LD(F("Removing empty directory %s") % directory);
310 1.1 jmmv fs::rmdir(directory);
311 1.1 jmmv }
312 1.1 jmmv
313 1.1 jmmv
314 1.1 jmmv /// Removes an empty directory.
315 1.1 jmmv ///
316 1.1 jmmv /// \param file The directory to remove.
317 1.1 jmmv ///
318 1.1 jmmv /// \throw fs::system_error If the call to rmdir(2) fails.
319 1.1 jmmv void
320 1.1 jmmv fs::rmdir(const path& file)
321 1.1 jmmv {
322 1.1 jmmv if (::rmdir(file.c_str()) == -1) {
323 1.1 jmmv const int original_errno = errno;
324 1.1 jmmv throw fs::system_error(F("Removal of %s failed") % file,
325 1.1 jmmv original_errno);
326 1.1 jmmv }
327 1.1 jmmv }
328 1.1 jmmv
329 1.1 jmmv
330 1.1 jmmv /// Removes a file.
331 1.1 jmmv ///
332 1.1 jmmv /// \param file The file to remove.
333 1.1 jmmv ///
334 1.1 jmmv /// \throw fs::system_error If the call to unlink(2) fails.
335 1.1 jmmv void
336 1.1 jmmv fs::unlink(const path& file)
337 1.1 jmmv {
338 1.1 jmmv if (::unlink(file.c_str()) == -1) {
339 1.1 jmmv const int original_errno = errno;
340 1.1 jmmv throw fs::system_error(F("Removal of %s failed") % file,
341 1.1 jmmv original_errno);
342 1.1 jmmv }
343 1.1 jmmv }
344