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