UNPKG

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