Merge branch 'core3-devel'

This concludes over two years of rewriting Qubes' core.
Thank God it's now merged to master. --@woju

QubesOS/qubes-issues#1825
This commit is contained in:
Marek Marczykowski-Górecki 2017-05-12 19:59:14 +02:00
commit eaf5c27b27
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
279 changed files with 31407 additions and 18811 deletions

12
.coveragerc Normal file
View File

@ -0,0 +1,12 @@
[report]
exclude_lines =
pragma: no cover
^\s*def __repr__
^\s*(el)?if os\.name ==
^\s*raise (RuntimeError|NotImplementedError)
^\s*except ImportError
[paths]
source =
qubes
/usr/lib/python2.7/site-packages/qubes
/usr/lib64/python2.7/site-packages/qubes

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
rpm/
pkgs/
qubes.egg-info/
.coverage
.coverage.*
htmlcov/
*.pyc
*.pyo
*~

267
.pylintrc Normal file
View File

@ -0,0 +1,267 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=no
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
#load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once).
# abstract-class-little-used: see http://www.logilab.org/ticket/111138
disable=
locally-disabled,
locally-enabled,
file-ignored,
duplicate-code,
star-args,
cyclic-import,
abstract-class-little-used,
bad-continuation
#
# OTHER NICE SETS
#
# IMPORTS
#disable=all
#enable=
# cyclic-import,
# import-error,
# no-member,
# super-on-old-class,
# undefined-variable,
# unused-import
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=yes
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
#ignored-classes=
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
#generated-members=
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=e,i,j,k,m,p,ex,Run,_,log,vm,xc,xs,ip,fd,fh,rw,st,tb
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=__.*__
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,FIX,XXX,TODO
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Maximum number of lines in a module
max-module-lines=3000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
#additional-builtins=
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[DESIGN]
# Maximum number of arguments for function / method
max-args=35
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
# Let's have max-args + 5
max-locals=40
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
# 4x the default value
max-branches=48
# Maximum number of statements in function / method body
# Double default
max-statements=100
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=15
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=100
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception,EnvironmentError

View File

@ -1,7 +1,29 @@
sudo: required
dist: trusty
language: generic
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
script: ~/qubes-builder/scripts/travis-build
language: python
python:
- '3.5'
install:
- pip install --quiet -r ci/requirements.txt
- git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-builder ~/qubes-builder
script:
- PYTHONPATH=test-packages pylint --rcfile=ci/pylintrc qubes qubespolicy
- ./run-tests --no-syslog
- ~/qubes-builder/scripts/travis-build
env:
- DIST_DOM0=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
- DIST_DOM0=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
after_success:
- ~/qubes-builder/scripts/travis-deploy
# don't build tags which are meant for code signing only
branches:
except:
- /.*_.*/
addons:
apt:
packages:
- debootstrap
# vim: ts=2 sts=2 sw=2 et

View File

@ -5,6 +5,7 @@ VERSION := $(shell cat version)
DIST_DOM0 ?= fc18
OS ?= Linux
PYTHON ?= python3
ifeq ($(OS),Linux)
DATADIR ?= /var/lib/qubes
@ -39,19 +40,10 @@ rpms-dom0:
$(RPMS_DIR)/x86_64/qubes-core-dom0-$(VERSION)*.rpm \
$(RPMS_DIR)/noarch/qubes-core-dom0-doc-$(VERSION)*rpm
clean:
make -C dispvm clean
make -C qmemman clean
all:
make all -C core
make all -C core-modules
make all -C tests
$(PYTHON) setup.py build
# make all -C tests
# Currently supported only on xen
ifeq ($(BACKEND_VMM),xen)
make all -C qmemman
make all -C dispvm
endif
install:
ifeq ($(OS),Linux)
@ -59,17 +51,19 @@ ifeq ($(OS),Linux)
$(MAKE) install -C linux/aux-tools
$(MAKE) install -C linux/system-config
endif
$(MAKE) install -C qvm-tools
$(MAKE) install -C core
$(MAKE) install -C core-modules
$(MAKE) install -C tests
$(PYTHON) setup.py install -O1 --skip-build --root $(DESTDIR)
ln -s qvm-device $(DESTDIR)/usr/bin/qvm-pci
ln -s qvm-device $(DESTDIR)/usr/bin/qvm-usb
# $(MAKE) install -C tests
$(MAKE) install -C relaxng
mkdir -p $(DESTDIR)/etc/qubes
ifeq ($(BACKEND_VMM),xen)
# Currently supported only on xen
$(MAKE) install -C qmemman
cp etc/qmemman.conf $(DESTDIR)/etc/qubes/
endif
$(MAKE) install -C dispvm
mkdir -p $(DESTDIR)/etc/qubes-rpc/policy
mkdir -p $(DESTDIR)/usr/libexec/qubes
cp qubes-rpc-policy/qubes.FeaturesRequest.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.FeaturesRequest
cp qubes-rpc-policy/qubes.Filecopy.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.Filecopy
cp qubes-rpc-policy/qubes.OpenInVM.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenInVM
cp qubes-rpc-policy/qubes.OpenURL.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenURL
@ -78,14 +72,21 @@ endif
cp qubes-rpc-policy/qubes.NotifyTools.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyTools
cp qubes-rpc-policy/qubes.GetImageRGBA.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetImageRGBA
cp qubes-rpc-policy/qubes.GetRandomizedTime.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetRandomizedTime
cp qubes-rpc/qubes.NotifyUpdates $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes.NotifyTools $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc-policy/qubes.NotifyTools.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyTools
cp qubes-rpc-policy/qubes.NotifyUpdates.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyUpdates
cp qubes-rpc-policy/qubes.OpenInVM.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenInVM
cp qubes-rpc-policy/qubes.VMShell.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.VMShell
cp qubes-rpc/qubes.FeaturesRequest $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes.GetRandomizedTime $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes.NotifyTools $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes.NotifyUpdates $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes-notify-updates $(DESTDIR)/usr/libexec/qubes/
cp qubes-rpc/qubes-notify-tools $(DESTDIR)/usr/libexec/qubes/
mkdir -p "$(DESTDIR)$(FILESDIR)"
cp vm-config/$(BACKEND_VMM)-vm-template.xml "$(DESTDIR)$(FILESDIR)/vm-template.xml"
cp vm-config/$(BACKEND_VMM)-vm-template-hvm.xml "$(DESTDIR)$(FILESDIR)/vm-template-hvm.xml"
cp -r templates "$(DESTDIR)$(FILESDIR)/templates"
rm -f "$(DESTDIR)$(FILESDIR)/templates/README"
mkdir -p $(DESTDIR)$(DATADIR)
mkdir -p $(DESTDIR)$(DATADIR)/vm-templates
mkdir -p $(DESTDIR)$(DATADIR)/appvms

View File

@ -1,5 +1,5 @@
ifeq ($(PACKAGE_SET),dom0)
RPM_SPEC_FILES := $(addprefix rpm_spec/,core-dom0.spec core-dom0-doc.spec)
RPM_SPEC_FILES := rpm_spec/core-dom0.spec
WIN_SOURCE_SUBDIRS := .
WIN_COMPILER := mingw
WIN_PACKAGE_CMD := make msi

10
README.md Normal file
View File

