mkdir_p: support non-readable parent directories (FDO #91000)

The previous mkdir_p() walked down top to bottom and checked each path
entry as it went along. That approach failed unnecessarily when some
existing parent directory could not be read (non-readable /home, for
example).

The new version tries to create the lowest directories first
(brute-force) and only walks up when that is necessary to avoid an
ENOENT.
This commit is contained in:
Patrick Ohly 2015-08-14 18:42:47 +02:00
parent 5a7bb9b572
commit bbfe468290
1 changed files with 26 additions and 19 deletions

View File

@ -140,28 +140,35 @@ bool relToAbs(string &path)
void mkdir_p(const string &path)
{
boost::scoped_array<char> dirs(new char[path.size() + 1]);
char *curr = dirs.get();
strcpy(curr, path.c_str());
do {
char *nextdir = strchr(curr, '/');
if (nextdir) {
*nextdir = 0;
nextdir++;
}
if (*curr) {
if (access(dirs.get(),
nextdir ? (R_OK|X_OK) : (R_OK|X_OK|W_OK)) &&
(errno != ENOENT ||
mkdir(dirs.get(), 0700))) {
Exception::throwError(SE_HERE, string(dirs.get()), errno);
// Try creating the requested directory. If that fails with ENOENT,
// try creating the parent first, then retry. The advantage over
// walking from top to bottom is that the we do not need read access
// to the parent directories (for example, /home is only a+x and
// not a+rx - FDO #91000).
int res = mkdir(path.c_str(), 0700);
if (res) {
switch (errno) {
case EEXIST:
// Assume that it is a directory. If not, using it as a directory
// will fail elsewhere.
break;
case ENOENT: {
// Need to create parent first.
std::string::size_type pos = path.rfind('/');
if (pos != std::string::npos) {
mkdir_p(path.substr(0, pos));
}
res = mkdir(path.c_str(), 0700);
if (res) {
Exception::throwError(SE_HERE, path, errno);
}
break;
}
if (nextdir) {
nextdir[-1] = '/';
default:
Exception::throwError(SE_HERE, path, errno);
break;
}
curr = nextdir;
} while (curr);
}
}
void rm_r(const string &path, boost::function<bool (const string &,