reactos/reactos/tools/sysreg/rosboot_test.cpp
Johannes Anderwald b57a996929 - hardcode the bios directory on linux system to /usr/share/qemu
- store the pid.txt in ROS_OUTPUT
- check on linux system if the emulator was successfully launched

svn path=/trunk/; revision=28787
2007-09-02 21:11:12 +00:00

884 lines
23 KiB
C++

/* $Id$
*
* PROJECT: System regression tool for ReactOS
* LICENSE: GPL - See COPYING in the top level directory
* FILE: tools/sysreg/conf_parser.h
* PURPOSE: ReactOS boot test
* PROGRAMMERS: Johannes Anderwald (johannes.anderwald at sbox tugraz at)
*/
#include "rosboot_test.h"
#include "pipe_reader.h"
#include "namedpipe_reader.h"
//#include "sym_file.h"
#include "file_reader.h"
#include "os_support.h"
#include "env_var.h"
#include <iostream>
#include <vector>
#include <fstream>
#include <time.h>
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <signal.h>
namespace Sysreg_
{
using std::vector;
using System_::PipeReader;
using System_::NamedPipeReader;
/* using System_::SymbolFile; */
using System_::FileReader;
using System_::OsSupport;
using System_::EnvironmentVariable;
#ifdef UNICODE
using std::wofstream;
typedef wofstream ofstream;
#else
using std::ofstream;
#endif
string RosBootTest::ROS_EMU_TYPE= _T("ROS_EMU_TYPE");
string RosBootTest::EMU_TYPE_QEMU = _T("qemu");
string RosBootTest::EMU_TYPE_VMWARE = _T("vmware");
string RosBootTest::ROS_EMU_PATH = _T("ROS_EMU_PATH");
string RosBootTest::ROS_HDD_IMAGE= _T("ROS_HDD_IMAGE");
string RosBootTest::ROS_CD_IMAGE = _T("ROS_CD_IMAGE");
string RosBootTest::ROS_MAX_TIME = _T("ROS_MAX_TIME");
string RosBootTest::ROS_LOG_FILE = _T("ROS_LOG_FILE");
string RosBootTest::ROS_SYM_DIR = _T("ROS_SYM_DIR");
string RosBootTest::ROS_DELAY_READ = _T("ROS_DELAY_READ");
string RosBootTest::ROS_SYSREG_CHECKPOINT = _T("SYSREG_CHECKPOINT:");
string RosBootTest::ROS_CRITICAL_IMAGE = _T("ROS_CRITICAL_IMAGE");
string RosBootTest::ROS_EMU_KILL = _T("ROS_EMU_KILL");
string RosBootTest::ROS_EMU_MEM = _T("ROS_EMU_MEM");
string RosBootTest::ROS_BOOT_CMD = _T("ROS_BOOT_CMD");
//---------------------------------------------------------------------------------------
RosBootTest::RosBootTest() : m_MaxTime(0.0), m_DelayRead(0)
{
}
//---------------------------------------------------------------------------------------
RosBootTest::~RosBootTest()
{
}
//---------------------------------------------------------------------------------------
bool RosBootTest::executeBootCmd()
{
m_Pid = OsSupport::createProcess ((TCHAR*)m_BootCmd.c_str(), 0, NULL, false);
if (!m_Pid)
{
cerr << "Error: failed to launch boot cmd" << m_BootCmd << endl;
return false;
}
return true;
}
//---------------------------------------------------------------------------------------
void RosBootTest::delayRead()
{
if (m_DelayRead)
{
///
/// delay reading until emulator is ready
///
OsSupport::sleep(m_DelayRead);
}
}
//---------------------------------------------------------------------------------------
void RosBootTest::getDefaultHDDImage(string & img)
{
img = "output-i386";
EnvironmentVariable::getValue(_T("ROS_OUTPUT"), img);
#ifdef __LINUX__
img += _T("/ros.hd");
#else
img += _T("\\ros.hd");
#endif
}
//---------------------------------------------------------------------------------------
bool RosBootTest::isFileExisting(string output)
{
FILE * file;
file = _tfopen(output.c_str(), _T("r"));
if (file)
{
/* the file exists */
fclose(file);
return true;
}
return false;
}
//---------------------------------------------------------------------------------------
bool RosBootTest::isDefaultHDDImageExisting()
{
string img;
getDefaultHDDImage(img);
return isFileExisting(img);
}
//---------------------------------------------------------------------------------------
bool RosBootTest::createDefaultHDDImage()
{
string qemuimgdir;
string output;
if (!getQemuDir(qemuimgdir))
{
return false;
}
#ifdef __LINUX__
qemuimgdir += _T("/qemu-img");
#else
qemuimgdir += _T("\\qemu-img.exe");
#endif
if (!isFileExisting(qemuimgdir))
{
cerr << "Error: ROS_EMU_PATH must contain the path to qemu and qemu-img " << qemuimgdir << endl;
return false;
}
TCHAR * options[] = { NULL,
_T("create"),
NULL,
_T("100M"),
NULL
};
getDefaultHDDImage(output);
options[0] = (TCHAR*)qemuimgdir.c_str();
options[2] = (TCHAR*)output.c_str();
cerr << "Creating HDD Image ..." << output << endl;
if (OsSupport::createProcess ((TCHAR*)qemuimgdir.c_str(), 4, options, true))
{
m_HDDImage = output;
return true;
}
return false;
}
//----------------------------------------------------------------------------------------
bool RosBootTest::isQemuPathValid()
{
string qemupath;
if (m_BootCmd.length())
{
/* the boot cmd is already provided
* check if path to qemu is valid
*/
string::size_type pos = m_BootCmd.find_first_of(_T(" "));
if (pos == string::npos)
{
/* the bootcmd is certainly not valid */
return false;
}
qemupath = m_BootCmd.substr(0, pos);
}
else
{
/* the qemu path is provided ROS_EMU_PATH variable */
qemupath = m_EmuPath;
}
return isFileExisting(qemupath);
}
//----------------------------------------------------------------------------------------
bool RosBootTest::getQemuDir(string & qemupath)
{
string::size_type pos;
#ifdef __LINUX__
pos = m_EmuPath.find_last_of(_T("/"));
#else
pos = m_EmuPath.find_last_of(_T("\\"));
#endif
if (pos == string::npos)
{
cerr << "Error: ROS_EMU_PATH is invalid!!!" << endl;
return false;
}
qemupath = m_EmuPath.substr(0, pos);
return true;
}
//----------------------------------------------------------------------------------------
bool RosBootTest::createBootCmd()
{
string pipe;
string qemudir;
if (m_MaxMem.length() == 0)
{
/* set default memory size to 64M */
m_MaxMem = "64";
}
#ifdef __LINUX__
pipe = _T("stdio");
m_Src = _T("");
qemudir = _T("/usr/share/qemu");
#else
pipe = _T("pipe:qemu");
m_Src = _T("\\\\.\\pipe\\qemu");
if (!getQemuDir(qemudir))
{
return false;
}
#endif
m_BootCmd = m_EmuPath + _T(" -L ") + qemudir + _T(" -m ") + m_MaxMem + _T(" -hda ") + m_HDDImage + _T(" -serial ") + pipe;
if (m_CDImage.length())
{
/* boot from cdrom */
m_BootCmd += _T(" -boot d -cdrom ") + m_CDImage;
}
else
{
/* boot from hdd */
m_BootCmd += _T(" -boot c ");
}
#ifdef __LINUX__
/* on linux we need get pid in order to be able
* to terminate the emulator in case of errors
* on windows we can get pid as return of CreateProcess
*/
m_PidFile = _T("output-i386");
EnvironmentVariable::getValue(_T("ROS_OUTPUT"), pid);
m_PidFile += _T("/pid.txt");
m_BootCmd += _T(" -pidfile ");
m_BootCmd += m_PidFile;
#endif
m_BootCmd += _T(" -no-reboot ");
return true;
}
//----------------------------------------------------------------------------------------
bool RosBootTest::extractPipeFromBootCmd()
{
string::size_type pos = m_BootCmd.find(_T("-serial"));
if (pos == string::npos)
{
/* no debug options provided */
return false;
}
string pipe = m_BootCmd.substr(pos + 7, m_BootCmd.size() - pos -7);
pos = pipe.find(_T("pipe:"));
if (pos == 0)
{
#ifdef __LINUX__
cerr << "Error: reading from pipes is not supported with linux hosts - use stdio" << endl;
return false;
}
#else
pipe = pipe.substr(pos + 5, pipe.size() - pos - 5);
pos = pipe.find(_T(" "));
if (pos != string::npos)
{
m_Src = _T("\\\\.\\pipe\\") + pipe.substr(0, pos);
}
else
{
m_Src = _T("\\\\.\\pipe\\") + pipe;
}
return true;
}
#endif
pos = pipe.find(_T("stdio"));
if (pos == 0)
{
#ifdef __LINUX__
m_Src = m_BootCmd;
return true;
#else
cerr << "Error: reading from stdio is not supported for windows hosts - use pipes" << endl;
return false;
#endif
}
cerr << "Error: no valid debug port specified - use stdio / pipes" << endl;
return false;
}
//----------------------------------------------------------------------------------------
bool RosBootTest::configureHDDImage()
{
if (m_HDDImage.length())
{
/* check if ROS_HDD_IMAGE points to hdd image */
return isFileExisting(m_HDDImage);
}
if (isDefaultHDDImageExisting())
{
/* ROS_HDD_IMAGE is not set but theres
* a default existing image
* to use */
getDefaultHDDImage(m_HDDImage);
return true;
}
if (!createDefaultHDDImage())
{
/* failed to create hdd image */
cerr << "Error: failed to create hdd image " << endl;
return false;
}
getDefaultHDDImage(m_HDDImage);
return true;
///
/// FIXME
/// scan m_BootCmd if theres -hda param provided
/// and check if it exists
return true;
}
//----------------------------------------------------------------------------------------
bool RosBootTest::configureCDImage()
{
if (m_CDImage.length())
{
/* we have a cd image lets check if its valid */
if (!isFileExisting(m_CDImage))
{
cerr << "Error: ROS_CD_IMAGE is not valid" << endl;
return false;
}
return true;
}
/* ROS_CD_IMAGE is not set
* lets check if m_BootCmd provides it
*/
if (!m_BootCmd.length())
{
/* ROS_BOOT_CMD not set
* check if theres a default image
*/
if (isFileExisting(_T("ReactOS-RegTest.iso")))
{
m_CDImage = _T("ReactOS-RegTest.iso");
}
return true;
}
string::size_type pos = m_BootCmd.find(_T("-boot "));
if (pos == string::npos)
{
/* ROS_BOOT_CMD must provide a boot parameter*/
cerr << "Error: ROS_BOOT_CMD misses boot parameter" << endl;
return false;
}
string rest = m_BootCmd.substr(pos + 6, m_BootCmd.length() - pos - 6);
if (rest.length() < 1)
{
/* boot parameter misses option where to boot from */
cerr << "Error: ROS_BOOT_CMD misses boot parameter" << endl;
return false;
}
if (rest[0] != _T('c') && rest[0] != _T('d'))
{
cerr << "Error: ROS_BOOT_CMD has invalid boot parameter" << endl;
return false;
}
if (rest[0] == _T('c'))
{
/* ROS_BOOT_CMD boots from hdd */
return true;
}
pos = m_BootCmd.find(_T("-cdrom "));
if (pos == string::npos)
{
cerr << "Error: ROS_BOOT_CMD misses cdrom parameter" << endl;
return false;
}
rest = m_BootCmd.substr(pos + 7, m_BootCmd.length() - pos - 7);
if (!rest.length())
{
cerr << "Error: ROS_BOOT_CMD misses cdrom parameter" << endl;
return false;
}
pos = rest.find(_T(" "));
if (pos != string::npos)
{
rest = rest.substr(0, pos);
}
if (!isFileExisting(rest))
{
cerr << "Error: cdrom image " << rest << " does not exist" << endl;
return false;
}
else
{
m_CDImage = rest;
return true;
}
}
//----------------------------------------------------------------------------------------
bool RosBootTest::configureQemu()
{
if (!isQemuPathValid())
{
cerr << "Error: the path to qemu is not valid" << endl;
return false;
}
if (!configureCDImage())
{
cerr << "Error: failed to set a valid cdrom configuration" << endl;
return false;
}
if (!configureHDDImage())
{
cerr << "Error: failed to set a valid hdd configuration" << endl;
return false;
}
if (m_BootCmd.length())
{
if (!extractPipeFromBootCmd())
{
return false;
}
}
else
{
if (!createBootCmd())
{
return false;
}
}
cerr << "Opening Data Source:" << m_BootCmd << endl;
#ifdef __LINUX__
_tremove(m_PidFile.c_str ());
m_DataSource = new PipeReader();
m_Src = m_BootCmd;
#else
m_DataSource = new NamedPipeReader();
if (!executeBootCmd())
{
cerr << "Error: failed to launch emulator with: " << m_BootCmd << endl;
return false;
}
#endif
return true;
}
//---------------------------------------------------------------------------------------
bool RosBootTest::configureVmWare()
{
cerr << "VmWare is currently not yet supported" << endl;
return false;
}
//---------------------------------------------------------------------------------------
bool RosBootTest::readConfigurationValues(ConfigParser &conf_parser)
{
#if 0
if (!conf_parser.getStringValue(RosBootTest::ROS_EMU_TYPE, m_EmuType))
{
cerr << "Error: ROS_EMU_TYPE is not set" << endl;
return false;
}
#endif
if (!conf_parser.getStringValue(RosBootTest::ROS_EMU_PATH, m_EmuPath))
{
cerr << "Error: ROS_EMU_PATH is not set" << endl;
return false;
}
conf_parser.getStringValue (RosBootTest::ROS_HDD_IMAGE, m_HDDImage);
conf_parser.getStringValue (RosBootTest::ROS_CD_IMAGE, m_CDImage);
conf_parser.getDoubleValue (RosBootTest::ROS_MAX_TIME, m_MaxTime);
conf_parser.getStringValue (RosBootTest::ROS_LOG_FILE, m_DebugFile);
conf_parser.getStringValue (RosBootTest::ROS_SYM_DIR, m_SymDir);
conf_parser.getIntValue (RosBootTest::ROS_DELAY_READ, m_DelayRead);
conf_parser.getStringValue (RosBootTest::ROS_SYSREG_CHECKPOINT, m_Checkpoint);
conf_parser.getStringValue (RosBootTest::ROS_CRITICAL_IMAGE, m_CriticalImage);
conf_parser.getStringValue (RosBootTest::ROS_EMU_KILL, m_KillEmulator);
conf_parser.getStringValue (RosBootTest::ROS_EMU_MEM, m_MaxMem);
conf_parser.getStringValue (RosBootTest::ROS_BOOT_CMD, m_BootCmd);
return true;
}
//---------------------------------------------------------------------------------------
void RosBootTest::cleanup()
{
m_DataSource->closeSource();
OsSupport::sleep(3 * CLOCKS_PER_SEC);
if (m_Pid)
{
OsSupport::terminateProcess (m_Pid);
}
delete m_DataSource;
m_DataSource = NULL;
}
//---------------------------------------------------------------------------------------
bool RosBootTest::execute(ConfigParser &conf_parser)
{
if (!readConfigurationValues(conf_parser))
{
return false;
}
#if 0
if (m_EmuType == EMU_TYPE_QEMU)
{
if (!configureQemu())
{
cerr << "Error: failed to configure qemu" << endl;
return false;
}
}
else if (m_EmuType == EMU_TYPE_VMWARE)
{
if (!configureVmWare())
{
cerr << "Error: failed to configure vmware" << endl;
return false;
}
}
else
{
///
/// unsupported emulator
cerr << "Error: ROS_EMU_TYPE value is not supported:" << m_EmuType << "=" << EMU_TYPE_QEMU << endl;
return false;
}
#else
if (!configureQemu())
{
cerr << "Error: failed to configure qemu" << endl;
return false;
}
#endif
#ifndef __LINUX__
OsSupport::sleep(500);
#endif
assert(m_DataSource != 0);
if (!m_DataSource->openSource(m_Src))
{
cerr << "Error: failed to open data source with " << m_Src << endl;
cleanup();
return false;
}
OsSupport::sleep(1000);
#ifdef __LINUX__
FILE * file = fopen(m_PidFile.c_str(), "r");
if (!file)
{
cerr << "Error: failed to launch emulator" << endl;
cleanup();
return false;
}
char buffer[128];
if (!fread(buffer, sizeof(buffer), 1, file))
{
cerr << "Error: pid file w/o pid!!! " << endl;
cleanup();
return false;
}
m_Pid = atoi(buffer);
fclose(file);
#endif
bool ret = analyzeDebugData();
cleanup();
return ret;
}
//---------------------------------------------------------------------------------------
void RosBootTest::dumpCheckpoints()
{
if (m_Checkpoints.size ())
{
cerr << "Dumping list of checkpoints: "<< endl;
while(!m_Checkpoints.empty ())
{
cerr << m_Checkpoints[0] << endl;
m_Checkpoints.erase (m_Checkpoints.begin ());
}
}
}
//---------------------------------------------------------------------------------------
RosBootTest::DebugState RosBootTest::checkDebugData(vector<string> & debug_data)
{
/// TBD the information needs to be written into an provided log object
/// which writes the info into HTML/log / sends etc ....
bool clear = true;
DebugState state = DebugStateContinue;
for(size_t i = 0; i < debug_data.size();i++)
{
string line = debug_data[i];
cout << line << endl;
if (line.find (RosBootTest::ROS_SYSREG_CHECKPOINT) != string::npos)
{
line.erase (0, line.find (RosBootTest::ROS_SYSREG_CHECKPOINT) +
RosBootTest::ROS_SYSREG_CHECKPOINT.length ());
if (!_tcsncmp(line.c_str (), m_Checkpoint.c_str (), m_Checkpoint.length ()))
{
state = DebugStateCPReached;
break;
}
m_Checkpoints.push_back (line);
}
if (line.find (_T("*** Fatal System Error")) != string::npos)
{
cerr << "Blue Screen of Death detected" <<endl;
if (m_Checkpoints.size ())
{
cerr << "dumping list of reached checkpoints:" << endl;
do
{
string cp = m_Checkpoints[0];
m_Checkpoints.erase (m_Checkpoints.begin ());
cerr << cp << endl;
}while(m_Checkpoints.size ());
cerr << _T("----------------------------------") << endl;
}
if (i + 1 < debug_data.size () )
{
cerr << "dumping rest of debug log" << endl;
while(i < debug_data.size ())
{
string data = debug_data[i];
cerr << data << endl;
i++;
}
cerr << _T("----------------------------------") << endl;
}
state = DebugStateBSODDetected;
break;
}
else if (line.find (_T("Unhandled exception")) != string::npos)
{
if (m_CriticalImage == _T("IGNORE"))
{
///
/// ignoring all user-mode exceptions
///
continue;
}
if (i + 3 >= debug_data.size ())
{
///
/// missing information is cut off -> try reconstruct at next call
///
clear = false;
break;
}
///
/// extract address from next line
///
string address = debug_data[i+2];
string::size_type pos = address.find_last_of (_T(" "));
if (pos == string::npos)
{
cerr << "Error: trace is not available (corrupted debug info" << endl;
continue;
}
address = address.substr (pos, address.length () - 1 - pos);
///
/// extract module name
///
string modulename = debug_data[i+3];
pos = modulename.find_last_of (_T("\\"));
if (pos == string::npos)
{
cerr << "Error: trace is not available (corrupted debug info" << endl;
continue;
}
string appname = modulename.substr (pos + 1, modulename.length () - pos);
if (m_CriticalImage.find (appname) == string::npos && m_CriticalImage.length () > 1)
{
/// the application is not in the list of
/// critical apps. Therefore we ignore the user-mode
/// exception
continue;
}
pos = appname.find_last_of (_T("."));
if (pos == string::npos)
{
cerr << "Error: trace is not available (corrupted debug info" << endl;
continue;
}
modulename = appname.substr (0, pos);
cerr << "UM detected" <<endl;
state = DebugStateUMEDetected;
///
/// resolve address
///
string result;
result.reserve (200);
#if 0
SymbolFile::resolveAddress (modulename, address, result);
cerr << result << endl;
#endif
///
/// TODO
///
/// resolve frame addresses
break;
}
}
if (clear && debug_data.size () > 5)
{
debug_data.clear ();
}
return state;
}
//---------------------------------------------------------------------------------------
bool RosBootTest::isTimeout(double max_timeout)
{
if (max_timeout == 0)
{
// no timeout specified
return false;
}
static time_t start = 0;
if (!start)
{
time(&start);
return false;
}
time_t stop;
time(&stop);
double elapsed = difftime(stop, start);
if (elapsed > max_timeout)
{
return true;
}
else
{
return false;
}
}
//---------------------------------------------------------------------------------------
bool RosBootTest::analyzeDebugData()
{
vector<string> vect;
size_t lines = 0;
bool write_log;
ofstream file;
bool ret = true;
if (m_DebugFile.length ())
{
_tremove(m_DebugFile.c_str ());
file.open (m_DebugFile.c_str ());
}
write_log = file.is_open ();
while(1)
{
if (isTimeout(m_MaxTime))
{
break;
}
size_t prev_count = vect.size ();
if (!m_DataSource->readSource (vect))
{
continue;
}
if (write_log)
{
for (size_t i = prev_count; i < vect.size (); i++)
{
string & line = vect[i];
file << line;
}
}
DebugState state = checkDebugData(vect);
if (state == DebugStateBSODDetected || state == DebugStateUMEDetected)
{
ret = false;
break;
}
else if (state == DebugStateCPReached)
{
break;
}
lines += (vect.size() -prev_count); //WTF?
}
m_DataSource->closeSource();
if (write_log)
{
file.close();
}
return ret;
}
} // end of namespace Sysreg_