AvianFlu / ncp

Asynchronous recursive file copying with Node.js.
MIT License
681 stars 103 forks source link

Another race condition when 2 calls copying files to the same path #145

Open Soulike opened 2 years ago

Soulike commented 2 years ago

Before copying a file, ncp first checks whether the target file exists. If it exists (writable is false), ncp will delete it and start copying.

https://github.com/AvianFlu/ncp/blob/6820b0fbe3f7400fdb283c741ef88b6d8e2d4994/lib/ncp.js#L89-L97

But the deleted file may be the file being copied by another ncp call at the same time, causing strange behavior.

https://github.com/AvianFlu/ncp/blob/6820b0fbe3f7400fdb283c741ef88b6d8e2d4994/lib/ncp.js#L113-L126

The test case to reproduce the bug is similar to #144 but with a slight difference:

const hash1 = crypto.createHash('sha1');
const hash2 = crypto.createHash('sha1');

const destination = path.join(os.tmpdir(), 'bigFile');
const bigFile1 = '/path/to/file1';
const bigFile2 = '/path/to/file2';

const file1Content = fs.readFileSync(bigFile1);
const original1Hash = hash1.update(file1Content).digest();
const file2Content = fs.readFileSync(bigFile2);
const original2Hash = hash2.update(file2Content).digest();

fs.rmSync(destination, {recursive: true, force: true});

// operation A
ncp(bigFile1, destination, function (err)
{
    if (err)
    {
        return console.error(err);
    }
    const hash = crypto.createHash('sha1');
    const copiedBigFile = destination;
    const copiedFileContext = fs.readFileSync(copiedBigFile);
    const copiedHash = hash.update(copiedFileContext).digest();

    // AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value
    assert.ok(original1Hash.equals(copiedHash));    // WTF, destination is not bigFile1!
});

setTimeout(() =>
{
    ncp(bigFile2, destination, function (err)
    {
        if (err)
        {
            return console.error(err);
        }

        // deal with the bug in #143
        setTimeout(() =>
        {
            const hash = crypto.createHash('sha1');
            const copiedBigFile = destination;
            const copiedFileContext = fs.readFileSync(copiedBigFile);
            const copiedHash = hash.update(copiedFileContext).digest();

            assert.ok(original2Hash.equals(copiedHash));
        }, 1000)
    });
}, 100);  // a small delay here