pax_global_header00006660000000000000000000000064130606321710014511gustar00rootroot0000000000000052 comment=e2daf9ab461004d10cc934e286052a6cd72a6127 fs-vacuum-1.2.10/000077500000000000000000000000001306063217100135005ustar00rootroot00000000000000fs-vacuum-1.2.10/.eslintrc000066400000000000000000000006451306063217100153310ustar00rootroot00000000000000{ "env" : { "node" : true }, "rules" : { "curly" : 0, "no-lonely-if" : 1, "no-mixed-requires" : 0, "no-underscore-dangle" : 0, "no-unused-vars" : [2, {"vars" : "all", "args" : "after-used"}], "no-use-before-define" : [2, "nofunc"], "quotes" : [1, "double", "avoid-escape"], "semi" : [2, "never"], "space-after-keywords" : 1, "space-infix-ops" : 0, "strict" : 0 } } fs-vacuum-1.2.10/.gitignore000066400000000000000000000000151306063217100154640ustar00rootroot00000000000000node_modules fs-vacuum-1.2.10/.travis.yml000066400000000000000000000003271306063217100156130ustar00rootroot00000000000000language: node_js node_js: - "5" - "4" - iojs - "0.12" - "0.10" - "0.8" sudo: false script: "npm test" before_install: - "npm -g install npm" notifications: slack: npm-inc:kRqQjto7YbINqHPb1X6nS3g8 fs-vacuum-1.2.10/LICENSE000066400000000000000000000013401306063217100145030ustar00rootroot00000000000000Copyright (c) 2015, Forrest L Norvell Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. fs-vacuum-1.2.10/README.md000066400000000000000000000022761306063217100147660ustar00rootroot00000000000000# fs-vacuum Remove the empty branches of a directory tree, optionally up to (but not including) a specified base directory. Optionally nukes the leaf directory. ## Usage ```javascript var logger = require("npmlog"); var vacuum = require("fs-vacuum"); var options = { base : "/path/to/my/tree/root", purge : true, log : logger.silly.bind(logger, "myCleanup") }; /* Assuming there are no other files or directories in "out", "to", or "my", * the final path will just be "/path/to/my/tree/root". */ vacuum("/path/to/my/tree/root/out/to/my/files", options, function (error) { if (error) console.error("Unable to cleanly vacuum:", error.message); }); ``` # vacuum(directory, options, callback) * `directory` {String} Leaf node to remove. **Must be a directory, symlink, or file.** * `options` {Object} * `base` {String} No directories at or above this level of the filesystem will be removed. * `purge` {Boolean} If set, nuke the whole leaf directory, including its contents. * `log` {Function} A logging function that takes `npmlog`-compatible argument lists. * `callback` {Function} Function to call once vacuuming is complete. * `error` {Error} What went wrong along the way, if anything. fs-vacuum-1.2.10/package.json000066400000000000000000000014751306063217100157750ustar00rootroot00000000000000{ "name": "fs-vacuum", "version": "1.2.10", "description": "recursively remove empty directories -- to a point", "main": "vacuum.js", "scripts": { "test": "standard && tap test/*.js" }, "repository": { "type": "git", "url": "https://github.com/npm/fs-vacuum.git" }, "keywords": [ "rm", "rimraf", "clean" ], "author": "Forrest L Norvell ", "license": "ISC", "bugs": { "url": "https://github.com/npm/fs-vacuum/issues" }, "homepage": "https://github.com/npm/fs-vacuum", "devDependencies": { "mkdirp": "^0.5.1", "standard": "^6.0.8", "errno": "~0.1.0", "require-inject": "~1.3.0", "tap": "^5.7.1", "tmp": "0.0.28" }, "dependencies": { "graceful-fs": "^4.1.2", "path-is-inside": "^1.0.1", "rimraf": "^2.5.2" } } fs-vacuum-1.2.10/test/000077500000000000000000000000001306063217100144575ustar00rootroot00000000000000fs-vacuum-1.2.10/test/arguments.js000066400000000000000000000013161306063217100170230ustar00rootroot00000000000000var test = require('tap').test var vacuum = require('../vacuum.js') test('vacuum throws on missing parameters', function (t) { t.throws(vacuum, 'called with no parameters') t.throws(function () { vacuum('directory', {}) }, 'called with no callback') t.end() }) test('vacuum throws on incorrect types ("Forrest is pedantic" section)', function (t) { t.throws(function () { vacuum({}, {}, function () {}) }, 'called with path parameter of incorrect type') t.throws(function () { vacuum('directory', 'directory', function () {}) }, 'called with options of wrong type') t.throws(function () { vacuum('directory', {}, 'whoops') }, "called with callback that isn't callable") t.end() }) fs-vacuum-1.2.10/test/base-leaf-mismatch.js000066400000000000000000000006251306063217100204420ustar00rootroot00000000000000var test = require('tap').test var vacuum = require('../vacuum.js') test('vacuum errors when base is set and path is not under it', function (t) { vacuum('/a/made/up/path', {base: '/root/elsewhere'}, function (er) { t.ok(er, 'got an error') t.equal( er.message, '/a/made/up/path is not a child of /root/elsewhere', 'got the expected error message' ) t.end() }) }) fs-vacuum-1.2.10/test/no-entries-file-no-purge.js000066400000000000000000000043721306063217100215550ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var statSync = require('graceful-fs').statSync var writeFile = require('graceful-fs').writeFile var readdirSync = require('graceful-fs').readdirSync var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a') var FULL_PATH = path.join(PARTIAL_PATH, 'file') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, partialPath, fullPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) partialPath = path.resolve(tmpdir, PARTIAL_PATH) fullPath = path.resolve(tmpdir, FULL_PATH) mkdirp(partialPath, function (er) { t.ifError(er, 'made test path') writeFile(fullPath, new Buffer('hi'), function (er) { t.ifError(er, 'made file') t.end() }) }) }) }) test('remove up to a point', function (t) { vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 6, 'got 5 removal & 1 finish message') t.equal(messages[5], 'finished vacuuming up to ' + testBase) var stat var verifyPath = fullPath function verify () { stat = statSync(verifyPath) } // handle the file separately t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) for (var i = 0; i < 4; i++) { t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') var files = readdirSync(testBase) t.equal(files.length, 0, 'nothing left in base directory') t.end() }) }) fs-vacuum-1.2.10/test/no-entries-link-no-purge.js000066400000000000000000000045711306063217100215740ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var statSync = require('graceful-fs').statSync var symlinkSync = require('graceful-fs').symlinkSync var readdirSync = require('graceful-fs').readdirSync var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var TARGET_PATH = path.join('target-link', 'in', 'the', 'middle') var PARTIAL_PATH = path.join(SHORT_PATH, 'with', 'a') var FULL_PATH = path.join(PARTIAL_PATH, 'link') var EXPANDO_PATH = path.join(SHORT_PATH, 'with', 'a', 'link', 'in', 'the', 'middle') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, targetPath, partialPath, fullPath, expandoPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) targetPath = path.resolve(tmpdir, TARGET_PATH) partialPath = path.resolve(tmpdir, PARTIAL_PATH) fullPath = path.resolve(tmpdir, FULL_PATH) expandoPath = path.resolve(tmpdir, EXPANDO_PATH) mkdirp(partialPath, function (er) { t.ifError(er, 'made test path') mkdirp(targetPath, function (er) { t.ifError(er, 'made target path') symlinkSync(path.join(tmpdir, 'target-link'), fullPath) t.end() }) }) }) }) test('remove up to a point', function (t) { vacuum(expandoPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 7, 'got 6 removal & 1 finish message') t.equal(messages[6], 'finished vacuuming up to ' + testBase) var stat var verifyPath = expandoPath function verify () { stat = statSync(verifyPath) } for (var i = 0; i < 6; i++) { t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') var files = readdirSync(testBase) t.equal(files.length, 0, 'nothing left in base directory') t.end() }) }) fs-vacuum-1.2.10/test/no-entries-no-purge.js000066400000000000000000000032061306063217100206330ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var statSync = require('graceful-fs').statSync var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var LONG_PATH = path.join(SHORT_PATH, 'of', 'a', 'certain', 'length') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testPath, testBase test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) testPath = path.resolve(tmpdir, LONG_PATH) mkdirp(testPath, function (er) { t.ifError(er, 'made test path') t.end() }) }) }) test('remove up to a point', function (t) { vacuum(testPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 5, 'got 4 removal & 1 finish message') t.equal(messages[4], 'finished vacuuming up to ' + testBase) var stat var verifyPath = testPath function verify () { stat = statSync(verifyPath) } for (var i = 0; i < 4; i++) { t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') t.end() }) }) fs-vacuum-1.2.10/test/no-entries-with-link-purge.js000066400000000000000000000046241306063217100221320ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var statSync = require('graceful-fs').statSync var writeFileSync = require('graceful-fs').writeFileSync var symlinkSync = require('graceful-fs').symlinkSync var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var TARGET_PATH = 'link-target' var FIRST_FILE = path.join(TARGET_PATH, 'monsieurs') var SECOND_FILE = path.join(TARGET_PATH, 'mesdames') var PARTIAL_PATH = path.join(SHORT_PATH, 'with', 'a', 'definite') var FULL_PATH = path.join(PARTIAL_PATH, 'target') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, partialPath, fullPath, targetPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) targetPath = path.resolve(tmpdir, TARGET_PATH) partialPath = path.resolve(tmpdir, PARTIAL_PATH) fullPath = path.resolve(tmpdir, FULL_PATH) mkdirp(partialPath, function (er) { t.ifError(er, 'made test path') mkdirp(targetPath, function (er) { t.ifError(er, 'made target path') writeFileSync(path.resolve(tmpdir, FIRST_FILE), new Buffer("c'est vraiment joli")) writeFileSync(path.resolve(tmpdir, SECOND_FILE), new Buffer('oui oui')) symlinkSync(targetPath, fullPath) t.end() }) }) }) }) test('remove up to a point', function (t) { vacuum(fullPath, {purge: true, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 5, 'got 4 removal & 1 finish message') t.equal(messages[0], 'purging ' + fullPath) t.equal(messages[4], 'finished vacuuming up to ' + testBase) var stat var verifyPath = fullPath function verify () { stat = statSync(verifyPath) } for (var i = 0; i < 4; i++) { t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') t.end() }) }) fs-vacuum-1.2.10/test/no-entries-with-purge.js000066400000000000000000000037711306063217100212010ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var statSync = require('graceful-fs').statSync var writeFileSync = require('graceful-fs').writeFileSync var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var LONG_PATH = path.join(SHORT_PATH, 'of', 'a', 'certain', 'kind') var FIRST_FILE = path.join(LONG_PATH, 'monsieurs') var SECOND_FILE = path.join(LONG_PATH, 'mesdames') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testPath, testBase test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) testPath = path.resolve(tmpdir, LONG_PATH) mkdirp(testPath, function (er) { t.ifError(er, 'made test path') writeFileSync(path.resolve(tmpdir, FIRST_FILE), new Buffer("c'est vraiment joli")) writeFileSync(path.resolve(tmpdir, SECOND_FILE), new Buffer('oui oui')) t.end() }) }) }) test('remove up to a point', function (t) { vacuum(testPath, {purge: true, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 5, 'got 4 removal & 1 finish message') t.equal(messages[0], 'purging ' + testPath) t.equal(messages[4], 'finished vacuuming up to ' + testBase) var stat var verifyPath = testPath function verify () { stat = statSync(verifyPath) } for (var i = 0; i < 4; i++) { t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') t.end() }) }) fs-vacuum-1.2.10/test/not-remove-home-directory.js000066400000000000000000000022061306063217100220400ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var BASE_PATH = path.join('foo') var HOME_PATH = path.join(BASE_PATH, 'foo', 'bar') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var homePath, basePath, realHome test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') homePath = path.resolve(tmpdir, HOME_PATH) basePath = path.resolve(tmpdir, BASE_PATH) realHome = process.env.HOME process.env.HOME = homePath mkdirp(homePath, function (er) { t.ifError(er, 'made test path') t.end() }) }) }) test('do not remove home directory', function (t) { vacuum(homePath, {purge: false, base: basePath, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages[0], 'quitting because cannot remove home directory ' + homePath) process.env.HOME = realHome t.end() }) }) fs-vacuum-1.2.10/test/other-directories-no-purge.js000066400000000000000000000042241306063217100222040ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var statSync = require('graceful-fs').statSync var mkdtemp = require('tmp').dir var mkdirp = require('mkdirp') var vacuum = require('../vacuum.js') // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var REMOVE_PATH = path.join(SHORT_PATH, 'of', 'a', 'certain', 'length') var OTHER_PATH = path.join(SHORT_PATH, 'of', 'no', 'qualities') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, testPath, otherPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) testPath = path.resolve(tmpdir, REMOVE_PATH) otherPath = path.resolve(tmpdir, OTHER_PATH) mkdirp(testPath, function (er) { t.ifError(er, 'made test path') mkdirp(otherPath, function (er) { t.ifError(er, 'made other path') t.end() }) }) }) }) test('remove up to a point', function (t) { vacuum(testPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 4, 'got 3 removal & 1 finish message') t.equal( messages[3], 'quitting because other entries in ' + testBase + '/of', 'got expected final message' ) var stat var verifyPath = testPath function verify () { stat = statSync(verifyPath) } for (var i = 0; i < 3; i++) { t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isDirectory(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(otherPath) }, otherPath + ' can be statted') t.ok(stat && stat.isDirectory(), otherPath + ' is still a directory') var intersection = path.join(testBase, 'of') t.doesNotThrow(function () { stat = statSync(intersection) }, intersection + ' can be statted') t.ok(stat && stat.isDirectory(), intersection + ' is still a directory') t.end() }) }) fs-vacuum-1.2.10/test/racy-entries-eexist.js000066400000000000000000000070661306063217100207320ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var readdir = require('graceful-fs').readdir var readdirSync = require('graceful-fs').readdirSync var rmdir = require('graceful-fs').rmdir var statSync = require('graceful-fs').statSync var writeFile = require('graceful-fs').writeFile var mkdirp = require('mkdirp') var mkdtemp = require('tmp').dir var tmpFile = require('tmp').file var EEXIST = require('errno').code.EEXIST var ENOTEMPTY = require('errno').code.ENOTEMPTY var requireInject = require('require-inject') var vacuum = requireInject('../vacuum.js', { 'graceful-fs': { 'lstat': require('graceful-fs').lstat, 'readdir': function (dir, cb) { readdir(dir, function (err, files) { // Simulate racy removal by creating a file after vacuum // thinks the directory is empty if (dir === partialPath && files.length === 0) { tmpFile({dir: dir}, function (err, path, fd) { if (err) throw err cb(err, files) }) } else { cb(err, files) } }) }, 'rmdir': function (dir, cb) { rmdir(dir, function (err) { // Force EEXIST error from rmdir if the directory is non-empty var mockErr = EEXIST if (err) { if (err.code === ENOTEMPTY.code) { err.code = err.errno = mockErr.code err.message = mockErr.code + ': ' + mockErr.description + ', ' + err.syscall + ' \'' + dir + '\'' } } cb(err) }) }, 'unlink': require('graceful-fs').unlink } }) // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a') var FULL_PATH = path.join(PARTIAL_PATH, 'file') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, partialPath, fullPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) partialPath = path.resolve(tmpdir, PARTIAL_PATH) fullPath = path.resolve(tmpdir, FULL_PATH) mkdirp(partialPath, function (er) { t.ifError(er, 'made test path') writeFile(fullPath, new Buffer('hi'), function (er) { t.ifError(er, 'made file') t.end() }) }) }) }) test('racy removal should quit gracefully', function (t) { vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 3, 'got 2 removal & 1 quit message') t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath) var stat var verifyPath = fullPath function verify () { stat = statSync(verifyPath) } // handle the file separately t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) for (var i = 0; i < 4; i++) { t.doesNotThrow(function () { stat = statSync(verifyPath) }, verifyPath + ' can be statted') t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') var files = readdirSync(testBase) t.equal(files.length, 1, 'base directory untouched') t.end() }) }) fs-vacuum-1.2.10/test/racy-entries-enotempty.js000066400000000000000000000070711306063217100214510ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var readdir = require('graceful-fs').readdir var readdirSync = require('graceful-fs').readdirSync var rmdir = require('graceful-fs').rmdir var statSync = require('graceful-fs').statSync var writeFile = require('graceful-fs').writeFile var mkdirp = require('mkdirp') var mkdtemp = require('tmp').dir var tmpFile = require('tmp').file var EEXIST = require('errno').code.EEXIST var ENOTEMPTY = require('errno').code.ENOTEMPTY var requireInject = require('require-inject') var vacuum = requireInject('../vacuum.js', { 'graceful-fs': { 'lstat': require('graceful-fs').lstat, 'readdir': function (dir, cb) { readdir(dir, function (err, files) { // Simulate racy removal by creating a file after vacuum // thinks the directory is empty if (dir === partialPath && files.length === 0) { tmpFile({dir: dir}, function (err, path, fd) { if (err) throw err cb(err, files) }) } else { cb(err, files) } }) }, 'rmdir': function (dir, cb) { rmdir(dir, function (err) { // Force ENOTEMPTY error from rmdir if the directory is non-empty var mockErr = ENOTEMPTY if (err) { if (err.code === EEXIST.code) { err.code = err.errno = mockErr.code err.message = mockErr.code + ': ' + mockErr.description + ', ' + err.syscall + ' \'' + dir + '\'' } } cb(err) }) }, 'unlink': require('graceful-fs').unlink } }) // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a') var FULL_PATH = path.join(PARTIAL_PATH, 'file') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, partialPath, fullPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) partialPath = path.resolve(tmpdir, PARTIAL_PATH) fullPath = path.resolve(tmpdir, FULL_PATH) mkdirp(partialPath, function (er) { t.ifError(er, 'made test path') writeFile(fullPath, new Buffer('hi'), function (er) { t.ifError(er, 'made file') t.end() }) }) }) }) test('racy removal should quit gracefully', function (t) { vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 3, 'got 2 removal & 1 quit message') t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath) var stat var verifyPath = fullPath function verify () { stat = statSync(verifyPath) } // handle the file separately t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) for (var i = 0; i < 4; i++) { t.doesNotThrow(function () { stat = statSync(verifyPath) }, verifyPath + ' can be statted') t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') var files = readdirSync(testBase) t.equal(files.length, 1, 'base directory untouched') t.end() }) }) fs-vacuum-1.2.10/test/racy-entries.js000066400000000000000000000060421306063217100174240ustar00rootroot00000000000000var path = require('path') var test = require('tap').test var readdir = require('graceful-fs').readdir var readdirSync = require('graceful-fs').readdirSync var statSync = require('graceful-fs').statSync var writeFile = require('graceful-fs').writeFile var mkdirp = require('mkdirp') var mkdtemp = require('tmp').dir var tmpFile = require('tmp').file var requireInject = require('require-inject') var vacuum = requireInject('../vacuum.js', { 'graceful-fs': { 'lstat': require('graceful-fs').lstat, 'readdir': function (dir, cb) { readdir(dir, function (err, files) { // Simulate racy removal by creating a file after vacuum // thinks the directory is empty if (dir === partialPath && files.length === 0) { tmpFile({dir: dir}, function (err, path, fd) { if (err) throw err cb(err, files) }) } else { cb(err, files) } }) }, 'rmdir': require('graceful-fs').rmdir, 'unlink': require('graceful-fs').unlink } }) // CONSTANTS var TEMP_OPTIONS = { unsafeCleanup: true, mode: '0700' } var SHORT_PATH = path.join('i', 'am', 'a', 'path') var PARTIAL_PATH = path.join(SHORT_PATH, 'that', 'ends', 'at', 'a') var FULL_PATH = path.join(PARTIAL_PATH, 'file') var messages = [] function log () { messages.push(Array.prototype.slice.call(arguments).join(' ')) } var testBase, partialPath, fullPath test('xXx setup xXx', function (t) { mkdtemp(TEMP_OPTIONS, function (er, tmpdir) { t.ifError(er, 'temp directory exists') testBase = path.resolve(tmpdir, SHORT_PATH) partialPath = path.resolve(tmpdir, PARTIAL_PATH) fullPath = path.resolve(tmpdir, FULL_PATH) mkdirp(partialPath, function (er) { t.ifError(er, 'made test path') writeFile(fullPath, new Buffer('hi'), function (er) { t.ifError(er, 'made file') t.end() }) }) }) }) test('racy removal should quit gracefully', function (t) { vacuum(fullPath, {purge: false, base: testBase, log: log}, function (er) { t.ifError(er, 'cleaned up to base') t.equal(messages.length, 3, 'got 2 removal & 1 quit message') t.equal(messages[2], 'quitting because new (racy) entries in ' + partialPath) var stat var verifyPath = fullPath function verify () { stat = statSync(verifyPath) } // handle the file separately t.throws(verify, verifyPath + ' cannot be statted') t.notOk(stat && stat.isFile(), verifyPath + ' is totally gone') verifyPath = path.dirname(verifyPath) for (var i = 0; i < 4; i++) { t.doesNotThrow(function () { stat = statSync(verifyPath) }, verifyPath + ' can be statted') t.ok(stat && stat.isDirectory(), verifyPath + ' is still a directory') verifyPath = path.dirname(verifyPath) } t.doesNotThrow(function () { stat = statSync(testBase) }, testBase + ' can be statted') t.ok(stat && stat.isDirectory(), testBase + ' is still a directory') var files = readdirSync(testBase) t.equal(files.length, 1, 'base directory untouched') t.end() }) }) fs-vacuum-1.2.10/vacuum.js000066400000000000000000000063461306063217100153470ustar00rootroot00000000000000var assert = require('assert') var dirname = require('path').dirname var resolve = require('path').resolve var isInside = require('path-is-inside') var rimraf = require('rimraf') var lstat = require('graceful-fs').lstat var readdir = require('graceful-fs').readdir var rmdir = require('graceful-fs').rmdir var unlink = require('graceful-fs').unlink module.exports = vacuum function vacuum (leaf, options, cb) { assert(typeof leaf === 'string', 'must pass in path to remove') assert(typeof cb === 'function', 'must pass in callback') if (!options) options = {} assert(typeof options === 'object', 'options must be an object') var log = options.log ? options.log : function () {} leaf = leaf && resolve(leaf) var base = options.base && resolve(options.base) if (base && !isInside(leaf, base)) { return cb(new Error(leaf + ' is not a child of ' + base)) } lstat(leaf, function (error, stat) { if (error) { if (error.code === 'ENOENT') return cb(null) log(error.stack) return cb(error) } if (!(stat && (stat.isDirectory() || stat.isSymbolicLink() || stat.isFile()))) { log(leaf, 'is not a directory, file, or link') return cb(new Error(leaf + ' is not a directory, file, or link')) } if (options.purge) { log('purging', leaf) rimraf(leaf, function (error) { if (error) return cb(error) next(dirname(leaf)) }) } else if (!stat.isDirectory()) { log('removing', leaf) unlink(leaf, function (error) { if (error) return cb(error) next(dirname(leaf)) }) } else { next(leaf) } }) function next (branch) { branch = branch && resolve(branch) // either we've reached the base or we've reached the root if ((base && branch === base) || branch === dirname(branch)) { log('finished vacuuming up to', branch) return cb(null) } readdir(branch, function (error, files) { if (error) { if (error.code === 'ENOENT') return cb(null) log('unable to check directory', branch, 'due to', error.message) return cb(error) } if (files.length > 0) { log('quitting because other entries in', branch) return cb(null) } if (branch === process.env.HOME) { log('quitting because cannot remove home directory', branch) return cb(null) } log('removing', branch) lstat(branch, function (error, stat) { if (error) { if (error.code === 'ENOENT') return cb(null) log('unable to lstat', branch, 'due to', error.message) return cb(error) } var remove = stat.isDirectory() ? rmdir : unlink remove(branch, function (error) { if (error) { if (error.code === 'ENOENT') { log('quitting because lost the race to remove', branch) return cb(null) } if (error.code === 'ENOTEMPTY' || error.code === 'EEXIST') { log('quitting because new (racy) entries in', branch) return cb(null) } log('unable to remove', branch, 'due to', error.message) return cb(error) } next(dirname(branch)) }) }) }) } }