FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Make Get-ChildItem follow symlinks on demand, with checks for link loops by jeffbi · Pull Request #4020 · PowerShell/PowerShell · GitHub

Make Get-ChildItem follow symlinks on demand, with checks for link loops by jeffbi · Pull Request #4020 · PowerShell/PowerShell · GitHub
Skip to content

Navigation Menu

Sign in

Make Get-ChildItem follow symlinks on demand, with checks for link loops - #4020

Merged
Dongbo Wang (daxian-dbw) merged 11 commits into
PowerShell:masterfrom
jeffbi:Get-Child-3951
Jul 6, 2017
Merged

Make Get-ChildItem follow symlinks on demand, with checks for link loops#4020
Dongbo Wang (daxian-dbw) merged 11 commits into
PowerShell:masterfrom
jeffbi:Get-Child-3951

Conversation

jeffbi commented Jun 15, 2017
edited by daxian-dbw
Loading

Copy link
Copy Markdown

Fixes #3951

Add the dynamic parameter -FollowSymlink to Get-ChildItem.
Add a mechanism for tracking visited directories.
Add native code to get device/inode information on Unix/Windows.
Add warning when refusing to enter an already-visited directory.

jeffbi commented Jun 15, 2017

Copy link
Copy Markdown
Author

Ilya (@iSazonov), Dongbo Wang (@daxian-dbw) can someone look at the AppVeyor failure? Looking at the details, It appears to be failing very early in the run

Copy link
Copy Markdown
Collaborator

jeffbi I believe it is temporary error - please restart CI by reward your commit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We should use autoproperty if we can.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Modified the -FollowSymlink parameter to set -Recurse, and the -Depth parameter does. Thus we cannot use auto-property here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Can we use a Tuple<dev, inode> as key? If so it exclude "dictionary in dictionary".
Sample and c# 7.0 pattern https://stackoverflow.com/questions/2877660/composite-key-dictionary

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Part of that discussion (https://stackoverflow.com/questions/2877660/composite-key-dictionary#comment14110652_2877820) suggests that the nested dictionary approach may be faster.

As for the C#7 pattern, I thought I read somewhere in another code review that C#7 code should be avoided. Is that not the case?

Ilya (iSazonov) Jun 18, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

C#7 pattern

We have no strict documentation. Different maintainers may have different requirements. My understanding: New patterns are welcome for the new code. Existing patterns (not relevant to the PR) should not be changed without maintainer's requiring.

the nested dictionary approach may be faster.

There, the main answer has recently been updated and there is a reference to gist with the tests. The conclusion is now Tuple keys is fast.
We both think that performance is important here. So maybe it makes sense to spend time testing cases.
Another thought is, in most cases, the user will have one volume. We could use one dictionary until we find a reference to another volume. Although it will complicate the code and it's not clear how much we win. Ooops, for other volume we can make separate recursive Dir() call - in the case we have to switch Dictionary (tracker) only on "top" level and only on Symlink when we can meet new volume. In other words, we will have two dictionaries but they will not be nested and the number of comparisons will be less - we'll be faster.

And maybe rename Dir() in something like DirectoryVisitor() ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It may be that on Windows the user will have only one volume, but that is much less the case on Unix. For example, on my Linux box the root (/) directory has device ID 2049, the /dev directory has ID 6, and the /proc directory has ID 4. Symlinks can cross device/volume boundaries.

I've changed the code to use the C#7 ValueTuple mechanism.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We have this on top level - see lines 1610-1616. I believe we should remove the two lines.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This function is called recursively, so I think we need to leave these lines here.

Ilya (iSazonov) Jun 16, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

tracker = new InodeTracker(); - It's not expensive. We can always create this on top level and not check many times during recursion.

We can tracker.Visit(directory.FullName); before call the Dir.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

In the Dir function, the tracker object also acts as a flag that the user gave the -FollowSymlink parameter.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I tried to refactor code and had no benefits.

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Should we check "recurse" here? Have we Recurse and FollowSymlink in only the same ParameterSet?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

There are two parameter sets for Get-ChildItem: Items and LiteralItems. The -Recurse and -FollowSymlink parameters are in both parameter sets.

The Depth parameter, which is also in both, implies -Recurse by setting the Recurse property. Should -FollowSymlink follow that pattern? That would remove the check of recurse here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We can not define explicitly mandatory sub-parameters 😕 That would be a useful feature.
So it is more safe to leave "recurse" here.

If -Depth implies -Recurse I believe we can do the same with -FollowSymlink .

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Modified the -FollowSymlink parameter to set -Recurse, and the -Depth parameter does.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))

Can we use FollowSymlink property?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

FollowSymlink is a property of GetChildItemCommand, not of the FileSystemProvider.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thanks for clarify!

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We can move "tracker != null" and exclude one "if":

