mkdir.js 5.1 KB

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