user3041398
user3041398

Reputation: 81

rmdir no such file directory error although directory exist

If use this function to remove a directory + all files inside.

function delete_files($target)
{
    if(is_dir($target))
    {
        $files = glob($target . '*', GLOB_MARK);
        foreach($files as $file)
        {
            delete_files($file);
        }
        rmdir($target);
    }
    elseif(is_file($target))
    {
        unlink($target);
    }
}
delete_files($directory);

But whenever I do this, I get this error message:

Warning: rmdir(directory/12) [function.rmdir]: No such file or directory in delete_files.php

"directory/12" is the correct name of the directory I wanted to delete. I don't understand why it says that it does not exist because it does! Weirdly though, even though I got the error message, the directory DID get deleted.

So I added a line of code print_n($files); before the for-loop and it game me two arrays -- one containing the directory ("directory/12") and the other containing all the files of the directory ("directory/12/01.gif", "directory/12/02.gif" etc). So I figured the directory must have gotten deleted in the for-loop and removed the line rmdir($target) and tried it again. This time, all the files within the directory got deleted but the directory itself remained.

So apparently, rmdir DOES indeed remove the directory correctly. But then, why does it give me the error message beforehand that it doesn't exist?

Upvotes: 1

Views: 4601

Answers (2)

Ilmari Karonen
Ilmari Karonen

Reputation: 50328

It will work if you append a slash to the directory name.

Explanation: When you initially call the function as delete_files("directory/12"), the parameters passed to the glob() call will look like this:

$files = glob("directory/12*", GLOB_MARK);

Assuming that you have no other files in directory/ with names beginning with 12, this will just return "directory/12/" (with a slash appended because of GLOB_MARK). The function will then recursively call itself with that parameter, resulting in the top-level directory being processed twice.

Of course, if you did happen to have some other file or directory named, say, directory/123, then it would also get deleted, which is presumably not what you want.


To fix this properly, you should make sure your function can properly handle directories even if they get passed in without a trailing slash. The simplest way to do that would be to always append the slash to directory names before globbing them, like this:

$files = glob($target . '/*');

However, note that this could still fail (albeit less destructively) if your directory happened to contain some files not matched by *, such as dotfiles, since they would not get deleted, causing the subsequent rmdir() to fail because the directory will not be empty.

A more robust solution would be to use scandir() instead of glob(), like this:

$files = array_diff( scandir($target), array('.', '..') );
foreach ($files as $file) {
    delete_files("$target/$file");
}

(The array_diff() is needed to eliminate the special . and .. directory entries, which would cause the code to recurse forever if they weren't excluded.)

One remaining potential failure mode is that this code will happily follow symlinks to directories and try to delete everything in the directory they point to (and then fail to remove the link itself, because rmdir() can't remove symlinks). To fix this issue, you'll probably want to replace the is_dir($target) test with !is_link($target) && is_dir($target).


All put together, the resulting code would look like this:

function delete_files($target)
{
    if(!is_link($target) && is_dir($target))
    {
        // it's a directory; recursively delete everything in it
        $files = array_diff( scandir($target), array('.', '..') );
        foreach($files as $file) {
            delete_files("$target/$file");
        }
        rmdir($target);
    }
    else
    {
        // probably a normal file or a symlink; either way, just unlink() it
        unlink($target);
    }
}
delete_files($directory);

Ps. See also How do I recursively delete a directory and its entire contents (files + sub dirs) in PHP?

Upvotes: 3

user3712744
user3712744

Reputation: 1

cause you call it twice the first time it works the second time it gives a error.

I can't prove it, but with recursive code like that it is the problem.

Upvotes: -1

Related Questions