chownr.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. 'use strict'
  2. const fs = require('fs')
  3. const path = require('path')
  4. /* istanbul ignore next */
  5. const LCHOWN = fs.lchown ? 'lchown' : 'chown'
  6. /* istanbul ignore next */
  7. const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync'
  8. /* istanbul ignore next */
  9. const needEISDIRHandled = fs.lchown &&
  10. !process.version.match(/v1[1-9]+\./) &&
  11. !process.version.match(/v10\.[6-9]/)
  12. const lchownSync = (path, uid, gid) => {
  13. try {
  14. return fs[LCHOWNSYNC](path, uid, gid)
  15. } catch (er) {
  16. if (er.code !== 'ENOENT')
  17. throw er
  18. }
  19. }
  20. /* istanbul ignore next */
  21. const chownSync = (path, uid, gid) => {
  22. try {
  23. return fs.chownSync(path, uid, gid)
  24. } catch (er) {
  25. if (er.code !== 'ENOENT')
  26. throw er
  27. }
  28. }
  29. /* istanbul ignore next */
  30. const handleEISDIR =
  31. needEISDIRHandled ? (path, uid, gid, cb) => er => {
  32. // Node prior to v10 had a very questionable implementation of
  33. // fs.lchown, which would always try to call fs.open on a directory
  34. // Fall back to fs.chown in those cases.
  35. if (!er || er.code !== 'EISDIR')
  36. cb(er)
  37. else
  38. fs.chown(path, uid, gid, cb)
  39. }
  40. : (_, __, ___, cb) => cb
  41. /* istanbul ignore next */
  42. const handleEISDirSync =
  43. needEISDIRHandled ? (path, uid, gid) => {
  44. try {
  45. return lchownSync(path, uid, gid)
  46. } catch (er) {
  47. if (er.code !== 'EISDIR')
  48. throw er
  49. chownSync(path, uid, gid)
  50. }
  51. }
  52. : (path, uid, gid) => lchownSync(path, uid, gid)
  53. // fs.readdir could only accept an options object as of node v6
  54. const nodeVersion = process.version
  55. let readdir = (path, options, cb) => fs.readdir(path, options, cb)
  56. let readdirSync = (path, options) => fs.readdirSync(path, options)
  57. /* istanbul ignore next */
  58. if (/^v4\./.test(nodeVersion))
  59. readdir = (path, options, cb) => fs.readdir(path, cb)
  60. const chown = (cpath, uid, gid, cb) => {
  61. fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, er => {
  62. // Skip ENOENT error
  63. cb(er && er.code !== 'ENOENT' ? er : null)
  64. }))
  65. }
  66. const chownrKid = (p, child, uid, gid, cb) => {
  67. if (typeof child === 'string')
  68. return fs.lstat(path.resolve(p, child), (er, stats) => {
  69. // Skip ENOENT error
  70. if (er)
  71. return cb(er.code !== 'ENOENT' ? er : null)
  72. stats.name = child
  73. chownrKid(p, stats, uid, gid, cb)
  74. })
  75. if (child.isDirectory()) {
  76. chownr(path.resolve(p, child.name), uid, gid, er => {
  77. if (er)
  78. return cb(er)
  79. const cpath = path.resolve(p, child.name)
  80. chown(cpath, uid, gid, cb)
  81. })
  82. } else {
  83. const cpath = path.resolve(p, child.name)
  84. chown(cpath, uid, gid, cb)
  85. }
  86. }
  87. const chownr = (p, uid, gid, cb) => {
  88. readdir(p, { withFileTypes: true }, (er, children) => {
  89. // any error other than ENOTDIR or ENOTSUP means it's not readable,
  90. // or doesn't exist. give up.
  91. if (er) {
  92. if (er.code === 'ENOENT')
  93. return cb()
  94. else if (er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP')
  95. return cb(er)
  96. }
  97. if (er || !children.length)
  98. return chown(p, uid, gid, cb)
  99. let len = children.length
  100. let errState = null
  101. const then = er => {
  102. if (errState)
  103. return
  104. if (er)
  105. return cb(errState = er)
  106. if (-- len === 0)
  107. return chown(p, uid, gid, cb)
  108. }
  109. children.forEach(child => chownrKid(p, child, uid, gid, then))
  110. })
  111. }
  112. const chownrKidSync = (p, child, uid, gid) => {
  113. if (typeof child === 'string') {
  114. try {
  115. const stats = fs.lstatSync(path.resolve(p, child))
  116. stats.name = child
  117. child = stats
  118. } catch (er) {
  119. if (er.code === 'ENOENT')
  120. return
  121. else
  122. throw er
  123. }
  124. }
  125. if (child.isDirectory())
  126. chownrSync(path.resolve(p, child.name), uid, gid)
  127. handleEISDirSync(path.resolve(p, child.name), uid, gid)
  128. }
  129. const chownrSync = (p, uid, gid) => {
  130. let children
  131. try {
  132. children = readdirSync(p, { withFileTypes: true })
  133. } catch (er) {
  134. if (er.code === 'ENOENT')
  135. return
  136. else if (er.code === 'ENOTDIR' || er.code === 'ENOTSUP')
  137. return handleEISDirSync(p, uid, gid)
  138. else
  139. throw er
  140. }
  141. if (children && children.length)
  142. children.forEach(child => chownrKidSync(p, child, uid, gid))
  143. return handleEISDirSync(p, uid, gid)
  144. }
  145. module.exports = chownr
  146. chownr.sync = chownrSync