#
# raid_dialog_gui.py: dialog for editting a raid request
#
# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Red Hat, Inc.
# All rights reserved.
#
# 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, see .
#
# Author(s): Michael Fulbright
# Jeremy Katz
#
import copy
import gobject
import gtk
import datacombo
import gui
import storage.devicelibs.mdraid as mdraidlib
from storage.devices import *
from storage.deviceaction import *
from partition_ui_helpers_gui import *
from constants import *
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
class RaidEditor:
def createAllowedRaidPartitionsList(self, allraidparts, reqraidpart,
preexist):
store = gtk.TreeStore(gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING,
gobject.TYPE_STRING)
partlist = WideCheckList(2, store)
sw = gtk.ScrolledWindow()
sw.add(partlist)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
sw.set_shadow_type(gtk.SHADOW_IN)
tempDevList = []
if not self.isNew:
# We need this list if we are editing.
for dev in reqraidpart:
tempDevList.append(dev)
partrow = 0
for part in allraidparts:
partname = "%s" % part.name
partsize = "%8.0f MB" % part.size
if part in tempDevList:
#list the partition and put it as selected
partlist.append_row((partname, partsize), True)
else:
if not self.origrequest.exists:
partlist.append_row((partname, partsize), False)
return (partlist, sw)
def createRaidLevelMenu(self, levels, reqlevel):
levelcombo = gtk.combo_box_new_text()
defindex = 0
if mdraidlib.RAID1 in levels:
defindex = levels.index(mdraidlib.RAID1)
i = 0
for lev in levels:
levelcombo.append_text("RAID%d" % lev)
if reqlevel is not None and lev == reqlevel:
defindex = i
i = i + 1
levelcombo.set_active(defindex)
if reqlevel is not None and reqlevel == mdraidlib.RAID0:
self.sparesb.set_sensitive(0)
if self.sparesb:
levelcombo.connect("changed", self.raidlevelchangeCB, self.sparesb)
return levelcombo
def createRaidMinorMenu(self, minors, reqminor):
minorcombo = datacombo.DataComboBox()
defindex = 0
i = 0
for minor in minors:
minorcombo.append("md%d" %(minor,), minor)
if reqminor and minor == reqminor:
defindex = i
i = i + 1
minorcombo.set_active(defindex)
return minorcombo
def raidlevelchangeCB(self, widget, sparesb):
raidlevel = widget.get_model()[widget.get_active()][0]
numparts = sparesb.get_data("numparts")
maxspares = mdraidlib.get_raid_max_spares(raidlevel, numparts)
if maxspares > 0 and not mdraidlib.isRaid(mdraidlib.RAID0, raidlevel):
adj = sparesb.get_adjustment()
value = adj.value
if adj.value > maxspares:
value = maxspares
sparesb.set_sensitive(1)
spareAdj = gtk.Adjustment(value = value, lower = 0,
upper = maxspares, step_incr = 1)
spareAdj.clamp_page(0, maxspares)
sparesb.set_adjustment(spareAdj)
sparesb.set_value(value)
else:
sparesb.set_value(0)
sparesb.set_sensitive(0)
def run(self):
if self.dialog is None:
return []
while 1:
rc = self.dialog.run()
# user hit cancel, do nothing
if rc in [2, gtk.RESPONSE_DELETE_EVENT]:
self.destroy()
return []
actions = []
luksdev = None
raidmembers = []
migrate = None
model = self.raidlist.get_model()
iter = model.get_iter_first()
format = None
while iter:
val = model.get_value(iter, 0)
part = model.get_value(iter, 1)
if val:
dev = self.storage.devicetree.getDeviceByName(part)
raidmembers.append(dev)
iter = model.iter_next(iter)
# The user has to select some devices to be part of the array.
if not raidmembers:
continue
mountpoint = self.mountCombo.get_children()[0].get_text()
if mountpoint == _(""):
mountpoint = ""
if mountpoint:
used = False
for (mp, dev) in self.storage.mountpoints.iteritems():
if mp == mountpoint and \
dev.id != self.origrequest.id and \
not (self.origrequest.format.type == "luks" and
self.origrequest in dev.parents):
used = True
break
if used:
self.intf.messageWindow(_("Mount point in use"),
_("The mount point \"%s\" is in "
"use. Please pick another.") %
(mountpoint,),
custom_icon="error")
continue
if not self.origrequest.exists:
# new device
fmt_class = self.fstypeCombo.get_active_value()
raidminor = int(self.minorCombo.get_active_value())
model = self.levelcombo.get_model()
raidlevel = model[self.levelcombo.get_active()][0]
if not mdraidlib.isRaid(mdraidlib.RAID0, raidlevel):
self.sparesb.update()
spares = self.sparesb.get_value_as_int()
else:
spares = 0
format = fmt_class(mountpoint=mountpoint)
members = len(raidmembers) - spares
try:
request = self.storage.newMDArray(minor=raidminor,
level=raidlevel,
format=format,
parents=raidmembers,
totalDevices=len(raidmembers),
memberDevices=members)
except ValueError, e:
self.intf.messageWindow(_("Error"), str(e),
custom_icon="error")
continue
# we must destroy luks leaf before original raid request
if self.origrequest.format.type == "luks":
# => not self.isNew
# destroy luks format and mapped device
# XXX remove catching, it should always succeed
try:
luksdev = self.storage.devicetree.getChildren(self.origrequest)[0]
except IndexError:
pass
else:
actions.append(ActionDestroyFormat(luksdev))
actions.append(ActionDestroyDevice(luksdev))
luksdev = None
if self.lukscb and self.lukscb.get_active():
luksdev = LUKSDevice("luks-%s" % request.name,
format=format,
parents=request)
format = getFormat("luks",
passphrase=self.storage.encryptionPassphrase)
request.format = format
elif self.lukscb and not self.lukscb.get_active() and \
self.origrequest.format.type == "luks":
# XXXRV not needed as we destroy origrequest ?
actions.append(ActionDestroyFormat(self.origrequest))
if not self.isNew:
# This may be handled in devicetree.registerAction,
# but not in case when we change minor and thus
# device name/path (at least with current md)
actions.append(ActionDestroyDevice(self.origrequest))
actions.append(ActionCreateDevice(request))
actions.append(ActionCreateFormat(request))
else:
# existing device
fmt_class = self.fsoptionsDict["fstypeCombo"].get_active_value()
if self.fsoptionsDict.has_key("formatcb") and \
self.fsoptionsDict["formatcb"].get_active():
format = fmt_class(mountpoint=mountpoint)
if self.fsoptionsDict.has_key("lukscb") and \
self.fsoptionsDict["lukscb"].get_active() and \
(self.origrequest.format.type != "luks" or
(self.origrequest.format.exists and
not self.origrequest.format.hasKey)):
luksdev = LUKSDevice("luks-%s" % self.origrequest.name,
format=format,
parents=self.origrequest)
format = getFormat("luks",
device=self.origrequest.path,
passphrase=self.storage.encryptionPassphrase)
elif self.fsoptionsDict.has_key("lukscb") and \
not self.fsoptionsDict["lukscb"].get_active() and \
self.origrequest.format.type == "luks":
# destroy luks format and mapped device
try:
luksdev = self.storage.devicetree.getChildren(self.origrequest)[0]
except IndexError:
pass
else:
actions.append(ActionDestroyFormat(luksdev))
actions.append(ActionDestroyDevice(luksdev))
luksdev = None
actions.append(ActionDestroyFormat(self.origrequest))
elif self.fsoptionsDict.has_key("formatcb") and \
not self.fsoptionsDict["formatcb"].get_active():
# if the format checkbutton is inactive, cancel all
# actions on this device that create or destroy formats
devicetree = self.storage.devicetree
request = self.origrequest
cancel = []
if request.originalFormat.type == "luks":
path = "/dev/mapper/luks-%s" % request.originalFormat.uuid
cancel.extend(devicetree.findActions(path=path))
cancel.extend(devicetree.findActions(type="destroy",
object="format",
devid=request.id))
cancel.extend(devicetree.findActions(type="create",
object="format",
devid=request.id))
for action in cancel:
devicetree.cancelAction(action)
# even though we cancelled a bunch of actions, it's
# pretty much impossible to be sure we cancelled them
# in the correct order. make sure things are back to
# their original state.
request.format = request.originalFormat
if request.format.type == "luks":
try:
usedev = devicetree.getChildren(request)[0]
except IndexError:
usedev = request
else:
usedev.format = usedev.originalFormat
else:
usedev = request
if usedev.format.mountable:
usedev.format.mountpoint = mountpoint
if self.origrequest.format.mountable:
self.origrequest.format.mountpoint = mountpoint
if self.fsoptionsDict.has_key("migratecb") and \
self.fsoptionsDict["migratecb"].get_active():
if self.origrequest.format.type == "luks":
try:
usedev = self.storage.devicetree.getChildren(self.origrequest)[0]
except IndexError:
usedev = self.origrequest
else:
usedev = self.origrequest
migrate = True
if self.origrequest.format.exists and not format and \
self.storage.formatByDefault(self.origrequest):
if not queryNoFormatPreExisting(self.intf):
continue
if format:
actions.append(ActionCreateFormat(self.origrequest, format))
# everything ok, break out
break
if luksdev:
actions.append(ActionCreateDevice(luksdev))
actions.append(ActionCreateFormat(luksdev))
if migrate:
actions.append(ActionMigrateFormat(usedev))
return actions
def destroy(self):
if self.dialog:
self.dialog.destroy()
self.dialog = None
def __init__(self, storage, intf, parent, origrequest, isNew = 0):
self.storage = storage
self.origrequest = origrequest
self.isNew = isNew
self.intf = intf
self.parent = parent
self.dialog = None
#
# start of editRaidRequest
#
availraidparts = self.storage.unusedMDMembers(array=self.origrequest)
# if no raid partitions exist, raise an error message and return
if len(availraidparts) < 2:
dlg = gtk.MessageDialog(self.parent, 0, gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK,
_("At least two unused software RAID "
"partitions are needed to create "
"a RAID device.\n\n"
"First create at least two partitions "
"of type \"software RAID\", and then "
"select the \"RAID\" option again."))
gui.addFrame(dlg)
dlg.show_all()
dlg.set_position(gtk.WIN_POS_CENTER)
dlg.run()
dlg.destroy()
return
if isNew:
tstr = _("Make RAID Device")
else:
if origrequest.minor is not None:
tstr = _("Edit RAID Device: %s") % (origrequest.path,)
else:
tstr = _("Edit RAID Device")
dialog = gtk.Dialog(tstr, self.parent)
gui.addFrame(dialog)
dialog.add_button('gtk-cancel', 2)
dialog.add_button('gtk-ok', 1)
dialog.set_position(gtk.WIN_POS_CENTER)
maintable = gtk.Table()
maintable.set_row_spacings(5)
maintable.set_col_spacings(5)
row = 0
# we'll maybe add this further down
self.lukscb = gtk.CheckButton(_("_Encrypt"))
self.lukscb.set_data("formatstate", 1)
if origrequest.format.type == "luks":
try:
luksdev = self.storage.devicetree.getChildren(origrequest)[0]
except IndexError:
luksdev = None
usedev = origrequest
format = origrequest.format
else:
usedev = luksdev
format = usedev.format
else:
luksdev = None
usedev = origrequest
format = origrequest.format
# Mount Point entry
lbl = createAlignedLabel(_("_Mount Point:"))
maintable.attach(lbl, 0, 1, row, row + 1)
self.mountCombo = createMountPointCombo(usedev)
lbl.set_mnemonic_widget(self.mountCombo)
maintable.attach(self.mountCombo, 1, 2, row, row + 1)
row = row + 1
# Filesystem Type
if not origrequest.exists:
lbl = createAlignedLabel(_("_File System Type:"))
maintable.attach(lbl, 0, 1, row, row + 1)
self.fstypeCombo = createFSTypeMenu(format,
fstypechangeCB,
self.mountCombo,
ignorefs = ["mdmember", "efi", "prepboot", "appleboot"])
lbl.set_mnemonic_widget(self.fstypeCombo)
maintable.attach(self.fstypeCombo, 1, 2, row, row + 1)
row += 1
else:
maintable.attach(createAlignedLabel(_("Original File System Type:")),
0, 1, row, row + 1)
self.fstypeCombo = gtk.Label(usedev.originalFormat.name)
maintable.attach(self.fstypeCombo, 1, 2, row, row + 1)
row += 1
if getattr(usedev.originalFormat, "label", None):
maintable.attach(createAlignedLabel(_("Original File System "
"Label:")),
0, 1, row, row + 1)
maintable.attach(gtk.Label(usedev.originalFormat.label),
1, 2, row, row + 1)
row += 1
# raid minors
lbl = createAlignedLabel(_("RAID _Device:"))
maintable.attach(lbl, 0, 1, row, row + 1)
if not origrequest.exists:
availminors = self.storage.unusedMDMinors[:16]
reqminor = origrequest.minor
if reqminor is not None and reqminor not in availminors:
availminors.append(reqminor)
availminors.sort()
self.minorCombo = self.createRaidMinorMenu(availminors, reqminor)
lbl.set_mnemonic_widget(self.minorCombo)
else:
self.minorCombo = gtk.Label("%s" %(origrequest.name,))
maintable.attach(self.minorCombo, 1, 2, row, row + 1)
row = row + 1
# raid level
lbl = createAlignedLabel(_("RAID _Level:"))
maintable.attach(lbl, 0, 1, row, row + 1)
if not origrequest.exists:
# Create here, pack below
numparts = len(availraidparts)
if origrequest.spares:
nspares = origrequest.spares
else:
nspares = 0
if origrequest.level:
maxspares = mdraidlib.get_raid_max_spares(origrequest.level,
numparts)
else:
maxspares = 0
spareAdj = gtk.Adjustment(value = nspares, lower = 0,
upper = maxspares, step_incr = 1)
self.sparesb = gtk.SpinButton(spareAdj, digits = 0)
self.sparesb.set_data("numparts", numparts)
if maxspares > 0:
self.sparesb.set_sensitive(1)
else:
self.sparesb.set_value(0)
self.sparesb.set_sensitive(0)
else:
self.sparesb = gtk.Label(str(origrequest.spares))
if not origrequest.exists:
self.levelcombo = self.createRaidLevelMenu(mdraidlib.raid_levels,
origrequest.level)
lbl.set_mnemonic_widget(self.levelcombo)
else:
self.levelcombo = gtk.Label(origrequest.level)
maintable.attach(self.levelcombo, 1, 2, row, row + 1)
row = row + 1
# raid members
lbl=createAlignedLabel(_("_RAID Members:"))
maintable.attach(lbl, 0, 1, row, row + 1)
# XXX need to pass in currently used partitions for this device
(self.raidlist, sw) = self.createAllowedRaidPartitionsList(availraidparts,
origrequest.devices,
origrequest.exists)
lbl.set_mnemonic_widget(self.raidlist)
self.raidlist.set_size_request(275, 80)
maintable.attach(sw, 1, 2, row, row + 1)
row = row + 1
if origrequest.exists:
self.raidlist.set_sensitive(False)
# number of spares - created widget above
lbl = createAlignedLabel(_("Number of _spares:"))
maintable.attach(lbl, 0, 1, row, row + 1)
maintable.attach(self.sparesb, 1, 2, row, row + 1)
lbl.set_mnemonic_widget(self.sparesb)
row = row + 1
# format or not?
self.formatButton = None
self.fsoptionsDict = {}
if not format.exists and not origrequest.exists:
self.formatButton = gtk.CheckButton(_("_Format partition?"))
if not format.type:
self.formatButton.set_active(1)
else:
self.formatButton.set_active(0)
# it only makes sense to show this for preexisting RAID
if origrequest.exists:
maintable.attach(self.formatButton, 0, 2, row, row + 1)
row = row + 1
# checkbutton for encryption using dm-crypt/LUKS
if origrequest.format.type == "luks":
self.lukscb.set_active(1)
else:
self.lukscb.set_active(0)
maintable.attach(self.lukscb, 0, 2, row, row + 1)
row = row + 1
else:
(row, self.fsoptionsDict) = createPreExistFSOptionSection(origrequest, maintable, row, self.mountCombo, self.storage, luksdev=luksdev)
# put main table into dialog
dialog.vbox.pack_start(maintable)
dialog.show_all()
self.dialog = dialog
return
class RaidCloneDialog:
def createDriveList(self, disks):
store = gtk.ListStore(gobject.TYPE_STRING)
view = gtk.TreeView(store)
sw = gtk.ScrolledWindow()
sw.add(view)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
sw.set_shadow_type(gtk.SHADOW_IN)
for disk in disks:
iter = store.append()
store.set_value(iter, 0, disk.name)
view.set_property("headers-visible", False)
col = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)
view.append_column(col)
return (sw, view)
def getInterestingRequestsForDrive(self, drive):
disk = self.storage.devicetree.getDeviceByName(drive)
allrequests = self.storage.devicetree.getDependentDevices(disk)
if not allrequests:
return allrequests
# remove extended partitions
requests = []
for req in allrequests:
if req.type == "partition" and req.isExtended:
continue
elif req.type != "partition":
continue
requests.append(req)
return requests
def sanityCheckSourceDrive(self):
# first see if it has any non-software RAID partitions
requests = self.getInterestingRequestsForDrive(self.sourceDrive)
errmsg1 = _("The source drive has no partitions to be cloned. "
"You must first define partitions of type "
"'software RAID' on this drive before it can be cloned.")
if requests is None or len(requests) == 0:
self.intf.messageWindow(_("Source Drive Error"), errmsg1,
custom_icon="error")
return 1
for req in requests:
if req.format.type != "mdmember":
self.intf.messageWindow(_("Source Drive Error"),
_("The source drive you selected has "
"partitions which are not of "
"type 'software RAID'.\n\n"
"You must remove these "
"partitions "
"before this drive can be cloned. "),
custom_icon="error")
return 1
sourceDev = self.storage.devicetree.getDeviceByName(self.sourceDrive)
for req in requests:
if not req.req_disks or len(req.req_disks) > 1 or \
req.req_disks[0] != self.sourceDrive:
self.intf.messageWindow(_("Source Drive Error"),
_("The source drive you selected has "
"partitions which are not "
"constrained to the drive %s.\n\n"
"You must remove these partitions "
"or restrict them to this "
"drive "
"before this drive can be cloned. ")
%(sourceDev.path,), custom_icon="error")
return 1
for req in requests:
if req not in self.storage.unusedMDMembers():
self.intf.messageWindow(_("Source Drive Error"),
_("The source drive you selected has "
"software RAID partition(s) which "
"are members of an active "
"software RAID device.\n\n"
"You must remove these partitions "
"before this drive "
"can be cloned."), custom_icon="error")
return 1
return 0
def sanityCheckTargetDrives(self):
sourceDev = self.storage.devicetree.getDeviceByName(self.sourceDrive)
if self.targetDrives is None or len(self.targetDrives) < 1:
self.intf.messageWindow(_("Target Drive Error"),
_("Please select the target drives "
"for the clone operation."), custom_icon="error")
return 1
if self.sourceDrive in self.targetDrives:
self.intf.messageWindow(_("Target Drive Error"),
_("The source drive %s cannot be "
"selected as a target drive as well.")
% (sourceDev.path,),
custom_icon="error")
return 1
for drive in self.targetDrives:
requests = self.getInterestingRequestsForDrive(drive)
if requests is None:
continue
targetDev = self.storage.devicetree.getDeviceByName(drive)
for req in requests:
rc = self.storage.deviceImmutable(req)
if rc:
self.intf.messageWindow(_("Target Drive Error"),
_("The target drive %(path)s "
"has a partition which cannot "
"be removed for the following "
"reason:\n\n\"%(rc)s\"\n\n"
"You must remove this partition "
"before "
"this drive can be a target.") %
{'path': targetDev.path, 'rc': rc},
custom_icon="error")
return 1
return 0
def cloneDrive(self):
# first create list of interesting partitions on the source drive
requests = self.getInterestingRequestsForDrive(self.sourceDrive)
# no requests to clone, bail out
if not requests:
return 0
# now try to clear the target drives
for devname in self.targetDrives:
device = self.storage.devicetree.getDeviceByName(devname)
doClearPartitionedDevice(self.intf, self.storage,
device, confirm=0, quiet=1)
# now clone!
for req in requests:
for drive in self.targetDrives:
# this feels really dirty
device = self.storage.devicetree.getDeviceByName(drive)
newdev = copy.deepcopy(req)
newdev.req_disks = [device]
newdev.exists = False
newdev.format.exists = False
newdev.format.device = None
self.storage.createDevice(newdev)
return
def targetSelectFunc(self, model, path, iter):
self.targetDrives.append(model.get_value(iter,0))
def run(self):
if self.dialog is None:
return None
while 1:
rc = self.dialog.run()
# user hit cancel, do nothing
if rc in [2, gtk.RESPONSE_DELETE_EVENT]:
self.destroy()
return None
# see what drive they selected as the source
selection = self.sourceView.get_selection()
(model, iter) = selection.get_selected()
if iter is None:
self.intf.messageWindow(_("Error"),
_("Please select a source drive."),
custom_icon="error")
continue
self.sourceDrive = model.get_value(iter, 0)
# sanity check it
if self.sanityCheckSourceDrive():
continue
# now get target drive(s)
self.targetDrives = []
selection = self.targetView.get_selection()
selection.selected_foreach(self.targetSelectFunc)
# sanity check it
if self.sanityCheckTargetDrives():
continue
# now give them last chance to bail
msgtxt = _("The drive %s will now be cloned to the "
"following drives:\n\n" % (self.sourceDrive,))
for drive in self.targetDrives:
msgtxt = msgtxt + "\t" + "%s" % (drive,)
msgtxt = msgtxt + _("\n\nWARNING! ALL DATA ON THE TARGET DRIVES "
"WILL BE DESTROYED.")
rc = self.intf.messageWindow(_("Final Warning"),
msgtxt, type="custom",
custom_buttons = ["gtk-cancel", _("Clone Drives")], custom_icon="warning")
if not rc:
return 0
# try to clone now
ret = self.cloneDrive()
if ret:
self.intf.messageWindow(_("Error"),
_("There was an error clearing the "
"target drives. Cloning failed."),
custom_icon="error")
return 0
# if everything ok, break out
if not ret:
break
return 1
def destroy(self):
if self.dialog:
self.dialog.destroy()
self.dialog = None
def __init__(self, storage, intf, parent):
self.storage = storage
self.intf = intf
self.parent = parent
self.dialog = None
self.dialog = gtk.Dialog(_("Clone Drive Tool"), self.parent)
self.dialog.set_default_size(500, 200)
gui.addFrame(self.dialog)
self.dialog.add_button('gtk-cancel', 2)
self.dialog.add_button('gtk-ok', 1)
self.dialog.set_position(gtk.WIN_POS_CENTER)
# present list of drives as source
vbox = gtk.VBox()
clnmessage = _("This tool clones the layout from a partitioned source "
"onto other similar sized drives. The source must have "
"partitions which are restricted to that drive and must "
"ONLY contain unused software RAID partitions. "
"EVERYTHING on the target drive(s) will be destroyed.\n")
lbl = gui.WrappingLabel(clnmessage)
vbox.pack_start(lbl)
box = gtk.HBox()
lbl = gtk.Label(_("Source Drive:"))
lbl.set_alignment(0.0, 0.0)
box.pack_start(lbl, padding=5)
(sw, self.sourceView) = self.createDriveList(storage.partitioned)
selection = self.sourceView.get_selection()
selection.set_mode(gtk.SELECTION_SINGLE)
box.pack_start(sw, padding=5)
lbl = gtk.Label(_("Target Drive(s):"))
lbl.set_alignment(0.0, 0.0)
box.pack_start(lbl, padding=5)
(sw, self.targetView) = self.createDriveList(storage.partitioned)
selection = self.targetView.get_selection()
selection.set_mode(gtk.SELECTION_MULTIPLE)
box.pack_start(sw, padding=5)
frame = gtk.Frame(_("Drives"))
frame.add(box)
vbox.pack_start(frame)
# put contents into dialog
self.dialog.vbox.pack_start(vbox)
self.dialog.show_all()
return