Skip to content

win: make uv_exepath resolve symbolic links - #293

Closed
TomCrypto wants to merge 4 commits into
libuv:v1.xfrom
TomCrypto:win-exepath
Closed

TomCrypto wants to merge 4 commits into
libuv:v1.xfrom
TomCrypto:win-exepath

Conversation

@TomCrypto

Copy link
Copy Markdown
Contributor

This is a draft of a possible fix to #206. It uses GetModuleFileNameW as before to get some kind of path to the executable, and then gets a handle to the file and resolves the full path (without any symlinks) using GetFinalPathNameByHandleW. It's not finished, of course, I have a few questions. With this commit the function will read through symbolic links and return resolved paths like:

\\?\C:\libuv\Debug\run-tests.exe

Which are completely valid (and fully resolved) Windows paths, but the VOLUME_NAME_DOS flag used means that the file actually needs to be on a mounted drive with an assigned drive letter or the function will fail with ERROR_PATH_NOT_FOUND, as MSDN states. The file could be on a mounted drive with no drive letter assigned, and in this case I assume it could be addressed via its GUID (via VOLUME_NAME_GUID) as a long UNC path. But is this realistic?

Basically should the function return "usual" paths like C:\path\to\executable.exe or should it return full-blown UNC paths (which, by the way, apparently can be slightly longer than 32768 characters)? Or whichever is shorter? Does anyone have an opinion on this?

I mean, logically the function should never fail (besides out of resource errors) since the executable always exists somewhere, but this could be a faulty assumption?

EDIT: actually I noticed that the exepath test expects the passed buffer to be filled and truncated if it is too short, so since uv_exepath is a two-step process now it might be necessary to allocate 32KB of memory upfront to reliably get the initial path and then have the GetFinalPathNameByHandleW function handle the truncation for us, assuming it does (since the expanded full path may be longer than the symlink). I'll need to look into that.

Comment thread src/win/util.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should be more deffensive here? Looks like this function could return ERROR_INSUFFICIENT_BUFFER. Also, these is some remark for Windows XP, which we (sadly) still support.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the function returns a DWORD, which is unsigned, so I'm not sure what the less than zero part was meant to signify? As I understand it the function returns zero on error and puts an explicit error code, such as ERROR_INSUFFICIENT_BUFFER, to be retrieved by GetLastError().

We should definitely make sure that the code works well for Windows XP, especially since these functions tend to behave strangely on XP with respect to null termination (unfortunately).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, reading the documentation again I see what you mean. Yes, I agree there should be a check for ERROR_INSUFFICIENT_BUFFER error case here. Taking note.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@saghul

saghul commented Mar 27, 2015

Copy link
Copy Markdown
Member

/cc @piscisaureus

@saghul saghul added the windows label Mar 27, 2015
@TomCrypto

Copy link
Copy Markdown
Contributor Author

Okay, I improved it a bit, and now it works reliably and also adheres to the semantics inferred from the last few asserts of the test-get-currentexe test when the input buffer is too short to hold the executable path (unless I messed up).

However the current test has some code that (I quote) "verify that the path returned from uv_exepath is a subdirectory of executable_path". This seems incredibly brittle since not only is executable_path equal to argv[0], the irregular nature of fully qualified windows paths (with the \\?\ prefix for instance) makes that part of the test fail anyway.

Do you think it is it worth trying to work around the problem by e.g. stripping \\?\ (if possible) or having a special case for Windows and whatnot, or should the test perhaps be rewritten to use a more "natural" validation measure, perhaps a three-step process that:

  1. checks the returned path is not a relative path [uv_exepath should return an absolute path]
  2. stats the returned path to check it contains no symlinks [uv_exepath should resolve symlinks]
  3. tries to execute the program at the returned path with some command-line argument that the test runner understands, and checks the output/exit code to validate the path is in fact the path to the executable [uv_exepath should return a path to the executable]

Together with the invalid parameter asserts I believe this should fully verify all the postconditions that the function should meet and would be more robust, though a little bit more work, than the current version.

Opinions?

@saghul

saghul commented Apr 2, 2015

Copy link
Copy Markdown
Member

@TomCrypto your suggestions sound great to me! Bert (@piscisaureus) is our Chief Windows Officer, maybe he has some extra thoughts.

Comment thread src/win/util.c Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't you just set all share flags (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I actually just lifted the parameters to CreateFile straight from the MSDN page for GetFinalPathNameByHandle thinking they would be appropriate, but I agree now they are not very permissive in the context of opening one's own executable file. Changing them.

@piscisaureus

Copy link
Copy Markdown
Contributor
  • When possible, try to return a DOS path. If there is no valid DOS path you could fall back to providing a GUID path, or just returning the GetModuleFileName result without further processing - I don't have a strong opinion about it.
  • My preference would be to chip off the \\?\ or \\?\UNC\ prefix if the file resides on device that has a DOS path or a mapped network drive path.

Another thing that worries me a little bit is that opening the .exe file may not succeed, so I'd like to be as defensive as possible. I spent some time checking whether there is a method to obtain the HANDLE to the primary section that was used to create the process, but it seems that there's no easy method for that. So I guess this strategy will have to do.

In general the patch looks well-written and clean to me. Good work!

@TomCrypto

Copy link
Copy Markdown
Contributor Author

Thanks! For the fallback, I think falling back to GUID is best. After all if the executable isn't on a mapped drive it's already a weird situation IMHO. If everything fails I guess the result of GetModuleFileName could be used, but this kind of best-effort behaviour should be documented for users.

About stripping \\?\, do we do that in every case or only if the DOS path is less than MAX_PATH characters in length? The documentation is a bit thick but suggests that paths longer than that actually need the prefix to be further used in Windows (unicode) API's?

@TomCrypto

Copy link
Copy Markdown
Contributor Author

Rewrote the function to be able to handle fallback, now it does the following:

  1. Gets the path to the module using GetModuleFileNameW. If that actually fails, returns an error.
  2. Attempts to open the .exe file at that path and get a handle to it. If that fails, returns the path to the module (and returns success).
  3. Attempts to get a full DOS path to said handle using GetFinalPathByHandleW. If that fails because there is no such path, attempts to get a GUID path instead. If that fails, or if getting a DOS path fails for any other reason, returns the path to the module (and returns "success").
  4. If it is returning the path to the module or a DOS path (not a GUID path), strips the \\?\UNC\ or \\?\ prefix from the start of the path, whichever one is longer.

That means that as long as GetModuleFileNameW succeeds, the function will at least return something sensible, but I don't know if that behaviour is wise. If that doesn't seem reasonable after some more thought, it's easy to tweak or revert :-)

@saghul saghul added this to the next milestone Jun 15, 2015
@piscisaureus

Copy link
Copy Markdown
Contributor

GetFinalPathNameByHandleW isn't supported in Windows XP/2k3. I'm fine with landing this after we officially drop support for them.

@saghul

saghul commented Dec 14, 2015

Copy link
Copy Markdown
Member

We now use GetFinalPathNameByHandleW in uv_fs_realpath, I guess we could conditionally use it now, and just don't do it in XP.

@saghul

saghul commented Aug 26, 2016

Copy link
Copy Markdown
Member

I'm going to close this one. We use GetFinalPathNameByHandleW in uv_fs_realpath, but there are many corner cases, I do not want to open pandora's box again... For reference see nodejs/node#7726

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants