Boost logo

Boost :

From: Ross Smith (r-smith_at_[hidden])
Date: 2002-03-27 02:29:09


Beman Dawes wrote:
>
> At 04:12 PM 3/26/2002, Ross Smith wrote:
>
> >-- create_directory() needs an option to create any necessary parent
> >directories (mkdir -p). At least, I don't think it does that now; it's
> >hard to tell from your description (branch? leaf?).
>
> That's what the "recurse" option would do.

Thanks, that wasn't clear from the comments. I'd be inclined to give
"recurse downwards" (as in remove()) and "recurse upwards" (as in
create_directory()) different option names (mkdir uses "parents" for the
latter), but no examples spring to mind where both options would be
meaningful, so it's probably not important.

> >-- Function to resolve a relative path into a fully qualified absolute
> >path.
>
> Probably. I need to do some research first.

My implementation for Unix is attached below.

-- 
Ross Smith ...................................... Auckland, New Zealand
r-smith_at_[hidden] ....................................................
  "We need a new cosmology. New gods. New sacraments. Another drink."
                                                       -- Patti Smith
#include <cstring>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <string>
#include <vector>
#include <limits.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#if defined(__CYGWIN__)
  #include <sys/cygwin.h>
#endif
// Exception for reporting Unix API function errors
class SystemError: public std::exception {
  public:
    SystemError(const std::string& details, int error):
      message_() {
        std::ostringstream out;
        out << details << '\n'
            << "System error " << error << '\n'
            << std::strerror(error);
        message_ = out.str();
      }
    ~SystemError() throw() {}
    virtual const char* what() const throw() {
      return message_.c_str();
    }
  private:
    std::string message_;
};
// File path resolution function
// Argument: A path which may be relative, and include ~ references.
// Returns: The corresponding fully qualified absolute path.
// Throws: std::invalid_argument if the argument is an empty string;
//         SystemError if anyting goes wrong with the API calls.
// Notes: It is not required that the named file, or any directory
//        component of the path argument, actually exists.
std::string resolve_path(const std::string& path) {
  // Some of this code is based on item 2.9 of the Unix programming FAQ
  // (http://www.erlenstar.demon.co.uk/unix/faq_toc.html)
  // First, check for the trivial case
  if (path.empty())
    throw std::invalid_argument("The path is empty");
  std::string resolved(path);
  // Special case for Cygwin, in case we get handed a native path
  #if defined(__CYGWIN__)
    std::vector<char> buffer(PATH_MAX);
    cygwin_conv_to_full_posix_path(path.c_str(), &buffer[0]);
    resolved = &buffer[0];
  #endif
  // Check for a leading ~, and expand this to the corresponding home
  // directory; if the directory doesn't exist, assume it's a real
  // file name with a leading ~
  if (resolved[0] == '~') {
    std::string prefix;
    std::string::size_type slash(resolved.find_first_of('/'));
    if (resolved.size() == 1 || slash == 1) {
      const char* home(std::getenv("HOME"));
      if (home == 0) {
        std::vector<char> buffer(sysconf(_SC_GETPW_R_SIZE_MAX));
        passwd temp_pwd;
        passwd* pwd(0);
        int rc(getpwuid_r(getuid(), &temp_pwd, &buffer[0],
                          buffer.size(), &pwd));
        if (rc != 0)
          throw SystemError("getpwuid_r(getuid()) failed", rc);
        if (pwd != 0)
          prefix = pwd->pw_dir;
      } else {
        prefix = home;
      }
    } else {
      std::string user;
      if (slash == std::string::npos)
        user = resolved.substr(1);
      else
        user = resolved.substr(1, slash - 1);
      std::vector<char> buffer(sysconf(_SC_GETPW_R_SIZE_MAX));
      passwd temp_pwd;
      passwd* pwd(0);
      int rc(getpwnam_r(user.c_str(), &temp_pwd, &buffer[0],
                        buffer.size(), &pwd));
      if (rc != 0 && rc != ENOENT)
        throw SystemError("getpwnam_r(" + user + ") failed", rc);
      if (rc != ENOENT && pwd != 0)
        prefix = pwd->pw_dir;
    }
    if (!prefix.empty()) {
      if (prefix[prefix.size() - 1] == '/')
        prefix.resize(prefix.size() - 1);
      resolved.replace(0, slash, prefix);
    }
  }
  // If the first character isn't /, prefix with the current directory
  if (resolved[0] != '/') {
    std::vector<char> buffer(PATH_MAX);
    const char* rc(getcwd(&buffer[0], PATH_MAX));
    if (rc == 0)
      throw SystemError("getcwd() failed", errno);
    std::size_t size(std::strlen(&buffer[0]));
    if (buffer[size - 1] != '/') {
      buffer[size] = '/';
      ++size;
    }
    resolved.insert(0, &buffer[0], size);
  }
  // Collapse any occurrences of . and .. in the path
  if (resolved[resolved.size() - 1] != '/')
    resolved += '/';
  std::string::size_type pos(0);
  for (;;) {
    pos = resolved.find("/./", pos);
    if (pos == std::string::npos)
      break;
    resolved.erase(pos, 2);
    ++pos;
  }
  pos = 0;
  for (;;) {
    pos = resolved.find("/../", pos);
    if (pos == std::string::npos)
      break;
    std::string::size_type cut(pos);
    while (cut > 0 && resolved[cut - 1] != '/')
      --cut;
    if (cut != 0)
      resolved.erase(cut, pos - cut + 4);
    pos = cut;
  }
  // Strip any trailing slash
  if (resolved.size() > 1 && resolved[resolved.size() - 1] == '/')
    resolved.resize(resolved.size() - 1);
  return resolved;
}
int main(int argc, char** argv) {
  try {
    for (int a(1); a < argc; ++a)
      std::cout << argv[a] << " -> " << resolve_path(argv[a]) << '\n';
    return 0;
  }
  catch (const std::exception& ex) {
    std::cerr << "*** " << ex.what() << '\n';
    return EXIT_FAILURE;
  }
}

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk