217 lines
6.7 KiB
Plaintext
217 lines
6.7 KiB
Plaintext
var common = require('./common');
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
|
|
var PERMS = (function (base) {
|
|
return {
|
|
OTHER_EXEC: base.EXEC,
|
|
OTHER_WRITE: base.WRITE,
|
|
OTHER_READ: base.READ,
|
|
|
|
GROUP_EXEC: base.EXEC << 3,
|
|
GROUP_WRITE: base.WRITE << 3,
|
|
GROUP_READ: base.READ << 3,
|
|
|
|
OWNER_EXEC: base.EXEC << 6,
|
|
OWNER_WRITE: base.WRITE << 6,
|
|
OWNER_READ: base.READ << 6,
|
|
|
|
// Literal octal numbers are apparently not allowed in "strict" javascript.
|
|
STICKY: parseInt('01000', 8),
|
|
SETGID: parseInt('02000', 8),
|
|
SETUID: parseInt('04000', 8),
|
|
|
|
TYPE_MASK: parseInt('0770000', 8),
|
|
};
|
|
}({
|
|
EXEC: 1,
|
|
WRITE: 2,
|
|
READ: 4,
|
|
}));
|
|
|
|
common.register('chmod', _chmod, {
|
|
});
|
|
|
|
//@
|
|
//@ ### chmod([options,] octal_mode || octal_string, file)
|
|
//@ ### chmod([options,] symbolic_mode, file)
|
|
//@
|
|
//@ Available options:
|
|
//@
|
|
//@ + `-v`: output a diagnostic for every file processed//@
|
|
//@ + `-c`: like verbose, but report only when a change is made//@
|
|
//@ + `-R`: change files and directories recursively//@
|
|
//@
|
|
//@ Examples:
|
|
//@
|
|
//@ ```javascript
|
|
//@ chmod(755, '/Users/brandon');
|
|
//@ chmod('755', '/Users/brandon'); // same as above
|
|
//@ chmod('u+x', '/Users/brandon');
|
|
//@ chmod('-R', 'a-w', '/Users/brandon');
|
|
//@ ```
|
|
//@
|
|
//@ Alters the permissions of a file or directory by either specifying the
|
|
//@ absolute permissions in octal form or expressing the changes in symbols.
|
|
//@ This command tries to mimic the POSIX behavior as much as possible.
|
|
//@ Notable exceptions:
|
|
//@
|
|
//@ + In symbolic modes, `a-r` and `-r` are identical. No consideration is
|
|
//@ given to the `umask`.
|
|
//@ + There is no "quiet" option, since default behavior is to run silent.
|
|
function _chmod(options, mode, filePattern) {
|
|
if (!filePattern) {
|
|
if (options.length > 0 && options.charAt(0) === '-') {
|
|
// Special case where the specified file permissions started with - to subtract perms, which
|
|
// get picked up by the option parser as command flags.
|
|
// If we are down by one argument and options starts with -, shift everything over.
|
|
[].unshift.call(arguments, '');
|
|
} else {
|
|
common.error('You must specify a file.');
|
|
}
|
|
}
|
|
|
|
options = common.parseOptions(options, {
|
|
'R': 'recursive',
|
|
'c': 'changes',
|
|
'v': 'verbose',
|
|
});
|
|
|
|
filePattern = [].slice.call(arguments, 2);
|
|
|
|
var files;
|
|
|
|
// TODO: replace this with a call to common.expand()
|
|
if (options.recursive) {
|
|
files = [];
|
|
filePattern.forEach(function addFile(expandedFile) {
|
|
var stat = common.statNoFollowLinks(expandedFile);
|
|
|
|
if (!stat.isSymbolicLink()) {
|
|
files.push(expandedFile);
|
|
|
|
if (stat.isDirectory()) { // intentionally does not follow symlinks.
|
|
fs.readdirSync(expandedFile).forEach(function (child) {
|
|
addFile(expandedFile + '/' + child);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
files = filePattern;
|
|
}
|
|
|
|
files.forEach(function innerChmod(file) {
|
|
file = path.resolve(file);
|
|
if (!fs.existsSync(file)) {
|
|
common.error('File not found: ' + file);
|
|
}
|
|
|
|
// When recursing, don't follow symlinks.
|
|
if (options.recursive && common.statNoFollowLinks(file).isSymbolicLink()) {
|
|
return;
|
|
}
|
|
|
|
var stat = common.statFollowLinks(file);
|
|
var isDir = stat.isDirectory();
|
|
var perms = stat.mode;
|
|
var type = perms & PERMS.TYPE_MASK;
|
|
|
|
var newPerms = perms;
|
|
|
|
if (isNaN(parseInt(mode, 8))) {
|
|
// parse options
|
|
mode.split(',').forEach(function (symbolicMode) {
|
|
var pattern = /([ugoa]*)([=\+-])([rwxXst]*)/i;
|
|
var matches = pattern.exec(symbolicMode);
|
|
|
|
if (matches) {
|
|
var applyTo = matches[1];
|
|
var operator = matches[2];
|
|
var change = matches[3];
|
|
|
|
var changeOwner = applyTo.indexOf('u') !== -1 || applyTo === 'a' || applyTo === '';
|
|
var changeGroup = applyTo.indexOf('g') !== -1 || applyTo === 'a' || applyTo === '';
|
|
var changeOther = applyTo.indexOf('o') !== -1 || applyTo === 'a' || applyTo === '';
|
|
|
|
var changeRead = change.indexOf('r') !== -1;
|
|
var changeWrite = change.indexOf('w') !== -1;
|
|
var changeExec = change.indexOf('x') !== -1;
|
|
var changeExecDir = change.indexOf('X') !== -1;
|
|
var changeSticky = change.indexOf('t') !== -1;
|
|
var changeSetuid = change.indexOf('s') !== -1;
|
|
|
|
if (changeExecDir && isDir) {
|
|
changeExec = true;
|
|
}
|
|
|
|
var mask = 0;
|
|
if (changeOwner) {
|
|
mask |= (changeRead ? PERMS.OWNER_READ : 0) + (changeWrite ? PERMS.OWNER_WRITE : 0) + (changeExec ? PERMS.OWNER_EXEC : 0) + (changeSetuid ? PERMS.SETUID : 0);
|
|
}
|
|
if (changeGroup) {
|
|
mask |= (changeRead ? PERMS.GROUP_READ : 0) + (changeWrite ? PERMS.GROUP_WRITE : 0) + (changeExec ? PERMS.GROUP_EXEC : 0) + (changeSetuid ? PERMS.SETGID : 0);
|
|
}
|
|
if (changeOther) {
|
|
mask |= (changeRead ? PERMS.OTHER_READ : 0) + (changeWrite ? PERMS.OTHER_WRITE : 0) + (changeExec ? PERMS.OTHER_EXEC : 0);
|
|
}
|
|
|
|
// Sticky bit is special - it's not tied to user, group or other.
|
|
if (changeSticky) {
|
|
mask |= PERMS.STICKY;
|
|
}
|
|
|
|
switch (operator) {
|
|
case '+':
|
|
newPerms |= mask;
|
|
break;
|
|
|
|
case '-':
|
|
newPerms &= ~mask;
|
|
break;
|
|
|
|
case '=':
|
|
newPerms = type + mask;
|
|
|
|
// According to POSIX, when using = to explicitly set the
|
|
// permissions, setuid and setgid can never be cleared.
|
|
if (common.statFollowLinks(file).isDirectory()) {
|
|
newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
|
|
}
|
|
break;
|
|
default:
|
|
common.error('Could not recognize operator: `' + operator + '`');
|
|
}
|
|
|
|
if (options.verbose) {
|
|
console.log(file + ' -> ' + newPerms.toString(8));
|
|
}
|
|
|
|
if (perms !== newPerms) {
|
|
if (!options.verbose && options.changes) {
|
|
console.log(file + ' -> ' + newPerms.toString(8));
|
|
}
|
|
fs.chmodSync(file, newPerms);
|
|
perms = newPerms; // for the next round of changes!
|
|
}
|
|
} else {
|
|
common.error('Invalid symbolic mode change: ' + symbolicMode);
|
|
}
|
|
});
|
|
} else {
|
|
// they gave us a full number
|
|
newPerms = type + parseInt(mode, 8);
|
|
|
|
// POSIX rules are that setuid and setgid can only be added using numeric
|
|
// form, but not cleared.
|
|
if (common.statFollowLinks(file).isDirectory()) {
|
|
newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
|
|
}
|
|
|
|
fs.chmodSync(file, newPerms);
|
|
}
|
|
});
|
|
return '';
|
|
}
|
|
module.exports = _chmod;
|