Refactor NativeMethods into PlatformUtil class which only exposes helper functions

This commit is contained in:
Caelan Sayler
2022-05-25 13:00:41 +01:00
parent d3751710de
commit 23e60cb0c1
18 changed files with 711 additions and 836 deletions

View File

@@ -102,7 +102,7 @@ namespace Squirrel.CommandLine
protected static void InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir)
{
var result = ProcessUtil.InvokeProcess(exePath, args, workingDir, CancellationToken.None);
var result = PlatformUtil.InvokeProcess(exePath, args, workingDir, CancellationToken.None);
if (result.ExitCode != 0) {
throw new Exception(
$"Command failed:\n{result.Command}\n\n" +

View File

@@ -99,7 +99,7 @@ namespace Squirrel.CommandLine.OSX
throw new OptionValidationException("--exeName is required when generating a new app bundle.");
var mainExePath = Path.Combine(options.packDirectory, exeName);
if (!File.Exists(mainExePath) || !Utility.IsMachOImage(mainExePath))
if (!File.Exists(mainExePath) || !PlatformUtil.IsMachOImage(mainExePath))
throw new OptionValidationException($"--exeName '{mainExePath}' does not exist or is not a mach-o executable.");
var appleId = $"com.{options.packAuthors ?? options.packId}.{options.packId}";

View File

@@ -54,10 +54,10 @@ namespace Squirrel.CommandLine.Windows
List<string> args = new List<string>();
args.Add("sign");
args.AddRange(NativeMethods.CommandLineToArgvW(signArguments));
args.AddRange(PlatformUtil.CommandLineToArgvW(signArguments));
args.Add(filePath);
var result = ProcessUtil.InvokeProcess(SignToolPath, args, null, CancellationToken.None);
var result = PlatformUtil.InvokeProcess(SignToolPath, args, null, CancellationToken.None);
if (result.ExitCode != 0) {
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
throw new Exception(
@@ -74,12 +74,12 @@ namespace Squirrel.CommandLine.Windows
if (CheckIsAlreadySigned(filePath)) return;
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
var args = NativeMethods.CommandLineToArgvW(command);
var args = PlatformUtil.CommandLineToArgvW(command);
if (args.Length < 2)
throw new OptionValidationException("Invalid signing template");
var result = ProcessUtil.InvokeProcess(args[0], args.Skip(1), null, CancellationToken.None);
var result = PlatformUtil.InvokeProcess(args[0], args.Skip(1), null, CancellationToken.None);
if (result.ExitCode != 0) {
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
throw new Exception(

View File

@@ -108,8 +108,7 @@ namespace Squirrel
}
if (force) {
this.Log().Info($"Killing running processes in '{RootAppDir}'.");
Utility.KillProcessesInDirectory(RootAppDir);
PlatformUtil.KillProcessesInDirectory(RootAppDir);
}
// 'current' does exist, and it's wrong, so lets get rid of it
@@ -192,7 +191,7 @@ namespace Squirrel
args.Add(arguments);
}
return ProcessUtil.StartNonBlocking(UpdateExePath, args, Path.GetDirectoryName(UpdateExePath));
return PlatformUtil.StartProcessNonBlocking(UpdateExePath, args, Path.GetDirectoryName(UpdateExePath));
}
internal VersionDirInfo GetLatestVersion()
@@ -375,11 +374,11 @@ namespace Squirrel
/// <inheritdoc />
public override bool IsUpdateExe { get; }
/// <inheritdoc />
public override string CurrentVersionDir => RootAppDir;
/// <inheritdoc />
public override SemanticVersion CurrentlyInstalledVersion { get; }
/// <inheritdoc />
public override string CurrentVersionDir => RootAppDir;
/// <inheritdoc />
public override string AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId);

View File

@@ -1,38 +0,0 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Squirrel
{
[SupportedOSPlatform("osx")]
internal static class NativeMac
{
private const string SystemLib = "libSystem.dylib";
[DllImport(SystemLib)]
public static extern int getppid();
[DllImport(SystemLib, SetLastError = true)]
private static extern int chmod(string pathname, int mode);
public static void ChmodAsExe(string filePath)
{
var filePermissionOctal = Convert.ToInt32("777", 8);
const int EINTR = 4;
int chmodReturnCode = 0;
do
{
chmodReturnCode = chmod(filePath, filePermissionOctal);
}
while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
if (chmodReturnCode == -1)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {filePath}.");
}
}
}
}

View File

@@ -1,391 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
namespace Squirrel
{
[SupportedOSPlatform("windows")]
internal static class NativeMethods
{
public static int GetParentProcessId()
{
var pbi = new PROCESS_BASIC_INFORMATION();
//Get a handle to our own process
IntPtr hProc = OpenProcess((ProcessAccess) 0x001F0FFF, false, Process.GetCurrentProcess().Id);
try {
int sizeInfoReturned;
int queryStatus = NtQueryInformationProcess(hProc, (PROCESSINFOCLASS) 0, ref pbi, pbi.Size, out sizeInfoReturned);
} finally {
if (!hProc.Equals(IntPtr.Zero)) {
//Close handle and free allocated memory
CloseHandle(hProc);
hProc = IntPtr.Zero;
}
}
return (int) pbi.InheritedFromUniqueProcessId;
}
[DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
private static extern IntPtr _LocalFree(IntPtr hMem);
[DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
public static string[] CommandLineToArgvW(string cmdLine)
{
IntPtr argv = IntPtr.Zero;
try {
int numArgs = 0;
argv = _CommandLineToArgvW(cmdLine, out numArgs);
if (argv == IntPtr.Zero) {
throw new Win32Exception();
}
var result = new string[numArgs];
for (int i = 0; i < numArgs; i++) {
IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
result[i] = Marshal.PtrToStringUni(currArg);
}
return result;
} finally {
_LocalFree(argv);
}
}
[DllImport("shell32.dll", SetLastError = true)]
public static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr LoadLibraryEx(string lpModuleName, IntPtr hFile, uint dwFlags);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, string lpType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResourceEx(IntPtr hModule, IntPtr lpType, IntPtr lpName, ushort wLanguage);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResourceEx(IntPtr hModule, string lpType, IntPtr lpName, ushort wLanguage);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResourceEx(IntPtr hModule, string lpType, string lpName, ushort wLanguage);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint SizeofResource(IntPtr hModule, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadResource(IntPtr hModule, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LockResource(IntPtr hglobal);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool FreeLibrary(IntPtr hModule);
[DllImport("version.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetFileVersionInfo(
string lpszFileName,
int dwHandleIgnored,
int dwLen,
[MarshalAs(UnmanagedType.LPArray)] byte[] lpData);
[DllImport("version.dll", SetLastError = true)]
internal static extern int GetFileVersionInfoSize(
string lpszFileName,
IntPtr dwHandleIgnored);
[DllImport("version.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool VerQueryValue(
byte[] pBlock,
string pSubBlock,
out IntPtr pValue,
out int len);
[DllImport("psapi.dll", SetLastError = true)]
internal static extern bool EnumProcesses(
IntPtr pProcessIds, // pointer to allocated DWORD array
int cb,
out int pBytesReturned);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool QueryFullProcessImageName(
IntPtr hProcess,
[In] int justPassZeroHere,
[Out] StringBuilder lpImageFileName,
[In][MarshalAs(UnmanagedType.U4)] ref int nSize);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr OpenProcess(
ProcessAccess processAccess,
bool bInheritHandle,
int processId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hHandle);
[DllImport("NTDLL.DLL", SetLastError = true)]
internal static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
[DllImport("kernel32.dll", EntryPoint = "GetStdHandle")]
internal static extern IntPtr GetStdHandle(StandardHandles nStdHandle);
[DllImport("kernel32.dll", EntryPoint = "AllocConsole")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll")]
internal static extern bool AttachConsole(int pid);
[DllImport("Kernel32.dll", SetLastError = true)]
internal static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("Kernel32.dll", SetLastError = true)]
internal static extern bool UpdateResource(IntPtr handle, string pType, IntPtr pName, short language, [MarshalAs(UnmanagedType.LPArray)] byte[] pData, int dwSize);
[DllImport("Kernel32.dll", SetLastError = true)]
internal static extern bool EndUpdateResource(IntPtr handle, bool discard);
#nullable enable
/// <summary>
/// The ApplyDelta function use the specified delta and source files to create a new copy of the target file.
/// </summary>
/// <param name="applyFlags">Either DELTA_FLAG_NONE or DELTA_APPLY_FLAG_ALLOW_PA19.</param>
/// <param name="sourceName">The name of the source file to which the delta is to be applied.</param>
/// <param name="deltaName">The name of the delta to be applied to the source file.</param>
/// <param name="targetName">The name of the target file that is to be created.</param>
/// <returns>
/// Returns TRUE on success or FALSE otherwise.
/// </returns>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#applydeltaaw
/// </remarks>
[DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool ApplyDelta(
[MarshalAs(UnmanagedType.I8)] ApplyFlags applyFlags,
string sourceName,
string deltaName,
string targetName);
/// <summary>
/// The CreateDelta function creates a delta from the specified source and target files and write the output delta to the designated file name.
/// </summary>
/// <param name="fileTypeSet">The file type set used for Create.</param>
/// <param name="setFlags">The file type set used for Create.</param>
/// <param name="resetFlags">The file type set used for Create.</param>
/// <param name="sourceName">The file type set used for Create.</param>
/// <param name="targetName">The name of the target against which the source is compared.</param>
/// <param name="sourceOptionsName">Reserved. Pass NULL.</param>
/// <param name="targetOptionsName">Reserved. Pass NULL.</param>
/// <param name="globalOptions">Reserved. Pass a DELTA_INPUT structure with lpStart set to NULL and uSize set to 0.</param>
/// <param name="targetFileTime">The time stamp set on the target file after delta Apply. If NULL, the timestamp of the target file during delta Create will be used.</param>
/// <param name="hashAlgId">ALG_ID of the algorithm to be used to generate the target signature.</param>
/// <param name="deltaName">The name of the delta file to be created.</param>
/// <returns>
/// Returns TRUE on success or FALSE otherwise.
/// </returns>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#createdeltaaw
/// </remarks>
[DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CreateDelta(
[MarshalAs(UnmanagedType.I8)] FileTypeSet fileTypeSet,
[MarshalAs(UnmanagedType.I8)] CreateFlags setFlags,
[MarshalAs(UnmanagedType.I8)] CreateFlags resetFlags,
string sourceName,
string targetName,
string? sourceOptionsName,
string? targetOptionsName,
DeltaInput globalOptions,
IntPtr targetFileTime,
[MarshalAs(UnmanagedType.U4)] HashAlgId hashAlgId,
string deltaName);
#nullable restore
}
[Flags]
internal enum ProcessAccess : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
internal enum PROCESSINFOCLASS : int
{
ProcessBasicInformation = 0, // 0, q: PROCESS_BASIC_INFORMATION, PROCESS_EXTENDED_BASIC_INFORMATION
ProcessQuotaLimits, // qs: QUOTA_LIMITS, QUOTA_LIMITS_EX
ProcessIoCounters, // q: IO_COUNTERS
ProcessVmCounters, // q: VM_COUNTERS, VM_COUNTERS_EX
ProcessTimes, // q: KERNEL_USER_TIMES
ProcessBasePriority, // s: KPRIORITY
ProcessRaisePriority, // s: ULONG
ProcessDebugPort, // q: HANDLE
ProcessExceptionPort, // s: HANDLE
ProcessAccessToken, // s: PROCESS_ACCESS_TOKEN
ProcessLdtInformation, // 10
ProcessLdtSize,
ProcessDefaultHardErrorMode, // qs: ULONG
ProcessIoPortHandlers, // (kernel-mode only)
ProcessPooledUsageAndLimits, // q: POOLED_USAGE_AND_LIMITS
ProcessWorkingSetWatch, // q: PROCESS_WS_WATCH_INFORMATION[]; s: void
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup, // s: BOOLEAN
ProcessPriorityClass, // qs: PROCESS_PRIORITY_CLASS
ProcessWx86Information,
ProcessHandleCount, // 20, q: ULONG, PROCESS_HANDLE_INFORMATION
ProcessAffinityMask, // s: KAFFINITY
ProcessPriorityBoost, // qs: ULONG
ProcessDeviceMap, // qs: PROCESS_DEVICEMAP_INFORMATION, PROCESS_DEVICEMAP_INFORMATION_EX
ProcessSessionInformation, // q: PROCESS_SESSION_INFORMATION
ProcessForegroundInformation, // s: PROCESS_FOREGROUND_BACKGROUND
ProcessWow64Information, // q: ULONG_PTR
ProcessImageFileName, // q: UNICODE_STRING
ProcessLUIDDeviceMapsEnabled, // q: ULONG
ProcessBreakOnTermination, // qs: ULONG
ProcessDebugObjectHandle, // 30, q: HANDLE
ProcessDebugFlags, // qs: ULONG
ProcessHandleTracing, // q: PROCESS_HANDLE_TRACING_QUERY; s: size 0 disables, otherwise enables
ProcessIoPriority, // qs: ULONG
ProcessExecuteFlags, // qs: ULONG
ProcessResourceManagement,
ProcessCookie, // q: ULONG
ProcessImageInformation, // q: SECTION_IMAGE_INFORMATION
ProcessCycleTime, // q: PROCESS_CYCLE_TIME_INFORMATION
ProcessPagePriority, // q: ULONG
ProcessInstrumentationCallback, // 40
ProcessThreadStackAllocation, // s: PROCESS_STACK_ALLOCATION_INFORMATION, PROCESS_STACK_ALLOCATION_INFORMATION_EX
ProcessWorkingSetWatchEx, // q: PROCESS_WS_WATCH_INFORMATION_EX[]
ProcessImageFileNameWin32, // q: UNICODE_STRING
ProcessImageFileMapping, // q: HANDLE (input)
ProcessAffinityUpdateMode, // qs: PROCESS_AFFINITY_UPDATE_MODE
ProcessMemoryAllocationMode, // qs: PROCESS_MEMORY_ALLOCATION_MODE
ProcessGroupInformation, // q: USHORT[]
ProcessTokenVirtualizationEnabled, // s: ULONG
ProcessConsoleHostProcess, // q: ULONG_PTR
ProcessWindowInformation, // 50, q: PROCESS_WINDOW_INFORMATION
ProcessHandleInformation, // q: PROCESS_HANDLE_SNAPSHOT_INFORMATION // since WIN8
ProcessMitigationPolicy, // s: PROCESS_MITIGATION_POLICY_INFORMATION
ProcessDynamicFunctionTableInformation,
ProcessHandleCheckingMode,
ProcessKeepAliveCount, // q: PROCESS_KEEPALIVE_COUNT_INFORMATION
ProcessRevokeFileHandles, // s: PROCESS_REVOKE_FILE_HANDLES_INFORMATION
MaxProcessInfoClass
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct PROCESS_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr PebBaseAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public UIntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
public int Size {
get { return (int) Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)); }
}
}
internal enum StandardHandles : int
{
STD_INPUT_HANDLE = -10,
STD_OUTPUT_HANDLE = -11,
STD_ERROR_HANDLE = -12,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltaflagtypeflags
/// </remarks>
internal enum ApplyFlags : long
{
/// <summary>Indicates no special handling.</summary>
None = 0,
/// <summary>Allow MSDelta to apply deltas created using PatchAPI.</summary>
AllowLegacy = 1,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#filetypesets
/// </remarks>
[Flags]
internal enum FileTypeSet : long
{
/// <summary>
/// File type set that includes I386, IA64 and AMD64 Portable Executable (PE) files. Others are treated as raw.
/// </summary>
Executables = 0x0FL,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltaflagtypeflags
/// </remarks>
internal enum CreateFlags : long
{
/// <summary>Indicates no special handling.</summary>
None = 0,
/// <summary>Allow the source, target and delta files to exceed the default size limit.</summary>
IgnoreFileSizeLimit = 1 << 17,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltainputstructure
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct DeltaInput
{
/// <summary>Memory address non-editable input buffer.</summary>
public IntPtr Start;
/// <summary>Size of the memory buffer in bytes.</summary>
public IntPtr Size;
/// <summary>
/// Defines whether MSDelta is allowed to edit the input buffer. If you make the input editable, the buffer will
/// be zeroed at function return. However this will cause most MSDelta functions to use less memory.
/// </summary>
[MarshalAs(UnmanagedType.Bool)] public bool Editable;
}
internal enum HashAlgId
{
/// <summary>No signature.</summary>
None = 0,
/// <summary>32-bit CRC defined in msdelta.dll.</summary>
Crc32 = 32,
}
}

View File

@@ -0,0 +1,529 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Squirrel.SimpleSplat;
namespace Squirrel
{
internal static class PlatformUtil
{
static IFullLogger Log => SquirrelLocator.Current.GetService<ILogManager>().GetLogger(typeof(PlatformUtil));
private const string OSX_CSTD_LIB = "libSystem.dylib";
private const string NIX_CSTD_LIB = "libc";
private const string WIN_KERNEL32 = "kernel32.dll";
private const string WIN_SHELL32 = "shell32.dll";
private const string WIN_NTDLL = "NTDLL.DLL";
private const string WIN_PSAPI = "psapi.dll";
[SupportedOSPlatform("linux")]
[DllImport(NIX_CSTD_LIB, EntryPoint = "getppid")]
private static extern int nix_getppid();
[SupportedOSPlatform("osx")]
[DllImport(OSX_CSTD_LIB, EntryPoint = "getppid")]
private static extern int osx_getppid();
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32)]
private static extern IntPtr GetCurrentProcess();
[SupportedOSPlatform("windows")]
[DllImport(WIN_NTDLL, SetLastError = true)]
private static extern int NtQueryInformationProcess(IntPtr hProcess, int pic, ref PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct PROCESS_BASIC_INFORMATION
{
public nint ExitStatus;
public nint PebBaseAddress;
public nint AffinityMask;
public nint BasePriority;
public nuint UniqueProcessId;
public nint InheritedFromUniqueProcessId;
}
public static Process GetParentProcess()
{
int parentId;
if (SquirrelRuntimeInfo.IsWindows) {
var pbi = new PROCESS_BASIC_INFORMATION();
NtQueryInformationProcess(GetCurrentProcess(), 0, ref pbi, Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)), out _);
parentId = (int) pbi.InheritedFromUniqueProcessId;
} else if (SquirrelRuntimeInfo.IsLinux) {
parentId = nix_getppid();
} else if (SquirrelRuntimeInfo.IsOSX) {
parentId = osx_getppid();
} else {
throw new PlatformNotSupportedException();
}
// the parent process has exited (nix/osx)
if (parentId <= 1)
return null;
try {
var p = Process.GetProcessById(parentId);
// the retrieved process is not our parent, the pid has been reused
if (p.StartTime > Process.GetCurrentProcess().StartTime)
return null;
return p;
} catch (ArgumentException) {
// the process has exited (windows)
return null;
}
}
public static void WaitForParentProcessToExit()
{
var p = GetParentProcess();
if (p == null) {
Log.Warn("Will not wait. Parent process has already exited.");
return;
}
Log.Info($"Waiting for PID {p.Id} to exit (60s timeout)...");
var exited = p.WaitForExit(60_000);
if (!exited) {
throw new Exception("Parent wait timed out.");
}
Log.Info($"PID {p.Id} has exited.");
}
[SupportedOSPlatform("osx")]
[DllImport(OSX_CSTD_LIB, SetLastError = true)]
private static extern int osx_chmod(string pathname, int mode);
[SupportedOSPlatform("linux")]
[DllImport(NIX_CSTD_LIB, SetLastError = true)]
private static extern int nix_chmod(string pathname, int mode);
public static void ChmodFileAsExecutable(string filePath)
{
Func<string, int, int> chmod;
if (SquirrelRuntimeInfo.IsOSX) chmod = osx_chmod;
else if (SquirrelRuntimeInfo.IsLinux) chmod = nix_chmod;
else return; // no-op on windows, all .exe files can be executed.
var filePermissionOctal = Convert.ToInt32("777", 8);
const int EINTR = 4;
int chmodReturnCode;
do {
chmodReturnCode = chmod(filePath, filePermissionOctal);
} while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
if (chmodReturnCode == -1) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {filePath}.");
}
}
private enum MagicMachO : uint
{
MH_MAGIC = 0xfeedface,
MH_CIGAM = 0xcefaedfe,
MH_MAGIC_64 = 0xfeedfacf,
MH_CIGAM_64 = 0xcffaedfe
}
public static bool IsMachOImage(string filePath)
{
using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath))) {
if (reader.BaseStream.Length < 256) // Header size
return false;
uint magic = reader.ReadUInt32();
return Enum.IsDefined(typeof(MagicMachO), magic);
}
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_PSAPI, SetLastError = true)]
private static extern bool EnumProcesses(
IntPtr pProcessIds, // pointer to allocated DWORD array
int cb,
out int pBytesReturned);
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, SetLastError = true)]
private static extern bool QueryFullProcessImageName(
IntPtr hProcess,
[In] int justPassZeroHere,
[Out] StringBuilder lpImageFileName,
[In] [MarshalAs(UnmanagedType.U4)] ref int nSize);
[Flags]
private enum ProcessAccess : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccess processAccess,
bool bInheritHandle,
int processId);
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, SetLastError = true)]
private static extern bool CloseHandle(IntPtr hHandle);
[SupportedOSPlatform("windows")]
private static List<(string ProcessExePath, int ProcessId)> GetRunningProcessesWindows()
{
var pids = new int[2048];
var gch = GCHandle.Alloc(pids, GCHandleType.Pinned);
try {
if (!EnumProcesses(gch.AddrOfPinnedObject(), sizeof(int) * pids.Length, out var bytesReturned))
throw new Win32Exception("Failed to enumerate processes");
if (bytesReturned < 1)
throw new Exception("Failed to enumerate processes");
List<(string ProcessExePath, int ProcessId)> ret = new();
for (int i = 0; i < bytesReturned / sizeof(int); i++) {
IntPtr hProcess = IntPtr.Zero;
try {
hProcess = OpenProcess(ProcessAccess.QueryLimitedInformation, false, pids[i]);
if (hProcess == IntPtr.Zero)
continue;
var sb = new StringBuilder(256);
var capacity = sb.Capacity;
if (!QueryFullProcessImageName(hProcess, 0, sb, ref capacity))
continue;
var exePath = sb.ToString();
if (String.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath))
continue;
ret.Add((sb.ToString(), pids[i]));
} catch (Exception) {
// don't care
} finally {
if (hProcess != IntPtr.Zero)
CloseHandle(hProcess);
}
}
return ret;
} finally {
gch.Free();
}
}
public static List<(string ProcessExePath, int ProcessId)> GetRunningProcesses()
{
IEnumerable<(string ProcessExePath, int ProcessId)> processes = SquirrelRuntimeInfo.IsWindows
? GetRunningProcessesWindows()
: Process.GetProcesses().Select(p => (p.MainModule?.FileName, p.Id));
return processes
.Where(x => !String.IsNullOrWhiteSpace(x.ProcessExePath)) // Processes we can't query will have an empty process name
.ToList();
}
public static void KillProcessesInDirectory(string directoryToKill)
{
Log.Info("Killing all processes in " + directoryToKill);
int c = 0;
foreach (var x in GetRunningProcesses()) {
if (!Utility.IsFileInDirectory(x.ProcessExePath, directoryToKill)) {
continue;
}
if (Utility.FullPathEquals(SquirrelRuntimeInfo.EntryExePath, x.ProcessExePath)) {
Log.Info($"Skipping '{x.ProcessExePath}' (is current process)");
continue;
}
try {
Process.GetProcessById(x.ProcessId).Kill();
c++;
} catch (Exception ex) {
Log.WarnException($"Unable to terminate process (pid.{x.ProcessId})", ex);
}
}
Log.Info($"Terminated {c} processes successfully.");
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "LocalFree", SetLastError = true)]
private static extern IntPtr _LocalFree(IntPtr hMem);
[SupportedOSPlatform("windows")]
[DllImport(WIN_SHELL32, EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
[SupportedOSPlatform("windows")]
public static string[] CommandLineToArgvW(string cmdLine)
{
IntPtr argv = IntPtr.Zero;
try {
argv = _CommandLineToArgvW(cmdLine, out var numArgs);
if (argv == IntPtr.Zero) {
throw new Win32Exception();
}
var result = new string[numArgs];
for (int i = 0; i < numArgs; i++) {
IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
result[i] = Marshal.PtrToStringUni(currArg);
}
return result;
} finally {
_LocalFree(argv);
}
}
/*
* caesay 09/12/2021 at 12:10 PM
* yeah
* can I steal this for squirrel?
* Roman 09/12/2021 at 12:10 PM
* sure :)
* reference CommandRunner.cs on the github url as source? :)
* https://github.com/RT-Projects/RT.Util/blob/ef660cd693f66bc946da3aaa368893b03b74eed7/RT.Util.Core/CommandRunner.cs#L327
*/
/// <summary>
/// Given a number of argument strings, constructs a single command line string with all the arguments escaped
/// correctly so that a process using standard Windows API for parsing the command line will receive exactly the
/// strings passed in here. See Remarks.</summary>
/// <remarks>
/// The string is only valid for passing directly to a process. If the target process is invoked by passing the
/// process name + arguments to cmd.exe then further escaping is required, to counteract cmd.exe's interpretation
/// of additional special characters. See <see cref="EscapeCmdExeMetachars"/>.</remarks>
[SupportedOSPlatform("windows")]
private static string ArgsToCommandLine(IEnumerable<string> args)
{
var sb = new StringBuilder();
foreach (var arg in args) {
if (arg == null)
continue;
if (sb.Length != 0)
sb.Append(' ');
// For details, see https://web.archive.org/web/20150318010344/http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
// or https://devblogs.microsoft.com/oldnewthing/?p=12833
if (arg.Length != 0 && arg.IndexOfAny(_cmdChars) < 0)
sb.Append(arg);
else {
sb.Append('"');
for (int c = 0; c < arg.Length; c++) {
int backslashes = 0;
while (c < arg.Length && arg[c] == '\\') {
c++;
backslashes++;
}
if (c == arg.Length) {
sb.Append('\\', backslashes * 2);
break;
} else if (arg[c] == '"') {
sb.Append('\\', backslashes * 2 + 1);
sb.Append('"');
} else {
sb.Append('\\', backslashes);
sb.Append(arg[c]);
}
}
sb.Append('"');
}
}
return sb.ToString();
}
private static readonly char[] _cmdChars = new[] { ' ', '"', '\n', '\t', '\v' };
/// <summary>
/// Escapes all cmd.exe meta-characters by prefixing them with a ^. See <see cref="ArgsToCommandLine"/> for more
/// information.</summary>
[SupportedOSPlatform("windows")]
private static string EscapeCmdExeMetachars(string command)
{
var result = new StringBuilder();
foreach (var ch in command) {
switch (ch) {
case '(':
case ')':
case '%':
case '!':
case '^':
case '"':
case '<':
case '>':
case '&':
case '|':
result.Append('^');
break;
}
result.Append(ch);
}
return result.ToString();
}
private static string ArgsToCommandLineUnix(IEnumerable<string> args)
{
var sb = new StringBuilder();
foreach (var arg in args) {
if (arg == null)
continue;
if (sb.Length != 0)
sb.Append(' ');
// there are just too many 'command chars' in unix, so we play it
// super safe here and escape the string if there are any non-alpha-numeric
if (System.Text.RegularExpressions.Regex.IsMatch(arg, @"$[\w]^")) {
sb.Append(arg);
} else {
// https://stackoverflow.com/a/33949338/184746
// single quotes are 'strong quotes' and can contain everything
// except never other single quotes.
sb.Append("'");
sb.Append(arg.Replace("'", @"'\''"));
sb.Append("'");
}
}
return sb.ToString();
}
private static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
{
var psi = new ProcessStartInfo(fileName);
psi.UseShellExecute = false;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.ErrorDialog = false;
psi.CreateNoWindow = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
return psi;
}
private static (ProcessStartInfo StartInfo, string CommandDisplayString) CreateProcessStartInfo(string fileName, IEnumerable<string> args, string workingDirectory)
{
var psi = CreateProcessStartInfo(fileName, workingDirectory);
string displayArgs;
#if NET5_0_OR_GREATER
foreach (var a in args) psi.ArgumentList.Add(a);
displayArgs = $"['{String.Join("', '", args)}']";
#else
psi.Arguments = displayArgs = SquirrelRuntimeInfo.IsWindows ? ArgsToCommandLine(args) : ArgsToCommandLineUnix(args);
#endif
return (psi, fileName + " " + displayArgs);
}
private static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
{
var pi = Process.Start(psi);
while (!ct.IsCancellationRequested) {
if (pi.WaitForExit(500)) break;
}
if (ct.IsCancellationRequested && !pi.HasExited) {
pi.Kill();
ct.ThrowIfCancellationRequested();
}
string output = pi.StandardOutput.ReadToEnd();
string error = pi.StandardError.ReadToEnd();
var all = (output ?? "") + Environment.NewLine + (error ?? "");
return (pi.ExitCode, all.Trim());
}
public static Process StartProcessNonBlocking(string fileName, IEnumerable<string> args, string workingDirectory)
{
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
return Process.Start(psi);
}
public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct)
{
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
var p = InvokeProcess(psi, ct);
return (p.ExitCode, p.StdOutput, cmd);
}
//public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, string args, string workingDirectory, CancellationToken ct)
//{
// var psi = CreateProcessStartInfo(fileName, workingDirectory);
// psi.Arguments = args;
// var p = InvokeProcess(psi, ct);
// return (p.ExitCode, p.StdOutput, fileName + " " + args);
//}
public static Task<(int ExitCode, string StdOutput, string Command)> InvokeProcessAsync(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct)
{
return Task.Run(() => InvokeProcess(fileName, args, workingDirectory, ct));
}
private enum StandardHandles : int
{
STD_INPUT_HANDLE = -10,
STD_OUTPUT_HANDLE = -11,
STD_ERROR_HANDLE = -12,
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "GetStdHandle")]
private static extern IntPtr GetStdHandle(StandardHandles nStdHandle);
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "AllocConsole")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32)]
private static extern bool AttachConsole(int pid);
static int consoleCreated = 0;
[SupportedOSPlatform("windows")]
public static void AttachConsole()
{
if (Environment.OSVersion.Platform != PlatformID.Win32NT) return;
if (Interlocked.CompareExchange(ref consoleCreated, 1, 0) == 1) return;
if (!AttachConsole(-1)) {
AllocConsole();
}
GetStdHandle(StandardHandles.STD_ERROR_HANDLE);
GetStdHandle(StandardHandles.STD_OUTPUT_HANDLE);
}
}
}

