1/*
2 Copyright (c) 2009 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "qemu.h"
21
22#include "global.h"
23#include "test.h"
24
25#include <KConfig>
26#include <KDebug>
27#include <KConfigGroup>
28#include <KShell>
29#include <KIO/NetAccess>
30
31#include <qtest_kde.h>
32
33#include <QDir>
34#include <QFile>
35#include <QTcpSocket>
36
37QEmu::QEmu(QObject* parent) :
38 QObject( parent ),
39 mVMConfig( 0 ),
40 mVMProcess( 0 ),
41 mPortOffset( 42000 ), // TODO should be somewhat dynamic to allo running multiple instances in parallel
42 mMonitorPort( -1 ),
43 mStarted( false )
44{
45 Q_ASSERT( parent );
46}
47
48QEmu::~QEmu()
49{
50 if ( mVMProcess && mVMProcess->state() == QProcess::Running )
51 stop();
52 delete mVMConfig;
53}
54
55void QEmu::setVMConfig(const QString& configFileName)
56{
57 delete mVMConfig;
58 mVMConfig = new KConfig( Global::basePath() + '/' + configFileName );
59}
60
61void QEmu::start()
62{
63 Q_ASSERT( mVMConfig );
64 if ( mVMProcess ) {
65 kWarning() << "VM already running.";
66 return;
67 }
68
69 KConfigGroup emuConf( mVMConfig, "Emulator" );
70 QStringList args = KShell::splitArgs( emuConf.readEntry( "Arguments", QString() ) );
71 const QList<int> ports = emuConf.readEntry( "Ports", QList<int>() );
72 foreach ( int port, ports ) {
73 args << "-redir" << QString::fromLatin1( "tcp:%1::%2" ).arg( port + mPortOffset ).arg( port );
74 }
75 mMonitorPort = emuConf.readEntry( "MonitorPort", 23 ) + mPortOffset;
76 args << "-monitor" << QString::fromLatin1( "tcp:127.0.0.1:%1,server,nowait" ).arg( mMonitorPort );
77 args << "-hda" << vmImage();
78
79 // If a SnapshotName is given in the configuration, load that snapshot
80 // with -loadvm and assume that it's OK to write to he image file.
81 // Othewise, use the -snapshot option to avoid changing the image
82 // file.
83 QString snapshotName = emuConf.readEntry( "SnapshotName", "" );
84 if ( snapshotName.isEmpty() ) {
85 args << "-snapshot";
86 } else {
87 args << "-loadvm" << snapshotName;
88 }
89
90 kDebug() << "Starting QEMU with arguments" << args << "...";
91
92 mVMProcess = new KProcess( this );
93 connect( mVMProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(vmFinished(int,QProcess::ExitStatus)) );
94 mVMProcess->setProgram( "qemu", args );
95 mVMProcess->start();
96 mVMProcess->waitForStarted();
97 mStarted = true;
98 kDebug() << "QEMU started.";
99
100 if ( emuConf.readEntry( "WaitForPorts", true ) ) {
101 const QList<int> waitPorts = emuConf.readEntry( "WaitForPorts", QList<int>() );
102 foreach ( int port, waitPorts )
103 waitForPort( port );
104 }
105}
106
107void QEmu::stop()
108{
109 mStarted = false;
110 if ( !mVMProcess )
111 return;
112 kDebug() << "Stopping QEMU...";
113
114 // send stop command via QEMU monitor
115 QTcpSocket socket;
116 socket.connectToHost( "localhost", mMonitorPort );
117 if ( socket.waitForConnected() ) {
118 socket.write( "quit\n");
119 socket.flush();
120 socket.waitForBytesWritten();
121 mVMProcess->waitForFinished(10000);
122 } else {
123 kDebug() << "Unable to connect to QEMU monitor:" << socket.errorString();
124 }
125
126 if ( mVMProcess->state() == QProcess::Running ) {
127 kDebug() << "qemu is still running. terminating";
128 mVMProcess->terminate();
129 }
130
131 delete mVMProcess;
132 mVMProcess = 0;
133 kDebug() << "QEMU stopped.";
134}
135
136QString QEmu::vmImage() const
137{
138 KConfigGroup conf( mVMConfig, "Image" );
139
140 const KUrl imageUrl = conf.readEntry( "Source", QString() );
141 Q_ASSERT( !imageUrl.isEmpty() );
142
143 const QString imageArchiveFileName = imageUrl.fileName();
144 Q_ASSERT( !imageArchiveFileName.isEmpty() );
145
146 // check if the image has been downloaded already
147 const QString localArchiveFileName = Global::vmPath() + imageArchiveFileName;
148 if ( !QFile::exists( localArchiveFileName ) ) {
149 kDebug() << "Downloading VM image from" << imageUrl << "to" << localArchiveFileName << "...";
150 const bool result = KIO::NetAccess::file_copy( imageUrl, localArchiveFileName, 0 );
151 if ( !result )
152 kFatal() << "Downloading" << imageUrl << "failed!";
153 kDebug() << "Downloading VM image complete.";
154 }
155
156 // check if the image archive has been extracted yet
157 const QString extractedDirName = Global::vmPath() + imageArchiveFileName + ".directory";
158 if ( !QDir::root().exists( extractedDirName ) ) {
159 kDebug() << "Extracting VM image...";
160 QDir::root().mkpath( extractedDirName );
161 KProcess proc;
162 proc.setWorkingDirectory( extractedDirName );
163 proc.setProgram( "tar", QStringList() << "xvf" << localArchiveFileName );
164 proc.execute();
165 kDebug() << "Extracting VM image complete.";
166 }
167
168 // check if the actual image file is there and return it
169 const QString imageFile = extractedDirName + QDir::separator() + conf.readEntry( "File", QString() );
170 if ( !QFile::exists( imageFile ) )
171 kFatal() << "Image file" << imageFile << "does not exist.";
172
173 return imageFile;
174}
175
176void QEmu::waitForPort(int port)
177{
178 kDebug() << "Waiting for port" << (port + mPortOffset) << "...";
179 forever {
180 QTcpSocket socket;
181 socket.connectToHost( "localhost", port + mPortOffset );
182 if ( !socket.waitForConnected( 5000 ) ) {
183 QTest::qWait( 5000 );
184 continue;
185 }
186 if ( socket.waitForReadyRead( 5000 ) )
187 break;
188 }
189}
190
191int QEmu::portOffset() const
192{
193 return mPortOffset;
194}
195
196void QEmu::vmFinished(int exitCode, QProcess::ExitStatus exitStatus)
197{
198 Q_UNUSED( exitCode );
199 Q_UNUSED( exitStatus );
200 if ( mStarted )
201 Test::instance()->fail( "QEMU termineated!" );
202}
203
204
205