#define _REENTRANT
#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE

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

/*
 * Program that reproduces a bug with cifs on linux-2.6.32.8
 * This program writes two copies of random data blocks to two files.
 * If one file is written to the local disk and the other on a cifs 
 * mounted directory, then there is a very high risk of corruption on
 * the file written to the cifs mounted directory. We expect the two
 * files to be strictly identical at the end, but this program
 * demonstrates that they sometimes differ. If may be necessary to
 * run this program multiples times to get the bug, but in general it's
 * quite frequent and it does not take a lot of time to reproduce it.
 * This problem only happens when the files are locked using
 * lockf(fd, F_LOCK, 0). If we don't lock the files, the corruption
 * on cifs disappears and the two files are identical.
 * 
 * Configuration:
 *   cifs client: linux-2.6.32.8
 *   cifs server: windows xp sp3
 * 
 * How to compile this program:
 *   gcc -o bug-cifs bug-cifs.c -Wall
 * 
 * How to reproduce the problem:
 *   mount command: mount -t cifs //winxp/sharename /mnt/cifs
 *   command line: ./bug-cifs /mnt/cifs/file1 /var/tmp/file2
 * 
 * Other information:
 *   error in syslog: "CIFS VFS: Write2 ret -13, wrote 0"
 * 
 */

#define LOCK_THE_FILES /* remove that line to fix the problem */

#define max(a,b)          ((a>b)?(a):(b))
#define MAX_BLOCKSIZE     2*1024*1024

int main(int argc, char *argv[])
{
    char filepath1[PATH_MAX];
    char filepath2[PATH_MAX];
    struct stat stat1, stat2;
    long long pos;
    char *block;
    long cursize=0;
    long blockno=0;
    int fd1, fd2;
    long lres, i;
    char c1, c2;
    bool identical;

    // get two filenames from the command line
    if (argc!=3)
    {
        printf("syntax: %s <local-file> <remote-file>\n", argv[0]);
        return -1;
    }

    snprintf(filepath1, PATH_MAX, "%s", argv[1]);
    snprintf(filepath2, PATH_MAX, "%s", argv[2]);

    // allocate memory and initialize buffer with random data
    if ((block=malloc(MAX_BLOCKSIZE))==NULL)
    {
        printf("malloc(%ld) failed\n", (long)MAX_BLOCKSIZE);
        return -1;
    }

    for (i=0; i<sizeof(MAX_BLOCKSIZE); i++)
        block[i]=i%128;

    // open files
    fd1=open64(filepath1, O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if (fd1 < 0)
    {
        printf("cannot create file1 %s\n", filepath1);
        return -1;
    }

    fd2=open64(filepath2, O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if (fd2 < 0)
    {
        printf("cannot create file2 %s\n", filepath2);
        return -1;
    }

    // lock the two files
#ifdef LOCK_THE_FILES
    if (lockf(fd1, F_LOCK, 0)!=0)
    {
        printf("Cannot lock file1\n");
        return -1;
    }

    if (lockf(fd2, F_LOCK, 0)!=0)
    {
        printf("Cannot lock file2\n");
        return -1;
    }
#endif

    // write random identical blocks to these two files
    drand48();
    for (blockno=0; blockno<64; blockno++)
    {
        cursize=max(1024, ((float)MAX_BLOCKSIZE)*drand48());

        if ((lres=write(fd1, block, cursize))!=cursize)
        {
            printf("write(fd1=%d, size=%ld)=%ld failed on file1 at blockno=%ld\n", fd1, (long)cursize, lres, blockno);
            return -1;
        }
        
        if ((lres=write(fd2, block, cursize))!=cursize)
        {  
            printf("write(fd2=%d, size=%ld)=%ld failed on file2 at blockno=%ld\n", fd2, (long)cursize, lres, blockno);
            return -1;
        }
        
        sleep(1);
    }

    // unlock the files
#ifdef LOCK_THE_FILES
    lockf(fd1, F_ULOCK, 0);
    lockf(fd2, F_ULOCK, 0);
#endif

    // free ressources
    free(block);

    fsync(fd1);
    fsync(fd2);

    close(fd1);
    close(fd2);

    // reopen files as read-only
    fd1=open64(filepath1, O_RDONLY|O_LARGEFILE);
    if (fd1 < 0)
    {
        printf("cannot open file1 %s\n", filepath1);
        return -1;
    }

    fd2=open64(filepath2, O_RDONLY|O_LARGEFILE);
    if (fd2 < 0)
    {
        printf("cannot open file2 %s\n", filepath2);
        return -1;
    }

    // compare the two files
    if (fstat(fd1, &stat1)!=0 || fstat(fd2, &stat2)!=0)
    {
        printf("cannot fstat files\n");
        return -1;
    }

    identical=true;
    
    if (stat1.st_size != stat2.st_size)
    {
        identical=false;
        printf("CHECK: different sizes: file1=%lld and file2=%lld\n", (long long)stat1.st_size, (long long)stat2.st_size);
    }

    for (pos=0; pos<stat1.st_size; pos++)
    {
        if (read(fd1, &c1, 1)!=1 || read(fd2, &c2, 1)!=1)
        {   
            printf("verification: cannot read files\n");
            return -1;
        }
        
        if (c1!=c2)
        {
            printf("verification: found different bytes at offset=%lld\n", (long long)pos);
            identical=false;
            break;
        }
    }

    // close files
    close(fd1);
    close(fd2);

    // print final result: has the file been corrupt ?
    printf ("RESULT: %s\n", identical?"OK":"CORRUPTION");

    return 0;
}
