Skip to content

Commit

Permalink
Check if lock is held on the file
Browse files Browse the repository at this point in the history
Added LockFinder project, to check processes holding file lock during truncate
Added Status Line to tell user if something didn't work
Renamed method and tooltip to indicate Truncate is not guaranteed
  • Loading branch information
jsuppe committed Jul 24, 2022
1 parent 8da04f2 commit 536840b
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 17 deletions.
77 changes: 77 additions & 0 deletions src/FileLockFinder/FileLockFinder.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{6C463EA8-FE3F-4832-B22B-E1B199F2C308}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FileLockFinder</RootNamespace>
<AssemblyName>FileLockFinder</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\bin\Debug\plugins\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\bin\Release\plugins\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\Solution Items\Key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Solution Items\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="LockFinder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\Solution Items\Key.snk">
<Link>Key.snk</Link>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>
198 changes: 198 additions & 0 deletions src/FileLockFinder/LockFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

// Expanded with some helpers from: https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4/
// Uses Windows Restart Manager.
// A more involved and cross platform solution to this problem is here: https://github.com/cklutz/LockCheck


namespace FileLockFinder
{
public class LockFinder
{

/// <summary>
/// Method <c>FindLockedProcessName</c> Retrieve the first process name
/// that is locking the file at the specified path
/// </summary>
/// <param name="path">The path of a file with a write lock held by a
/// process</param>
/// <resturns>The name of the first process found with a lock</resturns>
/// <exception cref="Exception">
/// Thrown when the file path is not locked
/// </exception>
static public string FindLockedProcessName(string path)
{
var list = FindLockProcesses(path);
if(list.Count == 0) { throw new Exception(
"No processes are locking the path specified"); }
return list[0].ProcessName;
}

/// <summary>
/// Method <c>CheckIfFileIsLocked</c> Check if the file specified has a
/// write lock held by a process
/// </summary>
/// <param name="path">The path of a file being checked if a write lock
/// held by a process</param>
/// <returns>true when one or more processes with lock</returns>
static public bool CheckIfFileIsLocked(string path)
{
var list = FindLockProcesses(path);
if(list.Count > 0) { return true; }
return false;
}

/// <summary>
/// Used to find processes holding a lock on the file. This would cause
/// other usage, such as file truncation or write opretions to throw
/// IOException if an exclusive lock is attempted.
/// </summary>
/// <param name="path">Path being checked</param>
/// <returns>List of processes holding file lock to path</returns>
/// <exception cref="Exception"></exception>
static public List<Process> FindLockProcesses(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();

int res = RmStartSession(out handle, 0, key);
if (res != 0)
{
throw new Exception("Could not begin restart session. " +
"Unable to determine file locker.");
}

try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0, pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path };

res = RmRegisterResources(handle, (uint)resources.Length,
resources, 0, null, 0, null);
if (res != 0)
{
throw new Exception("Could not register resource.");
}
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null,
ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
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)
{
processes = new List<Process>((int)pnProcInfo);
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].
Process.dwProcessId));
}
catch (ArgumentException) { }
}
}
else
{
throw new Exception("Could not list processes locking resource");
}
}
else if (res != 0)
{
throw new Exception("Could not list processes locking resource." +
"Failed to get size of result.");
}
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
finally
{
RmEndSession(handle);
}

return processes;
}
private const int RmRebootReasonNone = 0;
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;

[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.
ComTypes.FILETIME ProcessStartTime;
}
[DllImport("rstrtmgr.dll",
CharSet = CharSet.Auto, SetLastError = true)]
static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
[StructLayout(LayoutKind.Sequential,
CharSet = CharSet.Auto)]
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;
}

enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}

[DllImport("rstrtmgr.dll",
CharSet = CharSet.Auto,
SetLastError = true)]
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,
SetLastError = true)]
static extern int RmStartSession(
out uint pSessionHandle,
int dwSessionFlags,
string strSessionKey);

[DllImport("rstrtmgr.dll",
CharSet = CharSet.Auto,
SetLastError = true)]
static extern int RmEndSession(uint pSessionHandle);
}
}
12 changes: 12 additions & 0 deletions src/FileLockFinder/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("FileLockFinder")]
[assembly: AssemblyDescription("")]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6c463ea8-fe3f-4832-b22b-e1b199f2c308")]
24 changes: 22 additions & 2 deletions src/LogExpert.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.645
# Visual Studio Version 17
VisualStudioVersion = 17.2.32630.192
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogExpert", "LogExpert\LogExpert.csproj", "{F0C0D370-F416-44ED-939A-B4827D15AC14}"
ProjectSection(ProjectDependencies) = postProject
Expand Down Expand Up @@ -58,6 +58,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "setup", "setup", "{C625E7C2
setup\LogExpertInstaller.iss = setup\LogExpertInstaller.iss
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileLockFinder", "FileLockFinder\FileLockFinder.csproj", "{6C463EA8-FE3F-4832-B22B-E1B199F2C308}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -358,6 +360,24 @@ Global
{3D01E923-5219-488B-B0A7-98521841E680}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3D01E923-5219-488B-B0A7-98521841E680}.Release|Win32.ActiveCfg = Release|Any CPU
{3D01E923-5219-488B-B0A7-98521841E680}.Release|Win32.Build.0 = Release|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Win32.ActiveCfg = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Win32.Build.0 = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Any CPU.ActiveCfg = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Any CPU.Build.0 = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Mixed Platforms.Build.0 = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Win32.ActiveCfg = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Win32.Build.0 = Debug|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Any CPU.Build.0 = Release|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Win32.ActiveCfg = Release|Any CPU
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Win32.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
4 changes: 2 additions & 2 deletions src/LogExpert/Controls/LogTabWindow/LogTabWindow.designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ private void findInExplorerToolStripMenuItem_Click(object sender, EventArgs e)
private void truncateFileToolStripMenuItem_Click(object sender, EventArgs e)
{
LogWindow logWindow = dockPanel.ActiveContent as LogWindow;
File.WriteAllText(logWindow.Title,"");
logWindow.TryToTruncate();
}

private void exportBookmarksToolStripMenuItem_Click(object sender, EventArgs e)
Expand Down
Loading

0 comments on commit 536840b

Please sign in to comment.