View File

@@ -1,197 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Squirrel
{
internal static class ProcessUtil
{
/*
* caesay 09/12/2021 at 12:10 PM
* yeah
* can I steal this for squirrel?
* Roman 09/12/2021 at 12:10 PM
* sure :)
* reference CommandRunner.cs on the github url as source? :)
* https://github.com/RT-Projects/RT.Util/blob/ef660cd693f66bc946da3aaa368893b03b74eed7/RT.Util.Core/CommandRunner.cs#L327
*/
/// <summary>
/// Given a number of argument strings, constructs a single command line string with all the arguments escaped
/// correctly so that a process using standard Windows API for parsing the command line will receive exactly the
/// strings passed in here. See Remarks.</summary>
/// <remarks>
/// The string is only valid for passing directly to a process. If the target process is invoked by passing the
/// process name + arguments to cmd.exe then further escaping is required, to counteract cmd.exe's interpretation
/// of additional special characters. See <see cref="EscapeCmdExeMetachars"/>.</remarks>
[SupportedOSPlatform("windows")]
private static string ArgsToCommandLine(IEnumerable<string> args)
{
var sb = new StringBuilder();
foreach (var arg in args) {
if (arg == null)
continue;
if (sb.Length != 0)
sb.Append(' ');
// For details, see https://web.archive.org/web/20150318010344/http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
// or https://devblogs.microsoft.com/oldnewthing/?p=12833
if (arg.Length != 0 && arg.IndexOfAny(_cmdChars) < 0)
sb.Append(arg);
else {
sb.Append('"');
for (int c = 0; c < arg.Length; c++) {
int backslashes = 0;
while (c < arg.Length && arg[c] == '\\') {
c++;
backslashes++;
}
if (c == arg.Length) {
sb.Append('\\', backslashes * 2);
break;
} else if (arg[c] == '"') {
sb.Append('\\', backslashes * 2 + 1);
sb.Append('"');
} else {
sb.Append('\\', backslashes);
sb.Append(arg[c]);
}
}
sb.Append('"');
}
}
return sb.ToString();
}
private static readonly char[] _cmdChars = new[] { ' ', '"', '\n', '\t', '\v' };
/// <summary>
/// Escapes all cmd.exe meta-characters by prefixing them with a ^. See <see cref="ArgsToCommandLine"/> for more
/// information.</summary>
[SupportedOSPlatform("windows")]
private static string EscapeCmdExeMetachars(string command)
{
var result = new StringBuilder();
foreach (var ch in command) {
switch (ch) {
case '(':
case ')':
case '%':
case '!':
case '^':
case '"':
case '<':
case '>':
case '&':
case '|':
result.Append('^');
break;
}
result.Append(ch);
}
return result.ToString();
}
private static string ArgsToCommandLineUnix(IEnumerable<string> args)
{
var sb = new StringBuilder();
foreach (var arg in args) {
if (arg == null)
continue;
if (sb.Length != 0)
sb.Append(' ');
// there are just too many 'command chars' in unix, so we play it
// super safe here and escape the string if there are any non-alpha-numeric
if (System.Text.RegularExpressions.Regex.IsMatch(arg, @"$[\w]^")) {
sb.Append(arg);
} else {
// https://stackoverflow.com/a/33949338/184746
// single quotes are 'strong quotes' and can contain everything
// except never other single quotes.
sb.Append("'");
sb.Append(arg.Replace("'", @"'\''"));
sb.Append("'");
}
}
return sb.ToString();
}
private static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
{
var psi = new ProcessStartInfo(fileName);
psi.UseShellExecute = false;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.ErrorDialog = false;
psi.CreateNoWindow = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
return psi;
}
private static (ProcessStartInfo StartInfo, string CommandDisplayString) CreateProcessStartInfo(string fileName, IEnumerable<string> args, string workingDirectory)
{
var psi = CreateProcessStartInfo(fileName, workingDirectory);
string displayArgs;
#if NET5_0_OR_GREATER
foreach (var a in args) psi.ArgumentList.Add(a);
displayArgs = $"['{String.Join("', '", args)}']";
#else
psi.Arguments = displayArgs = SquirrelRuntimeInfo.IsWindows ? ArgsToCommandLine(args) : ArgsToCommandLineUnix(args);
#endif
return (psi, fileName + " " + displayArgs);
}
private static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
{
var pi = Process.Start(psi);
while (!ct.IsCancellationRequested) {
if (pi.WaitForExit(500)) break;
}
if (ct.IsCancellationRequested && !pi.HasExited) {
pi.Kill();
ct.ThrowIfCancellationRequested();
}
string output = pi.StandardOutput.ReadToEnd();
string error = pi.StandardError.ReadToEnd();
var all = (output ?? "") + Environment.NewLine + (error ?? "");
return (pi.ExitCode, all.Trim());
}
public static Process StartNonBlocking(string fileName, IEnumerable<string> args, string workingDirectory)
{
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
return Process.Start(psi);
}
public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct)
{
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
var p = InvokeProcess(psi, ct);
return (p.ExitCode, p.StdOutput, cmd);
}
//public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, string args, string workingDirectory, CancellationToken ct)
//{
// var psi = CreateProcessStartInfo(fileName, workingDirectory);
// psi.Arguments = args;
// var p = InvokeProcess(psi, ct);
// return (p.ExitCode, p.StdOutput, fileName + " " + args);
//}
public static Task<(int ExitCode, string StdOutput, string Command)> InvokeProcessAsync(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct)
{
return Task.Run(() => InvokeProcess(fileName, args, workingDirectory, ct));
}
}
}

