-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Replace string-compare-based test for copying to same file with more … #3441
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
Changes from 1 commit
d970809
3263893
a1e905b
9fff05b
8814054
19702a0
07b1594
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,6 +62,11 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, | |
| // copy script will accomodate the new value. | ||
| private const int FILETRANSFERSIZE = 4 * 1024 * 1024; | ||
|
|
||
| // The name of the key in an exception's Data dictionary when attempting | ||
| // to copy an item onto itself. | ||
| private const string SelfCopyDataKey = "SelfCopy"; | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// An instance of the PSTraceSource class used for trace output | ||
| /// using "FileSystemProvider" as the category. | ||
|
|
@@ -3460,14 +3465,14 @@ protected override void CopyItem( | |
| _excludeMatcher = SessionStateUtilities.CreateWildcardsFromStrings(Exclude, WildcardOptions.IgnoreCase); | ||
|
|
||
| // if the source and destination path are same (for a local copy) then flag it as error. | ||
| if ((toSession == null) && (fromSession == null) && path.Equals(destinationPath, StringComparison.OrdinalIgnoreCase)) | ||
| if ((toSession == null) && (fromSession == null) && InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(path, destinationPath)) | ||
| { | ||
| String error = StringUtil.Format(FileSystemProviderStrings.CopyError, path); | ||
| Exception e = new IOException(error); | ||
| e.Data[SelfCopyDataKey] = destinationPath; | ||
| WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, path)); | ||
| return; | ||
| } | ||
|
|
||
| // Copy-Item from session | ||
| if (fromSession != null) | ||
| { | ||
|
|
@@ -3777,14 +3782,14 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, | |
| } | ||
|
|
||
| //if the source and destination path are same then flag it as error. | ||
| if (destinationPath.Equals(file.FullName, StringComparison.OrdinalIgnoreCase)) | ||
| if (InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(destinationPath, file.FullName)) | ||
| { | ||
| String error = StringUtil.Format(FileSystemProviderStrings.CopyError, destinationPath); | ||
| Exception e = new IOException(error); | ||
| e.Data[SelfCopyDataKey] = file.FullName; | ||
| WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destinationPath)); | ||
| return; | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unneeded formatting. Please renew the comments.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
| // Verify that the target doesn't represent a device name | ||
| if (PathIsReservedDeviceName(destinationPath, "CopyError")) | ||
| { | ||
|
|
@@ -8211,6 +8216,42 @@ internal static bool WinIsHardLink(FileSystemInfo fileInfo) | |
| return isHardLink; | ||
| } | ||
|
|
||
| internal static bool IsSameFileSystemItem(string pathOne, string pathTwo) | ||
| { | ||
| #if UNIX | ||
| return Platform.NonWindowsIsSameFileSystemItem(pathOne, pathTwo); | ||
| #else | ||
| return WinIsSameFileSystemItem(pathOne, pathTwo); | ||
| #endif | ||
| } | ||
|
|
||
| internal static bool WinIsSameFileSystemItem(string pathOne, string pathTwo) | ||
| { | ||
| var access = FileAccess.Read; | ||
| var share = FileShare.Read; | ||
| var creation = FileMode.Open; | ||
| var attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please clarify - do we really need PosixSemantics here?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's for if/when we find ourselves in running in a Windows environment in which case-sensitivity has been turned on. Since the purpose of the function is to see if two files/directories are the same, it made sense to me to give it every advantage in making a correct assessment. |
||
|
|
||
| using (var sfOne = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathOne, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) | ||
| using (var sfTwo = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathTwo, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) | ||
| { | ||
| if (!sfOne.IsInvalid && !sfTwo.IsInvalid) | ||
| { | ||
| BY_HANDLE_FILE_INFORMATION infoOne; | ||
| BY_HANDLE_FILE_INFORMATION infoTwo; | ||
| if ( GetFileInformationByHandle(sfOne.DangerousGetHandle(), out infoOne) | ||
| && GetFileInformationByHandle(sfTwo.DangerousGetHandle(), out infoTwo)) | ||
| { | ||
| return infoOne.VolumeSerialNumber == infoTwo.VolumeSerialNumber | ||
| && infoOne.FileIndexHigh == infoTwo.FileIndexHigh | ||
| && infoOne.FileIndexLow == infoTwo.FileIndexLow; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| internal static bool IsHardLink(ref IntPtr handle) | ||
| { | ||
| #if UNIX | ||
|
|
@@ -8664,7 +8705,7 @@ internal static void SetZoneOfOrigin(string path, SecurityZone securityZone) | |
| // the code above seems cleaner and more robust than the IAttachmentExecute approach | ||
| } | ||
|
|
||
| private static class NativeMethods | ||
| internal static class NativeMethods | ||
| { | ||
| internal const int ERROR_HANDLE_EOF = 38; | ||
| internal enum StreamInfoLevels { FindStreamInfoStandard = 0 } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| //! @file issamefilesystemitem.cpp | ||
| //! @author Jeff Bienstadt <[email protected]> | ||
| //! @brief returns if two paths ultimately point to the same filesystem object | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. returns what?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please correct this. |
||
|
|
||
| #include "getstat.h" | ||
| #include "issamefilesystemitem.h" | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra line.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discarded. |
||
| #include <assert.h> | ||
| #include <sys/types.h> | ||
| #include <sys/stat.h> | ||
| #include <unistd.h> | ||
|
|
||
| //! @brief returns if two paths ultimately refer to the same file or directory. | ||
| //! | ||
| //! IsSameFileSystemItem | ||
| //! | ||
| //! @param[in] path_one | ||
| //! @parblock | ||
| //! A pointer to the buffer that contains the first path. | ||
| //! | ||
| //! char* is marshaled as an LPStr, which on Linux is UTF-8. | ||
| //! @endparblock | ||
| //! | ||
| //! @param[in] path_two | ||
| //! @parblock | ||
| //! A pointer to the buffer that contains the second path. | ||
| //! | ||
| //! char* is marshaled as an LPStr, which on Linux is UTF-8. | ||
| //! @endparblock | ||
| //! | ||
| //! @retval true if both paths point to the same filesystem object, | ||
| //! false otherwise | ||
| //! | ||
| bool IsSameFileSystemItem(const char* path_one, const char* path_two) | ||
| { | ||
| assert(path_one); | ||
| assert(path_two); | ||
|
|
||
| struct stat buf_1; | ||
| struct stat buf_2; | ||
|
|
||
| if (GetStat(path_one, &buf_1) == 0 && GetStat(path_two, &buf_2) == 0) | ||
| { | ||
| return buf_1.st_dev == buf_2.st_dev && buf_1.st_ino == buf_2.st_ino; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #pragma once | ||
|
|
||
| #include "pal.h" | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra line.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discarded. |
||
| #include <stdbool.h> | ||
|
|
||
| PAL_BEGIN_EXTERNC | ||
|
|
||
| bool IsSameFileSystemItem(const char* path_one, const char* path_two); | ||
|
|
||
| PAL_END_EXTERNC | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -246,6 +246,178 @@ Describe "Basic FileSystem Provider Tests" -Tags "CI" { | |
| } | ||
| } | ||
|
|
||
| Describe "Copy-Item can avoid copying an item onto itself" -Tags "CI" { | ||
| BeforeAll { | ||
| # In Windows, only Administrators can create symbolic links. | ||
| # In Unix, anyone can. | ||
| function CanMakeSymlink | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unneeded function because
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function removed, along with use of |
||
| { | ||
| if ($IsWindows) | ||
| { | ||
| $winId = [System.Security.Principal.WindowsIdentity]::GetCurrent() | ||
| $principal = New-Object System.Security.Principal.WindowsPrincipal($WinId) | ||
| $admin = [System.Security.Principal.WindowsBuiltInRole]::Administrator | ||
|
|
||
| return $principal.IsInRole($admin) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually we set |
||
| } | ||
| else | ||
| { | ||
| return $true | ||
| } | ||
| } | ||
|
|
||
| # For now, we'll assume the tests are running the platform's | ||
| # native filesystem, in its default mode | ||
| $isCaseSensitive = $IsLinux | ||
| $canSymlink = CanMakeSymlink | ||
| $canDirSymLink = $IsWindows -And (CanMakeSymlink) | ||
| $canJunction = $IsWindows | ||
|
|
||
| # The name of the key in an exception's Data dictionary when an | ||
| # attempt is made to copy an item onto itself. | ||
| $selfCopyKey = "SelfCopy" | ||
|
|
||
| # Names of files, directories, and links we'll be using to test | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is obvious and useless. Please remove.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
| $subDir = "$TestDrive/sub" | ||
| $otherSubDir = "$TestDrive/other-sub" | ||
| $otherFile = "$otherSubDir/other-file" | ||
| $otherFileName = "other-file" | ||
| $otherFile = "$otherSubDir/$otherFileName" | ||
| $symToOther = "$subDir/sym-to-other" | ||
| $secondSymToOther = "$subDir/another-sym-to-other" | ||
| $symToSym = "$subDir/sym-to-sym-to-other" | ||
| $symToOtherFile = "$subDir/sym-to-other-file" | ||
| $hardToOtherFile = "$subDir/hard-to-other-file" | ||
| $symdToOther = "$subDir/symd-to-other" | ||
| $junctionToOther = "$subDir/junction-to-other" | ||
|
|
||
| # Set up our directories and links | ||
| New-Item -ItemType Directory $subDir >$null | ||
| New-Item -ItemType Directory $otherSubDir >$null | ||
| New-Item -ItemType File $otherFile -Value "some text" >$null | ||
| if ($canSymlink) | ||
| { | ||
| New-Item -ItemType SymbolicLink $symToOther -Value $otherSubDir >$null | ||
| New-Item -ItemType SymbolicLink $secondSymToOther -Value $otherSubDir >$null | ||
| New-Item -ItemType SymbolicLink $symToSym -Value $symToOther >$null | ||
| New-Item -ItemType SymbolicLink $symToOtherFile -Value $otherFile >$null | ||
| } | ||
| New-Item -ItemType HardLink $hardToOtherFile -Value $otherFile >$null | ||
|
|
||
| if ($canJunction) | ||
| { | ||
| New-Item -ItemType Junction $junctionToOther -Value $otherSubDir >$null | ||
| } | ||
| if ($canDirSymLink) | ||
| { | ||
| New-Item -ItemType SymbolicLink $symdToOther -Value $otherSubDir >$null | ||
| } | ||
|
|
||
| # Test the ability to avoid an item copying onto itself | ||
| function TestSelfCopy($testCase) | ||
| { | ||
| It "$($testCase.Name)" -Skip:($testCase.SkipIf) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With moving to |
||
| try | ||
| { | ||
| Copy-Item -Path $testCase.Source -Destination $testCase.Destination -ErrorAction Stop | ||
|
|
||
| if ($testCase.SelfCopyExpected) | ||
| { | ||
| # we expected the copy to fail and it did not | ||
| throw "Copy-Item should not have succeeded!" | ||
| } | ||
| else | ||
| { | ||
| # we expected the copy to succeed. make sure it did | ||
| Test-Path $testCase.Destination | Should Be $true | ||
| Remove-Item -Path $testCase.Destination -Force -ErrorAction SilentlyContinue | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If previous |
||
| } | ||
| } | ||
| catch | ||
| { | ||
| # we expected the copy to fail and it did. make sure it failed the right way | ||
| $_.FullyQualifiedErrorId | Should Be "CopyError,Microsoft.PowerShell.Commands.CopyItemCommand" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use $exc = {
... } | ShouldBeErrorId ...
$exc.Exception | Should BeOfType System.IO.IOException
... |
||
| $_.Exception | Should BeOfType System.IO.IOException | ||
| $_.Exception.Data[$selfCopyKey] | Should Not Be $null | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Set up our test cases | ||
| $testCases = @( | ||
| @{ | ||
| Name = "Copy to same path" | ||
| Source = $otherFile | ||
| Destination = $otherFile | ||
| SelfCopyExpected = $true | ||
| } | ||
| @{ | ||
| Name = "Copy to similar path, different case" | ||
| Source = $otherFile | ||
| Destination = "$otherSubDir/" + $otherFileName.ToUpper() | ||
| SelfCopyExpected = -Not $IsCaseSensitive | ||
| } | ||
| @{ | ||
| Name = "Copy hard link" | ||
| Source = $hardToOtherFile | ||
| Destination = $otherFile | ||
| SelfCopyExpected = $true | ||
| } | ||
| @{ | ||
| Name = "Copy hard link, reversed" | ||
| Source = $otherFile | ||
| Destination = $hardToOtherFile | ||
| SelfCopyExpected = $true | ||
| } | ||
|
|
||
| # Only do the symbolic link tests if we were priveliged to create the links | ||
| @{ | ||
| Name = "Copy symbolic link to target" | ||
| Source = $symToOtherFile | ||
| Destination = $otherFile | ||
| SelfCopyExpected = $true | ||
| SkipIf = -Not $canSymlink | ||
| } | ||
| @{ | ||
| Name = "Copy symbolic link to symbolic link with same target" | ||
| Source = $secondSymToOther | ||
| Destination = $symToOther | ||
| SelfCopyExpected = $true | ||
| SkipIf = -Not $canSymlink | ||
| } | ||
| @{ | ||
| Name = "Copy through chain of symbolic links" | ||
| Source = $symToSym | ||
| Destination = $otherSubDir | ||
| SelfCopyExpected = $true | ||
| SkipIf = -Not $canSymlink | ||
| } | ||
|
|
||
| # Junctions and directory symbolic links are Windows and NTFS only | ||
| @{ | ||
| Name = "Copy junction to target" | ||
| Source = $junctionToOther | ||
| Destination = $otherSubDir | ||
| SelfCopyExpected = $true | ||
| SkipIf = -Not $canJunction | ||
| } | ||
| @{ | ||
| Name = "Copy directory symbolic link to target" | ||
| Source = $symdToOther | ||
| Destination = $otherSubDir | ||
| SelfCopyExpected = $true | ||
| SkipIf = -Not $canDirSymLink | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| # run each of our tests | ||
| foreach ($case in $testCases) | ||
| { | ||
| TestSelfCopy($case) | ||
| } | ||
| } | ||
|
|
||
| Describe "Extended FileSystem Item/Content Cmdlet Provider Tests" -Tags "Feature" { | ||
| BeforeAll { | ||
| $testDir = "testDir" | ||
|
|
@@ -629,14 +801,14 @@ Describe "Extended FileSystem Path/Location Cmdlet Provider Tests" -Tags "Featur | |
| $result = Split-Path -Path $level1_0Full -Leaf | ||
| $result | Should Be $level1_0 | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It distracts attention. We should make separate PR for formatting. |
||
| It 'Validate LeafBase' { | ||
| $result = Split-Path -Path "$level2_1Full$fileExt" -LeafBase | ||
| $result | Should Be $level2_1 | ||
| } | ||
|
|
||
| It 'Validate LeafBase is not over-zealous' { | ||
|
|
||
| $result = Split-Path -Path "$level2_1Full$fileExt$fileExt" -LeafBase | ||
| $result | Should Be "$level2_1$fileExt" | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneeded formatting. Please renew the comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed