I'm busy writing a module based upon File::Copy, to copy/move a file to a different location, but which avoids overwriting an exisitng file: if a file of the desired name exists, it'll look for a variation of the file name, involving a count number, that isn't taken yet. Think of how Windows Explorer, if you copy/paste a file, will not replace an existing file, but instead, uses a derived name.

I'm not sure what File::Copy will do in case a destination filename exists: will it replace the old file, or fail to copy? I've had a good look at the source already, and it looks to me like copy might fail if the destination file exists, at least on Windows; but move will overwrite it — if only because move will try to rename, and AFAIK that always replaces an existing file. But I'm not really willing to trust that to be the case for all platforms, and I'm not even planning on trying them all out and write platform-dependant code. I've already had enough troubles with API calls that behave differently on one and the same platform, but on different types of disks (NTFS vs. FAT). So that's a dead end.

I've already developed two strategies (to avoid race conditions), for what to do if you're sure a copy will fail, and what if a copy will overwrite an existing file. I'm using a sub (details not important now) that composes a filename out of the base name, the file extension and a counter number. For example, a file "landscape.jpg" could be copied to a destination "landscape(1).jpg". And "copy" is used as an example here, it could be any of File::Copy's copy/move.

Case 1: an existing file is replaced

I simply try to create the destination file in a mode that fails if the file already exists, and then simply overwrite the newly created file. The code looks like this:
use Fcntl qw(O_CREAT O_EXCL O_WRONLY); my $i; while(1) { my $dest = compose_name($base, $ext, $i++); if(sysopen my $fh, $dest, O_CREAT | O_EXCL | O_WRONLY) { close $fh; copy $source, $dest; last; } }

An extra caveat is that I've read that O_EXCL isn't reliable on NFS.

Case 2: copy fails if the destination exists

The idea is to just try to copy the file to a new name, until it succeeds. What is lacking is protection against different reasons for failure. The simplified code looks like:
my $i; while(1) { my $dest = compose_name($base, $ext, $i++); if(copy $source, $dest) { last; } }

I don't know what case I'm in, and I'd like to use common code for copy and for move. What I need is an algorithm that works reliably for either case. I've thought of the following:

use Fcntl qw(O_CREAT O_EXCL O_WRONLY); my $i; while(1) { my $dest = compose_name($base, $ext, $i++); if(sysopen my $fh, $dest, O_CREAT | O_EXCL | O_WRONLY) { close $fh; my $tempfile = generate_tempfilename($destdir); copy $source, $tempfile; rename $tempfile, $dest; # should replace file last; } }
but the problem remains: how to generate a unique temporary filename without clobbering other existing files? It could be that several scripts using the same module are trying to copy similarly named files to the same disk, and thus, we have a race condition. And there's still the problem of NFS.

So, I'm polling for ideas... how would you tackle this?


In reply to How to move/copy a file without overwriting an existing file by bart

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.