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