RestoreFile Call (File Management API - fmapi.dll) Returns Invalid Handle

1

Background:

I'm trying to write a C++ application that can scan for and attempt to restore deleted files from a WinPE environment, mostly as a learning exercise. This app utilizes the FMAPI library (fmapi.dll), which is a scarcely-documented library that only works in a WinPE environment (does not work in a full Windows OS). I've been using the ScanRestorableFiles example released by MS (available here) as a starting point.

Now, I've done a LOT of digging and have found next to nothing when it comes to FMAPI documentation - just the sample noted above and some basic MSDN docs here. The MSDN pages provide the definitions for the API functions and a few extra notes on a couple of the functions that provide a hint or two, but that's it. So I thought I'd come here in hopes of finding some assistance.

Also please note, as far as development languages go, C++ is not my strong point - I would consider myself novice at best.

Now to the issue at hand:

My app is able to successfully load the library, create the file restore context and scan for restorable files. However, once I try to call the RestoreFile() function on one of the restorable items that was returned by the ScanRestorableFiles() call, I get an Invalid Handle error. The "restored file" ends up being a 0-byte file (it does get created successfully in the proper place) with no data in it.

Interestingly, even after returning the Invalid Handle error code, my app holds a handle open on the file until the file restore context is closed (I know this because if I try to read the restored file immediately after attempting to restore it, I get a "file is in use by another process" error).

Posted below is the entire code for my app (its just a single source file, not counting headers and such) - since this seems to be a rarely-used API, I feel like I should post the whole thing to add context to each function call (and also because I'm not quite sure exactly what may or may not be relevant to the issue I'm having).

Code:

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <sstream>
#include <stdlib.h>

#define SCAN_PATH L"\\"

//
//Define the needed FMAPI structures as documented in FMAPI
//
#define FILE_RESTORE_MAJOR_VERSION_2    0x0002
#define FILE_RESTORE_MINOR_VERSION_2    0x0000
#define FILE_RESTORE_VERSION_2          ((FILE_RESTORE_MAJOR_VERSION_2 << 16) | FILE_RESTORE_MINOR_VERSION_2)

using namespace std;

//External API function declarations

//We don't have an import library or
//header for the FMAPI functions, so
//we must dynamically link to them at
//runtime.

typedef PVOID PFILE_RESTORE_CONTEXT;

typedef enum  {
    ContextFlagVolume                   = 0x00000001,
    ContextFlagDisk                     = 0x00000002,
    FlagScanRemovedFiles                = 0x00000004,
    FlagScanRegularFiles                = 0x00000008,
    FlagScanIncludeRemovedDirectories   = 0x00000010 
} RESTORE_CONTEXT_FLAGS;

typedef enum {
    FileRestoreProgressInfo = 100,
    FileRestoreFinished     = 101
} FILE_RESTORE_PACKET_TYPE, *PFILE_RESTORE_PACKET_TYPE;

typedef BOOL (WINAPI *FuncCreateFileRestoreContext) (
    _In_  PCWSTR                Volume,
    _In_  RESTORE_CONTEXT_FLAGS Flags,
    _In_  LONGLONG              StartSector,
    _In_  LONGLONG              BootSector,
    _In_  DWORD                 Version,
    _Out_ PFILE_RESTORE_CONTEXT* Context
    );

typedef BOOL (WINAPI *FuncCloseFileRestoreContext) (
  _In_  PFILE_RESTORE_CONTEXT Context
);

typedef struct _RESTORABLE_FILE_INFO
{
    ULONG           Size;
    DWORD           Version;
    ULONGLONG       FileSize;
    FILETIME        CreationTime;
    FILETIME        LastAccessTime;
    FILETIME        LastWriteTime;
    DWORD           Attributes;
    BOOL            IsRemoved;
    LONGLONG        ClustersUsedByFile;
    LONGLONG        ClustersCurrentlyInUse;
    ULONG           RestoreDataOffset;
    WCHAR           FileName[1]; // Single-element array indicates a variable-length structure
} RESTORABLE_FILE_INFO, *PRESTORABLE_FILE_INFO;

typedef struct _FILE_RESTORE_PROGRESS_INFORMATION {
    LONGLONG    TotalFileSize;
    LONGLONG    TotalBytesCompleted;
    LONGLONG    StreamSize;
    LONGLONG    StreamBytesCompleted;
    PVOID       ClbkArg;
} FILE_RESTORE_PROGRESS_INFORMATION, *PFILE_RESTORE_PROGRESS_INFORMATION;

