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