Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use fs.chmod when target is not a symlink #20

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Reimplement fix with smaller risk of TOCTOU
Maxime Bargiel committed Oct 18, 2018
commit 8a1b4c0fd90cfc09f08c90aa3125aa3d36a62ebb
29 changes: 25 additions & 4 deletions chmodr.js
Original file line number Diff line number Diff line change
@@ -27,6 +27,15 @@ const dirMode = mode => {
return mode
}

const chmodFileOrSymlink = (p, mode, cb) => {
fs[LCHMOD](p, mode, (er) => {
// EACCES on lchmod means it's a readonly file
// so we fallback to chmod; in other cases, give up
if (fs.lchmod && er && er.code === 'EACCES')
return fs.chmod(p, mode, cb)
return cb(er)
})
}

const chmodrKid = (p, child, mode, cb) => {
if (typeof child === 'string')
@@ -44,7 +53,7 @@ const chmodrKid = (p, child, mode, cb) => {
fs.chmod(path.resolve(p, child.name), dirMode(mode), cb)
})
} else
fs[child.isSymbolicLink() ? LCHMOD : 'chmod'](path.resolve(p, child.name), mode, cb)
chmodFileOrSymlink(path.resolve(p, child.name), mode, cb)
}


@@ -53,7 +62,7 @@ const chmodr = (p, mode, cb) => {
// any error other than ENOTDIR means it's not readable, or
// doesn't exist. give up.
if (er && er.code !== 'ENOTDIR') return cb(er)
if (er) return chmodrKid(p, '.', mode, cb)
if (er) return chmodFileOrSymlink(p, mode, cb)
if (!children.length) return fs.chmod(p, dirMode(mode), cb)

let len = children.length
@@ -68,6 +77,18 @@ const chmodr = (p, mode, cb) => {
})
}

const chmodFileOrSymlinkSync = (p, mode) => {
try {
return fs[LCHMODSYNC](p, mode)
} catch (er) {
// EACCES on lchmod means it's a readonly file
// so we fallback to chmod; in other cases, give up
if (fs.lchmodSync && er && er.code === 'EACCES')
return fs.chmodSync(p, mode)
throw er
}
}

const chmodrKidSync = (p, child, mode) => {
if (typeof child === 'string') {
const stats = fs.lstatSync(path.resolve(p, child))
@@ -79,15 +100,15 @@ const chmodrKidSync = (p, child, mode) => {
chmodrSync(path.resolve(p, child.name), mode)
fs.chmodSync(path.resolve(p, child.name), dirMode(mode))
} else
fs[child.isSymbolicLink() ? LCHMODSYNC : 'chmodSync'](path.resolve(p, child.name), mode)
chmodFileOrSymlinkSync(path.resolve(p, child.name), mode)
}

const chmodrSync = (p, mode) => {
let children
try {
children = readdirSync(p, { withFileTypes: true })
} catch (er) {
if (er && er.code === 'ENOTDIR') return chmodrKidSync(p, '.', mode)
if (er && er.code === 'ENOTDIR') return chmodFileOrSymlinkSync(p, mode)
throw er
}