mkdir.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. cb(er)
  37. })
  38. }
  39. module.exports = (dir, opt, cb) => {
  40. dir = normPath(dir)
  41. // if there's any overlap between mask and mode,
  42. // then we'll need an explicit chmod
  43. const umask = opt.umask
  44. const mode = opt.mode | 0o0700
  45. const needChmod = (mode & umask) !== 0
  46. const uid = opt.uid
  47. const gid = opt.gid
  48. const doChown = typeof uid === 'number' &&
  49. typeof gid === 'number' &&
  50. (uid !== opt.processUid || gid !== opt.processGid)
  51. const preserve = opt.preserve
  52. const unlink = opt.unlink
  53. const cache = opt.cache
  54. const cwd = normPath(opt.cwd)
  55. const done = (er, created) => {
  56. if (er)
  57. cb(er)
  58. else {
  59. cSet(cache, dir, true)
  60. if (created && doChown)
  61. chownr(created, uid, gid, er => done(er))
  62. else if (needChmod)
  63. fs.chmod(dir, mode, cb)
  64. else
  65. cb()
  66. }
  67. }
  68. if (cache && cGet(cache, dir) === true)
  69. return done()
  70. if (dir === cwd)
  71. return checkCwd(dir, done)
  72. if (preserve)
  73. return mkdirp(dir, {mode}).then(made => done(null, made), done)
  74. const sub = normPath(path.relative(cwd, dir))
  75. const parts = sub.split('/')
  76. mkdir_(cwd, parts, mode, cache, unlink, cwd, null, done)
  77. }
  78. const mkdir_ = (base, parts, mode, cache, unlink, cwd, created, cb) => {
  79. if (!parts.length)
  80. return cb(null, created)
  81. const p = parts.shift()
  82. const part = normPath(path.resolve(base + '/' + p))
  83. if (cGet(cache, part))
  84. return mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
  85. fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb))
  86. }
  87. const onmkdir = (part, parts, mode, cache, unlink, cwd, created, cb) => er => {
  88. if (er) {
  89. fs.lstat(part, (statEr, st) => {
  90. if (statEr) {
  91. statEr.path = statEr.path && normPath(statEr.path)
  92. cb(statEr)
  93. } else if (st.isDirectory())
  94. mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
  95. else if (unlink) {
  96. fs.unlink(part, er => {
  97. if (er)
  98. return cb(er)
  99. fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink, cwd, created, cb))
  100. })
  101. } else if (st.isSymbolicLink())
  102. return cb(new SymlinkError(part, part + '/' + parts.join('/')))
  103. else
  104. cb(er)
  105. })
  106. } else {
  107. created = created || part
  108. mkdir_(part, parts, mode, cache, unlink, cwd, created, cb)
  109. }
  110. }
  111. const checkCwdSync = dir => {
  112. let ok = false
  113. let code = 'ENOTDIR'
  114. try {
  115. ok = fs.statSync(dir).isDirectory()
  116. } catch (er) {
  117. code = er.code
  118. } finally {
  119. if (!ok)
  120. throw new CwdError(dir, code)
  121. }
  122. }
  123. module.exports.sync = (dir, opt) => {
  124. dir = normPath(dir)
  125. // if there's any overlap between mask and mode,
  126. // then we'll need an explicit chmod
  127. const umask = opt.umask
  128. const mode = opt.mode | 0o0700
  129. const needChmod = (mode & umask) !== 0
  130. const uid = opt.uid
  131. const gid = opt.gid
  132. const doChown = typeof uid === 'number' &&
  133. typeof gid === 'number' &&
  134. (uid !== opt.processUid || gid !== opt.processGid)
  135. const preserve = opt.preserve
  136. const unlink = opt.unlink
  137. const cache = opt.cache
  138. const cwd = normPath(opt.cwd)
  139. const done = (created) => {
  140. cSet(cache, dir, true)
  141. if (created && doChown)
  142. chownr.sync(created, uid, gid)
  143. if (needChmod)
  144. fs.chmodSync(dir, mode)
  145. }
  146. if (cache && cGet(cache, dir) === true)
  147. return done()
  148. if (dir === cwd) {
  149. checkCwdSync(cwd)
  150. return done()
  151. }
  152. if (preserve)
  153. return done(mkdirp.sync(dir, mode))
  154. const sub = normPath(path.relative(cwd, dir))
  155. const parts = sub.split('/')
  156. let created = null
  157. for (let p = parts.shift(), part = cwd;
  158. p && (part += '/' + p);
  159. p = parts.shift()) {
  160. part = normPath(path.resolve(part))
  161. if (cGet(cache, part))
  162. continue
  163. try {
  164. fs.mkdirSync(part, mode)
  165. created = created || part
  166. cSet(cache, part, true)
  167. } catch (er) {
  168. const st = fs.lstatSync(part)
  169. if (st.isDirectory()) {
  170. cSet(cache, part, true)
  171. continue
  172. } else if (unlink) {
  173. fs.unlinkSync(part)
  174. fs.mkdirSync(part, mode)
  175. created = created || part
  176. cSet(cache, part, true)
  177. continue
  178. } else if (st.isSymbolicLink())
  179. return new SymlinkError(part, part + '/' + parts.join('/'))
  180. }
  181. }
  182. return done(created)
  183. }