diff options
Diffstat (limited to 'deps/node/deps/npm/node_modules/gentle-fs/lib/rm.js')
-rw-r--r-- | deps/node/deps/npm/node_modules/gentle-fs/lib/rm.js | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/deps/node/deps/npm/node_modules/gentle-fs/lib/rm.js b/deps/node/deps/npm/node_modules/gentle-fs/lib/rm.js new file mode 100644 index 00000000..00760c66 --- /dev/null +++ b/deps/node/deps/npm/node_modules/gentle-fs/lib/rm.js @@ -0,0 +1,256 @@ +'use strict' + +const path = require('path') +const validate = require('aproba') +const fs = require('graceful-fs') +const isInside = require('path-is-inside') +const vacuum = require('fs-vacuum') +const chain = require('slide').chain +const asyncMap = require('slide').asyncMap +const readCmdShim = require('read-cmd-shim') +const iferr = require('iferr') + +exports = module.exports = rm + +function rm (target, opts, cb) { + var targetPath = path.normalize(path.resolve(opts.prefix, target)) + if (opts.prefixes.indexOf(targetPath) !== -1) { + return cb(new Error('May not delete: ' + targetPath)) + } + var options = {} + if (opts.force) { options.purge = true } + if (opts.base) options.base = path.normalize(path.resolve(opts.prefix, opts.base)) + + if (!opts.gently) { + options.purge = true + return vacuum(targetPath, options, cb) + } + + var parent = options.base = options.base || path.normalize(opts.prefix) + + // Do all the async work we'll need to do in order to tell if this is a + // safe operation + chain([ + [isEverInside, parent, opts.prefixes, opts.log], + [readLinkOrShim, targetPath], + [isEverInside, targetPath, opts.prefixes, opts.log], + [isEverInside, targetPath, [parent], opts.log] + ], function (er, results) { + if (er) { + if (er.code === 'ENOENT') return cb() + return cb(er) + } + var parentInfo = { + path: parent, + managed: results[0] + } + var targetInfo = { + path: targetPath, + symlink: results[1], + managed: results[2], + inParent: results[3] + } + + isSafeToRm(parentInfo, targetInfo, opts.name, opts.log, iferr(cb, thenRemove)) + + function thenRemove (toRemove, removeBase) { + if (!toRemove) return cb() + if (removeBase) options.base = removeBase + return vacuum(toRemove, options, cb) + } + }) +} + +exports._isSafeToRm = isSafeToRm +function isSafeToRm (parent, target, pkgName, log, cb) { + log.silly('gentlyRm', 'parent.path =', parent.path) + log.silly('gentlyRm', 'parent.managed =', + parent.managed && parent.managed.target + ' is in ' + parent.managed.path) + log.silly('gentlyRm', 'target.path = ', target.path) + log.silly('gentlyRm', 'target.symlink =', target.symlink) + log.silly('gentlyRm', 'target.managed =', + target.managed && target.managed.target + ' is in ' + target.managed.path) + log.silly('gentlyRm', 'target.inParent = ', target.inParent) + + // The parent directory or something it symlinks to must eventually be in + // a folder that we maintain. + if (!parent.managed) { + log.info('gentlyRm', parent.path, + 'is not contained in any directory ' + pkgName + ' is known to control or ' + + 'any place they link to') + return cb(clobberFail(target.path, 'containing path ' + parent.path + + " isn't under " + pkgName + "'s control")) + } + + // The target or something it symlinks to must eventually be in the parent + // or something the parent symlinks to + if (target.inParent) { + var actualTarget = target.inParent.target + var targetsParent = target.inParent.path + // if the target.path was what we found in some version of parent, remove + // using that parent as the base + if (target.path === actualTarget) { + return cb(null, target.path, targetsParent) + } else { + // If something the target.path links to was what was found, just + // remove target.path in the location it was found. + return cb(null, target.path, path.dirname(target.path)) + } + } + + // If the target is in a managed directory and is in a symlink, but was + // not in our parent that usually means someone else installed a bin file + // with the same name as one of our bin files. + if (target.managed && target.symlink) { + log.warn('rm', 'not removing', target.path, + "as it wasn't installed by", parent.path) + return cb() + } + + if (target.symlink) { + return cb(clobberFail(target.path, target.symlink + + ' symlink target is not controlled by ' + pkgName + ' ' + parent.path)) + } else { + return cb(clobberFail(target.path, 'is outside ' + parent.path + + ' and not a link')) + } +} + +function clobberFail (target, msg) { + validate('SS', arguments) + var er = new Error('Refusing to delete ' + target + ': ' + msg) + er.code = 'EEXIST' + er.path = target + return er +} + +function isENOENT (err) { + return err && err.code === 'ENOENT' +} + +function notENOENT (err) { + return !isENOENT(err) +} + +function skipENOENT (cb) { + return function (err, value) { + if (isENOENT(err)) { + return cb(null, false) + } else { + return cb(err, value) + } + } +} + +function errorsToValues (fn) { + return function () { + var args = Array.prototype.slice.call(arguments) + var cb = args.pop() + args.push(function (err, value) { + if (err) { + return cb(null, err) + } else { + return cb(null, value) + } + }) + fn.apply(null, args) + } +} + +function isNotError (value) { + return !(value instanceof Error) +} + +exports._isEverInside = isEverInside +// return the first of path, where target (or anything it symlinks to) +// isInside the path (or anything it symlinks to) +function isEverInside (target, paths, log, cb) { + validate('SAOF', arguments) + asyncMap(paths, errorsToValues(readAllLinks), iferr(cb, function (resolvedPaths) { + var errorFree = resolvedPaths.filter(isNotError) + if (errorFree.length === 0) { + var badErrors = resolvedPaths.filter(notENOENT) + if (badErrors.length === 0) { + return cb(null, false) + } else { + return cb(badErrors[0]) + } + } + readAllLinks(target, iferr(skipENOENT(cb), function (targets) { + cb(null, areAnyInsideAny(targets, errorFree, log)) + })) + })) +} + +exports._areAnyInsideAny = areAnyInsideAny +// Return the first path found that any target is inside +function areAnyInsideAny (targets, paths, log) { + validate('AAO', arguments) + var toCheck = [] + paths.forEach(function (path) { + targets.forEach(function (target) { + toCheck.push([target, path]) + }) + }) + for (var ii = 0; ii < toCheck.length; ++ii) { + var target = toCheck[ii][0] + var path = toCheck[ii][1] + var inside = isInside(target, path) + if (!inside) log.silly('isEverInside', target, 'is not inside', path) + if (inside && path) return inside && path && {target: target, path: path} + } + return false +} + +exports._readAllLinks = readAllLinks +// resolves chains of symlinks of unlimited depth, returning a list of paths +// it's seen in the process when it hits either a symlink cycle or a +// non-symlink +function readAllLinks (path, cb) { + validate('SF', arguments) + var seen = {} + _readAllLinks(path) + + function _readAllLinks (path) { + if (seen[path]) return cb(null, Object.keys(seen)) + seen[path] = true + resolveSymlink(path, iferr(cb, _readAllLinks)) + } +} + +exports._resolveSymlink = resolveSymlink +var resolvedPaths = {} +function resolveSymlink (symlink, cb) { + validate('SF', arguments) + var cached = resolvedPaths[symlink] + if (cached) return cb(null, cached) + + readLinkOrShim(symlink, iferr(cb, function (symlinkTarget) { + if (symlinkTarget) { + resolvedPaths[symlink] = path.resolve(path.dirname(symlink), symlinkTarget) + } else { + resolvedPaths[symlink] = symlink + } + return cb(null, resolvedPaths[symlink]) + })) +} + +exports._readLinkOrShim = readLinkOrShim +function readLinkOrShim (path, cb) { + validate('SF', arguments) + fs.lstat(path, iferr(cb, function (stat) { + if (stat.isSymbolicLink()) { + fs.readlink(path, cb) + } else { + readCmdShim(path, function (er, source) { + if (!er) return cb(null, source) + // lstat wouldn't return an error on these, so we don't either. + if (er.code === 'ENOTASHIM' || er.code === 'EISDIR') { + return cb(null, null) + } else { + return cb(er) + } + }) + } + })) +} |