mmap() + write() bug

Jim Nance (jlnance@avanticorp.com)
Thu, 25 Sep 1997 19:39:32 -0400


Hello David,
I think I have found a bug or at least a wart in the kernel. I was
playing around with some ideas for how to efficiently delete the center
of a file by mmap()ing it into the address space, and then calling
write() back to the same file using the mmap()ed area as the input to
write(). This seems to work quite well, as does a similar process using
read(). I was actually quite impressed that this worked, so I decided to
see how it would work if I tried to duplicate a block in the center of a
file using a similar process. That did not work as well. For example,
lets say I have a file with 5 chars in it: "abcde" If I want to duplicate
the c character, I can do:

fd = open(file, O_RDWR);
ptr = mmap(0, stuff, fd, 0);
lseek(fd, 3, SEEK_SET);
write(fd, ptr+2, 3);

and this should produce a file which contains "abccde", and it does for
this case. If instead of a single character, each letter represents a
large block of characters, then I get a file that looks like "abcccc".

In case you are interested in this, I am including a test program to reproduce
it. Just run it w/o arguments, and do an "od -a test_file" after it runs.
If you put a break statement at the bottom of the for() loop, so that it just
creates a small file, you will get the expected results when you do the od.

Thanks,

Jim

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

int main(int ac, char **av)
{
int fifth;
int dir = 3;

if(ac==2 && !strcmp(av[1], "bw")) dir=1; else
if(ac==2 && !strcmp(av[1], "br")) dir=2; else
if(ac==2 && !strcmp(av[1], "fw")) dir=3; else
; /* this is not just dangling, it goes with the else */

printf("Testcase = %d\n", dir);

for(fifth=100; fifth<1024*1024; fifth *=2) {
char *ptr;
int size = 5*fifth;
int fd = open("test_file", O_TRUNC | O_CREAT | O_RDWR, 0666);

if(fd<0) {
fprintf(stderr,"Failed to open() file\n");
exit(1);
}

printf("Working on size=%d\n", size);
ftruncate(fd, size);
ptr = mmap(NULL, size, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);

if(ptr==(char*)-1) {
fprintf(stderr,"Failed to mmap() file\n");
exit(1);
}

/* Write an aaabbbccc pattern into the file */
memset(ptr+0*fifth, 'a', fifth);
memset(ptr+1*fifth, 'b', fifth);
memset(ptr+2*fifth, 'c', fifth);
memset(ptr+3*fifth, 'd', fifth);
memset(ptr+4*fifth, 'e', fifth);

switch(dir) {
/* Remove the ccc portion */
case 1:
lseek(fd, 2*fifth, SEEK_SET);
write(fd, ptr+3*fifth, 2*fifth);
ftruncate(fd, 4*fifth);
break;
/* Remove the ccc portion */
case 2:
lseek(fd, 3*fifth, SEEK_SET);
read(fd, ptr+2*fifth, 2*fifth);
ftruncate(fd, 4*fifth);
break;
/* Extend the ccc portion */
case 3:
lseek(fd, 3*fifth, SEEK_SET);
write(fd, ptr+2*fifth, 3*fifth);
break;
default:
fprintf(stderr,"Lost\n"); exit(1);
}
munmap(ptr, size);
close(fd);
}

return 0;
}