<commit_before>#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2010-2015  <NAME> <<EMAIL>>
# Copyright (C) 2013-2015  <NAME>
#                              <<EMAIL>>
# Copyright (C) 2014-2015  <NAME> <<EMAIL>>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
#

from __future__ import absolute_import

import asyncio
import base64
import grp
import os
import os.path
import shutil
import string
import subprocess
import uuid
import warnings

import libvirt  # pylint: disable=import-error
import lxml

import qubes
import qubes.config
import qubes.exc
import qubes.storage
import qubes.storage.file
import qubes.utils
import qubes.vm
import qubes.vm.mix.net

qmemman_present = False
try:
    import qubes.qmemman.client  # pylint: disable=wrong-import-position
    qmemman_present = True
except ImportError:
    pass

# overhead of per-qube/per-vcpu Xen structures,
# taken from OpenStack nova/virt/xenapi/driver.py
# see https://wiki.openstack.org/wiki/XenServer/Overhead
# add an extra MB because Nova rounds up to MBs
MEM_OVERHEAD_BASE = (3 + 1) * 1024 * 1024
MEM_OVERHEAD_PER_VCPU = 3 * 1024 * 1024 / 2


def _setter_kernel(self, prop, value):
    ''' Helper for setting the domain kernel and running sanity checks on it.
    '''  # pylint: disable=unused-argument
    if not value:
        return ''
    value = str(value)
    if '/' in value:
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            'Kernel name cannot contain \'/\'')
    return value


def _setter_positive_int(self, prop, value):
    ''' Helper for setting a positive int. Checks that the int is > 0 '''
    # pylint: disable=unused-argument
    value = int(value)
    if value <= 0:
        raise ValueError('Value must be positive')
    return value

def _setter_non_negative_int(self, prop, value):
    ''' Helper for setting a positive int. Checks that the int is >= 0 '''
    # pylint: disable=unused-argument
    value = int(value)
    if value < 0:
        raise ValueError('Value must be positive or zero')
    return value


def _setter_default_user(self, prop, value):
    ''' Helper for setting default user '''
    value = str(value)
    # specifically forbid: ':', ' ', ''', '"'
    allowed_chars = string.ascii_letters + string.digits + '_-+,.'
    if not all(c in allowed_chars for c in value):
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            'Username can contain only those characters: ' + allowed_chars)
    return value

def _setter_virt_mode(self, prop, value):
    value = str(value)
    value = value.lower()
    if value not in ('hvm', 'pv', 'pvh'):
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            'Invalid virtualization mode, supported values: hvm, pv, pvh')
    if value == 'pvh' and list(self.devices['pci'].persistent()):
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            "pvh mode can't be set if pci devices are attached")
    return value

def _default_virt_mode(self):
    if self.devices['pci'].persistent():
        return 'hvm'
    try:
        return self.template.virt_mode
    except AttributeError:
        return 'pvh'

def _default_with_template(prop, default):
    '''Return a callable for 'default' argument of a property. Use a value
    from a template (if any), otherwise *default*
    '''

    def _func(self):
        try:
            return getattr(self.template, prop)
        except AttributeError:
            if callable(default):
                return default(self)
            return default

    return _func


def _default_maxmem(self):
    # first check for any reason to _not_ enable qmemman
    if not self.is_memory_balancing_possible():
        return 0

    # Linux specific cap: max memory can't scale beyond 10.79*init_mem
    # see https://groups.google.com/forum/#!topic/qubes-devel/VRqkFj1IOtA
    if self.features.get('os', None) == 'Linux':
        default_maxmem = self.memory * 10
    else:
        default_maxmem = 4000

    # don't use default larger than half of physical ram
    default_maxmem = min(default_maxmem,
        int(self.app.host.memory_total / 1024 / 2))

    return _default_with_template('maxmem', default_maxmem)(self)

