Skip to content

Commit 906befa

Browse files
committed
Check if lock is held on the file
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
1 parent 3a8c9bc commit 906befa

File tree

8 files changed

+339
-5
lines changed

8 files changed

+339
-5
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
6+
<ProductVersion>9.0.21022</ProductVersion>
7+
<SchemaVersion>2.0</SchemaVersion>
8+
<ProjectGuid>{6C463EA8-FE3F-4832-B22B-E1B199F2C308}</ProjectGuid>
9+
<OutputType>Library</OutputType>
10+
<AppDesignerFolder>Properties</AppDesignerFolder>
11+
<RootNamespace>FileLockFinder</RootNamespace>
12+
<AssemblyName>FileLockFinder</AssemblyName>
13+
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
14+
<FileAlignment>512</FileAlignment>
15+
<FileUpgradeFlags>
16+
</FileUpgradeFlags>
17+
<UpgradeBackupLocation>
18+
</UpgradeBackupLocation>
19+
<OldToolsVersion>3.5</OldToolsVersion>
20+
<TargetFrameworkProfile />
21+
</PropertyGroup>
22+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
23+
<DebugSymbols>true</DebugSymbols>
24+
<DebugType>full</DebugType>
25+
<Optimize>false</Optimize>
26+
<OutputPath>..\..\bin\Debug\plugins\</OutputPath>
27+
<DefineConstants>DEBUG;TRACE</DefineConstants>
28+
<ErrorReport>prompt</ErrorReport>
29+
<WarningLevel>4</WarningLevel>
30+
<Prefer32Bit>false</Prefer32Bit>
31+
</PropertyGroup>
32+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
33+
<DebugType>pdbonly</DebugType>
34+
<Optimize>true</Optimize>
35+
<OutputPath>..\..\bin\Release\plugins\</OutputPath>
36+
<DefineConstants>TRACE</DefineConstants>
37+
<ErrorReport>prompt</ErrorReport>
38+
<WarningLevel>4</WarningLevel>
39+
<Prefer32Bit>false</Prefer32Bit>
40+
</PropertyGroup>
41+
<PropertyGroup>
42+
<SignAssembly>true</SignAssembly>
43+
</PropertyGroup>
44+
<PropertyGroup>
45+
<AssemblyOriginatorKeyFile>..\Solution Items\Key.snk</AssemblyOriginatorKeyFile>
46+
</PropertyGroup>
47+
<ItemGroup>
48+
<Reference Include="System" />
49+
<Reference Include="System.Data" />
50+
<Reference Include="System.Drawing" />
51+
<Reference Include="System.Windows.Forms" />
52+
</ItemGroup>
53+
<ItemGroup>
54+
<Compile Include="..\Solution Items\AssemblyVersion.cs">
55+
<Link>Properties\AssemblyVersion.cs</Link>
56+
</Compile>
57+
<Compile Include="LockFinder.cs" />
58+
<Compile Include="Properties\AssemblyInfo.cs" />
59+
</ItemGroup>
60+
<ItemGroup>
61+
<None Include="..\Solution Items\Key.snk">
62+
<Link>Key.snk</Link>
63+
</None>
64+
</ItemGroup>
65+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
66+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
67+
Other similar extension points exist, see Microsoft.Common.targets.
68+
<Target Name="BeforeBuild">
69+
</Target>
70+
<Target Name="AfterBuild">
71+
</Target>
72+
-->
73+
<PropertyGroup>
74+
<PostBuildEvent>
75+
</PostBuildEvent>
76+
</PropertyGroup>
77+
</Project>

