From dea2331377632b2ca6f0c106e57774c4453b33ca Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Thu, 6 Jan 2011 18:30:03 +0100 Subject: [PATCH] Rework fs.realpath, primordal windows compatibility --- lib/fs.js | 269 +++++++++++++++++++++++++++++------------------------- 1 file changed, 144 insertions(+), 125 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index af18877a8d..3719a97b9f 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -487,150 +487,169 @@ fs.unwatchFile = function(filename) { }; // Realpath - -var path = require('path'); -var normalize = path.normalize; -var normalizeArray = path.normalizeArray; - -// realpath // Not using realpath(2) because it's bad. // See: http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html -fs.realpathSync = realpathSync; -fs.realpath = realpath; -function realpathSync(p) { - if (p.charAt(0) !== '/') { - p = path.join(process.cwd(), p); - } - p = path.split(p); - var buf = []; - var seenLinks = {}; - var knownHard = {}; - // walk down the path, swapping out linked pathparts for their real - // values, and pushing non-link path bits onto the buffer. - // then return the buffer. - // NB: path.length changes. - for (var i = 0; i < p.length; i++) { - // skip over empty path parts. - if (p[i] === '') continue; - - var part = path.join.apply(path, buf.concat(p[i])); - - if (knownHard[part]) { - buf.push(p[i]); - continue; - } - var stat = fs.lstatSync(part); - if (!stat.isSymbolicLink()) { - // not a symlink. easy. - knownHard[part] = true; - buf.push(p[i]); - continue; - } +var path = require('path'), + normalize = path.normalize, + isWindows = process.platform === 'win32'; + +if (isWindows) { + // Node doesn't support symlinks / lstat on windows. Hence realpatch is just + // the same as path.resolve that fails if the path doesn't exists. + + // windows version + fs.realpathSync = function realpathSync(p) { + var p = path.resolve(p); + fs.statSync(p); + return p; + }; + + // windows version + fs.realpath = function(p, cb) { + var p = path.resolve(p); + fs.stat(p, function(err) { + if (err) cb(err); + cb(null, p); + }); + }; + + +} else /* posix */ { + + // Regexp that finds the next partion of a (partial) path + // result is [base_with_slash, base], e.g. ['somedir/', 'somedir'] + var nextPartRe = /(.*?)(?:[\/]+|$)/g; + + // posix version + fs.realpathSync = function realpathSync(p) { + // make p is absolute + p = path.resolve(p); + + var seenLinks = {}, + knownHard = {}; + + var pos = 0, // current character position in p + current = "", // the partial path so far, including a trailing slash if any + base = "", // the partial path without a trailing slash + previous = ""; // the partial path scanned in the previous round, with slash + + // walk down the path, swapping out linked pathparts for their real + // values + // NB: p.length changes. + while (pos < p.length) { + // find the next part + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; + + // continue if not a symlink, or if root + if (!base || knownHard[base]) { + continue; + } + var stat = fs.lstatSync(base); + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + continue; + } - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (!seenLinks[id]) { - fs.statSync(part); - seenLinks[id] = fs.readlinkSync(part); - } + // read the link if it wasn't read before + var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + if (!seenLinks[id]) { + fs.statSync(base); + seenLinks[id] = fs.readlinkSync(base); + } - var target = seenLinks[id]; - if (target.charAt(0) === '/') { - // absolute. Start over. - buf = []; - p = path.normalizeArray(path.split(target).concat(p.slice(i + 1))); - i = -1; - continue; + // resolve the link, then start over + p = path.resolve(previous, seenLinks[id], p.slice(pos)); + pos = 0; + previous = base = current = ""; } - // not absolute. join and splice. - if (i === 0 && p[i].charAt(0) === '/') { - target = '/' + target; - } - target = path.split(target); - Array.prototype.splice.apply(p, [i, 1].concat(target)); - p = path.normalizeArray(p); - i = -1; - buf = []; - } - return path.join(buf.join('/') || '/'); -} + return p; + }; -function realpath(p, cb) { - if (p.charAt(0) !== '/') { - p = path.join(process.cwd(), p); - } - p = path.split(p); - var buf = []; - var seenLinks = {}; - var knownHard = {}; - // walk down the path, swapping out linked pathparts for their real - // values, and pushing non-link path bits onto the buffer. - // then return the buffer. - // NB: path.length changes. - var i = -1; - var part; - - LOOP(); - function LOOP() { - i++; - if (!(i < p.length)) return exit(); - // skip over empty path parts. - if (p[i] === '') return process.nextTick(LOOP); - part = path.join(buf.join('/') + '/' + p[i]); - if (knownHard[part]) { - buf.push(p[i]); - return process.nextTick(LOOP); - } - return fs.lstat(part, gotStat); - } + // posix version + fs.realpath = function realpath(p, cb) { + // make p is absolute + p = path.resolve(p); - function gotStat(er, stat) { - if (er) return cb(er); - if (!stat.isSymbolicLink()) { - // not a symlink. easy. - knownHard[part] = true; - buf.push(p[i]); - return process.nextTick(LOOP); + var seenLinks = {}, + knownHard = {}; + + var pos = 0, // current character position in p + current = "", // the partial path so far, including a trailing slash if any + base = "", // the partial path without a trailing slash + previous = ""; // the partial path scanned in the previous round, with slash + + // walk down the path, swapping out linked pathparts for their real + // values + LOOP(); + function LOOP() { + // stop if scanned past end of path + if (pos >= p.length) { + return cb(null, p); + } + + // find the next part + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; + + // continue if known to be hard or if root + if (!base || knownHard[base]) { + return process.nextTick(LOOP); + } + + return fs.lstat(base, gotStat); } - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (seenLinks[id]) return gotTarget(null, seenLinks[id]); - fs.stat(part, function(er) { - if (er) return cb(er); - fs.readlink(part, function(er, target) { - gotTarget(er, seenLinks[id] = target); + function gotStat(err, stat) { + if (err) return cb(err); + + // if not a symlink, skip to the next path part + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + return process.nextTick(LOOP); + } + + // stat & read the link if not read before + // call gotTarget as soon as the link target is known + var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + if (seenLinks[id]) { + return gotTarget(null, seenLinks[id]); + } + fs.stat(base, function(err) { + if (err) return cb(err); + + fs.readlink(base, function(err, target) { + gotTarget(err, seenLinks[id] = target); + }); }); - }); - } + } + + function gotTarget(err, target) { + if (err) return cb(err); + + // resolve the link, then start over + p = path.resolve(previous, target, p.slice(pos)); + pos = 0; + previous = base = current = ""; - function gotTarget(er, target) { - if (er) return cb(er); - if (target.charAt(0) === '/') { - // absolute. Start over. - buf = []; - p = path.normalizeArray(path.split(target).concat(p.slice(i + 1))); - i = -1; return process.nextTick(LOOP); } - // not absolute. join and splice. - if (i === 0 && p[i].charAt(0) === '/') { - target = '/' + target; - } - target = path.split(target); - Array.prototype.splice.apply(p, [i, 1].concat(target)); - p = path.normalizeArray(p); - i = -1; - buf = []; - return process.nextTick(LOOP); - } + }; - function exit() { - cb(null, path.join(buf.join('/') || '/')); - } } + var pool; function allocNewPool() {