Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 19 Feb 97 15:13 CST
From:      gordon@sneaky.lerctr.org
To:        FreeBSD-gnats-submit@freebsd.org
Subject:   kern/2774: NFS blowing holes in files.
Message-ID:  <m0vxJKl-000109C@hammy.lonestar.org>
Resent-Message-ID: <199702192130.NAA20186@freefall.freebsd.org>

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

>Number:         2774
>Category:       kern
>Synopsis:       NFS client on 2.2-BETA blows holes in files
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Feb 19 13:30:02 PST 1997
>Last-Modified:
>Originator:     Gordon Burditt
>Organization:
>Release:        FreeBSD 2.2-BETA
>Environment:

	

Machine G: FreeBSD 2.2-BETA, Pentium-100, IDE Hard disk, NE2000-clone
Ethernet card, 32M

Machine H: FreeBSD 2.1.5-RELEASE, 486DX-33, IDE and SCSI hard disks,
NE200-clone Ethernet card, 16M

Both machines are set up as NFS servers and clients.


>Description:

	

The enclosed program, which does a very rapid cycle of
open file/read file header/compute seek offset/write record at (presumed)
end of file/rewrite header/close file/ repeat, succeeds in sticking
a large bunch of NUL characters into the file, even though it never
writes NULs (except for the record count in the header) and only writes
at what should be the current end of file.  If you take the open
and close out of the loop, it doesn't fail.

Server:		G (2.2-BETA)	H (2.1.5-RELEASE)
Client:
G (2.2-BETA)	FAIL		FAIL
H (2.1.5-RELEASE) PASS		PASS

I am presuming that NFS-mounting a partition on the server itself (at another
mount point) actually goes through NFS.  From the change in execution times, 
it appears this assumption is correct.  

In addition, the program runs fine on native filesystems on both
G and H.  The problem appears to be in using 2.2-BETA as a NFS client.

The characteristic that seems to distinguish this problem from everything
else which seems to work is rapid opening and closing of the same file.

>How-To-Repeat:

	<Code/input/activities to reproduce the problem (multiple lin

Extract the Makefile and x.c into an empty directory on a
NFS-mounted directory on a client running 2.2-BETA.  Run "make test".

The grep of the output of hexdump should show no output (no groups
of 4 or more nulls).  When it fails, it typically shows one lines, and 
typically offset range 20c0-3fff is filled with nulls instead of 
what was written.  If you define KEEPOPEN, which takes the open
and close outside the loop, the problem doesn't happen.  The test
runs the KEEPOPEN version first and then the regular one.
(Incidentally, if you put a fsync(fd) before the close, you often
wind up with TWO holes blown in the file.)  Ignore complaints
from make about the exit code of grep.

(Well, actually the starting offset of the nulls varies a bit.)

This program is a dumbed-down version of a real application, but
which still shows the bug.

Makefile:
________________________________________________________________________
x:	x.c
	$(CC) -o x x.c
y:	x.c
	cp x.c y.c
	$(CC) -DKEEPOPEN -o y y.c

test:	x y
	./y
	echo "Should see no grep output"
	-hexdump foo.dat | grep " 0000 0000"
	./x
	echo "Should see no grep output, usually see 1 line on failure"
	-hexdump foo.dat | grep " 0000 0000"
_________________________________________________________________________
x.c:
_________________________________________________________________________
# include <assert.h>
# include <stdio.h>
# include <string.h>
# include <fcntl.h>
# include <unistd.h>

struct header
{
    int             maxrec;
    char            x[124];
};

struct record
{
    char            filler[64];
};

int
main(int argc, char **argv)
{
    int             fd;
    int             ret;
    struct header   h;
    struct record   r;
    int             i;

    /* set up the file to initially contain a header */
    /* and 100 records */
    fd = open("foo.dat", O_RDWR | O_CREAT | O_TRUNC, 0666);
    assert(fd >= 0);

    memset(&r, 'R', sizeof(r));
    memset(&h, 'H', sizeof(h));
    h.maxrec = 100;

    ret = write(fd, &h, sizeof(h));
    assert(ret == sizeof(h));

    for (i = 0; i < 100; i++)
    {
	ret = write(fd, &r, sizeof(r));
	assert(ret == sizeof(r));
    }
    ret = close(fd);
    assert(ret == 0);

# ifdef KEEPOPEN
    /* open file */
    fd = open("foo.dat", O_RDWR, 0666);
    assert(fd >= 0);
# endif

    /* now add a bunch of records, using the header to */
    /* determine where the end-of-file is */
    for (i = 0; i < 200; i++)
    {
# ifndef KEEPOPEN
	/* open file */
	fd = open("foo.dat", O_RDWR, 0666);
	assert(fd >= 0);
# endif

	/* read header */
	ret = lseek(fd, 0L, 0);
	assert(ret != (off_t) - 1);
	ret = read(fd, &h, sizeof(h));
	assert(ret == sizeof(h));
	assert(h.maxrec == i + 100);

	/* add a record onto the end */
	ret = lseek(fd, sizeof(h) + (off_t) sizeof(r) * h.maxrec, 0);
	assert(ret != (off_t) - 1);
	memset(&r, "abcdefghijklmnopqrstuvwxyz"[i % 26], sizeof(r));
	ret = write(fd, &r, sizeof(r));
	assert(ret == sizeof(r));

	/* increment record count in header and write it back */
	h.maxrec++;
	ret = lseek(fd, 0L, 0);
	assert(ret != (off_t) - 1);
	ret = write(fd, &h, sizeof(h));
	assert(ret == sizeof(h));

# ifndef KEEPOPEN
	/* fsync(fd); here makes the problem worse */
	/* close the file */
	ret = close(fd);
	assert(ret == 0);
# endif
    }
# ifdef KEEPOPEN
    /* close the file */
    ret = close(fd);
    assert(ret == 0);
# endif
    exit(0);
}
_________________________________________________________________________

Typical output on failure:
./x
hexdump foo.dat | grep " 0000 0000"
00020c0 0000 0000 0000 0000 0000 0000 0000 0000

>Fix:
	
	

None known, but apparently avoiding rapid opening and closing of the
same file gets around it.
>Audit-Trail:
>Unformatted:



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