| 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. 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 || octal_string, 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('755', '/Users/brandon'); // same as above |
| //@ 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 { |
| common.error('You must specify a file.'); |
| } |
| } |
| |
| options = common.parseOptions(options, { |
| 'R': 'recursive', |
| 'c': 'changes', |
| 'v': 'verbose' |
| }); |
| |
| if (typeof filePattern === 'string') { |
| filePattern = [ filePattern ]; |
| } |
| |
| var files; |
| |
| if (options.recursive) { |
| files = []; |
| common.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 = common.expand(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 && 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 { |
| 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 (fs.statSync(file).isDirectory()) { |
| newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms; |
| } |
| |
| fs.chmodSync(file, newPerms); |
| } |
| }); |
| } |
| module.exports = _chmod; |