Nasty perl rename bug

84 views
Skip to first unread message

Brian Matthews

unread,
Apr 29, 1990, 2:05:07 PM4/29/90
to
There is a problem with the rename system call emulation in perl (see
doio.c around line 268). To wit:

$ perl -v | head -2
$Header: perly.c,v 3.0.1.5 90/03/27 16:20:57 lwall Locked $
Patch level: 18
$ ls
$ echo >file1
$ ls
file1
$ perl -e 'rename ("file1", "file2")'
$ ls
file2
$ perl -e 'rename ("file2", "file2")'
$ ls
$

The rename emulation first unlinks the destination, but this is a Bad Thing
if the source and dest are the same.
--
Brian L. Matthews b...@6sceng.UUCP

Randal Schwartz

unread,
Apr 30, 1990, 5:03:34 PM4/30/90
to
In article <79...@jpl-devvax.JPL.NASA.GOV>, lwall@jpl-devvax (Larry Wall) writes:
| In article <19...@polari.UUCP> 6si...@polari.UUCP (Brian Matthews) writes:
| : The rename emulation first unlinks the destination, but this is a Bad Thing

| : if the source and dest are the same.
|
| Ouch. And it's rather difficult to do it right, since two differing strings
| may in fact refer to the same directory entry. Sigh...
|
| I'm gonna have to break apart each name into directory and filename,
| stat the two directories to see if they're the same directory, and then
| compare the filenames. Well, maybe the opposite order would be more
| efficient, since if the filenames differ there's no need to do the
| directory comparison.

Naaah.

In Perl:

sub safe_rename {
local($f1,$f2) = @_;
@f1 = stat($f1);
return undef unless @f1; # $! is set already, I hope
@f2 = stat($f2);
return rename($f1,$f2) unless @f2;
# both files exist... lessee if they are the same:
if ((shift(@f1) == shift(@f2)) && (shift(@f1) == shift(@f2))) {
# success... same device/inode
$! = 0;
return 1;
}
rename($f1,$f2);
}

If you want it in C, implement it. But, if you really need a safe
rename, try this.

$_="Just_another_Perl_hacker,";open(_,">$_");print<$_*>;unlink;
--
/=Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ==========\
| on contract to Intel's iWarp project, Beaverton, Oregon, USA, Sol III |
| mer...@iwarp.intel.com ...!any-MX-mailer-like-uunet!iwarp.intel.com!merlyn |
\=Cute Quote: "Welcome to Portland, Oregon, home of the California Raisins!"=/

Larry Wall

unread,
Apr 30, 1990, 3:44:35 PM4/30/90
to
In article <19...@polari.UUCP> 6si...@polari.UUCP (Brian Matthews) writes:
: The rename emulation first unlinks the destination, but this is a Bad Thing

: if the source and dest are the same.

Ouch. And it's rather difficult to do it right, since two differing strings


may in fact refer to the same directory entry. Sigh...

I'm gonna have to break apart each name into directory and filename,
stat the two directories to see if they're the same directory, and then
compare the filenames. Well, maybe the opposite order would be more
efficient, since if the filenames differ there's no need to do the
directory comparison.

Larry

Larry Wall

unread,
May 1, 1990, 11:36:32 AM5/1/90
to
In article <1990Apr30....@iwarp.intel.com> mer...@iwarp.intel.com (Randal Schwartz) writes:

Braakkkk. Next contestant...

You've just prevented me from saying

link('foo', 'bar');
...
&safe_rename('foo', 'bar');

At least, you've prevented that part of rename's semantics that says that
the name 'foo' won't exist any more.

You want to test whether the two names are the same directory entry, not
whether they're the same file.

Of course, if they are the same file already, and not the same directory
entry, you can do the rename merely by unlinking the first name. But you'd
still have to test to see if it's a different directory entry, or unlinking
the first name unlinks the second too.

I've already got the C routine written. It goes like this:

#ifndef RENAME
int
same_dirent(a,b)
char *a;
char *b;
{
char *fa = rindex(a,'/');
char *fb = rindex(b,'/');
struct stat tmpstatbuf1;
struct stat tmpstatbuf2;
#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif
char tmpbuf[MAXPATHLEN+1];

if (fa)
fa++;
else
fa = a;
if (fb)
fb++;
else
fb = b;
if (strNE(a,b))
return FALSE;
if (fa == a)
strcpy(tmpbuf,".")
else
strncpy(tmpbuf, a, fa - a);
if (stat(tmpbuf, &tmpstatbuf1) < 0)
return FALSE;
if (fb == b)
strcpy(tmpbuf,".")
else
strncpy(tmpbuf, b, fb - b);
if (stat(tmpbuf, &tmpstatbuf2) < 0)
return FALSE;
return tmpstatbuf1.st_dev == tmpstatbuf2.st_dev &&
tmpstatbuf1.st_ino == tmpstatbuf2.st_ino;
}
#endif /* !RENAME */

It has a little problem in that

/some/path/name/

doesn't compare to

/some/path/name/.

but I consider that a minor problem, on the GIGO principle.

Besides, REAL computers have a rename() system call. :-)

Larry

Brian Matthews

unread,
May 1, 1990, 10:50:59 PM5/1/90
to
In article <79...@jpl-devvax.JPL.NASA.GOV> lw...@jpl-devvax.JPL.NASA.GOV (Larry Wall) writes:
|In article <19...@polari.UUCP> 6si...@polari.UUCP (Brian Matthews) writes:
|: The rename emulation first unlinks the destination, but this is a Bad Thing
|: if the source and dest are the same.
|Ouch. And it's rather difficult to do it right, since two differing strings
|may in fact refer to the same directory entry. Sigh...

Let the kernel do the hard stuff :-) Just stat the two files, and do
the obvious. In fact, it's probably fairly simple to do in perl
itself, but it still needs to be done in C someday.

Chip Salzenberg

unread,
May 1, 1990, 6:08:11 PM5/1/90
to
According to mer...@iwarp.intel.com (Randal Schwartz):
>According to lwall@jpl-devvax (Larry Wall):

>| I'm gonna have to break apart each name into directory and filename,
>| stat the two directories to see if they're the same directory, and then
>| compare the filenames.
>
>Naaah.

Yes, he will, Randal. There's no way I'm going to put "sub
safe_rename" in each and every Perl script. A Perl implementation in
which "rename($from, $to)" sometimes clobbers $from is buggy.

We should all be thankful (we are thankful, right?) that Larry is so
quick to provide bug fixes instead of workarounds.
--
Chip Salzenberg at ComDev/TCT <chip%t...@ateng.com>, <uunet!ateng!tct!chip>

Reply all
Reply to author
Forward
0 new messages