#
# Classes for building disk device xml
#
# Copyright 2006-2008, 2012  Red Hat, Inc.
# Jeremy Katz <katzj@redhat.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 os
import stat
import pwd
import statvfs
import subprocess
import logging
import re

import urlgrabber.progress as progress
import libvirt

import virtinst
import _util
import Storage
from VirtualDevice import VirtualDevice
from XMLBuilderDomain import _xml_property
from virtinst import _gettext as _

def _vdisk_create(path, size, kind, sparse=True):
    force_fixed = "raw"
    path = os.path.expanduser(path)
    if kind in force_fixed or not sparse:
        _type = kind + ":fixed"
    else:
        _type = kind + ":sparse"
    try:
        rc = subprocess.call([ '/usr/sbin/vdiskadm', 'create', '-t', _type,
            '-s', str(size), path ])
        return rc == 0
    except OSError:
        return False

def _vdisk_clone(path, clone):
    logging.debug("Using vdisk clone.")

    path = os.path.expanduser(path)
    clone = os.path.expanduser(clone)
    try:
        rc = subprocess.call([ '/usr/sbin/vdiskadm', 'clone', path, clone ])
        return rc == 0
    except OSError:
        return False

def _qemu_sanitize_drvtype(phystype, fmt, manual_format=False):
    """
    Sanitize libvirt storage volume format to a valid qemu driver type
    """
    raw_list = [ "iso" ]

    if phystype == VirtualDisk.TYPE_BLOCK:
        if not fmt:
            return VirtualDisk.DRIVER_QEMU_RAW
        if fmt and not manual_format:
            return VirtualDisk.DRIVER_QEMU_RAW

    if fmt in raw_list:
        return VirtualDisk.DRIVER_QEMU_RAW

    return fmt

def _name_uid(user):
    """
    Return UID for string username
    """
    pwdinfo = pwd.getpwnam(user)
    return pwdinfo[2]

