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