Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 17 May 2008 10:53:00 GMT
From:      Timo Sirainen <tss@iki.fi>
To:        freebsd-gnats-submit@FreeBSD.org
Subject:   kern/123755: NFS: fstat() fails to return ESTALE with rename()d files
Message-ID:  <200805171053.m4HAr0Yi033843@www.freebsd.org>
Resent-Message-ID: <200805171100.m4HB05p2091927@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         123755
>Category:       kern
>Synopsis:       NFS: fstat() fails to return ESTALE with rename()d files
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sat May 17 11:00:05 UTC 2008
>Closed-Date:
>Last-Modified:
>Originator:     Timo Sirainen
>Release:        6.2-RELEASE and 7.0-STABLE
>Organization:
>Environment:
amd64
>Description:
I have a file that gets updated by writing to a temp file and then being rename()d over. In another process I want to know if the file got replaced so I can reopen and read the updated contents. So I do this check by:

1. Flush NFS caches (not really relevant)
2. stat(file, &st1)
3. fstat(opened_file_fd, &st2)
 - if it failed with ESTALE, it means the file was replaced
4. if (st1.st_ino != st2.st_ino) then file was replaced

The problem with FreeBSD is that both stats successfully return the same inode even if the file has been replaced already. This is probably because the old file has been deleted from the server and the inode has been reused for the new file. But in that condition the fstat() in 3. check should fail with ESTALE, which it doesn't in my rename() tests (but it does in my unlink() tests, so it's not completely broken).

>How-To-Repeat:
1. Compile the attached test program
2. Run it without any parameters on a FreeBSD NFS client
3. Run it with some parameters on either another NFS client or on the NFS server (doesn't matter which OS)
4. See that with FreeBSD it immediately starts reporting "inodes match, sizes don't" errors and "errors" counter never increases from zero (no commands fail with ESTALE).

If the same program is run on Linux NFS client it doesn't give size mismatch errors and error counter increases, as expected. (I'm pretty sure Solaris NFS client also works, but I couldn't verify it right now.)
>Fix:


Patch attached with submission follows:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

static int myfstat(int fd, struct stat *st)
{
	if (fstat(fd, st) == 0)
		return 0;
	if (errno != ESTALE)
		perror("fstat()");
	return -1;
}

int main(int argc, char *argv[], char **envp)
{
	struct stat st1, st2, st3;
	const char *path;
	int fd, errors = 0, ok = 0;

	path = argc == 1 ? "foo.1" : "foo.2";
	for (;;) {
		if ((errors + ok) % 10 == 0)
			printf("errors %d, ok %d\n", errors, ok);
		fd = creat(path, 0600);
		if (fd == -1) perror("creat()");
		if (argc == 1)
			write(fd, "a", 1);
		else
			write(fd, "bb", 2);
		if (fsync(fd) < 0) perror("fsync()");
		if (rename(path, "foo") < 0) perror("rename()");

		usleep(100 * (rand() % 10));

		if (myfstat(fd, &st1) < 0 ||
		    stat("foo", &st2) < 0 ||
		    myfstat(fd, &st3) < 0)
			errors++;
		else {
			ok++;
			if (st1.st_ino == st2.st_ino &&
			    st1.st_size == st3.st_size &&
			    st1.st_size != st2.st_size) {
				printf("inodes match, sizes don't\n");
			}
		}
		close(fd);
	}
	return 0;
}


>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200805171053.m4HAr0Yi033843>