View File

@@ -2,13 +2,45 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using static Squirrel.NativeMethods;
namespace Squirrel.Lib
{
[SupportedOSPlatform("windows")]
internal class ResourceReader : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibraryEx(string lpModuleName, IntPtr hFile, uint dwFlags);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, string lpType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindResourceEx(IntPtr hModule, IntPtr lpType, IntPtr lpName, ushort wLanguage);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindResourceEx(IntPtr hModule, string lpType, IntPtr lpName, ushort wLanguage);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindResourceEx(IntPtr hModule, string lpType, string lpName, ushort wLanguage);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint SizeofResource(IntPtr hModule, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadResource(IntPtr hModule, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LockResource(IntPtr hglobal);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeLibrary(IntPtr hModule);
private IntPtr hModule;
const uint LOAD_LIBRARY_AS_DATAFILE = 2;
private bool _disposed;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
@@ -747,92 +747,6 @@ namespace Squirrel
guid[right] = temp;
}
[SupportedOSPlatform("windows")]
private static List<(string ProcessExePath, int ProcessId)> EnumerateProcessesWindows()
{
var pids = new int[2048];
var gch = GCHandle.Alloc(pids, GCHandleType.Pinned);
try {
if (!NativeMethods.EnumProcesses(gch.AddrOfPinnedObject(), sizeof(int) * pids.Length,
out var bytesReturned))
throw new Win32Exception("Failed to enumerate processes");
if (bytesReturned < 1)
throw new Exception("Failed to enumerate processes");
List<(string ProcessExePath, int ProcessId)> ret = new();
for (int i = 0; i < bytesReturned / sizeof(int); i++) {
IntPtr hProcess = IntPtr.Zero;
try {
hProcess = NativeMethods.OpenProcess(ProcessAccess.QueryLimitedInformation, false, pids[i]);
if (hProcess == IntPtr.Zero)
continue;
var sb = new StringBuilder(256);
var capacity = sb.Capacity;
if (!NativeMethods.QueryFullProcessImageName(hProcess, 0, sb, ref capacity))
continue;
var exePath = sb.ToString();
if (String.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath))
continue;
ret.Add((sb.ToString(), pids[i]));
} catch (Exception) {
// don't care
} finally {
if (hProcess != IntPtr.Zero)
NativeMethods.CloseHandle(hProcess);
}
}
return ret;
} finally {
gch.Free();
}
}
public static List<(string ProcessExePath, int ProcessId)> EnumerateProcesses()
{
IEnumerable<(string ProcessExePath, int ProcessId)> allRunningProcesses = SquirrelRuntimeInfo.IsWindows
? EnumerateProcessesWindows()
: Process.GetProcesses().Select(p => (p.MainModule?.FileName, p.Id));
return allRunningProcesses
.Where(x => !String.IsNullOrWhiteSpace(x.ProcessExePath)) // Processes we can't query will have an empty process name
.ToList();
}
public static List<(string ProcessExePath, int ProcessId)> EnumerateProcessesInDirectory(string directory)
{
return EnumerateProcesses()
.Where(x => IsFileInDirectory(x.ProcessExePath, directory))
.ToList();
}
public static void KillProcessesInDirectory(string directoryToKill)
{
EnumerateProcessesInDirectory(directoryToKill)
.Where(x => {
// Never kill our own EXE
if (FullPathEquals(SquirrelRuntimeInfo.EntryExePath, x.ProcessExePath))
return false;
var name = Path.GetFileName(x.ProcessExePath).ToLowerInvariant();
if (name == "squirrel.exe" || name == "update.exe") return false;
return true;
})
.ForEach(x => {
try {
Process.GetProcessById(x.ProcessId).Kill();
} catch (Exception ex) {
Log().WarnException($"Unable to terminate process (pid.{x.ProcessId})", ex);
}
});
}
public const string SpecVersionFileName = "sq.version";
public static NuspecManifest ReadManifestFromVersionDir(string appVersionDir)
@@ -858,24 +772,5 @@ namespace Squirrel
return null;
}
private enum Magic : uint
{
MH_MAGIC = 0xfeedface,
MH_CIGAM = 0xcefaedfe,
MH_MAGIC_64 = 0xfeedfacf,
MH_CIGAM_64 = 0xcffaedfe
}
public static bool IsMachOImage(string filePath)
{
using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath))) {
if (reader.BaseStream.Length < 256) // Header size
return false;
uint magic = reader.ReadUInt32();
return Enum.IsDefined(typeof(Magic), magic);
}
}
}
}

