home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / boot / i386 / root / usr / share / YaST2 / clients / inst_vm_disks.ycp < prev    next >
Text File  |  2006-11-29  |  27KB  |  905 lines

  1. /**
  2.  * Module:    inst_vm_disks.ycp
  3.  *
  4.  * Authors:    Ladislav Slezak <lslezak@suse.cz>
  5.  *        Michael G. Fritch <mgfritch@novell.com>
  6.  *
  7.  * Purpose:    Ask the user for virtual disks configuration.
  8.  *
  9.  * $Id: inst_vm_disks.ycp 32320 2006-08-10 18:08:16Z mgfritch $
  10.  *
  11.  */
  12. {
  13.     textdomain "vm";
  14.  
  15.     import "VM";
  16.     import "VM_Common";
  17.     import "Label";
  18.     import "Popup";
  19.     import "Wizard";
  20.     import "Report";
  21.     import "Sequencer";
  22.     import "Storage";
  23.  
  24.     include "partitioning/lvm_lv_lib.ycp";
  25.  
  26.  
  27.     list<map<string,any> > disks = VM::GetDiskConfig();
  28.     integer selected_disk = nil;
  29.  
  30.  
  31.     /**
  32.     * Disk type dialog - choose type of disk to add: new loopback, existing loopback, or block device (physical)
  33.     * @return dialog result
  34.     */
  35.     define symbol DiskTypeDialog() {
  36.  
  37.     // dialog title
  38.     string caption = _("Add Virtual Disk");
  39.  
  40.     term contents = 
  41.         `MarginBox(1.5, 0.2,
  42.             `RadioButtonGroup(`id(`rbg),
  43.                 `Frame( caption,
  44.                     `VBox(
  45.                         `VSpacing(0.5),
  46.                         `Left(`RadioButton(`id(`create), _("&Create New Disk Image"), true)),
  47.                         `VSpacing(0.5),
  48.                         `Left(`RadioButton(`id(`use), _("&Use Existing Disk Image"))),
  49.                         `VSpacing(0.5),
  50.                         `Left(`RadioButton(`id(`block), _("U&se Block Device"))),
  51.                         `VSpacing(0.5)
  52.                     )
  53.                 )
  54.             )
  55.         );
  56.  
  57.     // Add virtual disk - help text 1/2
  58.     string help_text = sformat(_("<p><b><big>%1</big></b></p>"), caption)
  59.         // Add virtual disk - help text 2/2
  60.         + _("<p>A virtual disk can be added by creating a new disk image, selecting an existing disk image, or specifying a block device.</p>");
  61.  
  62.     Wizard::SetContents(caption, contents, help_text, true, true);
  63.  
  64.     symbol ui = `dummy;
  65.  
  66.     while(!contains([`next, `back, `abort, `cancel], ui)) {
  67.         ui = (symbol) UI::UserInput();
  68.         if (ui == `abort || ui == `cancel) {
  69.             if (!Popup::ReallyAbort(VM_Common::GetModified()))
  70.                 ui = `again;
  71.         }
  72.     }
  73.  
  74.     if (ui == `next) {
  75.         ui = (symbol) UI::QueryWidget (`id(`rbg), `CurrentButton);
  76.     }
  77.  
  78.     return ui;
  79.     }
  80.  
  81.  
  82.  
  83.     /**
  84.     * Add(create) an new loopback image
  85.     * @param device target device in a VM
  86.     * @param size of the device (in MB)
  87.     * @param space true = create a space image file
  88.     * @param ro true = read-only access, false = read/write access
  89.     * @return map<string, any>
  90.     */
  91.     define map<string,any> AddNewLoopBackDialog (string device, string prefix, integer s, boolean sparse, boolean ro) ``{
  92.  
  93.     list<string> valid_virtual_devices = VM::GetValidVirtualDevices(VM::GetConfiguredDiskTargets(disks));
  94.  
  95.     // dialog title
  96.     string caption = _("Create New Disk Image");
  97.  
  98.     term contents =
  99.         `MarginBox(1.5, 0.2,
  100.             `Frame(caption,
  101.                 `MarginBox(1.5, 0.2,
  102.                     `VBox(
  103.                         `ComboBox(`id(`device), `opt(`editable,`hstretch),
  104.                             // combobox label - the device name as it appers to the virtual machine
  105.                             _("&Virtual Disk"),
  106.                             valid_virtual_devices),
  107.                         // label - directory where the disk image will be created
  108.                         `TextEntry(`id(`dir), `opt(`hstretch), _("&Disk Image Directory"), prefix),
  109.                         `ComboBox(`id(`size), `opt(`editable,`hstretch),
  110.                             // combobox label
  111.                             _("Maximum &Size in MB"),
  112.                             ["1024", "2048", "4096", "8192"]),
  113.                         `VSpacing(1.5),
  114.                         `Left(`CheckBox(`id(`sparse), _("Create &Sparse Image File"), sparse)),
  115.                         `Left(`CheckBox(`id(`ro), _("&Read-Only Access"), ro)),
  116.                         `VSpacing(0.2)
  117.                     )
  118.                 )
  119.             )
  120.         );
  121.  
  122.     // Create New Disk Image - help text 1/4
  123.     string help_text = sformat(_("<p><b><big>%1</big></b></p>"), caption);
  124.     // Create New Disk Image - help text 2/4
  125.     help_text = help_text + _("<p>The <b>virtual disk</b> configuration describes how the virtual disk appears to the virtual machine. This controls the ordering of virtual disks within the VM, and is specified using the Linux naming convention.</p>");
  126.     // Create New Disk Image - help text 3/4
  127.     help_text = help_text + _("<p>The disk image file will be created in the <b>disk image directory</b> with the specified <b>maximum size</b>. A <b>sparse image file</b> only allocates disk space as it is needed.</p>");
  128.     // Create New Disk Image - help text 4/4
  129.     help_text = help_text + _("<p>Virtual disks can be safely shared among multiple VMs only if every use of the disk is marked <b>read-only</b>. If it is read-only, it cannot be modified by the VM.</p>");
  130.  
  131.     Wizard::SetContents(caption, contents, help_text, true, true);
  132.  
  133.     UI::ChangeWidget (`id(`device), `Value, device);
  134.     UI::ChangeWidget (`id(`device), `InputMaxLength, 255);
  135.     UI::ChangeWidget (`id(`size), `ValidChars, "0123456789");
  136.     UI::ChangeWidget (`id(`size), `Value, tostring (s));
  137.  
  138.     map<string,any> ret = nil;
  139.  
  140.     symbol ui = `dummy;
  141.     while(!contains([`next, `accept, `back, `abort, `cancel], ui)) {
  142.  
  143.         ui = (symbol) UI::UserInput();
  144.         y2debug("ui: %1", ui);
  145.  
  146.         if (ui == `abort || ui == `cancel) {
  147.         if (!Popup::ReallyAbort(VM_Common::GetModified()))
  148.             ui = `again;
  149.         }
  150.         else if (ui == `next || ui == `accept) {
  151.         boolean sparse_file = (boolean) UI::QueryWidget (`id(`sparse), `Value);
  152.         string dev = (string) UI::QueryWidget (`id(`device), `Value);
  153.         string new_dir = (string) UI::QueryWidget (`id(`dir), `Value);
  154.         string sz = (string)UI::QueryWidget (`id(`size), `Value);
  155.         integer sz_int = tointeger(sz) ;
  156.         boolean new_ro = (boolean) UI::QueryWidget (`id(`ro), `Value);
  157.  
  158.         if (dev == nil || dev == "") {
  159.             // error message
  160.             Report::Error(_("The Virtual Disk field cannot be left empty."));
  161.             ui = `again;
  162.         }
  163.  
  164.         // make sure the dev has not already been defined for another virtual disk...
  165.         list<string> used_devices = VM::GetConfiguredDiskTargets(disks);
  166.         if (contains(used_devices, dev) && ui != `again) {
  167.             // popup error message
  168.             Report::Error(_("The Virtual Disk has already been used
  169. with another configured virtual disk image.
  170.  
  171. Please select a different Virtual Disk."));
  172.             ui = `again;
  173.             continue;
  174.         }
  175.  
  176.         // make sure the new disk image does not already exist
  177.         string fullpath = new_dir;
  178.         if (findlastof(fullpath, "/") < size(fullpath)-1) { // no ending /
  179.             fullpath = sformat("%1/%2/", new_dir, VM::GetConfigName());
  180.         }
  181.         else {
  182.             fullpath = sformat("%1%2/", new_dir, VM::GetConfigName());
  183.         }
  184.         list<string> existing_disks = (list<string>)SCR::Read(.target.dir, fullpath); // list of image dir names
  185.         if (contains(existing_disks, dev) && ui != `again) {
  186.             // popup error message
  187.             Report::Error(_("The Virtual Disk has already been used
  188. with another configured virtual disk image.
  189.  
  190. Please select a different Virtual Disk."));
  191.             ui = `again;
  192.             continue;
  193.         }
  194.  
  195.  
  196.  
  197.  
  198.  
  199.  
  200.  
  201.         if ((new_dir == nil || substring(new_dir, 0, 1) != "/") && ui != `again) {
  202.             // error message
  203.             Report::Error(_("The Disk Image Directory must be an absolute path."));
  204.             ui = `again;
  205.         }
  206.  
  207.         if ((sz_int == nil || sz_int == 0) && ui != `again) {
  208.             // error message
  209.             Report::Error(_("An invalid disk size was specified."));
  210.             ui = `again;
  211.         }
  212.  
  213.         if (!contains(valid_virtual_devices, dev) && ui != `again) {
  214.             // popup error message with continue/cancel buttons
  215.             if (!Popup::ContinueCancelHeadline(_("Warning"), _("The Virtual Disk might not be a supported virtual device.")))
  216.             ui = `again;
  217.         }
  218.  
  219.         if (ui == `next || ui == `accept) { // no input errors
  220.             ret = $[ "prefix" : new_dir, "source" : dev, "target" : dev, "sparse" : sparse_file, "ro" : new_ro, "type" : "loop-create", "size" : sz_int ];
  221.         }
  222.         }
  223.     }
  224.  
  225.     y2debug("ret: %1", ret);
  226.     return ret;
  227.     }
  228.  
  229.  
  230.     /**
  231.     * Add an existing loopback image
  232.     * @param device target device in a VM
  233.     * @param source image file
  234.     * @param ro true = read-only access, false = read/write access
  235.     * @return map<string, any>
  236.     */
  237.     define map<string,any> AddExistingLoopBackDialog(string device, string source, boolean ro) ``{
  238.  
  239.     list<string> valid_virtual_devices = VM::GetValidVirtualDevices(VM::GetConfiguredDiskTargets(disks));
  240.  
  241.     // dialog title
  242.     string caption = _("Use Existing Disk Image");
  243.  
  244.     term contents = 
  245.         `MarginBox(1.5, 0.2,
  246.             `Frame(caption,
  247.                 `MarginBox(1.5, 0.2,
  248.                     // heading in a popup dialog
  249.                     `VBox(
  250.                         `ComboBox(`id(`device), `opt(`editable,`hstretch),
  251.                             // combobox label - the device name as it appers to the virtual machine
  252.                             _("&Virtual Disk"),
  253.                             valid_virtual_devices),
  254.                         `HBox(
  255.                             `TextEntry(`id(`source), `opt(`hstretch), _("&Disk Image File"), source),
  256.                             `HSpacing(1),
  257.                             `VBox(
  258.                                 `Label(""),
  259.                                 `PushButton(`id(`select_file), _("&Select Image..."))
  260.                             )
  261.                         ),
  262.                         `VSpacing(1.5),
  263.                         `Left(`CheckBox(`id(`ro), _("&Read-Only Access"), ro)),
  264.                         `VSpacing(0.2)
  265.                     )
  266.                 )
  267.             )
  268.         );
  269.  
  270.     // Use Existing Disk Image- help text 1/4
  271.     string help_text = sformat(_("<p><b><big>%1</big></b></p>"), caption);
  272.     // Use Existing Disk Image - help text 2/4
  273.     help_text = help_text + _("<p>The <b>virtual disk</b> configuration describes how the virtual disk appears to the virtual machine. This controls the ordering of virtual disks within the VM, and is specified using the Linux naming convention.</p>");
  274.     // Use Existing Disk Image - help text 1/4
  275.     help_text = help_text + _("<p>The <b>disk image file</b> is an existing image file to be used by the VM.</p>");
  276.     // Use Existing Disk Image - help text 1/4
  277.     help_text = help_text + _("<p>Virtual disks can be safely shared among multiple VMs only if every use of the disk is marked read-only. If it is <b>read-only</b>, it cannot be modified by the VM.</p>");
  278.  
  279.     Wizard::SetContents(caption, contents, help_text, true, true);
  280.  
  281.     UI::ChangeWidget (`id(`device), `Value, device);
  282.     UI::ChangeWidget (`id(`device), `InputMaxLength, 255);
  283.  
  284.     map<string,any> ret = nil;
  285.  
  286.     symbol ui = `dummy;
  287.     while(!contains([`next, `accept, `back, `abort, `cancel], ui)) {
  288.  
  289.         ui = (symbol) UI::UserInput();
  290.         string new_src = (string) UI::QueryWidget (`id(`source), `Value);
  291.  
  292.         if (ui == `abort || ui == `cancel) {
  293.         if (!Popup::ReallyAbort(VM_Common::GetModified()))
  294.             ui = `again;
  295.         }
  296.         else if (ui == `next || ui == `accept) {
  297.         string dev = (string) UI::QueryWidget (`id(`device), `Value);
  298.         boolean new_ro = (boolean) UI::QueryWidget (`id(`ro), `Value);
  299.  
  300.         if (dev == nil || dev == "") {
  301.             // error message
  302.             Report::Error(_("The Virtual Disk field cannot be left empty."));
  303.             ui = `again;
  304.         }
  305.  
  306.         // make sure the dev has not already been defined for another virtual disk...
  307.         list<string> used_devices = VM::GetConfiguredDiskTargets(disks);
  308.         if (contains(used_devices, dev) && ui != `again) {
  309.             // popup error message
  310.             Report::Error(_("The Virtual Disk has already been used
  311. with another configured virtual disk image.
  312.  
  313. Please, select a different Virtual Disk."));
  314.             ui = `again;
  315.         }
  316.  
  317.         if ((new_src == nil || new_src == "") && ui != `again) {
  318.             // error message
  319.             Report::Error(_("The Disk Image File field cannot be left empty."));
  320.             ui = `again;
  321.         }
  322.  
  323.         if ((new_src == nil || substring(new_src, 0, 1) != "/") && ui != `again) {
  324.             // error message
  325.             Report::Error(_("The Disk Image File must be an absolute path."));
  326.             ui = `again;
  327.         }
  328.  
  329.         if (ui != `again) {
  330.             // check whether the file exists
  331.             if ((integer)SCR::Read(.target.size, new_src) < 0) {
  332.             // popup error message
  333.             Report::Error(_("The specified Disk Image File does not exist."));
  334.             ui = `again;
  335.             }
  336.             else if (! new_ro) {
  337.             // If file is read-only in the filesystem, the user must check read-only
  338.             integer i = (integer)SCR::Execute(.target.bash, sformat("stat --format=%%A %1| grep -q w", new_src));
  339.             if (i != 0) {
  340.                 // File is read-only; UI specified read-write.
  341.                 // popup error message with continue/cancel buttons
  342.                 if (!Popup::ContinueCancelHeadline(_("Warning"), _("The Disk Image File is read-only in the filesystem,
  343. so it will be exported to the VM as read-only.")))
  344.                 ui = `again;
  345.                 else
  346.                 new_ro = true;
  347.             }
  348.             }
  349.         }
  350.  
  351.         if (!contains(valid_virtual_devices, dev) && ui != `again) {
  352.             // popup error message with continue/cancel buttons
  353.             if (!Popup::ContinueCancelHeadline(_("Warning"), _("The Virtual Disk might not be a supported virtual device.")))
  354.             ui = `again;
  355.         }
  356.  
  357.         if (ui == `next || ui == `accept) { // no input errors
  358.             ret = $[ "source" : new_src, "target" : dev, "type" : "loop-use", "ro" : new_ro ];
  359.         }
  360.         }
  361.         else if (ui == `select_file) {
  362.         new_src = UI::AskForExistingFile(new_src, "*", _("Select Disk Image"));
  363.         if (new_src != nil) {
  364.             UI::ChangeWidget(`id(`source), `Value, new_src);
  365.         }
  366.         }
  367.     }
  368.  
  369.     y2debug("ret: %1", ret);
  370.     return ret;
  371.     }
  372.  
  373.  
  374.     define list<map> getBlockDevicesList() {
  375.     // Read /proc/partitions to generate a list of available physical devices.
  376.     list<map> devmap = (list<map>)SCR::Read(.proc.partitions);
  377.     y2milestone("current partitions (/proc/partitions): %1", devmap);
  378.  
  379.     // Add Logical Volumes (LVM) to the list of available physical devices. See Bugzilla #186930.
  380.     map<string,map> targetMap = Storage::GetTargetMap();
  381.     list<map> parts_lv   = get_lvs_and_mounted_partitions( targetMap, true, "none" );
  382.     y2milestone("parts_lv (get_lvs_and_mounted_partitions): %1", parts_lv);
  383.     foreach(map m, parts_lv, {
  384.         if (m["type"]:`none == `lvm) {
  385.             boolean dev_exists = false;
  386.             string lv_name = sformat("%1/%2", m["lvm_name"]:"", m["name"]:"");
  387.             integer lv_size = m["size_k"]:-1;
  388.             // don't add the device if it already exists in the list of available physical devices.
  389.             foreach(map m, devmap, { if (m["name"]:"" == lv_name) dev_exists = true; });
  390.             if ( !dev_exists ) {
  391.                 devmap = add(devmap, $["name":lv_name, "size":lv_size]);
  392.             }
  393.         }
  394.     });
  395.  
  396.     y2milestone("devmap: %1", devmap);
  397.     return devmap;
  398.     }
  399.  
  400.  
  401.     /**
  402.     * Add a block device (in dom0)
  403.     * @param device target device in a VM
  404.     * @param phys source (physical) device (in dom0)
  405.     * @param ro true = read-only access, false = read/write access
  406.     * @return map<string, any>
  407.     */
  408.     define map<string,any> AddBlockDeviceDialog(string device, string phys, boolean ro) ``{
  409.  
  410.     list<map> devmap = getBlockDevicesList();
  411.     list<map> mounted = (list<map>) SCR::Read(.proc.mounts);
  412.     y2milestone("mounted partitions (/proc/mounts): %1", mounted);
  413.     list<map> swaps = (list<map>) SCR::Read(.proc.swaps);
  414.     y2milestone("swap partitions (/proc/swaps): %1", swaps);
  415.  
  416.     // Remove currently mounted partitions from the list of available physical devices.  See bugzilla #173433.
  417.     foreach(map m, mounted, {
  418.         // remove the partition from the list of devices (i.e. hda1, hda2, hdb1, etc.)
  419.         devmap = filter(map d, devmap, {
  420.             return (m["spec"]:"" != sformat("/dev/%1", d["name"]:""));
  421.         });
  422.         // remove the entire device from the list of devices (i.e. hda, hdb, etc.)
  423.         devmap = filter(map d, devmap, {
  424.             return (regexpsub(m["spec"]:"", "(.*)[[0123456789]]*$", "\\1") != sformat("/dev/%1", d["name"]:""));
  425.         });
  426.     });
  427.  
  428.     // Remove any dom0 swap partitions from the list of available physical devices.
  429.     foreach(map m, swaps, {
  430.         // remove the partition from the list of devices (i.e. hda1, hda2, hdb1, etc.)
  431.         devmap = filter(map d, devmap, {
  432.             return (m["file"]:"" != sformat("/dev/%1", d["name"]:""));
  433.         });
  434.         // remove the entire device from the list of devices (i.e. hda, hdb, etc.)
  435.         devmap = filter(map d, devmap, {
  436.             return (regexpsub(m["file"]:"", "(.*)[[0123456789]]*$", "\\1") != sformat("/dev/%1", d["name"]:""));
  437.         });
  438.     });
  439.  
  440.     // Remove all loopback devices from the list of avaliable physical devices.
  441.     devmap = filter(map d, devmap, {
  442.         return (regexpmatch(d["name"]:"", ".*loop.*") != true); // don't include device names that contain the string 'loop'
  443.     });
  444.  
  445.  
  446.     y2milestone("list of available partitions: %1", devmap);
  447.  
  448.     list<string> valid_phys_devices = [];
  449.     list<term> valid_phys_devices_term = [];
  450.  
  451.     // generate a list of valid block devices (in dom0)
  452.     if (devmap != nil) {
  453.         foreach(map d, devmap, {
  454.             string name = d["name"]:"";
  455.             if (name != nil && name != "") {
  456.                 name = "/dev/" + name;
  457.                 // Don't add currently mounted partitions to the list of available physical devices.  See bugzilla #173433.
  458.                 boolean isMounted = false;
  459.                 foreach(map m, mounted, {
  460.                     if (m["spec"]:"" == name) {
  461.                         isMounted = true;
  462.                         break;
  463.                     }
  464.                 });
  465.  
  466.                 if (isMounted == false) {
  467.                     valid_phys_devices_term = add(valid_phys_devices_term, `item(`id(name), name));
  468.                     valid_phys_devices = add(valid_phys_devices, name);
  469.                 }
  470.             }
  471.         });
  472.     }
  473.  
  474.     list<string> valid_virtual_devices = VM::GetValidVirtualDevices(VM::GetConfiguredDiskTargets(disks));
  475.  
  476.     // dialog title
  477.     string caption = _("Use Block Device");
  478.  
  479.     term contents =
  480.         `MarginBox(1.5, 0.2,
  481.             `Frame(caption,
  482.                 `MarginBox(1.5, 0.2,
  483.                     // heading in a popup dialog
  484.                     `VBox(
  485.                         `ComboBox(`id(`device), `opt(`editable,`hstretch),
  486.                             // combobox label - the device name as it appers to the virtual machine
  487.                             _("&Virtual Disk"),
  488.                             valid_virtual_devices),
  489.                         `ComboBox(`id(`phys), `opt(`editable, `hstretch),
  490.                             // combobox label - the block device to use (in dom0)
  491.                             _("Block Device"),
  492.                             valid_phys_devices_term),
  493.                         `VSpacing(1.5),
  494.                         `Left(`CheckBox(`id(`ro), _("&Read-Only Access"), ro)),
  495.                         `VSpacing(0.2)
  496.                     )
  497.                 )
  498.             )
  499.         );
  500.     // Use Block Device - help text 1/4
  501.     string help_text = sformat(_("<p><b><big>%1</big></b></p>"), caption);
  502.     // Use Block Device - help text 2/4
  503.     help_text = help_text + _("<p>The <b>virtual disk</b> configuration describes how the virtual disk appears to the virtual machine. This controls the ordering of virtual disks within the VM, and is specified using the Linux naming convention.</p>");
  504.     // Use Block Device - help text 3/4
  505.     help_text = help_text + _("<p>The <b>block device</b> is a block device on the VM Server.</p>");
  506.     // Use Block Device - help text 4/4
  507.     help_text = help_text + _("<p>Virtual disks can be safely shared among multiple VMs only if every use of the disk is marked read-only. If it is <b>read-only</b>, it cannot be modified by the VM.</p>");
  508.  
  509.     Wizard::SetContents(caption, contents, help_text, true, true);
  510.  
  511.     UI::ChangeWidget (`id(`device), `Value, device);
  512.     UI::ChangeWidget (`id(`device), `InputMaxLength, 255);
  513.     UI::ChangeWidget(`id(`phys), `Value, phys);
  514.     UI::ChangeWidget (`id(`phys), `InputMaxLength, 255);
  515.  
  516.     string new_dev = "";
  517.     string new_phys = "";
  518.  
  519.     symbol ui = `dummy;
  520.     while(!contains([`next, `back, `abort, `cancel], ui)) {
  521.  
  522.         ui = (symbol) UI::UserInput();
  523.  
  524.         if (ui == `abort || ui == `cancel) {
  525.         if (!Popup::ReallyAbort(VM_Common::GetModified()))
  526.             ui = `again;
  527.         }
  528.         else if (ui == `next) {
  529.         new_dev = (string) UI::QueryWidget (`id(`device), `Value);
  530.         new_phys = (string) UI::QueryWidget (`id(`phys), `Value);
  531.  
  532.         if (new_dev == nil || new_dev == "") {
  533.             // error message
  534.             Report::Error(_("The Virtual Disk field cannot be left empty."));
  535.             ui = `again;
  536.         }
  537.  
  538.         // make sure the dev has not already been defined for another virtual disk...
  539.         list<string> used_devices = VM::GetConfiguredDiskTargets(disks);
  540.         if (contains(used_devices, new_dev) && ui != `again) {
  541.             // popup error message
  542.             Report::Error(_("The Virtual Disk has already been used
  543. with another configured virtual disk image.
  544.  
  545. Please select a different Virtual Disk."));
  546.             ui = `again;
  547.         }
  548.  
  549.         if ((new_phys == nil || new_phys == "") && ui != `again) {
  550.             // error message
  551.             Report::Error(_("The Block Device field cannot be left empty."));
  552.             ui = `again;
  553.         }
  554.  
  555.         if (!contains(valid_virtual_devices, new_dev) && ui != `again) {
  556.             // popup error message with continue/cancel buttons
  557.             if (!Popup::ContinueCancelHeadline(_("Warning"), _("The Virtual Disk might not be a supported virtual device.")))
  558.             ui = `again;
  559.         }
  560.  
  561.         if (!contains(valid_phys_devices, new_phys) && ui != `again) {
  562.             // popup error message with continue/cancel buttons
  563.             if (!Popup::ContinueCancelHeadline(_("Warning"), _("The Block Device might not be a valid block device in domain 0.")))
  564.                 ui = `again;
  565.         }
  566.         
  567.         }
  568.     }
  569.  
  570.     boolean new_ro = (boolean) UI::QueryWidget (`id(`ro), `Value);
  571.  
  572.     if (ui != `next) {
  573.         return nil;
  574.     }
  575.  
  576.     map<string,any> ret = $[ "source" : new_phys, "target" : new_dev, "ro" : new_ro, "type" : "phys" ];
  577.  
  578.     y2debug("ret: %1", ret);
  579.     return ret;
  580.     }
  581.  
  582.  
  583.  
  584. /**
  585.  * Redraw the DiskOverview table
  586.  * @param list<map<string,any> > containing all the current disk settings
  587.  * @return void
  588.  */
  589.     define void refresh_table(list<map<string,any> > disks) {
  590.     integer id_cnt = 0;
  591.     list <term> d = [];
  592.     list<map> devmap = getBlockDevicesList();
  593.  
  594.     map<string,integer> device_sizes = $[];
  595.     foreach(map device, devmap, {
  596.         string name = device["name"]:"";
  597.         integer sz = device["size"]:0;
  598.  
  599.         if (name != nil && name != "")
  600.         {
  601.             device_sizes = add(device_sizes, name, sz);
  602.         }
  603.         }
  604.     );
  605.     y2milestone("device_sizes: %1", device_sizes);
  606.  
  607.     foreach (map<string,any> m, disks, ``{
  608.  
  609.         y2milestone("disk: %1", m);
  610.  
  611.         integer sz = -1;
  612.         string prefix = "";
  613.  
  614.         if (m["type"]:"" == "loop-create") {
  615.         prefix = m["prefix"]:VM::GetImgPrefix();
  616.         if (findlastof(prefix, "/") < size(prefix)-1) { // no ending /
  617.             prefix = sformat("%1/%2/", m["prefix"]:VM::GetImgPrefix(), VM::GetConfigName());
  618.         }
  619.         else {
  620.             prefix = sformat("%1%2/", m["prefix"]:VM::GetImgPrefix(), VM::GetConfigName());
  621.         }
  622.         sz = m["size"]:0; // size is already in MB
  623.         }
  624.         else if (m["type"]:"" == "loop-use") {
  625.         sz = (integer)SCR::Read(.target.size, m["source"]:"");
  626.         if (sz >= 0) {
  627.             // calculate size in MB
  628.             sz = (sz / 1024) / 1024;
  629.         }
  630.         }
  631.         else if (m["type"]:"" == "phys") {
  632.         prefix = "";
  633.         // get size of the device, it's in sectors (512B)   (x*0.5 = x*512/1024)
  634.         sz = (device_sizes[regexpsub(m["source"]:"", "/dev/(.*)", "\\1")]:0) / 1024;  // calculate size in MB
  635.         }
  636.  
  637.         string sz_str = "";
  638.         if (sz == nil || sz < 0)
  639.         sz_str = "";
  640.         else
  641.         sz_str = tostring(sz);
  642.  
  643.         y2milestone("sz: %1", sz);
  644.  
  645.         d = add(d, `item (`id(id_cnt), m["target"]:"", prefix + m["source"]:"", sz_str, (m["ro"]:false) ? UI::Glyph(`CheckMark) : "", (m["sparse"]:false) ? UI::Glyph(`CheckMark) : ""));
  646.         id_cnt = id_cnt + 1;
  647.     });
  648.  
  649.     UI::ChangeWidget (`id(`disks), `Items, d);
  650.     }
  651.  
  652.  
  653. /**
  654.  * Display an overview table containing a list of configured disks, with add, edit, and delete buttons
  655.  * @return dialog result
  656.  */
  657. define symbol DiskOverview() {
  658.     // build and show dialog
  659.  
  660.     // dialog title - virtual disk configuration
  661.     string caption = _("Virtual Disks");
  662.  
  663.     // table - column heading
  664.     term heading = `header (_("Virtual Disk"), _("Source"), `Right(_("Disk Size (MB)")), `Center(_("Read-Only")), `Center(_("Sparse File")));
  665.  
  666.     term contents =
  667.         `MarginBox(1.5, 1,
  668.             `VBox(
  669.                 `Table(`id(`disks), heading, []),
  670.                 `HBox(
  671.                     `PushButton (`id (`add), Label::AddButton()),
  672.                     `PushButton (`id (`edit), Label::EditButton()),
  673.                     `PushButton (`id (`delete), Label::DeleteButton())
  674.                 )
  675.             )
  676.         );
  677.  
  678.  
  679.     // Virtual Disks - help text 1/2
  680.     string help_text = sformat(_("<p><b><big>%1</big><b></p>"), caption);
  681.     // Virtual Disks - help text 2/2
  682.     help_text = help_text + _("<p>A virtual machine can have one or more virtual disks. Each virtual disk points to a file or block device on the VM Server.</p>");
  683.  
  684.     Wizard::SetContents (caption, contents, help_text, true, true);
  685.     Wizard::SetNextButton(`next, Label::AcceptButton());
  686.     Wizard::SetAbortButton(`abort, Label::CancelButton());
  687.     Wizard::HideBackButton();
  688.  
  689.     refresh_table(disks);
  690.  
  691.     symbol ret = nil;
  692.  
  693.     while (true) {
  694.  
  695.     ret = (symbol) Wizard::UserInput();
  696.  
  697.     if (ret == `abort || ret == `cancel) {
  698.         if (Popup::ReallyAbort(VM_Common::GetModified()))
  699.             break;
  700.     }
  701.     else if (ret == `back || ret == `add) {
  702.         break;
  703.     }
  704.     else if (ret == `edit) {
  705.         selected_disk = (integer) UI::QueryWidget (`id(`disks), `CurrentItem);
  706.         break;
  707.     }
  708.     else if (ret == `delete) {
  709.         integer id = (integer) UI::QueryWidget (`id(`disks), `CurrentItem);
  710.  
  711.         if (id != nil)
  712.         {
  713.         list<map<string,any> > new_disks = [];
  714.  
  715.         foreach(map<string,any> m, disks, ``{
  716.             if (id != 0)
  717.             {
  718.                 new_disks = add(new_disks, m);
  719.             }
  720.             id = id - 1;
  721.             }
  722.         );
  723.  
  724.         disks = new_disks;
  725.         refresh_table(disks);
  726.         }
  727.     }
  728.     else if (ret == `next) {
  729.         if (size (disks) <= 0)
  730.         {
  731.         // error popup
  732.         string message = _("At least one disk is required.");
  733.         Popup::Message (message);
  734.         continue;
  735.         }
  736.  
  737.         VM::SetDiskConfig(disks);
  738.         break;
  739.     }
  740.     else {
  741.         y2error("unexpected retcode: %1", ret);
  742.         continue;
  743.     }
  744.     }
  745.  
  746.     Wizard::SetNextButton(`next, Label::NextButton());
  747.     Wizard::SetAbortButton(`abort, Label::AbortButton());
  748.     Wizard::RestoreBackButton();
  749.  
  750.     return ret;
  751. }
  752.  
  753.  
  754. symbol AddNewLoopBack() {
  755.     map<string,any> m = AddNewLoopBackDialog("", VM::GetImgPrefix(), 4096, true, false);
  756.     symbol ret = `back;
  757.  
  758.     if (m != nil)
  759.     {
  760.     disks = add(disks, m);
  761.     ret = `next;
  762.     }
  763.  
  764.     y2milestone("AddNewLoopBack: %1", ret);
  765.  
  766.     return ret;
  767. }
  768.  
  769. symbol AddExistingLoopBack() {
  770.     map<string,any> m = AddExistingLoopBackDialog("", "", false);
  771.     symbol ret = `back;
  772.  
  773.     if (m != nil)
  774.     {
  775.     disks = add(disks, m);
  776.     ret = `next;
  777.     }
  778.  
  779.     y2milestone("AddExistingLoopBack: %1", ret);
  780.  
  781.     return ret;
  782. }
  783.  
  784. symbol AddBlockDevice() {
  785.     map<string,any> m = AddBlockDeviceDialog("", "", false);
  786.  
  787.     symbol ret = `back;
  788.  
  789.     if (m != nil)
  790.     {
  791.     disks = add(disks, m);
  792.     ret = `next;
  793.     }
  794.  
  795.     y2milestone("AddBlockDevice: %1", ret);
  796.  
  797.     return ret;
  798. }
  799.  
  800.  
  801. /**
  802.  * Edit a select disk in the disk overview table
  803.  * @return dialog result
  804.  */
  805. symbol EditDisk() {
  806.     if (selected_disk != nil)
  807.     {
  808.     map<string,any> d = disks[selected_disk]:$[];
  809.     map<string,any> new_disk = nil;
  810.  
  811.     // Temporarily remove the selected disk, to avoid false warnings.
  812.     disks[selected_disk] = $[];
  813.  
  814.     if (d["type"]:"" == "phys")
  815.     {
  816.         new_disk = AddBlockDeviceDialog(d["target"]:"", d["source"]:"", d["ro"]:false);
  817.     }
  818.     else if (d["type"]:"" == "loop-create")
  819.     {
  820.         new_disk = AddNewLoopBackDialog(d["target"]:"", d["prefix"]:VM::GetImgPrefix(), d["size"]:4096, d["sparse"]:false, d["ro"]:false);
  821.     }
  822.     else if (d["type"]:"" == "loop-use")
  823.     {
  824.         new_disk = AddExistingLoopBackDialog(d["target"]:"", d["source"]:"", d["ro"]:false);
  825.     }
  826.     else
  827.     {
  828.         y2error("Unknown disk type %1", d["type"]:"");
  829.     }
  830.  
  831.     if (new_disk != nil)
  832.         disks[selected_disk] = new_disk;
  833.     else
  834.         disks[selected_disk] = d;
  835.     }
  836.  
  837.     return `next;
  838. }
  839.  
  840.  
  841.  
  842.  
  843.     Wizard::OpenNextBackDialog();
  844.  
  845.     map aliases = $[
  846.         "DiskOverview"        : ``(DiskOverview()),
  847.         "AddNewLoopBack"        : ``(AddNewLoopBack()),
  848.         "AddExistingLoopBack"    : ``(AddExistingLoopBack()),
  849.         "DiskType"            : ``(DiskTypeDialog()),
  850.         "AddBlockDevice"        : ``(AddBlockDevice()),
  851.         "EditDisk"            : ``(EditDisk()),
  852.     ];
  853.  
  854.     map sequence = $[
  855.         "ws_start"    : "DiskOverview",
  856.         "DiskOverview"    :
  857.         $[
  858.             `next        :    `next,
  859.             `add        :    "DiskType",
  860.             `edit        :    "EditDisk",
  861.             `abort        :    `abort
  862.         ],
  863.         "DiskType"        :
  864.         $[
  865.             `create        :    "AddNewLoopBack",
  866.             `use        :    "AddExistingLoopBack",
  867.             `block        :    "AddBlockDevice",
  868.             `abort        :    `abort
  869.         ],
  870.         "AddNewLoopBack"    :
  871.         $[
  872.             `next        :    "DiskOverview",
  873.             `abort        :    `abort
  874.         ],
  875.         "AddExistingLoopBack"    :
  876.         $[
  877.             `next        :    "DiskOverview",
  878.             `abort        :    `abort
  879.         ],
  880.         "AddBlockDevice"    :
  881.         $[
  882.             `next        :    "DiskOverview",
  883.             `abort        :    `abort
  884.         ],
  885.         "EditDisk"        :
  886.         $[
  887.             `next        :    "DiskOverview",
  888.             `abort        :    `abort
  889.         ]
  890.         ];
  891.  
  892.  
  893.     symbol ret = Sequencer::Run(aliases, sequence);
  894.  
  895.     Wizard::CloseDialog();
  896.  
  897.     if (ret == `next || ret == `finish) {
  898.     if (VM_Common::proposal_type != "install") {
  899.         VM::ResetSource();
  900.     }
  901.     }
  902.  
  903.     return ret;
  904. }
  905.