0

I have a list of file and there is only one in used in a time. So i wanna know which file is being used by specific program. Since i can use 'unlocker' to find out a file that are in used like this question have mentioned. But i want a programming way so that my program can help me find out. Is there any way?

Specially, the simple 'open' function in whatever r/w mode CAN access the using file and python won't throw any exception. I can tell which file being used only by 'unlocker'.

I have find out that the python 'open' function in w mode have took the access permission from that specific program, and the program then don't work so well. In this moment i open the unlocker and i can see two process accessing the file. Is there any 'weak' method that can only detect whether the file is being used?

Community
  • 1
  • 1
prehawk
  • 195
  • 12

2 Answers2

3

I'm not sure which of these two you want to find:

  • Are there are any existing HANDLEs for a given file, like the handle and Process Explorer tools shows?
  • Are there any existing locked HANDLEs for a given file, like the Unlocker tool shows.

But either way, the answer is similar.

Obviously it's doable, or those tools couldn't do it, right? Unfortunately, there is nothing in the Python stdlib that can help. So, how do you do it?

You will need to access Windows APIs functions—through pywin32, ctypes, or otherwise.

There are two ways to go about it. The first, mucking about with the NT kernel object APIs, is much harder, and only really needed if you need to work with very old versions of Windows. The second, NtQuerySystemInformation, is the one you probably want.

The details are pretty complicated (and not well documented), but they're explained in a CodeProject sample program and on the Sysinternals forums.

If you don't understand the code on those pages, or how to call it from Python, you really shouldn't be doing this. If you get the gist, but have questions, or get stuck, of course you can ask for help here (or at the sysinternals forums or elsewhere).

However, even if you have used ctypes for Windows before, there are a few caveats you may not know about:

Many of these functions either aren't documented, or the documentation doesn't tell you which DLL to find them in. They will all be in either ntdll or kernel32; in some cases, however, the documented NtFooBar name is just an alias for the real ZwFooBar function, so if you don't find NtFooBar in either DLL, look for ZwFooBar.

At least on older versions of Windows, ZwQuerySystemInformation does not act as you'd expect: You cannot call it with a 0 SystemInformationLength, check the ReturnLength, allocate a buffer of that size, and try again. The only thing you can do is start with a buffer with enough room for, say, 8 handles, try that, see if you get an error STATUS_INFO_LENGTH_MISMATCH, increase that number 8, and try again until it succeeds (or fails with a different error). The code looks something like this (assuming you've defined the SYSTEM_HANDLE structure):

STATUS_INFO_LENGTH_MISMATCH = 0xc0000004
i = 8
while True:
    class SYSTEM_HANDLE_INFORMATION(Structure):
        _fields_ = [('HandleCount', c_ulong),
                    ('Handles', SYSTEM_HANDLE * i)]
    buf = SYSTEM_HANDLE_INFORMATION()
    return_length = sizeof(buf)
    rc = ntdll.ZwQuerySystemInformation(SystemHandleInformation,
                                        buf, sizeof(buf),
                                        byref(return_length))
    if rc == STATUS_INFO_LENGTH_MISMATCH:
        i += 8
        continue
    elif rc == 0:
        return buf.Handles[:buf.HandleCount]
    else:
        raise SomeKindOfError(rc)

Finally, the documentation doesn't really explain this anywhere, but the way to get from a HANDLE that you know is a file to a pathname is a bit convoluted. Just using NtQueryObject(ObjectNameInformation) returns you a kernel object space pathname, which you then have to map to either a DOS pathname, a possibly-UNC normal NT pathname, or a \?\ pathname. Of course the first doesn't work files on network drives without a mapped drive letter; neither of the first two work for files with very long pathnames.


Of course there's a simpler alternative: Just drive handle, Unlocker, or some other command-line tool via subprocess.


Or, somewhere in between, build the CodeProject project linked above and just open its DLL via ctypes and call its GetOpenedFiles method, and it will do the hard work for you.

Since the project builds a normal WinDLL-style DLL, you can call it in the normal ctypes way. If you've never used ctypes, the examples in the docs show you almost everything you need to know, but I'll give some pseudocode to get you started.