if (tracker != null && tracker.Visited(recursiveDirectory.FullName)) 

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It seems we can exclude enterDir:

                            if (tracker != null)
                            {
                                if (tracker.Visited(recursiveDirectory.FullName))
                                {
                                    WriteWarning(StringUtil.Format(FileSystemProviderStrings.AlreadyListedDirectory,
                                                                    recursiveDirectory.FullName));
                                    continue;
                                }
                            }
                            else
                            {
                                if (InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(recursiveDirectory))
                                {
                                    continue;
                                }
                            }

                            bool hidden = false;
                            if (!Force)
                            {
                                hidden = (recursiveDirectory.Attributes & FileAttributes.Hidden) != 0;
                            }

                            // if "Hidden" is explicitly specified anywhere in the attribute filter, then override
                            // default hidden attribute filter.
                            if (Force || !hidden || isFilterHiddenSpecified || isSwitchFilterHiddenSpecified)
                            {
                                Dir(recursiveDirectory, recurse, depth - 1, tracker, nameOnly, returnContainers);
                            }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Refactored code to eliminate enterDir.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Please re-format the comment. Maybe put a) and b) on new lines. And "the directory the symlink" confuse me.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Modified the comment.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

"{0}" can bee very long so maybe we re-format the message?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Re-formatted the message to put the path at the end.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Comment thread src/libpsl-native/src/getinodedata.cpp Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Duplicated "brief" ?

jeffbi Jun 16, 2017
edited
Loading

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This one describes the function. The other describes the file.

I used the existing getlinkcount.cpp as the pattern for this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Can we use the pattern?

enterDir = tracker != null;

Or we should use explicit "false"?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Is it supported on Windows 7?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It's supported as early as XP.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))

Can we use FollowSymlink property?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Now we don't use dev and inode and we could out Tuple.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We do still use dev and inode. That's how we uniquely identify a file system object.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I see the GetInodeData is only called in Visit and Visited method.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yes. Did you want to move the implementation up into the InodeTracker class?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I meant simple change like internal static bool GetInodeData(string path, out (dev, inode) var)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Can we exclude repetition "listing ... listed"?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Changed "already-listed" to "already-visited"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

In line 170

this.Recurse = true; // Bug 2391925 - Get-ChildItem -Depth should auto-set -Recurse

Should we use the same pattern?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It looks like that was in response to a specific bug. In our case it's just part of a new feature.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'm afraid we could catch the same bug here. Maybe maintainers clarify.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The question for maintainers: should we follow the file patterns and move the private variable to a line below 342?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Please use the same pattern in Visited and Visit methods - insert a new line here or remove a line after "var" in Visit.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thanks for catching that. New line inserted.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Do it makes sense to break it down into two lines?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Made the calculation more concise.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Dongbo Wang (@daxian-dbw) Could you please assign a reviewer?

Ilya (iSazonov) commented Jun 22, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

jeffbi Great work! Thanks!
I leave two questions opened for maintainers.

Copy link
Copy Markdown
Member

jeffbi and Ilya (@iSazonov) thanks for the good work and review! I will quickly go through the changes before merging.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Please don't use public modifier for members of an internal class. Use internal/private as appropriate.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This method name should be IsPathVisited. It's good for readability.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

Dongbo Wang (daxian-dbw) Jun 22, 2017
edited
Loading

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think -FollowSymlink should be a dynamic parameter provided by file system provider so that when using Get-ChildItem with another provider, this parameter won't show up.
-FollowSymlink is very much like the -CodeSigningCert parameter of Get-ChildItem -- -CodeSigningCert only applicable to the certificate provider and -FollowSymlink only applicable to FileSystemProvider. It's unlike the -Depth parameter which could be commonly meaningful to many underlying providers.

PS:17> Get-ChildItem Cert:\ -CodeSigningCert  -Recurse

   PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\REQUEST

Thumbprint                                Subject
----------                                -------
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  CN=BLAHBLAH
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  CN=LAHBLAHB

It's doable to make -FollowSymlink depends on the appearance of -Recurse in the dynamic parameter, but I suggest for this PR let's make things simpler by not having this design. If it's a highly demanded feature, we can enable it in a separate PR.

Please take a look at the following places to see how dynamic parameters are retrieved from the underlying provider:
https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs#L372-L387

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

So then -FollowSymlink should not imply -Recurse? It has meaning only if -Recurse is also explicitly given?

Ilya (iSazonov) Jun 28, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

powercode initiated #4102 for disscusion of parameter sets selection. So it is design case whether or not imply "mandatory" parameter by subparameter.

And what is if a path in -Path is Symlink?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Event without -FollowSymlink, the cmdlet will follow symlinks specified in -Path