def _default_kernelopts(self):
    '''
    Return default kernel options for the given kernel. If kernel directory
    contains 'default-kernelopts-{pci,nopci}.txt' file, use that. Otherwise
    use built-in defaults.
    For qubes without PCI devices, kernelopts of qube's template are
    considered (for template-based qubes).
    '''
    if not self.kernel:
        return ''
    if 'kernel' in self.volumes:
        kernels_dir = self.storage.kernels_dir
    else:
        kernels_dir = os.path.join(
            qubes.config.system_path['qubes_kernels_base_dir'],
            self.kernel)
    pci = bool(list(self.devices['pci'].persistent()))
    if pci:
        path = os.path.join(kernels_dir, 'default-kernelopts-pci.txt')
    else:
        try:
            return self.template.kernelopts
        except AttributeError:
            pass
        path = os.path.join(kernels_dir, 'default-kernelopts-nopci.txt')
    if os.path.exists(path):
        with open(path) as f_kernelopts:
            return f_kernelopts.read().strip()
    else:
        return (qubes.config.defaults['kernelopts_pcidevs'] if pci else
            qubes.config.defaults['kernelopts'])


class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
    '''Base functionality of Qubes VM shared between all VMs.

    The following events are raised on this class or its subclasses:

        .. event:: domain-init (subject, event)

            Fired at the end of class' constructor.

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-init'``)

        .. event:: domain-load (subject, event)

            Fired after the qube was loaded from :file:`qubes.xml`

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-loaded'``)

        .. event:: domain-pre-start \
                (subject, event, start_guid, mem_required)

            Fired at the beginning of :py:meth:`start` method.

            Handler for this event can be asynchronous (a coroutine).

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-pre-start'``)

            *other arguments are as in :py:meth:`start`*

        .. event:: domain-spawn (subject, event, start_guid)

            Fired after creating libvirt domain.

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-spawn'``)

            Handler for this event can be asynchronous (a coroutine).

            *other arguments are as in :py:meth:`start`*

        .. event:: domain-start (subject, event, start_guid)

            Fired at the end of :py:meth:`start` method.

            Handler for this event can be asynchronous (a coroutine).

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-start'``)

            *other arguments are as in :py:meth:`start`*

        .. event:: domain-start-failed (subject, event, reason)

            Fired when :py:meth:`start` method fails.
            *reason* argument is a textual error message.

            Handler for this event can be asynchronous (a coroutine).

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-start'``)

            *other arguments are as in :py:meth:`start`*
<commit_msg>Fix domain-start-failed event documentation.<commit_after>#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2010-2015  <NAME> <<EMAIL>>
# Copyright (C) 2013-2015  <NAME>
#                              <<EMAIL>>
# Copyright (C) 2014-2015  <NAME> <<EMAIL>>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
#

from __future__ import absolute_import

import asyncio
import base64
import grp
import os
import os.path
import shutil
import string
import subprocess
import uuid
import warnings

import libvirt  # pylint: disable=import-error
import lxml

import qubes
import qubes.config
import qubes.exc
import qubes.storage
import qubes.storage.file
import qubes.utils
import qubes.vm
import qubes.vm.mix.net

qmemman_present = False
try:
    import qubes.qmemman.client  # pylint: disable=wrong-import-position
    qmemman_present = True
except ImportError:
    pass

# overhead of per-qube/per-vcpu Xen structures,
# taken from OpenStack nova/virt/xenapi/driver.py
# see https://wiki.openstack.org/wiki/XenServer/Overhead
# add an extra MB because Nova rounds up to MBs
MEM_OVERHEAD_BASE = (3 + 1) * 1024 * 1024
MEM_OVERHEAD_PER_VCPU = 3 * 1024 * 1024 / 2


def _setter_kernel(self, prop, value):
    ''' Helper for setting the domain kernel and running sanity checks on it.
    '''  # pylint: disable=unused-argument
    if not value:
        return ''
    value = str(value)
    if '/' in value:
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            'Kernel name cannot contain \'/\'')
    return value


def _setter_positive_int(self, prop, value):
    ''' Helper for setting a positive int. Checks that the int is > 0 '''
    # pylint: disable=unused-argument
    value = int(value)
    if value <= 0:
        raise ValueError('Value must be positive')
    return value

def _setter_non_negative_int(self, prop, value):
    ''' Helper for setting a positive int. Checks that the int is >= 0 '''
    # pylint: disable=unused-argument
    value = int(value)
    if value < 0:
        raise ValueError('Value must be positive or zero')
    return value


def _setter_default_user(self, prop, value):
    ''' Helper for setting default user '''
    value = str(value)
    # specifically forbid: ':', ' ', ''', '"'
    allowed_chars = string.ascii_letters + string.digits + '_-+,.'
    if not all(c in allowed_chars for c in value):
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            'Username can contain only those characters: ' + allowed_chars)
    return value

def _setter_virt_mode(self, prop, value):
    value = str(value)
    value = value.lower()
    if value not in ('hvm', 'pv', 'pvh'):
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            'Invalid virtualization mode, supported values: hvm, pv, pvh')
    if value == 'pvh' and list(self.devices['pci'].persistent()):
        raise qubes.exc.QubesPropertyValueError(self, prop, value,
            "pvh mode can't be set if pci devices are attached")
    return value

def _default_virt_mode(self):
    if self.devices['pci'].persistent():
        return 'hvm'
    try:
        return self.template.virt_mode
    except AttributeError:
        return 'pvh'

def _default_with_template(prop, default):
    '''Return a callable for 'default' argument of a property. Use a value
    from a template (if any), otherwise *default*
    '''

    def _func(self):
        try:
            return getattr(self.template, prop)
        except AttributeError:
            if callable(default):
                return default(self)
            return default

    return _func


def _default_maxmem(self):
    # first check for any reason to _not_ enable qmemman
    if not self.is_memory_balancing_possible():
        return 0

    # Linux specific cap: max memory can't scale beyond 10.79*init_mem
    # see https://groups.google.com/forum/#!topic/qubes-devel/VRqkFj1IOtA
    if self.features.get('os', None) == 'Linux':
        default_maxmem = self.memory * 10
    else:
        default_maxmem = 4000

    # don't use default larger than half of physical ram
    default_maxmem = min(default_maxmem,
        int(self.app.host.memory_total / 1024 / 2))

    return _default_with_template('maxmem', default_maxmem)(self)

def _default_kernelopts(self):
    '''
    Return default kernel options for the given kernel. If kernel directory
    contains 'default-kernelopts-{pci,nopci}.txt' file, use that. Otherwise
    use built-in defaults.
    For qubes without PCI devices, kernelopts of qube's template are
    considered (for template-based qubes).
    '''
    if not self.kernel:
        return ''
    if 'kernel' in self.volumes:
        kernels_dir = self.storage.kernels_dir
    else:
        kernels_dir = os.path.join(
            qubes.config.system_path['qubes_kernels_base_dir'],
            self.kernel)
    pci = bool(list(self.devices['pci'].persistent()))
    if pci:
        path = os.path.join(kernels_dir, 'default-kernelopts-pci.txt')
    else:
        try:
            return self.template.kernelopts
        except AttributeError:
            pass
        path = os.path.join(kernels_dir, 'default-kernelopts-nopci.txt')
    if os.path.exists(path):
        with open(path) as f_kernelopts:
            return f_kernelopts.read().strip()
    else:
        return (qubes.config.defaults['kernelopts_pcidevs'] if pci else
            qubes.config.defaults['kernelopts'])


class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
    '''Base functionality of Qubes VM shared between all VMs.

    The following events are raised on this class or its subclasses:

        .. event:: domain-init (subject, event)

            Fired at the end of class' constructor.

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-init'``)

        .. event:: domain-load (subject, event)

            Fired after the qube was loaded from :file:`qubes.xml`

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-loaded'``)

        .. event:: domain-pre-start \
                (subject, event, start_guid, mem_required)

            Fired at the beginning of :py:meth:`start` method.

            Handler for this event can be asynchronous (a coroutine).

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-pre-start'``)

            *other arguments are as in :py:meth:`start`*

        .. event:: domain-spawn (subject, event, start_guid)

            Fired after creating libvirt domain.

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-spawn'``)

            Handler for this event can be asynchronous (a coroutine).

            *other arguments are as in :py:meth:`start`*

        .. event:: domain-start (subject, event, start_guid)

            Fired at the end of :py:meth:`start` method.

            Handler for this event can be asynchronous (a coroutine).

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-start'``)

            *other arguments are as in :py:meth:`start`*

        .. event:: domain-start-failed (subject, event, reason)

            Fired when :py:meth:`start` method fails.
            *reason* argument is a textual error message.

            Handler for this event can be asynchronous (a coroutine).

            :param subject: Event emitter (the qube object)
            :param event: Event name (``'domain-start-failed'``)

{'fraction_non_alphanumeric': 0.09927051671732523, 'fraction_numerical': 0.008389057750759878, 'mean_word_length': 2.7346197502837684, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 8, 'lorem ipsum': 0, 'www.': 4, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5588749', 'n_tokens_mistral': 5006, 'n_tokens_neox': 4705, 'n_words': 1629}