First, we need to create an OF_CALLBACK type for the callback function you're going to write, as described in Callback functions. Since the prototype for OF_CALLBACK is defined in a .h file that I can't get to here, I'm just guessing at it; you'll have to look at the real version and translate it yourself. But your code is going to look something like this:

from ctypes import windll, c_int, WINFUNCTYPE
from ctypes.wintypes import LPCWSTR, UINT_PTR, HANDLE

# assuming int (* OF_CALLBACK)(int, HANDLE, int, LPCWSTR, UINT_PTR)
OF_CALLBACK = WINFUNCTYPE(c_int, HANDLE, c_int, LPWCSTR, UINT_PTR)
def my_callback(handle, namelen, name, context):
    # do whatever you want with each handle, and whatever other args you get
my_of_callback = OF_CALLBACK(my_callback)

OpenFileFinder = windll.OpenFileFinder
# Might be GetOpenedFilesW, as with most Microsoft DLLs, as explained in docs
OpenFileFinder.GetOpenedFiles.argtypes = (LPCWSTR, c_int, OF_CALLBACK, UINT_PTR)
OpenFileFinder.GetOpenedFiles.restype = None

OpenFileFinder.GetOpenedFiles(ru'C:\Path\To\File\To\Check.txt', 0, my_of_callback, None)

It's quite possible that what you get in the callback is actually a pointer to some kind of structure or list of structures you're going to have to define—likely the same SYSTEM_HANDLE you'd need for calling the Nt/Zw functions directly, so let me show that as an example—if you get back a SYSTEM_HANDLE *, not a HANDLE, in the callback, it's as simple as this:

class SYSTEM_HANDLE(Structure):
    _fields_ = [('dwProcessId', DWORD),
                ('bObjectType', BYTE),
                ('bFlags', BYTE),
                ('wValue', WORD),
                ('pAddress', PVOID),
                ('GrantedAccess', DWORD)]

# assuming int (* OF_CALLBACK)(int, SYSTEM_HANDLE *, int, LPCWSTR, UINT_PTR)
OF_CALLBACK = WINFUNCTYPE(c_int, POINTER(SYSTEM_HANDLE), c_int, LPWCSTR, UINT_PTR)
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I know the exact program class name (i use spyxx.exe to check it), but pywin32 seems so hard to me. But i'll try. – prehawk Aug 03 '13 at 03:24
  • I think you mean the window class name, which isn't going to help you here at all. But no matter _what_ information you have, it isn't going to make your job any easier; there's no way around enumerating every open handle and then filtering out the ones you want (by process, by path, whatever). – abarnert Aug 03 '13 at 03:27
  • @prehawk: Also, in my example above, I did it with `ctypes` rather than `pywin32`. I don't know if you can access everything you need through `pywin32`, but if you can, it'll probably be a lot easier than my example. – abarnert Aug 03 '13 at 03:28
  • 1
    @prehawk: One last thing: If you know how to set up and use a compiler, Cython may be a whole lot easier. It lets you write code that's very similar to Python code, but can call C functions directly, without any need for mucking around with ctypes. In the long run, it would likely make your life easier. But in the short run, I suspect it would just mean an even steeper learning curve. – abarnert Aug 03 '13 at 03:29
  • +1 for that Cython comment. This looks like an excellent candidate for farming out to C code, especially since it'll only ever be called in Windows. (Of course, your point that learning C is a bad short term solution is also true). – Prime Aug 03 '13 at 05:27
  • @abarnert may be "Listing Used Files" is the easiest way for me, but i don't know how to pass arguments to that dll, can you show me? – prehawk Aug 03 '13 at 14:18
  • @prehawk: You mean that you want to build the CodeProject DLL, and then call its `GetOpenedFiles` method from Python? I'll update the answer to show how to do that. – abarnert Aug 05 '13 at 18:45
0

You could use a try/except block.

check if a file is open in Python

   try:
     f = open("file.txt", "r")
   except IOError:
     print "This file is already in use"
Community
  • 1
  • 1
JSutton
  • 71
  • 5
  • I'm sorry, my question was not so clear just now. The file that is being used can be 'open' in both r/w mode. And that's where confused me. Is there any other way? – prehawk Aug 03 '13 at 02:14