mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
282 lines
11 KiB
C#
282 lines
11 KiB
C#
#region Copyright Preamble
|
|
|
|
//
|
|
// Copyright © 2015 NCode Group
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#endregion
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using NCode.ReparsePoints.Win32;
|
|
|
|
namespace NCode.ReparsePoints
|
|
{
|
|
internal class ReparsePointProvider
|
|
{
|
|
/// <summary>
|
|
/// Given a path, determines the type of reparse point.
|
|
/// </summary>
|
|
/// <param name="path">The path to inspect.</param>
|
|
/// <returns>A <see cref="LinkType"/> enumeration.</returns>
|
|
public virtual LinkType GetLinkType(string path)
|
|
{
|
|
Win32FindData data;
|
|
using (var handle = NativeMethods.FindFirstFile(path, out data)) {
|
|
if (handle.IsInvalid)
|
|
return LinkType.Unknown;
|
|
|
|
if (!data.FileAttributes.HasFlag(FileAttributes.ReparsePoint)) {
|
|
return data.FileAttributes.HasFlag(FileAttributes.Directory)
|
|
? LinkType.Unknown
|
|
: LinkType.HardLink;
|
|
}
|
|
|
|
switch (data.Reserved0) {
|
|
case Win32Constants.IO_REPARSE_TAG_SYMLINK:
|
|
return LinkType.Symbolic;
|
|
|
|
case Win32Constants.IO_REPARSE_TAG_MOUNT_POINT:
|
|
return LinkType.Junction;
|
|
}
|
|
}
|
|
|
|
return LinkType.Unknown;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a path, returns the information about a reparse point.
|
|
/// </summary>
|
|
/// <param name="path">The path to inspect.</param>
|
|
/// <returns>A <see cref="ReparseLink"/> that contains the information
|
|
/// about a reparse point.</returns>
|
|
public virtual ReparseLink GetLink(string path)
|
|
{
|
|
FileAttributes attributes;
|
|
try {
|
|
attributes = File.GetAttributes(path);
|
|
} catch (DirectoryNotFoundException) {
|
|
return new ReparseLink();
|
|
} catch (FileNotFoundException) {
|
|
return new ReparseLink();
|
|
}
|
|
|
|
var link = new ReparseLink {
|
|
Attributes = attributes
|
|
};
|
|
|
|
if (!attributes.HasFlag(FileAttributes.ReparsePoint)) {
|
|
link.Type = attributes.HasFlag(FileAttributes.Directory)
|
|
? LinkType.Unknown
|
|
: LinkType.HardLink;
|
|
|
|
return link;
|
|
}
|
|
|
|
var encoding = Encoding.Unicode;
|
|
var reparseHeaderSize = Marshal.SizeOf<ReparseHeader>();
|
|
var bufferLength = reparseHeaderSize + 2048;
|
|
|
|
using (var hReparsePoint = OpenReparsePoint(path, AccessRights.GenericRead)) {
|
|
int error;
|
|
do {
|
|
using (var buffer = SafeLocalAllocHandle.Allocate(bufferLength)) {
|
|
int bytesReturned;
|
|
var b = NativeMethods.DeviceIoControl(
|
|
hReparsePoint,
|
|
Win32Constants.FSCTL_GET_REPARSE_POINT,
|
|
SafeLocalAllocHandle.InvalidHandle,
|
|
0,
|
|
buffer,
|
|
bufferLength,
|
|
out bytesReturned,
|
|
IntPtr.Zero);
|
|
error = Marshal.GetLastWin32Error();
|
|
|
|
if (b) {
|
|
var reparseHeader = buffer.Read<ReparseHeader>(0);
|
|
|
|
IReparseData data;
|
|
switch (reparseHeader.ReparseTag) {
|
|
case Win32Constants.IO_REPARSE_TAG_MOUNT_POINT:
|
|
data = buffer.Read<JunctionData>(reparseHeaderSize);
|
|
link.Type = LinkType.Junction;
|
|
break;
|
|
|
|
case Win32Constants.IO_REPARSE_TAG_SYMLINK:
|
|
data = buffer.Read<SymbolicData>(reparseHeaderSize);
|
|
link.Type = LinkType.Symbolic;
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(
|
|
String.Format(
|
|
"An unknown reparse tag {0:X} was encountered.",
|
|
reparseHeader.ReparseTag));
|
|
}
|
|
|
|
var offset = Marshal.SizeOf(data) + reparseHeaderSize;
|
|
var target = buffer.ReadString(offset + data.SubstituteNameOffset, data.SubstituteNameLength, encoding);
|
|
|
|
link.Target = ParseDosDevicePath(target);
|
|
return link;
|
|
}
|
|
|
|
if (error == Win32Constants.ERROR_INSUFFICIENT_BUFFER) {
|
|
var reparseHeader = buffer.Read<ReparseHeader>(0);
|
|
bufferLength = reparseHeader.ReparseDataLength;
|
|
} else {
|
|
throw new Win32Exception(error);
|
|
}
|
|
}
|
|
} while (error == Win32Constants.ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
public virtual void CreateHardLink(string file, string target)
|
|
{
|
|
if (!NativeMethods.CreateHardLink(file, target, IntPtr.Zero))
|
|
throw new Win32Exception();
|
|
}
|
|
|
|
public virtual void CreateSymbolicLink(string path, string target, bool isDirectory)
|
|
{
|
|
var flags = isDirectory
|
|
? SymbolicLinkFlag.Directory
|
|
: SymbolicLinkFlag.File;
|
|
|
|
if (!NativeMethods.CreateSymbolicLink(path, target, flags))
|
|
throw new Win32Exception();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to create a junction.
|
|
/// </summary>
|
|
/// <param name="path">The path of the junction to create.</param>
|
|
/// <param name="target">The target for the junction.</param>
|
|
public virtual void CreateJunction(string path, string target)
|
|
{
|
|
path = Path.GetFullPath(path);
|
|
target = Path.GetFullPath(target);
|
|
|
|
Win32FindData data;
|
|
using (var handle = NativeMethods.FindFirstFile(path, out data)) {
|
|
if (!handle.IsInvalid)
|
|
throw new InvalidOperationException("A file or folder already exists with the same name as the junction.");
|
|
}
|
|
|
|
Directory.CreateDirectory(path);
|
|
|
|
var encoding = Encoding.Unicode;
|
|
var nullChar = new byte[] { 0, 0 };
|
|
|
|
var printName = ParseDosDevicePath(target);
|
|
var printNameBytes = encoding.GetBytes(printName);
|
|
var printNameLength = printNameBytes.Length;
|
|
|
|
var substituteName = FormatDosDevicePath(printName, false);
|
|
var substituteNameBytes = encoding.GetBytes(substituteName);
|
|
var substituteNameLength = substituteNameBytes.Length;
|
|
|
|
var junction = new JunctionData {
|
|
SubstituteNameOffset = 0,
|
|
SubstituteNameLength = checked((ushort) substituteNameLength),
|
|
PrintNameOffset = checked((ushort) (substituteNameLength + nullChar.Length)),
|
|
PrintNameLength = checked((ushort) printNameLength)
|
|
};
|
|
|
|
var junctionLength = Marshal.SizeOf(junction) + nullChar.Length * 2;
|
|
var reparseLength = junctionLength + junction.SubstituteNameLength + junction.PrintNameLength;
|
|
|
|
var reparse = new ReparseHeader {
|
|
ReparseTag = Win32Constants.IO_REPARSE_TAG_MOUNT_POINT,
|
|
ReparseDataLength = checked((ushort) (reparseLength)),
|
|
Reserved = 0,
|
|
};
|
|
|
|
var bufferLength = Marshal.SizeOf(reparse) + reparse.ReparseDataLength;
|
|
|
|
using (var hReparsePoint = OpenReparsePoint(path, AccessRights.GenericWrite))
|
|
using (var buffer = SafeLocalAllocHandle.Allocate(bufferLength)) {
|
|
var offset = buffer.Write(0, reparse);
|
|
offset += buffer.Write(offset, junction);
|
|
offset += buffer.Write(offset, substituteNameBytes, 0, substituteNameBytes.Length);
|
|
offset += buffer.Write(offset, nullChar, 0, nullChar.Length);
|
|
offset += buffer.Write(offset, printNameBytes, 0, printNameBytes.Length);
|
|
offset += buffer.Write(offset, nullChar, 0, nullChar.Length);
|
|
Debug.Assert(offset == bufferLength);
|
|
|
|
int bytesReturned;
|
|
var b = NativeMethods.DeviceIoControl(
|
|
hReparsePoint,
|
|
Win32Constants.FSCTL_SET_REPARSE_POINT,
|
|
buffer,
|
|
bufferLength,
|
|
SafeLocalAllocHandle.InvalidHandle,
|
|
0,
|
|
out bytesReturned,
|
|
IntPtr.Zero);
|
|
|
|
if (!b) throw new Win32Exception();
|
|
}
|
|
}
|
|
|
|
private static string FormatDosDevicePath(string path, bool sanitize)
|
|
{
|
|
if (sanitize)
|
|
path = ParseDosDevicePath(path);
|
|
|
|
return Win32Constants.NonInterpretedPathPrefix + path + "\\";
|
|
}
|
|
|
|
private static string ParseDosDevicePath(string path)
|
|
{
|
|
var result = Win32Constants
|
|
.DosDevicePrefixes
|
|
.Where(prefix => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
|
.Aggregate(path, (current, prefix) => current.Remove(0, prefix.Length));
|
|
|
|
while (result.EndsWith("\\"))
|
|
result = result.Remove(result.Length - 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
private static SafeFileHandle OpenReparsePoint(string reparsePoint, AccessRights accessRights)
|
|
{
|
|
var hFile = NativeMethods.CreateFile(
|
|
reparsePoint,
|
|
accessRights,
|
|
FileShareMode.FileShareRead | FileShareMode.FileShareWrite,
|
|
IntPtr.Zero,
|
|
FileCreationDisposition.OpenExisting,
|
|
FileAttributeFlags.FileFlagBackupSemantics | FileAttributeFlags.FileFlagOpenReparsePoint,
|
|
IntPtr.Zero);
|
|
|
|
if (hFile.IsInvalid)
|
|
throw new Win32Exception();
|
|
|
|
return hFile;
|
|
}
|
|
}
|
|
} |