typedef struct _FILE_RESTORE_FINISHED_INFORMATION {
    BOOL    Success;
    ULONG   FinalResult;
    PVOID   ClbkArg;
} FILE_RESTORE_FINISHED_INFORMATION, *PFILE_RESTORE_FINISHED_INFORMATION;

typedef BOOL (WINAPI *FuncScanRestorableFiles) (
    _In_                        PFILE_RESTORE_CONTEXT   Context,
    _In_                        PCWSTR                  Path,
    _In_                        ULONG                   FileInfoSize,
    _Out_bytecap_(FileInfoSize) PRESTORABLE_FILE_INFO   FileInfo,
    _Out_                       PULONG                  FileInfoUsed
);

typedef BOOLEAN (*FILE_RESTORE_CALLBACK) (
    _In_    FILE_RESTORE_PACKET_TYPE    PacketType,
    _In_    ULONG                       PacketLength,
    _In_    PVOID                       PacketData
);

typedef BOOL (WINAPI *FuncRestoreFile) (
    _In_        PFILE_RESTORE_CONTEXT   Context,
    _In_        PRESTORABLE_FILE_INFO   RestorableFile,
    _In_        PCWSTR                  DstFile,
    _In_opt_    FILE_RESTORE_CALLBACK   Callback,
    _In_opt_    PVOID                   ClbkArg
);

HMODULE hLib;
wchar_t VOLUME[255];

BOOLEAN FuncRestoreCallback(_In_ FILE_RESTORE_PACKET_TYPE pType, _In_ ULONG pLength, _In_ PVOID pData)
{
    // This is the callback that is passed to RestoreFile(), which
    // returns data about the status of an attempted restoration.
    if (pType == FileRestoreProgressInfo)
    {
        wcout << L"FILE RESTORE PROGRESS INFO:" << L"\n";
        wprintf(L"Length of status data: %lu\n", pLength);
        wcout << L"Location of data: " << pData << L"\n";
        PFILE_RESTORE_PROGRESS_INFORMATION restoreProgressInfo = static_cast<PFILE_RESTORE_PROGRESS_INFORMATION>(pData);
        wprintf(L"Total file size: %lld\n", restoreProgressInfo->TotalFileSize);
        wprintf(L"Total bytes completed: %lld\n", restoreProgressInfo->TotalBytesCompleted);
        wprintf(L"Stream size: %lld\n", restoreProgressInfo->StreamSize);
        wprintf(L"Stream bytes completed: %lld\n", restoreProgressInfo->StreamBytesCompleted);
        //wcout << L"Callback arg data: " << restoreProgressInfo->ClbkArg << L"\n";
        wprintf(L"Callback arg: %p\n", restoreProgressInfo->ClbkArg);
    }
    else if (pType == FileRestoreFinished)
    {
        wcout << L"FILE RESTORE FINISHED INFO:" << L"\n";
        wprintf(L"Length of status data: %lu\n", pLength);
        wcout << L"Location of data: " << pData << L"\n";
        // Obtain the struct
        PFILE_RESTORE_FINISHED_INFORMATION restoreFinishedInfo = static_cast<PFILE_RESTORE_FINISHED_INFORMATION>(pData);
        // Try to read some data from it
        wprintf(L"Success data: %d\n", restoreFinishedInfo->Success);
        wprintf(L"Final result data: %lu\n", restoreFinishedInfo->FinalResult);
        wprintf(L"Callback arg: %p\n", restoreFinishedInfo->ClbkArg);
    }
    return TRUE;
}

