1

I'd like to find an efficient way to get all the processes using a particular file.

I know I can do psutil.process_iter() and then search process.open_files for the file for each process. This is very inefficient as searching every single process, and every file each process has open, takes a lot of time (10 seconds on my machine).

Is there a faster way? Is it possible to just directly get all of the processes using a file, rather than searching every single process?

Daniel Paczuski Bak
  • 3,720
  • 8
  • 32
  • 78
  • Each platform has a different way of doing this, and I don’t think psutil or any other library wraps them up in a cross-platform way. Do you need it to be portable, or do you only care about one platform (presumably Windows)? – abarnert Sep 06 '18 at 23:43
  • If your answer is that you sort of care about every platform but mainly Windows, I think you can use a Windows-specific library on Windows, then handle everything else by using `subprocess` to call the `lsof` command. (I’m not sure exactly what set of arguments works the same way on both GNU and BSD and gets exactly what you want, but I’ll bet there’s a question on Super User or Unix SE that covers that.) – abarnert Sep 06 '18 at 23:45
  • Do you know what that windows-specific library might be? – Daniel Paczuski Bak Sep 06 '18 at 23:47
  • for this task, starting with Windows Vista special exist `FileProcessIdsUsingFileInformation` [`FILE_INFORMATION_CLASS`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ne-wdm-_file_information_class). in *c++* solution can look like [this](https://stackoverflow.com/questions/47507578/winapi-get-the-process-which-has-specific-handle-of-a-file/47510579#47510579) – RbMm Sep 06 '18 at 23:49
  • No, I don’t. Last time I needed something like this was long, long ago. I downloaded the C source to the `HANDLE.EXE` tool from sysinternals and ported it to Python using ctypes to call the same APIs. Hopefully there’s something simpler nowadays, but if there is, I have no idea what. – abarnert Sep 06 '18 at 23:49
  • you need open file with `FILE_READ_ATTRIBUTES` access and call `NtQueryInformationFile` with `FileProcessIdsUsingFileInformation` class - you got list of process id which use this file – RbMm Sep 06 '18 at 23:52
  • I don't understand you, RbMm. I can't find out how to import these functions and classes. – Daniel Paczuski Bak Sep 07 '18 at 01:16
  • You need ctypes. I'll write it up for you if you want, but note that `FileProcessIdsUsingFileInformation` is technically "reserved for system use". – Eryk Sun Sep 07 '18 at 01:18

1 Answers1

0

Yes, in Windows, you can do it in the following way:

import ctypes
from ctypes import wintypes

path = r"C:\temp\test.txt"

# -----------------------------------------------------------------------------
# generic strings and constants
# -----------------------------------------------------------------------------

ntdll = ctypes.WinDLL('ntdll')
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

NTSTATUS = wintypes.LONG

INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
FILE_READ_ATTRIBUTES = 0x80
FILE_SHARE_READ = 1
OPEN_EXISTING = 3
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000

FILE_INFORMATION_CLASS = wintypes.ULONG
FileProcessIdsUsingFileInformation = 47

LPSECURITY_ATTRIBUTES = wintypes.LPVOID
ULONG_PTR = wintypes.WPARAM


# -----------------------------------------------------------------------------
# create handle on concerned file with dwDesiredAccess == FILE_READ_ATTRIBUTES
# -----------------------------------------------------------------------------

kernel32.CreateFileW.restype = wintypes.HANDLE
kernel32.CreateFileW.argtypes = (
    wintypes.LPCWSTR,      # In     lpFileName
    wintypes.DWORD,        # In     dwDesiredAccess
    wintypes.DWORD,        # In     dwShareMode
    LPSECURITY_ATTRIBUTES,  # In_opt lpSecurityAttributes
    wintypes.DWORD,        # In     dwCreationDisposition
    wintypes.DWORD,        # In     dwFlagsAndAttributes
    wintypes.HANDLE)       # In_opt hTemplateFile
hFile = kernel32.CreateFileW(
    path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, None, OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS, None)
if hFile == INVALID_HANDLE_VALUE:
    raise ctypes.WinError(ctypes.get_last_error())


# -----------------------------------------------------------------------------
# prepare data types for system call
# -----------------------------------------------------------------------------

class IO_STATUS_BLOCK(ctypes.Structure):
    class _STATUS(ctypes.Union):
        _fields_ = (('Status', NTSTATUS),
                    ('Pointer', wintypes.LPVOID))
    _anonymous_ = '_Status',
    _fields_ = (('_Status', _STATUS),
                ('Information', ULONG_PTR))


iosb = IO_STATUS_BLOCK()


class FILE_PROCESS_IDS_USING_FILE_INFORMATION(ctypes.Structure):
    _fields_ = (('NumberOfProcessIdsInList', wintypes.LARGE_INTEGER),
                ('ProcessIdList', wintypes.LARGE_INTEGER * 64))


info = FILE_PROCESS_IDS_USING_FILE_INFORMATION()

PIO_STATUS_BLOCK = ctypes.POINTER(IO_STATUS_BLOCK)
ntdll.NtQueryInformationFile.restype = NTSTATUS
ntdll.NtQueryInformationFile.argtypes = (
    wintypes.HANDLE,        # In  FileHandle
    PIO_STATUS_BLOCK,       # Out IoStatusBlock
    wintypes.LPVOID,        # Out FileInformation
    wintypes.ULONG,         # In  Length
    FILE_INFORMATION_CLASS)  # In  FileInformationClass

# -----------------------------------------------------------------------------
# system call to retrieve list of PIDs currently using the file
# -----------------------------------------------------------------------------
status = ntdll.NtQueryInformationFile(hFile, ctypes.byref(iosb),
                                      ctypes.byref(info),
                                      ctypes.sizeof(info),
                                      FileProcessIdsUsingFileInformation)
pidList = info.ProcessIdList[0:info.NumberOfProcessIdsInList]
print(pidList)
Robert
  • 1,357
  • 15
  • 26