Get file size using GetFileSize reports a segv on a valid file
Asked Answered
S

2

5

I have an application that scans file and collects meta data about a file. One function is to get the file size of a file. To do so, I am using the winapi function GetFileSizeEx(Handle, PLARGE_INTEGER). The parameters require a file HANDLE and a reference to a PLARGE_INTEGER aka (*LARGE_INTEGER).

To get a file HANDLE in Qt, it is suggested here to get the file handle using QFile::handle() and pass the result into _get_osfhandle(int) and cast it as a HANDLE.

HANDLE handle = (HANDLE) _get_osfhandle(file.handle()).

Using this handle, pass it with a PLARGE_INTEGER variable into the [GetFileSizeEx(Handle, PLARGE_INTEGER)] which returns 0 if the operation failed or != 0 if the operation succeeded.

GetFileSize(HANDLE, PLARGE_INTEGER) returns with:

  • 0 (or failure), one can use GetLastError in accordance with this list of error codes (as a start, there are many more) to help.

  • any non-zero value indicating success

Problem:

Using this approach, I attempt the same on a valid file, however I get a SEGV when calling GetFileSizeEx(Handle, PLARGE_INTEGER). I attempted this before and after calling QFile::open(QIODevice::ReadOnly) as a test, however it fails non the less.

 int localHandle = file.handle(); // handle returns -1 as expected
 bool localOpen = file.open(QIODevice::ReadOnly);
 if (localOpen) {
      int localFileHandle = file.handle(); // returns a valid >0 handle value
      HANDLE handle = (HANDLE) _get_osfhandle(localFileHandle); // returns a valid > 0 handle value
      PLARGE_INTEGER l = PLARGE_INTEGER(); // representation value of 0
      BOOL b = GetFileSizeEx(handle, l); // segv
      if (!b) {
           qDebug() << getLastErrorMsg();
           return QFileInfo(filepath).size();
      }

      return l->QuadPart;
 }

Did I do something wrong?


See attached screenshot

enter image description here

MVCE

#include <QCoreApplication>
#include "windows.h"
#include <comdef.h>
#include <QFile>
#include <QDebug>
#include <QString>
#include <QFileInfo>

#include <windows.h>
#include <fileapi.h>
#include <io.h>

static QString toString(HRESULT hr)
{
     _com_error err{hr};
     const TCHAR* lastError = err.ErrorMessage();
     return QStringLiteral("Error 0x%1: %2").arg((quint32)hr, 8, 16, QLatin1Char('0'))
            .arg(lastError);
}

static QString getLastErrorMsg()
{
     DWORD lastError = GetLastError();
     QString s = toString(HRESULT_FROM_WIN32(lastError));
     return s;
}

static qint64 getWinAPIFileSize(QString filepath)
{
     QFile file(filepath);
     if (!file.exists()) {
          return 0;
     }

     int localHandle = file.handle(); // handle returns -1 as expected
     bool localOpen = file.open(QIODevice::ReadOnly);
     if (localOpen) {
          int localFileHandle = file.handle(); // returns a valid >0 handle value
          HANDLE handle = (HANDLE) _get_osfhandle(localFileHandle); // returns a valid > 0 handle value
          PLARGE_INTEGER l = PLARGE_INTEGER(); // representation value of 0
          BOOL b = GetFileSizeEx(handle, l); // segv
          if (!b) {
               qDebug() << getLastErrorMsg();
               return QFileInfo(filepath).size();
          }

          return l->QuadPart;
     }

     return QFileInfo(filepath).size();
}

int main(int argc, char* argv[])
{
     QCoreApplication a(argc, argv);

     QString src = QString("C:/Users/CybeX/.bash_history"); // change path to a valid file on your PC

     qint64 size = getWinAPIFileSize(src);
     qDebug() << size;

     return a.exec();
}
Sacken answered 19/8, 2020 at 16:53 Comment(1)
Do you know what a PLARGE_INTEGER is?Ascender
C
10

The type PLARGE_INTEGER is an alias for "pointer to LARGE_INTEGER". And PLARGE_INTEGER() is a default value initialization for the type "pointer to LARGE_INTEGER". And for all pointers such an initialization is a null pointer.

That means the definition

PLARGE_INTEGER l = PLARGE_INTEGER();

is equivalent to

LARGE_INTEGER* l = nullptr;

Which means you pass a null-pointer to GetFileSizeEx.

You need to create an actual LARGE_INTEGER object, and pass a pointer to that object using the address-of operator &:

LARGE_INTEGER l;
GetFileSizeEx(handle, &l);  // Pass pointer to the variable l
Coacervate answered 19/8, 2020 at 17:0 Comment(4)
aha. See that was my logic for the = PLARGE_INTEGER part. My next question (related by doesn't suite an entire new question) is how do you know that. I posted the definition of PLARGE_INTEGER in the post, but it does not mention any constructor of the sort, atleast not what I see.Sacken
@Sacken Simple types (like for example a pointer to any type) can be value initialized. Value initialization is also sometimes called "zero initialization" because for e.g int the default value is zero. For a pointer the default value is a null pointer (which could also be seen as zero in a sense, especially considering that the compiler can recognize 0 as a null pointer in the correct context).Coacervate
@Sacken And knowledge of the different initialization categories comes with experience (and good references... ;))Coacervate
@cyb And once you realize, that we have some 20 ways to initialize an object you are a fair bit closer to leaving the hell that is C++ behind. For good.Mitzimitzie
J
3

The problem is that you are mishandling the PLARGE_INTEGER. You are creating a PLARGE_INTEGER (aka LARGE_INTEGER*) pointer that doesn't actually point anywhere, which is why GetFileSizeEx() crashes when it tries to write out the file size.

You need to allocate a LARGE_INTEGER (not a PLARGE_INTEGER) and then pass its memory address to GetFileSizeEx(), eg:

static qint64 getWinAPIFileSize(QString filepath)
{
    QFile file(filepath);

    if (file.open(QIODevice::ReadOnly)) {
        int localFileHandle = file.handle();
        HANDLE handle = (HANDLE) _get_osfhandle(localFileHandle);
        LARGE_INTEGER l;
        if (GetFileSizeEx(handle, &l)) {
            return l.QuadPart;
        }
        qDebug() << getLastErrorMsg();
    }

    return QFileInfo(file).size();
}

That being said, there is really no point in using GetFileSizeEx() manually at all in this situation. Depending on its implementation (which I can't check), QFileInfo.size() will either open the file for you, or it will simply query the file's size directly from the filesystem's metadata and not actually open the file at all:

static qint64 getWinAPIFileSize(QString filepath)
{
    return QFileInfo(filepath).size();
}
Jink answered 19/8, 2020 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.