void Scan(_In_ PFILE_RESTORE_CONTEXT context, _In_ LPCWSTR path)
{
    // This is the main function that scans the files
    // Dynamically link to the needed FMAPI functions
    FuncScanRestorableFiles ScanRestorableFiles;
    ScanRestorableFiles = reinterpret_cast<FuncScanRestorableFiles>( GetProcAddress( hLib, "ScanRestorableFiles" ) );

    ULONG neededBufferSize = 0;
    BOOL success = TRUE;
    RESTORABLE_FILE_INFO tempFileInfo;

    // Call ScanRestorableFiles the first time with a size of 0 to get the required buffer size
    if ( ! ScanRestorableFiles(context, path, 0, &tempFileInfo, &neededBufferSize) )
    {
        wprintf(L"Failed to retrieve required buffer size, Error: #%u\n", GetLastError());
        return;
    }

    // Create the buffer needed to hold restoration information
    BYTE *buffer = new BYTE[neededBufferSize];
    wcout << L"Initial buffer size is: " << neededBufferSize << L"\n";

    // Loops until an error occurs or no more files found
    while (success)
    {        
        // Cast the byte buffer pointer into a structure pointer
        PRESTORABLE_FILE_INFO fileInfo = reinterpret_cast<PRESTORABLE_FILE_INFO>(buffer);
        #pragma warning( push ) 
        #pragma warning( disable : 6386 ) /* warning is ignored since fileInfo grows in size by design */
        success = ScanRestorableFiles(context, path, neededBufferSize, fileInfo, &neededBufferSize);
        #pragma warning( pop )
        wcout << L"Current buffer size is: " << neededBufferSize << L"\n";
        if (success)
        {
            wcout << L"Call returned success! Required buffer size from latest call is " << neededBufferSize <<
                L" bytes." L"\n";
            if (fileInfo->IsRemoved)
            {
                // Found restorable file
                wprintf(L"Restorable file found: %s\n", fileInfo->FileName);

                // Echo size of char array containing file name
                wcout << L"Restorable file name size: " <<
                    (sizeof(fileInfo->FileName) / sizeof(fileInfo->FileName[0])) << L"\n";

                // Echo RESTORABLE_FILE_INFO structure info to console
                wcout << L"Restorable file info structure memory address: " << fileInfo << L"\n";
                wcout << L"Restorable file info structure size (returned via RESTORABLE_FILE_INFO): " << fileInfo->Size << L"\n";
                wcout << L"Restorable file info structure size (returned via sizeof()): " << sizeof(*fileInfo) << L"\n";

                // Echo restorable file (FMAPI) version to console
                wcout << L"Restorable file version: " << fileInfo->Version << L"\n";

                // Retrieve creation, write and access times for the file
                // Define temp FILETIME, SYSTEMTIME and TIME_ZONE_INFORMATION
                // structure vars
                // All of these types and functions are defined in windows.h
                FILETIME tmpFT;
                SYSTEMTIME tmpST;
                TIME_ZONE_INFORMATION tmpTZI;
                // Initialize empty char arrays
                wchar_t szLocalDate[255] = {0}, szLocalTime[255] = {0};
                // Get local time zone info
                SetTimeZoneInformation(&tmpTZI);
                // Get file creation time
                FileTimeToLocalFileTime(&(fileInfo->CreationTime), &tmpFT);
                FileTimeToSystemTime(&tmpFT, &tmpST);
                // Format to readable output and store in char arrays
                GetDateFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, DATE_LONGDATE, &tmpST, NULL, szLocalDate, 255, NULL);
                GetTimeFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, 0, &tmpST, NULL, szLocalTime, 255);
                wcout << L"Restorable file created: " << szLocalDate << " " << szLocalTime << L"\n";
                // Clear array for re-use
                fill(begin(szLocalDate), end(szLocalDate), 0);
                // Get last write time
                FileTimeToLocalFileTime(&(fileInfo->LastWriteTime), &tmpFT);
                FileTimeToSystemTime(&tmpFT, &tmpST);
                // Format to readable output and store in char arrays
                GetDateFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, DATE_LONGDATE, &tmpST, NULL, szLocalDate, 255, NULL);
                GetTimeFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, 0, &tmpST, NULL, szLocalTime, 255);
                wcout << L"Restorable file last written: " << szLocalDate << " " << szLocalTime << L"\n";
                // Clear array for re-use
                fill(begin(szLocalDate), end(szLocalDate), 0);
                // Get last access time
                FileTimeToLocalFileTime(&(fileInfo->LastAccessTime), &tmpFT);
                FileTimeToSystemTime(&tmpFT, &tmpST);
                // Format to readable output and store in char arrays
                GetDateFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, DATE_LONGDATE, &tmpST, NULL, szLocalDate, 255, NULL);
                GetTimeFormatEx(LOCALE_NAME_SYSTEM_DEFAULT, 0, &tmpST, NULL, szLocalTime, 255);
                wcout << L"Restorable file last accessed: " << szLocalDate << " " << szLocalTime << L"\n";

                // Output the rest of the file info
                wcout << L"Restorable file attributes: " << fileInfo->Attributes << L"\n";
                wcout << L"Restorable file size: " << fileInfo->FileSize << L"\n";
                wcout << L"Restorable file ClustersUsedByFile: " << fileInfo->ClustersUsedByFile << L"\n";
                wcout << L"Restorable file ClustersCurrentlyInUse: " << fileInfo->ClustersCurrentlyInUse << L"\n";
                wcout << L"Restorable file RestoreDataOffset: " << fileInfo->RestoreDataOffset << L"\n";

                // Attempt to restore the file
                wstring tmpStr;
                getline(wcin, tmpStr);
                // Convert input to uppercase
                for (wstring::size_type i = 0; i < tmpStr.size(); i++)
                {
                    towupper(tmpStr[i]);
                }
                wcout << L"tmpStr is: " << tmpStr << L"\n";
                if (tmpStr == L"RESTORE")
                {
                    // Attempt to restore the file
                    wcout << L"Attempting to restore file " << fileInfo->FileName << L"..." << L"\n";
                    FuncRestoreFile RestoreFile;
                    //RestoreFile = (FuncRestoreFile)GetProcAddress( hLib, "RestoreFile" );
                    RestoreFile = reinterpret_cast<FuncRestoreFile>(GetProcAddress(hLib, "RestoreFile"));
                    wcout << L"RestoreFile address: " << RestoreFile << L"\n";
                    BOOL tmpRetVal = false;
                    PCWSTR restoredFileName = L"X:\\testfile.txt";
                    wcout << L"New file name: " << restoredFileName << L"\n";
                    PVOID cbArg = NULL;
                    tmpRetVal = RestoreFile(context, fileInfo, restoredFileName, &FuncRestoreCallback, cbArg);
                    wcout << L"Return value: " << tmpRetVal << L" ; cbArg: " << cbArg << L"\n";
                    if (tmpRetVal == 0)
                    {
                        wcout << L"Error was: " << GetLastError() << L"\n";
                    }
                }
                else if (tmpStr == L"CLOSE")
                {
                    // Abort the scanning process and close the file restore context
                    wcout << L"Aborting scan and closing file restore context..." << L"\n";
                    success = false;
                }
            }
        } 
        else
        {
            DWORD err = GetLastError();
            if (ERROR_INSUFFICIENT_BUFFER == err)
            {
                wcout << L"Insufficient buffer size! Current size is " << sizeof(buffer) << L" bytes; " <<
                    L" required size is " << neededBufferSize << L" bytes. Resizing..." << L"\n";
                delete [] buffer;
                buffer = new BYTE[neededBufferSize];
                success = true;
            } 
            else if (ERROR_NO_MORE_FILES == err)
            {
                wprintf(L"Scanning Complete.\n");
                success = false;
            }
            else
            {
                wprintf(L"ScanRestorableFiles, Error #%u.\n", err);
            }
        }
    }

    delete [] buffer;
    buffer = NULL;
}

