Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

File Locked By Another Process

162 views
Skip to first unread message

Jason Efting

unread,
Mar 15, 2003, 3:41:15 PM3/15/03
to
Hi,
I wrote a service that monitors a directory for incoming files (created),
once it finds a file it is transferred to a remote ftp server. When a big
file is copied into the directory the service will try and work with the
file while it is still being copied - thus it is still locked.

How can I determine programmatically whether the file is locked by another
process before trying to access it?

Thanks
Jason


-glenn-

unread,
Mar 16, 2003, 11:23:23 AM3/16/03
to
"Jason Efting" <jef...@vouzi.co.uk> wrote in
news:eWkoFNz6...@TK2MSFTNGP10.phx.gbl:

Jason, you must be opening the file at some point. You could put a
try/catch block aroud the open and if "file locked" is thrown then have
the thread sleep for a while and try again. You might want to have the
routine that copies to the FTP server run on a separate thread from the
main thread to keep large files from holding up the copying of small
files.

-glenn-

Derek Harmon

unread,
Mar 16, 2003, 12:06:31 PM3/16/03
to
"Jason Efting" <jef...@vouzi.co.uk> wrote in message news:eWkoFNz6...@TK2MSFTNGP10.phx.gbl...

> file is copied into the directory the service will try and work with the
> file while it is still being copied - thus it is still locked.
>
> How can I determine programmatically whether the file is locked by another
> process before trying to access it?

The standard WIN API practice is:

1. Call CreateFile( )
2. Receive INVALID_HANDLE return value from CreateFile( )
3. Call GetLastError( )
4. Receive ERROR_SHARING_VIOLATION.

For efficiency, you could call CreateFile( ) using P/Invoke with SetLastError = true,
then use Marshal.GetLastWin32Error( ) to test for 32.

Otherwise, there is an uncomplicated C# practice for handling this:

1. Try opening the file for reading.
2. Catch the IOException.

How much improvement over C# the P/Invoke approach rewards you with depends
on how often the situation occurs. If there was no other process, you're left holding
a file handle that's an anathema to the Framework and you must close it.

If it's necessary for you to distinguish between the file being locked by another
process and all other possible IOExceptions, the IOException's Message property
is very clear. In the English releases of the runtime it reads:

"The process cannot access the file {absolute filespec appears here} because
it is being used by another process."

Don't do an IOException::Message::EndsWith( "another process.") though. This
solution is brittle (the message may change in future releases) and not suited for
applications that must port internationally (I wonder how that message reads in
Chinese hanzi?)

Instead, use that integer that uniquely identifies the underlying cause of every
Exception: the HResult property!

Unfortunately, as you would soon discover, the property is protected meaning its
normally only accessible to inheritors. Even if you inherit, and expose HResult
via public get method, you have 0 chance of convincing the run-time to throw
your specific SharingViolationException extension of IOException.

Instead, the kludge requires reflection and permission granted to reflect non-public
properties. Permission to reflect non-public properties isn't something developers
can count on, so by all means, ask the proprietor to expose the HResult property.
I can't think of a reason not to, except it may not get set 100 percent of the time?

Once you have it though (see below), the offending HRESULT is 0x80131620.
AFAIK, this should (in principle) be unique to IOExceptions that are really
Sharing Violations. Here's a demo,

- - - SharingViolation1.cs
using System;
using System.IO;
using System.Reflection;

public class SharingViolationApp
{
private string fileName;

public static void Main( string[] args)
{
if ( args.Length != 1 )
{
Console.WriteLine("Usage: amisharing <filename.ext>");
return;
}

theApp = new SharingViolationApp( args[ 0]);
theApp.Run( );
return;
}

public SharingViolationApp( string fileToOpen)
{
fileName = fileToOpen;
}

public void DisplayException( Exception e)
{
Console.WriteLine( "{0}\r\nHRESULT = 0x{1:X}", e,
e.GetType( ).GetProperty( "HResult", BindingFlags.NonPublic | BindingFlags.Instance).GetValue( e, null) );
}

public void Run( )
{
Console.WriteLine( "Trying to open {0}...", fileName);
FileStream _inStream = null;
try
{

_inStream = File.OpenRead( fileName);
}
catch ( IOException _ioe)
{
DisplayException( _ioe);
}
if ( _inStream != null )
{
Console.WriteLine( "{0} is not in use by another process.", fileName);
_inStream.Close( );
}
}
}
- - -

