Windows: correctly resolve drive-relative paths
authorBert Belder <bertbelder@gmail.com>
Thu, 24 Nov 2011 01:38:34 +0000 (02:38 +0100)
committerBert Belder <bertbelder@gmail.com>
Thu, 24 Nov 2011 01:38:34 +0000 (02:38 +0100)
lib/path.js
src/node.cc

index c843d53..162a15f 100644 (file)
@@ -86,7 +86,18 @@ if (isWindows) {
         resolvedAbsolute = false;
 
     for (var i = arguments.length - 1; i >= -1; i--) {
-      var path = (i >= 0) ? arguments[i] : process.cwd();
+      var path;
+      if (i >= 0) {
+        path = arguments[i];
+      } else if (!resolvedDevice) {
+        path = process.cwd();
+      } else {
+        // Windows has the concept of drive-specific current working
+        // directories. If we've resolved a drive letter but not yet an
+        // absolute path, get cwd for that drive. We're sure the device is not
+        // an unc path at this points, because unc paths are always absolute.
+        path = process._cwdForDrive(resolvedDevice[0]);
+      }
 
       // Skip empty and invalid entries
       if (typeof path !== 'string' || !path) {
@@ -119,21 +130,6 @@ if (isWindows) {
       }
     }
 
-    if (!resolvedAbsolute && resolvedDevice) {
-      // If we still don't have an absolute path,
-      // prepend the current path for the device found.
-
-      // TODO
-      // Windows stores the current directories for 'other' drives
-      // as hidden environment variables like =C:=c:\windows (literally)
-      // var deviceCwd = os.getCwdForDrive(resolvedDevice);
-      var deviceCwd = '';
-
-      // If there is no cwd set for the drive, it is at root
-      resolvedTail = deviceCwd + '\\' + resolvedTail;
-      resolvedAbsolute = true;
-    }
-
     // Replace slashes (in UNC share name) by backslashes
     resolvedDevice = resolvedDevice.replace(/\//g, '\\');
 
index 1f5e2c9..d61fd82 100644 (file)
@@ -1178,6 +1178,76 @@ static Handle<Value> Cwd(const Arguments& args) {
   return scope.Close(cwd);
 }
 
+#ifdef _WIN32
+static Handle<Value> CwdForDrive(const Arguments& args) {
+  HandleScope scope;
+
+  if (args.Length() < 1) {
+    Local<Value> exception = Exception::Error(
+        String::New("process._cwdForDrive takes exactly 1 argument."));
+    return ThrowException(exception);
+  }
+
+  Local<String> driveLetter = args[0]->ToString();
+  if (driveLetter->Length() != 1) {
+    Local<Value> exception = Exception::Error(
+        String::New("Drive name should be 1 character."));
+    return ThrowException(exception);
+  }
+
+  char drive;
+
+  driveLetter->WriteAscii(&drive, 0, 1, 0);
+  if (drive >= 'a' && drive <= 'z') {
+    // Convert to uppercase
+    drive += 'A' - 'a';
+  } else if (drive < 'A' || drive > 'Z') {
+    // Not a letter
+    Local<Value> exception = Exception::Error(
+        String::New("Drive name should be a letter."));
+    return ThrowException(exception);
+  }
+
+  WCHAR env_key[] = L"=X:";
+  env_key[1] = (WCHAR) drive;
+
+  DWORD len = GetEnvironmentVariableW(env_key, NULL, 0);
+  if (len == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
+    // There is no current directory for that drive. Default to drive + ":\".
+    Local<String> cwd = String::Concat(String::New(&drive, 1),
+                                       String::New(":\\"));
+    return scope.Close(cwd);
+
+  } else if (len == 0) {
+    // Error
+    Local<Value> exception = Exception::Error(
+      String::New(winapi_strerror(GetLastError())));
+    return ThrowException(exception);
+  }
+
+  WCHAR* buffer = new WCHAR[len];
+  if (buffer == NULL) {
+    Local<Value> exception = Exception::Error(
+        String::New("Out of memory."));
+    return ThrowException(exception);
+  }
+
+  DWORD len2 = GetEnvironmentVariableW(env_key, buffer, len);
+  if (len2 == 0 || len2 >= len) {
+    // Error
+    delete[] buffer;
+    Local<Value> exception = Exception::Error(
+      String::New(winapi_strerror(GetLastError())));
+    return ThrowException(exception);
+  }
+
+  Local<String> cwd = String::New(reinterpret_cast<uint16_t*>(buffer), len2);
+  delete[] buffer;
+  return scope.Close(cwd);
+}
+#endif
+
+
 static Handle<Value> Umask(const Arguments& args) {
   HandleScope scope;
   unsigned int old;
@@ -1931,6 +2001,10 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
   NODE_SET_METHOD(process, "chdir", Chdir);
   NODE_SET_METHOD(process, "cwd", Cwd);
 
+#ifdef _WIN32
+  NODE_SET_METHOD(process, "_cwdForDrive", CwdForDrive);
+#endif
+
   NODE_SET_METHOD(process, "umask", Umask);
 
 #ifdef __POSIX__