Rework fs.realpath, primordal windows compatibility
authorBert Belder <bertbelder@gmail.com>
Thu, 6 Jan 2011 17:30:03 +0000 (18:30 +0100)
committerRyan Dahl <ry@tinyclouds.org>
Thu, 6 Jan 2011 23:38:50 +0000 (15:38 -0800)
lib/fs.js

index af18877..3719a97 100644 (file)
--- 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() {