Added gui for booting VM from CDROM/block device.
Available via command line (qubes-vm-boot-from-device) and from a button in VM Settings/Advanced.
This commit is contained in:
parent
0e5feeac0d
commit
2ac9b1d182
130
qubesmanager/bootfromdevice.py
Normal file
130
qubesmanager/bootfromdevice.py
Normal file
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
#
|
||||
|
||||
import subprocess
|
||||
from . import utils
|
||||
from .firewall import *
|
||||
from .ui_bootfromdevice import *
|
||||
import qubesadmin.tools.qvm_start as qvm_start
|
||||
|
||||
|
||||
class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
|
||||
def __init__(self, vm, qapp, parent=None):
|
||||
super(VMBootFromDeviceWindow, self).__init__(parent)
|
||||
|
||||
self.vm = vm
|
||||
self.qapp = qapp
|
||||
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle(self.tr("Boot {vm} from device").format(vm=self.vm.name))
|
||||
|
||||
self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
|
||||
self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
|
||||
|
||||
# populate buttons and such
|
||||
self.__init_buttons__()
|
||||
|
||||
|
||||
def reject(self):
|
||||
self.done(0)
|
||||
|
||||
def save_and_apply(self):
|
||||
if self.blockDeviceRadioButton.isChecked():
|
||||
cdrom_location = self.blockDeviceComboBox.currentText()
|
||||
elif self.fileRadioButton.isChecked():
|
||||
cdrom_location = self.vm_list[self.fileVM.currentIndex()] + ":" + self.pathText.text()
|
||||
else:
|
||||
QMessageBox.warning(None,
|
||||
self.tr(
|
||||
"ERROR!"),
|
||||
self.tr("No file or block device selected; please select one."))
|
||||
return
|
||||
|
||||
qvm_start.main(['--cdrom', cdrom_location, self.vm.name])
|
||||
|
||||
def __init_buttons__(self):
|
||||
self.fileVM.setEnabled(False)
|
||||
self.selectFileButton.setEnabled(False)
|
||||
self.blockDeviceComboBox.setEnabled(False)
|
||||
|
||||
self.blockDeviceRadioButton.clicked.connect(self.radio_button_clicked)
|
||||
self.fileRadioButton.clicked.connect(self.radio_button_clicked)
|
||||
self.selectFileButton.clicked.connect(self.select_file_dialog)
|
||||
|
||||
self.vm_list, self.vm_idx = utils.prepare_vm_choice(
|
||||
self.fileVM,
|
||||
self.vm, None,
|
||||
None,
|
||||
None,
|
||||
allow_default=False, allow_none=False)
|
||||
|
||||
self.block_list, self.block_idx = utils.prepare_choice(
|
||||
self.blockDeviceComboBox,
|
||||
self.vm,
|
||||
None,
|
||||
[device for domain in self.vm.app.domains
|
||||
for device in domain.devices["block"]],
|
||||
None,
|
||||
None,
|
||||
allow_default=False, allow_none=False
|
||||
)
|
||||
|
||||
def radio_button_clicked(self):
|
||||
self.blockDeviceComboBox.setEnabled(self.blockDeviceRadioButton.isChecked())
|
||||
self.fileVM.setEnabled(self.fileRadioButton.isChecked())
|
||||
self.selectFileButton.setEnabled(self.fileRadioButton.isChecked())
|
||||
self.pathText.setEnabled(self.fileRadioButton.isChecked())
|
||||
|
||||
def select_file_dialog(self):
|
||||
backend_vm = self.vm_list[self.fileVM.currentIndex()]
|
||||
|
||||
try:
|
||||
new_path = utils.get_path_from_vm(backend_vm, "qubes.SelectFile")
|
||||
except subprocess.CalledProcessError:
|
||||
new_path = None
|
||||
|
||||
if new_path:
|
||||
self.pathText.setText(new_path)
|
||||
|
||||
|
||||
parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1)
|
||||
|
||||
def main(args=None):
|
||||
global bootfromdevice_window
|
||||
|
||||
args = parser.parse_args(args)
|
||||
vm = args.domains.pop()
|
||||
|
||||
qapp = QApplication(sys.argv)
|
||||
qapp.setOrganizationName('Invisible Things Lab')
|
||||
qapp.setOrganizationDomain("https://www.qubes-os.org/")
|
||||
qapp.setApplicationName("Qubes VM Settings")
|
||||
|
||||
# if not utils.is_debug(): #FIXME
|
||||
# sys.excepthook = handle_exception
|
||||
|
||||
bootfromdevice_window = VMBootFromDeviceWindow(vm, qapp)
|
||||
bootfromdevice_window.show()
|
||||
|
||||
qapp.exec_()
|
||||
qapp.exit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -46,6 +46,7 @@ from .backup_utils import get_path_for_vm
|
||||
from .firewall import *
|
||||
|
||||
from .ui_settingsdlg import *
|
||||
from .bootfromdevice import main as bootfromdevice
|
||||
|
||||
class VMSettingsWindow(Ui_SettingsDialog, QDialog):
|
||||
tabs_indices = collections.OrderedDict((
|
||||
@ -91,6 +92,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
|
||||
self.connect(self.init_mem, SIGNAL("editingFinished()"), self.check_mem_changes)
|
||||
self.connect(self.max_mem_size, SIGNAL("editingFinished()"), self.check_mem_changes)
|
||||
self.drive_path_button.clicked.connect(self.drive_path_button_pressed)
|
||||
self.bootFromDeviceButton.clicked.connect(self.boot_from_cdrom_button_pressed)
|
||||
|
||||
###### firewall tab
|
||||
if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']):
|
||||
@ -454,22 +456,6 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
|
||||
self.vm.features.get('services.meminfo-writer', True))
|
||||
self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
|
||||
|
||||
# try:
|
||||
# self.root_img_path.setText('{volume.pool}:{volume.vid}'.format(
|
||||
# volume=self.vm.volumes['root']))
|
||||
# except AttributeError:
|
||||
# self.root_img_path.setText("n/a")
|
||||
# try:
|
||||
# self.volatile_img_path.setText('{volume.pool}:{volume.vid}'.format(
|
||||
# volume=self.vm.volumes['volatile']))
|
||||
# except AttributeError:
|
||||
# self.volatile_img_path.setText('n/a')
|
||||
# self.private_img_path.setText('{volume.pool}:{volume.vid}'.format(
|
||||
# volume=self.vm.volumes['private']))
|
||||
|
||||
|
||||
#kernel
|
||||
|
||||
#in case VM is HVM
|
||||
if hasattr(self.vm, "kernel"):
|
||||
self.kernel_groupbox.setVisible(True)
|
||||
@ -582,6 +568,10 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
|
||||
|
||||
return msg
|
||||
|
||||
def boot_from_cdrom_button_pressed(self):
|
||||
self.save_and_apply()
|
||||
subprocess.check_call(['qubes-vm-boot-from-device', self.vm.name])
|
||||
|
||||
def drive_path_button_pressed(self):
|
||||
if str(self.drive_domain.currentText()) in ["dom0", "dom0 (current)"]:
|
||||
file_dialog = QFileDialog()
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
import functools
|
||||
import os
|
||||
|
||||
import re
|
||||
import qubesadmin
|
||||
|
||||
from PyQt4.QtGui import QIcon
|
||||
@ -135,3 +135,31 @@ def debug(*args, **kwargs):
|
||||
if not is_debug():
|
||||
return
|
||||
print(*args, **kwargs)
|
||||
|
||||
|
||||
def get_path_from_vm(vm, service_name):
|
||||
"""
|
||||
Displays a file/directory selection window for the given VM.
|
||||
|
||||
:param vm: vm from which to select path
|
||||
:param service_name: qubes.SelectFile or qubes.SelectDirectory
|
||||
:return: path to file, checked for validity
|
||||
"""
|
||||
|
||||
path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*")
|
||||
path_max_len = 512
|
||||
|
||||
if not vm:
|
||||
return None
|
||||
stdout, stderr = vm.run_service_for_stdio(service_name)
|
||||
|
||||
untrusted_path = stdout.decode(encoding='ascii')[:path_max_len]
|
||||
|
||||
if len(untrusted_path) == 0:
|
||||
return None
|
||||
if path_re.match(untrusted_path):
|
||||
assert '../' not in untrusted_path
|
||||
assert '\0' not in untrusted_path
|
||||
return untrusted_path.strip()
|
||||
else:
|
||||
raise ValueError('Unexpected characters in path.')
|
||||
|
@ -61,6 +61,7 @@ rm -rf $RPM_BUILD_ROOT
|
||||
/usr/bin/qubes-global-settings
|
||||
/usr/bin/qubes-vm-settings
|
||||
/usr/bin/qubes-vm-create
|
||||
/usr/bin/qubes-vm-boot-from-device
|
||||
/usr/libexec/qubes-manager/mount_for_backup.sh
|
||||
/usr/libexec/qubes-manager/qvm_about.sh
|
||||
|
||||
@ -85,10 +86,12 @@ rm -rf $RPM_BUILD_ROOT
|
||||
%{python3_sitelib}/qubesmanager/create_new_vm.py
|
||||
%{python3_sitelib}/qubesmanager/thread_monitor.py
|
||||
%{python3_sitelib}/qubesmanager/utils.py
|
||||
%{python3_sitelib}/qubesmanager/bootfromdevice.py
|
||||
|
||||
%{python3_sitelib}/qubesmanager/resources_rc.py
|
||||
|
||||
%{python3_sitelib}/qubesmanager/ui_backupdlg.py
|
||||
%{python3_sitelib}/qubesmanager/ui_bootfromdevice.py
|
||||
%{python3_sitelib}/qubesmanager/ui_globalsettingsdlg.py
|
||||
%{python3_sitelib}/qubesmanager/ui_multiselectwidget.py
|
||||
%{python3_sitelib}/qubesmanager/ui_newappvmdlg.py
|
||||
|
1
setup.py
1
setup.py
@ -21,5 +21,6 @@ if __name__ == '__main__':
|
||||
'qubes-global-settings = qubesmanager.global_settings:main',
|
||||
'qubes-vm-settings = qubesmanager.settings:main',
|
||||
'qubes-vm-create = qubesmanager.create_new_vm:main',
|
||||
'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main'
|
||||
],
|
||||
})
|
||||
|
117
ui/bootfromdevice.ui
Normal file
117
ui/bootfromdevice.ui
Normal file
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BootDialog</class>
|
||||
<widget class="QDialog" name="BootDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>170</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Boot from device</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Boot qube from device</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>30</y>
|
||||
<width>581</width>
|
||||
<height>72</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QRadioButton" name="blockDeviceRadioButton">
|
||||
<property name="text">
|
||||
<string>from existing block device</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="fileRadioButton">
|
||||
<property name="text">
|
||||
<string>from file in qube</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="fileVM">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="pathText"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectFileButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="blockDeviceComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -29,7 +29,7 @@
|
||||
<locale language="English" country="UnitedStates"/>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="basic_tab">
|
||||
<property name="locale">
|
||||
@ -383,6 +383,99 @@
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<item row="0" column="1" rowspan="2" colspan="2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="kernel_groupbox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Kernel</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_9">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>Kernel:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>kernel</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="kernel"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Kernel opts:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="kernel_opts">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>[]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="other_groupbox">
|
||||
<property name="title">
|
||||
<string>Other</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Default DispVM:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>default_dispvm</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="default_dispvm"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="bootFromDeviceButton">
|
||||
<property name="text">
|
||||
<string>Boot qube from CDROM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="enabled">
|
||||
@ -942,11 +1035,6 @@
|
||||
<tabstop>vcpus</tabstop>
|
||||
<tabstop>include_in_balancing</tabstop>
|
||||
<tabstop>kernel</tabstop>
|
||||
<tabstop>drive_groupbox</tabstop>
|
||||
<tabstop>drive_type</tabstop>
|
||||
<tabstop>drive_domain</tabstop>
|
||||
<tabstop>drive_path</tabstop>
|
||||
<tabstop>drive_path_button</tabstop>
|
||||
<tabstop>policyAllowRadioButton</tabstop>
|
||||
<tabstop>policyDenyRadioButton</tabstop>
|
||||
<tabstop>icmpCheckBox</tabstop>
|
||||
|
Loading…
Reference in New Issue
Block a user