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