tags:

views:

2729

answers:

3

When you use the PHP copy function, the operation blindly copies over the destination file, even if it already exists. How do you copy a file safely, only performing the copy if there is no existing file?

+3  A: 

I think you answered your own question - check to make sure the destination file exists before performing the copy. If the file exists, skip the copy.

Update: I see you really did answer your own question. You mention race conditions, but if you do find that the file already exists, how do you know that:

  • the file that is already there is really the one you want to copy
  • the other process copying the file has completed its job (the file data is all there)
  • the other process copying the file isn't going to fail (and leave an incomplete file, or delete the new file)

I think you should consider these questions when designing a solution to your problem.

Greg Hewgill
+5  A: 

The obvious solution would be to call file_exists to check to see if the file exists, but doing that could cause a race condition. There is always the possibility that the other file will be created in between when you call file_exists and when you call copy. The only safe way to check if the file exists is to use fopen.

When you call fopen, set the mode to 'x'. This tells fopen to create the file, but only if it doesn't exist. If it exists, fopen will fail, and you'll know that you couldn't create the file. If it succeeds, you will have a created a file at the destination that you can safely copy over. Sample code is below:

// The PHP copy function blindly copies over existing files.  We don't wish
// this to happen, so we have to perform the copy a bit differently.  The
// only safe way to ensure we don't overwrite an existing file is to call
// fopen in create-only mode (mode 'x').  If it succeeds, the file did not
// exist before, and we've successfully created it, meaning we own the
// file.  After that, we can safely copy over our own file.

$filename = 'sourcefile.txt'
$copyname = 'sourcefile_copy.txt'
if ($file = @fopen($copyname, 'x')) {
    // We've successfully created a file, so it's ours.  We'll close
    // our handle.
    if (!@fclose($file)) {
        // There was some problem with our file handle.
        return false;
    }

    // Now we copy over the file we created.
    if (!@copy($filename, $copyname)) {
        // The copy failed, even though we own the file, so we'll clean
        // up by itrying to remove the file and report failure.
        unlink($copyname);
        return false;
    }

    return true;
}
Douglas Mayle
You still have a race condition. But you're on the right track--if the fopen succeeds, use fwrite() to perform the copy, and unlink the source file when complete.
Nathan Strong
A: 

Try using the link() function instead of copy().

function safe_copy($src, $dest) {
    if (link($src, $dest)) {
        // Link succeeded, remove old name
        unlink($filename);
        return true;
    } else {
        // Link failed; filesystem has not been altered
        return false;
    }
}

Unfortunately, this will not work on Windows.

Ben Blank