Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: fix long symlinks in realpath #7548

Closed
wants to merge 3 commits into from

Conversation

trevnorris
Copy link
Contributor

Checklist
  • make -j4 test (UNIX), or vcbuild test nosign (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

fs, uv

Description of change
fs: allow realpath to resolve deep symlinks

realpath(3) would fail if the symbolic link depth was too deep. If ELOOP
is encountered then resolve the path in parts until the entire thing is
resolved.
uv: expose uv_strerror() to JS API

Retrieving the error message along with the error name is useful. So
exposing to the JS API.

Also add additional -UV_ERRNO_MAX range check to ErrName() to help
prevent the following comment from libuv documentation on both
uv_strerror() and uv_err_name():

   Leaks a few bytes of memory when you call it with an unknown error
   code.

Fixes: #7175

This definitely isn't the prettiest code I've written. Want to get feedback while I finish up the tests.

R=@bnoordhuis
R=@saghul

@trevnorris trevnorris added the fs Issues and PRs related to the fs subsystem / file system. label Jul 5, 2016
@nodejs-github-bot nodejs-github-bot added the c++ Issues and PRs that require attention from people who are familiar with C++. label Jul 5, 2016
size_t slash_count = 0;

while ((offset = str_path.find('/', offset + 1)) != std::string::npos) {
if (++slash_count < 32) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Where does 32 come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From @saghul's comment in #7175 (comment).

Copy link
Contributor

Choose a reason for hiding this comment

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

I think having a comment here about why 32 was chosen might be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

heh. true.

@saghul There a reason you mentioned 32 in your comment?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops. missed that. thanks.

@Trott
Copy link
Member

Trott commented Jul 5, 2016

Possible/reasonable to add one or more tests?

@Trott
Copy link
Member

Trott commented Jul 5, 2016

/cc @jhamhader

@trevnorris
Copy link
Contributor Author

@Trott

Possible/reasonable to add one or more tests?

Comment in OP :-)

Want to get feedback while I finish up the tests.

@Trott
Copy link
Member

Trott commented Jul 5, 2016

Heh...I got as far as "feedback" and stopped. Last mile problems.

static size_t CountSlashes(const char* str, size_t len) {
size_t cntr = 0;
for (size_t i = 0; i < len; i++) {
if (str[i] == '/') cntr++;
Copy link
Member

Choose a reason for hiding this comment

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

won't this be a problem on Windows? (I this code ever gets run there, that is)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn't see in any of the win calls of uv_fs_realpath() that could return ERROR_CANT_RESOLVE_FILENAME (mapped to UV_ELOOP). Specifically those are CreateFileW() and pGetFinalPathNameByHandleW(). I just added a couple tests that will push up in a moment and run CI to check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@saghul So, seems I can get ELOOP from windows. Working on that.

@trevnorris
Copy link
Contributor Author

Updated and running CI: https://ci.nodejs.org/job/node-test-commit/3996/

@trevnorris
Copy link
Contributor Author

Hm, the PR doesn't build properly on Windows. Looking into it.

On all the node-compile-windows there's:

src\node_file.cc(906): error C2660: 'uv_fs_realpath' : function does not take 3 arguments [c:\workspace\node-compile-windows\label\win-vs2013\node.vcxproj]

Not sure why that's failing for Windows and not any other box.

From node-test-binary-windows:

ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.

Not sure what that's about.

@Fishrock123
Copy link
Contributor

cc @nodejs/platform-windows ^^^

@bzoz
Copy link
Contributor

bzoz commented Jul 7, 2016

If you change macro call SYNC_CALL_NO_THROW(req_wrap, realpath, path, path); to what it should expand to SYNC_DEST_CALL_NO_THROW(req_wrap, realpath, nullptr, path, path); it compiles under vs2015. I'll investigate what is happening there.

@bzoz
Copy link
Contributor

bzoz commented Jul 7, 2016

It's a thing with how msvc expands __VA_ARGS__ (stackoverlow discussion). There is a workaround for this, by changing SYNC_CALL_NO_THROW definition:

#define EXPAND(X) X
#define SYNC_CALL_NO_THROW(func, path, ...)                                      \
  EXPAND(SYNC_DEST_CALL_NO_THROW(func, path, nullptr, __VA_ARGS__))

@trevnorris
Copy link
Contributor Author

trevnorris commented Jul 7, 2016

@bzoz Thanks much for looking into this. Does make me curious why the use of ASYNC_CALL and ASYNC_DEST_CALL don't suffer the same issue. Oh well. Trying out your fix now.

EDIT: Ah yup. I see it now. Thanks for pointing this out. Missed that the macros could be simplified, which will solve the issue.

@trevnorris
Copy link
Contributor Author

Realized two things. 1) this doesn't address the async call (small oversight...) and 2) it can be made more reliable than it was before. Working on both.

@trevnorris
Copy link
Contributor Author

Updated w/ most of it, but going to forget the "more reliable" part for now. That's an enhancement for another PR.

CI: https://ci.nodejs.org/job/node-test-pull-request/3201/

@trevnorris
Copy link
Contributor Author

trevnorris commented Jul 7, 2016

Compiling but now getting an issue with the resolved path on windows. Looking into it.

EDIT: Looks like it may be from exceeding MAX_PATH. Updating test and resubmitting.

@trevnorris
Copy link
Contributor Author

Path length fixed.

CI: https://ci.nodejs.org/job/node-test-pull-request/3202/

req.oncomplete = pathResolver;
binding.realpath(pathModule._makeLong(tmpPath), options.encoding, req);
}(null, ''));
}
Copy link
Member