@ -0,0 +1,10 @@
Qubes core, version 3
---------------------
[![Build Status](https://travis-ci.org/woju/qubes-core-admin.svg?branch=core3-devel)](https://travis-ci.org/woju/qubes-core-admin)
<img src="https://readthedocs.org/projects/qubes-core-admin/badge/?version=latest">
This is development branch of the Qubes OS core. This branch is subject to
rebase without warning until further notice.
API documentation is available: https://qubes-core-admin.readthedocs.org/en/latest/.

3
ci/coveragerc Normal file
View File

@ -0,0 +1,3 @@
[run]
source = qubes
omit = qubes/tests/*

27
ci/lvm-manage Executable file
View File

@ -0,0 +1,27 @@
# This is to include LVM-requiring tests in Travis-CI
if [ "$1" = "setup-lvm" -a -n "$2" ]; then
POOL_PATH=$2
VG_NAME=`echo $POOL_PATH | cut -f 1 -d /`
POOL_NAME=`echo $POOL_PATH | cut -f 2 -d /`
if lvs $VG_NAME >/dev/null 2>&1 || lvs $POOL_PATH >/dev/null 2>&1; then
echo "WARNING: either VG '$VG_NAME' or thin pool '$POOL_PATH' already exists, not reusing" >&2
exit 1
fi
set -e
loop_file=`mktemp`
truncate -s 1G $loop_file
loop_dev=`losetup -f --show $loop_file`
# auto cleanup
rm -f $loop_file
vgcreate "$VG_NAME" $loop_dev
lvcreate --thinpool "$POOL_NAME" --type thin-pool -L 960M "$VG_NAME"
exit 0
elif [ "$1" = "cleanup-lvm" -a -n "$2" ]; then
VG_NAME=`echo $2 | cut -f 1 -d /`
set -e
pvs=`vgs --noheadings -o pv_name $VG_NAME | tr -d ' '`
lvremove -f "$2"
vgremove "$VG_NAME"
losetup -d $pvs
exit 0
fi

199
ci/pylintrc Normal file
View File

@ -0,0 +1,199 @@
[MASTER]
persistent=no
ignore=tests,backup.py
[MESSAGES CONTROL]
# abstract-class-little-used: see http://www.logilab.org/ticket/111138
# deprecated-method:
# enable again after disabling py-3.4.3 asyncio.ensure_future compat hack
disable=
abstract-class-little-used,
bad-continuation,
cyclic-import,
deprecated-method,
duplicate-code,
file-ignored,
fixme,
locally-disabled,
locally-enabled,
logging-format-interpolation,
missing-docstring,
star-args
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html
output-format=colorized
#files-output=no
reports=yes
[TYPECHECK]
ignored-classes=
VMProperty,
libvirt,libvirtError,
dbus,SystemBus,
PCIDevice
ignore-mixin-members=yes
generated-members=
iter_entry_points,
Element,ElementTree,QName,SubElement,fromstring,parse,tostring,
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions=apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm,xc,xs,ip,fd,fh,rw,st,tb,cb,ff
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match functions or classes name which do
# not require a docstring
no-docstring-rgx=__.*__
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,FIX,XXX,TODO
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Maximum number of lines in a module
max-module-lines=3000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_|dummy
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[DESIGN]
# Maximum number of arguments for function / method
max-args=35
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
# Let's have max-args + 5
max-locals=40
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
# 4x the default value
max-branches=48
# Maximum number of statements in function / method body
# Double default
max-statements=100
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=15
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=100
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception,EnvironmentError
# vim: ft=conf

9
ci/requirements.txt Normal file
View File

@ -0,0 +1,9 @@
# WARNING: those requirements are used only for travis-ci.org
# they SHOULD NOT be used under normal conditions; use system package manager
coverage
docutils
jinja2
lxml
pylint
sphinx
pydbus

147
contrib/check-events Executable file
View File

@ -0,0 +1,147 @@
#!/usr/bin/env python2
from __future__ import print_function
from pprint import pprint
import argparse
import ast
import os
import sys
SOMETHING = '<something>'
parser = argparse.ArgumentParser()
parser.add_argument('--never-handled',
action='store_true', dest='never_handled',
help='mark never handled events')
parser.add_argument('--no-never-handled',
action='store_false', dest='never_handled',
help='do not mark never handled events')
parser.add_argument('directory', metavar='DIRECTORY',
help='directory to search for .py files')
class Event(object):
def __init__(self, events, name):
self.events = events
self.name = name
self.fired = []
self.handled = []
def fire(self, filename, lineno):
self.fired.append((filename, lineno))
def handle(self, filename, lineno):
self.handled.append((filename, lineno))
def print_summary_one(self, stream, attr, colour, never=True):
lines = getattr(self, attr)
if lines:
for filename, lineno in lines:
stream.write(' \033[{}m{}\033[0m {} +{}\n'.format(
colour, attr[0], filename, lineno))
elif never:
stream.write(' \033[1;33mnever {}\033[0m\n'.format(attr))
def print_summary(self, stream, never_handled):
stream.write('\033[1m{}\033[0m\n'.format(self.name))
self.print_summary_one(stream, 'fired', '1;31')
self.print_summary_one(stream, 'handled', '1;32', never=never_handled)
class Events(dict):
def __missing__(self, key):
self[key] = Event(self, key)
return self[key]
class EventVisitor(ast.NodeVisitor):
def __init__(self, events, filename, *args, **kwargs):
super(EventVisitor, self).__init__(*args, **kwargs)
self.events = events
self.filename = filename
def resolve_attr(self, node):
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Attribute):
return '{}.{}'.format(self.resolve_attr(node.value), node.attr)
raise TypeError('resolve_attr() does not support {!r}'.format(node))
def visit_Call(self, node):
try:
name = self.resolve_attr(node.func)
except TypeError:
# name got something else than identifier in the attribute path;
# this may have been 'str'.format() for example; we can't call
# events this way
return
if name.endswith('.fire_event') or name.endswith('.fire_event_pre'):
# here we throw events; event name is the first argument; sometimes
# it is expressed as 'event-stem:' + some_variable
eventnode = node.args[0]
if isinstance(eventnode, ast.Str):
event = eventnode.s
elif isinstance(eventnode, ast.BinOp) \
and isinstance(eventnode.left, ast.Str):
event = eventnode.left.s
else:
raise AssertionError('fishy event {!r} in {} +{}'.format(
eventnode, self.filename, node.lineno))
if ':' in event:
event = ':'.join((event.split(':', 1)[0], SOMETHING))
self.events[event].fire(self.filename, node.lineno)
return
if name in ('qubes.events.handler', 'qubes.ext.handler'):
# here we handle; event names (there may be more than one) are all
# positional arguments
if node.starargs is not None:
raise AssertionError(
'event handler with *args in {} +{}'.format(
self.filename, node.lineno))
for arg in node.args:
if not isinstance(arg, ast.Str):
raise AssertionError(
'event handler with non-string arg in {} +{}'.format(
self.filename, node.lineno))
event = arg.s
if ':' in event:
event = ':'.join((event.split(':', 1)[0], SOMETHING))
self.events[event].handle(self.filename, node.lineno)
return
self.generic_visit(node)
return
def main():
args = parser.parse_args()
events = Events()
for dirpath, dirnames, filenames in os.walk(args.directory):
for filename in filenames:
if not filename.endswith('.py'):
continue
filepath = os.path.join(dirpath, filename)
EventVisitor(events, filepath).visit(
ast.parse(open(filepath).read(), filepath))
for event in sorted(events):
events[event].print_summary(
sys.stdout, never_handled=args.never_handled)
if __name__ == '__main__':
main()

180
contrib/import-graph Executable file
View File

@ -0,0 +1,180 @@
#!/usr/bin/env python3
import itertools
import os
import re
import sys
re_import = re.compile(r'^import (.*?)$', re.M)
re_import_from = re.compile(r'^from (.*?) import .*?$', re.M)
class Import(object):
defstyle = {'arrowhead': 'open', 'arrowtail':'none'}
def __init__(self, importing, imported, **kwargs):
self.importing = importing
self.imported = imported
self.style = self.defstyle.copy()
self.style.update(kwargs)
def __str__(self):
return '{}"{}" -> "{}" [{}];'.format(
('//' if self.commented else ''),
self.importing,
self.imported,
', '.join('{}="{}"'.format(*i) for i in self.style.items()))
def __eq__(self, other):
return (self.importing.name, self.imported.name) \
== (other.importing.name, other.imported.name)
def __hash__(self):
return hash((self.importing.name, self.imported.name))
@property
def commented(self):
if self.style.get('color', '') != 'red':
return True
# for i in (self.importing, self.imported):
# if i.name.startswith('qubes.tests'): return True
# if i.name.startswith('qubes.tools'): return True
class Module(set):
def __init__(self, package, path):
self.package = package
self.path = path
def process(self):
with open(os.path.join(self.package.root, self.path)) as fh:
data = fh.read()
data.replace('\\\n', ' ')
for imported in re_import.findall(data):
try:
imported = self.package[imported]
except KeyError:
continue
self.add(Import(self, imported))
for imported in re_import_from.findall(data):
try:
imported = self.package[imported]
except KeyError:
continue
self.add(Import(self, imported, style='dotted'))
def __getitem__(self, key):
for i in self:
if i.imported == key:
return i
raise KeyError(key)
@property
def name(self):
names = os.path.splitext(self.path)[0].split('/')
names.insert(0, self.package.name)
if names[-1] == '__init__':
del names[-1]
return '.'.join(names)
def __hash__(self):
return hash(self.name)
def __str__(self):
return self.name
def __repr__(self):
return '<{} {!r}>'.format(self.__class__.__name__, self.name)
def __lt__(self, other):
return self.name < other.name
def __eq__(self, other):
return self.name == other.name
class Cycle(tuple):
def __new__(cls, modules):
i = modules.index(sorted(modules)[0])
# sys.stderr.write('modules={!r} i={!r}\n'.format(modules, i))
return super(Cycle, cls).__new__(cls, modules[i:] + modules[:i+1])
# def __lt__(self, other):
# if len(self) < len(other):
# return True
# elif len(self) > len(other):
# return False
#
# return super(Cycle, self).__lt__(other)
class Package(dict):
def __init__(self, root):
super(Package, self).__init__()
self.root = root
for dirpath, dirnames, filenames in os.walk(self.root):
for filename in filenames:
if not os.path.splitext(filename)[1] == '.py':
continue
module = Module(self,
os.path.relpath(os.path.join(dirpath, filename), self.root))
self[module.name] = module
for name, module in self.items():
module.process()
@property
def name(self):
return os.path.basename(self.root.rstrip(os.path.sep))
def _find_cycles(self):
# stolen from codereview.stackexchange.com/questions/86021 and hacked
path = []
visited = set()
def visit(module):
# if module in visited:
# return
# visited.add(module)
path.append(module)
for i in module:
if i.imported in path:
yield Cycle(path[path.index(i.imported):])
else:
yield from visit(i.imported)
path.pop()
for v in self.values():
yield from visit(v)
def find_cycles(self):
return list(sorted(set(self._find_cycles())))
def get_all_imports(self):
for module in self.values():
yield from module
def __str__(self):
return '''\n
digraph "import" {{
charset="utf-8"
rankdir=BT
{}
}}
'''.format('\n'.join(str(i) for i in self.get_all_imports()))
def main():
package = Package(sys.argv[1])
for cycle in package.find_cycles():
for i in range(len(cycle) - 1):
edge = cycle[i][cycle[i+1]]
edge.style['color'] = 'red'
sys.stderr.write(' -> '.join(str(module) for module in cycle) + '\n')
sys.stdout.write(str(package))
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +0,0 @@
#!/usr/bin/python2
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# 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.
#
#
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
from qubes.qubes import (
register_qubes_vm_class,
QubesException,
QubesVm,
)
from time import sleep
class QubesResizableVm(QubesVm):
def resize_root_img(self, size, allow_start=False):
if self.template:
raise QubesException("Cannot resize root.img of template-based VM"
". Resize the root.img of the template "
"instead.")
if self.is_running():
raise QubesException("Cannot resize root.img of running VM")
if size < self.get_root_img_sz():
raise QubesException(
"For your own safety shringing of root.img is disabled. If "
"you really know what you are doing, use 'truncate' manually.")
f_root = open(self.root_img, "a+b")
f_root.truncate(size)
f_root.close()
class QubesResizableVmWithResize2fs(QubesResizableVm):
def resize_root_img(self, size, allow_start=False):
super(QubesResizableVmWithResize2fs, self).\
resize_root_img(size, allow_start=allow_start)
if not allow_start:
raise QubesException("To complete the resize operation start the "
"qube. You may need to run resize2fs manually"
"in the qube .")
self.start(start_guid=False)
self.run("resize2fs /dev/mapper/dmroot", user="root", wait=True,
gui=False)
self.shutdown()
while self.is_running():
sleep(1)
register_qubes_vm_class(QubesResizableVm)
register_qubes_vm_class(QubesResizableVmWithResize2fs)

View File

@ -1,103 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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 sys
from qubes.qubes import (
defaults,
dry_run,
register_qubes_vm_class,
system_path,
vmm,
QubesResizableVmWithResize2fs,
QubesVmCollection,
)
class QubesTemplateVm(QubesResizableVmWithResize2fs):
"""
A class that represents an TemplateVM. A child of QubesVm.
"""
# In which order load this VM type from qubes.xml
load_order = 50
def get_attrs_config(self):
attrs_config = super(QubesTemplateVm, self).get_attrs_config()
attrs_config['dir_path']['func'] = \
lambda value: value if value is not None else \
os.path.join(system_path["qubes_templates_dir"], self.name)
attrs_config['label']['default'] = defaults["template_label"]
return attrs_config
def __init__(self, **kwargs):
super(QubesTemplateVm, self).__init__(**kwargs)
self.appvms = QubesVmCollection()
@property
def type(self):
return "TemplateVM"
@property
def updateable(self):
return True
def is_template(self):
return True
def get_firewall_defaults(self):
return { "rules": list(), "allow": False, "allowDns": False, "allowIcmp": False, "allowYumProxy": True }
@property
def rootcow_img(self):
return self.storage.rootcow_img
def clone_disk_files(self, src_vm, verbose):
if dry_run:
return
super(QubesTemplateVm, self).clone_disk_files(src_vm=src_vm, verbose=verbose)
# Create root-cow.img
self.commit_changes(verbose=verbose)
def commit_changes (self, verbose = False):
self.log.debug('commit_changes()')
if not vmm.offline_mode:
assert not self.is_running(), "Attempt to commit changes on running Template VM!"
if verbose:
print >> sys.stderr, "--> Commiting template updates... COW: {0}...".format (self.rootcow_img)
if dry_run:
return
self.storage.commit_template_changes()
register_qubes_vm_class(QubesTemplateVm)

View File

@ -1,181 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import sys
import os.path
import libvirt
from qubes.qubes import QubesVm,register_qubes_vm_class,vmm,dry_run
from qubes.qubes import defaults,system_path,vm_files
from qubes.qubes import QubesVmCollection,QubesException
class QubesNetVm(QubesVm):
"""
A class that represents a NetVM. A child of QubesCowVM.
"""
# In which order load this VM type from qubes.xml
load_order = 70
def get_attrs_config(self):
attrs_config = super(QubesNetVm, self).get_attrs_config()
attrs_config['dir_path']['func'] = \
lambda value: value if value is not None else \
os.path.join(system_path["qubes_servicevms_dir"], self.name)
attrs_config['uses_default_netvm']['func'] = lambda x: False
attrs_config['label']['default'] = defaults["servicevm_label"]
attrs_config['memory']['default'] = 300
# New attributes
attrs_config['netid'] = {
'save': lambda: str(self.netid),
'order': 30,
'func': lambda value: value if value is not None else
self._collection.get_new_unused_netid() }
attrs_config['netprefix'] = {
'func': lambda x: "10.137.{0}.".format(self.netid) }
attrs_config['dispnetprefix'] = {
'func': lambda x: "10.138.{0}.".format(self.netid) }
# Dont save netvm prop
attrs_config['netvm'].pop('save')
attrs_config['uses_default_netvm'].pop('save')
return attrs_config
def __init__(self, **kwargs):
super(QubesNetVm, self).__init__(**kwargs)
self.connected_vms = QubesVmCollection()
self.__network = "10.137.{0}.0".format(self.netid)
self.__netmask = defaults["vm_default_netmask"]
self.__gateway = self.netprefix + "1"
self.__secondary_dns = self.netprefix + "254"
self.__external_ip_allowed_xids = set()
self.log.debug('network={} netmask={} gateway={} secondary_dns={}'.format(
self.network, self.netmask, self.gateway, self.secondary_dns))
@property
def type(self):
return "NetVM"
def is_netvm(self):
return True
@property
def gateway(self):
return self.__gateway
@property
def secondary_dns(self):
return self.__secondary_dns
@property
def netmask(self):
return self.__netmask
@property
def network(self):
return self.__network
def get_ip_for_vm(self, qid):
lo = qid % 253 + 2
assert lo >= 2 and lo <= 254, "Wrong IP address for VM"
return self.netprefix + "{0}".format(lo)
def get_ip_for_dispvm(self, dispid):
lo = dispid % 254 + 1
assert lo >= 1 and lo <= 254, "Wrong IP address for VM"
return self.dispnetprefix + "{0}".format(lo)
def update_external_ip_permissions(self, xid = -1):
# TODO: VMs in __external_ip_allowed_xids should be notified via RPC
# service on exteran IP change
pass
def start(self, **kwargs):
if dry_run:
return
xid=super(QubesNetVm, self).start(**kwargs)
# Connect vif's of already running VMs
for vm in self.connected_vms.values():
if not vm.is_running():
continue
if 'verbose' in kwargs and kwargs['verbose']:
print >> sys.stderr, "--> Attaching network to '{0}'...".format(vm.name)
# Cleanup stale VIFs
vm.cleanup_vifs()
# force frontend to forget about this device
# module actually will be loaded back by udev, as soon as network is attached
try:
vm.run("modprobe -r xen-netfront xennet", user="root")
except:
pass
try:
vm.attach_network(wait=False)
except QubesException as ex:
print >> sys.stderr, ("WARNING: Cannot attach to network to '{0}': {1}".format(vm.name, ex))
return xid
def shutdown(self, force=False):
if dry_run:
return
connected_vms = [vm for vm in self.connected_vms.values() if vm.is_running()]
if connected_vms and not force:
raise QubesException("There are other VMs connected to this VM: " + str([vm.name for vm in connected_vms]))
# detach network interfaces of connected VMs before shutting down,
# otherwise libvirt will not notice it and will try to detach them
# again (which would fail, obviously).
# This code can be removed when #1426 got implemented
for vm in self.connected_vms.values():
if vm.is_running():
try:
vm.detach_network()
except (QubesException, libvirt.libvirtError):
# ignore errors
pass
super(QubesNetVm, self).shutdown(force=force)
def add_external_ip_permission(self, xid):
if int(xid) < 0:
return
self.__external_ip_allowed_xids.add(int(xid))
self.update_external_ip_permissions()
def remove_external_ip_permission(self, xid):
self.__external_ip_allowed_xids.discard(int(xid))
self.update_external_ip_permissions()
register_qubes_vm_class(QubesNetVm)

View File

@ -1,107 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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.
#
#
from qubes.qubes import QubesNetVm,register_qubes_vm_class
from qubes.qubes import defaults
from qubes.qubes import QubesException,dry_run,vmm
import psutil
class QubesAdminVm(QubesNetVm):
# In which order load this VM type from qubes.xml
load_order = 10
def get_attrs_config(self):
attrs = super(QubesAdminVm, self).get_attrs_config()
attrs.pop('kernel')
attrs.pop('kernels_dir')
attrs.pop('kernelopts')
attrs.pop('uses_default_kernel')
attrs.pop('uses_default_kernelopts')
return attrs
def __init__(self, **kwargs):
super(QubesAdminVm, self).__init__(qid=0, name="dom0", netid=0,
dir_path=None,
private_img = None,
template = None,
maxmem = 0,
vcpus = 0,
label = defaults["template_label"],
**kwargs)
@property
def xid(self):
return 0
@property
def libvirt_domain(self):
raise ValueError("Dom0 do not have libvirt object")
@property
def type(self):
return "AdminVM"
def is_running(self):
return True
def get_power_state(self):
return "Running"
def get_mem(self):
return psutil.virtual_memory().total/1024
def get_mem_static_max(self):
return vmm.libvirt_conn.getInfo()[1]
def get_cputime(self):
# TODO: measure it somehow
return 0
def get_disk_usage(self, file_or_dir):
return 0
def get_disk_utilization(self):
return 0
def get_disk_utilization_private_img(self):
return 0
def get_private_img_sz(self):
return 0
@property
def ip(self):
return "10.137.0.2"
def start(self, **kwargs):
raise QubesException ("Cannot start Dom0 fake domain!")
def suspend(self):
return
def verify_files(self):
return True
register_qubes_vm_class(QubesAdminVm)

View File

@ -1,208 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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.
#
#
from datetime import datetime
from qubes.qubes import QubesNetVm,register_qubes_vm_class,vmm,dry_run
from qubes.qubes import QubesVmCollection,QubesException
yum_proxy_ip = '10.137.255.254'
yum_proxy_port = '8082'
class QubesProxyVm(QubesNetVm):
"""
A class that represents a ProxyVM, ex FirewallVM. A child of QubesNetVM.
"""
def get_attrs_config(self):
attrs_config = super(QubesProxyVm, self).get_attrs_config()
attrs_config['uses_default_netvm']['func'] = lambda x: False
# Save netvm prop again
attrs_config['netvm']['save'] = \
lambda: str(self.netvm.qid) if self.netvm is not None else "none"
return attrs_config
def __init__(self, **kwargs):
super(QubesProxyVm, self).__init__(**kwargs)
self.rules_applied = None
@property
def type(self):
return "ProxyVM"
def is_proxyvm(self):
return True
def _set_netvm(self, new_netvm):
old_netvm = self.netvm
super(QubesProxyVm, self)._set_netvm(new_netvm)
if vmm.offline_mode:
return
if self.netvm is not None:
self.netvm.add_external_ip_permission(self.get_xid())
self.write_netvm_domid_entry()
if old_netvm is not None:
old_netvm.remove_external_ip_permission(self.get_xid())
def post_vm_net_attach(self, vm):
""" Called after some VM net-attached to this ProxyVm """
self.write_iptables_qubesdb_entry()
def post_vm_net_detach(self, vm):
""" Called after some VM net-detached from this ProxyVm """
self.write_iptables_qubesdb_entry()
def start(self, **kwargs):
if dry_run:
return
retcode = super(QubesProxyVm, self).start(**kwargs)
if self.netvm is not None:
self.netvm.add_external_ip_permission(self.get_xid())
self.write_netvm_domid_entry()
return retcode
def force_shutdown(self, **kwargs):
if dry_run:
return
if self.netvm is not None:
self.netvm.remove_external_ip_permission(kwargs['xid'] if 'xid' in kwargs else self.get_xid())
super(QubesProxyVm, self).force_shutdown(**kwargs)
def create_qubesdb_entries(self):
if dry_run:
return
super(QubesProxyVm, self).create_qubesdb_entries()
self.qdb.write("/qubes-iptables-error", '')
self.write_iptables_qubesdb_entry()
def write_netvm_domid_entry(self, xid = -1):
if not self.is_running():
return
if xid < 0:
xid = self.get_xid()
if self.netvm is None:
self.qdb.write("/qubes-netvm-domid", '')
else:
self.qdb.write("/qubes-netvm-domid",
"{0}".format(self.netvm.get_xid()))
def write_iptables_qubesdb_entry(self):
self.qdb.rm("/qubes-iptables-domainrules/")
iptables = "# Generated by Qubes Core on {0}\n".format(datetime.now().ctime())
iptables += "*filter\n"
iptables += ":INPUT DROP [0:0]\n"
iptables += ":FORWARD DROP [0:0]\n"
iptables += ":OUTPUT ACCEPT [0:0]\n"
# Strict INPUT rules
iptables += "-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP\n"
iptables += "-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED " \
"-j ACCEPT\n"
iptables += "-A INPUT -p icmp -j ACCEPT\n"
iptables += "-A INPUT -i lo -j ACCEPT\n"
iptables += "-A INPUT -j REJECT --reject-with icmp-host-prohibited\n"
iptables += "-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED " \
"-j ACCEPT\n"
# Allow dom0 networking
iptables += "-A FORWARD -i vif0.0 -j ACCEPT\n"
# Deny inter-VMs networking
iptables += "-A FORWARD -i vif+ -o vif+ -j DROP\n"
iptables += "COMMIT\n"
self.qdb.write("/qubes-iptables-header", iptables)
vms = [vm for vm in self.connected_vms.values()]
for vm in vms:
iptables="*filter\n"
conf = vm.get_firewall_conf()
xid = vm.get_xid()
if xid < 0: # VM not active ATM
continue
ip = vm.ip
if ip is None:
continue
# Anti-spoof rules are added by vif-script (vif-route-qubes), here we trust IP address
accept_action = "ACCEPT"
reject_action = "REJECT --reject-with icmp-host-prohibited"
if conf["allow"]:
default_action = accept_action
rules_action = reject_action
else:
default_action = reject_action
rules_action = accept_action
for rule in conf["rules"]:
iptables += "-A FORWARD -s {0} -d {1}".format(ip, rule["address"])
if rule["netmask"] != 32:
iptables += "/{0}".format(rule["netmask"])
if rule["proto"] is not None and rule["proto"] != "any":
iptables += " -p {0}".format(rule["proto"])
if rule["portBegin"] is not None and rule["portBegin"] > 0:
iptables += " --dport {0}".format(rule["portBegin"])
if rule["portEnd"] is not None and rule["portEnd"] > rule["portBegin"]:
iptables += ":{0}".format(rule["portEnd"])
iptables += " -j {0}\n".format(rules_action)
if conf["allowDns"] and self.netvm is not None:
# PREROUTING does DNAT to NetVM DNSes, so we need self.netvm.
# properties
iptables += "-A FORWARD -s {0} -p udp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.gateway)
iptables += "-A FORWARD -s {0} -p udp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.secondary_dns)
iptables += "-A FORWARD -s {0} -p tcp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.gateway)
iptables += "-A FORWARD -s {0} -p tcp -d {1} --dport 53 -j " \
"ACCEPT\n".format(ip,self.netvm.secondary_dns)
if conf["allowIcmp"]:
iptables += "-A FORWARD -s {0} -p icmp -j ACCEPT\n".format(ip)
if conf["allowYumProxy"]:
iptables += "-A FORWARD -s {0} -p tcp -d {1} --dport {2} -j ACCEPT\n".format(ip, yum_proxy_ip, yum_proxy_port)
else:
iptables += "-A FORWARD -s {0} -p tcp -d {1} --dport {2} -j DROP\n".format(ip, yum_proxy_ip, yum_proxy_port)
iptables += "-A FORWARD -s {0} -j {1}\n".format(ip, default_action)
iptables += "COMMIT\n"
self.qdb.write("/qubes-iptables-domainrules/"+str(xid), iptables)
# no need for ending -A FORWARD -j DROP, cause default action is DROP
self.write_netvm_domid_entry()
self.rules_applied = None
self.qdb.write("/qubes-iptables", 'reload')
register_qubes_vm_class(QubesProxyVm)

View File

@ -1,54 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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.path
from qubes.qubes import (
register_qubes_vm_class,
system_path,
QubesResizableVmWithResize2fs,
QubesVmLabel,
)
class QubesAppVm(QubesResizableVmWithResize2fs):
"""
A class that represents an AppVM. A child of QubesVm.
"""
def get_attrs_config(self):
attrs_config = super(QubesAppVm, self).get_attrs_config()
attrs_config['dir_path']['func'] = \
lambda value: value if value is not None else \
os.path.join(system_path["qubes_appvms_dir"], self.name)
return attrs_config
@property
def type(self):
return "AppVM"
def is_appvm(self):
return True
register_qubes_vm_class(QubesAppVm)

View File

@ -1,248 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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 sys
import libvirt
import time
from qubes.qubes import QubesVm,QubesVmLabel,register_qubes_vm_class, \
QubesException
from qubes.qubes import QubesDispVmLabels
from qubes.qubes import dry_run,vmm
import grp
qmemman_present = False
try:
from qubes.qmemman_client import QMemmanClient
qmemman_present = True
except ImportError:
pass
DISPID_STATE_FILE = '/var/run/qubes/dispid'
GUID_SHMID_FILE = ['/var/run/qubes/shm.id', '/var/run/shm.id']
class QubesDisposableVm(QubesVm):
"""
A class that represents an DisposableVM. A child of QubesVm.
"""
# In which order load this VM type from qubes.xml
load_order = 120
def _assign_new_dispid(self):
# This method in called while lock on qubes.xml is held, so no need for
# additional lock
if os.path.exists(DISPID_STATE_FILE):
f = open(DISPID_STATE_FILE, 'r+')
dispid = int(f.read())
f.seek(0)
f.truncate(0)
f.write(str(dispid+1))
f.close()
else:
dispid = 1
f = open(DISPID_STATE_FILE, 'w')
f.write(str(dispid+1))
f.close()
os.chown(DISPID_STATE_FILE, -1, grp.getgrnam('qubes').gr_gid)
os.chmod(DISPID_STATE_FILE, 0664)
return dispid
def get_attrs_config(self):
attrs_config = super(QubesDisposableVm, self).get_attrs_config()
attrs_config['name']['func'] = \
lambda x: "disp%d" % self.dispid if x is None else x
# New attributes
attrs_config['dispid'] = {
'func': lambda x: (self._assign_new_dispid() if x is None
else int(x)),
'save': lambda: str(self.dispid),
# needs to be set before name
'order': 0
}
attrs_config['include_in_backups']['func'] = lambda x: False
attrs_config['disp_savefile'] = {
'default': '/var/run/qubes/current-savefile',
'save': lambda: str(self.disp_savefile) }
return attrs_config
def __init__(self, **kwargs):
disp_template = None
if 'disp_template' in kwargs.keys():
disp_template = kwargs['disp_template']
kwargs['template'] = disp_template.template
kwargs['dir_path'] = disp_template.dir_path
kwargs['kernel'] = disp_template.kernel
kwargs['uses_default_kernel'] = disp_template.uses_default_kernel
kwargs['kernelopts'] = disp_template.kernelopts
kwargs['uses_default_kernelopts'] = \
disp_template.uses_default_kernelopts
super(QubesDisposableVm, self).__init__(**kwargs)
assert self.template is not None, "Missing template for DisposableVM!"
if disp_template:
self.clone_attrs(disp_template)
# Use DispVM icon with the same color
if self._label:
self._label = QubesDispVmLabels[self._label.name]
self.icon_path = self._label.icon_path
@property
def type(self):
return "DisposableVM"
def is_disposablevm(self):
return True
@property
def ip(self):
if self.netvm is not None:
return self.netvm.get_ip_for_dispvm(self.dispid)
else:
return None
def get_clone_attrs(self):
attrs = super(QubesDisposableVm, self).get_clone_attrs()
attrs.remove('_label')
return attrs
def do_not_use_get_xml_attrs(self):
# Minimal set - do not inherit rest of attributes
attrs = {}
attrs["qid"] = str(self.qid)
attrs["name"] = self.name
attrs["dispid"] = str(self.dispid)
attrs["template_qid"] = str(self.template.qid)
attrs["label"] = self.label.name
attrs["firewall_conf"] = self.relative_path(self.firewall_conf)
attrs["netvm_qid"] = str(self.netvm.qid) if self.netvm is not None else "none"
return attrs
def verify_files(self):
return True
def get_config_params(self):
attrs = super(QubesDisposableVm, self).get_config_params()
attrs['privatedev'] = ''
return attrs
def create_qubesdb_entries(self):
super(QubesDisposableVm, self).create_qubesdb_entries()
self.qdb.write("/qubes-vm-persistence", "none")
self.qdb.write('/qubes-restore-complete', '1')
def start(self, verbose = False, **kwargs):
self.log.debug('start()')
if dry_run:
return
# Intentionally not used is_running(): eliminate also "Paused", "Crashed", "Halting"
if self.get_power_state() != "Halted":
raise QubesException ("VM is already running!")
if self.netvm is not None:
if self.netvm.qid != 0:
if not self.netvm.is_running():
if verbose:
print >> sys.stderr, "--> Starting NetVM {0}...".\
format(self.netvm.name)
self.netvm.start(verbose=verbose, **kwargs)
if verbose:
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
print >>sys.stderr, "time=%s, creating config file" % (str(time.time()))
# refresh config file
domain_config = self.create_config_file()
qmemman_client = self.request_memory()
# dispvm cannot have PCI devices
assert (len(self.pcidevs) == 0), "DispVM cannot have PCI devices"
print >>sys.stderr, "time=%s, calling restore" % (str(time.time()))
vmm.libvirt_conn.restoreFlags(self.disp_savefile,
domain_config, libvirt.VIR_DOMAIN_SAVE_PAUSED)
print >>sys.stderr, "time=%s, done" % (str(time.time()))
self._libvirt_domain = None
if verbose:
print >> sys.stderr, "--> Starting Qubes DB..."
self.start_qubesdb()
self.services['qubes-dvm'] = True
if verbose:
print >> sys.stderr, "--> Setting Qubes DB info for the VM..."
self.create_qubesdb_entries()
print >>sys.stderr, "time=%s, done qubesdb" % (str(time.time()))
# fire hooks
for hook in self.hooks_start:
hook(self, verbose = verbose, **kwargs)
if verbose:
print >> sys.stderr, "--> Starting the VM..."
self.libvirt_domain.resume()
print >>sys.stderr, "time=%s, resumed" % (str(time.time()))
# close() is not really needed, because the descriptor is close-on-exec
# anyway, the reason to postpone close() is that possibly xl is not done
# constructing the domain after its main process exits
# so we close() when we know the domain is up
# the successful unpause is some indicator of it
if qmemman_present:
qmemman_client.close()
if kwargs.get('start_guid', True) and \
any(os.path.exists(x) for x in GUID_SHMID_FILE):
self.start_guid(verbose=verbose, before_qrexec=True,
notify_function=kwargs.get('notify_function', None))
self.start_qrexec_daemon(verbose=verbose,
notify_function=kwargs.get('notify_function', None))
print >>sys.stderr, "time=%s, qrexec done" % (str(time.time()))
if kwargs.get('start_guid', True) and \
any(os.path.exists(x) for x in GUID_SHMID_FILE):
self.start_guid(verbose=verbose,
notify_function=kwargs.get('notify_function', None))
print >>sys.stderr, "time=%s, guid done" % (str(time.time()))
return self.xid
def remove_from_disk(self):
# nothing to remove
pass
# register classes
register_qubes_vm_class(QubesDisposableVm)

View File

@ -1,457 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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 os.path
import signal
import subprocess
import sys
import shutil
from xml.etree import ElementTree
from qubes.qubes import (
dry_run,
defaults,
register_qubes_vm_class,
system_path,
vmm,
QubesException,
QubesResizableVm,
)
system_path["config_template_hvm"] = '/usr/share/qubes/vm-template-hvm.xml'
defaults["hvm_disk_size"] = 20*1024*1024*1024
defaults["hvm_private_img_size"] = 2*1024*1024*1024
defaults["hvm_memory"] = 512
class QubesHVm(QubesResizableVm):
"""
A class that represents an HVM. A child of QubesVm.
"""
# FIXME: logically should inherit after QubesAppVm, but none of its methods
# are useful for HVM
def get_attrs_config(self):
attrs = super(QubesHVm, self).get_attrs_config()
attrs.pop('kernel')
attrs.pop('kernels_dir')
attrs.pop('kernelopts')
attrs.pop('uses_default_kernel')
attrs.pop('uses_default_kernelopts')
attrs['dir_path']['func'] = lambda value: value if value is not None \
else os.path.join(system_path["qubes_appvms_dir"], self.name)
attrs['config_file_template']['func'] = \
lambda x: system_path["config_template_hvm"]
attrs['drive'] = { 'attr': '_drive',
'save': lambda: str(self.drive) }
# Remove this two lines when HVM will get qmemman support
attrs['maxmem'].pop('save')
attrs['maxmem']['func'] = lambda x: self.memory
attrs['timezone'] = { 'default': 'localtime',
'save': lambda: str(self.timezone) }
attrs['qrexec_installed'] = { 'default': False,
'attr': '_qrexec_installed',
'save': lambda: str(self._qrexec_installed) }
attrs['guiagent_installed'] = { 'default' : False,
'attr': '_guiagent_installed',
'save': lambda: str(self._guiagent_installed) }
attrs['seamless_gui_mode'] = { 'default': False,
'attr': '_seamless_gui_mode',
'save': lambda: str(self._seamless_gui_mode) }
attrs['services']['default'] = "{'meminfo-writer': False}"
attrs['memory']['default'] = defaults["hvm_memory"]
return attrs
def __init__(self, **kwargs):
super(QubesHVm, self).__init__(**kwargs)
# Default for meminfo-writer have changed to (correct) False in the
# same version as introduction of guiagent_installed, so for older VMs
# with wrong setting, change is based on 'guiagent_installed' presence
if "guiagent_installed" not in kwargs and \
(not 'xml_element' in kwargs or kwargs['xml_element'].get('guiagent_installed') is None):
self.services['meminfo-writer'] = False
@property
def type(self):
return "HVM"
def is_appvm(self):
return True
@classmethod
def is_template_compatible(cls, template):
if template and (not template.is_template() or template.type != "TemplateHVM"):
return False
return True
def get_clone_attrs(self):
attrs = super(QubesHVm, self).get_clone_attrs()
attrs.remove('kernel')
attrs.remove('uses_default_kernel')
attrs.remove('kernelopts')
attrs.remove('uses_default_kernelopts')
attrs += [ 'timezone' ]
attrs += [ 'qrexec_installed' ]
attrs += [ 'guiagent_installed' ]
return attrs
@property
def qrexec_installed(self):
return self._qrexec_installed or \
bool(self.template and self.template.qrexec_installed)
@qrexec_installed.setter
def qrexec_installed(self, value):
if self.template and self.template.qrexec_installed and not value:
print >>sys.stderr, "WARNING: When qrexec_installed set in template, it will be propagated to the VM"
self._qrexec_installed = value
@property
def guiagent_installed(self):
return self._guiagent_installed or \
bool(self.template and self.template.guiagent_installed)
@guiagent_installed.setter
def guiagent_installed(self, value):
if self.template and self.template.guiagent_installed and not value:
print >>sys.stderr, "WARNING: When guiagent_installed set in template, it will be propagated to the VM"
self._guiagent_installed = value
@property
def seamless_gui_mode(self):
if not self.guiagent_installed:
return False
return self._seamless_gui_mode
@seamless_gui_mode.setter
def seamless_gui_mode(self, value):
if self._seamless_gui_mode == value:
return
if not self.guiagent_installed and value:
raise ValueError("Seamless GUI mode requires GUI agent installed")
self._seamless_gui_mode = value
if self.is_running():
self.send_gui_mode()
@property
def drive(self):
return self._drive
@drive.setter
def drive(self, value):
if value is None:
self._drive = None
return
# strip type for a moment
drv_type = "cdrom"
if value.startswith("hd:") or value.startswith("cdrom:"):
(drv_type, unused, value) = value.partition(":")
drv_type = drv_type.lower()
# sanity check
if drv_type not in ['hd', 'cdrom']:
raise QubesException("Unsupported drive type: %s" % type)
if value.count(":") == 0:
value = "dom0:" + value
if value.count(":/") == 0:
# FIXME: when Windows backend will be supported, improve this
raise QubesException("Drive path must be absolute")
self._drive = drv_type + ":" + value
def create_on_disk(self, verbose, source_template = None):
self.log.debug('create_on_disk(source_template={!r})'.format(
source_template))
if dry_run:
return
if source_template is None:
source_template = self.template
# create empty disk
self.storage.private_img_size = defaults["hvm_private_img_size"]
self.storage.root_img_size = defaults["hvm_disk_size"]
self.storage.create_on_disk(verbose, source_template)
if verbose:
print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
try:
if hasattr(os, "symlink"):
os.symlink (self.label.icon_path, self.icon_path)
else:
shutil.copy(self.label.icon_path, self.icon_path)
except Exception as e:
print >> sys.stderr, "WARNING: Failed to set VM icon: %s" % str(e)
# Make sure that we have UUID allocated
self._update_libvirt_domain()
# fire hooks
for hook in self.hooks_create_on_disk:
hook(self, verbose, source_template=source_template)
def get_private_img_sz(self):
if not os.path.exists(self.private_img):
return 0
return os.path.getsize(self.private_img)
def resize_private_img(self, size):
assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
if self.is_running():
raise NotImplementedError("Online resize of HVM's private.img not implemented, shutdown the VM first")
self.storage.resize_private_img(size)
def get_config_params(self):
params = super(QubesHVm, self).get_config_params()
self.storage.drive = self.drive
params.update(self.storage.get_config_params())
params['volatiledev'] = ''
if self.timezone.lower() == 'localtime':
params['time_basis'] = 'localtime'
params['timeoffset'] = '0'
elif self.timezone.isdigit():
params['time_basis'] = 'UTC'
params['timeoffset'] = self.timezone
else:
print >>sys.stderr, "WARNING: invalid 'timezone' value: %s" % self.timezone
params['time_basis'] = 'UTC'
params['timeoffset'] = '0'
return params
def verify_files(self):
if dry_run:
return
self.storage.verify_files()
# fire hooks
for hook in self.hooks_verify_files:
hook(self)
return True
@property
def vif(self):
if self.xid < 0:
return None
if self.netvm is None:
return None
return "vif{0}.+".format(self.stubdom_xid)
@property
def mac(self):
if self._mac is not None:
return self._mac
elif self.template is not None:
return self.template.mac
else:
return "00:16:3E:5E:6C:{qid:02X}".format(qid=self.qid)
@mac.setter
def mac(self, value):
self._mac = value
def run(self, command, **kwargs):
if self.qrexec_installed:
if 'gui' in kwargs and kwargs['gui']==False:
command = "nogui:" + command
return super(QubesHVm, self).run(command, **kwargs)
else:
raise QubesException("Needs qrexec agent installed in VM to use this function. See also qvm-prefs.")
@property
def stubdom_xid(self):
if self.xid < 0:
return -1
if vmm.xs is None:
return -1
stubdom_xid_str = vmm.xs.read('', '/local/domain/%d/image/device-model-domid' % self.xid)
if stubdom_xid_str is not None:
return int(stubdom_xid_str)
else:
return -1
def validate_drive_path(self, drive):
drive_type, drive_domain, drive_path = drive.split(':', 2)
if drive_domain == 'dom0':
if not os.path.exists(drive_path):
raise QubesException("Invalid drive path '{}'".format(
drive_path))
def start(self, *args, **kwargs):
if self.drive:
self.validate_drive_path(self.drive)
# make it available to storage.prepare_for_vm_startup, which is
# called before actually building VM libvirt configuration
self.storage.drive = self.drive
if self.template and self.template.is_running():
raise QubesException("Cannot start the HVM while its template is running")
try:
if 'mem_required' not in kwargs:
# Reserve 44MB for stubdomain
kwargs['mem_required'] = (self.memory + 44) * 1024 * 1024
return super(QubesHVm, self).start(*args, **kwargs)
except QubesException as e:
capabilities = vmm.libvirt_conn.getCapabilities()
tree = ElementTree.fromstring(capabilities)
os_types = tree.findall('./guest/os_type')
if 'hvm' not in map(lambda x: x.text, os_types):
raise QubesException("Cannot start HVM without VT-x/AMD-v enabled")
else:
raise
def start_stubdom_guid(self, verbose=True):
guid_cmd = [system_path["qubes_guid_path"],
"-d", str(self.stubdom_xid),
"-t", str(self.xid),
"-N", self.name,
"-c", self.label.color,
"-i", self.label.icon_path,
"-l", str(self.label.index)]
if self.debug:
guid_cmd += ['-v', '-v']
elif not verbose:
guid_cmd += ['-q']
retcode = subprocess.call (guid_cmd)
if (retcode != 0) :
raise QubesException("Cannot start qubes-guid!")
def start_guid(self, verbose=True, notify_function=None,
before_qrexec=False, **kwargs):
if not before_qrexec:
return
if not self.guiagent_installed or self.debug:
if verbose:
print >> sys.stderr, "--> Starting Qubes GUId (full screen)..."
self.start_stubdom_guid(verbose=verbose)
kwargs['extra_guid_args'] = kwargs.get('extra_guid_args', []) + \
['-Q', '-n']
stubdom_guid_pidfile = \
'/var/run/qubes/guid-running.%d' % self.stubdom_xid
if not self.debug and os.path.exists(stubdom_guid_pidfile):
# Terminate stubdom guid once "real" gui agent connects
stubdom_guid_pid = int(open(stubdom_guid_pidfile, 'r').read())
kwargs['extra_guid_args'] += ['-K', str(stubdom_guid_pid)]
super(QubesHVm, self).start_guid(verbose, notify_function, **kwargs)
def start_qrexec_daemon(self, **kwargs):
if not self.qrexec_installed:
if kwargs.get('verbose', False):
print >> sys.stderr, "--> Starting the qrexec daemon..."
xid = self.get_xid()
qrexec_env = os.environ.copy()
qrexec_env['QREXEC_STARTUP_NOWAIT'] = '1'
retcode = subprocess.call ([system_path["qrexec_daemon_path"], str(xid), self.name, self.default_user], env=qrexec_env)
if (retcode != 0) :
self.force_shutdown(xid=xid)
raise OSError ("ERROR: Cannot execute qrexec-daemon!")
else:
super(QubesHVm, self).start_qrexec_daemon(**kwargs)
if self.guiagent_installed:
if kwargs.get('verbose'):
print >> sys.stderr, "--> Waiting for user '%s' login..." % self.default_user
self.wait_for_session(notify_function=kwargs.get('notify_function', None))
self.send_gui_mode()
def send_gui_mode(self):
if self.seamless_gui_mode:
service_input = "SEAMLESS"
else:
service_input = "FULLSCREEN"
self.run_service("qubes.SetGuiMode", input=service_input)
def _cleanup_zombie_domains(self):
super(QubesHVm, self)._cleanup_zombie_domains()
if not self.is_running():
xc_stubdom = self.get_xc_dominfo(name=self.name+'-dm')
if xc_stubdom is not None:
if xc_stubdom['paused'] == 1:
subprocess.call(['xl', 'destroy', str(xc_stubdom['domid'])])
if xc_stubdom['dying'] == 1:
# GUID still running?
guid_pidfile = \
'/var/run/qubes/guid-running.%d' % xc_stubdom['domid']
if os.path.exists(guid_pidfile):
guid_pid = open(guid_pidfile).read().strip()
os.kill(int(guid_pid), 15)
def suspend(self):
if dry_run:
return
if not self.is_running() and not self.is_paused():
raise QubesException ("VM not running!")
self.pause()
def is_guid_running(self):
# If user force the guiagent, is_guid_running will mimic a standard QubesVM
if self.guiagent_installed:
return super(QubesHVm, self).is_guid_running()
else:
xid = self.stubdom_xid
if xid < 0:
return False
if not os.path.exists('/var/run/qubes/guid-running.%d' % xid):
return False
return True
def is_fully_usable(self):
# Running gui-daemon implies also VM running
if not self.is_guid_running():
return False
if self.qrexec_installed and not self.is_qrexec_running():
return False
return True
register_qubes_vm_class(QubesHVm)

View File

@ -1,110 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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 os.path
import subprocess
import stat
import sys
import re
from qubes.qubes import QubesHVm,register_qubes_vm_class,dry_run,vmm
from qubes.qubes import QubesException,QubesVmCollection
from qubes.qubes import system_path,defaults
class QubesTemplateHVm(QubesHVm):
"""
A class that represents an HVM template. A child of QubesHVm.
"""
# In which order load this VM type from qubes.xml
load_order = 50
def get_attrs_config(self):
attrs_config = super(QubesTemplateHVm, self).get_attrs_config()
attrs_config['dir_path']['func'] = \
lambda value: value if value is not None else \
os.path.join(system_path["qubes_templates_dir"], self.name)
attrs_config['label']['default'] = defaults["template_label"]
return attrs_config
def __init__(self, **kwargs):
super(QubesTemplateHVm, self).__init__(**kwargs)
self.appvms = QubesVmCollection()
@property
def type(self):
return "TemplateHVM"
@property
def updateable(self):
return True
def is_template(self):
return True
def is_appvm(self):
return False
@property
def rootcow_img(self):
return self.storage.rootcow_img
@classmethod
def is_template_compatible(cls, template):
if template is None:
return True
return False
def resize_root_img(self, size):
for vm in self.appvms.values():
if vm.is_running():
raise QubesException("Cannot resize root.img while any VM "
"based on this tempate is running")
return super(QubesTemplateHVm, self).resize_root_img(size)
def start(self, *args, **kwargs):
for vm in self.appvms.values():
if vm.is_running():
raise QubesException("Cannot start HVM template while VMs based on it are running")
return super(QubesTemplateHVm, self).start(*args, **kwargs)
def commit_changes (self, verbose = False):
self.log.debug('commit_changes()')
if not vmm.offline_mode:
assert not self.is_running(), "Attempt to commit changes on running Template VM!"
if verbose:
print >> sys.stderr, "--> Commiting template updates... COW: {0}...".format (self.rootcow_img)
if dry_run:
return
self.storage.commit_template_changes()
register_qubes_vm_class(QubesTemplateHVm)

View File

@ -1,15 +0,0 @@
PYTHON_QUBESMODPATH = $(PYTHON_SITEPATH)/qubes/modules
all:
python -m compileall .
python -O -m compileall .
install:
ifndef PYTHON_SITEPATH
$(error PYTHON_SITEPATH not defined)
endif
mkdir -p $(DESTDIR)$(PYTHON_QUBESMODPATH)
cp 0*.py $(DESTDIR)$(PYTHON_QUBESMODPATH)
cp 0*.py[co] $(DESTDIR)$(PYTHON_QUBESMODPATH)
cp __init__.py $(DESTDIR)$(PYTHON_QUBESMODPATH)
cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESMODPATH)

View File

@ -1,5 +0,0 @@
This directory contains Qubes core modules. It will be loaded in
lexicographical order, use numeric prefix to force load ordering.
0* - Qubes base modules
00* - Qubes core VM classes

1
core/.gitignore vendored
View File

@ -1 +0,0 @@
*.pyo

View File

@ -1,33 +0,0 @@
OS ?= Linux
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
SETTINGS_SUFFIX = $(BACKEND_VMM)-$(OS)
all:
python -m compileall .
python -O -m compileall .
make -C storage all
install:
ifndef PYTHON_SITEPATH
$(error PYTHON_SITEPATH not defined)
endif
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)
cp qubes.py $(DESTDIR)$(PYTHON_QUBESPATH)
cp qubes.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
cp qubesutils.py $(DESTDIR)$(PYTHON_QUBESPATH)
cp qubesutils.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
cp guihelpers.py $(DESTDIR)$(PYTHON_QUBESPATH)
cp guihelpers.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
cp notify.py $(DESTDIR)$(PYTHON_QUBESPATH)
cp notify.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
cp backup.py $(DESTDIR)$(PYTHON_QUBESPATH)
cp backup.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)
ifneq ($(BACKEND_VMM),)
if [ -r settings-$(SETTINGS_SUFFIX).py ]; then \
cp settings-$(SETTINGS_SUFFIX).py $(DESTDIR)$(PYTHON_QUBESPATH)/settings.py && \
cp settings-$(SETTINGS_SUFFIX).pyc $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyc && \
cp settings-$(SETTINGS_SUFFIX).pyo $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyo; \
fi
endif
make -C storage install

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import sys
from optparse import OptionParser
from PyQt4.QtGui import QApplication,QMessageBox
app = None
system_bus = None
def prepare_app():
global app
app = QApplication(sys.argv)
app.setOrganizationName("The Qubes Project")
app.setOrganizationDomain("http://qubes-os.org")
app.setApplicationName("Qubes")
def ask(text, title="Question", yestoall=False):
global app
if app is None:
prepare_app()
buttons = QMessageBox.Yes | QMessageBox.No
if yestoall:
buttons |= QMessageBox.YesToAll
reply = QMessageBox.question(None, title, text, buttons, defaultButton=QMessageBox.Yes)
if reply == QMessageBox.Yes:
return 0
elif reply == QMessageBox.No:
return 1
elif reply == QMessageBox.YesToAll:
return 2
else:
#?!
return 127

View File

@ -1 +0,0 @@
../core-modules

View File

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2014 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import sys
system_bus = None
session_bus = None
notify_object = None
def tray_notify_init():
import dbus
global notify_object
try:
notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
except dbus.DBusException as ex:
print >>sys.stderr, "WARNING: failed connect to tray notification service: %s" % str(ex)
def tray_notify(msg, label, timeout = 3000):
if notify_object:
if label:
if not isinstance(label, str):
label = label.icon
notify_object.Notify("Qubes", 0, label, "Qubes", msg, [], [], timeout,
dbus_interface="org.freedesktop.Notifications")
def tray_notify_error(msg, timeout = 3000):
if notify_object:
notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", msg, [], [],
timeout, dbus_interface="org.freedesktop.Notifications")
def notify_error_qubes_manager(name, message):
import dbus
global system_bus
if system_bus is None:
system_bus = dbus.SystemBus()
try:
qubes_manager = system_bus.get_object('org.qubesos.QubesManager',
'/org/qubesos/QubesManager')
qubes_manager.notify_error(name, message, dbus_interface='org.qubesos.QubesManager')
except dbus.DBusException:
# ignore the case when no qubes-manager is running
pass
def clear_error_qubes_manager(name, message):
import dbus
global system_bus
if system_bus is None:
system_bus = dbus.SystemBus()
try:
qubes_manager = system_bus.get_object('org.qubesos.QubesManager',
'/org/qubesos/QubesManager')
qubes_manager.clear_error_exact(name, message, dbus_interface='org.qubesos.QubesManager')
except dbus.DBusException:
# ignore the case when no qubes-manager is running
pass

View File

@ -1,965 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.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.
#
#
from __future__ import absolute_import
import atexit
import grp
import logging
import os
import os.path
import sys
import tempfile
import time
import warnings
import xml.parsers.expat
import lxml.etree
if os.name == 'posix':
import fcntl
elif os.name == 'nt':
import win32con
import win32file
import pywintypes
else:
raise RuntimeError, "Qubes works only on POSIX or WinNT systems"
# Do not use XenAPI or create/read any VM files
# This is for testing only!
dry_run = False
#dry_run = True
if not dry_run:
import libvirt
try:
import xen.lowlevel.xs
except ImportError:
pass
qubes_base_dir = "/var/lib/qubes"
system_path = {
'qubes_guid_path': '/usr/bin/qubes-guid',
'qrexec_daemon_path': '/usr/lib/qubes/qrexec-daemon',
'qrexec_client_path': '/usr/lib/qubes/qrexec-client',
'qubesdb_daemon_path': '/usr/sbin/qubesdb-daemon',
'qubes_base_dir': qubes_base_dir,
# Relative to qubes_base_dir
'qubes_appvms_dir': 'appvms',
'qubes_templates_dir': 'vm-templates',
'qubes_servicevms_dir': 'servicevms',
'qubes_store_filename': 'qubes.xml',
'qubes_kernels_base_dir': 'vm-kernels',
# qubes_icon_dir is obsolete
# use QIcon.fromTheme() where applicable
'qubes_icon_dir': '/usr/share/icons/hicolor/128x128/devices',
'qrexec_policy_dir': '/etc/qubes-rpc/policy',
'config_template_pv': '/usr/share/qubes/vm-template.xml',
'qubes_pciback_cmd': '/usr/lib/qubes/unbind-pci-device.sh',
'prepare_volatile_img_cmd': '/usr/lib/qubes/prepare-volatile-img.sh',
}
vm_files = {
'root_img': 'root.img',
'rootcow_img': 'root-cow.img',
'volatile_img': 'volatile.img',
'private_img': 'private.img',
'kernels_subdir': 'kernels',
'firewall_conf': 'firewall.xml',
'whitelisted_appmenus': 'whitelisted-appmenus.list',
'updates_stat_file': 'updates.stat',
}
defaults = {
'libvirt_uri': 'xen:///',
'memory': 400,
'kernelopts': "nopat",
'kernelopts_pcidevs': "nopat iommu=soft swiotlb=8192",
'dom0_update_check_interval': 6*3600,
'private_img_size': 2*1024*1024*1024,
'root_img_size': 10*1024*1024*1024,
'storage_class': None,
# how long (in sec) to wait for VMs to shutdown,
# before killing them (when used qvm-run with --wait option),
'shutdown_counter_max': 60,
'vm_default_netmask': "255.255.255.0",
# Set later
'appvm_label': None,
'template_label': None,
'servicevm_label': None,
}
qubes_max_qid = 254
qubes_max_netid = 254
class QubesException (Exception):
pass
class QubesVMMConnection(object):
def __init__(self):
self._libvirt_conn = None
self._xs = None
self._xc = None
self._offline_mode = False
@property
def offline_mode(self):
return self._offline_mode
@offline_mode.setter
def offline_mode(self, value):
if not value and self._libvirt_conn is not None:
raise QubesException("Cannot change offline mode while already connected")
self._offline_mode = value
def _libvirt_error_handler(self, ctx, error):
pass
def init_vmm_connection(self):
if self._libvirt_conn is not None:
# Already initialized
return
if self._offline_mode:
# Do not initialize in offline mode
return
if 'xen.lowlevel.xs' in sys.modules:
self._xs = xen.lowlevel.xs.xs()
self._libvirt_conn = libvirt.open(defaults['libvirt_uri'])
if self._libvirt_conn == None:
raise QubesException("Failed connect to libvirt driver")
libvirt.registerErrorHandler(self._libvirt_error_handler, None)
atexit.register(self._libvirt_conn.close)
def _common_getter(self, name):
if self._offline_mode:
# Do not initialize in offline mode
raise QubesException("VMM operations disabled in offline mode")
if self._libvirt_conn is None:
self.init_vmm_connection()
return getattr(self, name)
@property
def libvirt_conn(self):
return self._common_getter('_libvirt_conn')
@property
def xs(self):
if 'xen.lowlevel.xs' in sys.modules:
return self._common_getter('_xs')
else:
return None
##### VMM global variable definition #####
if not dry_run:
vmm = QubesVMMConnection()
##########################################
class QubesHost(object):
def __init__(self):
(model, memory, cpus, mhz, nodes, socket, cores, threads) = vmm.libvirt_conn.getInfo()
self._total_mem = long(memory)*1024
self._no_cpus = cpus
# print "QubesHost: total_mem = {0}B".format (self.xen_total_mem)
# print "QubesHost: free_mem = {0}".format (self.get_free_xen_memory())
# print "QubesHost: total_cpus = {0}".format (self.xen_no_cpus)
@property
def memory_total(self):
return self._total_mem
@property
def no_cpus(self):
return self._no_cpus
# TODO
def measure_cpu_usage(self, qvmc, previous=None, previous_time = None,
wait_time=1):
"""measure cpu usage for all domains at once"""
if previous is None:
previous_time = time.time()
previous = {}
for vm in qvmc.values():
if not vm.is_running():
continue
cputime = vm.get_cputime()
previous[vm.xid] = {}
previous[vm.xid]['cpu_time'] = (
cputime / max(vm.vcpus, 1))
previous[vm.xid]['cpu_usage'] = 0
time.sleep(wait_time)
current_time = time.time()
current = {}
for vm in qvmc.values():
if not vm.is_running():
continue
cputime = vm.get_cputime()
current[vm.xid] = {}
current[vm.xid]['cpu_time'] = (
cputime / max(vm.vcpus, 1))
if vm.xid in previous.keys():
current[vm.xid]['cpu_usage'] = (
float(current[vm.xid]['cpu_time'] -
previous[vm.xid]['cpu_time']) /
long(1000**3) / (current_time-previous_time) * 100)
if current[vm.xid]['cpu_usage'] < 0:
# VM has been rebooted
current[vm.xid]['cpu_usage'] = 0
else:
current[vm.xid]['cpu_usage'] = 0
return (current_time, current)
class QubesVmLabel(object):
def __init__(self, index, color, name, dispvm=False):
self.index = index
self.color = color
self.name = name
self.dispvm = dispvm
self.icon = '{}-{}'.format(('dispvm' if dispvm else 'appvm'), name)
def __repr__(self):
return '{}({!r}, {!r}, {!r}, dispvm={!r})'.format(
self.__class__.__name__,
self.index,
self.color,
self.name,
self.dispvm)
# self.icon_path is obsolete
# use QIcon.fromTheme(label.icon) where applicable
@property
def icon_path(self):
return os.path.join(system_path['qubes_icon_dir'], self.icon) + ".png"
def register_qubes_vm_class(vm_class):
QubesVmClasses[vm_class.__name__] = vm_class
# register class as local for this module - to make it easy to import from
# other modules
setattr(sys.modules[__name__], vm_class.__name__, vm_class)
class QubesVmCollection(dict):
"""
A collection of Qubes VMs indexed by Qubes id (qid)
"""
def __init__(self, store_filename=None):
super(QubesVmCollection, self).__init__()
self.default_netvm_qid = None
self.default_fw_netvm_qid = None
self.default_template_qid = None
self.default_kernel = None
self.updatevm_qid = None
self.qubes_store_filename = store_filename
if not store_filename:
self.qubes_store_filename = system_path["qubes_store_filename"]
self.clockvm_qid = None
self.qubes_store_file = None
self.log = logging.getLogger('qubes.qvmc.{:x}'.format(id(self)))
self.log.debug('instantiated store_filename={!r}'.format(
self.qubes_store_filename))
def __repr__(self):
return '<{} {!r}>'.format(self.__class__.__name__, list(sorted(self.keys())))
def clear(self):
self.log.debug('clear()')
# Hack for releasing FDs, which otherwise would be leaked because of
# circular dependencies on QubesVMs objects (so garbage collector
# doesn't handle them). See #1380 for details
for vm in self.values():
try:
if vm._qdb_connection:
vm._qdb_connection.close()
vm._qdb_connection = None
except AttributeError:
pass
super(QubesVmCollection, self).clear()
def values(self):
for qid in self.keys():
yield self[qid]
def items(self):
for qid in self.keys():
yield (qid, self[qid])
def __iter__(self):
for qid in sorted(super(QubesVmCollection, self).keys()):
yield qid
keys = __iter__
def __setitem__(self, key, value):
self.log.debug('[{!r}] = {!r}'.format(key, value))
if key not in self:
return super(QubesVmCollection, self).__setitem__(key, value)
else:
assert False, "Attempt to add VM with qid that already exists in the collection!"
def add_new_vm(self, vm_type, **kwargs):
self.log.debug('add_new_vm(vm_type={}, **kwargs={!r})'.format(
vm_type, kwargs))
if vm_type not in QubesVmClasses.keys():
raise ValueError("Unknown VM type: %s" % vm_type)
qid = self.get_new_unused_qid()
vm_cls = QubesVmClasses[vm_type]
if 'template' in kwargs:
if not vm_cls.is_template_compatible(kwargs['template']):
raise QubesException("Template not compatible with selected "
"VM type")
vm = vm_cls(qid=qid, collection=self, **kwargs)
if not self.verify_new_vm(vm):
raise QubesException("Wrong VM description!")
self[vm.qid] = vm
# make first created NetVM the default one
if self.default_fw_netvm_qid is None and vm.is_netvm():
self.set_default_fw_netvm(vm)
if self.default_netvm_qid is None and vm.is_proxyvm():
self.set_default_netvm(vm)
# make first created TemplateVM the default one
if self.default_template_qid is None and vm.is_template():
self.set_default_template(vm)
# make first created ProxyVM the UpdateVM
if self.updatevm_qid is None and vm.is_proxyvm():
self.set_updatevm_vm(vm)
# by default ClockVM is the first NetVM
if self.clockvm_qid is None and vm.is_netvm():
self.set_clockvm_vm(vm)
return vm
def add_new_appvm(self, name, template,
dir_path = None, conf_file = None,
private_img = None,
label = None):
warnings.warn("Call to deprecated function, use add_new_vm instead",
DeprecationWarning, stacklevel=2)
return self.add_new_vm("QubesAppVm", name=name, template=template,
dir_path=dir_path, conf_file=conf_file,
private_img=private_img,
netvm = self.get_default_netvm(),
kernel = self.get_default_kernel(),
uses_default_kernel = True,
label=label)
def add_new_hvm(self, name, label = None):
warnings.warn("Call to deprecated function, use add_new_vm instead",
DeprecationWarning, stacklevel=2)
return self.add_new_vm("QubesHVm", name=name, label=label)
def add_new_disposablevm(self, name, template, dispid,
label = None, netvm = None):
warnings.warn("Call to deprecated function, use add_new_vm instead",
DeprecationWarning, stacklevel=2)
return self.add_new_vm("QubesDisposableVm", name=name, template=template,
netvm = netvm,
label=label, dispid=dispid)
def add_new_templatevm(self, name,
dir_path = None, conf_file = None,
root_img = None, private_img = None,
installed_by_rpm = True):
warnings.warn("Call to deprecated function, use add_new_vm instead",
DeprecationWarning, stacklevel=2)
return self.add_new_vm("QubesTemplateVm", name=name,
dir_path=dir_path, conf_file=conf_file,
root_img=root_img, private_img=private_img,
installed_by_rpm=installed_by_rpm,
netvm = self.get_default_netvm(),
kernel = self.get_default_kernel(),
uses_default_kernel = True)
def add_new_netvm(self, name, template,
dir_path = None, conf_file = None,
private_img = None, installed_by_rpm = False,
label = None):
warnings.warn("Call to deprecated function, use add_new_vm instead",
DeprecationWarning, stacklevel=2)
return self.add_new_vm("QubesNetVm", name=name, template=template,
label=label,
private_img=private_img, installed_by_rpm=installed_by_rpm,
uses_default_kernel = True,
dir_path=dir_path, conf_file=conf_file)
def add_new_proxyvm(self, name, template,
dir_path = None, conf_file = None,
private_img = None, installed_by_rpm = False,
label = None):
warnings.warn("Call to deprecated function, use add_new_vm instead",
DeprecationWarning, stacklevel=2)
return self.add_new_vm("QubesProxyVm", name=name, template=template,
label=label,
private_img=private_img, installed_by_rpm=installed_by_rpm,
dir_path=dir_path, conf_file=conf_file,
uses_default_kernel = True,
netvm = self.get_default_fw_netvm())
def set_default_template(self, vm):
self.log.debug('set_default_template({!r})'.format(vm))
if vm is None:
self.default_template_qid = None
else:
assert vm.is_template(), "VM {0} is not a TemplateVM!".format(vm.name)
self.default_template_qid = vm.qid
def get_default_template(self):
if self.default_template_qid is None:
return None
else:
return self[self.default_template_qid]
def set_default_netvm(self, vm):
self.log.debug('set_default_netvm({!r})'.format(vm))
if vm is None:
self.default_netvm_qid = None
else:
assert vm.is_netvm(), "VM {0} does not provide network!".format(vm.name)
self.default_netvm_qid = vm.qid
def get_default_netvm(self):
if self.default_netvm_qid is None:
return None
else:
return self[self.default_netvm_qid]
def set_default_kernel(self, kernel):
self.log.debug('set_default_kernel({!r})'.format(kernel))
assert os.path.exists(
os.path.join(system_path["qubes_kernels_base_dir"], kernel)), \
"Kerel {0} not installed!".format(kernel)
self.default_kernel = kernel
def get_default_kernel(self):
return self.default_kernel
def set_default_fw_netvm(self, vm):
self.log.debug('set_default_fw_netvm({!r})'.format(vm))
if vm is None:
self.default_fw_netvm_qid = None
else:
assert vm.is_netvm(), "VM {0} does not provide network!".format(vm.name)
self.default_fw_netvm_qid = vm.qid
def get_default_fw_netvm(self):
if self.default_fw_netvm_qid is None:
return None
else:
return self[self.default_fw_netvm_qid]
def set_updatevm_vm(self, vm):
self.log.debug('set_updatevm_vm({!r})'.format(vm))
if vm is None:
self.updatevm_qid = None
else:
self.updatevm_qid = vm.qid
def get_updatevm_vm(self):
if self.updatevm_qid is None:
return None
else:
return self[self.updatevm_qid]
def set_clockvm_vm(self, vm):
self.log.debug('set_clockvm({!r})'.format(vm))
if vm is None:
self.clockvm_qid = None
else:
self.clockvm_qid = vm.qid
def get_clockvm_vm(self):
if self.clockvm_qid is None:
return None
else:
return self[self.clockvm_qid]
def get_vm_by_name(self, name):
for vm in self.values():
if (vm.name == name):
return vm
return None
def get_qid_by_name(self, name):
vm = self.get_vm_by_name(name)
return vm.qid if vm is not None else None
def get_vms_based_on(self, template_qid):
vms = set([vm for vm in self.values()
if (vm.template and vm.template.qid == template_qid)])
return vms
def get_vms_connected_to(self, netvm_qid):
new_vms = [ netvm_qid ]
dependend_vms_qid = []
# Dependency resolving only makes sense on NetVM (or derivative)
if not self[netvm_qid].is_netvm():
return set([])
while len(new_vms) > 0:
cur_vm = new_vms.pop()
for vm in self[cur_vm].connected_vms.values():
if vm.qid not in dependend_vms_qid:
dependend_vms_qid.append(vm.qid)
if vm.is_netvm():
new_vms.append(vm.qid)
vms = [vm for vm in self.values() if vm.qid in dependend_vms_qid]
return vms
def verify_new_vm(self, new_vm):
# Verify that qid is unique
for vm in self.values():
if vm.qid == new_vm.qid:
print >> sys.stderr, "ERROR: The qid={0} is already used by VM '{1}'!".\
format(vm.qid, vm.name)
return False
# Verify that name is unique
for vm in self.values():
if vm.name == new_vm.name:
print >> sys.stderr, \
"ERROR: The name={0} is already used by other VM with qid='{1}'!".\
format(vm.name, vm.qid)
return False
return True
def get_new_unused_qid(self):
used_ids = set([vm.qid for vm in self.values()])
for id in range (1, qubes_max_qid):
if id not in used_ids:
return id
raise LookupError ("Cannot find unused qid!")
def get_new_unused_netid(self):
used_ids = set([vm.netid for vm in self.values() if vm.is_netvm()])
for id in range (1, qubes_max_netid):
if id not in used_ids:
return id
raise LookupError ("Cannot find unused netid!")
def check_if_storage_exists(self):
try:
f = open (self.qubes_store_filename, 'r')
except IOError:
return False
f.close()
return True
def create_empty_storage(self):
self.log.debug('create_empty_storage()')
self.qubes_store_file = open (self.qubes_store_filename, 'w')
self.clear()
self.save()
def lock_db_for_reading(self):
if self.qubes_store_file is not None:
raise QubesException("lock already taken")
# save() would rename the file over qubes.xml, _then_ release lock,
# so we need to ensure that the file for which we've got the lock is
# still the right file
self.log.debug('lock_db_for_reading()')
while True:
self.qubes_store_file = open (self.qubes_store_filename, 'r')
if os.name == 'posix':
fcntl.lockf (self.qubes_store_file, fcntl.LOCK_SH)
elif os.name == 'nt':
overlapped = pywintypes.OVERLAPPED()
win32file.LockFileEx(win32file._get_osfhandle(self.qubes_store_file.fileno()),
0, 0, -0x10000, overlapped)
if os.fstat(self.qubes_store_file.fileno()) == os.stat(
self.qubes_store_filename):
break
self.qubes_store_file.close()
def lock_db_for_writing(self):
if self.qubes_store_file is not None:
raise QubesException("lock already taken")
# save() would rename the file over qubes.xml, _then_ release lock,
# so we need to ensure that the file for which we've got the lock is
# still the right file
self.log.debug('lock_db_for_writing()')
while True:
self.qubes_store_file = open (self.qubes_store_filename, 'r+')
if os.name == 'posix':
fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX)
elif os.name == 'nt':
overlapped = pywintypes.OVERLAPPED()
win32file.LockFileEx(win32file._get_osfhandle(self.qubes_store_file.fileno()),
win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, overlapped)
if os.fstat(self.qubes_store_file.fileno()) == os.stat(
self.qubes_store_filename):
break
self.qubes_store_file.close()
def unlock_db(self):
if self.qubes_store_file is None:
return
# intentionally do not call explicit unlock to not unlock the file
# before all buffers are flushed
self.log.debug('unlock_db()')
self.qubes_store_file.close()
self.qubes_store_file = None
def save(self):
self.log.debug('save()')
root = lxml.etree.Element(
"QubesVmCollection",
default_template=str(self.default_template_qid) \
if self.default_template_qid is not None else "None",
default_netvm=str(self.default_netvm_qid) \
if self.default_netvm_qid is not None else "None",
default_fw_netvm=str(self.default_fw_netvm_qid) \
if self.default_fw_netvm_qid is not None else "None",
updatevm=str(self.updatevm_qid) \
if self.updatevm_qid is not None else "None",
clockvm=str(self.clockvm_qid) \
if self.clockvm_qid is not None else "None",
default_kernel=str(self.default_kernel) \
if self.default_kernel is not None else "None",
)
for vm in self.values():
element = vm.create_xml_element()
if element is not None:
root.append(element)
tree = lxml.etree.ElementTree(root)
try:
new_store_file = tempfile.NamedTemporaryFile(prefix=self.qubes_store_filename, delete=False)
if os.name == 'posix':
fcntl.lockf (new_store_file, fcntl.LOCK_EX)
elif os.name == 'nt':
overlapped = pywintypes.OVERLAPPED()
win32file.LockFileEx(win32file._get_osfhandle(new_store_file.fileno()),
win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, overlapped)
tree.write(new_store_file, encoding="UTF-8", pretty_print=True)
new_store_file.flush()
os.chmod(new_store_file.name, 0660)
os.chown(new_store_file.name, -1, grp.getgrnam('qubes').gr_gid)
os.rename(new_store_file.name, self.qubes_store_filename)
self.qubes_store_file.close()
self.qubes_store_file = new_store_file
except EnvironmentError as err:
print("{0}: export error: {1}".format(
os.path.basename(sys.argv[0]), err))
return False
return True
def set_netvm_dependency(self, element):
kwargs = {}
attr_list = ("qid", "netvm_qid")
for attribute in attr_list:
kwargs[attribute] = element.get(attribute)
vm = self[int(kwargs["qid"])]
if vm.uses_default_netvm is True:
if vm.is_proxyvm():
netvm = self.get_default_fw_netvm()
else:
netvm = self.get_default_netvm()
kwargs.pop("netvm_qid")
else:
if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None:
netvm = None
kwargs.pop("netvm_qid")
else:
netvm_qid = int(kwargs.pop("netvm_qid"))
if netvm_qid not in self:
netvm = None
else:
netvm = self[netvm_qid]
# directly set internal attr to not call setters...
vm._netvm = netvm
if netvm:
netvm.connected_vms[vm.qid] = vm
def load_globals(self, element):
default_template = element.get("default_template")
self.default_template_qid = int(default_template) \
if default_template.lower() != "none" else None
default_netvm = element.get("default_netvm")
if default_netvm is not None:
self.default_netvm_qid = int(default_netvm) \
if default_netvm != "None" else None
#assert self.default_netvm_qid is not None
default_fw_netvm = element.get("default_fw_netvm")
if default_fw_netvm is not None:
self.default_fw_netvm_qid = int(default_fw_netvm) \
if default_fw_netvm != "None" else None
#assert self.default_netvm_qid is not None
updatevm = element.get("updatevm")
if updatevm is not None:
self.updatevm_qid = int(updatevm) \
if updatevm != "None" else None
#assert self.default_netvm_qid is not None
clockvm = element.get("clockvm")
if clockvm is not None:
self.clockvm_qid = int(clockvm) \
if clockvm != "None" else None
self.default_kernel = element.get("default_kernel")
def _check_global(self, attr, default):
qid = getattr(self, attr)
if qid is None:
return
try:
self[qid]
except KeyError:
setattr(self, attr, default)
def check_globals(self):
'''Ensure that all referenced qids are present in the collection'''
self._check_global('default_template_qid', None)
self._check_global('default_fw_netvm_qid', None)
self._check_global('default_netvm_qid', self.default_fw_netvm_qid)
self._check_global('updatevm_qid', self.default_netvm_qid)
self._check_global('clockvm_qid', self.default_netvm_qid)
def load(self):
self.log.debug('load()')
self.clear()
try:
self.qubes_store_file.seek(0)
tree = lxml.etree.parse(self.qubes_store_file)
except (EnvironmentError,
xml.parsers.expat.ExpatError) as err:
print("{0}: import error: {1}".format(
os.path.basename(sys.argv[0]), err))
return False
self.load_globals(tree.getroot())
for (vm_class_name, vm_class) in sorted(QubesVmClasses.items(),
key=lambda _x: _x[1].load_order):
vms_of_class = tree.findall(vm_class_name)
# first non-template based, then template based
sorted_vms_of_class = sorted(vms_of_class, key= \
lambda x: str(x.get('template_qid')).lower() != "none")
for element in sorted_vms_of_class:
try:
vm = vm_class(xml_element=element, collection=self)
self[vm.qid] = vm
except (ValueError, LookupError) as err:
print("{0}: import error ({1}): {2}".format(
os.path.basename(sys.argv[0]), vm_class_name, err))
raise
return False
# After importing all VMs, set netvm references, in the same order
for (vm_class_name, vm_class) in sorted(QubesVmClasses.items(),
key=lambda _x: _x[1].load_order):
for element in tree.findall(vm_class_name):
try:
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
print("{0}: import error2 ({}): {}".format(
os.path.basename(sys.argv[0]), vm_class_name, err))
return False
self.check_globals()
# if there was no clockvm entry in qubes.xml, try to determine default:
# root of default NetVM chain
if tree.getroot().get("clockvm") is None:
if self.default_netvm_qid is not None:
clockvm = self[self.default_netvm_qid]
# Find root of netvm chain
while clockvm.netvm is not None:
clockvm = clockvm.netvm
self.clockvm_qid = clockvm.qid
# Disable ntpd in ClockVM - to not conflict with ntpdate (both are
# using 123/udp port)
if self.clockvm_qid is not None:
self[self.clockvm_qid].services['ntpd'] = False
# Add dom0 if wasn't present in qubes.xml
if not 0 in self.keys():
dom0vm = QubesAdminVm (collection=self)
self[dom0vm.qid] = dom0vm
return True
def pop(self, qid):
self.log.debug('pop({})'.format(qid))
if self.default_netvm_qid == qid:
self.default_netvm_qid = None
if self.default_fw_netvm_qid == qid:
self.default_fw_netvm_qid = None
if self.clockvm_qid == qid:
self.clockvm_qid = None
if self.updatevm_qid == qid:
self.updatevm_qid = None
if self.default_template_qid == qid:
self.default_template_qid = None
return super(QubesVmCollection, self).pop(qid)
class QubesDaemonPidfile(object):
def __init__(self, name):
self.name = name
self.path = "/var/run/qubes/" + name + ".pid"
def create_pidfile(self):
f = open (self.path, 'w')
f.write(str(os.getpid()))
f.close()
def pidfile_exists(self):
return os.path.exists(self.path)
def read_pid(self):
f = open (self.path)
pid = f.read ().strip()
f.close()
return int(pid)
def pidfile_is_stale(self):
if not self.pidfile_exists():
return False
# check if the pid file is valid...
proc_path = "/proc/" + str(self.read_pid()) + "/cmdline"
if not os.path.exists (proc_path):
print >> sys.stderr, \
"Path {0} doesn't exist, assuming stale pidfile.".\
format(proc_path)
return True
return False # It's a good pidfile
def remove_pidfile(self):
os.remove (self.path)
def __enter__ (self):
# assumes the pidfile doesn't exist -- you should ensure it before opening the context
self.create_pidfile()
def __exit__ (self, exc_type, exc_val, exc_tb):
self.remove_pidfile()
return False
### Initialization code
# Globally defined lables
QubesVmLabels = {
"red": QubesVmLabel(1, "0xcc0000", "red" ),
"orange": QubesVmLabel(2, "0xf57900", "orange" ),
"yellow": QubesVmLabel(3, "0xedd400", "yellow" ),
"green": QubesVmLabel(4, "0x73d216", "green" ),
"gray": QubesVmLabel(5, "0x555753", "gray" ),
"blue": QubesVmLabel(6, "0x3465a4", "blue" ),
"purple": QubesVmLabel(7, "0x75507b", "purple" ),
"black": QubesVmLabel(8, "0x000000", "black" ),
}
QubesDispVmLabels = {
k: QubesVmLabel(index=v.index, color=v.color, name=v.name, dispvm=True)
for k, v in QubesVmLabels.iteritems()
}
defaults["appvm_label"] = QubesVmLabels["red"]
defaults["template_label"] = QubesVmLabels["black"]
defaults["servicevm_label"] = QubesVmLabels["red"]
QubesVmClasses = {}
modules_dir = os.path.join(os.path.dirname(__file__), 'modules')
for module_file in sorted(os.listdir(modules_dir)):
if not module_file.endswith(".py") or module_file == "__init__.py":
continue
__import__('qubes.modules.%s' % module_file[:-3])
try:
import qubes.settings
qubes.settings.apply(system_path, vm_files, defaults)
except ImportError:
pass
for path_key in system_path.keys():
if not os.path.isabs(system_path[path_key]):
system_path[path_key] = os.path.join(
system_path['qubes_base_dir'], system_path[path_key])
# vim:sw=4:et:

View File

@ -1,879 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@invisiblethingslab.com>
# Copyright (C) 2014 Wojciech Porczyk <wojciech@porczyk.eu>
#
# 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.
#
#
from __future__ import absolute_import
import string
import errno
from lxml import etree
from lxml.etree import ElementTree, SubElement, Element
from qubes.qubes import QubesException
from qubes.qubes import vmm,defaults
from qubes.qubes import system_path,vm_files
import sys
import os
import subprocess
import re
import time
import stat
import libvirt
from qubes.qdb import QubesDB,Error,DisconnectedError
import xen.lowlevel.xc
import xen.lowlevel.xs
BLKSIZE = 512
# all frontends, prefer xvdi
# TODO: get this from libvirt driver?
AVAILABLE_FRONTENDS = ['xvd'+c for c in
string.lowercase[8:]+string.lowercase[:8]]
class USBProxyNotInstalled(QubesException):
pass
def mbytes_to_kmg(size):
if size > 1024:
return "%d GiB" % (size/1024)
else:
return "%d MiB" % size
def kbytes_to_kmg(size):
if size > 1024:
return mbytes_to_kmg(size/1024)
else:
return "%d KiB" % size
def bytes_to_kmg(size):
if size > 1024:
return kbytes_to_kmg(size/1024)
else:
return "%d B" % size
def size_to_human (size):
"""Humane readable size, with 1/10 precission"""
if size < 1024:
return str (size);
elif size < 1024*1024:
return str(round(size/1024.0,1)) + ' KiB'
elif size < 1024*1024*1024:
return str(round(size/(1024.0*1024),1)) + ' MiB'
else:
return str(round(size/(1024.0*1024*1024),1)) + ' GiB'
def parse_size(size):
units = [ ('K', 1024), ('KB', 1024),
('M', 1024*1024), ('MB', 1024*1024),
('G', 1024*1024*1024), ('GB', 1024*1024*1024),
]
size = size.strip().upper()
if size.isdigit():
return int(size)
for unit, multiplier in units:
if size.endswith(unit):
size = size[:-len(unit)].strip()
return int(size)*multiplier
raise QubesException("Invalid size: {0}.".format(size))
def get_disk_usage_one(st):
try:
return st.st_blocks * BLKSIZE
except AttributeError:
return st.st_size
def get_disk_usage(path):
try:
st = os.lstat(path)
except OSError:
return 0
ret = get_disk_usage_one(st)
# if path is not a directory, this is skipped
for dirpath, dirnames, filenames in os.walk(path):
for name in dirnames + filenames:
ret += get_disk_usage_one(os.lstat(os.path.join(dirpath, name)))
return ret
def print_stdout(text):
print (text)
def print_stderr(text):
print >> sys.stderr, (text)
###### Block devices ########
def block_devid_to_name(devid):
major = devid / 256
minor = devid % 256
dev_class = ""
if major == 202:
dev_class = "xvd"
elif major == 8:
dev_class = "sd"
else:
raise QubesException("Unknown device class %d" % major)
if minor % 16 == 0:
return "%s%c" % (dev_class, ord('a')+minor/16)
else:
return "%s%c%d" % (dev_class, ord('a')+minor/16, minor%16)
def block_name_to_majorminor(name):
# check if it is already devid
if isinstance(name, int):
return (name / 256, name % 256)
if name.isdigit():
return (int(name) / 256, int(name) % 256)
if os.path.exists('/dev/%s' % name):
blk_info = os.stat(os.path.realpath('/dev/%s' % name))
if stat.S_ISBLK(blk_info.st_mode):
return (blk_info.st_rdev / 256, blk_info.st_rdev % 256)
major = 0
minor = 0
dXpY_style = False
disk = True
if name.startswith("xvd"):
major = 202
elif name.startswith("sd"):
major = 8
elif name.startswith("mmcblk"):
dXpY_style = True
major = 179
elif name.startswith("scd"):
disk = False
major = 11
elif name.startswith("sr"):
disk = False
major = 11
elif name.startswith("loop"):
dXpY_style = True
disk = False
major = 7
elif name.startswith("md"):
dXpY_style = True
major = 9
elif name.startswith("dm-"):
disk = False
major = 253
else:
# Unknown device
return (0, 0)
if not dXpY_style:
name_match = re.match(r"^([a-z]+)([a-z-])([0-9]*)$", name)
else:
name_match = re.match(r"^([a-z]+)([0-9]*)(?:p([0-9]+))?$", name)
if not name_match:
raise QubesException("Invalid device name: %s" % name)
if disk:
if dXpY_style:
minor = int(name_match.group(2))*8
else:
minor = (ord(name_match.group(2))-ord('a')) * 16
else:
minor = 0
if name_match.group(3):
minor += int(name_match.group(3))
return (major, minor)
def block_name_to_devid(name):
# check if it is already devid
if isinstance(name, int):
return name
if name.isdigit():
return int(name)
(major, minor) = block_name_to_majorminor(name)
return major << 8 | minor
def block_find_unused_frontend(vm = None):
assert vm is not None
assert vm.is_running()
xml = vm.libvirt_domain.XMLDesc()
parsed_xml = etree.fromstring(xml)
used = [target.get('dev', None) for target in
parsed_xml.xpath("//domain/devices/disk/target")]
for dev in AVAILABLE_FRONTENDS:
if dev not in used:
return dev
return None
def block_list_vm(vm, system_disks = False):
name_re = re.compile(r"^[a-z0-9-]{1,12}$")
device_re = re.compile(r"^[a-z0-9/-]{1,64}$")
# FIXME: any better idea of desc_re?
desc_re = re.compile(r"^.{1,255}$")
mode_re = re.compile(r"^[rw]$")
assert vm is not None
if not vm.is_running():
return []
devices_list = {}
try:
untrusted_devices = vm.qdb.multiread('/qubes-block-devices/')
except Error:
vm.refresh()
return {}
def get_dev_item(dev, item):
return untrusted_devices.get(
'/qubes-block-devices/%s/%s' % (dev, item),
None)
untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
untrusted_devices.keys())))
for untrusted_dev_name in untrusted_devices_names:
if name_re.match(untrusted_dev_name):
dev_name = untrusted_dev_name
untrusted_device_size = get_dev_item(dev_name, 'size')
untrusted_device_desc = get_dev_item(dev_name, 'desc')
untrusted_device_mode = get_dev_item(dev_name, 'mode')
untrusted_device_device = get_dev_item(dev_name, 'device')
if untrusted_device_desc is None or untrusted_device_mode is None\
or untrusted_device_size is None:
print >>sys.stderr, "Missing field in %s device parameters" %\
dev_name
continue
if untrusted_device_device is None:
untrusted_device_device = '/dev/' + dev_name
if not device_re.match(untrusted_device_device):
print >> sys.stderr, "Invalid %s device path in VM '%s'" % (
dev_name, vm.name)
continue
device_device = untrusted_device_device
if not untrusted_device_size.isdigit():
print >> sys.stderr, "Invalid %s device size in VM '%s'" % (
dev_name, vm.name)
continue
device_size = int(untrusted_device_size)
if not desc_re.match(untrusted_device_desc):
print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
dev_name, vm.name)
continue
device_desc = untrusted_device_desc
if not mode_re.match(untrusted_device_mode):
print >> sys.stderr, "Invalid %s device mode in VM '%s'" % (
dev_name, vm.name)
continue
device_mode = untrusted_device_mode
if not system_disks:
if vm.qid == 0 and device_desc.startswith(system_path[
"qubes_base_dir"]):
continue
visible_name = "%s:%s" % (vm.name, dev_name)
devices_list[visible_name] = {
"name": visible_name,
"vm": vm.name,
"device": device_device,
"size": device_size,
"desc": device_desc,
"mode": device_mode
}
return devices_list
def block_list(qvmc = None, vm = None, system_disks = False):
if vm is not None:
if not vm.is_running():
return []
else:
vm_list = [ vm ]
else:
if qvmc is None:
raise QubesException("You must pass either qvm or vm argument")
vm_list = qvmc.values()
devices_list = {}
for vm in vm_list:
devices_list.update(block_list_vm(vm, system_disks))
return devices_list
def block_check_attached(qvmc, device):
"""
@type qvmc: QubesVmCollection
"""
if qvmc is None:
# TODO: ValueError
raise QubesException("You need to pass qvmc argument")
for vm in qvmc.values():
if vm.qid == 0:
# Connecting devices to dom0 not supported
continue
if not vm.is_running():
continue
try:
libvirt_domain = vm.libvirt_domain
if libvirt_domain:
xml = libvirt_domain.XMLDesc()
else:
xml = None
except libvirt.libvirtError:
if vmm.libvirt_conn.virConnGetLastError()[0] == libvirt.VIR_ERR_NO_DOMAIN:
xml = None
else:
raise
if xml:
parsed_xml = etree.fromstring(xml)
disks = parsed_xml.xpath("//domain/devices/disk")
for disk in disks:
backend_name = 'dom0'
if disk.find('backenddomain') is not None:
backend_name = disk.find('backenddomain').get('name')
source = disk.find('source')
if disk.get('type') == 'file':
path = source.get('file')
elif disk.get('type') == 'block':
path = source.get('dev')
else:
# TODO: logger
print >>sys.stderr, "Unknown disk type '%s' attached to " \
"VM '%s'" % (source.get('type'),
vm.name)
continue
if backend_name == device['vm'] and (path == device['device']
or not path.startswith('/dev/') and path == device[
'desc']):
return {
"frontend": disk.find('target').get('dev'),
"vm": vm}
return None
def device_attach_check(vm, backend_vm, device, frontend, mode):
""" Checks all the parameters, dies on errors """
if not vm.is_running():
raise QubesException("VM %s not running" % vm.name)
if not backend_vm.is_running():
raise QubesException("VM %s not running" % backend_vm.name)
if device['mode'] == 'r' and mode == 'w':
raise QubesException("Cannot attach read-only device in read-write "
"mode")
def block_attach(qvmc, vm, device, frontend=None, mode="w", auto_detach=False, wait=True):
backend_vm = qvmc.get_vm_by_name(device['vm'])
device_attach_check(vm, backend_vm, device, frontend, mode)
if frontend is None:
frontend = block_find_unused_frontend(vm)
if frontend is None:
raise QubesException("No unused frontend found")
else:
# Check if any device attached at this frontend
xml = vm.libvirt_domain.XMLDesc()
parsed_xml = etree.fromstring(xml)
disks = parsed_xml.xpath("//domain/devices/disk/target[@dev='%s']" %
frontend)
if len(disks):
raise QubesException("Frontend %s busy in VM %s, detach it first" % (frontend, vm.name))
# Check if this device is attached to some domain
attached_vm = block_check_attached(qvmc, device)
if attached_vm:
if auto_detach:
block_detach(attached_vm['vm'], attached_vm['frontend'])
else:
raise QubesException("Device %s from %s already connected to VM "
"%s as %s" % (device['device'],
backend_vm.name, attached_vm['vm'], attached_vm['frontend']))
disk = Element("disk")
disk.set('type', 'block')
disk.set('device', 'disk')
SubElement(disk, 'driver').set('name', 'phy')
SubElement(disk, 'source').set('dev', device['device'])
SubElement(disk, 'target').set('dev', frontend)
if backend_vm.qid != 0:
SubElement(disk, 'backenddomain').set('name', device['vm'])
if mode == "r":
SubElement(disk, 'readonly')
vm.libvirt_domain.attachDevice(etree.tostring(disk, encoding='utf-8'))
try:
# trigger watches to update device status
# FIXME: this should be removed once libvirt will report such
# events itself
vm.qdb.write('/qubes-block-devices', '')
except Error:
pass
def block_detach(vm, frontend = "xvdi"):
xml = vm.libvirt_domain.XMLDesc()
parsed_xml = etree.fromstring(xml)
attached = parsed_xml.xpath("//domain/devices/disk")
for disk in attached:
if frontend is not None and disk.find('target').get('dev') != frontend:
# Not the device we are looking for
continue
if frontend is None:
# ignore system disks
if disk.find('domain') == None and \
disk.find('source').get('dev').startswith(system_path[
"qubes_base_dir"]):
continue
vm.libvirt_domain.detachDevice(etree.tostring(disk, encoding='utf-8'))
try:
# trigger watches to update device status
# FIXME: this should be removed once libvirt will report such
# events itself
vm.qdb.write('/qubes-block-devices', '')
except Error:
pass
def block_detach_all(vm):
""" Detach all non-system devices"""
block_detach(vm, None)
####### USB devices ######
usb_ver_re = re.compile(r"^(1|2)$")
usb_device_re = re.compile(r"^[0-9]+-[0-9]+(_[0-9]+)?$")
usb_port_re = re.compile(r"^$|^[0-9]+-[0-9]+(\.[0-9]+)?$")
usb_desc_re = re.compile(r"^[ -~]{1,255}$")
# should match valid VM name
usb_connected_to_re = re.compile(r"^[a-zA-Z][a-zA-Z0-9_.-]*$")
def usb_decode_device_from_qdb(qdb_encoded_device):
""" recover actual device name (xenstore doesn't allow dot in key names, so it was translated to underscore) """
return qdb_encoded_device.replace('_', '.')
def usb_encode_device_for_qdb(device):
""" encode actual device name (xenstore doesn't allow dot in key names, so translated it into underscore) """
return device.replace('.', '_')
def usb_list_vm(qvmc, vm):
if not vm.is_running():
return {}
try:
untrusted_devices = vm.qdb.multiread('/qubes-usb-devices/')
except Error:
vm.refresh()
return {}
def get_dev_item(dev, item):
return untrusted_devices.get(
'/qubes-usb-devices/%s/%s' % (dev, item),
None)
devices = {}
untrusted_devices_names = list(set(map(lambda x: x.split("/")[2],
untrusted_devices.keys())))
for untrusted_dev_name in untrusted_devices_names:
if usb_device_re.match(untrusted_dev_name):
dev_name = untrusted_dev_name
untrusted_device_desc = get_dev_item(dev_name, 'desc')
if not usb_desc_re.match(untrusted_device_desc):
print >> sys.stderr, "Invalid %s device desc in VM '%s'" % (
dev_name, vm.name)
continue
device_desc = untrusted_device_desc
untrusted_connected_to = get_dev_item(dev_name, 'connected-to')
if untrusted_connected_to:
if not usb_connected_to_re.match(untrusted_connected_to):
print >>sys.stderr, \
"Invalid %s device 'connected-to' in VM '%s'" % (
dev_name, vm.name)
continue
connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
if connected_to is None:
print >>sys.stderr, \
"Device {} appears to be connected to {}, " \
"but such VM doesn't exist".format(
dev_name, untrusted_connected_to)
else:
connected_to = None
device = usb_decode_device_from_qdb(dev_name)
full_name = vm.name + ':' + device
devices[full_name] = {
'vm': vm,
'device': device,
'qdb_path': '/qubes-usb-devices/' + dev_name,
'name': full_name,
'desc': device_desc,
'connected-to': connected_to,
}
return devices
def usb_list(qvmc, vm=None):
"""
Returns a dictionary of USB devices (for PVUSB backends running in all VM).
The dictionary is keyed by 'name' (see below), each element is a dictionary itself:
vm = backend domain object
device = device ID
name = <backend-vm>:<device>
desc = description
"""
if vm is not None:
if not vm.is_running():
return {}
else:
vm_list = [vm]
else:
vm_list = qvmc.values()
devices_list = {}
for vm in vm_list:
devices_list.update(usb_list_vm(qvmc, vm))
return devices_list
def usb_check_attached(qvmc, device):
"""Reread device attachment status"""
vm = device['vm']
untrusted_connected_to = vm.qdb.read(
'{}/connected-to'.format(device['qdb_path']))
if untrusted_connected_to:
if not usb_connected_to_re.match(untrusted_connected_to):
raise QubesException(
"Invalid %s device 'connected-to' in VM '%s'" % (
device['device'], vm.name))
connected_to = qvmc.get_vm_by_name(untrusted_connected_to)
if connected_to is None:
print >>sys.stderr, \
"Device {} appears to be connected to {}, " \
"but such VM doesn't exist".format(
device['device'], untrusted_connected_to)
else:
connected_to = None
return connected_to
def usb_attach(qvmc, vm, device, auto_detach=False, wait=True):
if not vm.is_running():
raise QubesException("VM {} not running".format(vm.name))
if not device['vm'].is_running():
raise QubesException("VM {} not running".format(device['vm'].name))
connected_to = usb_check_attached(qvmc, device)
if connected_to:
if auto_detach:
usb_detach(qvmc, device)
else:
raise QubesException("Device {} already connected, to {}".format(
device['name'], connected_to
))
# set qrexec policy to allow this device
policy_line = '{} {} allow\n'.format(vm.name, device['vm'].name)
policy_path = '/etc/qubes-rpc/policy/qubes.USB+{}'.format(device['device'])
policy_exists = os.path.exists(policy_path)
if not policy_exists:
try:
fd = os.open(policy_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
with os.fdopen(fd, 'w') as f:
f.write(policy_line)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
else:
with open(policy_path, 'r+') as f:
policy = f.readlines()
policy.insert(0, policy_line)
f.truncate(0)
f.seek(0)
f.write(''.join(policy))
try:
# and actual attach
p = vm.run_service('qubes.USBAttach', passio_popen=True, user='root')
(stdout, stderr) = p.communicate(
'{} {}\n'.format(device['vm'].name, device['device']))
if p.returncode == 127:
raise USBProxyNotInstalled(
"qubes-usb-proxy not installed in the VM")
elif p.returncode != 0:
# TODO: sanitize and include stdout
sanitized_stderr = ''.join([c for c in stderr if ord(c) >= 0x20])
raise QubesException('Device attach failed: {}'.format(
sanitized_stderr))
finally:
# FIXME: there is a race condition here - some other process might
# modify the file in the meantime. This may result in unexpected
# denials, but will not allow too much
if not policy_exists:
os.unlink(policy_path)
else:
with open(policy_path, 'r+') as f:
policy = f.readlines()
policy.remove('{} {} allow\n'.format(vm.name, device['vm'].name))
f.truncate(0)
f.seek(0)
f.write(''.join(policy))
def usb_detach(qvmc, vm, device):
connected_to = usb_check_attached(qvmc, device)
# detect race conditions; there is still race here, but much smaller
if connected_to is None or connected_to.qid != vm.qid:
raise QubesException(
"Device {} not connected to VM {}".format(
device['name'], vm.name))
p = device['vm'].run_service('qubes.USBDetach', passio_popen=True,
user='root')
(stdout, stderr) = p.communicate(
'{}\n'.format(device['device']))
if p.returncode != 0:
# TODO: sanitize and include stdout
raise QubesException('Device detach failed')
def usb_detach_all(qvmc, vm):
for dev in usb_list(qvmc).values():
connected_to = dev['connected-to']
if connected_to is not None and connected_to.qid == vm.qid:
usb_detach(qvmc, connected_to, dev)
####### QubesWatch ######
def only_in_first_list(l1, l2):
ret=[]
for i in l1:
if not i in l2:
ret.append(i)
return ret
class QubesWatch(object):
def __init__(self):
self._qdb = {}
self._qdb_events = {}
self.block_callback = None
self.meminfo_callback = None
self.domain_callback = None
libvirt.virEventRegisterDefaultImpl()
# open new libvirt connection because above
# virEventRegisterDefaultImpl is in practice effective only for new
# connections
self.libvirt_conn = libvirt.open(defaults['libvirt_uri'])
self.libvirt_conn.domainEventRegisterAny(
None,
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
self._domain_list_changed, None)
self.libvirt_conn.domainEventRegisterAny(
None,
libvirt.VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED,
self._device_removed, None)
# TODO: device attach libvirt event
for vm in vmm.libvirt_conn.listAllDomains():
try:
if vm.isActive():
self._register_watches(vm)
except libvirt.libvirtError as e:
# this will happen if we loose a race with another tool,
# which can just remove the domain
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
pass
else:
raise
# and for dom0
self._register_watches(None)
def _qdb_handler(self, watch, fd, events, domain_name):
try:
path = self._qdb[domain_name].read_watch()
except DisconnectedError:
libvirt.virEventRemoveHandle(watch)
del(self._qdb_events[domain_name])
self._qdb[domain_name].close()
del(self._qdb[domain_name])
return
if path.startswith('/qubes-block-devices'):
if self.block_callback is not None:
self.block_callback(domain_name)
def setup_block_watch(self, callback):
self.block_callback = callback
def setup_meminfo_watch(self, callback):
raise NotImplementedError
def setup_domain_watch(self, callback):
self.domain_callback = callback
def get_meminfo_key(self, xid):
return '/local/domain/%s/memory/meminfo' % xid
def _register_watches(self, libvirt_domain):
if libvirt_domain and libvirt_domain.ID() == 0:
# don't use libvirt object for dom0, to always have the same
# hardcoded "dom0" name
libvirt_domain = None
if libvirt_domain:
name = libvirt_domain.name()
if name in self._qdb:
return
if not libvirt_domain.isActive():
return
# open separate connection to Qubes DB:
# 1. to not confuse pull() with responses to real commands sent from
# other threads (like read, write etc) with watch events
# 2. to not think whether QubesDB is thread-safe (it isn't)
try:
self._qdb[name] = QubesDB(name)
except Error as e:
if e.args[0] != 2:
raise
libvirt.virEventAddTimeout(500, self._retry_register_watches,
libvirt_domain)
return
else:
name = "dom0"
if name in self._qdb:
return
self._qdb[name] = QubesDB(name)
try:
self._qdb[name].watch('/qubes-block-devices')
except Error as e:
if e.args[0] == 102: # Connection reset by peer
# QubesDB daemon not running - most likely we've connected to
# stale daemon which just exited; retry later
libvirt.virEventAddTimeout(500, self._retry_register_watches,
libvirt_domain)
return
self._qdb_events[name] = libvirt.virEventAddHandle(
self._qdb[name].watch_fd(),
libvirt.VIR_EVENT_HANDLE_READABLE,
self._qdb_handler, name)
def _retry_register_watches(self, timer, libvirt_domain):
libvirt.virEventRemoveTimeout(timer)
self._register_watches(libvirt_domain)
def _unregister_watches(self, libvirt_domain):
if libvirt_domain and libvirt_domain.ID() == 0:
name = "dom0"
else:
name = libvirt_domain.name()
if name in self._qdb_events:
libvirt.virEventRemoveHandle(self._qdb_events[name])
del(self._qdb_events[name])
if name in self._qdb:
self._qdb[name].close()
del(self._qdb[name])
def _domain_list_changed(self, conn, domain, event, reason, param):
# use VIR_DOMAIN_EVENT_RESUMED instead of VIR_DOMAIN_EVENT_STARTED to
# make sure that qubesdb daemon is already running
if event == libvirt.VIR_DOMAIN_EVENT_RESUMED:
self._register_watches(domain)
elif event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
self._unregister_watches(domain)
else:
# ignore other events for now
return None
if self.domain_callback:
self.domain_callback(name=domain.name(), uuid=domain.UUID())
def _device_removed(self, conn, domain, device, param):
if self.block_callback is not None:
self.block_callback(domain.name())
def watch_loop(self):
while True:
libvirt.virEventRunDefaultImpl()
##### updates check #####
UPDATES_DOM0_DISABLE_FLAG='/var/lib/qubes/updates/disable-updates'
UPDATES_DEFAULT_VM_DISABLE_FLAG=\
'/var/lib/qubes/updates/vm-default-disable-updates'
def updates_vms_toggle(qvm_collection, value):
# Flag for new VMs
if value:
if os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG):
os.unlink(UPDATES_DEFAULT_VM_DISABLE_FLAG)
else:
open(UPDATES_DEFAULT_VM_DISABLE_FLAG, "w").close()
# Change for existing VMs
for vm in qvm_collection.values():
if vm.qid == 0:
continue
if value:
vm.services.pop('qubes-update-check', None)
if vm.is_running():
try:
vm.run("systemctl start qubes-update-check.timer",
user="root")
except:
pass
else:
vm.services['qubes-update-check'] = False
if vm.is_running():
try:
vm.run("systemctl stop qubes-update-check.timer",
user="root")
except:
pass
def updates_dom0_toggle(qvm_collection, value):
if value:
if os.path.exists(UPDATES_DOM0_DISABLE_FLAG):
os.unlink(UPDATES_DOM0_DISABLE_FLAG)
else:
open(UPDATES_DOM0_DISABLE_FLAG, "w").close()
def updates_dom0_status(qvm_collection):
return not os.path.exists(UPDATES_DOM0_DISABLE_FLAG)
def updates_vms_status(qvm_collection):
# default value:
status = not os.path.exists(UPDATES_DEFAULT_VM_DISABLE_FLAG)
# check if all the VMs uses the default value
for vm in qvm_collection.values():
if vm.qid == 0:
continue
if vm.services.get('qubes-update-check', True) != status:
# "mixed"
return None
return status
# vim:sw=4:et:

View File

@ -1,11 +0,0 @@
#!/usr/bin/python2
from __future__ import absolute_import
from qubes.storage.xen import XenStorage, XenPool
def apply(system_path, vm_files, defaults):
defaults['storage_class'] = XenStorage
defaults['pool_drivers'] = {'xen': XenPool}
defaults['pool_config'] = {'dir_path': '/var/lib/qubes/'}

View File

@ -1,24 +0,0 @@
OS ?= Linux
SYSCONFDIR ?= /etc
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
all:
python -m compileall .
python -O -m compileall .
install:
ifndef PYTHON_SITEPATH
$(error PYTHON_SITEPATH not defined)
endif
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage
cp __init__.py $(DESTDIR)$(PYTHON_QUBESPATH)/storage
cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage
mkdir -p $(DESTDIR)$(SYSCONFDIR)/qubes
cp storage.conf $(DESTDIR)$(SYSCONFDIR)/qubes/
ifneq ($(BACKEND_VMM),)
if [ -r $(BACKEND_VMM).py ]; then \
cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \
cp $(BACKEND_VMM).py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage; \
fi
endif

View File

@ -1,446 +0,0 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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.
#
from __future__ import absolute_import
import ConfigParser
import os
import os.path
import shutil
import subprocess
import sys
import qubes.qubesutils
from qubes.qubes import QubesException, defaults, system_path
CONFIG_FILE = '/etc/qubes/storage.conf'
class QubesVmStorage(object):
"""
Class for handling VM virtual disks. This is base class for all other
implementations, mostly with Xen on Linux in mind.
"""
def __init__(self, vm,
private_img_size = None,
root_img_size = None,
modules_img = None,
modules_img_rw = False):
self.vm = vm
self.vmdir = vm.dir_path
if private_img_size:
self.private_img_size = private_img_size
else:
self.private_img_size = defaults['private_img_size']
if root_img_size:
self.root_img_size = root_img_size
else:
self.root_img_size = defaults['root_img_size']
self.root_dev = "xvda"
self.private_dev = "xvdb"
self.volatile_dev = "xvdc"
self.modules_dev = "xvdd"
# For now compute this path still in QubesVm
self.modules_img = modules_img
self.modules_img_rw = modules_img_rw
# Additional drive (currently used only by HVM)
self.drive = None
def format_disk_dev(self, path, script, vdev, rw=True, type="disk",
domain=None):
if path is None:
return ''
template = " <disk type='block' device='{type}'>\n" \
" <driver name='phy'/>\n" \
" <source dev='{path}'/>\n" \
" <target dev='{vdev}' bus='xen'/>\n" \
"{params}" \
" </disk>\n"
params = ""
if not rw:
params += " <readonly/>\n"
if domain:
params += " <backenddomain name='%s'/>\n" % domain
if script:
params += " <script path='%s'/>\n" % script
return template.format(path=path, vdev=vdev, type=type, params=params)
def get_config_params(self):
args = {}
args['rootdev'] = self.root_dev_config()
args['privatedev'] = self.private_dev_config()
args['volatiledev'] = self.volatile_dev_config()
args['otherdevs'] = self.other_dev_config()
return args
def root_dev_config(self):
raise NotImplementedError
def private_dev_config(self):
raise NotImplementedError
def volatile_dev_config(self):
raise NotImplementedError
def other_dev_config(self):
if self.modules_img is not None:
return self.format_disk_dev(self.modules_img,
None,
self.modules_dev,
self.modules_img_rw)
elif self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_type == "hd":
drive_type = "disk"
writable = False
if drive_type == "disk":
writable = True
if drive_domain.lower() == "dom0":
drive_domain = None
return self.format_disk_dev(drive_path, None,
self.modules_dev,
rw=writable,
type=drive_type,
domain=drive_domain)
else:
return ''
def _copy_file(self, source, destination):
"""
Effective file copy, preserving sparse files etc.
"""
# TODO: Windows support
# We prefer to use Linux's cp, because it nicely handles sparse files
retcode = subprocess.call (["cp", "--reflink=auto", source, destination])
if retcode != 0:
raise IOError ("Error while copying {0} to {1}".\
format(source, destination))
def get_disk_utilization(self):
return qubes.qubesutils.get_disk_usage(self.vmdir)
def get_disk_utilization_private_img(self):
return qubes.qubesutils.get_disk_usage(self.private_img)
def get_private_img_sz(self):
if not os.path.exists(self.private_img):
return 0
return os.path.getsize(self.private_img)
def resize_private_img(self, size):
raise NotImplementedError
def create_on_disk_private_img(self, verbose, source_template = None):
raise NotImplementedError
def create_on_disk_root_img(self, verbose, source_template = None):
raise NotImplementedError
def create_on_disk(self, verbose, source_template = None):
if source_template is None:
source_template = self.vm.template
old_umask = os.umask(002)
if verbose:
print >> sys.stderr, "--> Creating directory: {0}".format(self.vmdir)
os.mkdir (self.vmdir)
self.create_on_disk_private_img(verbose, source_template)
self.create_on_disk_root_img(verbose, source_template)
self.reset_volatile_storage(verbose, source_template)
os.umask(old_umask)
def clone_disk_files(self, src_vm, verbose):
if verbose:
print >> sys.stderr, "--> Creating directory: {0}".format(self.vmdir)
os.mkdir (self.vmdir)
if src_vm.private_img is not None and self.private_img is not None:
if verbose:
print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
format(src_vm.private_img, self.private_img)
self._copy_file(src_vm.private_img, self.private_img)
if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
if verbose:
print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
format(src_vm.root_img, self.root_img)
self._copy_file(src_vm.root_img, self.root_img)
# TODO: modules?
def rename(self, old_name, new_name):
old_vmdir = self.vmdir
new_vmdir = os.path.join(os.path.dirname(self.vmdir), new_name)
os.rename(self.vmdir, new_vmdir)
self.vmdir = new_vmdir
if self.private_img:
self.private_img = self.private_img.replace(old_vmdir, new_vmdir)
if self.root_img:
self.root_img = self.root_img.replace(old_vmdir, new_vmdir)
if self.volatile_img:
self.volatile_img = self.volatile_img.replace(old_vmdir, new_vmdir)
def verify_files(self):
if not os.path.exists (self.vmdir):
raise QubesException (
"VM directory doesn't exist: {0}".\
format(self.vmdir))
if self.root_img and not os.path.exists (self.root_img):
raise QubesException (
"VM root image file doesn't exist: {0}".\
format(self.root_img))
if self.private_img and not os.path.exists (self.private_img):
raise QubesException (
"VM private image file doesn't exist: {0}".\
format(self.private_img))
if self.modules_img is not None:
if not os.path.exists(self.modules_img):
raise QubesException (
"VM kernel modules image does not exists: {0}".\
format(self.modules_img))
def remove_from_disk(self):
shutil.rmtree (self.vmdir)
def reset_volatile_storage(self, verbose = False, source_template = None):
if source_template is None:
source_template = self.vm.template
# Re-create only for template based VMs
if source_template is not None and self.volatile_img:
if os.path.exists(self.volatile_img):
os.remove(self.volatile_img)
# For StandaloneVM create it only if not already exists (eg after backup-restore)
if self.volatile_img and not os.path.exists(self.volatile_img):
if verbose:
print >> sys.stderr, "--> Creating volatile image: {0}...".\
format(self.volatile_img)
subprocess.check_call([system_path["prepare_volatile_img_cmd"],
self.volatile_img, str(self.root_img_size / 1024 / 1024)])
def prepare_for_vm_startup(self, verbose):
self.reset_volatile_storage(verbose=verbose)
if self.private_img and not os.path.exists (self.private_img):
print >>sys.stderr, "WARNING: Creating empty VM private image file: {0}".\
format(self.private_img)
self.create_on_disk_private_img(verbose=False)
def dump(o):
""" Returns a string represention of the given object
Args:
o (object): anything that response to `__module__` and `__class__`
Given the class :class:`qubes.storage.QubesVmStorage` it returns
'qubes.storage.QubesVmStorage' as string
"""
return o.__module__ + '.' + o.__class__.__name__
def load(string):
""" Given a dotted full module string representation of a class it loads it
Args:
string (str) i.e. 'qubes.storage.xen.QubesXenVmStorage'
Returns:
type
See also:
:func:`qubes.storage.dump`
"""
if not type(string) is str:
# This is a hack which allows giving a real class to a vm instead of a
# string as string_class parameter.
return string
components = string.split(".")
module_path = ".".join(components[:-1])
klass = components[-1:][0]
module = __import__(module_path, fromlist=[klass])
return getattr(module, klass)
def get_pool(name, vm):
""" Instantiates the storage for the specified vm """
config = _get_storage_config_parser()
klass = _get_pool_klass(name, config)
keys = [k for k in config.options(name) if k != 'driver' and k != 'class']
values = [config.get(name, o) for o in keys]
config_kwargs = dict(zip(keys, values))
if name == 'default':
kwargs = defaults['pool_config'].copy()
kwargs.update(keys)
else:
kwargs = config_kwargs
return klass(vm, **kwargs)
def pool_exists(name):
""" Check if the specified pool exists """
try:
_get_pool_klass(name)
return True
except StoragePoolException:
return False
def add_pool(name, **kwargs):
""" Add a storage pool to config."""
config = _get_storage_config_parser()
config.add_section(name)
for key, value in kwargs.iteritems():
config.set(name, key, value)
_write_config(config)
def remove_pool(name):
""" Remove a storage pool from config file. """
config = _get_storage_config_parser()
config.remove_section(name)
_write_config(config)
def _write_config(config):
with open(CONFIG_FILE, 'w') as configfile:
config.write(configfile)
def _get_storage_config_parser():
""" Instantiates a `ConfigParaser` for specified storage config file.
Returns:
RawConfigParser
"""
config = ConfigParser.RawConfigParser()
config.read(CONFIG_FILE)
return config
def _get_pool_klass(name, config=None):
""" Returns the storage klass for the specified pool.
Args:
name: The pool name.
config: If ``config`` is not specified
`_get_storage_config_parser()` is called.
Returns:
type: A class inheriting from `QubesVmStorage`
"""
if config is None:
config = _get_storage_config_parser()
if not config.has_section(name):
raise StoragePoolException('Uknown storage pool ' + name)
if config.has_option(name, 'class'):
klass = load(config.get(name, 'class'))
elif config.has_option(name, 'driver'):
pool_driver = config.get(name, 'driver')
klass = defaults['pool_drivers'][pool_driver]
else:
raise StoragePoolException('Uknown storage pool driver ' + name)
return klass
class StoragePoolException(QubesException):
pass
class Pool(object):
def __init__(self, vm, dir_path):
assert vm is not None
assert dir_path is not None
self.vm = vm
self.dir_path = dir_path
self.create_dir_if_not_exists(self.dir_path)
self.vmdir = self.vmdir_path(vm, self.dir_path)
appvms_path = os.path.join(self.dir_path, 'appvms')
self.create_dir_if_not_exists(appvms_path)
servicevms_path = os.path.join(self.dir_path, 'servicevms')
self.create_dir_if_not_exists(servicevms_path)
vm_templates_path = os.path.join(self.dir_path, 'vm-templates')
self.create_dir_if_not_exists(vm_templates_path)
def vmdir_path(self, vm, pool_dir):
""" Returns the path to vmdir depending on the type of the VM.
The default QubesOS file storage saves the vm images in three
different directories depending on the ``QubesVM`` type:
* ``appvms`` for ``QubesAppVm`` or ``QubesHvm``
* ``vm-templates`` for ``QubesTemplateVm`` or ``QubesTemplateHvm``
* ``servicevms`` for any subclass of ``QubesNetVm``
Args:
vm: a QubesVM
pool_dir: the root directory of the pool
Returns:
string (str) absolute path to the directory where the vm files
are stored
"""
if vm.is_appvm():
subdir = 'appvms'
elif vm.is_template():
subdir = 'vm-templates'
elif vm.is_netvm():
subdir = 'servicevms'
elif vm.is_disposablevm():
subdir = 'appvms'
return os.path.join(pool_dir, subdir, vm.template.name + '-dvm')
else:
raise QubesException(vm.type() + ' unknown vm type')
return os.path.join(pool_dir, subdir, vm.name)
def create_dir_if_not_exists(self, path):
""" Check if a directory exists in if not create it.
This method does not create any parent directories.
"""
if not os.path.exists(path):
os.mkdir(path)

View File

@ -1,12 +0,0 @@
[default] ; poolname
driver=xen ; the default xen storage
; class = qubes.storage.xen.XenStorage ; class always overwrites the driver
;
; To use our own storage adapter, you need just to specify the module path and
; class name
; [pool-b]
; class = foo.bar.MyStorage
;
; [test-dummy]
; driver=dummy

View File

@ -1,240 +0,0 @@
#!/usr/bin/python2
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.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.
#
from __future__ import absolute_import
import os
import os.path
import re
import subprocess
import sys
from qubes.qubes import QubesException, vm_files
from qubes.storage import Pool, QubesVmStorage
class XenStorage(QubesVmStorage):
"""
Class for VM storage of Xen VMs.
"""
def __init__(self, vm, vmdir, **kwargs):
""" Instantiate the storage.
Args:
vm: a QubesVM
vmdir: the root directory of the pool
"""
assert vm is not None
assert vmdir is not None
super(XenStorage, self).__init__(vm, **kwargs)
self.vmdir = vmdir
if self.vm.is_template():
self.rootcow_img = os.path.join(self.vmdir,
vm_files["rootcow_img"])
else:
self.rootcow_img = None
self.private_img = os.path.join(vmdir, 'private.img')
if self.vm.template:
self.root_img = self.vm.template.root_img
else:
self.root_img = os.path.join(vmdir, 'root.img')
self.volatile_img = os.path.join(vmdir, 'volatile.img')
def root_dev_config(self):
if self.vm.is_template() and self.rootcow_img:
return self.format_disk_dev(
"{root}:{rootcow}".format(
root=self.root_img, rootcow=self.rootcow_img),
"block-origin", self.root_dev, True)
elif self.vm.template and not hasattr(self.vm, 'kernel'):
# HVM template-based VM - only one device-mapper layer, in dom0 (
# root+volatile)
# HVM detection based on 'kernel' property is massive hack,
# but taken from assumption that VM needs Qubes-specific kernel (
# actually initramfs) to assemble the second layer of device-mapper
return self.format_disk_dev(
"{root}:{volatile}".format(
root=self.vm.template.storage.root_img,
volatile=self.volatile_img),
"block-snapshot", self.root_dev, True)
elif self.vm.template:
# any other template-based VM - two device-mapper layers: one
# in dom0 (here) from root+root-cow, and another one from
# this+volatile.img
return self.format_disk_dev(
"{root}:{rootcow}".format(
root=self.root_img,
rootcow=self.vm.template.storage.rootcow_img),
"block-snapshot", self.root_dev, False)
else:
return self.format_disk_dev(
"{root}".format(root=self.root_img),
None, self.root_dev, True)
def private_dev_config(self):
return self.format_disk_dev(self.private_img, None,
self.private_dev, True)
def volatile_dev_config(self):
return self.format_disk_dev(self.volatile_img, None,
self.volatile_dev, True)
def create_on_disk_private_img(self, verbose, source_template = None):
if source_template:
template_priv = source_template.private_img
if verbose:
print >> sys.stderr, "--> Copying the template's private image: {0}".\
format(template_priv)
self._copy_file(template_priv, self.private_img)
else:
f_private = open (self.private_img, "a+b")
f_private.truncate (self.private_img_size)
f_private.close ()
def create_on_disk_root_img(self, verbose, source_template = None):
if source_template:
if not self.vm.updateable:
# just use template's disk
return
else:
template_root = source_template.root_img
if verbose:
print >> sys.stderr, "--> Copying the template's root image: {0}".\
format(template_root)
self._copy_file(template_root, self.root_img)
else:
f_root = open (self.root_img, "a+b")
f_root.truncate (self.root_img_size)
f_root.close ()
if self.vm.is_template():
self.commit_template_changes()
def rename(self, old_name, new_name):
super(XenStorage, self).rename(old_name, new_name)
old_dirpath = os.path.join(os.path.dirname(self.vmdir), old_name)
if self.rootcow_img:
self.rootcow_img = self.rootcow_img.replace(old_dirpath,
self.vmdir)
def resize_private_img(self, size):
f_private = open (self.private_img, "a+b")
f_private.truncate (size)
f_private.close ()
# find loop device if any
p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
stdout=subprocess.PIPE)
result = p.communicate()
m = re.match(r"^(/dev/loop\d+):\s", result[0])
if m is not None:
loop_dev = m.group(1)
# resize loop device
subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
def commit_template_changes(self):
assert self.vm.is_template()
if not self.rootcow_img:
return
if os.path.exists (self.rootcow_img):
os.rename (self.rootcow_img, self.rootcow_img + '.old')
old_umask = os.umask(002)
f_cow = open (self.rootcow_img, "w")
f_root = open (self.root_img, "r")
f_root.seek(0, os.SEEK_END)
f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
f_cow.close ()
f_root.close()
os.umask(old_umask)
def reset_volatile_storage(self, verbose = False, source_template = None):
if source_template is None:
source_template = self.vm.template
if source_template is not None:
# template-based VM with only one device-mapper layer -
# volatile.img used as upper layer on root.img, no root-cow.img
# intermediate layer
if not source_template.storage.rootcow_img:
if os.path.exists(self.volatile_img):
if self.vm.debug:
if os.path.getmtime(source_template.storage.root_img)\
> os.path.getmtime(self.volatile_img):
if verbose:
print >>sys.stderr, "--> WARNING: template have changed, resetting root.img"
else:
if verbose:
print >>sys.stderr, "--> Debug mode: not resetting root.img"
print >>sys.stderr, "--> Debug mode: if you want to force root.img reset, either update template VM, or remove volatile.img file"
return
os.remove(self.volatile_img)
f_volatile = open(self.volatile_img, "w")
f_root = open(source_template.storage.root_img, "r")
f_root.seek(0, os.SEEK_END)
f_volatile.truncate(f_root.tell()) # make empty sparse file of the same size as root.img
f_volatile.close()
f_root.close()
return
super(XenStorage, self).reset_volatile_storage(
verbose=verbose, source_template=source_template)
def prepare_for_vm_startup(self, verbose):
super(XenStorage, self).prepare_for_vm_startup(verbose=verbose)
if self.drive is not None:
(drive_type, drive_domain, drive_path) = self.drive.split(":")
if drive_domain.lower() != "dom0":
try:
# FIXME: find a better way to access QubesVmCollection
drive_vm = self.vm._collection.get_vm_by_name(drive_domain)
# prepare for improved QubesVmCollection
if drive_vm is None:
raise KeyError
if not drive_vm.is_running():
raise QubesException(
"VM '{}' holding '{}' isn't running".format(
drive_domain, drive_path))
except KeyError:
raise QubesException(
"VM '{}' holding '{}' does not exists".format(
drive_domain, drive_path))
if self.rootcow_img and not os.path.exists(self.rootcow_img):
self.commit_template_changes()
class XenPool(Pool):
def __init__(self, vm, dir_path):
super(XenPool, self).__init__(vm, dir_path)
def getStorage(self):
""" Returns an instantiated ``XenStorage``. """
return XenStorage(self.vm, vmdir=self.vmdir)

View File

@ -1,48 +0,0 @@
#!/usr/bin/python -O
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2014 Wojciech Porczyk <wojciech@porczyk.eu>
#
# 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 subprocess
import unittest
import qubes.qubesutils
class TestCaseFunctionsAndConstants(unittest.TestCase):
def check_output_int(self, cmd):
return int(subprocess.check_output(cmd).strip().split(None, 1)[0])
def test_00_BLKSIZE(self):
# this may fail on systems without st_blocks
self.assertEqual(qubes.qubesutils.BLKSIZE, self.check_output_int(['stat', '-c%B', '.']))
def test_01_get_size_one(self):
# this may fail on systems without st_blocks
self.assertEqual(qubes.qubesutils.get_disk_usage_one(os.stat('.')),
self.check_output_int(['stat', '-c%b', '.']) * qubes.qubesutils.BLKSIZE)
def test_02_get_size(self):
self.assertEqual(qubes.qubesutils.get_disk_usage('.'),
self.check_output_int(['du', '-s', '--block-size=1', '.']))
if __name__ == '__main__':
unittest.main()

2
dispvm/.gitignore vendored
View File

@ -1,2 +0,0 @@
qubes_restore
xenstore-watch

View File

@ -1,21 +0,0 @@
UNITDIR ?= /usr/lib/systemd/system
all:
true
clean:
true
install:
mkdir -p $(DESTDIR)/etc/xen/scripts
cp block.qubes $(DESTDIR)/etc/xen/scripts
mkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/usr/lib/qubes
cp qubes-prepare-saved-domain.sh $(DESTDIR)/usr/lib/qubes
cp qubes-update-dispvm-savefile-with-progress.sh $(DESTDIR)/usr/lib/qubes
cp qfile-daemon-dvm $(DESTDIR)/usr/lib/qubes
mkdir -p $(DESTDIR)$(UNITDIR)
cp startup-dvm.sh $(DESTDIR)/usr/lib/qubes
cp qubes-setupdvm.service $(DESTDIR)$(UNITDIR)

View File

@ -1,61 +0,0 @@
#!/bin/bash
HOTPLUG_STORE="/var/run/xen-hotplug/${XENBUS_PATH//\//-}"
hd_arr[10]=a
hd_arr[11]=b
hd_arr[12]=c
hd_arr[13]=d
hd_arr[14]=e
hd_arr[15]=f
hexdigit()
{
if [ $1 -lt 10 ] ; then
RET=$1
else
RET=${hd_arr[$1]}
fi
}
hexnumber()
{
hexdigit $(($1/16))
ret2=$RET
hexdigit $(($1%16))
HEXNUMBER="$ret2"$RET
}
process()
{
if ! [ "x""$1" = "xfile" ] ; then
exec flock /var/run/qubes/hotplug-block /etc/xen/scripts/block $ORIG_ARGS
fi
while true ; do
dev=$(losetup -f --show $2)
if [ -n "$dev" ] ; then break ; fi
done
hexnumber ${dev:9:70}
xenstore-write "$XENBUS_PATH/node" "$dev" \
"$XENBUS_PATH/physical-device" "7:"$HEXNUMBER \
"$XENBUS_PATH/hotplug-status" connected
echo "$dev" > "$HOTPLUG_STORE-node"
echo "file" > "$HOTPLUG_STORE-type"
}
#exec 2>>/tmp/block.$$
#set -x
export PATH="/sbin:/bin:/usr/bin:/usr/sbin:$PATH"
XENBUS_PATH="${XENBUS_PATH:?}"
if ! [ "$1" = "add" ] || ! [ -f /var/run/qubes/fast-block-attach ] ; then
script=$(xenstore-read "$XENBUS_PATH/script")
exec flock /var/run/qubes/hotplug-block $script "$@"
fi
ORIG_ARGS="$@"
vars=$(xenstore-read "$XENBUS_PATH/type" "$XENBUS_PATH/params")
process $vars
exit 0

View File

@ -1,200 +0,0 @@
#!/usr/bin/python2
# coding=utf-8
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
# Copyright (C) 2013-2015 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.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 subprocess
import sys
import shutil
import time
from qubes.qubes import QubesVmCollection, QubesException
from qubes.qubes import QubesDispVmLabels
from qubes.notify import tray_notify, tray_notify_error, tray_notify_init
current_savefile = '/var/run/qubes/current-savefile'
current_savefile_vmdir = '/var/lib/qubes/dvmdata/vmdir'
class QfileDaemonDvm:
def __init__(self, name):
self.name = name
@staticmethod
def get_disp_templ():
vmdir = os.readlink(current_savefile_vmdir)
return vmdir.split('/')[-1]
def do_get_dvm(self):
tray_notify("Starting new DispVM...", "red")
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing()
try:
tar_process = subprocess.Popen(
['bsdtar', '-C', current_savefile_vmdir,
'-xSUf', os.path.join(current_savefile_vmdir, 'saved-cows.tar')])
qvm_collection.load()
print >>sys.stderr, "time=%s, collection loaded" % (str(time.time()))
vm = qvm_collection.get_vm_by_name(self.name)
if vm is None:
sys.stderr.write('Domain ' + self.name + ' does not exist ?')
return None
label = vm.label
if len(sys.argv) > 4 and len(sys.argv[4]) > 0:
assert sys.argv[4] in QubesDispVmLabels.keys(), "Invalid label"
label = QubesDispVmLabels[sys.argv[4]]
disp_templ = self.get_disp_templ()
vm_disptempl = qvm_collection.get_vm_by_name(disp_templ)
if vm_disptempl is None:
sys.stderr.write('Domain ' + disp_templ + ' does not exist ?')
return None
dispvm = qvm_collection.add_new_vm('QubesDisposableVm',
disp_template=vm_disptempl,
label=label)
print >>sys.stderr, "time=%s, VM created" % (str(time.time()))
# By default inherit firewall rules from calling VM
disp_firewall_conf = '/var/run/qubes/%s-firewall.xml' % dispvm.name
dispvm.firewall_conf = disp_firewall_conf
if os.path.exists(vm.firewall_conf):
shutil.copy(vm.firewall_conf, disp_firewall_conf)
elif vm.qid == 0 and os.path.exists(vm_disptempl.firewall_conf):
# for DispVM called from dom0, copy use rules from DispVM template
shutil.copy(vm_disptempl.firewall_conf, disp_firewall_conf)
if len(sys.argv) > 5 and len(sys.argv[5]) > 0:
assert os.path.exists(sys.argv[5]), "Invalid firewall.conf location"
dispvm.firewall_conf = sys.argv[5]
if vm.qid != 0:
dispvm.uses_default_netvm = False
# netvm can be changed before restore,
# but cannot be enabled/disabled
if (dispvm.netvm is None) == (vm.dispvm_netvm is None):
dispvm.netvm = vm.dispvm_netvm
# Wait for tar to finish
if tar_process.wait() != 0:
sys.stderr.write('Failed to unpack saved-cows.tar')
return None
print >>sys.stderr, "time=%s, VM starting" % (str(time.time()))
try:
dispvm.start()
except (MemoryError, QubesException) as e:
tray_notify_error(str(e))
raise
if vm.qid != 0:
# if need to enable/disable netvm, do it while DispVM is alive
if (dispvm.netvm is None) != (vm.dispvm_netvm is None):
dispvm.netvm = vm.dispvm_netvm
print >>sys.stderr, "time=%s, VM started" % (str(time.time()))
qvm_collection.save()
finally:
qvm_collection.unlock_db()
# Reload firewall rules
print >>sys.stderr, "time=%s, reloading firewall" % (str(time.time()))
for vm in qvm_collection.values():
if vm.is_proxyvm() and vm.is_running():
vm.write_iptables_qubesdb_entry()
return dispvm
@staticmethod
def dvm_setup_ok():
dvmdata_dir = '/var/lib/qubes/dvmdata/'
if not os.path.isfile(current_savefile):
return False
if not os.path.isfile(dvmdata_dir+'default-savefile') or \
not os.path.isfile(dvmdata_dir+'savefile-root'):
return False
dvm_mtime = os.stat(current_savefile).st_mtime
root_mtime = os.stat(dvmdata_dir+'savefile-root').st_mtime
if dvm_mtime < root_mtime:
template_name = os.path.basename(
os.path.dirname(os.readlink(dvmdata_dir+'savefile-root')))
if subprocess.call(["xl", "domid", template_name],
stdout=open(os.devnull, "w")) == 0:
tray_notify("For optimum performance, you should not "
"start DispVM when its template is running.", "red")
return False
return True
def get_dvm(self):
if not self.dvm_setup_ok():
if os.system("/usr/lib/qubes/"
"qubes-update-dispvm-savefile-with-progress.sh"
" >/dev/null </dev/null") != 0:
tray_notify_error("DVM savefile creation failed")
return None
return self.do_get_dvm()
@staticmethod
def finish_disposable(name):
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing()
qvm_collection.load()
vm = qvm_collection.get_vm_by_name(name)
if vm is None:
qvm_collection.unlock_db()
return False
try:
vm.force_shutdown()
except QubesException:
# VM already destroyed
pass
qvm_collection.pop(vm.qid)
qvm_collection.save()
qvm_collection.unlock_db()
def main():
exec_index = sys.argv[1]
if exec_index == "FINISH":
QfileDaemonDvm.finish_disposable(sys.argv[2])
return
src_vmname = sys.argv[2]
user = sys.argv[3]
# accessed directly by get_dvm()
# sys.argv[4] - override label
# sys.argv[5] - override firewall
print >>sys.stderr, "time=%s, qfile-daemon-dvm init" % (str(time.time()))
tray_notify_init()
print >>sys.stderr, "time=%s, creating DispVM" % (str(time.time()))
qfile = QfileDaemonDvm(src_vmname)
dispvm = qfile.get_dvm()
if dispvm is not None:
if exec_index == "LAUNCH":
print dispvm.name
return
print >>sys.stderr, "time=%s, starting VM process" % (str(time.time()))
subprocess.call(['/usr/lib/qubes/qrexec-client', '-d', dispvm.name,
user+':exec /usr/lib/qubes/qubes-rpc-multiplexer ' +
exec_index + " " + src_vmname])
QfileDaemonDvm.finish_disposable(dispvm.name)
main()

View File

@ -1,86 +0,0 @@
#!/bin/bash
set -o pipefail
get_encoded_script()
{
ENCODED_SCRIPT=`
if [ "$1" == "vm-default" ]; then
echo /usr/lib/qubes/dispvm-prerun.sh
else
cat "$1"
fi | base64 -w0` || exit 1
}
if [ $# != 2 -a $# != 3 ] ; then
echo "usage: $0 domainname savefile_to_be_created [preload script]" >&2
exit 1
fi
export PATH=$PATH:/sbin:/usr/sbin
if [ $# = 3 ] ; then
get_encoded_script $3
fi
VMDIR=/var/lib/qubes/appvms/$1
if ! [ -d $VMDIR ] ; then
echo "$VMDIR does not exist ?" >&2
exit 1
fi
if ! qvm-start $1 --dvm ; then
exit 1
fi
ID=`virsh -c xen:/// domid $1`
echo "Waiting for DVM $1 ..." >&2
if [ -n "$ENCODED_SCRIPT" ] ; then
qubesdb-write -d $1 /qubes-save-script "$ENCODED_SCRIPT"
fi
#set -x
qubesdb-write -d $1 /qubes-save-request 1
qubesdb-watch -d $1 /qubes-used-mem
qubesdb-read -d $1 /qubes-gateway | \
cut -d . -f 3 | tr -d "\n" > $VMDIR/netvm-id.txt
kill `cat /var/run/qubes/guid-running.$ID`
# FIXME: get connection URI from core scripts
virsh -c xen:/// detach-disk $1 xvdb
MEM=$(qubesdb-read -d $1 /qubes-used-mem | grep '^[0-9]\+$' | head -n 1)
echo "DVM boot complete, memory used=$MEM. Saving image..." >&2
QMEMMAN_STOP=/var/run/qubes/do-not-membalance
touch $QMEMMAN_STOP
virsh -c xen:/// setmem $1 $MEM
# Add some safety margin
virsh -c xen:/// setmaxmem $1 $[ $MEM + 1024 ]
# Stop qubesdb daemon now, so VM can restart it later
kill `cat /var/run/qubes/qubesdb.$1.pid`
sleep 1
touch $2
if ! virsh -c xen:/// save $1 $2; then
rm -f $QMEMMAN_STOP
qvm-kill $1
exit 1
fi
rm -f $QMEMMAN_STOP
# Do not allow smaller allocation than 400MB. If that small number comes from
# an error, it would prevent further savefile regeneration (because VM would
# not start with too little memory). Also 'maxmem' depends on 'memory', so
# 400MB is sane compromise.
if [ "$MEM" -lt 409600 ]; then
qvm-prefs -s $1 memory 400
else
qvm-prefs -s $1 memory $[ $MEM / 1024 ]
fi
ln -snf $VMDIR /var/lib/qubes/dvmdata/vmdir
cd $VMDIR
fstype=`df --output=fstype $VMDIR | tail -n 1`
if [ "$fstype" = "tmpfs" ]; then
# bsdtar doesn't work on tmpfs because FS_IOC_FIEMAP ioctl isn't supported
# there
tar -cSf saved-cows.tar volatile.img || exit 1
else
errors=`bsdtar -cSf saved-cows.tar volatile.img 2>&1`
if [ -n "$errors" ]; then
echo "Failed to create saved-cows.tar: $errors" >&2
rm -f saved-cows.tar
exit 1
fi
fi
echo "DVM savefile created successfully."

View File

@ -1,12 +0,0 @@
[Unit]
Description=Qubes DispVM startup setup
After=qubes-core.service
[Service]
Type=oneshot
ExecStart=/usr/lib/qubes/startup-dvm.sh
[Install]
WantedBy=multi-user.target
# Cover legacy init.d script
Alias=qubes_setupdvm.service

View File

@ -1,23 +0,0 @@
#!/bin/sh
line1="<b>Please wait (up to 120s) while the DispVM savefile is being updated.</b>"
line2="<i><small>This only happens when you have updated the template.</small></i>"
line3="<i><small>Next time will be much faster.</small></i>"
if [ -n "$KDE_FULL_SESSION" ]; then
br="<br/>"
else
br="
"
fi
notify-send --icon=/usr/share/qubes/icons/qubes.png --expire-time=120000 \
"Updating default DispVM savefile" "$line1$br$line2$br$line3"
ret=0
rm -f /var/run/qubes/qvm-create-default-dvm.stdout
if ! qvm-create-default-dvm --used-template --default-script >/var/run/qubes/qvm-create-default-dvm.stdout </dev/null ; then
ret=1
fi
exit $ret

View File

@ -1,22 +0,0 @@
#!/bin/sh
# Setup DispVM things at Qubes system startup
printf "\x00\x00\x00\x00" > /var/run/qubes/dispVM.seq
chown root:qubes /var/run/qubes/dispVM.seq
chmod 660 /var/run/qubes/dispVM.seq
DEFAULT=/var/lib/qubes/dvmdata/default-savefile
# setup DispVM files only when they exists
if [ -r $DEFAULT ]; then
if [ -f /var/lib/qubes/dvmdata/dont-use-shm ] ; then
ln -s $DEFAULT /var/run/qubes/current-savefile
else
mkdir -m 770 /dev/shm/qubes
chown root.qubes /dev/shm/qubes
cp -a $(readlink $DEFAULT) /dev/shm/qubes/current-savefile
chown root.qubes /dev/shm/qubes/current-savefile
chmod 660 /dev/shm/qubes/current-savefile
ln -s /dev/shm/qubes/current-savefile /var/run/qubes/current-savefile
fi
fi

4
doc/.gitignore vendored
View File

@ -1 +1,3 @@
*.gz
_build
sandbox.rst
autoxml.rst

View File

@ -1,30 +1,169 @@
QVM_DIR=qvm-tools
QUBES_DIR=qubes-tools
PANDOC=pandoc -s -f rst -t man
# Makefile for Sphinx documentation
#
QVM_DOCS=$(patsubst %.rst,%.1.gz,$(wildcard $(QVM_DIR)/*.rst))
QUBES_DOCS=$(patsubst %.rst,%.1.gz,$(wildcard $(QUBES_DIR)/*.rst))
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD ?= sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
DEPEND = autoxml.rst
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "make rst=example.rst preview -- generate manpage preview from example.rst"
@echo "make manpages -- generate manpages"
@echo "make install -- generate manpages and copy them to /usr/share/man"
install: manpages
mkdir -p $(DESTDIR)/usr/share/man/man1
cp $(QVM_DOCS) $(DESTDIR)/usr/share/man/man1/
cp $(QUBES_DOCS) $(DESTDIR)/usr/share/man/man1/
%.1: %.rst
$(PANDOC) $< > $@
%.1.gz: %.1
gzip -f $<
manpages: $(QVM_DOCS) $(QUBES_DOCS) $(VM_DOCS)
preview: $(rst)
$(PANDOC) $(rst) | groff -mandoc -Tlatin1 | less -R
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo
@echo " install to generate manpages and copy them to \$$(DESTDIR)/usr/share/man"
clean:
rm -f $(QVM_DOCS) $(QUBES_DOCS) $(VM_DOCS)
-rm -rf $(BUILDDIR)/* $(DEPEND)
html: $(DEPEND)
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml: $(DEPEND)
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml: $(DEPEND)
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle: $(DEPEND)
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json: $(DEPEND)
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp: $(DEPEND)
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp: $(DEPEND)
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/core-admin.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/core-admin.qhc"
devhelp: $(DEPEND)
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/core-admin"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/core-admin"
@echo "# devhelp"
epub: $(DEPEND)
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex: $(DEPEND)
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf: $(DEPEND)
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text: $(DEPEND)
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man: $(DEPEND)
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
for file in $(BUILDDIR)/man/*.[12345678]; do \
gzip -f $$file; \
done
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo: $(DEPEND)
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info: $(DEPEND)
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext: $(DEPEND)
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes: $(DEPEND)
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck: $(DEPEND)
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest: $(DEPEND)
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
autoxml.rst: ../relaxng/qubes.rng example.xml
$(PYTHON) ../qubes/rngdoc.py $+ > $@
.PHONY: install
install: man
mkdir -p $(DESTDIR)/usr/share/man/man1
cp $(BUILDDIR)/man/* $(DESTDIR)/usr/share/man/man1/

View File

@ -1,69 +0,0 @@
Dedicated usbvm (optional)
~~~~~~~~~~~~~~~~~~~~~~~~~~
In dom0, once:
qvm-create -l red usbvm
# FIXME: use your own PCI device IDs
qvm-pci -a usbvm 00:1d.0
qvm-pci -a usbvm 00:1d.1
qvm-pci -a usbvm 00:1d.2
qvm-pci -a usbvm 00:1d.7
After each dom0 reboot:
qvm-start usbvm
List
~~~~
In dom0:
qvm-usb -l
Example output:
dom0:7-4 0718:061a TDKMedia_Trans-It_Drive_070326AE8AF92D95 (attached to qdvp:0-1)
dom0:7-5 0b05:1706 ASUS_802.11g_WLAN_Drive (attached to sys-net:0-1)
dom0:1-1 045e:0084 Microsoft_Basic_Optical_Mouse
usbvm:4-6 05e3:0723 Generic_USB_Storage (attached to qdvp:1-1)
Attach
~~~~~~
In dom0:
qvm-usb -a [--no-auto-detach] <vm-name> <device-vm-name>:<backend-controller>-<backend-port>
Example:
qvm-usb -a sys-net usbvm:4-1
Detach
~~~~~~
In dom0:
qvm-usb -d <vm-name>:<vusb-controller>-<vusb-port>
Example:
qvm-usb -d sys-net:0-1
Known issues
~~~~~~~~~~~~
List/attach/detach operations seem to work ok, devices are recognized by the target VM etc.
But actual usage of the attached devices is unstable at best. In fact the only working device
I saw was one USB stick (and this only after it took a minute to time out and reset the bus
couple times). Kernel crashes are normal as well. I have not investigated these issues yet,
I had similar experience with Marek's scripts.
* System keyboard / mouse are listed and can be detached away
* Virtual USB devices (ones created by PVUSB frontend) may be listed
* The installation/configuration is not persistent, not retained between reboots
* No debugging / logging / audit trail
* When an attached device is physically unplugged, USB port remains mapped but not displayed
in the list. If device is plugged back it continues to work. Unlisted device cannot be detached.
* We are not attaching actual devices, but USB ports (different behavior from VMWare, might be confusing)
* After device is detached from the frontend and returned back to the backend it is not alwayws usable there
* Code changing configuration of pvusb fe/be and vusb bind/unbind helper are located
misc/xl-qvm-usb-attach.py misc/xl-qvm-usb-detach.py misc/vusb-ctl.py. These helpers are
deployed into the backend domain. The initialization code is qubesutils.py in usb_setup(),
should probably also be moved into an external helper. Perhaps the functionality of these
external helpers should be merged into libxl? The is one catch is invokation of vusb helper
in the backend domain -- now it relies on qubes-specific API.
* After reboot attached USB devices are not listed by 'qvm-usb -l' until replugged.

9
doc/_static/qubes.css vendored Normal file
View File

@ -0,0 +1,9 @@
/* http://stackoverflow.com/questions/21027105/how-can-i-have-sphinx-tables-fit-to-width */
table.docutils col {
width: auto;
}
/*
vim: ts=4 sw=4 et
*/

4
doc/_templates/layout.html vendored Normal file
View File

@ -0,0 +1,4 @@
{% extends '!layout.html' %}
{% set css_files = css_files + ['_static/qubes.css'] %}
{# vim: ts=4 sw=4 et #}

279
doc/conf.py Normal file
View File

@ -0,0 +1,279 @@
# -*- coding: utf-8 -*-
#
# core-admin documentation build configuration file, created by
# sphinx-quickstart on Thu Nov 13 15:02:15 2014.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import subprocess
import sys
import time
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../'))
sys.path.insert(1, os.path.abspath('../test-packages'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.coverage',
'sphinx.ext.doctest',
'sphinx.ext.graphviz',
'sphinx.ext.inheritance_diagram',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'qubes.dochelpers',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'core-admin'
copyright = u'2010-{}, Invisible Things Lab'.format(time.strftime('%Y'))
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = open('../version').read().strip()
# The full version, including alpha/beta/rc tags.
release = subprocess.check_output(['git', 'describe', '--long', '--dirty']).strip().decode()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
today_fmt = '%d.%m.%Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
autodoc_member_order = 'groupwise'
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = 'default'
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
# 'collapsiblesidebar': True,
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%d.%m.%Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html links do not work with svg!
graphviz_output_format = 'png'
# Output file base name for HTML help builder.
htmlhelp_basename = 'core-admin-doc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'core-admin.tex', u'core-admin Documentation',
u'Invisible Things Lab', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
# authors should be empty and authors should be specified in each man page,
# because html builder will omit them
_man_pages_author = []
man_pages = [
('manpages/qubesd-query', 'qubesd-query',
u'Low-level qubesd interrogation tool', _man_pages_author, 1),
]
if os.path.exists('sandbox.rst'):
man_pages.append(('sandbox', 'sandbox',
u'Sandbox manpage', 'Sandbox Author', 1))
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'core-admin', u'core-admin Documentation',
u'Invisible Things Lab', 'core-admin', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('http://docs.python.org/', None)}

45
doc/example.xml Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<qubes version="3.0">
<properties>
<property name="default_netvm" ref="domain-1" />
</properties>
<labels>
<label id="label-1" color="#cc0000">red</label>
</labels>
<domains>
<domain class="QubesVM" id="domain-1">
<properties>
<property name="qid">1</property>
<property name="name">netvm</property>
<property name="label" ref="label-1" />
</properties>
<features>
<feature name="meminfo-writer"></feature>
<feature name="qubes-firewall">1</feature>
</features>
<devices class="pci">
<device backend-domain="dom0" id="01:23.45"/>
</devices>
</domain>
<domain class="QubesVM" id="domain-2">
<properties>
<property name="qid">2</property>
<property name="name">appvm</property>
<property name="label" ref="label-1" />
</properties>
<tags>
<tag name="userdef">qwe123</tag>
</tags>
</domain>
</domains>
</qubes>
<!--
vim: ts=4 sw=4 et
-->

43
doc/index.rst Normal file
View File

@ -0,0 +1,43 @@
.. core-admin documentation master file, created by
sphinx-quickstart on Thu Nov 13 15:02:15 2014.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to core-admin's documentation!
======================================
This page contains documentation autogenerated from source tree. It includes
manpages and API documentation. For primary user documentation, see
`https://wiki.qubes-os.org <https://wiki.qubes-os.org>`_.
.. toctree::
:maxdepth: 2
qubes
qubes-vm/index
qubes-events
qubes-exc
qubes-ext
qubes-log
qubes-mgmt
qubes-policy
qubes-backup
qubes-tools/index
qubes-tests
qubes-dochelpers
.. toctree::
:maxdepth: 1
libvirt
autoxml
manpages/index
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. vim: ts=3 sw=3 et

114
doc/libvirt.rst Normal file
View File

@ -0,0 +1,114 @@
Custom libvirt config
=====================
Starting from Qubes OS R4.0, libvirt domain config is generated using jinja
templates. Those templates can be overridden by the user in a couple of ways.
A basic knowledge of jinja template language and libvirt xml spec is needed.
.. seealso::
https://libvirt.org/formatdomain.html
Format of the domain XML in libvirt.
http://jinja.pocoo.org/docs/dev/templates/
Template format documentation.
File paths
----------
In order of increasing precedence: the main template, from which the config is
generated is :file:`/usr/share/templates/libvirt/xen.xml`).
The distributor may put a file at
:file:`/usr/share/qubes/template/xen-dist.xml`) to override this file.
User may put a file at either
:file:`/etc/qubes/templates/libvirt/xen-user.xml` or
:file:`/etc/qubes/templates/libvirt/by-name/<name>.xml`, where ``<name>`` is
full name of the domain. Wildcards are not supported but symlinks are.
Jinja has a concept of template names, which basically is the path below some
load point, which in Qubes' case is :file:`/etc/qubes/templates` and
:file:`/usr/share/qubes/templates`. Thus names of those templates are
respectively ``'libvirt/xen.xml'``, ``'libvirt/xen-dist.xml'``,
``'libvirt/xen-user.xml'`` and ``'libvirt/by-name/<name>.xml'``.
This will be important later.
.. note::
Those who know jinja python API will know that the abovementioned locations
aren't the only possibilities. Yes, it's a lie, but a justified one.
What to put in the template
---------------------------
In principle the user may put anything in the template and there is no attempt
to constrain the user from doing stupid things. One obvious thing is to copy the
original config file and make changes.
.. code-block:: jinja
<domain type="xen">
<name>{{ vm.name }}</name>
...
The better way is to inherit from the original template and override any number
of blocks. This is the point when we need the name of the original template.
.. code-block:: jinja
{% extends 'libvirt/xen.xml' %}
{% block devices %}
{{ super() }}
<serial type='pty'>
<target port='0'/>
</serial>
{% endblock %}
``{% extends %}`` specifies which template we inherit from. Then you may put any
block by putting new content inside ``{% block %}{% endblock %}``.
``{{ super() }}`` is substituted with original content of the block as specified
in the parent template. Untouched blocks remain as they were.
The example above adds serial device.
Template API
------------
.. warning::
This API is provisional and subject to change at the minor releases until
further notice. No backwards compatibility is promised.
Globals
```````
vm
the domain object (instance of subclass of
:py:class:`qubes.vm.qubesvm.QubesVM`)
Filters
```````
No custom filters at the moment.
Blocks in the default template
``````````````````````````````
basic
Contains ``<name>``, ``<uuid>``, ``<memory>``, ``<currentMemory>`` and
``<vcpu>`` nodes.
os
Contents of ``<os>`` node.
features
Contents of ``<features>`` node.
clock
Contains the ``<clock>`` node.
on
Contains ``<on_*>`` nodes.
devices
Contents of ``<devices>`` node.
.. vim: ts=3 sts=3 sw=3 et

10
doc/manpages/index.rst Normal file
View File

@ -0,0 +1,10 @@
Command line utilities
======================
Those are manual pages provided for command line tools, just formatted in HTML.
.. toctree::
:maxdepth: 1
:glob:
*

View File

@ -0,0 +1,38 @@
.. program:: qubes-create
:program:`qubes-create` -- Create new Qubes OS store.
=====================================================
This command is the only supported way to create new qubes.xml. It is intended
to be readable though, so you can probably create it manually if you like.
Synopsis
--------
:command:`qubes-create` [-h] [--qubesxml *XMLFILE*] [--property *NAME*=*VALUE*]
Options
-------
.. option:: --help, -h
show help message and exit
.. option:: --verbose, -v
Increase verbosity.
.. option:: --quiet, -q
Decrease verbosity.
.. option:: --property=NAME=VALUE, --prop=NAME=VALUE, -p NAME=VALUE
On creation, set global property *NAME* to *VALUE*.
Authors
-------
| Wojtek Porczyk <woju at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80

View File

@ -0,0 +1,42 @@
.. program:: qubesd-query
:program:`qubesd-query` -- low-level qubesd interrogation tool
==============================================================
Synopsis
--------
:command:`qubesd-query` [-h] [--connect *PATH*] *SRC* *METHOD* *DEST* [*ARGUMENT*]
Options
-------
.. option:: --help, -h
Show the help message and exit.
.. option:: --connect=PATH, -c PATH
Change path to qubesd UNIX socket from default.
.. option:: --empty, -e
Send empty payload. Do not attempt to read anything from standard input, but
send the request immediately.
Description
-----------
This tool is used to directly invoke qubesd. The parameters of RPC call shall be
given as arguments to the command. Payload should be written to standard input.
Result can be read from standard output.
Authors
-------
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
| Wojtek Porczyk <woju at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80

8
doc/qubes-backup.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.backup` -- Backup
================================
.. automodule:: qubes.backup
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

8
doc/qubes-dochelpers.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.dochelpers` -- Helpers for Sphinx documentation
==============================================================
.. automodule:: qubes.dochelpers
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

153
doc/qubes-events.rst Normal file
View File

@ -0,0 +1,153 @@
:py:mod:`qubes.events` -- Qubes events
======================================
Some objects in qubes (most notably domains) emit events. You may hook them and
execute your code when particular event is fired. Events in qubes are added
class-wide -- it is not possible to add event handler to one instance only, you
have to add handler for whole class.
Firing events
-------------
Events are fired by calling :py:meth:`qubes.events.Emitter.fire_event`. The
first argument is event name (a string). You can fire any event you wish, the
names are not checked in any way, however each class' documentation tells what
standard events will be fired on it. When firing an event, caller may specify
some optional keyword arguments. Those are dependent on the particular event in
question -- they are passed as-is to handlers.
Event handlers are fired in reverse method resolution order, that is, first for
parent class and then for it's child. For each class, first are called handlers
defined in it's source, then handlers from extensions and last the callers added
manually.
There is second method, :py:meth:`qubes.events.Emitter.fire_event_pre`, which
fires events in reverse order. It is suitable for events fired before some
action is performed. You may at your own responsibility raise exceptions from
such events to try to prevent such action.
Events handlers may yield values. Those values are aggregated and returned
to the caller as a list of those values. See below for details.
Handling events
---------------
There are several ways to handle events. In all cases you supply a callable
(most likely function or method) that will be called when someone fires the
event. The first argument passed to the callable will be the object instance on
which the event was fired and the second one is the event name. The rest are
passed from :py:meth:`qubes.events.Emitter.fire_event` as described previously.
One callable can handle more than one event.
The easiest way to hook an event is to use
:py:func:`qubes.events.handler` decorator.
.. code-block:: python
import qubes.events
class MyClass(qubes.events.Emitter):
@qubes.events.handler('event1', 'event2')
def event_handler(self, event):
if event == 'event1':
print('Got event 1')
elif event == 'event2':
print('Got event 2')
o = MyClass()
o.fire_event('event1')
Note that your handler will be called for all instances of this class.
.. TODO: extensions
.. TODO: add/remove_handler
Handling events with variable signature
---------------------------------------
Some events are specified with variable signature (i.e. they may have different
number of arguments on each call to handlers). You can write handlers just like
every other python function with variable signature.
.. code-block:: python
import qubes
def on_property_change(subject, event, name, newvalue, oldvalue=None):
if oldvalue is None:
print('Property {} initialised to {!r}'.format(name, newvalue))
else:
print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
app = qubes.Qubes()
app.add_handler('property-set:default_netvm')
If you expect :py:obj:`None` to be a reasonable value of the property, you have
a problem. One way to solve it is to invent your very own, magic
:py:class:`object` instance.
.. code-block:: python
import qubes
MAGIC_NO_VALUE = object()
def on_property_change(subject, event, name, newvalue, oldvalue=MAGIC_NO_VALUE):
if oldvalue is MAGIC_NO_VALUE:
print('Property {} initialised to {!r}'.format(name, newvalue))
else:
print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
app = qubes.Qubes()
app.add_handler('property-set:default_netvm')
There is no possible way of collision other than intentionally passing this very
object (not even passing similar featureless ``object()``), because ``is``
python syntax checks object's :py:meth:`id`\ entity, which will be different for
each :py:class:`object` instance.
Returning values from events
----------------------------
Some events may be called to collect values from the handlers. For example the
event ``is-fully-usable`` allows plugins to report a domain as not fully usable.
Such handlers, instead of returning :py:obj:`None` (which is the default when
the function does not include ``return`` statement), should return an iterable
or itself be a generator. Those values are aggregated from all handlers and
returned to the caller as list. The order of this list is undefined.
.. code-block:: python
import qubes.events
class MyClass(qubes.events.Emitter):
@qubes.events.handler('event1')
def event1_handler1(self, event):
# do not return anything, equivalent to "return" and "return None"
pass
@qubes.events.handler('event1')
def event1_handler2(self, event):
yield 'aqq'
yield 'zxc'
@qubes.events.handler('event1')
def event1_handler3(self, event):
return ('123', '456')
o = MyClass()
# returns ['aqq', 'zxc', '123', '456'], possibly not in order
effect = o.fire_event('event1')
Module contents
---------------
.. automodule:: qubes.events
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

63
doc/qubes-exc.rst Normal file
View File

@ -0,0 +1,63 @@
:py:mod:`qubes.exc` -- Exceptions
=================================
As most of the modern programming languages, Python has exceptions, which can be
thrown (``raise``\ d) when something goes bad. What exactly means "bad" depends
on several circumstances.
One of those circumstances is who exactly is to blame: programmer or user? Some
errors are commited by programmer and will most probably result it program
failure. But most errors are caused by the user, notably by specifying invalid
commands or data input. Those errors *should not* result in failure, but fault
and be handled gracefuly. One more time, what "gracefuly" means depends on
specific program and its interface (for example GUI programs should most likely
display some admonition, but will not crash).
In Qubes we have special exception class, :py:class:`qubes.exc.QubesException`,
which is dedicated to handling user-caused problems. Programmer errors should
not result in raising QubesException, but it should instead result in one of the
standard Python exception. QubesExceptions should have a nice message that can
be shown to the user. On the other hand, some children classes of QubesException
also inherit from children of :py:class:`StandardException` to allow uniform
``except`` clauses.
Often the error relates to some domain, because we expect it to be in certain
state, but it is not. For example to start a machine, it should be halted. For
that we have the children of the :py:class:`qubes.exc.QubesVMError` class. They
all take the domain in question as their first argument and an (optional)
message as the second. If not specified, there is stock message which is
generally informative enough.
On writing error messages
-------------------------
As a general rule, error messages should be short but precise. They should not
blame user for error, but the user should know, what had been done wrong and
what to do next.
If possible, write the message that is stating the fact, for example "Domain is
not running" instead of "You forgot to start the domain" (you fool!). Avoid
commanding user, like "Start the domain first" (user is not a function you can
call for effect). Instead consider writing in negative form, implying expected
state: "Domain is not running" instead of "Domain is paused" (yeah, what's wrong
with that?).
Also avoid implying the personhood of the computer, including adressing user in
second person. For example, write "Sending message failed" instead of "I failed
to send the message".
Inheritance diagram
-------------------
.. inheritance-diagram:: qubes.exc
Module contents
---------------
.. automodule:: qubes.exc
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et tw=80

8
doc/qubes-ext.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.ext` -- Qubes extensions
=======================================
.. automodule:: qubes.ext
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

8
doc/qubes-log.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.log` -- Logging routines
=======================================
.. automodule:: qubes.log
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

87
doc/qubes-policy.rst Normal file
View File

@ -0,0 +1,87 @@
:py:mod:`qubes.policy` -- Qubes RPC policy
==========================================
Every Qubes domain can trigger various RPC services, but if such call would be
allowed depends on Qubes RPC policy (qrexec policy in short).
Qrexec policy format
--------------------
Policy consists of a file, which is parsed line-by-line. First matching line
is used as an action.
Each line consist of three values separated by white characters (space(s), tab(s)):
1. Source specification, which is one of:
- domain name
- `$anyvm` - any domain
- `$tag:some-tag` - VM having tag `some-tag`
- `$type:vm-type` - VM of `vm-type` type, available types:
AppVM, TemplateVM, StandaloneVM, DispVM
2. Target specification, one of:
- domain name
- `$anyvm` - any domain, excluding dom0
- `$tag:some-tag` - domain having tag `some-tag`
- `$type:vm-type` - domain of `vm-type` type, available types:
AppVM, TemplateVM, StandaloneVM, DispVM
- `$default` - used when caller did not specified any VM
- `$dispvm:vm-name` - _new_ Disposable VM created from AppVM `vm-name`
- `$dispvm` - _new_ Disposable VM created from AppVM pointed by caller
property `default_dispvm`, which defaults to global property `default_dispvm`
3. Action and optional action parameters, one of:
- `allow` - allow the call, without further questions; optional parameters:
- `target=` - override caller provided call target -
possible values are: domain name, `$dispvm` or `$dispvm:vm-name`
- `user=` - call the service using this user, instead of the user
pointed by target VM's `default_user` property
- `deny` - deny the call, without further questions; no optional
parameters are supported
- `ask` - ask the user for confirmation; optional parameters:
- `target=` - override user provided call target
- `user=` - call the service using this user, instead of the user
pointed by target VM's `default_user` property
- `default_target=` - suggest this target when prompting the user for
confirmation
Alternatively, a line may consist of a single keyword `$include:` followed by a
path. This will load a given file as its content would be in place of
`$include` line. Relative paths are resolved relative to
`/etc/qubes-rpc/policy` directory.
Evaluating `ask` action
-----------------------
When qrexec policy specify `ask` action, the user is asked whether the call
should be allowed or denied. In addition to that, user also need to choose
target domain. User have to choose from a set of targets specified by the
policy. Such set is calculated using the algorithm below:
1. If `ask` action have `target=` option specified, only that target is
considered. A prompt window will allow to choose only this value and it will
also be pre-filled value.
2. If no `target=` option is specified, all rules are evaluated to see what
target domains (for a given source domain) would result in `ask` or `allow`
action. If any of them have `target=` option set, that value is used instead of
the one specified in "target" column (for this particular line). Then the user
is presented with a confirmation dialog and an option to choose from those
domains.
3. If `default_target=` option is set, it is used as
suggested value, otherwise no suggestion is made (regardless of calling domain
specified any target or not).
Module contents
---------------
.. automodule:: qubespolicy
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

144
doc/qubes-tests.rst Normal file
View File

@ -0,0 +1,144 @@
:py:mod:`qubes.tests` -- Writing tests for qubes
================================================
Writing tests is very important for ensuring quality of code that is delivered.
Given test case may check for variety of conditions, but they generally fall
inside those two categories of conformance tests:
* Unit tests: these test smallest units of code, probably methods of functions,
or even combination of arguments for one specific method.
* Integration tests: these test interworking of units.
We are interested in both categories.
There is also distinguished category of regression tests (both unit- and
integration-level), which are included because they check for specific bugs that
were fixed in the past and should not happen in the future. Those should be
accompanied with reference to closed ticked that describes the bug.
Qubes' tests are written using :py:mod:`unittest` module from Python Standard
Library for both unit test and integration tests.
Test case organisation
----------------------
Every module (like :py:mod:`qubes.vm.qubesvm`) should have its companion (like
``qubes.tests.vm.qubesvm``). Packages ``__init__.py`` files should be
accompanied by ``init.py`` inside respective directory under :file:`tests/`.
Inside tests module there should be one :py:class:`qubes.tests.QubesTestCase`
class for each class in main module plus one class for functions and global
variables. :py:class:`qubes.tests.QubesTestCase` classes should be named
``TC_xx_ClassName``, where ``xx`` is two-digit number. Test functions should be
named ``test_xxx_test_name``, where ``xxx`` is three-digit number. You may
introduce some structure of your choice in this number.
FIXME: where are placed integration tests?
Writing tests
-------------
First of all, testing is art, not science. Testing is not panaceum and won't
solve all of your problems. Rules given in this guide and elsewhere should be
followed, but shouldn't be worshipped.
Test can be divided into three phases. The first part is setup phase. In this
part you should arrange for a test condition to occur. You intentionally put
system under test in some specific state. Phase two is executing test condition
-- for example you check some variable for equality or expect that some
exception is raised. Phase three is responsible for returning a verdict. This is
largely done by the framework.
When writing test, you should think about order of execution. This is the reason
of numbers in names of the classes and test methods. Tests should be written
bottom-to-top, that is, test setups that are ran later may depend on features
that are tested after but not the other way around. This is important, because
when encountering failure we expect the reason happen *before*, and not after
failure occured. Therefore, when encountering multiple errors, we may instantly
focus on fixing the first one and not wondering if any later problems may be
relevant or not. Some people also like to enable
:py:attr:`unittest.TestResult.failfast` feature, which stops on the first failed
test -- with wrong order this messes up their workflow.
Test should fail for one reason only and test one specific issue. This does not
mean that you can use one ``.assert*`` method per ``test_`` function: for
example when testing one regular expression you are welcome to test many valid
and/or invalid inputs, especcialy when test setup is complicated. However, if
you encounter problems during setup phase, you should *skip* the test, and not
fail it. This also aids interpretation of results.
You may, when it makes sense, manipulate private members of classes under tests.
This violates one of the founding principles of object-oriented programming, but
may be required to write tests in correct order if your class provides public
methods with circular dependencies. For example containers may check if added
item is already in container, but you can't test ``__contains__`` method without
something already inside. Don't forget to test the other method later.
Special Qubes-specific considerations
-------------------------------------
Events
^^^^^^
:py:class:`qubes.tests.QubesTestCase` provides convenient methods for checking
if event fired or not: :py:meth:`qubes.tests.QubesTestCase.assertEventFired` and
:py:meth:`qubes.tests.QubesTestCase.assertEventNotFired`. These require that
emitter is subclass of :py:class:`qubes.tests.TestEmitter`. You may instantiate
it directly::
import qubes.tests
class TC_10_SomeClass(qubes.tests.QubesTestCase):
def test_000_event(self):
emitter = qubes.tests.TestEmitter()
emitter.fire_event('did-fire')
self.assertEventFired(emitter, 'did-fire')
If you need to snoop specific class (which already is a child of
:py:class:`qubes.events.Emitter`, possibly indirect), you can define derivative
class which uses :py:class:`qubes.tests.TestEmitter` as mix-in::
import qubes
import qubes.tests
class TestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
pass
class TC_20_PropertyHolder(qubes.tests.QubesTestCase):
def test_000_event(self):
emitter = TestHolder()
self.assertEventNotFired(emitter, 'did-not-fire')
Dom0
^^^^
Qubes is a complex piece of software and depends on number other complex pieces,
notably VM hypervisor or some other isolation provider. Not everything may be
testable under all conditions. Some tests (mainly unit tests) are expected to
run during compilation, but many tests (probably all of the integration tests
and more) can run only inside already deployed Qubes installation. There is
special decorator, :py:func:`qubes.tests.skipUnlessDom0` which causes test (or
even entire class) to be skipped outside dom0. Use it freely::
import qubes.tests
class TC_30_SomeClass(qubes.tests.QubesTestCase):
@qubes.tests.skipUnlessDom0
def test_000_inside_dom0(self):
# this is skipped outside dom0
pass
@qubes.tests.skipUnlessDom0
class TC_31_SomeOtherClass(qubes.tests.QubesTestCase):
# all tests in this class are skipped
pass
Module contents
---------------
.. automodule:: qubes.tests
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et tw=80

32
doc/qubes-tools/index.rst Normal file
View File

@ -0,0 +1,32 @@
:py:mod:`qubes.tools` -- Command line utilities
===============================================
Those are Python modules that house actual functionality of CLI tools -- the
files installed in :file:`/usr/bin` only import these modules and run ``main()``
function.
The modules should make available for import theirs command line parsers
(instances of :py:class:`argparse.ArgumentParser`) as either ``.parser``
attribute or function ``get_parser()``, which returns parser. Manual page will
be automatically checked during generation if its "Options" section contains all
options from this parser (and only those).
Module contents
---------------
.. automodule:: qubes.tools
:members:
:show-inheritance:
All CLI tools
-------------
.. toctree::
:maxdepth: 1
:glob:
*
.. vim: ts=3 sw=3 et tw=80

View File

@ -0,0 +1,8 @@
:py:mod:`qubes.tools.qmemmand` -- qmemman daemon
================================================
.. automodule:: qubes.tools.qmemmand
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

View File

@ -1,24 +0,0 @@
===========
qubes-prefs
===========
NAME
====
qubes-prefs - display system-wide Qubes settings, such as:
- clock VM
- update VM
- default template
- default firewallVM
- default kernel
- default netVM
SYNOPSIS
========
| qubes-prefs
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,30 +0,0 @@
==========
qubes_guid
==========
NAME
====
qubes_guid
SYNOPSIS
========
| qubes_guid -d domain_id [-c color] [-l label_index] [-i icon name, no suffix] [-v] [-q]
OPTIONS
=======
-v
Increase log verbosity
-q
Decrease log verbosity
Log levels:
0. only errors
1. some basic messages (default)
2. debug
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

8
doc/qubes-vm/adminvm.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.vm.adminvm` -- Dom0
==================================
.. automodule:: qubes.vm.adminvm
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

8
doc/qubes-vm/appvm.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.vm.appvm` -- Application VM
==========================================
.. automodule:: qubes.vm.appvm
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

8
doc/qubes-vm/dispvm.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.vm.dispvm` -- Disposable VM
==========================================
.. automodule:: qubes.vm.dispvm
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

84
doc/qubes-vm/index.rst Normal file
View File

@ -0,0 +1,84 @@
:py:mod:`qubes.vm` -- Different Virtual Machine types
=====================================================
Qubes is composed of several virtual machines that are interconnected in
several ways. From now on they will be called „domains”, as they may not
actually be true virtual machines -- we plan to support LXC containers for
example. Because of Xen-only legacy of Qubes code, it is custom to refer to them
in long/plural as ``domains`` and in short/singular as ``vm``.
Domain object
-------------
There are couple of programming objects that refer to domain. The main is the
instance of :py:class:`qubes.vm.QubesVM`. This is the main „porcelain” object,
which carries other objects and supplies convenience methods like
:py:meth:`qubes.vm.qubesvm.QubesVM.start`. This class is actually divided in
two, the :py:class:`qubes.vm.qubesvm.QubesVM` cares about Qubes-specific
actions, that are more or less directly related to security model. It is
intended to be easily auditable by non-expert programmers (ie. we don't use
Python's magic there). The second class is its parent,
:py:class:`qubes.vm.BaseVM`, which is concerned about technicalities like XML
serialising/deserialising. It is of less concern to threat model auditors, but
still relevant to overall security of the Qubes OS. It is written for
programmers by programmers.
The second object is the XML node that refers to the domain. It can be accessed
as :py:attr:`Qubes.vm.BaseVM.xml` attribute of the domain object. The third one
is :py:attr:`Qubes.vm.qubesvm.QubesVM.libvirt_domain` object for directly
interacting with libvirt. Those objects are intended to be used from core and/or
plugins, but not directly by user or from qvm-tools. They are however public, so
there are no restrictions.
Domain classes
--------------
There are several different types of VM, because not every Qubes domain is equal
-- some of them perform specific functions, like NetVM; others have different
life cycle, like DisposableVM. For that, different domains have different Python
classes. They are all defined in this package, generally one class per module,
but some modules contain private globals that serve this particular class.
Package contents
----------------
Main public classes
^^^^^^^^^^^^^^^^^^^
.. autoclass:: qubes.vm.BaseVM
:members:
:show-inheritance:
Helper classes and functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: qubes.vm.Features
:members:
:show-inheritance:
Particular VM classes
^^^^^^^^^^^^^^^^^^^^^
Main types:
.. toctree::
:maxdepth: 1
qubesvm
appvm
templatevm
Special VM types:
.. toctree::
:maxdepth: 1
dispvm
adminvm
.. standalonevm
.. vim: ts=3 sw=3 et

8
doc/qubes-vm/qubesvm.rst Normal file
View File

@ -0,0 +1,8 @@
:py:mod:`qubes.vm.qubesvm` -- Shared functionality
==================================================
.. automodule:: qubes.vm.qubesvm
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

View File

@ -0,0 +1,8 @@
:py:mod:`qubes.vm.templatevm` -- Template for AppVM
===================================================
.. automodule:: qubes.vm.templatevm
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

220
doc/qubes.rst Normal file
View File

@ -0,0 +1,220 @@
:py:mod:`qubes` -- Common concepts
==================================
Global Qubes object
-------------------
Because all objects in Qubes' world are interconnected, there is no possibility
to instantiate them separately. They are all loaded together and contained in
the one ``app`` object, an instance of :py:class:`qubes.Qubes` class.
The loading from XML is done in stages, because Qubes domains are dependent on
each other in what can be even a circular dependency. Therefore some properties
(especcialy those that refer to another domains) are loaded later. Refer to
:py:class:`qubes.Qubes` class documentation to get description of every stage.
Properties
----------
Many parameters of Qubes can be changed -- from names of particular domains to
default NetVM for all AppVMs. All of those *configurable* parameters are called
*properties* and can be accessed like Python attributes on their owners::
>>> import qubes
>>> app = qubes.Qubes()
>>> app.domain[0] # docutils: +ELLIPSIS
<AdminVM ...>
>>> app.domain[0].name
'dom0'
Definition
^^^^^^^^^^
Properties are defined on global :py:class:`qubes.Qubes` application object and
on every domain. Those classess inherit from :py:class:`PropertyHolder` class,
which is responsible for operation of properties.
Each Qubes property is actually a *data descriptor* (a Python term), which means
they are attributes of their classess, but when trying to access it from
*instance*, they return it's underlying value instead. They can be thought of as
Python's builtin :py:class:`property`, but greatly enhanced. They are defined in
definition of their class::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop')
>>> instance = MyTestHolder()
>>> instance.testprop = 'aqq'
>>> instance.testprop
'aqq'
If you like to access some attributes of the property *itself*, you should refer
to instance's class::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop')
>>> instance = MyTestHolder()
>>> instance.testprop = 'aqq'
>>> type(instance.testprop)
<type 'str'>
>>> type(instance.__class__.testprop)
<class 'qubes.property'>
>>> instance.__class__.testprop.__name__
'testprop'
As a rule, properties are intended to be serialised and deserialised to/from XML
file. There are very few exceptions, but if you don't intend to save the
property to XML, you should generally go for builtin :py:class:`property`.
One important difference from builtin properties is that there is no getter
function, only setter. In other words, they are not dynamic, you cannot return
different value every time something wants to access it. This is to ensure that
while saving the property is not a moving target.
Property's properties
^^^^^^^^^^^^^^^^^^^^^
You can specify some parameters while defining the property. The most important
is the `type`: on setting, the value is coerced to this type. It is well suited
to builtin types like :py:class:`int`::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop')
>>> intprop = qubes.property('intprop', type=int)
>>> instance = MyTestHolder()
>>> instance.testprop = '123'
>>> instance.intprop = '123'
>>> instance.testprop
'123'
>>> instance.intprop
123
Every property should be documented. You should add a short description to your
property, which will appear, among others, in :program:`qvm-prefs` and
:program:`qvm-ls` programs. It should not use any Sphinx-specific markup::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop',
>>> doc='My new and shiny property.')
>>> MyTestHolder.testprop.__doc__
'My new and shiny property.'
In addition to `type`, properties also support `setter` parameter. It acts
similar to `type`, but is always executed (not only when types don't agree) and
accepts more parameters: `self`, `prop` and `value` being respectively: owners'
instance, property's instance and the value being set. There is also `saver`,
which does reverse: given value of the property it should return a string that
can be parsed by `saver`.
Unset properties and default values
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Properties may be unset, even if they are defined (that is, on access they raise
:py:exc:`AttributeError` -- that is the normal Python way to tell that the
attribute is absent). You can manually unset a property using Python's ``del``
statement::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop')
>>> instance = MyTestHolder()
>>> instance.testprop
AttributeError: ...
>>> instance.testprop = 123
>>> instance.testprop
123
>>> del instance.testprop
>>> instance.testprop
AttributeError: ...
Alternatively, some properties may return some other value instead, if that's
the reasonable thing to do. For example, when
:py:attr:`qubes.vm.qubesvm.QubesVM.netvm` is unset, we check global setting
:py:attr:`qubes.Qubes.default_netvm` instead. Returning :py:obj:`None` as
default would be wrong, as it is marker that means „no NetVM, machine
disconnected”.
You can define a default value either as constant or as a callable. In the
second case, the callable should accept one argument, the instance that owns the
property::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop')
>>> def1prop = qubes.property('testprop', default=123)
>>> netvm = qubes.property('testprop',
>>> default=(lambda self: self.app.default_netvm))
>>> instance = MyTestHolder()
>>> instance.testprop
AttributeError: ...
>>> instance.def1prop
123
>>> instance.netvm # doctest: +SKIP
<NetVM ...>
Setting netvm on particular domain of course does not affect global default, but
only this instance. But there are two problems:
- You don't know if the value of the property you just accessed was it's
true or default value.
- After ``del``'ing a property, you still will get a value on access. You
cannot count on `AttributeError` raised from them.
Therefore Qubes support alternative semantics. You can (and probably should,
wherever applicable) use no ``del``, but assignment of special magic object
:py:obj:`qubes.property.DEFAULT`. There is also method
:py:meth:`qubes.PropertyHolder.property_is_default`, which can be used to
distinguish unset from set properties::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop', default=123)
>>> instance.testprop
123
>>> instance.property_is_default('testprop')
True
>>> instance.testprop = 123
>>> instance.testprop
>>> instance.property_is_default('testprop')
False
>>> instance.testprop = qubes.property.DEFAULT
>>> instance.property_is_default('testprop')
True
Inheritance
^^^^^^^^^^^
Properties in subclassess overload properties from their parents, like
expected::
>>> import qubes
>>> class MyTestHolder(qubes.PropertyHolder):
>>> testprop = qubes.property('testprop')
>>> class MyOtherHolder(MyTestHolder):
>>> testprop = qubes.property('testprop', setter=qubes.property.forbidden)
>>> instance = MyOtherHolder()
>>> instane.testprop = 123
TypeError: ...
Module contents
---------------
.. automodule:: qubes
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

View File

@ -1,30 +0,0 @@
=============
qvm-add-appvm
=============
NAME
====
qvm-add-appvm - add an already installed appvm to the Qubes DB
WARNING: Normally you should not need this command, and you should use qvm-create instead!
SYNOPSIS
========
| qvm-add-appvm [options] <appvm-name> <vm-template-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-p DIR_PATH, --path=DIR_PATH
Specify path to the template directory
-c CONF_FILE, --conf=CONF_FILE
Specify the Xen VM .conf file to use(relative to the template dir path)
--force-root
Force to run, even with root privileges
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,28 +0,0 @@
================
qvm-add-template
================
NAME
====
qvm-add-template - adds an already installed template to the Qubes DB
SYNOPSIS
========
| qvm-add-template [options] <vm-template-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-p DIR_PATH, --path=DIR_PATH
Specify path to the template directory
-c CONF_FILE, --conf=CONF_FILE
Specify the Xen VM .conf file to use(relative to the template dir path)
--rpm
Template files have been installed by RPM
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,50 +0,0 @@
==================
qvm-backup-restore
==================
NAME
====
qvm-backup-restore - restores Qubes VMs from backup
SYNOPSIS
========
| qvm-backup-restore [options] <backup-dir>
OPTIONS
=======
-h, --help
Show this help message and exit
--verify-only
Do not restore the data, only verify backup integrity
--skip-broken
Do not restore VMs that have missing templates or netvms
--ignore-missing
Ignore missing templates and netvms, and restore the VMs anyway
--skip-conflicting
Do not restore VMs that are already present on the host
--force-root
Force to run with root privileges
--replace-template=REPLACE_TEMPLATE
Restore VMs using another template, syntax: old-template-name:new-template-name (can be repeated)
-x EXCLUDE, --exclude=EXCLUDE
Skip restore of specified VM (can be repeated)
--skip-dom0-home
Do not restore dom0's user home directory
--ignore-username-mismatch
Ignore dom0 username mismatch when restoring dom0's user home directory
-d APPVM, --dest-vm=APPVM
Restore from a backup located in a specific AppVM
-e, --encrypted
The backup is encrypted
-p, --passphrase-file
Read passphrase from file, or use '-' to read from stdin
-z, --compressed
The backup is compressed
--debug
Enable (a lot of) debug output
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,46 +0,0 @@
==========
qvm-backup
==========
NAME
====
qvm-backup
SYNOPSIS
========
| qvm-backup [options] <backup-dir-path> [vms-to-be-included ...]
OPTIONS
=======
-h, --help
Show this help message and exit
-x EXCLUDE_LIST, --exclude=EXCLUDE_LIST
Exclude the specified VM from backup (might be repeated)
--force-root
Force to run with root privileges
-d, --dest-vm
Specify the destination VM to which the backup will be set (implies -e)
-e, --encrypt
Encrypt the backup
--no-encrypt
Skip encryption even if sending the backup to a VM
-p, --passphrase-file
Read passphrase from a file, or use '-' to read from stdin
-E, --enc-algo
Specify a non-default encryption algorithm. For a list of supported algorithms, execute 'openssl list-cipher-algorithms' (implies -e)
-H, --hmac-algo
Specify a non-default HMAC algorithm. For a list of supported algorithms, execute 'openssl list-message-digest-algorithms'
-z, --compress
Compress the backup
-Z, --compress-filter
Specify a non-default compression filter program (default: gzip)
--tmpdir
Specify a temporary directory (if you have at least 1GB free RAM in dom0, use of /tmp is advised) (default: /var/tmp)
--debug
Enable (a lot of) debug output
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,44 +0,0 @@
=========
qvm-block
=========
NAME
====
qvm-block - list/set VM PCI devices.
SYNOPSIS
========
| qvm-block -l [options]
| qvm-block -a [options] <vm-name> <device-vm-name>:<device>
| qvm-block -A [options] <vm-name> <file-vm-name>:<file>
| qvm-block -d [options] <device-vm-name>:<device>
| qvm-block -d [options] <vm-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-l, --list
List block devices
-A, --attach-file
Attach specified file instead of physical device
-a, --attach
Attach block device to specified VM
-d, --detach
Detach block device
-f FRONTEND, --frontend=FRONTEND
Specify device name at destination VM [default: xvdi]
--ro
Force read-only mode
--no-auto-detach
Fail when device already connected to other VM
--show-system-disks
List also system disks
--force-root
Force to run, even with root privileges
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,30 +0,0 @@
=========
qvm-check
=========
NAME
====
qvm-check - Specify no state options to check if VM exists
SYNOPSIS
========
| qvm-check [options] <vm-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-q, --quiet
Be quiet
--running
Determine if VM is running
--paused
Determine if VM is paused
--template
Determine if VM is a template
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,31 +0,0 @@
=========
qvm-clone
=========
NAME
====
qvm-clone - clones an existing VM by copying all its disk files
SYNOPSIS
========
| qvm-clone [options] <src-name> <new-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-q, --quiet
Be quiet
-p DIR_PATH, --path=DIR_PATH
Specify path to the template directory
--force-root
Force to run, even with root privileges
-P, --pool
Specify in to which storage pool to clone
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,35 +0,0 @@
======================
qvm-create-default-dvm
======================
NAME
====
qvm-create-default-dvm - creates a default disposable VM
SYNOPSIS
========
| qvm-create-default-dvm templatename|--default-template|--used-template [script-name|--default-script]
OPTIONS
=======
templatename
Base DispVM on given template. The command will create AppVM named after
template with "-dvm" suffix. This VM will be used to create DispVM
savefile. If you want to customize DispVM, use this VM - take a look at
https://wiki.qubes-os.org/wiki/UserDoc/DispVMCustomization
--default-template
Use default template for the DispVM
--used-template
Use the same template as earlier
--default-script
Use default script for seeding DispVM home.
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,53 +0,0 @@
==========
qvm-create
==========
NAME
====
qvm-create - creates a new VM
SYNOPSIS
========
| qvm-create [options] <vm-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-t TEMPLATE, --template=TEMPLATE
Specify the TemplateVM to use
-l LABEL, --label=LABEL
Specify the label to use for the new VM (e.g. red, yellow, green, ...)
-p, --proxy
Create ProxyVM
-n, --net
Create NetVM
-H, --hvm
Create HVM (standalone, unless --template option used)
--hvm-template
Create HVM template
-R ROOT_MOVE, --root-move-from=ROOT_MOVE
Use provided root.img instead of default/empty one
(file will be MOVED)
-r ROOT_COPY, --root-copy-from=ROOT_COPY
Use provided root.img instead of default/empty one
(file will be COPIED)
-s, --standalone
Create standalone VM - independent of template
-m MEM, --mem=MEM
Initial memory size (in MB)
-c VCPUS, --vcpus=VCPUS
VCPUs count
-i, --internal
Create VM for internal use only (hidden in qubes-manager, no appmenus)
--force-root
Force to run, even with root privileges
-q, --quiet
Be quiet
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,48 +0,0 @@
============
qvm-firewall
============
NAME
====
qvm-firewall - manage VM's firewall rules
SYNOPSIS
========
| qvm-firewall [-n] <vm-name> [action] [rule spec]
Rule specification can be one of:
1. address|hostname[/netmask] tcp|udp port[-port]
2. address|hostname[/netmask] tcp|udp service_name
3. address|hostname[/netmask] any
OPTIONS
=======
-h, --help
Show this help message and exit
-l, --list
List firewall settings (default action)
-a, --add
Add rule
-d, --del
Remove rule (given by number or by rule spec)
-P SET_POLICY, --policy=SET_POLICY
Set firewall policy (allow/deny)
-i SET_ICMP, --icmp=SET_ICMP
Set ICMP access (allow/deny)
-D SET_DNS, --dns=SET_DNS
Set DNS access (allow/deny)
-Y SET_YUM_PROXY, --yum-proxy=SET_YUM_PROXY
Set access to Qubes yum proxy (allow/deny).
*Note:* if set to "deny", access will be rejected even if policy set to "allow"
-r, --reload
Reload firewall (implied by any change action)
-n, --numeric
Display port numbers instead of services (makes sense only with --list)
--force-root
Force to run, even with root privileges
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,22 +0,0 @@
================
qvm-grow-private
================
NAME
====
qvm-grow-private - increase private storage capacity of a specified VM
SYNOPSIS
========
| qvm-grow-private <vm-name> <size>
OPTIONS
=======
-h, --help
Show this help message and exit
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,24 +0,0 @@
=============
qvm-grow-root
=============
NAME
====
qvm-grow-root - increase root storage capacity of a specified VM
SYNOPSIS
========
| qvm-grow-root <vm-name> <size>
OPTIONS
=======
-h, --help
Show this help message and exit
--allow-start
Allow VM to be started to complete the operation
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,23 +0,0 @@
========
qvm-kill
========
NAME
====
qvm-kill - kills the specified VM
SYNOPSIS
========
| qvm-kill [options] <vm-name>
OPTIONS
=======
-h, --help
Show this help message and exit
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,42 +0,0 @@
======
qvm-ls
======
NAME
====
qvm-ls - list VMs and various information about their state
SYNOPSIS
========
| qvm-ls [options] <vm-name>
OPTIONS
=======
-h, --help
Show help message and exit
-n, --network
Show network addresses assigned to VMs
-c, --cpu
Show CPU load
-m, --mem
Show memory usage
-d, --disk
Show VM disk utilization statistics
-i, --ids
Show Qubes and Xen id
-k, --kernel
Show VM kernel options
-b, --last-backup
Show date of last VM backup
--raw-list
List only VM names one per line
--raw-data
Display specify data of specified VMs. Intended for bash-parsing.
--list-fields
List field names valid for --raw-data
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,36 +0,0 @@
=======
qvm-pci
=======
NAME
====
qvm-pci - list/set VM PCI devices
SYNOPSIS
========
| qvm-pci -l [options] <vm-name>
| qvm-pci -a [options] <vm-name> <device>
| qvm-pci -d [options] <vm-name> <device>
OPTIONS
=======
-h, --help
Show this help message and exit
-l, --list
List VM PCI devices
-a, --add
Add a PCI device to specified VM
-C, --add-class
Add all devices of given class:
net - network interfaces,
usb - USB controllers
-d, --delete
Remove a PCI device from specified VM
--offline-mode
Offline mode
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,215 +0,0 @@
=========
qvm-prefs
=========
NAME
====
qvm-prefs - list/set various per-VM properties
SYNOPSIS
========
| qvm-prefs -l [options] <vm-name>
| qvm-prefs -g [options] <vm-name> <property>
| qvm-prefs -s [options] <vm-name> <property> [...]
OPTIONS
=======
-h, --help
Show this help message and exit
-l, --list
List properties of a specified VM
-g, --get
Get a single property of a specified VM
-s, --set
Set properties of a specified VM
--force-root
Force to run, even with root privileges
--offline-mode
Offline mode
PROPERTIES
==========
include_in_backups
Accepted values: ``True``, ``False``
Control whenever this VM will be included in backups by default (for now
works only in qubes-manager). You can always manually select or
deselect any VM for backup.
pcidevs
PCI devices assigned to the VM. Should be edited using qvm-pci tool.
pci_strictreset
Accepted values: ``True``, ``False``
Control whether prevent assigning to VM a device which does not support any
reset method. Generally such devices should not be assigned to any VM,
because there will be no way to reset device state after VM shutdown, so
the device could attack next VM to which it will be assigned. But in some
cases it could make sense - for example when the VM to which it is assigned
is trusted one, or is running all the time.
pci_e820_host
Accepted values: ``True``, ``False``
Give VM with PCI devices a memory map (e820) of the host. This is
required for some devices to properly resolve conflicts in address space.
This option is enabled by default for VMs with PCI devices and have no
effect for VMs without devices.
label
Accepted values: ``red``, ``orange``, ``yellow``, ``green``, ``gray``,
``blue``, ``purple``, ``black``
Color of VM label (icon, appmenus, windows border). If VM is running,
change will be applied at first VM restart.
netvm
Accepted values: netvm name, ``default``, ``none``
To which NetVM connect. Setting to ``default`` will follow system-global
default NetVM (managed by qubes-prefs). Setting to ``none`` will disable
networking in this VM.
dispvm_netvm
Accepted values: netvm name, ``default``, ``none``
Which NetVM should be used for Disposable VMs started by this one.
``default`` is to use the same NetVM as the VM itself.
maxmem
Accepted values: memory size in MB
Maximum memory size available for this VM. Dynamic memory management (aka
qmemman) will not be able to balloon over this limit. For VMs with
qmemman disabled, this will be overridden by *memory* property (at VM
startup).
memory
Accepted values: memory size in MB
Initial memory size for VM. This should be large enough to allow VM startup
- before qmemman starts managing memory for this VM. For VM with qmemman
disabled, this is static memory size.
kernel
Accepted values: kernel version, ``default``, ``none``
Kernel version to use (only for PV VMs). Available kernel versions will be
listed when no value given (there are in /var/lib/qubes/vm-kernels).
Setting to ``default`` will follow system-global default kernel (managed
via qubes-prefs). Setting to ``none`` will use "kernels" subdir in
VM directory - this allows having VM-specific kernel; also this the only
case when /lib/modules is writable from within VM.
template
Accepted values: TemplateVM name
TemplateVM on which VM base. It can be changed only when VM isn't running.
vcpus
Accepted values: no of CPUs
Number of CPU (cores) available to VM. Some VM types (eg DispVM) will not
work properly with more than one CPU.
kernelopts
Accepted values: string, ``default``
VM kernel parameters (available only for PV VMs). This can be used to
workaround some hardware specific problems (eg for NetVM). Setting to
``default`` will use some reasonable defaults (currently different for VMs
with PCI devices and without). For VM without PCI devices
``default`` option means inherit this value from the VM template (if any).
Some helpful options (for debugging purposes): ``earlyprintk=xen``,
``init=/bin/bash``
name
Accepted values: alphanumerical name
Name of the VM. Can be only changed when VM isn't running.
drive
Accepted values: [hd:\|cdrom:][backend-vm:]path
Additional drive for the VM (available only for HVMs). This can be used to
attach installation image. ``path`` can be file or physical device (eg.
/dev/sr0). The same syntax can be used in qvm-start --drive - to
attach drive only temporarily.
mac
Accepted values: MAC address, ``auto``
Can be used to force specific of virtual ethernet card in the VM. Setting
to ``auto`` will use automatic-generated MAC - based on VM id. Especially
useful when licensing requires a static MAC address.
For template-based HVM ``auto`` mode means to clone template MAC.
default_user
Accepted values: username
Default user used by qvm-run. Note that it make sense only on non-standard
template, as the standard one always have "user" account.
debug
Accepted values: ``on``, ``off``
Enables debug mode for VM. This can be used to turn on/off verbose logging
in many Qubes components at once (gui virtualization, VM kernel, some other
services).
For template-based HVM, enabling debug mode also disables automatic reset
root.img (actually volatile.img) before each VM startup, so changes made to
root filesystem stays intact. To force reset root.img when debug mode
enabled, either change something in the template (simple start+stop will
do, even touch its root.img is enough), or remove VM's volatile.img
(check the path with qvm-prefs).
qrexec_installed
Accepted values: ``True``, ``False``
This HVM have qrexec agent installed. When VM have qrexec agent installed,
one can use qvm-run to start VM process, VM will benefit from Qubes RPC
services (like file copy, or inter-vm clipboard). This option will be
automatically turned on during Qubes Windows Tools installation, but if you
install qrexec agent in some other OS, you need to turn this option on
manually.
guiagent_installed
Accepted values: ``True``, ``False``
This HVM have gui agent installed. This option disables full screen GUI
virtualization and enables per-window seemless GUI mode. This option will
be automatically turned on during Qubes Windows Tools installation, but if
you install Qubes gui agent in some other OS, you need to turn this option
on manually. You can turn this option off to troubleshoot some early HVM OS
boot problems (enter safe mode etc), but the option will be automatically
enabled at first VM normal startup (and will take effect from the next
startup).
*Notice:* when Windows GUI agent is installed in the VM, SVGA device (used
to full screen video) is disabled, so even if you disable this
option, you will not get functional full desktop access (on normal VM
startup). Use some other means for that (VNC, RDP or so).
autostart
Accepted values: ``True``, ``False``
Start the VM during system startup. The default netvm is autostarted
regardless of this setting.
timezone
Accepted values: ``localtime``, time offset in seconds
Set emulated HVM clock timezone. Use ``localtime`` (the default) to use the
same time as dom0 have. Note that HVM will get only clock value, not the
timezone itself, so if you use ``localtime`` setting, OS inside of HVM
should also be configured to treat hardware clock as local time (and have
proper timezone set).
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,28 +0,0 @@
==========
qvm-remove
==========
NAME
====
qvm-remove - remove a VM
SYNOPSIS
========
| qvm-remove [options] <vm-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-q, --quiet
Be quiet
--just-db
Remove only from qubes.xml; do not remove any files
--force-root
Force to run, even with root privileges
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,24 +0,0 @@
===========================
qvm-revert-template-changes
===========================
NAME
====
qvm-revert-template-changes
SYNOPSIS
========
| qvm-revert-template-changes [options] <template-name>
OPTIONS
=======
-h, --help
Show this help message and exit
--force
Do not prompt for confirmation
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,62 +0,0 @@
=======
qvm-run
=======
NAME
====
qvm-run - run a command on a specified VM
SYNOPSIS
========
| qvm-run [options] [<vm-name>] [<cmd>]
OPTIONS
=======
-h, --help
Show this help message and exit
-q, --quiet
Be quiet
-a, --auto
Auto start the VM if not running
-u USER, --user=USER
Run command in a VM as a specified user
--tray
Use tray notifications instead of stdout
--all
Run command on all currently running VMs (or all paused, in case of --unpause)
--exclude=EXCLUDE_LIST
When --all is used: exclude this VM name (might be repeated)
--wait
Wait for the VM(s) to shutdown
--shutdown
(deprecated) Do 'xl shutdown' for the VM(s) (can be combined this with --all and --wait)
--pause
Do 'xl pause' for the VM(s) (can be combined this with --all and --wait)
--unpause
Do 'xl unpause' for the VM(s) (can be combined this with --all and --wait)
-p, --pass-io
Pass stdin/stdout/stderr from remote program
--localcmd=LOCALCMD
With --pass-io, pass stdin/stdout/stderr to the given program
--nogui
Run command without gui
--filter-escape-chars
Filter terminal escape sequences (default if output is terminal)
--no-filter-escape-chars
Do not filter terminal escape sequences - overrides --filter-escape-chars, DANGEROUS when output is terminal
--no-color-output
Disable marking VM output with red color
--no-color-stderr
Disable marking VM stderr with red color
--color-output
Force marking VM output with given ANSI style (use 31 for red)
--color-stderr
Force marking VM stderr with given ANSI style (use 31 for red)
--force
Force operation, even if may damage other VMs (eg. shutdown of NetVM)
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,133 +0,0 @@
===========
qvm-service
===========
NAME
====
qvm-service - manage (Qubes-specific) services started in VM
SYNOPSIS
========
| qvm-service [-l] <vmname>
| qvm-service [-e|-d|-D] <vmname> <service>
OPTIONS
=======
-h, --help
Show this help message and exit
-l, --list
List services (default action)
-e, --enable
Enable service
-d, --disable
Disable service
-D, --default
Reset service to its default state (remove from the list). Default state
means "lets VM choose" and can depend on VM type (NetVM, AppVM etc).
SUPPORTED SERVICES
==================
This list can be incomplete as VM can implement any additional service without knowledge of qubes-core code.
meminfo-writer
Default: enabled everywhere excluding NetVM
This service reports VM memory usage to dom0, which effectively enables dynamic memory management for the VM.
*Note:* this service is enforced to be set by dom0 code. If you try to
remove it (reset to default state), will be recreated with the rule: enabled
if VM have no PCI devices assigned, otherwise disabled.
qubes-dvm
Default: disabled
Used internally when creating DispVM savefile.
qubes-firewall
Default: enabled only in ProxyVM
Dynamic firewall manager, based on settings in dom0 (qvm-firewall, firewall tab in qubes-manager).
This service is not supported in netvms.
qubes-network
Default: enabled only in NetVM and ProxyVM
Expose network for other VMs. This includes enabling network forwarding, MASQUERADE, DNS redirection and basic firewall.
qubes-netwatcher
Default: enabled only in ProxyVM
Monitor IP change notification from NetVM. When received, reload qubes-firewall service (to force DNS resolution).
This service makes sense only with qubes-firewall enabled.
qubes-update-check
Default: enabled
Notify dom0 about updates available for this VM. This is shown in qubes-manager as 'update-pending' flag.
cups
Default: enabled only in AppVM
Enable CUPS service. The user can disable cups in VM which do not need printing to speed up booting.
crond
Default: disabled
Enable CRON service.
network-manager
Default: enabled in NetVM
Enable NetworkManager. Only VM with direct access to network device needs
this service, but can be useful in ProxyVM to ease VPN setup.
ntpd
Default: disabled
Enable NTPD service. By default Qubes calls ntpdate every 6 minutes in
selected VM (aka ClockVM), then propagate the result using qrexec calls.
Enabling ntpd *do not* disable this behaviour.
qubes-yum-proxy
Deprecated name for qubes-updates-proxy.
qubes-updates-proxy
Default: enabled in NetVM
Provide proxy service, which allow access only to yum repos. Filtering is
done based on URLs, so it shouldn't be used as leak control (pretty easy to
bypass), but is enough to prevent some erroneous user actions.
yum-proxy-setup
Deprecated name for updates-proxy-setup.
updates-proxy-setup
Default: enabled in AppVM (also in templates)
Setup yum at startup to use qubes-yum-proxy service.
*Note:* this service is automatically enabled when you allow VM to access
yum proxy (in firewall settings) and disabled when you deny access to yum
proxy.
disable-default-route
Default: disabled
Disables the default route for networking. Enabling this service
will prevent the creation of the default route, but the VM will
still be able to reach it's direct neighbors. The functionality
is implemented in /usr/lib/qubes/setup-ip.
disable-dns-server
Default: disabled
Enabling this service will result in an empty /etc/resolv.conf.
The functionality is implemented in /usr/lib/qubes/setup-ip.
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,36 +0,0 @@
============
qvm-shutdown
============
NAME
====
qvm-shutdown
:Date: 2012-04-11
SYNOPSIS
========
| qvm-shutdown [options] <vm-name> [vm-name ...]
OPTIONS
=======
-h, --help
Show this help message and exit
-q, --quiet
Be quiet
--force
Force operation, even if may damage other VMs (eg. shutdown of NetVM)
--wait
Wait for the VM(s) to shutdown
--wait-time
Timeout after which VM will be killed when --wait is used
--all
Shutdown all running VMs
--exclude=EXCLUDE_LIST
When --all is used: exclude this VM name (might be repeated)
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

View File

@ -1,44 +0,0 @@
=========
qvm-start
=========
NAME
====
qvm-start - start a specified VM
SYNOPSIS
========
| qvm-start [options] <vm-name>
OPTIONS
=======
-h, --help
Show this help message and exit
-q, --quiet
Be quiet
--tray
Use tray notifications instead of stdout
--no-guid
Do not start the GUId (ignored)
--drive
Temporarily attach specified drive as CD/DVD or hard disk (can be specified with prefix 'hd' or 'cdrom:', default is cdrom)
--hddisk
Temporarily attach specified drive as hard disk
--cdrom
Temporarily attach specified drive as CD/DVD
--install-windows-tools
Attach Windows tools CDROM to the VM
--dvm
Do actions necessary when preparing DVM image
--custom-config=CUSTOM_CONFIG
Use custom Xen config instead of Qubes-generated one
--skip-if-running
Do no fail if the VM is already running
--debug
Enable debug mode for this VM (until its shutdown)
AUTHORS
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>

Some files were not shown because too many files have changed in this diff Show More