def _is_dir_searchable(uid, username, path):
    """
    Check if passed directory is searchable by uid
    """
    try:
        statinfo = os.stat(path)
    except OSError:
        return False

    if uid == statinfo.st_uid:
        flag = stat.S_IXUSR
    elif uid == statinfo.st_gid:
        flag = stat.S_IXGRP
    else:
        flag = stat.S_IXOTH

    if bool(statinfo.st_mode & flag):
        return True

    # Check POSIX ACL (since that is what we use to 'fix' access)
    cmd = ["getfacl", path]
    try:
        proc = subprocess.Popen(cmd,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        out, err = proc.communicate()
    except OSError:
        logging.debug("Didn't find the getfacl command.")
        return False

    if proc.returncode != 0:
        logging.debug("Cmd '%s' failed: %s", cmd, err)
        return False

    return bool(re.search("user:%s:..x" % username, out))

def _check_if_pool_source(conn, path):
    """
    If passed path is a host disk device like /dev/sda, want to let the user
    use it
    """
    if not _util.is_storage_capable(conn):
        return None

    def check_pool(poolname, path):
        pool = conn.storagePoolLookupByName(poolname)
        xml = pool.XMLDesc(0)

        for element in ["dir", "device", "adapter"]:
            xml_path = _util.get_xml_path(xml,
                                          "/pool/source/%s/@path" % element)
            if xml_path == path:
                return pool

    running_list = conn.listStoragePools()
    inactive_list = conn.listDefinedStoragePools()
    for plist in [running_list, inactive_list]:
        for name in plist:
            p = check_pool(name, path)
            if p:
                return p
    return None

def _check_if_path_managed(conn, path):
    """
    Determine if we can use libvirt storage APIs to create or lookup
    the passed path. If we can't, throw an error
    """
    vol = None
    pool = None
    verr = None
    path_is_pool = False

    def lookup_vol_by_path():
        try:
            vol = conn.storageVolLookupByPath(path)
            vol.info()
            return vol, None
        except libvirt.libvirtError, e:
            if (hasattr(libvirt, "VIR_ERR_NO_STORAGE_VOL")
                and e.get_error_code() != libvirt.VIR_ERR_NO_STORAGE_VOL):
                raise
            return None, e

    def lookup_vol_name(name):
        try:
            name = os.path.basename(path)
            if pool and name in pool.listVolumes():
                return pool.lookupByName(name)
        except:
            pass
        return None

    vol = lookup_vol_by_path()[0]
    if not vol:
        pool = _util.lookup_pool_by_path(conn, os.path.dirname(path))

        # Is pool running?
        if pool and pool.info()[0] != libvirt.VIR_STORAGE_POOL_RUNNING:
            pool = None

    # Attempt to lookup path as a storage volume
    if pool and not vol:
        try:
            # Pool may need to be refreshed, but if it errors,
            # invalidate it
            pool.refresh(0)
            vol, verr = lookup_vol_by_path()
            if verr:
                vol = lookup_vol_name(os.path.basename(path))
        except Exception, e:
            vol = None
            pool = None
            verr = str(e)

    if not vol:
        # See if path is a pool source, and allow it through
        trypool = _check_if_pool_source(conn, path)
        if trypool:
            path_is_pool = True
            pool = trypool

    if not vol and not pool:
        if not _util.is_uri_remote(conn.getURI(), conn=conn):
            # Building local disk
            return None, None, False

        if not verr:
            # Since there is no error, no pool was ever found
            err = (_("Cannot use storage '%(path)s': '%(rootdir)s' is "
                     "not managed on the remote host.") %
                      { 'path' : path,
                        'rootdir' : os.path.dirname(path)})
        else:
            err = (_("Cannot use storage %(path)s: %(err)s") %
                    { 'path' : path, 'err' : verr })

        raise ValueError(err)

    return vol, pool, path_is_pool

def _build_vol_install(path, pool, size, sparse):
    # Path wasn't a volume. See if base of path is a managed
    # pool, and if so, setup a StorageVolume object
    if size == None:
        raise ValueError(_("Size must be specified for non "
                           "existent volume path '%s'" % path))

    logging.debug("Path '%s' is target for pool '%s'. "
                  "Creating volume '%s'.",
                  os.path.dirname(path), pool.name(),
                  os.path.basename(path))

    volclass = Storage.StorageVolume.get_volume_for_pool(pool_object=pool)
    cap = (size * 1024 * 1024 * 1024)
    if sparse:
        alloc = 0
    else:
        alloc = cap

    volinst = volclass(name=os.path.basename(path),
                       capacity=cap, allocation=alloc, pool=pool)
    return volinst


class VirtualDisk(VirtualDevice):
    """
    Builds a libvirt domain disk xml description

    The VirtualDisk class is used for building libvirt domain xml descriptions
    for disk devices. If creating a disk object from an existing local block
    device or file, a path is all that should be required. If you want to
    create a local file, a size also needs to be specified.

    The remote case is a bit more complex. The options are:
        1. A libvirt virStorageVol instance (passed as 'volObject') for an
           existing storage volume.
        2. A virtinst L{StorageVolume} instance for creating a volume (passed
           as 'volInstall').
        3. An active connection ('conn') and a path to a storage volume on
           that connection.
        4. An active connection and a tuple of the form ("poolname",
           "volumename")
        5. An active connection and a path. The base of the path must
           point to the target path for an active pool.

    For cases 3 and 4, the lookup will be performed, and 'vol_object'
    will be set to the returned virStorageVol. For the last case, 'volInstall'
    will be populated for a StorageVolume instance. All the above cases also
    work on a local connection as well, the only difference being that
    option 3 won't neccessarily error out if the volume isn't found.

    __init__ and setting all properties performs lots of validation,
    and will throw ValueError's if problems are found.
    """

    _virtual_device_type = VirtualDevice.VIRTUAL_DEV_DISK

    DRIVER_FILE = "file"
    DRIVER_PHY = "phy"
    DRIVER_TAP = "tap"
    DRIVER_QEMU = "qemu"
    driver_names = [DRIVER_FILE, DRIVER_PHY, DRIVER_TAP, DRIVER_QEMU]

    DRIVER_QEMU_RAW = "raw"
    # No list here, since there are many other valid values

    DRIVER_TAP_RAW = "aio"
    DRIVER_TAP_QCOW = "qcow"
    DRIVER_TAP_VMDK = "vmdk"
    DRIVER_TAP_VDISK = "vdisk"
    driver_types = [DRIVER_TAP_RAW, DRIVER_TAP_QCOW,
        DRIVER_TAP_VMDK, DRIVER_TAP_VDISK]

    CACHE_MODE_NONE = "none"
    CACHE_MODE_WRITETHROUGH = "writethrough"
    CACHE_MODE_WRITEBACK = "writeback"
    cache_types = [CACHE_MODE_NONE, CACHE_MODE_WRITETHROUGH,
        CACHE_MODE_WRITEBACK]

    DEVICE_DISK = "disk"
    DEVICE_LUN = "lun"
    DEVICE_CDROM = "cdrom"
    DEVICE_FLOPPY = "floppy"
    devices = [DEVICE_DISK, DEVICE_LUN, DEVICE_CDROM, DEVICE_FLOPPY]

    TYPE_FILE = "file"
    TYPE_BLOCK = "block"
    TYPE_DIR = "dir"
    types = [TYPE_FILE, TYPE_BLOCK, TYPE_DIR]

    _target_props = ["file", "dev", "dir"]

    IO_MODE_NATIVE = "native"
    IO_MODE_THREADS = "threads"
    io_modes = [IO_MODE_NATIVE, IO_MODE_THREADS]

    @staticmethod
    def disk_type_to_xen_driver_name(disk_type):
        """
        Convert a value of VirtualDisk.type to it's associated Xen
        <driver name=/> property
        """
        if disk_type == VirtualDisk.TYPE_BLOCK:
            return "phy"
        elif disk_type == VirtualDisk.TYPE_FILE:
            return "file"
        return "file"

    @staticmethod
    def disk_type_to_target_prop(disk_type):
        """
        Convert a value of VirtualDisk.type to it's associated XML
        target property name
        """
        if disk_type == VirtualDisk.TYPE_FILE:
            return "file"
        elif disk_type == VirtualDisk.TYPE_BLOCK:
            return "dev"
        elif disk_type == VirtualDisk.TYPE_DIR:
            return "dir"
        return "file"

    error_policies = ["ignore", "stop", "enospace"]

    @staticmethod
    def path_exists(conn, path):
        """
        Check if path exists. If we can't determine, return False
        """
        is_remote = _util.is_uri_remote(conn.getURI(), conn=conn)
        try:
            vol = None
            path_is_pool = False
            try:
                vol, ignore, path_is_pool = _check_if_path_managed(conn, path)
            except:
                pass

            if vol or path_is_pool:
                return True

            if not is_remote:
                return os.path.exists(path)
        except:
            pass

        return False

    @staticmethod
    def check_path_search_for_user(conn, path, username):
        """
        Check if the passed user has search permissions for all the
        directories in the disk path.

        @return: List of the directories the user cannot search, or empty list
        @rtype : C{list}
        """
        if path is None:
            return []
        if _util.is_uri_remote(conn.getURI(), conn=conn):
            return []

        try:
            uid = _name_uid(username)
        except Exception, e:
            logging.debug("Error looking up username: %s", str(e))
            return []

        fixlist = []

        if os.path.isdir(path):
            dirname = path
            base = "-"
        else:
            dirname, base = os.path.split(path)

        while base:
            if not _is_dir_searchable(uid, username, dirname):
                fixlist.append(dirname)

            dirname, base = os.path.split(dirname)

        return fixlist

    @staticmethod
    def fix_path_search_for_user(conn, path, username):
        """
        Try to fix any permission problems found by check_path_search_for_user

        @return: Return a dictionary of entries { broken path : error msg }
        @rtype : C{dict}
        """
        def fix_perms(dirname, useacl=True):
            if useacl:
                cmd = ["setfacl", "--modify", "user:%s:x" % username, dirname]
                proc = subprocess.Popen(cmd,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE)
                out, err = proc.communicate()

                logging.debug("Ran command '%s'", cmd)
                if out or err:
                    logging.debug("out=%s\nerr=%s", out, err)

                if proc.returncode != 0:
                    raise ValueError(err)
            else:
                logging.debug("Setting +x on %s", dirname)
                mode = os.stat(dirname).st_mode
                newmode = mode | stat.S_IXOTH
                os.chmod(dirname, newmode)
                if os.stat(dirname).st_mode != newmode:
                    # Trying to change perms on vfat at least doesn't work
                    # but also doesn't seem to error. Try and detect that
                    raise ValueError(_("Permissions on '%s' did not stick") %
                                     dirname)

        fixlist = VirtualDisk.check_path_search_for_user(conn, path, username)
        if not fixlist:
            return []

        fixlist.reverse()
        errdict = {}

        useacl = True
        for dirname in fixlist:
            try:
                try:
                    fix_perms(dirname, useacl)
                except:
                    # If acl fails, fall back to chmod and retry
                    if not useacl:
                        raise
                    useacl = False

                    logging.debug("setfacl failed, trying old fashioned way")
                    fix_perms(dirname, useacl)
            except Exception, e:
                errdict[dirname] = str(e)

        return errdict

    @staticmethod
    def path_in_use_by(conn, path, check_conflict=False):
        """
        Return a list of VM names that are using the passed path.

        @param conn: virConnect to check VMs
        @param path: Path to check for
        @param check_conflict: Only return names that are truly conflicting:
                               this will omit guests that are using the disk
                               with the 'shareable' flag, and possible other
                               heuristics
        """
        if not path:
            return

        active, inactive = _util.fetch_all_guests(conn)
        vms = active + inactive

        def count_cb(ctx):
            c = 0

            template = "count(/domain/devices/disk["
            if check_conflict:
                template += "not(shareable) and "
            template += "source/@%s='%s'])"

            for dtype in VirtualDisk._target_props:
                xpath = template % (dtype, _util.xml_escape(path))
                c += ctx.xpathEval(xpath)

            return c

        names = []
        for vm in vms:
            xml = vm.XMLDesc(0)
            tmpcount = _util.get_xml_path(xml, func=count_cb)
            if tmpcount:
                names.append(vm.name())

        return names

    @staticmethod
    def stat_local_path(path):
        """
        Return tuple (storage type, storage size) for the passed path on
        the local machine. This is a best effort attempt.

        @return: tuple of
                 (True if regular file, False otherwise, default is True,
                 max size of storage, default is 0)
        """
        try:
            return _util.stat_disk(path)
        except:
            return (True, 0)

    @staticmethod
    def lookup_vol_object(conn, name_tuple):
        """
        Return a volume instance from parameters that are passed
        to disks volName init parameter
        """
        if (type(name_tuple) is not tuple or
            len(name_tuple) != 2 or
            (type(name_tuple[0]) is not type(name_tuple[1]) is not str)):
            raise ValueError(_("volName must be a tuple of the form "
                               "('poolname', 'volname')"))

        if not conn:
            raise ValueError(_("'volName' requires a passed connection."))
        if not _util.is_storage_capable(conn):
            raise ValueError(_("Connection does not support storage lookup."))

        try:
            pool = conn.storagePoolLookupByName(name_tuple[0])
            return pool.storageVolLookupByName(name_tuple[1])
        except Exception, e:
            raise ValueError(_("Couldn't lookup volume object: %s" % str(e)))

    def __init__(self, path=None, size=None, transient=False, type=None,
                 device=None, driverName=None, driverType=None,
                 readOnly=False, sparse=True, conn=None, volObject=None,
                 volInstall=None, volName=None, bus=None, shareable=False,
                 driverCache=None, selinuxLabel=None, format=None,
                 validate=True, parsexml=None, parsexmlnode=None, caps=None,
                 driverIO=None, sizebytes=None):
        """
        @param path: filesystem path to the disk image.
        @type path: C{str}
        @param size: size of local file to create in gigabytes
        @type size: C{int} or C{long} or C{float}
        @param transient: whether to keep disk around after guest install
        @type transient: C{bool}
        @param type: disk media type (file, block, ...)
        @type type: C{str}
        @param device: Emulated device type (disk, cdrom, floppy, ...)
        @type device: member of devices
        @param driverName: name of driver
        @type driverName: member of driver_names
        @param driverType: type of driver
        @type driverType: member of driver_types
        @param readOnly: Whether emulated disk is read only
        @type readOnly: C{bool}
        @param sparse: Create file as a sparse file
        @type sparse: C{bool}
        @param conn: Connection disk is being installed on
        @type conn: libvirt.virConnect
        @param volObject: libvirt storage volume object to use
        @type volObject: libvirt.virStorageVol
        @param volInstall: StorageVolume instance to build for new storage
        @type volInstall: L{StorageVolume}
        @param volName: Existing StorageVolume lookup information,
                        (parent pool name, volume name)
        @type volName: C{tuple} of (C{str}, C{str})
        @param bus: Emulated bus type (ide, scsi, virtio, ...)
        @type bus: C{str}
        @param shareable: If disk can be shared among VMs
        @type shareable: C{bool}
        @param driverCache: Disk cache mode (none, writethrough, writeback)
        @type driverCache: member of cache_types
        @param selinuxLabel: Used for labelling new or relabel existing storage
        @type selinuxLabel: C{str}
        @param format: Storage volume format to use when creating storage
        @type format: C{str}
        @param validate: Whether to validate passed parameters against the
                         local system. Omitting this may cause issues, be
                         warned!
        @type validate: C{bool}
        @param sizebytes: Optionally specify storage size in bytes. Takes
                          precedence over size if specified.
        @type sizebytes: C{int}
        """

        VirtualDevice.__init__(self, conn=conn,
                               parsexml=parsexml, parsexmlnode=parsexmlnode,
                               caps=caps)

        self._path = None
        self._size = None
        self._type = None
        self._device = None
        self._sparse = None
        self._readOnly = None
        self._vol_object = None
        self._pool_object = None
        self._vol_install = None
        self._bus = None
        self._shareable = None
        self._driver_cache = None
        self._selinux_label = None
        self._clone_path = None
        self._format = None
        self._driverName = driverName
        self._driverType = driverType
        self._driver_io = None
        self._error_policy = None
        self._serial = None
        self._target = None
        self._iotune_read_bytes_sec = None
        self._iotune_read_iops_sec = None
        self._iotune_total_bytes_sec = None
        self._iotune_total_iops_sec = None
        self._iotune_write_bytes_sec = None
        self._iotune_write_iops_sec = None
        self._validate = validate

        # XXX: No property methods for these
        self.transient = transient

        if volName and not volObject:
            volObject = self.lookup_vol_object(conn, volName)

        if sizebytes is not None:
            size = (float(sizebytes) / float(1024 ** 3))

        if self._is_parse():
            self._validate = False
            return

        self.set_read_only(readOnly, validate=False)
        self.set_sparse(sparse, validate=False)
        self.set_type(type, validate=False)
        self.set_device(device or self.DEVICE_DISK, validate=False)
        self._set_path(path, validate=False)
        self._set_size(size, validate=False)
        self._set_vol_object(volObject, validate=False)
        self._set_vol_install(volInstall, validate=False)
        self._set_bus(bus, validate=False)
        self._set_shareable(shareable, validate=False)
        self._set_driver_cache(driverCache, validate=False)
        self._set_selinux_label(selinuxLabel, validate=False)
        self._set_format(format, validate=False)
        self._set_driver_io(driverIO, validate=False)

        self.__change_storage(self.path,
                              self.vol_object,
                              self.vol_install)
        self.__validate_params()


    #
    # Parameters for specifying the backing storage
    #

    def _get_path(self):
        retpath = self._path
        if self.vol_object:
            retpath = self.vol_object.path()
        elif self.vol_install:
            retpath = (_util.get_xml_path(self.vol_install.pool.XMLDesc(0),
                                          "/pool/target/path") + "/" +
                       self.vol_install.name)

        return retpath
    def _set_path(self, val, validate=True):
        if val is not None:
            self._check_str(val, "path")
            val = os.path.abspath(val)

        if validate:
            self.__change_storage(path=val)
        self.__validate_wrapper("_path", val, validate, self.path)
    def _xml_get_xpath(self):
        xpath = None
        for prop in self._target_props:
            xpath = "./source/@" + prop
            if self._xml_ctx.xpathEval(xpath):
                return xpath
        return "./source/@file"
    def _xml_set_xpath(self):
        return "./source/@" + self.disk_type_to_target_prop(self.type)
    path = _xml_property(_get_path, _set_path,
                         xml_get_xpath=_xml_get_xpath,
                         xml_set_xpath=_xml_set_xpath,)


    def _get_vol_object(self):
        return self._vol_object
    def _set_vol_object(self, val, validate=True):
        if val is not None and not isinstance(val, libvirt.virStorageVol):
            raise ValueError(_("vol_object must be a virStorageVol instance"))

        if validate:
            self.__change_storage(vol_object=val)
        self.__validate_wrapper("_vol_object", val, validate, self.vol_object)
    vol_object = property(_get_vol_object, _set_vol_object)

    def _get_vol_install(self):
        return self._vol_install
    def _set_vol_install(self, val, validate=True):
        if val is not None and not isinstance(val, Storage.StorageVolume):
            raise ValueError(_("vol_install must be a StorageVolume "
                               " instance."))

        if validate:
            self.__change_storage(vol_install=val)
        self.__validate_wrapper("_vol_install", val, validate, self.vol_install)
    vol_install = property(_get_vol_install, _set_vol_install)

    #
    # Other properties
    #
    def _get_clone_path(self):
        return self._clone_path
    def _set_clone_path(self, val, validate=True):
        if val is not None:
            self._check_str(val, "path")
            val = os.path.abspath(val)

            # Pass the path to a VirtualDisk, which should provide validation
            # for us
            try:
                # If this disk isn't managed, don't pass 'conn' to this
                # validation disk, to ensure we have permissions for manual
                # cloning
                conn = self.__managed_storage() and self.conn or None
                VirtualDisk(conn=conn, path=val)
            except Exception, e:
                raise ValueError(_("Error validating clone path: %s") % e)
        self.__validate_wrapper("_clone_path", val, validate, self.clone_path)
    clone_path = property(_get_clone_path, _set_clone_path)

    def _get_size(self):
        retsize = self.__existing_storage_size()
        if retsize is None:
            if self.vol_install:
                retsize = self.vol_install.capacity / 1024.0 / 1024.0 / 1024.0
            else:
                retsize = self._size

        return retsize
    def _set_size(self, val, validate=True):
        if val is not None:
            if type(val) not in [int, float, long] or val < 0:
                raise ValueError(_("'size' must be a number greater than 0."))

        self.__validate_wrapper("_size", val, validate, self.size)
    size = property(_get_size, _set_size)

    def get_type(self):
        if self._type:
            return self._type
        return self.__existing_storage_dev_type()
    def set_type(self, val, validate=True):
        if val is not None:
            self._check_str(val, "type")
            if val not in self.types:
                raise ValueError(_("Unknown storage type '%s'" % val))
        self.__validate_wrapper("_type", val, validate, self.type)
    type = _xml_property(get_type, set_type,
                         xpath="./@type")

    def get_device(self):
        return self._device
    def set_device(self, val, validate=True):
        self._check_str(val, "device")
        if val not in self.devices:
            raise ValueError(_("Unknown device type '%s'" % val))

        if val == self._device:
            return

        if self._is_parse():
            self.bus = None
            self.target = None
        self.__validate_wrapper("_device", val, validate, self.device)
    device = _xml_property(get_device, set_device,
                           xpath="./@device")

    def get_driver_name(self):
        retname = self._driverName
        if not retname:
            retname, ignore = self.__get_default_driver()
        return retname
    def set_driver_name(self, val, validate=True):
        ignore = validate
        self._driverName = val
    driver_name = _xml_property(get_driver_name, set_driver_name,
                                xpath="./driver/@name")

    def get_driver_type(self):
        rettype = self._driverType
        if not rettype:
            ignore, rettype = self.__get_default_driver()
        return rettype
    def set_driver_type(self, val, validate=True):
        ignore = validate
        self._driverType = val
    driver_type = _xml_property(get_driver_type, set_driver_type,
                                xpath="./driver/@type")

    def get_sparse(self):
        return self._sparse
    def set_sparse(self, val, validate=True):
        self._check_bool(val, "sparse")
        self.__validate_wrapper("_sparse", val, validate, self.sparse)
    sparse = property(get_sparse, set_sparse)

    def get_read_only(self):
        return self._readOnly
    def set_read_only(self, val, validate=True):
        self._check_bool(val, "read_only")
        self.__validate_wrapper("_readOnly", val, validate, self.read_only)
    read_only = _xml_property(get_read_only, set_read_only,
                              xpath="./readonly", is_bool=True)

    def _get_bus(self):
        return self._bus
    def _set_bus(self, val, validate=True):
        if val is not None:
            self._check_str(val, "bus")
        self.__validate_wrapper("_bus", val, validate, self.bus)
    bus = _xml_property(_get_bus, _set_bus,
                        xpath="./target/@bus")
    def _get_target(self):
        return self._target
    def _set_target(self, val, validate=True):
        ignore = validate
        if val is not None:
            self._check_str(val, "target")
        self._target = val
    target = _xml_property(_get_target, _set_target,
                           xpath="./target/@dev")

    def _get_shareable(self):
        return self._shareable
    def _set_shareable(self, val, validate=True):
        self._check_bool(val, "shareable")
        self.__validate_wrapper("_shareable", val, validate, self.shareable)
    shareable = _xml_property(_get_shareable, _set_shareable,
                              xpath="./shareable", is_bool=True)

    def _get_driver_cache(self):
        return self._driver_cache
    def _set_driver_cache(self, val, validate=True):
        if val is not None:
            self._check_str(val, "cache")
            if val not in self.cache_types:
                raise ValueError(_("Unknown cache mode '%s'" % val))
        self.__validate_wrapper("_driver_cache", val, validate,
                                self.driver_cache)
    driver_cache = _xml_property(_get_driver_cache, _set_driver_cache,
                                 xpath="./driver/@cache")


    def _get_driver_io(self):
        return self._driver_io
    def _set_driver_io(self, val, validate=True):
        if val is not None:
            self._check_str(val, "driver_io")
            if val not in self.io_modes:
                raise ValueError(_("Unknown io mode '%s'" % val))
        self.__validate_wrapper("_driver_io", val, validate,
                                self.driver_io)
    driver_io = _xml_property(_get_driver_io, _set_driver_io,
                              xpath="./driver/@io")

    def _get_error_policy(self):
        return self._error_policy
    def _set_error_policy(self, val, validate=True):
        if val is not None:
            self._check_str(val, "error_policy")
            if val not in self.error_policies:
                raise ValueError(_("Unknown error policy '%s'" % val))
        self.__validate_wrapper("_error_policy", val, validate,
                                self.error_policy)
    error_policy = _xml_property(_get_error_policy, _set_error_policy,
                                 xpath="./driver/@error_policy")

    def _get_serial(self):
        return self._serial
    def _set_serial(self, val, validate=True):
        if val is not None:
            self._check_str(val, "serial")
        self.__validate_wrapper("_serial", val, validate,
                                self.serial)
    serial = _xml_property(_get_serial, _set_serial,
                           xpath="./serial")
    
    def _get_iotune_read_bytes_sec(self):
        return self._iotune_read_bytes_sec
    def _set_iotune_read_bytes_sec(self, val):
        if (type(val) is not type(1) or val < 0):
            raise ValueError(_("IOTune read bytes per second value must be an "
                               "integer"))
        self._iotune_read_bytes_sec = val
    iotune_read_bytes_sec = _xml_property(_get_iotune_read_bytes_sec,
                                          _set_iotune_read_bytes_sec,
                                          xpath="./iotune/read_bytes_sec",
                                          get_converter=lambda s, x: int(x or 0),
                                          set_converter=lambda s, x: int(x))
    
    def _get_iotune_read_iops_sec(self):
        return self._iotune_read_iops_sec
    def _set_iotune_read_iops_sec(self, val):
        if (type(val) is not type(1) or val < 0):
            raise ValueError(_("IOTune read iops per second value must be an "
                               "integer"))
        self._iotune_read_iops_sec = val
    iotune_read_iops_sec = _xml_property(_get_iotune_read_iops_sec,
                                         _set_iotune_read_iops_sec,
                                         xpath="./iotune/read_iops_sec",
                                         get_converter=lambda s, x: int(x or 0),
                                         set_converter=lambda s, x: int(x))
    
    def _get_iotune_total_bytes_sec(self):
        return self._iotune_total_bytes_sec
    def _set_iotune_total_bytes_sec(self, val):
        if (type(val) is not type(1) or val < 0):
            raise ValueError(_("IOTune total bytes per second value must be an "
                               "integer"))
        self._iotune_total_bytes_sec = val
    iotune_total_bytes_sec = _xml_property(_get_iotune_total_bytes_sec,
                                           _set_iotune_total_bytes_sec,
                                           xpath="./iotune/total_bytes_sec",
                                           get_converter=lambda s, x: int(x or 0),
                                           set_converter=lambda s, x: int(x))
    
    def _get_iotune_total_iops_sec(self):
        return self._iotune_total_iops_sec
    def _set_iotune_total_iops_sec(self, val):
        if (type(val) is not type(1) or val < 0):
            raise ValueError(_("IOTune total iops per second value must be an "
                               "integer"))
        self._iotune_total_iops_sec = val
    iotune_total_iops_sec = _xml_property(_get_iotune_total_iops_sec,
                                          _set_iotune_total_iops_sec,
                                          xpath="./iotune/total_iops_sec",
                                          get_converter=lambda s, x: int(x or 0),
                                          set_converter=lambda s, x: int(x))
    
    def _get_iotune_write_bytes_sec(self):
        return self._iotune_write_bytes_sec
    def _set_iotune_write_bytes_sec(self, val):
        if (type(val) is not type(1) or val < 0):
            raise ValueError(_("IOTune write bytes per second value must be an "
                               "integer"))
        self._iotune_write_bytes_sec = val
    iotune_write_bytes_sec = _xml_property(_get_iotune_write_bytes_sec,
                                           _set_iotune_write_bytes_sec,
                                           xpath="./iotune/write_bytes_sec",
                                           get_converter=lambda s, x: int(x or 0),
                                           set_converter=lambda s, x: int(x))
    
    def _get_iotune_write_iops_sec(self):
        return self._iotune_write_iops_sec
    def _set_iotune_write_iops_sec(self, val):
        if (type(val) is not type(1) or val < 0):
            raise ValueError(_("IOTune write iops per second value must be an "
                               "integer"))
        self._iotune_write_iops_sec = val
    iotune_write_iops_sec = _xml_property(_get_iotune_write_iops_sec,
                                          _set_iotune_write_iops_sec,
                                          xpath="./iotune/write_iops_sec",
                                          get_converter=lambda s, x: int(x or 0),
                                          set_converter=lambda s, x: int(x))

    # If there is no selinux support on the libvirt connection or the
    # system, we won't throw errors if this is set, just silently ignore.
    def _get_selinux_label(self):
        # If selinux_label manually specified, return it
        # If we are using existing storage, pull the label from it
        # If we are installing via vol_install, pull from the parent pool
        # If we are creating local storage, use the expected label
        retlabel = self._selinux_label
        if not retlabel:
            retlabel = ""
            if self.creating_storage() and not self.__managed_storage():
                retlabel = self._expected_security_label()
            else:
                retlabel = self._storage_security_label()

        return retlabel
    def _set_selinux_label(self, val, validate=True):
        if val is not None:
            self._check_str(val, "selinux_label")

            if (self._support_selinux() and
                not _util.selinux_is_label_valid(val)):
                # XXX Not valid if we support changing labels remotely
                raise ValueError(_("SELinux label '%s' is not valid.") % val)

        self.__validate_wrapper("_selinux_label", val, validate,
                                self.selinux_label)
    selinux_label = property(_get_selinux_label, _set_selinux_label)

    def _get_format(self):
        return self._format
    def _set_format(self, val, validate=True):
        if val is not None:
            self._check_str(val, "format")
        self.__validate_wrapper("_format", val, validate, self.format)
    format = property(_get_format, _set_format)

    # Validation assistance methods

    # Initializes attribute if it hasn't been done, then validates args.
    # If validation fails, reset attribute to original value and raise error
    def __validate_wrapper(self, varname, newval, validate, origval):
        orig = origval
        setattr(self, varname, newval)

        if validate:
            try:
                self.__validate_params()
            except:
                setattr(self, varname, orig)
                raise

    def can_be_empty(self):
        return (self.device == self.DEVICE_FLOPPY or
                self.device == self.DEVICE_CDROM)

    def __change_storage(self, path=None, vol_object=None, vol_install=None):
        """
        Validates and updates params when the backing storage is changed
        """
        pool = None

        storage_capable = bool(self.conn and
                               _util.is_storage_capable(self.conn))

        # Try to lookup self.path storage objects
        if vol_object or vol_install:
            pass
        elif not storage_capable:
            pass
        elif path:
            vol_object, pool, path_is_pool = _check_if_path_managed(self.conn,
                                                                    path)
            if (pool and
                not vol_object and
                not path_is_pool and
                not self._is_parse()):
                vol_install = _build_vol_install(path, pool,
                                                 self.size,
                                                 self.sparse)

            if not path_is_pool:
                pool = None

        # Finally, set the relevant params
        self._set_path(path, validate=False)
        self._set_vol_object(vol_object, validate=False)
        self._set_vol_install(vol_install, validate=False)
        self._pool_object = pool

        # XXX: Hack, we shouldn't have to conditionalize for parsing
        if self._is_parse():
            self.type = self.get_type()
            self.driver_name = self.get_driver_name()
            self.driver_type = self.get_driver_type()


    def __set_format(self):
        if not self.format:
            return

        if not self.creating_storage():
            return

        if self.vol_install:
            if not hasattr(self.vol_install, "format"):
                raise ValueError(_("Storage type does not support format "
                                   "parameter."))
            if self.vol_install.format != self.format:
                self.vol_install.format = self.format

        elif self.format != "raw":
            raise RuntimeError(_("Format cannot be specified for "
                                 "unmanaged storage."))

    def __existing_storage_size(self):
        """
        Return size of existing storage
        """
        if self.creating_storage():
            return

        if self.vol_object:
            newsize = _util.get_xml_path(self.vol_object.XMLDesc(0),
                                         "/volume/capacity")
            try:
                newsize = float(newsize) / 1024.0 / 1024.0 / 1024.0
            except:
                newsize = 0
        elif self._pool_object:
            newsize = _util.get_xml_path(self.vol_object.XMLDesc(0),
                                         "/pool/capacity")
            try:
                newsize = float(newsize) / 1024.0 / 1024.0 / 1024.0
            except:
                newsize = 0
        elif self.path is None:
            newsize = 0
        else:
            ignore, newsize = _util.stat_disk(self.path)
            newsize = newsize / 1024.0 / 1024.0 / 1024.0

        return newsize

    def __existing_storage_dev_type(self):
        """
        Detect disk 'type' () from passed storage parameters
        """

        dtype = None
        if self.vol_object:
            # vol info is [ vol type (file or block), capacity, allocation ]
            t = self.vol_object.info()[0]
            if t == libvirt.VIR_STORAGE_VOL_FILE:
                dtype = self.TYPE_FILE
            elif t == libvirt.VIR_STORAGE_VOL_BLOCK:
                dtype = self.TYPE_BLOCK
            else:
                dtype = self.TYPE_FILE

        elif self.vol_install:
            if self.vol_install.file_type == libvirt.VIR_STORAGE_VOL_FILE:
                dtype = self.TYPE_FILE
            else:
                dtype = self.TYPE_BLOCK
        elif self._pool_object:
            xml = self._pool_object.XMLDesc(0)
            for source, source_type in [("dir", self.TYPE_DIR),
                                        ("device", self.TYPE_BLOCK),
                                        ("adapter", self.TYPE_BLOCK)]:
                if _util.get_xml_path(xml, "/pool/source/%s/@dev" % source):
                    dtype = source_type
                    break

        elif self.path:
            if os.path.isdir(self.path):
                dtype = self.TYPE_DIR
            elif _util.stat_disk(self.path)[0]:
                dtype = self.TYPE_FILE
            else:
                dtype = self.TYPE_BLOCK

        if not dtype:
            dtype = self._type or self.TYPE_BLOCK

        return dtype

    def __get_default_driver(self):
        """
        Set driverName and driverType from passed parameters

        Where possible, we want to force driverName = "raw" if installing
        a QEMU VM. Without telling QEMU to expect a raw file, the emulator
        is forced to autodetect, which has security implications:

        http://lists.gnu.org/archive/html/qemu-devel/2008-04/msg00675.html
        """
        drvname = self._driverName
        drvtype = self._driverType

        if self.conn:
            is_qemu = self.is_qemu()
            if is_qemu and not drvname:
                drvname = self.DRIVER_QEMU

        if self.format:
            if drvname == self.DRIVER_QEMU:
                drvtype = _qemu_sanitize_drvtype(self.type, self.format,
                                                 manual_format=True)

        elif self.vol_object:
            fmt = _util.get_xml_path(self.vol_object.XMLDesc(0),
                                     "/volume/target/format/@type")
            if drvname == self.DRIVER_QEMU:
                drvtype = _qemu_sanitize_drvtype(self.type, fmt)

        elif self.vol_install:
            if drvname == self.DRIVER_QEMU:
                if hasattr(self.vol_install, "format"):
                    drvtype = _qemu_sanitize_drvtype(self.type,
                                                     self.vol_install.format)

        elif self.creating_storage():
            if drvname == self.DRIVER_QEMU:
                drvtype = self.DRIVER_QEMU_RAW

        elif self.path and os.path.exists(self.path):
            if _util.is_vdisk(self.path):
                drvname = self.DRIVER_TAP
                drvtype = self.DRIVER_TAP_VDISK

        return drvname or None, drvtype or None

    def __managed_storage(self):
        """
        Return bool representing if managed storage parameters have
        been explicitly specified or filled in
        """
        return bool(self.vol_object != None or
                    self.vol_install != None or
                    self._pool_object != None)

    def creating_storage(self):
        """
        Return True if the user requested us to create a device
        """
        if self.__no_storage():
            return False

        if self.__managed_storage():
            if self.vol_object or self._pool_object:
                return False
            return True

        if (not self.is_remote() and
            self.path and
            os.path.exists(self.path)):
            return False

        return True

    def __no_storage(self):
        """
        Return True if no path or storage was specified
        """
        if self.__managed_storage():
            return False
        if self.path:
            return False
        return True


    def _storage_security_label(self):
        """
        Return SELinux label of existing storage, or None
        """
        context = ""

        if self.__no_storage():
            return context

        if self.vol_object:
            context = _util.get_xml_path(self.vol_object.XMLDesc(0),
                                         "/volume/target/permissions/label")
        elif self._pool_object:
            context = _util.get_xml_path(self._pool_object.XMLDesc(0),
                                         "/pool/target/permissions/label")
        elif self.vol_install:
            # XXX: If user entered a manual label, should we sync this
            # to vol_install?
            l = _util.get_xml_path(self.vol_install.pool.XMLDesc(0),
                                   "/pool/target/permissions/label")
            context = l or ""
        else:
            context = _util.selinux_getfilecon(self.path)

        return context


    def __validate_params(self):
        """
        function to validate all the complex interaction between the various
        disk parameters.
        """
        if not self._validate:
            return

        # No storage specified for a removable device type (CDROM, floppy)
        if self.__no_storage():
            if not self.can_be_empty():
                raise ValueError(_("Device type '%s' requires a path") %
                                 self.device)

            return True

        storage_capable = bool(self.conn and
                               _util.is_storage_capable(self.conn))

        if self.is_remote():
            if not storage_capable:
                raise ValueError(_("Connection doesn't support remote "
                                   "storage."))
            if not self.__managed_storage():
                raise ValueError(_("Must specify libvirt managed storage "
                                   "if on a remote connection"))

        # The main distinctions from this point forward:
        # - Are we doing storage API operations or local media checks?
        # - Do we need to create the storage?

        managed_storage = self.__managed_storage()
        create_media = self.creating_storage()

        self.__set_format()

        # If not creating the storage, our job is easy
        if not create_media:
            # Make sure we have access to the local path
            if not managed_storage:
                if (os.path.isdir(self.path) and
                    not _util.is_vdisk(self.path) and
                    not self.device == self.DEVICE_FLOPPY):
                    raise ValueError(_("The path '%s' must be a file or a "
                                       "device, not a directory") % self.path)

            return True


        if (self.device == self.DEVICE_FLOPPY or
            self.device == self.DEVICE_CDROM):
            raise ValueError(_("Cannot create storage for %s device.") %
                               self.device)

        if not managed_storage:
            if self.type is self.TYPE_BLOCK:
                raise ValueError(_("Local block device path '%s' must "
                                   "exist.") % self.path)

            # Path doesn't exist: make sure we have write access to dir
            if not os.access(os.path.dirname(self.path), os.R_OK):
                raise ValueError("No read access to directory '%s'" %
                                 os.path.dirname(self.path))
            if self.size is None:
                raise ValueError(_("size is required for non-existent disk "
                                   "'%s'" % self.path))
            if not os.access(os.path.dirname(self.path), os.W_OK):
                raise ValueError(_("No write access to directory '%s'") %
                                   os.path.dirname(self.path))

        # Applicable for managed or local storage
        ret = self.is_size_conflict()
        if ret[0]:
            raise ValueError(ret[1])
        elif ret[1]:
            logging.warn(ret[1])

    # Storage creation routines
    def _do_create_storage(self, progresscb):
        # If a clone_path is specified, but not vol_install.input_vol,
        # that means we are cloning unmanaged -> managed, so skip this
        if (self.vol_install and
            (not self.clone_path or self.vol_install.input_vol)):
            self._set_vol_object(self.vol_install.install(meter=progresscb),
                                 validate=False)
            # Then just leave: vol_install should handle any selinux stuff
            return

        if self.clone_path:
            text = (_("Cloning %(srcfile)s") %
                    {'srcfile' : os.path.basename(self.clone_path)})
        else:
            text = _("Creating storage file %s") % os.path.basename(self.path)

        size_bytes = long(self.size * 1024L * 1024L * 1024L)
        progresscb.start(filename=self.path, size=long(size_bytes),
                         text=text)

        if self.clone_path:
            # VDisk clone
            if (_util.is_vdisk(self.clone_path) or
                (os.path.exists(self.path) and _util.is_vdisk(self.path))):

                if (not _util.is_vdisk(self.clone_path) or
                    os.path.exists(self.path)):
                    raise RuntimeError(_("copying to an existing vdisk is not"
                                         " supported"))
                if not _vdisk_clone(self.clone_path, self.path):
                    raise RuntimeError(_("failed to clone disk"))
                progresscb.end(size_bytes)

            else:
                # Plain file clone
                self._clone_local(progresscb, size_bytes)

        elif _util.is_vdisk(self.path):
            # Create vdisk
            progresscb.update(1024)
            if not _vdisk_create(self.path, size_bytes, "vmdk", self.sparse):
                raise RuntimeError(_("Error creating vdisk %s" % self.path))

            progresscb.end(self.size)
        else:
            # Plain file creation
            self._create_local_file(progresscb, size_bytes)

    def _create_local_file(self, progresscb, size_bytes):
        """
        Helper function which attempts to build self.path
        """
        fd = None
        path = self.path
        sparse = self.sparse

        try:
            try:
                fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_DSYNC)

                if sparse:
                    os.ftruncate(fd, size_bytes)
                else:
                    # 1 meg of nulls
                    mb = 1024 * 1024
                    buf = '\x00' * mb

                    left = size_bytes
                    while left > 0:
                        if left < mb:
                            buf = '\x00' * left
                        left = max(left - mb, 0)

                        os.write(fd, buf)
                        progresscb.update(size_bytes - left)
            except OSError, e:
                raise RuntimeError(_("Error creating diskimage %s: %s") %
                                   (path, str(e)))
        finally:
            if fd is not None:
                os.close(fd)
            progresscb.end(size_bytes)

    def _clone_local(self, meter, size_bytes):

        # if a destination file exists and sparse flg is True,
        # this priority takes a existing file.
        if (os.path.exists(self.path) == False and self.sparse == True):
            clone_block_size = 4096
            sparse = True
            fd = None
            try:
                fd = os.open(self.path, os.O_WRONLY | os.O_CREAT)
                os.ftruncate(fd, size_bytes)
            finally:
                if fd:
                    os.close(fd)
        else:
            clone_block_size = 1024 * 1024 * 10
            sparse = False

        logging.debug("Local Cloning %s to %s, sparse=%s, block_size=%s",
                      self.clone_path, self.path, sparse, clone_block_size)

        zeros = '\0' * 4096

        src_fd, dst_fd = None, None
        try:
            try:
                src_fd = os.open(self.clone_path, os.O_RDONLY)
                dst_fd = os.open(self.path, os.O_WRONLY | os.O_CREAT)

                i = 0
                while 1:
                    l = os.read(src_fd, clone_block_size)
                    s = len(l)
                    if s == 0:
                        meter.end(size_bytes)
                        break
                    # check sequence of zeros
                    if sparse and zeros == l:
                        os.lseek(dst_fd, s, 1)
                    else:
                        b = os.write(dst_fd, l)
                        if s != b:
                            meter.end(i)
                            break
                    i += s
                    if i < size_bytes:
                        meter.update(i)
            except OSError, e:
                raise RuntimeError(_("Error cloning diskimage %s to %s: %s") %
                                       (self.clone_path, self.path, str(e)))
        finally:
            if src_fd is not None:
                os.close(src_fd)
            if dst_fd is not None:
                os.close(dst_fd)

    def setup_dev(self, conn=None, meter=None):
        """
        Build storage (if required)

        If storage doesn't exist (a non-existent file 'path', or 'vol_install'
        was specified), we create it.

        @param conn: Optional connection to use if self.conn not specified
        @param meter: Progress meter to report file creation on
        @type meter: instanceof urlgrabber.BaseMeter
        """
        return self.setup(meter)

    def setup(self, progresscb=None):
        """
        DEPRECATED: Please use setup_dev instead
        """
        if not progresscb:
            progresscb = progress.BaseMeter()

        if self.creating_storage() or self.clone_path:
            self._do_create_storage(progresscb)

        # Relabel storage if it was requested
        storage_label = self._storage_security_label()
        if storage_label and storage_label != self.selinux_label:
            if not self._support_selinux():
                logging.debug("No support for changing selinux context.")
            elif not self._security_can_fix():
                logging.debug("Can't fix selinux context in this case.")
            else:
                logging.debug("Changing path=%s selinux label %s -> %s",
                              self.path, storage_label, self.selinux_label)
                _util.selinux_setfilecon(self.path, self.selinux_label)

    def _get_xml_config(self, disknode=None):
        """
        @param disknode: device name in host (xvda, hdb, etc.). self.target
                         takes precedence.
        @type disknode: C{str}
        """
        typeattr = self.type
        if self.type == VirtualDisk.TYPE_BLOCK:
            typeattr = 'dev'

        if self.target:
            disknode = self.target
        if not disknode:
            raise ValueError(_("'disknode' or self.target must be set!"))

        path = None
        if self.vol_object:
            path = self.vol_object.path()
        elif self.path:
            path = self.path
        if path:
            path = _util.xml_escape(path)

        ret = "    <disk type='%s' device='%s'>\n" % (self.type, self.device)

        cache = self.driver_cache
        iomode = self.driver_io

        if virtinst.enable_rhel6_defaults:
            # Enable cache=none for non-CDROM devs
            if (self.is_qemu() and
                not cache and
                self.device != self.DEVICE_CDROM):
                cache = self.CACHE_MODE_NONE

            # Enable AIO native for block devices
            if (self.is_qemu() and
                not iomode and
                self.device == self.DEVICE_DISK and
                self.type == self.TYPE_BLOCK):
                iomode = self.IO_MODE_NATIVE

        if path:
            drvxml = ""
            if not self.driver_type is None:
                drvxml += " type='%s'" % self.driver_type
            if not cache is None:
                drvxml += " cache='%s'" % cache
            if not self.error_policy is None:
                drvxml += " error_policy='%s'" % self.error_policy
            if not iomode is None:
                drvxml += " io='%s'" % iomode

            if drvxml and self.driver_name is None:
                if self.is_qemu():
                    self.driver_name = "qemu"

            if not self.driver_name is None:
                drvxml = (" name='%s'" % self.driver_name) + drvxml

            if drvxml:
                ret += "      <driver%s/>\n" % drvxml

        if path is not None:
            ret += "      <source %s='%s'/>\n" % (typeattr, path)

        bus_xml = ""
        if self.bus is not None:
            bus_xml = " bus='%s'" % self.bus
        ret += "      <target dev='%s'%s/>\n" % (disknode, bus_xml)

        ro = self.read_only

        if self.device == self.DEVICE_CDROM:
            ro = True
        if self.shareable:
            ret += "      <shareable/>\n"
        if ro:
            ret += "      <readonly/>\n"

        if self.serial:
            ret += ("      <serial>%s</serial>\n" %
                    _util.xml_escape(self.serial))
        
        if (self.iotune_read_bytes_sec or self.iotune_read_iops_sec or
            self.iotune_total_bytes_sec or self.iotune_total_iops_sec or
            self.iotune_write_bytes_sec or self.iotune_write_iops_sec):
            ret += "      <iotune>"
            if self.iotune_read_bytes_sec:
                ret += "        <read_bytes_sec>%s</read_bytes_sec>" % (self.iotune_read_bytes_sec)
            if self.iotune_read_iops_sec:
                ret += "        <read_iops_sec>%s</read_iops_sec>" % (self.iotune_read_iops_sec)
            if self.iotune_total_bytes_sec:
                ret += "        <total_bytes_sec>%s</total_bytes_sec>" % (self.iotune_total_bytes_sec)
            if self.iotune_total_iops_sec:
                ret += "        <total_iops_sec>%s</total_iops_sec>" % (self.iotune_total_iops_sec)
            if self.iotune_write_bytes_sec:
                ret += "        <write_bytes_sec>%s</write_bytes_sec>" % (self.iotune_write_bytes_sec)
            if self.iotune_write_iops_sec:
                ret += "        <write_iops_sec>%s</write_iops_sec>" % (self.iotune_write_iops_sec)
            ret += "      </iotune>"

        ret += "    </disk>"
        return ret

    def is_size_conflict(self):
        """
        reports if disk size conflicts with available space

        returns a two element tuple:
            1. first element is True if fatal conflict occurs
            2. second element is a string description of the conflict or None
        Non fatal conflicts (sparse disk exceeds available space) will
        return (False, "description of collision")
        """

        if self.vol_install:
            return self.vol_install.is_size_conflict()

        if not self.creating_storage():
            return (False, None)

        ret = False
        msg = None
        vfs = os.statvfs(os.path.dirname(self.path))
        avail = vfs[statvfs.F_FRSIZE] * vfs[statvfs.F_BAVAIL]
        need = long(self.size * 1024L * 1024L * 1024L)
        if need > avail:
            if self.sparse:
                msg = _("The filesystem will not have enough free space"
                        " to fully allocate the sparse file when the guest"
                        " is running.")
            else:
                ret = True
                msg = _("There is not enough free space to create the disk.")


            if msg:
                msg += (_(" %d M requested > %d M available") %
                        ((need / (1024 * 1024)), (avail / (1024 * 1024))))
        return (ret, msg)

    def is_conflict_disk(self, conn, return_names=False):
        """
        check if specified storage is in use by any other VMs on passed
        connection.

        @param conn: connection to check for collisions on
        @type conn: libvirt.virConnect
        @param return_names: Whether or not to return a list of VM names using
                             the same storage (default = False)
        @type return_names: C{bool}

        @return: True if a collision, False otherwise (list of names if
                 return_names passed)
        @rtype: C{bool}
        """
        if self.vol_object:
            path = self.vol_object.path()
        else:
            path = self.path

        if not path:
            return False

        if not conn:
            conn = self.conn

        check_conflict = self.shareable
        names = self.path_in_use_by(conn, path,
                                    check_conflict=check_conflict)

        ret = False
        if names:
            ret = True
        if return_names:
            ret = names

        return ret

    def _support_selinux(self):
        """
        Return True if we have the requisite libvirt and library support
        for selinux commands
        """
        caps = self._get_caps()
        if not caps:
            return False

        elif "selinux" not in [x.model for x in caps.host.secmodels]:
            return False

        elif self.is_remote():
            return False

        elif not _util.have_selinux():
            # XXX: When libvirt supports changing labels via storage APIs,
            #      this will need changing.
            return False

        elif self.__managed_storage() and self.path:
            try:
                statinfo = os.stat(self.path)
            except:
                return False

            # Not sure if this is even the correct metric for
            # 'Can we change the file context'
            return os.geteuid() in ['0', statinfo.st_uid]

        return True

    def _expected_security_label(self):
        """
        Best guess at what the expected selinux label should be for the disk
        """
        label = None

        # XXX: These are really only approximations in the remote case?
        # XXX: Maybe libvirt should expose the relevant selinux labels in
        #      the capabilities XML?

        if not self._support_selinux():
            pass
        elif self.__no_storage():
            pass
        elif self.read_only:
            label = _util.selinux_readonly_label()
        elif self.shareable:
            # XXX: Should this be different? or do we not care about MLS here?
            label = _util.selinux_rw_label()
        else:
            label = _util.selinux_rw_label()

        return label or ""

    def _security_can_fix(self):
        can_fix = True

        if not self._support_selinux():
            can_fix = False
        elif self.__no_storage():
            can_fix = False
        elif self.type == VirtualDisk.TYPE_BLOCK:
            # Shouldn't change labelling on block devices (though we can)
            can_fix = False
        elif not self.read_only:
            # XXX Leave all other (R/W disk) relabeling up to libvirt/svirt
            # for now
            can_fix = False

        return can_fix

    def get_target_prefix(self):
        """
        Returns the suggested disk target prefix (hd, xvd, sd ...) for the
        disk.
        @returns: str prefix, or None if no reasonable guess can be made
        """
        # The upper limits here aren't necessarilly 1024, but let the HV
        # error as appropriate.
        if self.bus == "virtio":
            return ("vd", 1024)
        elif self.bus in ["sata", "scsi", "usb"]:
            return ("sd", 1024)
        elif self.bus == "xen":
            return ("xvd", 1024)
        elif self.bus == "fdc" or self.device == self.DEVICE_FLOPPY:
            return ("fd", 2)
        elif self.bus == "ide":
            return ("hd", 4)
        else:
            return (None, None)

    def generate_target(self, skip_targets):
        """
        Generate target device ('hda', 'sdb', etc..) for disk, excluding
        any targets in 'skip_targets'. Sets self.target, and returns the
        generated value

        @param skip_targets: list of targets to exclude
        @type skip_targets: C{list}
        @raise ValueError: can't determine target type, no targets available
        @returns generated target
        @rtype C{str}
        """

        # Only use these targets if there are no other options
        except_targets = ["hdc"]

        prefix, maxnode = self.get_target_prefix()
        if prefix is None:
            raise ValueError(_("Cannot determine device bus/type."))

        # Special case: IDE cdrom should prefer hdc for back compat
        if self.device == self.DEVICE_CDROM and prefix == "hd":
            if "hdc" not in skip_targets:
                self.target = "hdc"
                return self.target

        if maxnode > (26 * 26 * 26):
            raise RuntimeError("maxnode value is too high")

        # Regular scanning
        for i in range(1, maxnode + 1):
            gen_t = prefix

            tmp = i
            digits = []
            for factor in range(0, 3):
                amt = (tmp % (26 ** (factor + 1))) / (26 ** factor)
                if amt == 0 and tmp >= (26 ** (factor + 1)):
                    amt = 26
                tmp -= amt
                digits.insert(0, amt)

            seen_valid = False
            for digit in digits:
                if digit == 0:
                    if not seen_valid:
                        continue
                    digit = 1

                seen_valid = True
                gen_t += "%c" % (ord('a') + digit - 1)

            if gen_t in except_targets:
                continue
            if gen_t not in skip_targets:
                self.target = gen_t
                return self.target

        # Check except_targets for any options
        for t in except_targets:
            if t.startswith(prefix) and t not in skip_targets:
                self.target = t
                return self.target
        raise ValueError(_("No more space for disks of type '%s'" % prefix))


class XenDisk(VirtualDisk):
    """
    Back compat class to avoid ABI break.
    """
    pass
