UNPKG

5.62 kBJavaScriptView Raw
1var common = require('./common');
2var fs = require('fs');
3
4common.register('rm', _rm, {
5 cmdOptions: {
6 'f': 'force',
7 'r': 'recursive',
8 'R': 'recursive',
9 },
10});
11
12// Recursively removes 'dir'
13// Adapted from https://github.com/ryanmcgrath/wrench-js
14//
15// Copyright (c) 2010 Ryan McGrath
16// Copyright (c) 2012 Artur Adib
17//
18// Licensed under the MIT License
19// http://www.opensource.org/licenses/mit-license.php
20function rmdirSyncRecursive(dir, force, fromSymlink) {
21 var files;
22
23 files = fs.readdirSync(dir);
24
25 // Loop through and delete everything in the sub-tree after checking it
26 for (var i = 0; i < files.length; i++) {
27 var file = dir + '/' + files[i];
28 var currFile = common.statNoFollowLinks(file);
29
30 if (currFile.isDirectory()) { // Recursive function back to the beginning
31 rmdirSyncRecursive(file, force);
32 } else { // Assume it's a file - perhaps a try/catch belongs here?
33 if (force || isWriteable(file)) {
34 try {
35 common.unlinkSync(file);
36 } catch (e) {
37 /* istanbul ignore next */
38 common.error('could not remove file (code ' + e.code + '): ' + file, {
39 continue: true,
40 });
41 }
42 }
43 }
44 }
45
46 // if was directory was referenced through a symbolic link,
47 // the contents should be removed, but not the directory itself
48 if (fromSymlink) return;
49
50 // Now that we know everything in the sub-tree has been deleted, we can delete the main directory.
51 // Huzzah for the shopkeep.
52
53 var result;
54 try {
55 // Retry on windows, sometimes it takes a little time before all the files in the directory are gone
56 var start = Date.now();
57
58 // TODO: replace this with a finite loop
59 for (;;) {
60 try {
61 result = fs.rmdirSync(dir);
62 if (fs.existsSync(dir)) throw { code: 'EAGAIN' };
63 break;
64 } catch (er) {
65 /* istanbul ignore next */
66 // In addition to error codes, also check if the directory still exists and loop again if true
67 if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM' || er.code === 'EAGAIN')) {
68 if (Date.now() - start > 1000) throw er;
69 } else if (er.code === 'ENOENT') {
70 // Directory did not exist, deletion was successful
71 break;
72 } else {
73 throw er;
74 }
75 }
76 }
77 } catch (e) {
78 common.error('could not remove directory (code ' + e.code + '): ' + dir, { continue: true });
79 }
80
81 return result;
82} // rmdirSyncRecursive
83
84// Hack to determine if file has write permissions for current user
85// Avoids having to check user, group, etc, but it's probably slow
86function isWriteable(file) {
87 var writePermission = true;
88 try {
89 var __fd = fs.openSync(file, 'a');
90 fs.closeSync(__fd);
91 } catch (e) {
92 writePermission = false;
93 }
94
95 return writePermission;
96}
97
98function handleFile(file, options) {
99 if (options.force || isWriteable(file)) {
100 // -f was passed, or file is writable, so it can be removed
101 common.unlinkSync(file);
102 } else {
103 common.error('permission denied: ' + file, { continue: true });
104 }
105}
106
107function handleDirectory(file, options) {
108 if (options.recursive) {
109 // -r was passed, so directory can be removed
110 rmdirSyncRecursive(file, options.force);
111 } else {
112 common.error('path is a directory', { continue: true });
113 }
114}
115
116function handleSymbolicLink(file, options) {
117 var stats;
118 try {
119 stats = common.statFollowLinks(file);
120 } catch (e) {
121 // symlink is broken, so remove the symlink itself
122 common.unlinkSync(file);
123 return;
124 }
125
126 if (stats.isFile()) {
127 common.unlinkSync(file);
128 } else if (stats.isDirectory()) {
129 if (file[file.length - 1] === '/') {
130 // trailing separator, so remove the contents, not the link
131 if (options.recursive) {
132 // -r was passed, so directory can be removed
133 var fromSymlink = true;
134 rmdirSyncRecursive(file, options.force, fromSymlink);
135 } else {
136 common.error('path is a directory', { continue: true });
137 }
138 } else {
139 // no trailing separator, so remove the link
140 common.unlinkSync(file);
141 }
142 }
143}
144
145function handleFIFO(file) {
146 common.unlinkSync(file);
147}
148
149//@
150//@ ### rm([options,] file [, file ...])
151//@ ### rm([options,] file_array)
152//@
153//@ Available options:
154//@
155//@ + `-f`: force
156//@ + `-r, -R`: recursive
157//@
158//@ Examples:
159//@
160//@ ```javascript
161//@ rm('-rf', '/tmp/*');
162//@ rm('some_file.txt', 'another_file.txt');
163//@ rm(['some_file.txt', 'another_file.txt']); // same as above
164//@ ```
165//@
166//@ Removes files.
167function _rm(options, files) {
168 if (!files) common.error('no paths given');
169
170 // Convert to array
171 files = [].slice.call(arguments, 1);
172
173 files.forEach(function (file) {
174 var lstats;
175 try {
176 var filepath = (file[file.length - 1] === '/')
177 ? file.slice(0, -1) // remove the '/' so lstatSync can detect symlinks
178 : file;
179 lstats = common.statNoFollowLinks(filepath); // test for existence
180 } catch (e) {
181 // Path does not exist, no force flag given
182 if (!options.force) {
183 common.error('no such file or directory: ' + file, { continue: true });
184 }
185 return; // skip file
186 }
187
188 // If here, path exists
189 if (lstats.isFile()) {
190 handleFile(file, options);
191 } else if (lstats.isDirectory()) {
192 handleDirectory(file, options);
193 } else if (lstats.isSymbolicLink()) {
194 handleSymbolicLink(file, options);
195 } else if (lstats.isFIFO()) {
196 handleFIFO(file);
197 }
198 }); // forEach(file)
199 return '';
200} // rm
201module.exports = _rm;