Take another approach to creating intermediate dirs on *NIX

Walk up one level at a time until the directory creation succeeds, then go
back down one level at a time. This reduces the number of operations in the
most common case (when directory already exists).
This commit is contained in:
Mike Gelfand 2017-10-27 16:12:55 +03:00
parent 0378ee8298
commit 474aabbbc4
1 changed files with 87 additions and 51 deletions

View File

@ -138,81 +138,117 @@ static void set_file_for_single_pass(tr_sys_file_t handle)
#ifndef HAVE_MKDIRP
static bool create_path_require_dir(char const* path, tr_error** error)
{
struct stat sb;
if (stat(path, &sb) == -1)
{
set_system_error(error, errno);
return false;
}
if ((sb.st_mode & S_IFMT) != S_IFDIR)
{
tr_error_set(error, ENOTDIR, _("File \"%s\" is in the way"), path);
return false;
}
return true;
}
static bool create_path(char const* path_in, int permissions, tr_error** error)
{
char* p;
char* pp;
bool done;
int tmperr;
int rv;
struct stat sb;
char* path;
/* make a temporary copy of path */
path = tr_strdup(path_in);
char* path = tr_strdup(path_in);
/* walk past the root */
p = path;
char* p = path;
while (*p == TR_PATH_DELIMITER)
{
++p;
}
pp = p;
done = false;
char* path_end = p + strlen(p);
while ((p = strchr(pp, TR_PATH_DELIMITER)) || (p = strchr(pp, '\0')))
while (path_end > path && *path_end == TR_PATH_DELIMITER)
{
if (!*p)
{
done = true;
}
else
{
*p = '\0';
}
--path_end;
}
tmperr = errno;
rv = stat(path, &sb);
errno = tmperr;
char* pp;
bool ret = false;
tr_error* my_error = NULL;
if (rv)
{
tr_error* my_error = NULL;
/* Go one level up on each iteration and attempt to create */
for (pp = path_end; pp != NULL; pp = strrchr(p, TR_PATH_DELIMITER))
{
*pp = '\0';
/* Folder doesn't exist yet */
if (!tr_sys_dir_create(path, 0, permissions, &my_error))
{
tr_logAddError(_("Couldn't create \"%1$s\": %2$s"), path, my_error->message);
tr_free(path);
tr_error_propagate(error, &my_error);
return false;
}
}
else if ((sb.st_mode & S_IFMT) != S_IFDIR)
{
/* Node exists but isn't a folder */
char* const buf = tr_strdup_printf(_("File \"%s\" is in the way"), path);
tr_logAddError(_("Couldn't create \"%1$s\": %2$s"), path_in, buf);
tr_free(buf);
tr_free(path);
set_system_error(error, ENOTDIR);
return false;
}
ret = mkdir(path, permissions) != -1;
if (done)
if (ret)
{
break;
}
*p = TR_PATH_DELIMITER;
p++;
pp = p;
if (errno == EEXIST)
{
ret = create_path_require_dir(path, &my_error);
if (ret)
{
break;
}
goto failure;
}
if (errno != ENOENT)
{
set_system_error(&my_error, errno);
goto failure;
}
}
if (ret && pp == path_end)
{
goto cleanup;
}
/* Go one level down on each iteration and attempt to create */
for (; pp < path_end; pp += strlen(pp))
{
*pp = TR_PATH_DELIMITER;
if (mkdir(path, permissions) == -1)
{
break;
}
}
ret = create_path_require_dir(path, &my_error);
if (ret)
{
goto cleanup;
}
failure:
TR_ASSERT(!ret);
TR_ASSERT(my_error != NULL);
tr_logAddError(_("Couldn't create \"%1$s\": %2$s"), path, my_error->message);
tr_error_propagate(error, &my_error);
cleanup:
TR_ASSERT(my_error == NULL);
tr_free(path);
return true;
return ret;
}
#endif