src/FileLockFinder/LockFinder.cs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Runtime.InteropServices;
5+
6+
// Expanded with some helpers from: https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4/
7+
// Uses Windows Restart Manager.
8+
// A more involved and cross platform solution to this problem is here: https://github.com/cklutz/LockCheck
9+
10+
11+
namespace FileLockFinder
12+
{
13+
public class LockFinder
14+
{
15+
16+
/// <summary>
17+
/// Method <c>FindLockedProcessName</c> Retrieve the first process name
18+
/// that is locking the file at the specified path
19+
/// </summary>
20+
/// <param name="path">The path of a file with a write lock held by a
21+
/// process</param>
22+
/// <resturns>The name of the first process found with a lock</resturns>
23+
/// <exception cref="Exception">
24+
/// Thrown when the file path is not locked
25+
/// </exception>
26+
static public string FindLockedProcessName(string path)
27+
{
28+
var list = FindLockProcesses(path);
29+
if(list.Count == 0) { throw new Exception(
30+
"No processes are locking the path specified"); }
31+
return list[0].ProcessName;
32+
}
33+
34+
/// <summary>
35+
/// Method <c>CheckIfFileIsLocked</c> Check if the file specified has a
36+
/// write lock held by a process
37+
/// </summary>
38+
/// <param name="path">The path of a file being checked if a write lock
39+
/// held by a process</param>
40+
/// <returns>true when one or more processes with lock</returns>
41+
static public bool CheckIfFileIsLocked(string path)
42+
{
43+
var list = FindLockProcesses(path);
44+
if(list.Count > 0) { return true; }
45+
return false;
46+
}
47+
48+
/// <summary>
49+
/// Used to find processes holding a lock on the file. This would cause
50+
/// other usage, such as file truncation or write opretions to throw
51+
/// IOException if an exclusive lock is attempted.
52+
/// </summary>
53+
/// <param name="path">Path being checked</param>
54+
/// <returns>List of processes holding file lock to path</returns>
55+
/// <exception cref="Exception"></exception>
56+
static public List<Process> FindLockProcesses(string path)
57+
{
58+
uint handle;
59+
string key = Guid.NewGuid().ToString();
60+
List<Process> processes = new List<Process>();
61+
62+
int res = RmStartSession(out handle, 0, key);
63+
if (res != 0)
64+
{
65+
throw new Exception("Could not begin restart session. " +
66+
"Unable to determine file locker.");
67+
}
68+
69+
try
70+
{
71+
const int ERROR_MORE_DATA = 234;
72+
uint pnProcInfoNeeded = 0, pnProcInfo = 0,
73+
lpdwRebootReasons = RmRebootReasonNone;
74+
string[] resources = new string[] { path };
75+
76+
res = RmRegisterResources(handle, (uint)resources.Length,
77+
resources, 0, null, 0, null);
78+
if (res != 0)
79+
{
80+
throw new Exception("Could not register resource.");
81+
}
82+
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null,
83+
ref lpdwRebootReasons);
84+
if (res == ERROR_MORE_DATA)
85+
{
86+
RM_PROCESS_INFO[] processInfo =
87+
new RM_PROCESS_INFO[pnProcInfoNeeded];
88+
pnProcInfo = pnProcInfoNeeded;
89+
// Get the list.
90+
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo,
91+
processInfo, ref lpdwRebootReasons);
92+
if (res == 0)
93+
{
94+
processes = new List<Process>((int)pnProcInfo);
95+
for (int i = 0; i < pnProcInfo; i++)
96+
{
97+
try
98+
{
99+
processes.Add(Process.GetProcessById(processInfo[i].
100+
Process.dwProcessId));
101+
}
102+
catch (ArgumentException) { }
103+
}
104+
}
105+
else
106+
{
107+
throw new Exception("Could not list processes locking resource");
108+
}
109+
}
110+
else if (res != 0)
111+
{
112+
throw new Exception("Could not list processes locking resource." +
113+
"Failed to get size of result.");
114+
}
115+
}
116+
catch (Exception exception)
117+
{
118+
Console.WriteLine(exception.Message);
119+
}
120+
finally
121+
{
122+
RmEndSession(handle);
123+
}
124+
125+
return processes;
126+
}
127+
private const int RmRebootReasonNone = 0;
128+
private const int CCH_RM_MAX_APP_NAME = 255;
129+
private const int CCH_RM_MAX_SVC_NAME = 63;
130+
131+
[StructLayout(LayoutKind.Sequential)]
132+
struct RM_UNIQUE_PROCESS
133+
{
134+
public int dwProcessId;
135+
public System.Runtime.InteropServices.
136+
ComTypes.FILETIME ProcessStartTime;
137+
}
138+
[DllImport("rstrtmgr.dll",
139+
CharSet = CharSet.Auto, SetLastError = true)]
140+
static extern int RmGetList(uint dwSessionHandle,
141+
out uint pnProcInfoNeeded,
142+
ref uint pnProcInfo,
143+
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
144+
ref uint lpdwRebootReasons);
145+
[StructLayout(LayoutKind.Sequential,
146+
CharSet = CharSet.Auto)]
147+
struct RM_PROCESS_INFO
148+
{
149+
public RM_UNIQUE_PROCESS Process;
150+
[MarshalAs(UnmanagedType.ByValTStr,
151+
SizeConst = CCH_RM_MAX_APP_NAME + 1)]
152+
public string strAppName;
153+
[MarshalAs(UnmanagedType.ByValTStr,
154+
SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
155+
public string strServiceShortName;
156+
public RM_APP_TYPE ApplicationType;
157+
public uint AppStatus;
158+
public uint TSSessionId;
159+
[MarshalAs(UnmanagedType.Bool)]
160+
public bool bRestartable;
161+
}
162+
163+
enum RM_APP_TYPE
164+
{
165+
RmUnknownApp = 0,
166+
RmMainWindow = 1,
167+
RmOtherWindow = 2,
168+
RmService = 3,
169+
RmExplorer = 4,
170+
RmConsole = 5,
171+
RmCritical = 1000
172+
}
173+
174+
[DllImport("rstrtmgr.dll",
175+
CharSet = CharSet.Auto,
176+
SetLastError = true)]
177+
static extern int RmRegisterResources(
178+
uint pSessionHandle,
179+
UInt32 nFiles,
180+
string[] rgsFilenames,
181+
UInt32 nApplications,
182+
[In] RM_UNIQUE_PROCESS[] rgApplications,
183+
UInt32 nServices, string[] rgsServiceNames);
184+
185+
[DllImport("rstrtmgr.dll",
186+
CharSet = CharSet.Auto,
187+
SetLastError = true)]
188+
static extern int RmStartSession(
189+
out uint pSessionHandle,
190+
int dwSessionFlags,
191+
string strSessionKey);
192+
193+
[DllImport("rstrtmgr.dll",
194+
CharSet = CharSet.Auto,
195+
SetLastError = true)]
196+
static extern int RmEndSession(uint pSessionHandle);
197+
}
198+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle("FileLockFinder")]
9+
[assembly: AssemblyDescription("")]
10+
11+
// The following GUID is for the ID of the typelib if this project is exposed to COM
12+
[assembly: Guid("6c463ea8-fe3f-4832-b22b-e1b199f2c308")]

