move-file.js 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. 'use strict'
  2. const fs = require('fs')
  3. const util = require('util')
  4. const chmod = util.promisify(fs.chmod)
  5. const unlink = util.promisify(fs.unlink)
  6. const stat = util.promisify(fs.stat)
  7. const move = require('@npmcli/move-file')
  8. const pinflight = require('promise-inflight')
  9. module.exports = moveFile
  10. function moveFile (src, dest) {
  11. const isWindows = global.__CACACHE_TEST_FAKE_WINDOWS__ ||
  12. process.platform === 'win32'
  13. // This isn't quite an fs.rename -- the assumption is that
  14. // if `dest` already exists, and we get certain errors while
  15. // trying to move it, we should just not bother.
  16. //
  17. // In the case of cache corruption, users will receive an
  18. // EINTEGRITY error elsewhere, and can remove the offending
  19. // content their own way.
  20. //
  21. // Note that, as the name suggests, this strictly only supports file moves.
  22. return new Promise((resolve, reject) => {
  23. fs.link(src, dest, (err) => {
  24. if (err) {
  25. if (isWindows && err.code === 'EPERM') {
  26. // XXX This is a really weird way to handle this situation, as it
  27. // results in the src file being deleted even though the dest
  28. // might not exist. Since we pretty much always write files to
  29. // deterministic locations based on content hash, this is likely
  30. // ok (or at worst, just ends in a future cache miss). But it would
  31. // be worth investigating at some time in the future if this is
  32. // really what we want to do here.
  33. return resolve()
  34. } else if (err.code === 'EEXIST' || err.code === 'EBUSY') {
  35. // file already exists, so whatever
  36. return resolve()
  37. } else
  38. return reject(err)
  39. } else
  40. return resolve()
  41. })
  42. })
  43. .then(() => {
  44. // content should never change for any reason, so make it read-only
  45. return Promise.all([
  46. unlink(src),
  47. !isWindows && chmod(dest, '0444'),
  48. ])
  49. })
  50. .catch(() => {
  51. return pinflight('cacache-move-file:' + dest, () => {
  52. return stat(dest).catch((err) => {
  53. if (err.code !== 'ENOENT') {
  54. // Something else is wrong here. Bail bail bail
  55. throw err
  56. }
  57. // file doesn't already exist! let's try a rename -> copy fallback
  58. // only delete if it successfully copies
  59. return move(src, dest)
  60. })
  61. })
  62. })
  63. }