Ilya (iSazonov) Jun 28, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Should we follow w/o FollowSymlink in the case? If no we can use FollowSymlink w/o Recurce. If yes we should rename FollowSymlink in FollowSymlinkInRecursion.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Maybe it will turn out that making -FollowSymlink implicitly imply -Recurse is better, but for now here are my 2 cents:

  1. It's always better to force the user to be explicit about their intent, so dir -Recurse -FollowSymlink is more readable than dir -FollowSymlink in my opinion.
  2. For simplicity reason, this PR can just add the -FollowSymlink parameter without worrying about its dependency to -Recurse. In my understanding (correct me if I'm wrong) the related code won't execute when it's not recursively iterating file system items. Then we can get feedback from users and base on that to decide if we should make -FollowSymlink implicitly imply -Recurse.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Changed to dynamic parameter. -FollowSymlink no longer implies -Recurse.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

if (recurse && Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))

This is fragile as you don't check the value. Think about dir -follow:$false. We should go with the dynamic parameter approach for -FollowSymlink

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think Skip already-visited directory {0} might be better.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Should this be "Skip"? Or perhaps "Skipping" or "Skipped"?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I see that you are following a pattern like IsSameFileSystemItem or IsHardLink. Ideally, code here should be refactored to consolidate Unix/Windows implementations in one place and eliminate wrapper functions like IsSameFileSystemItem and IsHardLink. Instead they should be

internal static bool IsSameFileSystemItem(string pathOne, string pathTwo)
{
#if UNIX
   // Unix implementation goes here
#else
   // Windows implementation goes here
#endif
}

I'm not asking you to do this change for this PR, just want to call out that it should be this way moving forward. I opened #4086 to track the refactoring task.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Correspondingly, maybe this name can be changed to VisitPath.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

And, does this method really need to return anything? If GetInodeData failed, then we just don't skip anything.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed both.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Now we return nothing. Please remove the line.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Ilya (iSazonov) Jun 29, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Wrong pattern.
We can use tracker?.VisitPath(directory.FullName); or if (...) { ... }.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Closed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We can remove one if.

if (fspDynamicParam?.FollowSymlink) 

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Sorry, I again under 'tracker == null' doulble check pressure.
It seem we can remove one if remove lines 1675-1678, add its before first Dir call and here add:

                                else
                                {
                                    tracker.VisitPath(directory.FullName);
                                }

Next, we can exclude VisitPath at all if make "visit" in IsPathVisited (we should rename it maybe as TryPathVisit).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I don't agree with this. If we mark the directory has having been visited here, then if the test at line 1883 evaluates to false the Dir method will not be called and we will not visit the directory even though we have already claimed to have done so.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Do you mean "hidden" directories? If so we can safely mark "hidden" directories as "visited" - in both cases they is really skipped.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

A directory might be hidden. A symbolic link (or several such) might not be.

Ilya (iSazonov) Jul 2, 2017
edited
Loading

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

You are right. We can move the hidden check above line 1861 - it is less expensive then double call 'GetInodeData' in VisitPath and IsPathVisited.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Now CoreFX has a new method Dictionary.TryAdd - Could we use them?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Used new method

Copy link
Copy Markdown
Collaborator

LGTM.

Dongbo Wang (@daxian-dbw) Could you please continue the code review?

Dongbo Wang (daxian-dbw) left a comment

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thanks for switching to the dynamic parameter approach! See some more comments below.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Missing </summary>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Why using a Dictionary? It seems HashSet would be good.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We use new Dictionary.TryAdd() but HashSet.Add() works the same - so we can use HashSet.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

HashSet is much better. Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

A new parameter should usually be added to the end of the parameter list.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If recurse == false, then we should avoid creating the tracker.