//
// Program entry point
//
void __cdecl wmain(int argc, wchar_t *argv[])
{

    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); 

    // Load the FMAPI DLL
    hLib = ::LoadLibraryEx(L"fmapi.dll", NULL, NULL);    
    if ( !hLib )
    {
        wprintf(L"Could not load fmapi.dll. Error #%u.\n", GetLastError());
        return;
    }

    // Dynamically link to the needed FMAPI functions
    FuncCreateFileRestoreContext CreateFileRestoreContext;
    CreateFileRestoreContext = reinterpret_cast<FuncCreateFileRestoreContext>( GetProcAddress( hLib, "CreateFileRestoreContext" ) );

    FuncCloseFileRestoreContext CloseFileRestoreContext;
    CloseFileRestoreContext = reinterpret_cast<FuncCloseFileRestoreContext>( GetProcAddress( hLib, "CloseFileRestoreContext" ) );

    // Set the flags value for which kind of items we want to scan for
    RESTORE_CONTEXT_FLAGS flags;
    flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
    switch (argc)
    {
        case 2:
        {
            if (wcslen(argv[1]) < (sizeof(VOLUME) / sizeof(VOLUME[0]))) // ensure that argv[1] is not >255 chars long
            {
                wcscpy_s(VOLUME, argv[1]);
            }
            else
            {
                wcscpy_s(VOLUME, 255, argv[1]);
            }
            wcout << L"Volume set to: " << VOLUME << L"\n";
            wcout << L"Defaulting to VOLUME search type; setting flags accordingly..." << L"\n";
            flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
            break;
        }
        case 3:
        {
            if (wcslen(argv[1]) < (sizeof(VOLUME) / sizeof(VOLUME[0]))) // ensure that argv[1] is not >255 chars long
            {
                wcscpy_s(VOLUME, argv[1]);
            }
            else // if it is, only copy the first 255 chars
            {
                wcscpy_s(VOLUME, 255, argv[1]);
            }
            int len;
            // Get length of second argument passed by the user
            len = wcslen(argv[2]);
            // Allocate new wchar_t array big enough to hold it
            wchar_t *tmpArg = new (nothrow) wchar_t[len + 1];
            // Ensure that the memory was allocated successfully
            if (tmpArg == nullptr)
            {
                // Error allocating memory - bail out
                wcout << L"Error allocating memory! Bailing out..." << L"\n";
                return;
            }
            // Copy the argument string from argv to tmpArg
            wcscpy_s(tmpArg, len + 1, argv[2]);
            // Convert to uppercase
            _wcsupr_s(tmpArg, len + 1);
            // Check whether VOLUME or DISK search type was specified and set flags accordingly
            if (wcscmp(tmpArg, L"VOLUME") == 0)
            {
                wcout << L"Volume set to: " << VOLUME << L"\n";
                if (wcschr(VOLUME, L':') != NULL)
                {
                    wcout << L"VOLUME search type specified; setting flags accordingly..." << L"\n";
                    flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
                }
                else
                {
                    wcout << L"VOLUME search type specified, but the volume identifier given doesn't appear " <<
                        L"to be a valid mounted volume! Please check your arguments and try again." << L"\n";
                    delete [] tmpArg;
                    return;
                }
            }
            else if (wcscmp(tmpArg, L"DISK") == 0)
            {
                wcout << L"Disk set to: " << VOLUME << L"\n";
                if (wcschr(VOLUME, L':') == NULL)
                {
                    wcout << L"DISK search type specified; setting flags accordingly..." << L"\n";
                    flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagDisk | FlagScanRemovedFiles);
                }
                else
                {
                    wcout << L"DISK search type specified, but the disk identifier given doesn't appear " <<
                        L"to be a valid physical disk! Please check your arguments and try again." << L"\n";
                    delete [] tmpArg;
                    return;
                }
            }
            else
            {
                wcout << L"UNKNOWN search type specified! Defaulting to VOLUME; setting flags accordingly..." << L"\n";
                flags = (RESTORE_CONTEXT_FLAGS)(ContextFlagVolume | FlagScanRemovedFiles);
            }
            delete [] tmpArg;
            break;
        }
        default:
        {
            // Set the default volume to scan here
            wcscpy_s(VOLUME, L"\\\\.\\D:");
            wcout << L"No arguments specified! Defaulting to VOLUME search of \\\\.\\D:" << L"\n";
            break;
        }
    }

    // Create the FileRestoreContext
    PFILE_RESTORE_CONTEXT context = NULL;
    if ( ! CreateFileRestoreContext(VOLUME, flags, 0, 0, FILE_RESTORE_VERSION_2, &context) )
    {        
        DWORD err = GetLastError();        
        wprintf(L"Failed to Create FileRestoreContext, Error #%u.\n", err);
        return;
    }
    else
    {
        wcout << L"Success! File restore context created! Value of context is: " << context << L"\n";
    }

    // Find restorable files starting at the given directory
    Scan(context, SCAN_PATH);

    // Close the context
    if (context)
    {
        CloseFileRestoreContext(context);
        context = NULL;
    }
}

Please note this is still a very rudimentary app in its beginning stages. It basically begins a scan, defaulting (if no args are given) to scanning the D:\ drive and attempting to restore a deleted file to X:\testfile.txt. For those that may not be familiar with WinPE, X: is the system drive in a WinPE environment, and D: is usually the letter assigned to the OS drive of the machine that is booted to PE while in the PE environment, depending on the setup, with C: being the system reserved partition.

For each restorable file found, it pauses, and if the user enters the text "RESTORE" and hits ENTER, it will attempt to restore the file (this takes place around line 247). The callback function that indicates status for the restoration returns error code 6 (via FinalResult), as does GetLastError().

Any help on this is very much appreciated!

c++
asked on Stack Overflow Jul 16, 2018 by dwillis77

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0