mkdir.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. 'use strict'
  2. // wrapper around mkdirp for tar's needs.
  3. // TODO: This should probably be a class, not functionally
  4. // passing around state in a gazillion args.
  5. const mkdirp = require('mkdirp')
  6. const fs = require('fs')
  7. const path = require('path')
  8. const chownr = require('chownr')
  9. const normPath = require('./normalize-windows-path.js')
  10. class SymlinkError extends Error {
  11. constructor (symlink, path) {
  12. super('Cannot extract through symbolic link')
  13. this.path = path
  14. this.symlink = symlink
  15. }
  16. get name () {
  17. return 'SylinkError'
  18. }
  19. }
  20. class CwdError extends Error {
  21. constructor (path, code) {
  22. super(code + ': Cannot cd into \'' + path + '\'')
  23. this.path = path
  24. this.code = code
  25. }
  26. get name () {
  27. return 'CwdError'
  28. }
  29. }
  30. const cGet = (cache, key) => cache.get(normPath(key))
  31. const cSet = (cache, key, val) => cache.set(normPath(key), val)
  32. const checkCwd = (dir, cb) => {
  33. fs.stat(dir, (er, st) => {
  34. if (er || !st.isDirectory()) {
  35. er = new CwdError(dir, er && er.code || 'ENOTDIR')
  36. }
  37. cb(er)
  38. })
  39. }
  40. module.exports = (dir, opt, cb) => {
  41. dir = normPath(dir)
  42. // if there's any overlap between mask and mode,
  43. // then we'll need an explicit chmod
  44. const umask = opt.umask
  45. const mode = opt.mode | 0o0700
  46. const needChmod = (mode & umask) !== 0
  47. const uid = opt.uid
  48. const gid = opt.gid
  49. const doChown = typeof uid === 'number' &&
  50. typeof gid === 'number' &&
  51. (uid !== opt.processUid || gid !== opt.processGid)
  52. const preserve = opt.preserve
  53. const unlink = opt.unlink
  54. const cache = opt.cache
  55. const cwd = normPath(opt.cwd)
  56. const done = (er, created) => {
  57. if (er) {
  58. cb(er)
  59. } else {
  60. cSet(cache, dir, true)
  61. if (created && doChown) {
  62. chownr(created, uid, gid, er => done(er))
  63. } else if (needChmod) {
  64. fs.chmod(dir, mode, cb)
  65. } else {
  66. cb()
  67. }
  68. }
  69. }
  70. if (cache && cGet(cache, dir) === true) {
  71. return done()
  72. }
  73. if (dir === cwd) {
  74. return checkCwd(dir, done)
  75. }
  76. if (preserve) {
  77. return mkdirp(dir, { mode }).then(made => done(null, made), done)
  78. }
  79. const sub = normPath(path.relative(cwd, dir))
  80. const parts = sub.split('/')
  81. mkdir_(cwd, parts, mode, cache, unlink, cwd, null, done)
  82. }
  83. const mkdir_ = (base, parts, mode, cache, unlink, cwd, created, cb) => {
  84. if (!parts.length) {
  85. return cb(null, created)
  86. }
  87. const p = parts.shift()
  88. const part = normPath(path.resolve(base + '/' + p))
  89. if (cGet(cache, part)) {
  90. return mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
  91. }
  92. fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb))
  93. }
  94. const onmkdir = (part, parts, mode, cache, unlink, cwd, created, cb) => er => {
  95. if (er) {
  96. fs.lstat(part, (statEr, st) => {
  97. if (statEr) {
  98. statEr.path = statEr.path && normPath(statEr.path)
  99. cb(statEr)
  100. } else if (st.isDirectory()) {
  101. mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
  102. } else if (unlink) {
  103. fs.unlink(part, er => {
  104. if (er) {
  105. return cb(er)
  106. }
  107. fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb))
  108. })
  109. } else if (st.isSymbolicLink()) {
  110. return cb(new SymlinkError(part, part + '/' + parts.join('/')))
  111. } else {
  112. cb(er)
  113. }
  114. })
  115. } else {
  116. created = created || part
  117. mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
  118. }
  119. }
  120. const checkCwdSync = dir => {
  121. let ok = false
  122. let code = 'ENOTDIR'
  123. try {
  124. ok = fs.statSync(dir).isDirectory()
  125. } catch (er) {
  126. code = er.code
  127. } finally {
  128. if (!ok) {
  129. throw new CwdError(dir, code)
  130. }
  131. }
  132. }
  133. module.exports.sync = (dir, opt) => {
  134. dir = normPath(dir)
  135. // if there's any overlap between mask and mode,
  136. // then we'll need an explicit chmod
  137. const umask = opt.umask
  138. const mode = opt.mode | 0o0700
  139. const needChmod = (mode & umask) !== 0
  140. const uid = opt.uid
  141. const gid = opt.gid
  142. const doChown = typeof uid === 'number' &&
  143. typeof gid === 'number' &&
  144. (uid !== opt.processUid || gid !== opt.processGid)
  145. const preserve = opt.preserve
  146. const unlink = opt.unlink
  147. const cache = opt.cache
  148. const cwd = normPath(opt.cwd)
  149. const done = (created) => {
  150. cSet(cache, dir, true)
  151. if (created && doChown) {
  152. chownr.sync(created, uid, gid)
  153. }
  154. if (needChmod) {
  155. fs.chmodSync(dir, mode)
  156. }
  157. }
  158. if (cache && cGet(cache, dir) === true) {
  159. return done()
  160. }
  161. if (dir === cwd) {
  162. checkCwdSync(cwd)
  163. return done()
  164. }
  165. if (preserve) {
  166. return done(mkdirp.sync(dir, mode))
  167. }
  168. const sub = normPath(path.relative(cwd, dir))
  169. const parts = sub.split('/')
  170. let created = null
  171. for (let p = parts.shift(), part = cwd;
  172. p && (part += '/' + p);
  173. p = parts.shift()) {
  174. part = normPath(path.resolve(part))
  175. if (cGet(cache, part)) {
  176. continue
  177. }
  178. try {
  179. fs.mkdirSync(part, mode)
  180. created = created || part
  181. cSet(cache, part, true)
  182. } catch (er) {
  183. const st = fs.lstatSync(part)
  184. if (st.isDirectory()) {
  185. cSet(cache, part, true)
  186. continue
  187. } else if (unlink) {
  188. fs.unlinkSync(part)
  189. fs.mkdirSync(part, mode)
  190. created = created || part
  191. cSet(cache, part, true)
  192. continue
  193. } else if (st.isSymbolicLink()) {
  194. return new SymlinkError(part, part + '/' + parts.join('/'))
  195. }
  196. }
  197. }
  198. return done(created)
  199. }