private CreateHardLinkResult CreateHardLinkWin(AbsolutePath sourceFileName, AbsolutePath destinationFileName, bool replaceExisting) { SafeFileHandle sourceFileHandle = NativeMethods.CreateFile( sourceFileName.Path, 0, /* Do not need to request any particular access to modify link info */ FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open, 0 /* Allow symbolic links to redirect us */, IntPtr.Zero); using (sourceFileHandle) { if (sourceFileHandle.IsInvalid) { switch (Marshal.GetLastWin32Error()) { case NativeMethods.ERROR_FILE_NOT_FOUND: case NativeMethods.ERROR_PATH_NOT_FOUND: return(CreateHardLinkResult.FailedSourceDoesNotExist); case NativeMethods.ERROR_ACCESS_DENIED: return(CreateHardLinkResult.FailedSourceAccessDenied); default: return(CreateHardLinkResult.FailedSourceHandleInvalid); } } if (destinationFileName.Length >= FileSystemConstants.MaxPath) { return(CreateHardLinkResult.FailedPathTooLong); } const string DosToNtPathPrefix = @"\??\"; // NtSetInformationFile always expects a special prefix even for short paths. string path = DosToNtPathPrefix + destinationFileName.GetPathWithoutLongPathPrefix(); var linkInfo = new NativeMethods.FileLinkInformation(path, replaceExisting); NativeMethods.NtStatus status = setLink(sourceFileHandle, linkInfo); if (status.StatusCodeUint == (uint)NativeMethods.NtStatusCode.StatusAccessDenied) { // Access denied status can be returned by two reasons: // 1. Something went wrong with the source path // 2. Something went wrong with the destination path. var retry = false; // For case 1: we'll make sure that the source file allows attribute writes. if (!FileUtilities.HasWritableAttributeAccessControl(sourceFileName.Path)) { AllowAttributeWrites(sourceFileName); retry = true; } // For case 2: we'll check the destination's file attribute // and if the file has readonly attributes, then we'll remove them and will try to create hardlink one more time. if (this.TryGetFileAttributes(destinationFileName, out var attributes) && (attributes & FileAttributes.ReadOnly) != 0) { SetFileAttributes(destinationFileName, FileAttributes.Normal); retry = true; } if (retry) { status = setLink(sourceFileHandle, linkInfo); } } if (status.Failed) { switch (status.StatusCodeUint) { case (uint)NativeMethods.NtStatusCode.StatusTooManyLinks: return(CreateHardLinkResult.FailedMaxHardLinkLimitReached); case (uint)NativeMethods.NtStatusCode.StatusObjectNameCollision: return(CreateHardLinkResult.FailedDestinationExists); case (uint)NativeMethods.NtStatusCode.StatusNotSameDevice: return(CreateHardLinkResult.FailedSourceAndDestinationOnDifferentVolumes); case (uint)NativeMethods.NtStatusCode.StatusAccessDenied: return(CreateHardLinkResult.FailedAccessDenied); case (uint)NativeMethods.NtStatusCode.StatusNotSupported: return(CreateHardLinkResult.FailedNotSupported); case (uint)NativeMethods.NtStatusCode.StatusObjectPathNotFound: return(CreateHardLinkResult.FailedDestinationDirectoryDoesNotExist); default: throw new NTStatusException(status.StatusCodeUint, status.StatusName, string.Format( CultureInfo.InvariantCulture, "Unable to create hard link at '{0}', pointing to existing file '{1}' with NTSTATUS:[0x{2:X}] = [{3}]", destinationFileName, sourceFileName, status.StatusCodeUint, status.StatusName)); } } return(CreateHardLinkResult.Success); }
private CreateHardLinkResult CreateHardLinkWin(AbsolutePath sourceFileName, AbsolutePath destinationFileName, bool replaceExisting) { SafeFileHandle sourceFileHandle = NativeMethods.CreateFile( sourceFileName.Path, 0, /* Do not need to request any particular access to modify link info */ FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open, 0 /* Allow symbolic links to redirect us */, IntPtr.Zero); using (sourceFileHandle) { if (sourceFileHandle.IsInvalid) { switch (Marshal.GetLastWin32Error()) { case NativeMethods.ERROR_FILE_NOT_FOUND: case NativeMethods.ERROR_PATH_NOT_FOUND: return(CreateHardLinkResult.FailedSourceDoesNotExist); case NativeMethods.ERROR_ACCESS_DENIED: return(CreateHardLinkResult.FailedSourceAccessDenied); default: return(CreateHardLinkResult.FailedSourceHandleInvalid); } } if (destinationFileName.Length >= FileSystemConstants.MaxPath) { return(CreateHardLinkResult.FailedPathTooLong); } const string DosToNtPathPrefix = @"\??\"; // NtSetInformationFile always expects a special prefix even for short paths. string path = DosToNtPathPrefix + destinationFileName.GetPathWithoutLongPathPrefix(); var linkInfo = new NativeMethods.FileLinkInformation(path, replaceExisting); NativeMethods.NtStatus status = setLink(sourceFileHandle, linkInfo); if (status.StatusCodeUint == (uint)NativeMethods.NtStatusCode.StatusAccessDenied) { if ((GetFileAttributes(destinationFileName) & FileAttributes.ReadOnly) != 0) { SetFileAttributes(destinationFileName, FileAttributes.Normal); status = setLink(sourceFileHandle, linkInfo); } } if (status.Failed) { switch (status.StatusCodeUint) { case (uint)NativeMethods.NtStatusCode.StatusTooManyLinks: return(CreateHardLinkResult.FailedMaxHardLinkLimitReached); case (uint)NativeMethods.NtStatusCode.StatusObjectNameCollision: return(CreateHardLinkResult.FailedDestinationExists); case (uint)NativeMethods.NtStatusCode.StatusNotSameDevice: return(CreateHardLinkResult.FailedSourceAndDestinationOnDifferentVolumes); case (uint)NativeMethods.NtStatusCode.StatusAccessDenied: return(CreateHardLinkResult.FailedAccessDenied); case (uint)NativeMethods.NtStatusCode.StatusNotSupported: return(CreateHardLinkResult.FailedNotSupported); case (uint)NativeMethods.NtStatusCode.StatusObjectPathNotFound: return(CreateHardLinkResult.FailedDestinationDirectoryDoesNotExist); default: throw new NTStatusException(status.StatusCodeUint, status.StatusName, string.Format( CultureInfo.InvariantCulture, "Unable to create hard link at '{0}', pointing to existing file '{1}' with NTSTATUS:[0x{2:X}] = [{3}]", destinationFileName, sourceFileName, status.StatusCodeUint, status.StatusName)); } } return(CreateHardLinkResult.Success); } NativeMethods.NtStatus setLink(SafeFileHandle handle, NativeMethods.FileLinkInformation linkInfo) { return(NativeMethods.NtSetInformationFile( handle, out _, linkInfo, (uint)Marshal.SizeOf(linkInfo), NativeMethods.FileInformationClass.FileLinkInformation)); } }