1
|
'use strict'
|
2
|
const fs = require('fs')
|
3
|
const path = require('path')
|
4
|
|
5
|
/* istanbul ignore next */
|
6
|
const LCHOWN = fs.lchown ? 'lchown' : 'chown'
|
7
|
/* istanbul ignore next */
|
8
|
const LCHOWNSYNC = fs.lchownSync ? 'lchownSync' : 'chownSync'
|
9
|
|
10
|
/* istanbul ignore next */
|
11
|
const needEISDIRHandled = fs.lchown &&
|
12
|
!process.version.match(/v1[1-9]+\./) &&
|
13
|
!process.version.match(/v10\.[6-9]/)
|
14
|
|
15
|
const 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 */
|
25
|
const 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 */
|
35
|
const 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 */
|
48
|
const 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
|
61
|
const nodeVersion = process.version
|
62
|
let readdir = (path, options, cb) => fs.readdir(path, options, cb)
|
63
|
let readdirSync = (path, options) => fs.readdirSync(path, options)
|
64
|
/* istanbul ignore next */
|
65
|
if (/^v4\./.test(nodeVersion))
|
66
|
readdir = (path, options, cb) => fs.readdir(path, cb)
|
67
|
|
68
|
const 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
|
|
75
|
const 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
|
|
99
|
const 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
|
|
127
|
const 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
|
|
147
|
const 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
|
|
166
|
module.exports = chownr
|
167
|
chownr.sync = chownrSync
|