1 /* 2 * PROJECT: ReactOS Automatic Testing Utility 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Class implementing a journaled test list for the Crash Recovery feature 5 * COPYRIGHT: Copyright 2009 Colin Finck (colin@reactos.org) 6 */ 7 8 #include "precomp.h" 9 10 static const char szJournalHeader[] = "RAT_J-V1"; 11 static const WCHAR szJournalFileName[] = L"rosautotest.journal"; 12 13 /** 14 * Constructs a CJournaledTestList object for an associated CTest-derived object. 15 * 16 * @param Test 17 * Pointer to a CTest-derived object, for which this test list shall serve. 18 */ 19 CJournaledTestList::CJournaledTestList(CTest* Test) 20 : CTestList(Test) 21 { 22 WCHAR JournalFile[MAX_PATH]; 23 24 m_hJournal = INVALID_HANDLE_VALUE; 25 26 /* Build the path to the journal file */ 27 if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, JournalFile) != S_OK) 28 FATAL("SHGetFolderPathW failed\n"); 29 30 m_JournalFile = JournalFile; 31 m_JournalFile += L"\\rosautotest\\"; 32 33 /* Create the directory if necessary */ 34 if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES) 35 CreateDirectoryW(m_JournalFile.c_str(), NULL); 36 37 m_JournalFile += szJournalFileName; 38 39 /* Check if the journal already exists */ 40 if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES) 41 WriteInitialJournalFile(); 42 else 43 LoadJournalFile(); 44 } 45 46 /** 47 * Destructs a CJournaledTestList object. 48 */ 49 CJournaledTestList::~CJournaledTestList() 50 { 51 if(m_hJournal != INVALID_HANDLE_VALUE) 52 CloseHandle(m_hJournal); 53 } 54 55 /** 56 * Opens the journal file through the CreateFileW API using the m_hJournal handle. 57 * 58 * @param DesiredAccess 59 * dwDesiredAccess parameter passed to CreateFileW 60 * 61 * @param CreateNew 62 * true if the journal file shall be created, false if an existing one shall be opened 63 */ 64 void 65 CJournaledTestList::OpenJournal(DWORD DesiredAccess, bool CreateNew) 66 { 67 m_hJournal = CreateFileW( 68 m_JournalFile.c_str(), 69 DesiredAccess, 70 0, 71 NULL, 72 (CreateNew ? CREATE_ALWAYS : OPEN_EXISTING), 73 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, 74 NULL 75 ); 76 77 if(m_hJournal == INVALID_HANDLE_VALUE) 78 FATAL("CreateFileW failed\n"); 79 } 80 81 /** 82 * Serializes a std::string and writes it into the opened journal file. 83 * 84 * @param String 85 * The std::string to serialize 86 * 87 * @see UnserializeFromBuffer 88 */ 89 void 90 CJournaledTestList::SerializeIntoJournal(const string& String) 91 { 92 DWORD BytesWritten; 93 WriteFile(m_hJournal, String.c_str(), String.size() + 1, &BytesWritten, NULL); 94 FlushFileBuffers(m_hJournal); 95 } 96 97 /** 98 * Serializes a std::wstring and writes it into the opened journal file. 99 * 100 * @param String 101 * The std::wstring to serialize 102 * 103 * @see UnserializeFromBuffer 104 */ 105 void 106 CJournaledTestList::SerializeIntoJournal(const wstring& String) 107 { 108 DWORD BytesWritten; 109 WriteFile(m_hJournal, String.c_str(), (String.size() + 1) * sizeof(WCHAR), &BytesWritten, NULL); 110 FlushFileBuffers(m_hJournal); 111 } 112 113 /** 114 * Unserializes the next std::string from the journal buffer. 115 * The passed buffer pointer will point at the next element afterwards. 116 * 117 * @param Buffer 118 * Pointer to a pointer to a char array containing the journal buffer 119 * 120 * @param Output 121 * The std::string to unserialize the value into. 122 */ 123 void 124 CJournaledTestList::UnserializeFromBuffer(char** Buffer, string& Output) 125 { 126 Output = string(*Buffer); 127 *Buffer += Output.size() + 1; 128 } 129 130 /** 131 * Unserializes the next std::wstring from the journal buffer. 132 * The passed buffer pointer will point at the next element afterwards. 133 * 134 * @param Buffer 135 * Pointer to a pointer to a char array containing the journal buffer 136 * 137 * @param Output 138 * The std::wstring to unserialize the value into. 139 */ 140 void 141 CJournaledTestList::UnserializeFromBuffer(char** Buffer, wstring& Output) 142 { 143 Output = wstring((PWSTR)*Buffer); 144 *Buffer += (Output.size() + 1) * sizeof(WCHAR); 145 } 146 147 /** 148 * Gets all tests to be run and writes an initial journal file with this information. 149 */ 150 void 151 CJournaledTestList::WriteInitialJournalFile() 152 { 153 char TerminatingNull = 0; 154 CTestInfo* TestInfo; 155 DWORD BytesWritten; 156 157 StringOut("Writing initial journal file...\n\n"); 158 159 m_ListIterator = 0; 160 161 /* Store all command lines in the m_List vector */ 162 while((TestInfo = m_Test->GetNextTestInfo()) != 0) 163 { 164 m_List.push_back(*TestInfo); 165 delete TestInfo; 166 } 167 168 /* Serialize the vector and the iterator into a file */ 169 OpenJournal(GENERIC_WRITE, true); 170 171 WriteFile(m_hJournal, szJournalHeader, sizeof(szJournalHeader), &BytesWritten, NULL); 172 WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL); 173 174 for(size_t i = 0; i < m_List.size(); i++) 175 { 176 SerializeIntoJournal(m_List[i].CommandLine); 177 SerializeIntoJournal(m_List[i].Module); 178 SerializeIntoJournal(m_List[i].Test); 179 } 180 181 WriteFile(m_hJournal, &TerminatingNull, sizeof(TerminatingNull), &BytesWritten, NULL); 182 FlushFileBuffers(m_hJournal); 183 184 CloseHandle(m_hJournal); 185 m_hJournal = INVALID_HANDLE_VALUE; 186 187 /* m_ListIterator will be incremented before its first use */ 188 m_ListIterator = (size_t)-1; 189 } 190 191 /** 192 * Loads the existing journal file and sets all members to the values saved in that file. 193 */ 194 void 195 CJournaledTestList::LoadJournalFile() 196 { 197 char* Buffer; 198 char* pBuffer; 199 char FileHeader[sizeof(szJournalHeader)]; 200 DWORD BytesRead; 201 DWORD RemainingSize; 202 203 StringOut("Loading journal file...\n\n"); 204 205 OpenJournal(GENERIC_READ); 206 RemainingSize = GetFileSize(m_hJournal, NULL); 207 208 /* Verify the header of the journal file */ 209 ReadFile(m_hJournal, FileHeader, sizeof(szJournalHeader), &BytesRead, NULL); 210 RemainingSize -= BytesRead; 211 212 if(BytesRead != sizeof(szJournalHeader)) 213 EXCEPTION("Journal file contains no header!\n"); 214 215 if(strcmp(FileHeader, szJournalHeader)) 216 EXCEPTION("Journal file has an unsupported header!\n"); 217 218 /* Read the iterator */ 219 ReadFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesRead, NULL); 220 RemainingSize -= BytesRead; 221 222 if(BytesRead != sizeof(m_ListIterator)) 223 EXCEPTION("Journal file contains no m_ListIterator member!\n"); 224 225 /* Read the rest of the file into a buffer */ 226 Buffer = new char[RemainingSize]; 227 pBuffer = Buffer; 228 ReadFile(m_hJournal, Buffer, RemainingSize, &BytesRead, NULL); 229 230 CloseHandle(m_hJournal); 231 m_hJournal = NULL; 232 233 /* Now recreate the m_List vector out of that information */ 234 while(*pBuffer) 235 { 236 CTestInfo TestInfo; 237 238 UnserializeFromBuffer(&pBuffer, TestInfo.CommandLine); 239 UnserializeFromBuffer(&pBuffer, TestInfo.Module); 240 UnserializeFromBuffer(&pBuffer, TestInfo.Test); 241 242 m_List.push_back(TestInfo); 243 } 244 245 delete[] Buffer; 246 } 247 248 /** 249 * Writes the current m_ListIterator value into the journal. 250 */ 251 void 252 CJournaledTestList::UpdateJournal() 253 { 254 DWORD BytesWritten; 255 256 OpenJournal(GENERIC_WRITE); 257 258 /* Skip the header */ 259 SetFilePointer(m_hJournal, sizeof(szJournalHeader), NULL, FILE_CURRENT); 260 261 WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL); 262 FlushFileBuffers(m_hJournal); 263 264 CloseHandle(m_hJournal); 265 m_hJournal = NULL; 266 } 267 268 /** 269 * Interface to other classes for receiving information about the next test to be run. 270 * 271 * @return 272 * A pointer to a CTestInfo object, which contains all available information about the next test. 273 * The caller needs to free that object. 274 */ 275 CTestInfo* 276 CJournaledTestList::GetNextTestInfo() 277 { 278 CTestInfo* TestInfo; 279 280 /* Always jump to the next test here. 281 - If we're at the beginning of a new test list, the iterator will be set to 0. 282 - If we started with a loaded one, we assume that the test m_ListIterator is currently set 283 to crashed, so we move to the next test. */ 284 ++m_ListIterator; 285 286 /* Check whether the iterator would already exceed the number of stored elements */ 287 if(m_ListIterator == m_List.size()) 288 { 289 /* Delete the journal and return no pointer */ 290 DeleteFileW(m_JournalFile.c_str()); 291 292 TestInfo = NULL; 293 } 294 else 295 { 296 /* Update the journal with the current iterator and return the test information */ 297 UpdateJournal(); 298 299 TestInfo = new CTestInfo(m_List[m_ListIterator]); 300 } 301 302 return TestInfo; 303 } 304