user884248
user884248

Reputation: 2244

Am I using CreateFile/GetFileType/CloseHandle incorrectly?

I have a VB6 app which gets a file path and must check if this path points to an actual file, as opposed to an LPT device, pipe, etc. I'm using the APIs CreateFile, GetFileType, and then CloseHandle.

Although this code works, when I attach a debugger to the program, the debugger breaks on an exception, saying that CloseHandle is trying to close a non-existing handle (or, sometimes, saying "invalid argument"). This happens even though I check (as you can see from the code) that the handle <> INVALID_HANDLE_VALUE.

I have two questions.

First, I think I need to change the call to CreateFile, from

handle = CreateFile(FilePath, 0, 0, ByVal 0&, OPEN_EXISTING, 0&, 0&)

...to

handle = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE, 0&, OPEN_EXISTING, FILE_ATTRIBUTES_ENUM.FA_NORMAL, 0&)

am I correct about this? I'm only using CreateFile to get the handle to pass over to GetFileType. I'm not actually doing anything with this file.

Second, why could this be happening? I'm checking if handle <> INVALID_HANDLE_VALUE, but still getting this error!

The results of the a dump are attached after the code.

Private Const OPEN_EXISTING         As Long = 3
Private Const FILE_SHARE_READ       As Long = &H1
Private Const INVALID_HANDLE_VALUE  As Long = -1

Private Const FILE_TYPE_DISK        As Long = &H1
Private Const FILE_TYPE_CHAR        As Long = &H2
Private Const FILE_TYPE_PIPE        As Long = &H3
Private Const FILE_TYPE_REMOTE      As Long = &H8000
Private Const FILE_TYPE_UNKNOWN     As Long = &H0

Private Declare Function CloseHandle_API Lib "kernel32" Alias "CloseHandle" (ByVal hObject As Long) As Long
Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Declare Function GetFileType_API Lib "kernel32" Alias "GetFileType" (ByVal hFile As Long) As Long

Private Function IsAnActualFile(FilePath As String, fileType As Long) As Boolean
    On Error GoTo errHandle

    Dim handle                  As Long
    Dim lpSecurityAttributes    As Long

    lpSecurityAttributes = 0
    handle = CreateFile(FilePath, 0, 0, ByVal 0&, OPEN_EXISTING, 0&, 0&)
    If handle <> INVALID_HANDLE_VALUE Then
        fileType = GetFileType_API(handle)
        If fileType = FILE_TYPE_DISK Then
            IsAnActualFile = True
        End If
        CloseHandle_API handle
    End If

errHandle:
    If handle <> INVALID_HANDLE_VALUE Then CloseHandle_API handle
End Function

Here is the dump:

0:000> kb
ChildEBP RetAddr  Args to Child              
0018edf0 74ebc463 000038ec 000038ec 0018ee10 ntdll!NtClose+0x12
0018ee00 75141418 000038ec 00000001 0018ee68 KERNELBASE!CloseHandle+0x2d
0018ee10 1107ee38 000038ec 0018f32c 00000000 kernel32!CloseHandleImplementation+0x3f
0018ee68 1107d3ef 19cb5968 0018f32c 0018f158 MyProgram!Document::IsAnActualFile+0xb8

Upvotes: 0

Views: 1067

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597971

Yes, you should change the permissions you are requesting when opening the file. You are requesting exclusive access, which will fail if anybody has the file open for any reason.

As for the CloseHandle() error, when the file is successfully opened, you are closing the file handle twice. Your errHandle is always entered, but you did not reset your variable after closing it the first time:

Private Function IsAnActualFile(FilePath As String) As Boolean
    On Error GoTo errHandle

    Dim handle As Long

    handle = CreateFile(FilePath, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE Or FILE_SHARE_DELETE, 0&, OPEN_EXISTING, FILE_ATTRIBUTES_ENUM.FA_NORMAL, 0&)
    If handle <> INVALID_HANDLE_VALUE Then
        fileType = GetFileType_API(handle)
        If fileType = FILE_TYPE_DISK Then
            IsAnActualFile = True
        End If
        CloseHandle_API handle
        handle = INVALID_HANDLE_VALUE ' <-- add this!
    End If

errHandle:
    If handle <> INVALID_HANDLE_VALUE Then CloseHandle_API handle
End Function

Alternatively, don't let the code get into the errHandle after calling CloseHandle() the first time:

Private Function IsAnActualFile(FilePath As String, fileType As Long) As Boolean
    On Error GoTo errHandle

    Dim handle As Long

    handle = CreateFile(FilePath, GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE Or FILE_SHARE_DELETE, 0&, OPEN_EXISTING, FILE_ATTRIBUTES_ENUM.FA_NORMAL, 0&)
    If handle <> INVALID_HANDLE_VALUE Then
        fileType = GetFileType_API(handle)
        If fileType = FILE_TYPE_DISK Then
            IsAnActualFile = True
        End If
        CloseHandle_API handle
        Exit Function ' <-- add this!
    End If

errHandle:
    If handle <> INVALID_HANDLE_VALUE Then CloseHandle_API handle
End Function

That being said, you might also want to consider what happens if the caller passes in a path to a directory or a root drive, instead of an actual file. CreateFile() can still succeed, and GetFileType() is likely to still return FILE_TYPE_DISK since it is on the hard drive.

A simpler solution would be to use GetFileAttributes() instead of CreateFile() so that you do not have to actually open the file at all. GetFileAttributes() can only query files and directories on the hard drive, anything else will fail. So no need for GetFileType(), either.

Private Const FILE_ATTRIBUTE_DIRECTORY as Long = &H10
Private Const FILE_ATTRIBUTE_DEVICE as Long = &H40
Private Const FILE_ATTRIBUTE_REPARSE_POINT as Long = &H400

Private Const INVALID_FILE_ATTRIBUTES as Long = -1

Private Declare Function GetFileAttributes_API Lib "kernel32" Alias "GetFileAttributesA" (ByVal lpFileName As String) As Long

Private Function IsAnActualFile(FilePath As String) As Boolean
    Dim attrs As Long

    attrs = GetFileAttributes_API(FilePath)
    If attrs <> INVALID_FILE_ATTRIBUTES Then
        If (attrs And (FILE_ATTRIBUTE_DIRECTORY Or FILE_ATTRIBUTE_DEVICE Or FILE_ATTRIBUTE_REPARSE_POINT)) = 0 Then
            IsAnActualFile = True
        End If
    End If
End Function

Upvotes: 2

Related Questions