View File

@@ -2,6 +2,7 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Squirrel
@@ -9,6 +10,126 @@ namespace Squirrel
[SupportedOSPlatform("windows")]
internal class MsDeltaCompression
{
/// <summary>
/// The ApplyDelta function use the specified delta and source files to create a new copy of the target file.
/// </summary>
/// <param name="applyFlags">Either DELTA_FLAG_NONE or DELTA_APPLY_FLAG_ALLOW_PA19.</param>
/// <param name="sourceName">The name of the source file to which the delta is to be applied.</param>
/// <param name="deltaName">The name of the delta to be applied to the source file.</param>
/// <param name="targetName">The name of the target file that is to be created.</param>
/// <returns>
/// Returns TRUE on success or FALSE otherwise.
/// </returns>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#applydeltaaw
/// </remarks>
[DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ApplyDelta(
[MarshalAs(UnmanagedType.I8)] ApplyFlags applyFlags,
string sourceName,
string deltaName,
string targetName);
/// <summary>
/// The CreateDelta function creates a delta from the specified source and target files and write the output delta to the designated file name.
/// </summary>
/// <param name="fileTypeSet">The file type set used for Create.</param>
/// <param name="setFlags">The file type set used for Create.</param>
/// <param name="resetFlags">The file type set used for Create.</param>
/// <param name="sourceName">The file type set used for Create.</param>
/// <param name="targetName">The name of the target against which the source is compared.</param>
/// <param name="sourceOptionsName">Reserved. Pass NULL.</param>
/// <param name="targetOptionsName">Reserved. Pass NULL.</param>
/// <param name="globalOptions">Reserved. Pass a DELTA_INPUT structure with lpStart set to NULL and uSize set to 0.</param>
/// <param name="targetFileTime">The time stamp set on the target file after delta Apply. If NULL, the timestamp of the target file during delta Create will be used.</param>
/// <param name="hashAlgId">ALG_ID of the algorithm to be used to generate the target signature.</param>
/// <param name="deltaName">The name of the delta file to be created.</param>
/// <returns>
/// Returns TRUE on success or FALSE otherwise.
/// </returns>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#createdeltaaw
/// </remarks>
[DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CreateDelta(
[MarshalAs(UnmanagedType.I8)] FileTypeSet fileTypeSet,
[MarshalAs(UnmanagedType.I8)] CreateFlags setFlags,
[MarshalAs(UnmanagedType.I8)] CreateFlags resetFlags,
string sourceName,
string targetName,
string? sourceOptionsName,
string? targetOptionsName,
DeltaInput globalOptions,
IntPtr targetFileTime,
[MarshalAs(UnmanagedType.U4)] HashAlgId hashAlgId,
string deltaName);
private enum HashAlgId
{
/// <summary>No signature.</summary>
None = 0,
/// <summary>32-bit CRC defined in msdelta.dll.</summary>
Crc32 = 32,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltaflagtypeflags
/// </remarks>
private enum ApplyFlags : long
{
/// <summary>Indicates no special handling.</summary>
None = 0,
/// <summary>Allow MSDelta to apply deltas created using PatchAPI.</summary>
AllowLegacy = 1,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#filetypesets
/// </remarks>
[Flags]
private enum FileTypeSet : long
{
/// <summary>
/// File type set that includes I386, IA64 and AMD64 Portable Executable (PE) files. Others are treated as raw.
/// </summary>
Executables = 0x0FL,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltaflagtypeflags
/// </remarks>
private enum CreateFlags : long
{
/// <summary>Indicates no special handling.</summary>
None = 0,
/// <summary>Allow the source, target and delta files to exceed the default size limit.</summary>
IgnoreFileSizeLimit = 1 << 17,
}
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltainputstructure
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
private struct DeltaInput
{
/// <summary>Memory address non-editable input buffer.</summary>
public IntPtr Start;
/// <summary>Size of the memory buffer in bytes.</summary>
public IntPtr Size;
/// <summary>
/// Defines whether MSDelta is allowed to edit the input buffer. If you make the input editable, the buffer will
/// be zeroed at function return. However this will cause most MSDelta functions to use less memory.
/// </summary>
[MarshalAs(UnmanagedType.Bool)] public bool Editable;
}
//public void CreateDelta(string oldFilePath, string newFilePath, string deltaFilePath)
//{
// const string? sourceOptionsName = null;
@@ -26,7 +147,7 @@ namespace Squirrel
public static void ApplyDelta(string deltaFilePath, string oldFilePath, string newFilePath)
{
if (!NativeMethods.ApplyDelta(ApplyFlags.AllowLegacy, oldFilePath, deltaFilePath, newFilePath))
if (!ApplyDelta(ApplyFlags.AllowLegacy, oldFilePath, deltaFilePath, newFilePath))
throw new Win32Exception();
}
}

View File

@@ -138,8 +138,8 @@ namespace Squirrel.NuGet
Directory.CreateDirectory(fullTargetFile);
} else {
reader.WriteEntryToFile(fullTargetFile);
if (Utility.IsMachOImage(fullTargetFile)) {
NativeMac.ChmodAsExe(fullTargetFile);
if (PlatformUtil.IsMachOImage(fullTargetFile)) {
PlatformUtil.ChmodFileAsExecutable(fullTargetFile);
}
}
}, 5);

View File

@@ -92,7 +92,7 @@ namespace Squirrel
var args = new string[] { "/passive", "/norestart", "/showrmui" };
var quietArgs = new string[] { "/q", "/norestart" };
Log.Info($"Running {Id} installer '{pathToInstaller} {string.Join(" ", args)}'");
var p = await ProcessUtil.InvokeProcessAsync(pathToInstaller, isQuiet ? quietArgs : args, null, CancellationToken.None).ConfigureAwait(false);
var p = await PlatformUtil.InvokeProcessAsync(pathToInstaller, isQuiet ? quietArgs : args, null, CancellationToken.None).ConfigureAwait(false);
// https://johnkoerner.com/install/windows-installer-error-codes/

View File

@@ -97,7 +97,7 @@ namespace Squirrel
cts.CancelAfter(10 * 1000);
try {
var args = new string[] { "--squirrel-uninstall", currentVersion.ToString() };
await ProcessUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
await PlatformUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
} catch (Exception ex) {
this.Log().ErrorException("Failed to run cleanup hook, continuing: " + exe, ex);
}
@@ -248,7 +248,7 @@ namespace Squirrel
cts.CancelAfter(30 * 1000);
try {
await ProcessUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
await PlatformUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
} catch (Exception ex) {
this.Log().ErrorException("Couldn't run Squirrel hook, continuing: " + exe, ex);
}
@@ -311,7 +311,7 @@ namespace Squirrel
using (var cts = new CancellationTokenSource()) {
cts.CancelAfter(10 * 1000);
try {
await ProcessUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
await PlatformUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
} catch (Exception ex) {
this.Log().ErrorException("Coudln't run Squirrel hook, continuing: " + exe, ex);
}
@@ -324,7 +324,7 @@ namespace Squirrel
markAppFolderAsDead(v.DirectoryPath);
}
var runningProcesses = Utility.EnumerateProcesses();
var runningProcesses = PlatformUtil.GetRunningProcesses();
foreach (var dir in toDelete) {
// skip any directories with running processes

View File

@@ -178,7 +178,7 @@ namespace Squirrel
/// <inheritdoc/>
public void KillAllExecutablesBelongingToPackage()
{
Utility.KillProcessesInDirectory(_config.RootAppDir);
PlatformUtil.KillProcessesInDirectory(_config.RootAppDir);
}
/// <inheritdoc/>
@@ -204,12 +204,10 @@ namespace Squirrel
/// <param name="arguments">Arguments to start the exe with</param>
/// <remarks>See <see cref="RestartAppWhenExited(string, string)"/> for a version which does not
/// exit the current process immediately, but instead allows you to exit the current process
/// however you'd like.</remarks>
/// however you'd like after cleaning up resources.</remarks>
public static void RestartApp(string exeToStart = null, string arguments = null)
{
AppDesc.GetCurrentPlatform().StartRestartingProcess(exeToStart, arguments);
// NB: We have to give update.exe some time to grab our PID
Thread.Sleep(1000);
Environment.Exit(0);
}
@@ -224,28 +222,9 @@ namespace Squirrel
public static Process RestartAppWhenExited(string exeToStart = null, string arguments = null)
{
var process = AppDesc.GetCurrentPlatform().StartRestartingProcess(exeToStart, arguments);
// NB: We have to give update.exe some time to grab our PID
Thread.Sleep(1000);
return process;
}
/// <summary>
/// Launch Update.exe and ask it to wait until this process exits before starting
/// a new process. Used to re-start your app with the latest version after an update.
/// </summary>
/// <param name="exeToStart">The file *name* (not full path) of the exe to start, or null to re-launch
/// the current executable. </param>
/// <param name="arguments">Arguments to start the exe with</param>
/// <returns>The Update.exe process that is waiting for this process to exit</returns>
public static async Task<Process> RestartAppWhenExitedAsync(string exeToStart = null, string arguments = null)
{
var process = AppDesc.GetCurrentPlatform().StartRestartingProcess(exeToStart, arguments);
// NB: We have to give update.exe some time to grab our PID
await Task.Delay(1000).ConfigureAwait(false);
return process;
}
private static IUpdateSource CreateSource(string urlOrPath, IFileDownloader urlDownloader = null)
{
if (String.IsNullOrWhiteSpace(urlOrPath)) {

View File

@@ -46,7 +46,7 @@ namespace Squirrel.Update
static void ProcessStart(string exeName, string arguments, bool shouldWait, bool forceLatest)
{
if (shouldWait) waitForParentToExit();
if (shouldWait) PlatformUtil.WaitForParentProcessToExit();
// todo https://stackoverflow.com/questions/51441576/how-to-run-app-as-sudo
// https://stackoverflow.com/questions/10283062/getting-sudo-to-ask-for-password-via-the-gui
@@ -62,26 +62,7 @@ namespace Squirrel.Update
args.Add(arguments);
}
ProcessUtil.StartNonBlocking("/usr/bin/open", args, null);
}
static void waitForParentToExit()
{
var parentPid = NativeMac.getppid();
if (parentPid <= 1) {
Log.Warn("Cannot wait for parent to exit, it has already exited.");
return;
}
var proc = Process.GetProcessById(parentPid);
Log.Info($"Waiting for PID {parentPid} to exit (30s timeout)...");
var exited = proc.WaitForExit(30_000);
if (!exited) {
throw new Exception("Parent wait timed out.");
}
Log.Info($"PID {parentPid} has exited.");
PlatformUtil.StartProcessNonBlocking("/usr/bin/open", args, null);
}
}
}

View File

@@ -319,7 +319,7 @@ namespace Squirrel.Update
static async Task UpdateSelf()
{
waitForParentToExit();
PlatformUtil.WaitForParentProcessToExit();
var src = SquirrelRuntimeInfo.EntryExePath;
var updateDotExeForOurPackage = Path.Combine(
Path.GetDirectoryName(src),
@@ -434,7 +434,7 @@ namespace Squirrel.Update
return;
}
if (shouldWait) waitForParentToExit();
if (shouldWait) PlatformUtil.WaitForParentProcessToExit();
var config = new AppDescWindows();
var latestAppDir = config.UpdateAndRetrieveCurrentFolder(forceLatest);
@@ -493,30 +493,10 @@ namespace Squirrel.Update
static void ShowHelp()
{
ensureConsole();
PlatformUtil.AttachConsole();
opt.WriteOptionDescriptions();
}
static void waitForParentToExit()
{
// Grab a handle the parent process
var parentPid = NativeMethods.GetParentProcessId();
var handle = default(IntPtr);
// Wait for our parent to exit
try {
handle = NativeMethods.OpenProcess(ProcessAccess.Synchronize, false, parentPid);
if (handle != IntPtr.Zero) {
Log.Info("About to wait for parent PID {0}", parentPid);
NativeMethods.WaitForSingleObject(handle, 0xFFFFFFFF /*INFINITE*/);
} else {
Log.Info("Parent PID {0} no longer valid - ignoring", parentPid);
}
} finally {
if (handle != IntPtr.Zero) NativeMethods.CloseHandle(handle);
}
}
static string getMissingRuntimesMessage(string appname, Runtimes.RuntimeInfo[] missingFrameworks)
{
return missingFrameworks.Length > 1
@@ -602,21 +582,6 @@ namespace Squirrel.Update
return ret;
}
static int consoleCreated = 0;
static void ensureConsole()
{
if (Environment.OSVersion.Platform != PlatformID.Win32NT) return;
if (Interlocked.CompareExchange(ref consoleCreated, 1, 0) == 1) return;
if (!NativeMethods.AttachConsole(-1)) {
NativeMethods.AllocConsole();
}
NativeMethods.GetStdHandle(StandardHandles.STD_ERROR_HANDLE);
NativeMethods.GetStdHandle(StandardHandles.STD_OUTPUT_HANDLE);
}
}
public class ProgressSource

View File

@@ -178,7 +178,7 @@ namespace Squirrel.Tests
[Fact]
public void WeCanFetchAllProcesses()
{
var result = Utility.EnumerateProcesses();
var result = PlatformUtil.GetRunningProcesses();
Assert.True(result.Count > 1);
Assert.True(result.Count != 2048);
}