Choose a reason for hiding this comment

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

Would it make more sense to do a single async call to ResolveRealPath instead of mirroring the C++ code in JS?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. Reason I did it this way was because handling/passing the async resources was being a thorn at the time. I'll see about moving this into C++.

@trevnorris
Copy link
Contributor Author

trevnorris commented Jul 8, 2016

@nodejs/platform-windows So I can't get the test to repro properly on my VM. Could it have to do with the path that Jenkins is giving it? /cc @rvagg

Nm. Just noticed that it didn't fail b/c the test wasn't actually running... Can repro.

@trevnorris
Copy link
Contributor Author

Think I got it this time.

CI: https://ci.nodejs.org/job/node-test-pull-request/3223/

@Fishrock123
Copy link
Contributor

@trevnorris Lots of failures. :/

@trevnorris
Copy link
Contributor Author

@Fishrock123 Yeah. But Windows passed! Not sure if that's progress though...

@trevnorris
Copy link
Contributor Author

Another CI to help me understand what's going on: https://ci.nodejs.org/job/node-test-pull-request/3248/

@trevnorris
Copy link
Contributor Author

@addaleax Heh. I'm somewhere between JS and C++ here. Thanks for the reminder what I'm actually coding.

@trevnorris
Copy link
Contributor Author

Here we go again. CI: https://ci.nodejs.org/job/node-test-pull-request/3250/

@addaleax
Copy link
Member

Btw… is there any reason for the path delimiter to be on env?

@trevnorris
Copy link
Contributor Author

@addaleax didn't want to run strcmp() every time, and I've been trained by @bnoordhuis to never use globals. So, psychological conditioning would be the reason.

@addaleax
Copy link
Member

Just wondering if a #ifdef _WIN32-based macro wouldn’t be cheaper, the value does seem pretty constant to me. ;)

@trevnorris
Copy link
Contributor Author

trevnorris commented Jul 11, 2016

Hah. That would in fact be better C++. Again failed on the context switch from a temporary implementation in JS using process.platform. Thanks. =)

CI: https://ci.nodejs.org/job/node-test-pull-request/3251/

@trevnorris
Copy link
Contributor Author

Yey! No related failures. Will clean up commits.

Retrieving the error message along with the error name is useful. So
exposing to the JS API.

Extend the existing error messages to include more information about the
invalid error code.

Also add additional UV_ERRNO_MAX range check to ErrName() to help
prevent the following comment from libuv documentation on both
uv_strerror() and uv_err_name():

   Leaks a few bytes of memory when you call it with an unknown error
   code.
realpath(3) would fail if the symbolic link depth was too deep. If ELOOP
is encountered then resolve the path in parts until the entire thing is
resolved. This excludes if the number of symbolic links is too deep, or
if they are recursive.

Fixes: nodejs#7175
The test/benchmarks included also work for the previous JS
implementation of fs.realpath(). In case the new implementation of
realpath() needs to be reverted, we want these changes to stick around.
@jasnell
Copy link
Member

jasnell commented Aug 8, 2016

@trevnorris ... should this one remain open given that it looks like we're reverting the impl back to the old js impl?

@trevnorris
Copy link
Contributor Author

@jasnell well, if it's going to take a full major release cycle before libuv is fixed and landed then yeah. probably. though i'd like to hope that it won't.

@jasnell jasnell added the blocked PRs that are blocked by other issues or PRs. label Aug 9, 2016
@jasnell
Copy link
Member

jasnell commented Aug 9, 2016

Marking this as blocked for now then

@Fishrock123
Copy link
Contributor

This has since been fixed by reverting to js realpath, closing.

@trevnorris please let me know if closing this was in error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked PRs that are blocked by other issues or PRs. c++ Issues and PRs that require attention from people who are familiar with C++. fs Issues and PRs related to the fs subsystem / file system.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Node 6 fs.realpath behavior changes
9 participants