It's simple to test this, open two CMD shells. Go to the same directory in both. In one, do a

copy con notshared.txt
ABC
DEF
GHI

and then pause (don't hit CNTL-Z). Switch to the other shell window, build and run

csc /out:amisharing.exe SharingViolation1.cs
amisharing notshared.txt

and observe the IOException is thrown (note it's Message and HRESULT).

In my investigation I had tried another more esoteric approach, and I was a little disappointed it
didn't work. Conceptually it has merit, but in reality the sharing violation is so deeply ingrained
in I/O code that it seems out-of-reach ... to the implementation of FileIOPermission.

Yes -- SECURITY!

You would think, as I did for a moment, that it would be a major security snafu for the runtime to
allow you to read/write on a file that's currently in use by another process, when that other process
had paranoidly set its FileSharing mode to None. I thought perhaps a judicious demand on
FileIOPermission for the specific file might be more direct and efficient.

- - - SharingViolation2.cs
using System;
using System.Security;
using System.Security.Permission;

public class SharingViolationApp
{
private string fileName;

public static void Main( string[] args)
{
if ( args.Length != 1 )
{
Console.WriteLine("Usage: amisharing <filename.ext>");
return;
}

theApp = new SharingViolationApp( args[ 0]);
theApp.Run( );
return;
}

public SharingViolationApp( string fileToOpen)
{
// FileIOPermission requires absolute filespec.
//
string path = Environment.CurrentDirectory;
fileName = ( path.EndsWith( @"\") ) ? path + fileToOpen : path + @"\" + fileToOpen;
}

public void DisplayException( Exception e)
{
Console.WriteLine( "{0}\r\nHRESULT = 0x{1:X}", e,
e.GetType( ).GetProperty( "HResult", BindingFlags.NonPublic | BindingFlags.Instance).GetValue( e, null) );
}


public void Run( )
{
Console.WriteLine( "Trying to open {0}...", fileName);

FileIOPermission _available = new FileIOPermission( PermissionState.None); // blank slate
try
{
_available.SetPathList( FileIOPermissionAccess.Read, fileName);
_available.Demand( ); // does file permit shared reading?
Console.WriteLine( "{0} is O.K. to read.", fileName);
}
catch ( SecurityException _se)
{
DisplayException( _se);
}
}
}
- - -

If you run the same tests as above on the output of product of SharingViolation2.cs, you
would think the file you're creating allows shared reading. This isn't true.

What went wrong?

What it comes down to is that Code Access Security is imposed on the runtime from above.
Catching the condition wherein you are accessing a file already in use by another process
comes from much deeper within Windows.

Calling FileIOPermission::Demand( ) doesn't actually clear me for every security hurdle I
might expect. It only indicates that as far as privileges associated with me in the runtime's
code access security policy, I am trusted to read that file in that directory. It tells me I
may access the file, it doesn't state I can. "Can" is where the I/O system (or any other
system secured with its own code access security permission) takes over.

Now, it's conceivable an application could go beyond setting FileSharing to None, going as
far as to deny everybody else access to that file, and thereby enable a CAS demand to
identify this situation without having to go through the I/O plumbing. It's unlikely to become
the norm because there are generations of applications out there that assume it's enough
to pass 0 as the shareMode argument to the CreateFile( ) WIN API that would break.


In summary: sit back, relax, enjoy the .NET magic carpet ride. Try opening the file, and
catch the IOException to find out if it's in use.


Derek Harmon


Jason Efting

unread,
Mar 17, 2003, 2:01:03 PM3/17/03
to
Derek,
Thanks for your detailed reply. I have implemented the code according to
your suggestions.

Thanks again.
Jason

"Derek Harmon" <lore...@msn.com> wrote in message
news:#NiA8596...@TK2MSFTNGP12.phx.gbl...

0 new messages