reactos/modules/rostests/rosautotest/CJournaledTestList.cpp
Pierre Schweitzer 83d8fbf434 [ROSAUTOTEST] Don't disallow buffering for journal file
This fixes back journal in ReactOS "at low costs". Indeed,
because write are improperly aligned right now, journaling
just fails.
With that patch, Cc will take care of aligning writes and
journal will be written again. Because flush operations
happen at each and every write to the journal, we expect
changes to land on disk quickly (not as quickly as if
they were directly written). But that's a good trade off
between over engineering and fixing a broken feature.

CORE-15973
2019-05-04 11:54:39 +02:00

304 lines
8.3 KiB
C++

/*
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: Class implementing a journaled test list for the Crash Recovery feature
* COPYRIGHT: Copyright 2009 Colin Finck (colin@reactos.org)
*/
#include "precomp.h"
static const char szJournalHeader[] = "RAT_J-V1";
static const WCHAR szJournalFileName[] = L"rosautotest.journal";
/**
* Constructs a CJournaledTestList object for an associated CTest-derived object.
*
* @param Test
* Pointer to a CTest-derived object, for which this test list shall serve.
*/
CJournaledTestList::CJournaledTestList(CTest* Test)
: CTestList(Test)
{
WCHAR JournalFile[MAX_PATH];
m_hJournal = INVALID_HANDLE_VALUE;
/* Build the path to the journal file */
if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, JournalFile) != S_OK)
FATAL("SHGetFolderPathW failed\n");
m_JournalFile = JournalFile;
m_JournalFile += L"\\rosautotest\\";
/* Create the directory if necessary */
if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES)
CreateDirectoryW(m_JournalFile.c_str(), NULL);
m_JournalFile += szJournalFileName;
/* Check if the journal already exists */
if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES)
WriteInitialJournalFile();
else
LoadJournalFile();
}
/**
* Destructs a CJournaledTestList object.
*/
CJournaledTestList::~CJournaledTestList()
{
if(m_hJournal != INVALID_HANDLE_VALUE)
CloseHandle(m_hJournal);
}
/**
* Opens the journal file through the CreateFileW API using the m_hJournal handle.
*
* @param DesiredAccess
* dwDesiredAccess parameter passed to CreateFileW
*
* @param CreateNew
* true if the journal file shall be created, false if an existing one shall be opened
*/
void
CJournaledTestList::OpenJournal(DWORD DesiredAccess, bool CreateNew)
{
m_hJournal = CreateFileW(
m_JournalFile.c_str(),
DesiredAccess,
0,
NULL,
(CreateNew ? CREATE_ALWAYS : OPEN_EXISTING),
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
NULL
);
if(m_hJournal == INVALID_HANDLE_VALUE)
FATAL("CreateFileW failed\n");
}
/**
* Serializes a std::string and writes it into the opened journal file.
*
* @param String
* The std::string to serialize
*
* @see UnserializeFromBuffer
*/
void
CJournaledTestList::SerializeIntoJournal(const string& String)
{
DWORD BytesWritten;
WriteFile(m_hJournal, String.c_str(), String.size() + 1, &BytesWritten, NULL);
FlushFileBuffers(m_hJournal);
}
/**
* Serializes a std::wstring and writes it into the opened journal file.
*
* @param String
* The std::wstring to serialize
*
* @see UnserializeFromBuffer
*/
void
CJournaledTestList::SerializeIntoJournal(const wstring& String)
{
DWORD BytesWritten;
WriteFile(m_hJournal, String.c_str(), (String.size() + 1) * sizeof(WCHAR), &BytesWritten, NULL);
FlushFileBuffers(m_hJournal);
}
/**
* Unserializes the next std::string from the journal buffer.
* The passed buffer pointer will point at the next element afterwards.
*
* @param Buffer
* Pointer to a pointer to a char array containing the journal buffer
*
* @param Output
* The std::string to unserialize the value into.
*/
void
CJournaledTestList::UnserializeFromBuffer(char** Buffer, string& Output)
{
Output = string(*Buffer);
*Buffer += Output.size() + 1;
}
/**
* Unserializes the next std::wstring from the journal buffer.
* The passed buffer pointer will point at the next element afterwards.
*
* @param Buffer
* Pointer to a pointer to a char array containing the journal buffer
*
* @param Output
* The std::wstring to unserialize the value into.
*/
void
CJournaledTestList::UnserializeFromBuffer(char** Buffer, wstring& Output)
{
Output = wstring((PWSTR)*Buffer);
*Buffer += (Output.size() + 1) * sizeof(WCHAR);
}
/**
* Gets all tests to be run and writes an initial journal file with this information.
*/
void
CJournaledTestList::WriteInitialJournalFile()
{
char TerminatingNull = 0;
CTestInfo* TestInfo;
DWORD BytesWritten;
StringOut("Writing initial journal file...\n\n");
m_ListIterator = 0;
/* Store all command lines in the m_List vector */
while((TestInfo = m_Test->GetNextTestInfo()) != 0)
{
m_List.push_back(*TestInfo);
delete TestInfo;
}
/* Serialize the vector and the iterator into a file */
OpenJournal(GENERIC_WRITE, true);
WriteFile(m_hJournal, szJournalHeader, sizeof(szJournalHeader), &BytesWritten, NULL);
WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL);
for(size_t i = 0; i < m_List.size(); i++)
{
SerializeIntoJournal(m_List[i].CommandLine);
SerializeIntoJournal(m_List[i].Module);
SerializeIntoJournal(m_List[i].Test);
}
WriteFile(m_hJournal, &TerminatingNull, sizeof(TerminatingNull), &BytesWritten, NULL);
FlushFileBuffers(m_hJournal);
CloseHandle(m_hJournal);
m_hJournal = INVALID_HANDLE_VALUE;
/* m_ListIterator will be incremented before its first use */
m_ListIterator = (size_t)-1;
}
/**
* Loads the existing journal file and sets all members to the values saved in that file.
*/
void
CJournaledTestList::LoadJournalFile()
{
char* Buffer;
char* pBuffer;
char FileHeader[sizeof(szJournalHeader)];
DWORD BytesRead;
DWORD RemainingSize;
StringOut("Loading journal file...\n\n");
OpenJournal(GENERIC_READ);
RemainingSize = GetFileSize(m_hJournal, NULL);
/* Verify the header of the journal file */
ReadFile(m_hJournal, FileHeader, sizeof(szJournalHeader), &BytesRead, NULL);
RemainingSize -= BytesRead;
if(BytesRead != sizeof(szJournalHeader))
EXCEPTION("Journal file contains no header!\n");
if(strcmp(FileHeader, szJournalHeader))
EXCEPTION("Journal file has an unsupported header!\n");
/* Read the iterator */
ReadFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesRead, NULL);
RemainingSize -= BytesRead;
if(BytesRead != sizeof(m_ListIterator))
EXCEPTION("Journal file contains no m_ListIterator member!\n");
/* Read the rest of the file into a buffer */
Buffer = new char[RemainingSize];
pBuffer = Buffer;
ReadFile(m_hJournal, Buffer, RemainingSize, &BytesRead, NULL);
CloseHandle(m_hJournal);
m_hJournal = NULL;
/* Now recreate the m_List vector out of that information */
while(*pBuffer)
{
CTestInfo TestInfo;
UnserializeFromBuffer(&pBuffer, TestInfo.CommandLine);
UnserializeFromBuffer(&pBuffer, TestInfo.Module);
UnserializeFromBuffer(&pBuffer, TestInfo.Test);
m_List.push_back(TestInfo);
}
delete[] Buffer;
}
/**
* Writes the current m_ListIterator value into the journal.
*/
void
CJournaledTestList::UpdateJournal()
{
DWORD BytesWritten;
OpenJournal(GENERIC_WRITE);
/* Skip the header */
SetFilePointer(m_hJournal, sizeof(szJournalHeader), NULL, FILE_CURRENT);
WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL);
FlushFileBuffers(m_hJournal);
CloseHandle(m_hJournal);
m_hJournal = NULL;
}
/**
* Interface to other classes for receiving information about the next test to be run.
*
* @return
* A pointer to a CTestInfo object, which contains all available information about the next test.
* The caller needs to free that object.
*/
CTestInfo*
CJournaledTestList::GetNextTestInfo()
{
CTestInfo* TestInfo;
/* Always jump to the next test here.
- If we're at the beginning of a new test list, the iterator will be set to 0.
- If we started with a loaded one, we assume that the test m_ListIterator is currently set
to crashed, so we move to the next test. */
++m_ListIterator;
/* Check whether the iterator would already exceed the number of stored elements */
if(m_ListIterator == m_List.size())
{
/* Delete the journal and return no pointer */
DeleteFileW(m_JournalFile.c_str());
TestInfo = NULL;
}
else
{
/* Update the journal with the current iterator and return the test information */
UpdateJournal();
TestInfo = new CTestInfo(m_List[m_ListIterator]);
}
return TestInfo;
}