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 | |
37 | QEmu::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 | |
48 | QEmu::~QEmu() |
49 | { |
50 | if ( mVMProcess && mVMProcess->state() == QProcess::Running ) |
51 | stop(); |
52 | delete mVMConfig; |
53 | } |
54 | |
55 | void QEmu::setVMConfig(const QString& configFileName) |
56 | { |
57 | delete mVMConfig; |
58 | mVMConfig = new KConfig( Global::basePath() + '/' + configFileName ); |
59 | } |
60 | |
61 | void 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 | |
107 | void 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 | |
136 | QString 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 = 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 | |
176 | void 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 | |
191 | int QEmu::portOffset() const |
192 | { |
193 | return mPortOffset; |
194 | } |
195 | |
196 | void 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 | |