src/LogExpert.sln

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "setup", "setup", "{C625E7C2
5858
setup\LogExpertInstaller.iss = setup\LogExpertInstaller.iss
5959
EndProjectSection
6060
EndProject
61+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileLockFinder", "FileLockFinder\FileLockFinder.csproj", "{6C463EA8-FE3F-4832-B22B-E1B199F2C308}"
62+
EndProject
6163
Global
6264
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6365
Debug|Any CPU = Debug|Any CPU
@@ -358,6 +360,24 @@ Global
358360
{3D01E923-5219-488B-B0A7-98521841E680}.Release|Mixed Platforms.Build.0 = Release|Any CPU
359361
{3D01E923-5219-488B-B0A7-98521841E680}.Release|Win32.ActiveCfg = Release|Any CPU
360362
{3D01E923-5219-488B-B0A7-98521841E680}.Release|Win32.Build.0 = Release|Any CPU
363+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
364+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Any CPU.Build.0 = Debug|Any CPU
365+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
366+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
367+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Win32.ActiveCfg = Debug|Any CPU
368+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Debug|Win32.Build.0 = Debug|Any CPU
369+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Any CPU.ActiveCfg = Debug|Any CPU
370+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Any CPU.Build.0 = Debug|Any CPU
371+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Mixed Platforms.ActiveCfg = Debug|Any CPU
372+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Mixed Platforms.Build.0 = Debug|Any CPU
373+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Win32.ActiveCfg = Debug|Any CPU
374+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.DebugNoTimeout|Win32.Build.0 = Debug|Any CPU
375+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Any CPU.ActiveCfg = Release|Any CPU
376+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Any CPU.Build.0 = Release|Any CPU
377+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
378+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Mixed Platforms.Build.0 = Release|Any CPU
379+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Win32.ActiveCfg = Release|Any CPU
380+
{6C463EA8-FE3F-4832-B22B-E1B199F2C308}.Release|Win32.Build.0 = Release|Any CPU
361381
EndGlobalSection
362382
GlobalSection(SolutionProperties) = preSolution
363383
HideSolutionNode = FALSE

src/LogExpert/Controls/LogTabWindow/LogTabWindow.designer.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/LogExpert/Controls/LogTabWindow/LogTabWindowEventHandlers.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -815,8 +815,7 @@ private void OnFindInExplorerToolStripMenuItemClick(object sender, EventArgs e)
815815

816816
private void truncateFileToolStripMenuItem_Click(object sender, EventArgs e)
817817
{
818-
LogWindow logWindow = dockPanel.ActiveContent as LogWindow;
819-
File.WriteAllText(logWindow.Title, "");
818+
CurrentLogWindow?.TryToTruncate();
820819
}
821820

822821
private void OnExportBookmarksToolStripMenuItemClick(object sender, EventArgs e)

0 commit comments

Comments
 (0)