• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2const fs = require('fs')
3const path = require('path')
4
5/* istanbul ignore next */
6const LCHOWN = fs.lchown ? 'lchown' : 'chown'
7/* istanbul ignore next */
8const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync'
9
10/* istanbul ignore next */
11const needEISDIRHandled = fs.lchown &&
12  !process.version.match(/v1[1-9]+\./) &&
13  !process.version.match(/v10\.[6-9]/)
14
15const lchownSync = (path, uid, gid) => {
16  try {
17    return fs[LCHOWNSYNC](path, uid, gid)
18  } catch (er) {
19    if (er.code !== 'ENOENT')
20      throw er
21  }
22}
23
24/* istanbul ignore next */
25const chownSync = (path, uid, gid) => {
26  try {
27    return fs.chownSync(path, uid, gid)
28  } catch (er) {
29    if (er.code !== 'ENOENT')
30      throw er
31  }
32}
33
34/* istanbul ignore next */
35const handleEISDIR =
36  needEISDIRHandled ? (path, uid, gid, cb) => er => {
37    // Node prior to v10 had a very questionable implementation of
38    // fs.lchown, which would always try to call fs.open on a directory
39    // Fall back to fs.chown in those cases.
40    if (!er || er.code !== 'EISDIR')
41      cb(er)
42    else
43      fs.chown(path, uid, gid, cb)
44  }
45  : (_, __, ___, cb) => cb
46
47/* istanbul ignore next */
48const handleEISDirSync =
49  needEISDIRHandled ? (path, uid, gid) => {
50    try {
51      return lchownSync(path, uid, gid)
52    } catch (er) {
53      if (er.code !== 'EISDIR')
54        throw er
55      chownSync(path, uid, gid)
56    }
57  }
58  : (path, uid, gid) => lchownSync(path, uid, gid)
59
60// fs.readdir could only accept an options object as of node v6
61const nodeVersion = process.version
62let readdir = (path, options, cb) => fs.readdir(path, options, cb)
63let readdirSync = (path, options) => fs.readdirSync(path, options)
64/* istanbul ignore next */
65if (/^v4\./.test(nodeVersion))
66  readdir = (path, options, cb) => fs.readdir(path, cb)
67
68const chown = (cpath, uid, gid, cb) => {
69  fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, er => {
70    // Skip ENOENT error
71    cb(er && er.code !== 'ENOENT' ? er : null)
72  }))
73}
74
75const chownrKid = (p, child, uid, gid, cb) => {
76  if (typeof child === 'string')
77    return fs.lstat(path.resolve(p, child), (er, stats) => {
78      // Skip ENOENT error
79      if (er)
80        return cb(er.code !== 'ENOENT' ? er : null)
81      stats.name = child
82      chownrKid(p, stats, uid, gid, cb)
83    })
84
85  if (child.isDirectory()) {
86    chownr(path.resolve(p, child.name), uid, gid, er => {
87      if (er)
88        return cb(er)
89      const cpath = path.resolve(p, child.name)
90      chown(cpath, uid, gid, cb)
91    })
92  } else {
93    const cpath = path.resolve(p, child.name)
94    chown(cpath, uid, gid, cb)
95  }
96}
97
98
99const chownr = (p, uid, gid, cb) => {
100  readdir(p, { withFileTypes: true }, (er, children) => {
101    // any error other than ENOTDIR or ENOTSUP means it's not readable,
102    // or doesn't exist.  give up.
103    if (er) {
104      if (er.code === 'ENOENT')
105        return cb()
106      else if (er.code !== 'ENOTDIR' && er.code !== 'ENOTSUP')
107        return cb(er)
108    }
109    if (er || !children.length)
110      return chown(p, uid, gid, cb)
111
112    let len = children.length
113    let errState = null
114    const then = er => {
115      if (errState)
116        return
117      if (er)
118        return cb(errState = er)
119      if (-- len === 0)
120        return chown(p, uid, gid, cb)
121    }
122
123    children.forEach(child => chownrKid(p, child, uid, gid, then))
124  })
125}
126
127const chownrKidSync = (p, child, uid, gid) => {
128  if (typeof child === 'string') {
129    try {
130      const stats = fs.lstatSync(path.resolve(p, child))
131      stats.name = child
132      child = stats
133    } catch (er) {
134      if (er.code === 'ENOENT')
135        return
136      else
137        throw er
138    }
139  }
140
141  if (child.isDirectory())
142    chownrSync(path.resolve(p, child.name), uid, gid)
143
144  handleEISDirSync(path.resolve(p, child.name), uid, gid)
145}
146
147const chownrSync = (p, uid, gid) => {
148  let children
149  try {
150    children = readdirSync(p, { withFileTypes: true })
151  } catch (er) {
152    if (er.code === 'ENOENT')
153      return
154    else if (er.code === 'ENOTDIR' || er.code === 'ENOTSUP')
155      return handleEISDirSync(p, uid, gid)
156    else
157      throw er
158  }
159
160  if (children && children.length)
161    children.forEach(child => chownrKidSync(p, child, uid, gid))
162
163  return handleEISDirSync(p, uid, gid)
164}
165
166module.exports = chownr
167chownr.sync = chownrSync
168