• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// This file is a modified version of the rimraf module on npm. It has been
2// modified in the following ways:
3// - Use of the assert module has been replaced with core's error system.
4// - All code related to the glob dependency has been removed.
5// - Bring your own custom fs module is not currently supported.
6// - Some basic code cleanup.
7'use strict';
8
9const {
10  Promise,
11  Set,
12} = primordials;
13
14const { Buffer } = require('buffer');
15const {
16  chmod,
17  chmodSync,
18  lstat,
19  lstatSync,
20  readdir,
21  readdirSync,
22  rmdir,
23  rmdirSync,
24  stat,
25  statSync,
26  unlink,
27  unlinkSync
28} = require('fs');
29const { sep } = require('path');
30const { setTimeout } = require('timers');
31const { sleep } = require('internal/util');
32const notEmptyErrorCodes = new Set(['ENOTEMPTY', 'EEXIST', 'EPERM']);
33const retryErrorCodes = new Set(
34  ['EBUSY', 'EMFILE', 'ENFILE', 'ENOTEMPTY', 'EPERM']);
35const isWindows = process.platform === 'win32';
36const epermHandler = isWindows ? fixWinEPERM : _rmdir;
37const epermHandlerSync = isWindows ? fixWinEPERMSync : _rmdirSync;
38const readdirEncoding = 'buffer';
39const separator = Buffer.from(sep);
40
41
42function rimraf(path, options, callback) {
43  let retries = 0;
44
45  _rimraf(path, options, function CB(err) {
46    if (err) {
47      if (retryErrorCodes.has(err.code) && retries < options.maxRetries) {
48        retries++;
49        const delay = retries * options.retryDelay;
50        return setTimeout(_rimraf, delay, path, options, CB);
51      }
52
53      // The file is already gone.
54      if (err.code === 'ENOENT')
55        err = null;
56    }
57
58    callback(err);
59  });
60}
61
62
63function _rimraf(path, options, callback) {
64  // SunOS lets the root user unlink directories. Use lstat here to make sure
65  // it's not a directory.
66  lstat(path, (err, stats) => {
67    if (err) {
68      if (err.code === 'ENOENT')
69        return callback(null);
70
71      // Windows can EPERM on stat.
72      if (isWindows && err.code === 'EPERM')
73        return fixWinEPERM(path, options, err, callback);
74    } else if (stats.isDirectory()) {
75      return _rmdir(path, options, err, callback);
76    }
77
78    unlink(path, (err) => {
79      if (err) {
80        if (err.code === 'ENOENT')
81          return callback(null);
82        if (err.code === 'EISDIR')
83          return _rmdir(path, options, err, callback);
84        if (err.code === 'EPERM') {
85          return epermHandler(path, options, err, callback);
86        }
87      }
88
89      return callback(err);
90    });
91  });
92}
93
94
95function fixWinEPERM(path, options, originalErr, callback) {
96  chmod(path, 0o666, (err) => {
97    if (err)
98      return callback(err.code === 'ENOENT' ? null : originalErr);
99
100    stat(path, (err, stats) => {
101      if (err)
102        return callback(err.code === 'ENOENT' ? null : originalErr);
103
104      if (stats.isDirectory())
105        _rmdir(path, options, originalErr, callback);
106      else
107        unlink(path, callback);
108    });
109  });
110}
111
112
113function _rmdir(path, options, originalErr, callback) {
114  rmdir(path, (err) => {
115    if (err) {
116      if (notEmptyErrorCodes.has(err.code))
117        return _rmchildren(path, options, callback);
118      if (err.code === 'ENOTDIR')
119        return callback(originalErr);
120    }
121
122    callback(err);
123  });
124}
125
126
127function _rmchildren(path, options, callback) {
128  const pathBuf = Buffer.from(path);
129
130  readdir(pathBuf, readdirEncoding, (err, files) => {
131    if (err)
132      return callback(err);
133
134    let numFiles = files.length;
135
136    if (numFiles === 0)
137      return rmdir(path, callback);
138
139    let done = false;
140
141    files.forEach((child) => {
142      const childPath = Buffer.concat([pathBuf, separator, child]);
143
144      rimraf(childPath, options, (err) => {
145        if (done)
146          return;
147
148        if (err) {
149          done = true;
150          return callback(err);
151        }
152
153        numFiles--;
154        if (numFiles === 0)
155          rmdir(path, callback);
156      });
157    });
158  });
159}
160
161
162function rimrafPromises(path, options) {
163  return new Promise((resolve, reject) => {
164    rimraf(path, options, (err) => {
165      if (err)
166        return reject(err);
167
168      resolve();
169    });
170  });
171}
172
173
174function rimrafSync(path, options) {
175  let stats;
176
177  try {
178    stats = lstatSync(path);
179  } catch (err) {
180    if (err.code === 'ENOENT')
181      return;
182
183    // Windows can EPERM on stat.
184    if (isWindows && err.code === 'EPERM')
185      fixWinEPERMSync(path, options, err);
186  }
187
188  try {
189    // SunOS lets the root user unlink directories.
190    if (stats !== undefined && stats.isDirectory())
191      _rmdirSync(path, options, null);
192    else
193      _unlinkSync(path, options);
194  } catch (err) {
195    if (err.code === 'ENOENT')
196      return;
197    if (err.code === 'EPERM')
198      return epermHandlerSync(path, options, err);
199    if (err.code !== 'EISDIR')
200      throw err;
201
202    _rmdirSync(path, options, err);
203  }
204}
205
206
207function _unlinkSync(path, options) {
208  const tries = options.maxRetries + 1;
209
210  for (let i = 1; i <= tries; i++) {
211    try {
212      return unlinkSync(path);
213    } catch (err) {
214      // Only sleep if this is not the last try, and the delay is greater
215      // than zero, and an error was encountered that warrants a retry.
216      if (retryErrorCodes.has(err.code) &&
217          i < tries &&
218          options.retryDelay > 0) {
219        sleep(i * options.retryDelay);
220      }
221    }
222  }
223}
224
225
226function _rmdirSync(path, options, originalErr) {
227  try {
228    rmdirSync(path);
229  } catch (err) {
230    if (err.code === 'ENOENT')
231      return;
232    if (err.code === 'ENOTDIR')
233      throw originalErr;
234
235    if (notEmptyErrorCodes.has(err.code)) {
236      // Removing failed. Try removing all children and then retrying the
237      // original removal. Windows has a habit of not closing handles promptly
238      // when files are deleted, resulting in spurious ENOTEMPTY failures. Work
239      // around that issue by retrying on Windows.
240      const pathBuf = Buffer.from(path);
241
242      readdirSync(pathBuf, readdirEncoding).forEach((child) => {
243        const childPath = Buffer.concat([pathBuf, separator, child]);
244
245        rimrafSync(childPath, options);
246      });
247
248      const tries = options.maxRetries + 1;
249
250      for (let i = 1; i <= tries; i++) {
251        try {
252          return rmdirSync(path, options);
253        } catch (err) {
254          // Only sleep if this is not the last try, and the delay is greater
255          // than zero, and an error was encountered that warrants a retry.
256          if (retryErrorCodes.has(err.code) &&
257              i < tries &&
258              options.retryDelay > 0) {
259            sleep(i * options.retryDelay);
260          }
261        }
262      }
263    }
264  }
265}
266
267
268function fixWinEPERMSync(path, options, originalErr) {
269  try {
270    chmodSync(path, 0o666);
271  } catch (err) {
272    if (err.code === 'ENOENT')
273      return;
274
275    throw originalErr;
276  }
277
278  let stats;
279
280  try {
281    stats = statSync(path);
282  } catch (err) {
283    if (err.code === 'ENOENT')
284      return;
285
286    throw originalErr;
287  }
288
289  if (stats.isDirectory())
290    _rmdirSync(path, options, originalErr);
291  else
292    _unlinkSync(path, options);
293}
294
295
296module.exports = { rimraf, rimrafPromises, rimrafSync };
297