chmod.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. var common = require('./common');
  2. var fs = require('fs');
  3. var path = require('path');
  4. var PERMS = (function (base) {
  5. return {
  6. OTHER_EXEC: base.EXEC,
  7. OTHER_WRITE: base.WRITE,
  8. OTHER_READ: base.READ,
  9. GROUP_EXEC: base.EXEC << 3,
  10. GROUP_WRITE: base.WRITE << 3,
  11. GROUP_READ: base.READ << 3,
  12. OWNER_EXEC: base.EXEC << 6,
  13. OWNER_WRITE: base.WRITE << 6,
  14. OWNER_READ: base.READ << 6,
  15. // Literal octal numbers are apparently not allowed in "strict" javascript.
  16. STICKY: parseInt('01000', 8),
  17. SETGID: parseInt('02000', 8),
  18. SETUID: parseInt('04000', 8),
  19. TYPE_MASK: parseInt('0770000', 8),
  20. };
  21. }({
  22. EXEC: 1,
  23. WRITE: 2,
  24. READ: 4,
  25. }));
  26. common.register('chmod', _chmod, {
  27. });
  28. //@
  29. //@ ### chmod([options,] octal_mode || octal_string, file)
  30. //@ ### chmod([options,] symbolic_mode, file)
  31. //@
  32. //@ Available options:
  33. //@
  34. //@ + `-v`: output a diagnostic for every file processed//@
  35. //@ + `-c`: like verbose but report only when a change is made//@
  36. //@ + `-R`: change files and directories recursively//@
  37. //@
  38. //@ Examples:
  39. //@
  40. //@ ```javascript
  41. //@ chmod(755, '/Users/brandon');
  42. //@ chmod('755', '/Users/brandon'); // same as above
  43. //@ chmod('u+x', '/Users/brandon');
  44. //@ chmod('-R', 'a-w', '/Users/brandon');
  45. //@ ```
  46. //@
  47. //@ Alters the permissions of a file or directory by either specifying the
  48. //@ absolute permissions in octal form or expressing the changes in symbols.
  49. //@ This command tries to mimic the POSIX behavior as much as possible.
  50. //@ Notable exceptions:
  51. //@
  52. //@ + In symbolic modes, 'a-r' and '-r' are identical. No consideration is
  53. //@ given to the umask.
  54. //@ + There is no "quiet" option since default behavior is to run silent.
  55. function _chmod(options, mode, filePattern) {
  56. if (!filePattern) {
  57. if (options.length > 0 && options.charAt(0) === '-') {
  58. // Special case where the specified file permissions started with - to subtract perms, which
  59. // get picked up by the option parser as command flags.
  60. // If we are down by one argument and options starts with -, shift everything over.
  61. [].unshift.call(arguments, '');
  62. } else {
  63. common.error('You must specify a file.');
  64. }
  65. }
  66. options = common.parseOptions(options, {
  67. 'R': 'recursive',
  68. 'c': 'changes',
  69. 'v': 'verbose',
  70. });
  71. filePattern = [].slice.call(arguments, 2);
  72. var files;
  73. // TODO: replace this with a call to common.expand()
  74. if (options.recursive) {
  75. files = [];
  76. filePattern.forEach(function addFile(expandedFile) {
  77. var stat = fs.lstatSync(expandedFile);
  78. if (!stat.isSymbolicLink()) {
  79. files.push(expandedFile);
  80. if (stat.isDirectory()) { // intentionally does not follow symlinks.
  81. fs.readdirSync(expandedFile).forEach(function (child) {
  82. addFile(expandedFile + '/' + child);
  83. });
  84. }
  85. }
  86. });
  87. } else {
  88. files = filePattern;
  89. }
  90. files.forEach(function innerChmod(file) {
  91. file = path.resolve(file);
  92. if (!fs.existsSync(file)) {
  93. common.error('File not found: ' + file);
  94. }
  95. // When recursing, don't follow symlinks.
  96. if (options.recursive && fs.lstatSync(file).isSymbolicLink()) {
  97. return;
  98. }
  99. var stat = fs.statSync(file);
  100. var isDir = stat.isDirectory();
  101. var perms = stat.mode;
  102. var type = perms & PERMS.TYPE_MASK;
  103. var newPerms = perms;
  104. if (isNaN(parseInt(mode, 8))) {
  105. // parse options
  106. mode.split(',').forEach(function (symbolicMode) {
  107. var pattern = /([ugoa]*)([=\+-])([rwxXst]*)/i;
  108. var matches = pattern.exec(symbolicMode);
  109. if (matches) {
  110. var applyTo = matches[1];
  111. var operator = matches[2];
  112. var change = matches[3];
  113. var changeOwner = applyTo.indexOf('u') !== -1 || applyTo === 'a' || applyTo === '';
  114. var changeGroup = applyTo.indexOf('g') !== -1 || applyTo === 'a' || applyTo === '';
  115. var changeOther = applyTo.indexOf('o') !== -1 || applyTo === 'a' || applyTo === '';
  116. var changeRead = change.indexOf('r') !== -1;
  117. var changeWrite = change.indexOf('w') !== -1;
  118. var changeExec = change.indexOf('x') !== -1;
  119. var changeExecDir = change.indexOf('X') !== -1;
  120. var changeSticky = change.indexOf('t') !== -1;
  121. var changeSetuid = change.indexOf('s') !== -1;
  122. if (changeExecDir && isDir) {
  123. changeExec = true;
  124. }
  125. var mask = 0;
  126. if (changeOwner) {
  127. mask |= (changeRead ? PERMS.OWNER_READ : 0) + (changeWrite ? PERMS.OWNER_WRITE : 0) + (changeExec ? PERMS.OWNER_EXEC : 0) + (changeSetuid ? PERMS.SETUID : 0);
  128. }
  129. if (changeGroup) {
  130. mask |= (changeRead ? PERMS.GROUP_READ : 0) + (changeWrite ? PERMS.GROUP_WRITE : 0) + (changeExec ? PERMS.GROUP_EXEC : 0) + (changeSetuid ? PERMS.SETGID : 0);
  131. }
  132. if (changeOther) {
  133. mask |= (changeRead ? PERMS.OTHER_READ : 0) + (changeWrite ? PERMS.OTHER_WRITE : 0) + (changeExec ? PERMS.OTHER_EXEC : 0);
  134. }
  135. // Sticky bit is special - it's not tied to user, group or other.
  136. if (changeSticky) {
  137. mask |= PERMS.STICKY;
  138. }
  139. switch (operator) {
  140. case '+':
  141. newPerms |= mask;
  142. break;
  143. case '-':
  144. newPerms &= ~mask;
  145. break;
  146. case '=':
  147. newPerms = type + mask;
  148. // According to POSIX, when using = to explicitly set the
  149. // permissions, setuid and setgid can never be cleared.
  150. if (fs.statSync(file).isDirectory()) {
  151. newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
  152. }
  153. break;
  154. default:
  155. common.error('Could not recognize operator: `' + operator + '`');
  156. }
  157. if (options.verbose) {
  158. console.log(file + ' -> ' + newPerms.toString(8));
  159. }
  160. if (perms !== newPerms) {
  161. if (!options.verbose && options.changes) {
  162. console.log(file + ' -> ' + newPerms.toString(8));
  163. }
  164. fs.chmodSync(file, newPerms);
  165. perms = newPerms; // for the next round of changes!
  166. }
  167. } else {
  168. common.error('Invalid symbolic mode change: ' + symbolicMode);
  169. }
  170. });
  171. } else {
  172. // they gave us a full number
  173. newPerms = type + parseInt(mode, 8);
  174. // POSIX rules are that setuid and setgid can only be added using numeric
  175. // form, but not cleared.
  176. if (fs.statSync(file).isDirectory()) {
  177. newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms;
  178. }
  179. fs.chmodSync(file, newPerms);
  180. }
  181. });
  182. return '';
  183. }
  184. module.exports = _chmod;