Home | History | Annotate | Download | only in src
      1 // Copyright 2018 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "filepath.h"
      6 #include <string.h>
      7 #include <sys/stat.h>
      8 #include <sys/types.h>
      9 #include <vector>
     10 #include "utils.h"
     11 
     12 inline void AppendToString(std::string* target, std::string& source) {
     13   target->append(source);
     14 }
     15 
     16 FilePath FilePath::DirName() {
     17   FilePath new_path(path_);
     18 
     19   std::string::size_type last_separator =
     20       new_path.path_.find_last_of(kSeparators, std::string::npos);
     21 
     22   unsigned int letter = 0;
     23   if (last_separator == std::string::npos) {
     24     // path_ is in the current directory.
     25     new_path.path_.resize(letter + 1);
     26   } else if (last_separator == letter + 1) {
     27     // path_ is in the root directory.
     28     new_path.path_.resize(letter + 2);
     29   } else if (last_separator == letter + 2 &&
     30              IsSeparator(new_path.path_[letter + 1])) {
     31     // path_ is in "//" (possibly with a drive letter); leave the double
     32     // separator intact indicating alternate root.
     33     new_path.path_.resize(letter + 3);
     34   } else if (last_separator != 0) {
     35     // path_ is somewhere else, trim the basename.
     36     new_path.path_.resize(last_separator);
     37   }
     38   new_path.StripTrailingSeparatorsInternal();
     39   if (!new_path.path_.length())
     40     new_path.path_ = kCurrentDirectory;
     41 
     42   return new_path;
     43 }
     44 
     45 const std::string& FilePath::value() const {
     46   return this->path_;
     47 }
     48 
     49 bool FilePath::IsSeparator(char character) {
     50   for (size_t i = 0; i < strlen(kSeparators) - 1; ++i) {
     51     if (character == kSeparators[i]) {
     52       return true;
     53     }
     54   }
     55   return false;
     56 }
     57 
     58 FilePath FilePath::Append(const FilePath& path) {
     59   // return FilePath(this->path_ + path.path_);
     60   std::string component = path.value();
     61   std::string appended = component;
     62   std::string without_nuls;
     63 
     64   std::string::size_type nul_pos = component.find(kStringTerminator);
     65   if (nul_pos != std::string::npos) {
     66     // without_nuls = component.substr(0, nul_pos);
     67     appended = std::string(without_nuls);
     68   }
     69 
     70   // DCHECK(appended.length() <= 0 || appended[0] !=
     71   // this->separator.c_str()[0]);
     72   if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) {
     73     // Append normally doesn't do any normalization, but as a special case,
     74     // when appending to kCurrentDirectory, just return a new path for the
     75     // component argument.  Appending component to kCurrentDirectory would
     76     // serve no purpose other than needlessly lengthening the path, and
     77     // it's likely in practice to wind up with FilePath objects containing
     78     // only kCurrentDirectory when calling DirName on a single relative path
     79     // component.
     80     return FilePath(appended);
     81   }
     82   FilePath new_path(path_);
     83   new_path.StripTrailingSeparatorsInternal();
     84 
     85   // Don't append a separator if the path is empty (indicating the current
     86   // directory) or if the path component is empty (indicating nothing to
     87   // append).
     88   if (!appended.empty() && !new_path.path_.empty()) {
     89     // Don't append a separator if the path still ends with a trailing
     90     // separator after stripping (indicating the root directory).
     91     char tmp = new_path.path_.back();
     92     if (!IsSeparator(tmp)) {
     93       // Don't append a separator if the path is just a drive letter.
     94       if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) {
     95         new_path.path_.append(1, kSeparators[0]);
     96       }
     97     }
     98   }
     99   AppendToString(&new_path.path_, appended);
    100   return new_path;
    101 }
    102 
    103 void FilePath::StripTrailingSeparatorsInternal() {
    104   // If there is no drive letter, start will be 1, which will prevent
    105   // stripping the leading separator if there is only one separator.  If there
    106   // is a drive letter, start will be set appropriately to prevent stripping
    107   // the first separator following the drive letter, if a separator
    108   // immediately follows the drive letter.
    109   std::string::size_type start = FindDriveLetter(path_) + 2;
    110 
    111   std::string::size_type last_stripped = std::string::npos;
    112   for (std::string::size_type pos = path_.length();
    113        pos > start && IsSeparator(path_[pos - 1]); --pos) {
    114     // If the string only has two separators and they're at the beginning,
    115     // don't strip them, unless the string began with more than two
    116     // separators.
    117     if (pos != start + 1 || last_stripped == start + 2 ||
    118         !IsSeparator(path_[start - 1])) {
    119       path_.resize(pos - 1);
    120       last_stripped = pos;
    121     }
    122   }
    123 }
    124 
    125 std::string::size_type FindDriveLetter(std::string path) {
    126 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
    127   // This is dependent on an ASCII-based character set, but that's a
    128   // reasonable assumption.  iswalpha can be too inclusive here.
    129   if (path.length() >= 2 && path[1] == L':' &&
    130       ((path[0] >= L'A' && path[0] <= L'Z') ||
    131        (path[0] >= L'a' && path[0] <= L'z'))) {
    132     return 1;
    133   }
    134 #endif  // FILE_PATH_USES_DRIVE_LETTERS
    135   return std::string::npos;
    136 }
    137 
    138 bool CreateDirectory(FilePath& full_path) {
    139   std::vector<FilePath> subpaths;
    140 
    141   // Collect a list of all parent directories.
    142   FilePath last_path = full_path;
    143   subpaths.push_back(full_path);
    144   for (FilePath path = full_path.DirName(); path.value() != last_path.value();
    145        path = path.DirName()) {
    146     subpaths.push_back(path);
    147     last_path = path;
    148   }
    149 
    150   // Iterate through the parents and create the missing ones.
    151   for (auto i = subpaths.rbegin(); i != subpaths.rend(); ++i) {
    152     if (check_dir_existence(i->value().c_str()))
    153       continue;
    154     if (mkdir(i->value().c_str(), 0700) == 0)
    155       continue;
    156     // Mkdir failed, but it might have failed with EEXIST, or some other error
    157     // due to the the directory appearing out of thin air. This can occur if
    158     // two processes are trying to create the same file system tree at the same
    159     // time. Check to see if it exists and make sure it is a directory.
    160     if (!check_dir_existence(i->value().c_str())) {
    161       return false;
    162     }
    163   }
    164   return true;
    165 }
    166