Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,11 @@ internal static bool NonWindowsIsDirectory(string path)
return Unix.NativeMethods.IsDirectory(path);
}

internal static bool NonWindowsIsSameFileSystemItem(string pathOne, string pathTwo)
{
return Unix.NativeMethods.IsSameFileSystemItem(pathOne, pathTwo);
}

internal static bool NonWindowsIsExecutable(string path)
{
return Unix.NativeMethods.IsExecutable(path);
Expand All @@ -530,7 +535,7 @@ internal static int NonWindowsGetProcessParentPid(int pid)
return IsOSX ? Unix.NativeMethods.GetPPid(pid) : Unix.GetProcFSParentPid(pid);
}



// Unix specific implementations of required functionality
//
Expand Down Expand Up @@ -722,6 +727,11 @@ internal static extern int CreateHardLink([MarshalAs(UnmanagedType.LPStr)]string
[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool IsDirectory([MarshalAs(UnmanagedType.LPStr)]string filePath);

[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool IsSameFileSystemItem([MarshalAs(UnmanagedType.LPStr)]string filePathOne,
[MarshalAs(UnmanagedType.LPStr)]string filePathTwo);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 link
Copy Markdown
Collaborator

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.

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.

Fixed

// Copy-Item from session
if (fromSession != null)
{
Expand Down Expand Up @@ -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;
}

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.

Unneeded formatting. Please renew the comments.

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.

Fixed

// Verify that the target doesn't represent a device name
if (PathIsReservedDeviceName(destinationPath, "CopyError"))
{
Expand Down Expand Up @@ -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;

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.

Please clarify - do we really need PosixSemantics here?

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.

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
Expand Down Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions src/libpsl-native/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_library(psl-native SHARED
geterrorcategory.cpp
isfile.cpp
isdirectory.cpp
issamefilesystemitem.cpp
issymlink.cpp
isexecutable.cpp
setdate.cpp
Expand Down
48 changes: 48 additions & 0 deletions src/libpsl-native/src/issamefilesystemitem.cpp
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

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.

returns what?
Below the same.

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.

Please correct this.


#include "getstat.h"
#include "issamefilesystemitem.h"

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.

Extra 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.

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;
}
11 changes: 11 additions & 0 deletions src/libpsl-native/src/issamefilesystemitem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include "pal.h"

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.

Extra 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.

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
Expand Up @@ -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

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.

This is unneeded function because RequireAdminOnWindows ensures elevated rights.

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.

Function removed, along with use of $canSymLink

{
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)

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.

Usually we set -Tags @('CI', 'RequireAdminOnWindows').

}
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

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.

This comment is obvious and useless. Please remove.

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.

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) {

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.

With moving to 'RequireAdminOnWindows' I believe we can use It "" -TestCase $testCases directly without TestSelfCopy

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

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.

If previous Should Be is failed the cleanup code will be unreacheable.
Please move the cleanup code to AfterEach.

}
}
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"

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.

Please use ShouldBeErrorId

$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"
Expand Down Expand Up @@ -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
}

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.

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"
}
Expand Down