UltimateZoneBuilder/Source/Core/General/FileLockChecker.cs
antonino maniscalco aae0dd5669
Check for unix instead of linux in file locker (PR#885 by pac85)
The calls to native windows functions wouldn't be valid on any unix os
so expand the check.

The check for Windows XP is removed as that platform is not supported
anymore.
2023-05-19 22:55:02 +03:00

211 lines
6.8 KiB
C#
Executable file

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace CodeImp.DoomBuilder
{
internal static class FileLockChecker
{
internal class FileLockCheckResult //mxd
{
public string Error;
public List<Process> Processes = new List<Process>();
}
[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
private const int RmRebootReasonNone = 0;
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;
private enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
private static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
private static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
///
/// </remarks>
static public FileLockCheckResult CheckFile(string path)
{
//mxd. Do it the clunky way? (non-Windows)
if(Environment.OSVersion.Platform == PlatformID.Unix)
{
bool locked = false;
try
{
using(File.Open(path, FileMode.Open)) { }
}
catch(IOException e)
{
int errorcode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
locked = (errorcode == 32 || errorcode == 33);
}
return new FileLockCheckResult { Error = (locked ? "Unable to save the map. Map file is locked by another process." : string.Empty) };
}
//mxd. Needs Vista or newer...
uint handle;
string key = Guid.NewGuid().ToString();
FileLockCheckResult result = new FileLockCheckResult(); //mxd
string errorstart = "Unable to save the map: target file is locked by another process."
+ Environment.NewLine + "Also, unable to get the name of the offending process:"
+ Environment.NewLine + Environment.NewLine;
int res = RmStartSession(out handle, 0, key);
if(res != 0)
{
RmEndSession(handle); //mxd
result.Error = errorstart + "Error " + res + ". Could not begin restart session. Unable to determine file locker."; //mxd
return result;
}
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if(res != 0)
{
result.Error = errorstart + "Error " + res + ". Could not register resource."; //mxd
return result;
}
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if(res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if(res == 0)
{
result.Processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the list to be returned
for(int i = 0; i < pnProcInfo; i++)
{
try
{
Process process = Process.GetProcessById(processInfo[i].Process.dwProcessId);
result.Processes.Add(process);
}
// catch the error -- in case the process is no longer running
catch(ArgumentException) {}
}
//mxd
if(result.Processes.Count > 0)
{
result.Error = "Unable to save the map: target file is locked by the following process"
+ (result.Processes.Count > 1 ? "es" : "") + ":"
+ Environment.NewLine + Environment.NewLine;
foreach(Process process in result.Processes)
{
string processpath = string.Empty;
try
{
// All manner of exceptions are possible here...
processpath = process.MainModule.FileName;
}catch {}
result.Error += process.ProcessName
+ " (" + (!string.IsNullOrEmpty(processpath) ? "\"" + processpath + "\"" : "")
+ ", started at " + process.StartTime + ")"
+ Environment.NewLine;
}
}
}
else
{
result.Error = "Error " + res + ". Could not list processes locking the resource."; //mxd
return result;
}
}
else if(res != 0)
{
result.Error = "Error " + res + ". Could not list processes locking resource. Failed to get result size."; //mxd
return result;
}
}
finally
{
RmEndSession(handle);
}
return result;
}
}
}