Rework fs.realpath, primordal windows compatibility

This commit is contained in:
Bert Belder 2011-01-06 18:30:03 +01:00 committed by Ryan Dahl
parent 7c731ec9dd
commit dea2331377

233
lib/fs.js
View File

@ -487,149 +487,168 @@ fs.unwatchFile = function(filename) {
}; };
// Realpath // Realpath
var path = require('path');
var normalize = path.normalize;
var normalizeArray = path.normalizeArray;
// realpath
// Not using realpath(2) because it's bad. // Not using realpath(2) because it's bad.
// See: http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html // See: http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
fs.realpathSync = realpathSync;
fs.realpath = realpath; var path = require('path'),
function realpathSync(p) { normalize = path.normalize,
if (p.charAt(0) !== '/') { isWindows = process.platform === 'win32';
p = path.join(process.cwd(), p);
} if (isWindows) {
p = path.split(p); // Node doesn't support symlinks / lstat on windows. Hence realpatch is just
var buf = []; // the same as path.resolve that fails if the path doesn't exists.
var seenLinks = {};
var knownHard = {}; // 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 // walk down the path, swapping out linked pathparts for their real
// values, and pushing non-link path bits onto the buffer. // values
// then return the buffer. // NB: p.length changes.
// NB: path.length changes. while (pos < p.length) {
for (var i = 0; i < p.length; i++) { // find the next part
// skip over empty path parts. nextPartRe.lastIndex = pos;
if (p[i] === '') continue; var result = nextPartRe.exec(p);
previous = current;
current += result[0];
base = previous + result[1];
pos = nextPartRe.lastIndex;
var part = path.join.apply(path, buf.concat(p[i])); // continue if not a symlink, or if root
if (!base || knownHard[base]) {
if (knownHard[part]) {
buf.push(p[i]);
continue; continue;
} }
var stat = fs.lstatSync(base);
var stat = fs.lstatSync(part);
if (!stat.isSymbolicLink()) { if (!stat.isSymbolicLink()) {
// not a symlink. easy. knownHard[base] = true;
knownHard[part] = true;
buf.push(p[i]);
continue; continue;
} }
// read the link if it wasn't read before
var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
if (!seenLinks[id]) { if (!seenLinks[id]) {
fs.statSync(part); fs.statSync(base);
seenLinks[id] = fs.readlinkSync(part); seenLinks[id] = fs.readlinkSync(base);
} }
var target = seenLinks[id]; // resolve the link, then start over
if (target.charAt(0) === '/') { p = path.resolve(previous, seenLinks[id], p.slice(pos));
// absolute. Start over. pos = 0;
buf = []; previous = base = current = "";
p = path.normalizeArray(path.split(target).concat(p.slice(i + 1)));
i = -1;
continue;
} }
// not absolute. join and splice. return p;
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('/') || '/');
}
function realpath(p, cb) { // posix version
if (p.charAt(0) !== '/') { fs.realpath = function realpath(p, cb) {
p = path.join(process.cwd(), p); // make p is absolute
} p = path.resolve(p);
p = path.split(p);
var buf = []; var seenLinks = {},
var seenLinks = {}; knownHard = {};
var 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 // walk down the path, swapping out linked pathparts for their real
// values, and pushing non-link path bits onto the buffer. // values
// then return the buffer.
// NB: path.length changes.
var i = -1;
var part;
LOOP(); LOOP();
function LOOP() { function LOOP() {
i++; // stop if scanned past end of path
if (!(i < p.length)) return exit(); if (pos >= p.length) {
// skip over empty path parts. return cb(null, p);
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);
} }
function gotStat(er, stat) { // find the next part
if (er) return cb(er); 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);
}
function gotStat(err, stat) {
if (err) return cb(err);
// if not a symlink, skip to the next path part
if (!stat.isSymbolicLink()) { if (!stat.isSymbolicLink()) {
// not a symlink. easy. knownHard[base] = true;
knownHard[part] = true;
buf.push(p[i]);
return process.nextTick(LOOP); 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); var id = stat.dev.toString(32) + ':' + stat.ino.toString(32);
if (seenLinks[id]) return gotTarget(null, seenLinks[id]); if (seenLinks[id]) {
fs.stat(part, function(er) { return gotTarget(null, seenLinks[id]);
if (er) return cb(er); }
fs.readlink(part, function(er, target) { fs.stat(base, function(err) {
gotTarget(er, seenLinks[id] = target); if (err) return cb(err);
fs.readlink(base, function(err, target) {
gotTarget(err, seenLinks[id] = target);
}); });
}); });
} }
function gotTarget(er, target) { function gotTarget(err, target) {
if (er) return cb(er); if (err) return cb(err);
if (target.charAt(0) === '/') {
// absolute. Start over. // resolve the link, then start over
buf = []; p = path.resolve(previous, target, p.slice(pos));
p = path.normalizeArray(path.split(target).concat(p.slice(i + 1))); pos = 0;
i = -1; previous = base = current = "";
return process.nextTick(LOOP); 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; var pool;