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