]> granicus.if.org Git - transmission/commitdiff
Take another approach to creating intermediate dirs on *NIX
authorMike Gelfand <mikedld@mikedld.com>
Fri, 27 Oct 2017 13:12:55 +0000 (16:12 +0300)
committerMike Gelfand <mikedld@mikedld.com>
Mon, 30 Oct 2017 07:23:18 +0000 (10:23 +0300)
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).

libtransmission/file-posix.c

index b857be514df1863941e541f376bed35bb270615a..a63b438d6eff9bcef529075ab4493337d75e6bd7 100644 (file)
@@ -138,81 +138,117 @@ static void set_file_for_single_pass(tr_sys_file_t handle)
 
 #ifndef HAVE_MKDIRP
 
-static bool create_path(char const* path_in, int permissions, tr_error** error)
+static bool create_path_require_dir(char const* path, tr_error** error)
 {
-    char* p;
-    char* pp;
-    bool done;
-    int tmperr;
-    int rv;
     struct stat sb;
-    char* path;
 
+    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)
+{
     /* 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
+        --path_end;
+    }
+
+    char* pp;
+    bool ret = false;
+    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';
+
+        ret = mkdir(path, permissions) != -1;
+
+        if (ret)
         {
-            *p = '\0';
+            break;
         }
 
-        tmperr = errno;
-        rv = stat(path, &sb);
-        errno = tmperr;
-
-        if (rv)
+        if (errno == EEXIST)
         {
-            tr_error* my_error = NULL;
+            ret = create_path_require_dir(path, &my_error);
 
-            /* Folder doesn't exist yet */
-            if (!tr_sys_dir_create(path, 0, permissions, &my_error))
+            if (ret)
             {
-                tr_logAddError(_("Couldn't create \"%1$s\": %2$s"), path, my_error->message);
-                tr_free(path);
-                tr_error_propagate(error, &my_error);
-                return false;
+                break;
             }
+
+            goto failure;
         }
-        else if ((sb.st_mode & S_IFMT) != S_IFDIR)
+
+        if (errno != ENOENT)
         {
-            /* 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;
+            set_system_error(&my_error, errno);
+            goto failure;
         }
+    }
+
+    if (ret && pp == path_end)
+    {
+        goto cleanup;
+    }
 
-        if (done)
+    /* 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);
 
-        *p = TR_PATH_DELIMITER;
-        p++;
-        pp = p;
+    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