diff --git a/server/libs/fsExtra/move/move-sync.js b/server/libs/fsExtra/move/move-sync.js index 84533664e..b8e7e211e 100644 --- a/server/libs/fsExtra/move/move-sync.js +++ b/server/libs/fsExtra/move/move-sync.js @@ -37,7 +37,7 @@ function rename (src, dest, overwrite) { try { fs.renameSync(src, dest) } catch (err) { - if (err.code !== 'EXDEV') throw err + if (err.code !== 'EXDEV' && err.code !== 'EPERM') throw err return moveAcrossDevice(src, dest, overwrite) } } diff --git a/server/libs/fsExtra/move/move.js b/server/libs/fsExtra/move/move.js index 7dc6ecd1f..3a6dc1a06 100644 --- a/server/libs/fsExtra/move/move.js +++ b/server/libs/fsExtra/move/move.js @@ -56,7 +56,7 @@ function doRename (src, dest, overwrite, isChangingCase, cb) { function rename (src, dest, overwrite, cb) { fs.rename(src, dest, err => { if (!err) return cb() - if (err.code !== 'EXDEV') return cb(err) + if (err.code !== 'EXDEV' && err.code !== 'EPERM') return cb(err) return moveAcrossDevice(src, dest, overwrite, cb) }) } diff --git a/test/server/fsExtraCopy.test.js b/test/server/fsExtraCopy.test.js index 39455f630..b355694c7 100644 --- a/test/server/fsExtraCopy.test.js +++ b/test/server/fsExtraCopy.test.js @@ -66,6 +66,49 @@ describe('fsExtra copy fallback', () => { expect(fs.readFileSync(dest).equals(contents)).to.be.true }) + it('moves over an existing file when asynchronous rename and copyFile are denied', async () => { + fs.writeFileSync(dest, 'old database') + sinon.stub(fs, 'rename').callsFake((src, target, callback) => { + callback(Object.assign(new Error('rename denied'), { code: 'EPERM' })) + }) + sinon.stub(fs, 'copyFile').callsFake((src, target, callback) => { + callback(Object.assign(new Error('copyfile denied'), { code: 'EPERM' })) + }) + + await fsExtra.move(source, dest, { overwrite: true }) + + expect(fs.existsSync(source)).to.be.false + expect(fs.readFileSync(dest).equals(contents)).to.be.true + }) + + it('moves over an existing file when synchronous rename and copyFile are denied', () => { + fs.writeFileSync(dest, 'old database') + sinon.stub(fs, 'renameSync').throws(Object.assign(new Error('rename denied'), { code: 'EPERM' })) + sinon.stub(fs, 'copyFileSync').throws(Object.assign(new Error('copyfile denied'), { code: 'EPERM' })) + + fsExtra.moveSync(source, dest, { overwrite: true }) + + expect(fs.existsSync(source)).to.be.false + expect(fs.readFileSync(dest).equals(contents)).to.be.true + }) + + it('keeps the source when a move cannot copy it', async () => { + sinon.stub(fs, 'rename').callsFake((src, target, callback) => { + callback(Object.assign(new Error('rename denied'), { code: 'EPERM' })) + }) + sinon.stub(fs, 'copyFile').callsFake((src, target, callback) => { + callback(Object.assign(new Error('read failed'), { code: 'EIO' })) + }) + + try { + await fsExtra.move(source, dest) + expect.fail('Expected move to fail') + } catch (error) { + expect(error.code).to.equal('EIO') + } + expect(fs.readFileSync(source).equals(contents)).to.be.true + }) + it('propagates unrelated copy errors', async () => { sinon.stub(fs, 'copyFile').callsFake((src, target, callback) => { callback(Object.assign(new Error('file missing'), { code: 'ENOENT' }))