c5ed749c22
Devices used as part of other device (e.g. LUKS) are marked as unavailable. So after releasing the device it need to be discovered again. Unfortunately udev event isn't triggered on such occasion (only "remove" event for device-mapper dev). This patch triggers this event manually.
238 lines
9.2 KiB
Python
238 lines
9.2 KiB
Python
#!/usr/bin/python2
|
|
#
|
|
# The Qubes OS Project, http://www.qubes-os.org
|
|
#
|
|
# Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
|
|
#
|
|
# 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 sys
|
|
import os
|
|
from PyQt4.QtCore import *
|
|
from PyQt4.QtGui import *
|
|
|
|
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
|
|
|
|
import subprocess
|
|
import time
|
|
|
|
from thread_monitor import *
|
|
|
|
from datetime import datetime
|
|
from string import replace
|
|
|
|
mount_for_backup_path = '/usr/libexec/qubes-manager/mount_for_backup.sh'
|
|
|
|
def check_if_mounted(dev_path):
|
|
mounts_file = open("/proc/mounts")
|
|
for m in list(mounts_file):
|
|
if m.startswith(dev_path):
|
|
return m.split(" ")[1]
|
|
return None
|
|
|
|
|
|
def mount_device(dev_path):
|
|
try:
|
|
mount_dir_name = "backup" + replace(str(datetime.now()),' ', '-').split(".")[0]
|
|
pmount_cmd = [mount_for_backup_path, dev_path, mount_dir_name]
|
|
res = subprocess.check_call(pmount_cmd)
|
|
except Exception as ex:
|
|
QMessageBox.warning (None, "Error mounting selected device!", "<b>Could not mount {0}.</b><br><br>ERROR: {1}".format(dev_path, ex))
|
|
return None
|
|
if res == 0:
|
|
dev_mount_path = "/media/"+mount_dir_name
|
|
return dev_mount_path
|
|
return None
|
|
|
|
def umount_device(dev_mount_path):
|
|
while True:
|
|
try:
|
|
pumount_cmd = ["sudo", "pumount", "--luks-force", dev_mount_path]
|
|
res = subprocess.check_call(pumount_cmd)
|
|
if res == 0:
|
|
dev_mount_path = None
|
|
return dev_mount_path
|
|
except Exception as ex:
|
|
title = "Error unmounting backup device!"
|
|
text = "<b>Could not unmount {0}.</b><br>\
|
|
<b>Please retry or unmount it manually using</b><br> pumount {0}.<br><br>\
|
|
ERROR: {1}".format(dev_mount_path, ex)
|
|
button = QMessageBox.warning (None, title, text, QMessageBox.Ok | QMessageBox.Retry, QMessageBox.Retry)
|
|
if button == QMessageBox.Ok:
|
|
return dev_mount_path
|
|
|
|
def detach_device(dialog, dev_name):
|
|
""" Detach device from dom0, if device was attached from some VM"""
|
|
if not dev_name.startswith(dialog.vm.name+":"):
|
|
with dialog.blk_manager.blk_lock:
|
|
dialog.blk_manager.detach_device(dialog.vm, dev_name)
|
|
dialog.blk_manager.update()
|
|
else:
|
|
# umount/LUKS remove do not trigger udev event on underlying device,
|
|
# so trigger it manually - to publish back as available device
|
|
subprocess.call(["sudo", "udevadm", "trigger", "--action=change",
|
|
"--subsystem-match=block",
|
|
"--sysname-match=%s" % dev_name.split(":")[1]])
|
|
with dialog.blk_manager.blk_lock:
|
|
dialog.blk_manager.update()
|
|
|
|
|
|
def fill_appvms_list(dialog):
|
|
dialog.appvm_combobox.clear()
|
|
dialog.appvm_combobox.addItem("dom0")
|
|
|
|
dialog.appvm_combobox.setCurrentIndex(0) #current selected is null ""
|
|
|
|
for vm in dialog.qvm_collection.values():
|
|
if vm.is_appvm() and vm.internal:
|
|
continue
|
|
if vm.is_template() and vm.installed_by_rpm:
|
|
continue
|
|
|
|
if vm.is_running() and vm.qid != 0:
|
|
dialog.appvm_combobox.addItem(vm.name)
|
|
|
|
def fill_devs_list(dialog):
|
|
dialog.dev_combobox.clear()
|
|
dialog.dev_combobox.addItem("None")
|
|
|
|
dialog.blk_manager.blk_lock.acquire()
|
|
for a in dialog.blk_manager.attached_devs:
|
|
if dialog.blk_manager.attached_devs[a]['attached_to']['vm'] == dialog.vm.name :
|
|
att = a + " " + unicode(dialog.blk_manager.attached_devs[a]['size']) + " " + dialog.blk_manager.attached_devs[a]['desc']
|
|
dialog.dev_combobox.addItem(att, QVariant(a))
|
|
for a in dialog.blk_manager.free_devs:
|
|
att = a + " " + unicode(dialog.blk_manager.free_devs[a]['size']) + " " + dialog.blk_manager.free_devs[a]['desc']
|
|
dialog.dev_combobox.addItem(att, QVariant(a))
|
|
dialog.blk_manager.blk_lock.release()
|
|
|
|
dialog.dev_combobox.setCurrentIndex(0) #current selected is null ""
|
|
dialog.prev_dev_idx = 0
|
|
dialog.dir_line_edit.clear()
|
|
enable_dir_line_edit(dialog, True)
|
|
|
|
|
|
def enable_dir_line_edit(dialog, boolean):
|
|
dialog.dir_line_edit.setEnabled(boolean)
|
|
dialog.select_path_button.setEnabled(boolean)
|
|
|
|
def update_device_appvm_enabled(dialog, idx):
|
|
# Only one of those can be used
|
|
dialog.dev_combobox.setEnabled(dialog.appvm_combobox.currentIndex() == 0)
|
|
dialog.appvm_combobox.setEnabled(dialog.dev_combobox.currentIndex() == 0)
|
|
|
|
def dev_combobox_activated(dialog, idx):
|
|
if idx == dialog.prev_dev_idx: #nothing has changed
|
|
return
|
|
#there was a change
|
|
|
|
dialog.dir_line_edit.setText("")
|
|
|
|
# umount old device if any
|
|
if dialog.dev_mount_path != None:
|
|
dialog.dev_mount_path = umount_device(dialog.dev_mount_path)
|
|
if dialog.dev_mount_path != None:
|
|
dialog.dev_combobox.setCurrentIndex(dialog.prev_dev_idx)
|
|
return
|
|
else:
|
|
detach_device(dialog,
|
|
str(dialog.dev_combobox.itemData(dialog.prev_dev_idx).toString()))
|
|
|
|
# then attach new one
|
|
if dialog.dev_combobox.currentIndex() != 0: #An existing device chosen
|
|
dev_name = str(dialog.dev_combobox.itemData(idx).toString())
|
|
|
|
try:
|
|
with dialog.blk_manager.blk_lock:
|
|
if dev_name in dialog.blk_manager.free_devs:
|
|
if dev_name.startswith(dialog.vm.name): # originally attached to dom0
|
|
dev_path = "/dev/"+dev_name.split(":")[1]
|
|
|
|
else: # originally attached to another domain, eg. usbvm
|
|
#attach it to dom0, then treat it as an attached device
|
|
dialog.blk_manager.attach_device(dialog.vm, dev_name)
|
|
dialog.blk_manager.update()
|
|
|
|
if dev_name in dialog.blk_manager.attached_devs: #is attached to dom0
|
|
assert dialog.blk_manager.attached_devs[dev_name]['attached_to']['vm'] == dialog.vm.name
|
|
dev_path = "/dev/" + dialog.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
|
|
except QubesException as ex:
|
|
QMessageBox.warning (None, "Error attaching selected device!",
|
|
"<b>Could not attach {0}.</b><br><br>ERROR: {1}".format(dev_name, ex))
|
|
dialog.dev_combobox.setCurrentIndex(0) #if couldn't mount - set current device to "None"
|
|
dialog.prev_dev_idx = 0
|
|
return
|
|
|
|
#check if device mounted
|
|
dialog.dev_mount_path = check_if_mounted(dev_path)
|
|
if dialog.dev_mount_path == None:
|
|
dialog.dev_mount_path = mount_device(dev_path)
|
|
if dialog.dev_mount_path == None:
|
|
dialog.dev_combobox.setCurrentIndex(0) #if couldn't mount - set current device to "None"
|
|
dialog.prev_dev_idx = 0
|
|
detach_device(dialog,
|
|
str(dialog.dev_combobox.itemData(idx).toString()))
|
|
return
|
|
|
|
dialog.prev_dev_idx = idx
|
|
|
|
if hasattr(dialog, 'selected_vms'):
|
|
# backup window
|
|
if dialog.dev_mount_path != None:
|
|
# Initialize path with root of mounted device
|
|
dialog.dir_line_edit.setText(dialog.dev_mount_path)
|
|
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
|
|
|
|
def select_path_button_clicked(dialog, select_file = False):
|
|
backup_location = str(dialog.dir_line_edit.text())
|
|
file_dialog = QFileDialog()
|
|
file_dialog.setReadOnly(True)
|
|
|
|
if select_file:
|
|
file_dialog_function = file_dialog.getOpenFileName
|
|
else:
|
|
file_dialog_function = file_dialog.getExistingDirectory
|
|
|
|
new_appvm = None
|
|
new_path = None
|
|
if dialog.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
|
|
new_appvm = str(dialog.appvm_combobox.currentText())
|
|
elif dialog.dev_mount_path != None:
|
|
new_path = file_dialog_function(dialog, "Select backup location.", dialog.dev_mount_path)
|
|
else:
|
|
new_path = file_dialog_function(dialog, "Select backup location.", backup_location)
|
|
|
|
if new_path != None:
|
|
new_path = str(new_path)
|
|
if os.path.basename(new_path) == 'qubes.xml':
|
|
backup_location = os.path.dirname(new_path)
|
|
else:
|
|
backup_location = new_path
|
|
dialog.dir_line_edit.setText(backup_location)
|
|
|
|
if (new_path or new_appvm) and len(backup_location) > 0:
|
|
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
|
|
|
|
def simulate_long_lasting_proces(period, progress_callback):
|
|
for i in range(period):
|
|
progress_callback((i*100)/period)
|
|
time.sleep(1)
|
|
|
|
progress_callback(100)
|
|
return 0
|
|
|