Question

Consider this code:

public static function removeDir($src)
{
    if (is_dir($src)) {
        $dir = @opendir($src);
        if ($dir === false)
            return;
        while(($file = readdir($dir)) !== false) {
            if ($file != '.' && $file != '..') {
                $path = $src . DIRECTORY_SEPARATOR . $file;
                if (is_dir($path)) {
                    self::removeDir($path);
                } else {
                    @unlink($path);
                }
            }
        }
        closedir($dir);
        @rmdir($src);
    }
}

This will remove a directory. But if unlink fails or opendir fails on any subdirectory, the directory will be left with some content.

I want either everything deleted, or nothing deleted. I'm thinking of copying the directory before removal and if anything fails, restoring the copy. But maybe there's a better way - like locking the files or something similar?

Was it helpful?

Solution

I general I would confirm the comment:

"Copy it, delete it, copy back if deleted else throw deleting message fail..." – We0

However let's take some side considerations:

Trying to implement a transaction save file deletion indicates that you want to allow competing file locks on the same set of files. Transaction handling is usually the most 'expensive' way to ensure consistency. This holds true even if php would have any kind of testdelete available, because you would need to testdelete everything in a first run and then do a second loop which costs time (and where you are in danger that something changed on your file system in the meanwhile). There are other options:

  1. Try to isolate what really needs to be transaction save and handle those data accesses in databases. Eg. MySQL/InnoDB supports all the nitty gritty details of transaction handling
  2. Define and implement dedicated 'write/lock ownership'. So you have folders A and B with sub items and your php is allowed to lock files in A and some other process is allowed to lock files in B. Both your php and the other process are allowed to read A and B. This gets tricky on files, because a file read causes a lock as well which lasts the longer the bigger the files are. So on file basis you probably need to enrich this with file size limits, torance periods and so on.
  3. Define and implement dedicated access time frames. Eg. All files can be used within the week, but you have a maintenance time frame at sunday night which can also run deletions and therefore requires lock free environments.

Right, let's say my reasoning was not frightening enough :) - and you implement a transaction save file deletion anyway - your routine can be implemented this way:

  1. backup all files
  2. if the backup fails you could try a second, third, fourth time (this is an implementation decision)
  3. if there is no successful backup, full stop
  4. run your deletion process, two implementation options (in any way you need to log the files you deleted successfully):
    • always run through fully, and document all errors (this can be returned to the user later on as homework task list, however potentially runs long)
    • run through and stop at the first error
  5. if the deletion was the successful, all fine/full stop; if not proceed with rolling back
  6. copy back only previously successful deleted file from the archive (ONLY THEM!)
  7. Wipe out your backup

This then is only transaction save on file level. It does NOT handle the case where somebody changes permissions on folders in between step 5 and 6.

OTHER TIPS

Or you could try to just rename/move the directory to something like /tmp/, it succeeds or doesnt - but the files are not gone. Even if another process would have an open handle, the move should be ok. The files will be gone some time later when the tmp folder is emptied.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top