When calling with an absolute path, such as '/Users/joachim/bin/drupal_hooks/8', this function returns FALSE.

This is because the call to explode produces an array like this:

Array
(
    [0] =>
    [1] => Users
    [2] => joachim
    [3] => bin
    [4] => drupal_hooks
)

When the directory path is then built up from the pieces, the first one will be an empty string.

For that matter, this looks wrong:

    foreach ($components as $component) {
      $recursive_path .= $component;

because the exploding on DIRECTORY_SEPARATOR means that none of the pieces contain DIRECTORY_SEPARATOR. Therefore, the joining will just smush the pieces together without slashes.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

joachim’s picture

Status: Active » Needs review
Issue tags: +Needs tests
FileSize
572 bytes

Fix is very simple, but probably needs tests.

Status: Needs review » Needs work
Issue tags: -Needs tests

The last submitted patch, 2043771.drupal.file-drupal_mkdir-recursive.patch, failed testing.

slashrsm’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work
Issue tags: +Needs tests

The last submitted patch, 2043771.drupal.file-drupal_mkdir-recursive.patch, failed testing.

jbrown’s picture

Title: drupal_mkdir() doesn't work with absolute paths or recursive » drupal_mkdir() can't create an absolute path recursively
Priority: Major » Normal
Status: Needs work » Needs review
FileSize
941 bytes

Thanks for finding this @joachim!

DIRECTORY_SEPARATOR is appended at the end of the loop:

$recursive_path .= DIRECTORY_SEPARATOR;

How about this approach?

joachim’s picture

Status: Needs review » Needs work

Oh of course -- my patch will fail for relative paths :(

Your approach makes more sense. Might need a bit more commenting to make it easier to understand what it's doing.

+++ b/core/includes/file.inc
@@ -1483,8 +1483,17 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
+      $recursive_path = '/';

Shouldn't this be comparing DIRECTORY_SEPARATOR rather than assuming it's a / ?

+++ b/core/includes/file.inc
@@ -1483,8 +1483,17 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
+      array_shift($components);

I'm confused by this... is this because with an absolute path, the first item of the array is a ''?

jbrown’s picture

Status: Needs work » Needs review
FileSize
643 bytes
1006 bytes

Shouldn't this be comparing DIRECTORY_SEPARATOR rather than assuming it's a / ?

I actually want to get rid of DIRECTORY_SEPARATOR in this function: https://drupal.org/node/1068266#comment-7118718 but I put it back into the patch for consistency.

I'm confused by this... is this because with an absolute path, the first item of the array is a ''?

Yeah - if an absolute path is exploded then first string in the array empty and we need to ignore it.

slashrsm’s picture

Looks good to me. Can we call this RTBC?

joachim’s picture

Let's see if I can write a test for this...

Here's two patches, one with just a test, and one which is #7 plus the test.

slashrsm’s picture

Status: Needs review » Needs work
+++ b/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.phpundefined
@@ -50,6 +50,13 @@ function testFileCheckLocalDirectoryHandling() {
     $this->assertDirectoryPermissions($directory, $old_mode);
+
+    // Check creating a directory using an absolute path.
+    $absolute = $this->randomName();
+    // Create it within the local files directory anyway, as we know we can
+    // write there.
+    $absolute_path = realpath($directory) . DIRECTORY_SEPARATOR . $absolute;

This only creates one level directory. Shouldn't we also test recursive stuff (multi-level directories)?

jbrown’s picture

Status: Needs work » Needs review
FileSize
1.95 KB
987 bytes

Added an extra level to the directory created in the test.
Also, test descriptions are not suposed to be translated. The patch linked to in #7 fixes this.

slashrsm’s picture

Looks good to me. Let's wait for testbot.

slashrsm’s picture

Status: Needs review » Reviewed & tested by the community

It looks good, it is green and it is fairly simple patch. I'll set this to RTBC. Anybody feel free to change that if any other objections appear.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Nice find...

+++ b/core/includes/file.incundefined
@@ -1483,8 +1483,18 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
+    // Is the filepath relative or absolute?

I think this comment should be a little more verbose and explain why $components[0] is == '' for absolute paths.

+++ b/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.phpundefined
@@ -50,6 +50,10 @@ function testFileCheckLocalDirectoryHandling() {
+    // Check creating a directory using an absolute path.
+    $absolute_path = drupal_realpath($directory) . DIRECTORY_SEPARATOR . $this->randomName() . DIRECTORY_SEPARATOR . $this->randomName();
+    $this->assertTrue(drupal_mkdir($absolute_path, 0775, TRUE), 'No error reported when creating new absolute directories.', 'File');

I think we should also test that the directory actually exists... not just that no errors where reported.

jbrown’s picture

jbrown’s picture

Status: Needs work » Needs review
slashrsm’s picture

+++ b/core/modules/system/lib/Drupal/system/Tests/File/DirectoryTest.phpundefined
@@ -50,6 +50,11 @@ function testFileCheckLocalDirectoryHandling() {
+    $this->assertDirectoryPermissions($absolute_path, 0775);
   }

Could we use assertFileExists() here?

https://api.drupal.org/api/drupal/core%21vendor%21phpunit%21phpunit%21PH...

jbrown’s picture

assertDirectoryPermissions() checks that it exists and also checks the perms. Surely that is better?

slashrsm’s picture

Hm... you're right. Patch looks fine to me then.

jbrown’s picture

RTBC?

slashrsm’s picture

Status: Needs review » Reviewed & tested by the community

RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 420f38c and pushed to 8.x. Thanks!

Automatically closed -- issue fixed for 2 weeks with no activity.

guedressel’s picture

Issue summary: View changes
Status: Closed (fixed) » Patch (to be ported)

Drupal 7 is also affected.
Backport of the fix would be nice.

joachim’s picture

Version: 8.x-dev » 7.x-dev
n3uronick’s picture

Status: Patch (to be ported) » Needs review
FileSize
3.55 KB

I created a backport for this based on d8 function.

slashrsm’s picture

Issue tags: +Amsterdam2014
slashrsm’s picture

Status: Needs review » Needs work
  1. +++ b/includes/file.inc
    @@ -2409,7 +2411,66 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
    +  }  ¶
    +  ¶
    +  // If recursive, create each missing component of the parent directory
    +  // individually and set the mode explicitly to override the umask.
    

    Trailing whitespace.

  2. +++ b/includes/file.inc
    @@ -2409,7 +2411,66 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) {
    +        // Not necessary to use drupal_chmod() as there is no scheme.
    +        if (!chmod($recursive_path, $mode)) {
    +          return FALSE;
    +        }
    

    Any downside if using drupal_chmod()?

  3. Let's add test coverage.

  • alexpott committed 420f38c on 8.3.x
    Issue #2043771 by jbrown, joachim: Fixed drupal_mkdir() can't create an...

  • alexpott committed 420f38c on 8.3.x
    Issue #2043771 by jbrown, joachim: Fixed drupal_mkdir() can't create an...
20th’s picture

Status: Needs work » Needs review
FileSize
1.03 KB

I am pretty sure that D7 is not affected. Uploading a test.

Status: Needs review » Needs work

The last submitted patch, 31: 2043771-31-test-only.patch, failed testing.

The last submitted patch, 31: 2043771-31-test-only.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
FileSize
1.07 KB

Fixing test.

20th’s picture

Status: Needs review » Fixed

And works as expected.

The behavior is obvious if you look at the actual code, and the check in the test is very simple, no need to even bother committing it to core. Changing to fixed.

joachim’s picture

Status: Fixed » Reviewed & tested by the community

I think you mean RTBC -- 'fixed' means the patch has been committed.

20th’s picture

No, I mean exactly that, fixed.

This issue was originally for D8. The patch for D8 was committed and marked as "needs backport" for D7. But D7 is not affected, it can create directories recursively using absolute paths just fine. So there is nothing that needs to be done neither for D8, nor D7; hence, fixed.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

@20th well if there's no test coverage then adding some always seems like a good idea. That said...

+++ b/modules/simpletest/tests/file.test
@@ -1208,6 +1208,22 @@ class FileUnmanagedDeleteTest extends FileTestCase {
+    $unique = tempnam(sys_get_temp_dir(), 'drupal_directory_test_');

This shouldn't be using the temporary directory for testing. It should be using the site's files directory because using the temp directory means that it won't be cleaned up if this test somehow fails.

20th’s picture

Status: Needs work » Needs review
FileSize
1.17 KB
1.51 KB

@alexpott: got it. How about like this then.

Status: Needs review » Needs work

The last submitted patch, 39: 2043771-39.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
FileSize
1.3 KB
1.23 KB

Strange, that passes locally.

Status: Needs review » Needs work

The last submitted patch, 41: 2043771-41.patch, failed testing.

The last submitted patch, 41: 2043771-41.patch, failed testing.

jonathan1055’s picture

I see you have a tesbot CI error which is the same as mine in #2837008: Move scheduler.test into /tests and move three test module into /tests/modules. I started getting this problem yesterday 17 Dec 23:00 GMT.

I've raised #2837015: CI problem with permission when testing 7.x patches but there has been no response yet.

20th’s picture

Thanks for looking in this problem, @jonathan1055!

Actually, I've had some failures because of "Permission denied" error even before Dec 16, but that might be completely unrelated. For example, check failure logs in #39. That patch passes all tests locally without problems.

jonathan1055’s picture

The test failures in #39 are not related. Those are due to your assertions which fail due to lack of permission for the user running the tests. The 'CI error' failures we are now getting are before the tests even start to run, it is a failure of permission when trying to apply the patch (I think). You get the grey 'CI error' against the patch, instead of the pink 'pass / fail' indicator.

Hopefully someone on the testbot team will see that issue and respond. Are you on IRC or do you have any other way to alert the team? It is a fairly important fault affecting all 7.x project patches (and even core patches)

jonathan1055’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 41: 2043771-41.patch, failed testing.

20th’s picture

Thanks a lot, @jonathan1055!

20th’s picture

Status: Needs work » Needs review
FileSize
1.44 KB
775 bytes

Status: Needs review » Needs work

The last submitted patch, 50: 2043771-50.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
FileSize
1.47 KB

Status: Needs review » Needs work

The last submitted patch, 52: 2043771-52.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
FileSize
1.47 KB

Status: Needs review » Needs work

The last submitted patch, 54: 2043771-54.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
FileSize
1.46 KB

Status: Needs review » Needs work

The last submitted patch, 56: 2043771-56.patch, failed testing.

Mixologic’s picture

Okay, I've fixed a couple things on the testbots permissions wise (they were kinda messy) and now realpath should not be returning FALSE.

OTOH, if you use realpath on the testbots, your going to get unexpected behavior due to the fact that they run out of a *symlink* directory called /checkout - realpath is going to get rid of that symlink and the directories are not going to match.

We run them via a symlink to simulate running drupal out of a subdirectory, However, this is less than ideal and we should probably build them in a subdirectory to begin with.

I've opened an issue here: #2838194: Run out of real subdirectory, not symlink

20th’s picture

Looks like the behavior of realpath() is still the same and the test is still failing, so we are not quitre there yet. :)

@Mixologic, thank you very much for your help! But this issue is so minor that it is probably not worth investing any more time into it. I will keep an eye on #2838194: Run out of real subdirectory, not symlink and report there if realpath() starts behaving normally, or I find another way to test this.

For context:
To test recursive directory creation, I try to get the absolute path of public:// directory using realpath() but it returns FALSE instead. My theory is, this happens because the testbot user does not have enough permissions on some of the directories. This is a documented behavior of the realpath() function:

The running script must have executable permissions on all directories in the hierarchy, otherwise realpath() will return FALSE.

  • alexpott committed 420f38c on 8.4.x
    Issue #2043771 by jbrown, joachim: Fixed drupal_mkdir() can't create an...

  • alexpott committed 420f38c on 8.4.x
    Issue #2043771 by jbrown, joachim: Fixed drupal_mkdir() can't create an...