if (recurse)
{
    GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
    if (fspDynamicParam != null && fspDynamicParam.FollowSymlink)
    {
        tracker = new InodeTracker(directory.FullName);
    }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I thought I had a check for recurse at one time. It must have gotten lost in the shuffle. Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Here we can use else if (...) so that one nested block can be avoided.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This method goes against the convention of Try<Verb> pattern. Take TryAdd as an instance, if added, then return true; if not added, then return false. Following that conversion, TryVisitPath should be: if visited, then return true; if not visited, then return false. So TryVisitPath makes it confusing understanding the code.

How about making it IsPathVisited(bool visitIfNotAlready = false)?

internal bool IsPathVisited(bool visitIfNotAlready = false)
{
    bool isPathVisited = false;
    if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
    {
        if (visitIfNotAlready)
        {
            isPathVisited = !_visitations.Add(inodeData)
        }
        else
        {
            isPathVisited = _visitations.Contains(inodeData)
        }
    }
    return isPathVisited;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

With HashSet it will looks just better.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This class seems to be a helper that will only be used in FileSystemProvider code, so can we make it a private nested class within FileSystemProvider? (just like private static class NativeMethods in FileSystemProvider)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Minor comment: maybe use the following pattern to avoid var inodeData = (0UL, 0UL);?

if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Looks good.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Fixed.

Jeff Bienstadt added 9 commits July 5, 2017 23:41
…ops. (#3951)

    Add -FollowSymlink switch parameter to Get-ChildItem.
    Add a mechanism for tracking visited directories.
    Add native code to get dev/inode information on Unix.
    Add warning when refusing to enter an already-visited directory.
  o Switched from nested dictionary to single dictionary keyed by
    ValueTuple (device, inode).
  o Modified warning message to put path at end.
  o -FollowSymlink switch now implies -Recurse
  o Other code and comment refactorizings.
   * Make InodeTracker a private nested class
   * Change from Dictionary to HashSet in InodeTracker
   * Change InodeTracker's TryVisitPath to IsPathVisited with a parameter for marking a path as visited if not already
   * Add check for -Recurse before creating InodeTracker object
   * Change order of parameters in Dir method
   * Remove unneeded nested block level
   * Add missing </summary> tag
   * Add -Pending on some tests to get past Pester errors
       (see issue #4145)
/// </param>
/// <param name="visitIfNotAlready">
/// Flag to indicate whether the path should be marked as visited if
/// it has not already been visited.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Should we add the parameter if we don't use them?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Normally I would say no. In this case the existence of the parameter and its name give a clue about the side effect of marking the path as visited.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I give this some more thinking, and believes that the TryVisitPath signature is more natural in this case. But we need to follow the Try<> pattern.

Again, take HashSet.Add(object) as an example:

  1. If the object is not in the set, add it and return true.
  2. If the object is already in the set, return false which means it was not added in this call because it's already there.

Similarly, InodeTracker.TryVisitPath(path) should be

  1. If the path is not visited yet, visit it and return true.
  2. If the path is already visited, return false which means it was not visited in this call because it's already visited.

The code would be like:

internal bool TryVisitPath(string path)
{
    bool returnValue = false;
    if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
    {
        returnValue = _visitations.Add(inodeData);
    }
    return returnValue;
}

jeffbi Ilya (@iSazonov) what do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I like the TryVisitPath more the IsVisitPath.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Works for me. I'll make the change.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Awesome. Thank you both!

Dongbo Wang (daxian-dbw) left a comment

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

2 more comments

bool nameOnly,
ReturnContainers returnContainers)
ReturnContainers returnContainers,
InodeTracker tracker) // tracker will be non-null only if the user invoked the -FollowSymLinks switch parameter.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Minor comment: this comment is not accurate now -- tracker will be null when -FollowSymlinks is specified but -Recurse is not.

/// </param>
/// <param name="visitIfNotAlready">
/// Flag to indicate whether the path should be marked as visited if
/// it has not already been visited.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I give this some more thinking, and believes that the TryVisitPath signature is more natural in this case. But we need to follow the Try<> pattern.

Again, take HashSet.Add(object) as an example:

  1. If the object is not in the set, add it and return true.
  2. If the object is already in the set, return false which means it was not added in this call because it's already there.

Similarly, InodeTracker.TryVisitPath(path) should be

  1. If the path is not visited yet, visit it and return true.
  2. If the path is already visited, return false which means it was not visited in this call because it's already visited.

The code would be like:

internal bool TryVisitPath(string path)
{
    bool returnValue = false;
    if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
    {
        returnValue = _visitations.Add(inodeData);
    }
    return returnValue;
}

jeffbi Ilya (@iSazonov) what do you think?

Dongbo Wang (daxian-dbw) left a comment

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

jeffbi and Ilya (@iSazonov) Thank you both for the great work and thorough review!

Dongbo Wang (daxian-dbw) merged commit 1688703 into PowerShell:master Jul 6, 2017
jeffbi deleted the Get-Child-3951 branch July 6, 2017 23:14

Copy link
Copy Markdown
Collaborator

jeffbi Many thanks for the great work!

jeffbi commented Jul 7, 2017

Copy link
Copy Markdown
Author

Ilya (@iSazonov) and Dongbo Wang (@daxian-dbw) Thanks for the review.

Copy link
Copy Markdown
Contributor

Very good job!

P.S. I would probably have called the new parameter -FollowSymlinks

Thatgfsj (Thatgfsj) pushed a commit to Thatgfsj/PowerShell that referenced this pull request Aug 6, 2026
…ops (PowerShell#4020)

Add the dynamic parameter `-FollowSymlink` to `Get-ChildItem`.
Add a mechanism for tracking visited directories.
Add native code to get device/inode information on Unix/Windows.
Add warning when refusing to enter an already-visited directory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Need the ability to opt into / out of following symlinks with Get-ChildItem -Recurse, behavior differs between editions

5 participants

Footer

© 2026 GitHub, Inc.

Back | FazBrowse Home | New Git URL