Merge pull request #48 from brandonramirez/chmod

Implemented chmod command.  Github issue 35
This commit is contained in:
Artur Adib 2013-01-08 08:29:10 -08:00
Родитель 91dd5bd61d 79668501a5
Коммит 91cb4ebb66
6 изменённых файлов: 283 добавлений и 0 удалений

200
shell.js
Просмотреть файл

@ -1111,7 +1111,207 @@ function _exec(command, options, callback) {
}
exports.exec = wrap('exec', _exec, {notUnix:true});
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. Using parseInt is
// the preferred way, else a jshint warning is thrown.
STICKY : parseInt('01000', 8),
SETGID : parseInt('02000', 8),
SETUID : parseInt('04000', 8),
TYPE_MASK : parseInt('0770000', 8)
};
})({
EXEC : 1,
WRITE : 2,
READ : 4
});
//@
//@ ### chmod(octal_mode, file)
//@ ### chmod(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('u+x', '/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.
filePattern = mode;
mode = options;
options = '';
}
else {
error('You must specify a file.');
}
}
options = parseOptions(options, {
'R': 'recursive',
'c': 'changes',
'v': 'verbose'
});
if (typeof filePattern === 'string') {
filePattern = [ filePattern ];
}
var files;
if (options.recursive) {
files = [];
expand(filePattern).forEach(function addFile(expandedFile) {
var stat = fs.lstatSync(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 = expand(filePattern);
}
files.forEach(function innerChmod(file) {
file = path.resolve(file);
if (!fs.existsSync(file)) {
error('File not found: ' + file);
}
// When recursing, don't follow symlinks.
if (options.recursive && fs.lstatSync(file).isSymbolicLink()) {
return;
}
var perms = fs.statSync(file).mode;
var type = perms & PERMS.TYPE_MASK;
var newPerms = perms;
if (isNaN(parseInt(mode, 8))) {
// parse options
mode.split(',').forEach(function (symbolicMode) {
/*jshint regexdash:true */
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 changeSticky = change.indexOf('t') != -1;
var changeSetuid = change.indexOf('s') != -1;
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 (fs.statSync(file).isDirectory()) {
newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
}
break;
}
if (options.verbose) {
log(file + ' -> ' + newPerms.toString(8));
}
if (perms != newPerms) {
if (!options.verbose && options.changes) {
log(file + ' -> ' + newPerms.toString(8));
}
fs.chmodSync(file, newPerms);
}
}
else {
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 (fs.statSync(file).isDirectory()) {
newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
}
fs.chmodSync(file, newPerms);
}
});
}
exports.chmod = wrap('chmod', _chmod);
//@

81
test/chmod.js Normal file
Просмотреть файл

@ -0,0 +1,81 @@
var shell = require('..');
var assert = require('assert'),
path = require('path'),
fs = require('fs');
shell.config.silent = true;
//
// Invalids
//
shell.chmod('blah'); // missing args
assert.ok(shell.error());
shell.chmod('893', 'resources/chmod'); // invalid permissions - mode must be in octal
assert.ok(shell.error());
//
// Valids
//
// Test files - the bitmasking is to ignore the upper bits.
shell.chmod('755', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('777', 8), parseInt('755', 8));
shell.chmod('644', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('777', 8), parseInt('644', 8));
shell.chmod('o+x', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('007', 8), parseInt('005', 8));
shell.chmod('644', 'resources/chmod/file1');
shell.chmod('+x', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('777', 8), parseInt('755', 8));
shell.chmod('644', 'resources/chmod/file1');
// Test setuid
shell.chmod('u+s', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('4000', 8), parseInt('4000', 8));
shell.chmod('u-s', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('777', 8), parseInt('644', 8));
// according to POSIX standards at http://linux.die.net/man/1/chmod,
// setuid is never cleared from a directory unless explicitly asked for.
shell.chmod('u+s', 'resources/chmod/c');
shell.chmod('755', 'resources/chmod/c');
assert.equal(fs.statSync('resources/chmod/c').mode & parseInt('4000', 8), parseInt('4000', 8));
shell.chmod('u-s', 'resources/chmod/c');
// Test setgid
shell.chmod('g+s', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('2000', 8), parseInt('2000', 8));
shell.chmod('g-s', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('777', 8), parseInt('644', 8));
// Test sticky bit
shell.chmod('+t', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('1000', 8), parseInt('1000', 8));
shell.chmod('-t', 'resources/chmod/file1');
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('777', 8), parseInt('644', 8));
assert.equal(fs.statSync('resources/chmod/file1').mode & parseInt('1000', 8), 0);
// Test directories
shell.chmod('a-w', 'resources/chmod/b/a/b');
assert.equal(fs.statSync('resources/chmod/b/a/b').mode & parseInt('777', 8), parseInt('555', 8));
shell.chmod('755', 'resources/chmod/b/a/b');
// Test recursion
shell.chmod('-R', 'a+w', 'resources/chmod/b');
assert.equal(fs.statSync('resources/chmod/b/a/b').mode & parseInt('777', 8), parseInt('777', 8));
shell.chmod('-R', '755', 'resources/chmod/b');
assert.equal(fs.statSync('resources/chmod/b/a/b').mode & parseInt('777', 8), parseInt('755', 8));
// Test symbolic links w/ recursion - WARNING: *nix only
fs.symlinkSync('resources/chmod/b/a', 'resources/chmod/a/b/c/link', 'dir');
shell.chmod('-R', 'u-w', 'resources/chmod/a/b');
assert.equal(fs.statSync('resources/chmod/a/b/c').mode & parseInt('700', 8), parseInt('500', 8));
assert.equal(fs.statSync('resources/chmod/b/a').mode & parseInt('700', 8), parseInt('700', 8));
shell.chmod('-R', 'u+w', 'resources/chmod/a/b');
fs.unlinkSync('resources/chmod/a/b/c/link');
shell.exit(123);

0
test/resources/chmod/a/b/c/.gitignore поставляемый Normal file
Просмотреть файл

0
test/resources/chmod/b/a/b/.gitignore поставляемый Normal file
Просмотреть файл

0
test/resources/chmod/c/a/b/.gitignore поставляемый Normal file
Просмотреть файл

Просмотреть файл

@ -0,0 +1,2 @@
this is test file 1
default state should be 0644 (rw-r--r--)