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:
commit
eaf5c27b27
12
.coveragerc
Normal file
12
.coveragerc
Normal 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
4
.gitignore
vendored
@ -1,5 +1,9 @@
|
|||||||
rpm/
|
rpm/
|
||||||
pkgs/
|
pkgs/
|
||||||
|
qubes.egg-info/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*~
|
*~
|
||||||
|
267
.pylintrc
Normal file
267
.pylintrc
Normal 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
|
||||||
|
|
30
.travis.yml
30
.travis.yml
@ -1,7 +1,29 @@
|
|||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
language: generic
|
language: python
|
||||||
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
|
python:
|
||||||
script: ~/qubes-builder/scripts/travis-build
|
- '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:
|
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
|
||||||
|
43
Makefile
43
Makefile
@ -5,6 +5,7 @@ VERSION := $(shell cat version)
|
|||||||
DIST_DOM0 ?= fc18
|
DIST_DOM0 ?= fc18
|
||||||
|
|
||||||
OS ?= Linux
|
OS ?= Linux
|
||||||
|
PYTHON ?= python3
|
||||||
|
|
||||||
ifeq ($(OS),Linux)
|
ifeq ($(OS),Linux)
|
||||||
DATADIR ?= /var/lib/qubes
|
DATADIR ?= /var/lib/qubes
|
||||||
@ -39,19 +40,10 @@ rpms-dom0:
|
|||||||
$(RPMS_DIR)/x86_64/qubes-core-dom0-$(VERSION)*.rpm \
|
$(RPMS_DIR)/x86_64/qubes-core-dom0-$(VERSION)*.rpm \
|
||||||
$(RPMS_DIR)/noarch/qubes-core-dom0-doc-$(VERSION)*rpm
|
$(RPMS_DIR)/noarch/qubes-core-dom0-doc-$(VERSION)*rpm
|
||||||
|
|
||||||
clean:
|
|
||||||
make -C dispvm clean
|
|
||||||
make -C qmemman clean
|
|
||||||
|
|
||||||
all:
|
all:
|
||||||
make all -C core
|
$(PYTHON) setup.py build
|
||||||
make all -C core-modules
|
# make all -C tests
|
||||||
make all -C tests
|
|
||||||
# Currently supported only on xen
|
# Currently supported only on xen
|
||||||
ifeq ($(BACKEND_VMM),xen)
|
|
||||||
make all -C qmemman
|
|
||||||
make all -C dispvm
|
|
||||||
endif
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
ifeq ($(OS),Linux)
|
ifeq ($(OS),Linux)
|
||||||
@ -59,17 +51,19 @@ ifeq ($(OS),Linux)
|
|||||||
$(MAKE) install -C linux/aux-tools
|
$(MAKE) install -C linux/aux-tools
|
||||||
$(MAKE) install -C linux/system-config
|
$(MAKE) install -C linux/system-config
|
||||||
endif
|
endif
|
||||||
$(MAKE) install -C qvm-tools
|
$(PYTHON) setup.py install -O1 --skip-build --root $(DESTDIR)
|
||||||
$(MAKE) install -C core
|
ln -s qvm-device $(DESTDIR)/usr/bin/qvm-pci
|
||||||
$(MAKE) install -C core-modules
|
ln -s qvm-device $(DESTDIR)/usr/bin/qvm-usb
|
||||||
$(MAKE) install -C tests
|
# $(MAKE) install -C tests
|
||||||
|
$(MAKE) install -C relaxng
|
||||||
|
mkdir -p $(DESTDIR)/etc/qubes
|
||||||
ifeq ($(BACKEND_VMM),xen)
|
ifeq ($(BACKEND_VMM),xen)
|
||||||
# Currently supported only on xen
|
# Currently supported only on xen
|
||||||
$(MAKE) install -C qmemman
|
cp etc/qmemman.conf $(DESTDIR)/etc/qubes/
|
||||||
endif
|
endif
|
||||||
$(MAKE) install -C dispvm
|
|
||||||
mkdir -p $(DESTDIR)/etc/qubes-rpc/policy
|
mkdir -p $(DESTDIR)/etc/qubes-rpc/policy
|
||||||
mkdir -p $(DESTDIR)/usr/libexec/qubes
|
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.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.OpenInVM.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenInVM
|
||||||
cp qubes-rpc-policy/qubes.OpenURL.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenURL
|
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.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.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-policy/qubes.GetRandomizedTime.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetRandomizedTime
|
||||||
cp qubes-rpc/qubes.NotifyUpdates $(DESTDIR)/etc/qubes-rpc/
|
cp qubes-rpc-policy/qubes.NotifyTools.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyTools
|
||||||
cp qubes-rpc/qubes.NotifyTools $(DESTDIR)/etc/qubes-rpc/
|
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.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-updates $(DESTDIR)/usr/libexec/qubes/
|
||||||
cp qubes-rpc/qubes-notify-tools $(DESTDIR)/usr/libexec/qubes/
|
cp qubes-rpc/qubes-notify-tools $(DESTDIR)/usr/libexec/qubes/
|
||||||
|
|
||||||
mkdir -p "$(DESTDIR)$(FILESDIR)"
|
mkdir -p "$(DESTDIR)$(FILESDIR)"
|
||||||
cp vm-config/$(BACKEND_VMM)-vm-template.xml "$(DESTDIR)$(FILESDIR)/vm-template.xml"
|
cp -r templates "$(DESTDIR)$(FILESDIR)/templates"
|
||||||
cp vm-config/$(BACKEND_VMM)-vm-template-hvm.xml "$(DESTDIR)$(FILESDIR)/vm-template-hvm.xml"
|
rm -f "$(DESTDIR)$(FILESDIR)/templates/README"
|
||||||
|
|
||||||
mkdir -p $(DESTDIR)$(DATADIR)
|
mkdir -p $(DESTDIR)$(DATADIR)
|
||||||
mkdir -p $(DESTDIR)$(DATADIR)/vm-templates
|
mkdir -p $(DESTDIR)$(DATADIR)/vm-templates
|
||||||
mkdir -p $(DESTDIR)$(DATADIR)/appvms
|
mkdir -p $(DESTDIR)$(DATADIR)/appvms
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
ifeq ($(PACKAGE_SET),dom0)
|
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_SOURCE_SUBDIRS := .
|
||||||
WIN_COMPILER := mingw
|
WIN_COMPILER := mingw
|
||||||
WIN_PACKAGE_CMD := make msi
|
WIN_PACKAGE_CMD := make msi
|
||||||
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Qubes core, version 3
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
[](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
3
ci/coveragerc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[run]
|
||||||
|
source = qubes
|
||||||
|
omit = qubes/tests/*
|
27
ci/lvm-manage
Executable file
27
ci/lvm-manage
Executable 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
199
ci/pylintrc
Normal 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
9
ci/requirements.txt
Normal 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
147
contrib/check-events
Executable 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
180
contrib/import-graph
Executable 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
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
1
core/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
*.pyo
|
|
@ -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
|
|
2325
core/backup.py
2325
core/backup.py
File diff suppressed because it is too large
Load Diff
@ -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
|
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
../core-modules
|
|
@ -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
|
|
||||||
|
|
965
core/qubes.py
965
core/qubes.py
@ -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:
|
|
@ -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:
|
|
@ -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/'}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
@ -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
2
dispvm/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
qubes_restore
|
|
||||||
xenstore-watch
|
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
@ -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()
|
|
@ -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."
|
|
@ -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
|
|
@ -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
|
|
@ -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
4
doc/.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
*.gz
|
_build
|
||||||
|
sandbox.rst
|
||||||
|
autoxml.rst
|
||||||
|
189
doc/Makefile
189
doc/Makefile
@ -1,30 +1,169 @@
|
|||||||
QVM_DIR=qvm-tools
|
# Makefile for Sphinx documentation
|
||||||
QUBES_DIR=qubes-tools
|
#
|
||||||
PANDOC=pandoc -s -f rst -t man
|
|
||||||
|
|
||||||
QVM_DOCS=$(patsubst %.rst,%.1.gz,$(wildcard $(QVM_DIR)/*.rst))
|
# You can set these variables from the command line.
|
||||||
QUBES_DOCS=$(patsubst %.rst,%.1.gz,$(wildcard $(QUBES_DIR)/*.rst))
|
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:
|
help:
|
||||||
@echo "make rst=example.rst preview -- generate manpage preview from example.rst"
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
@echo "make manpages -- generate manpages"
|
@echo " html to make standalone HTML files"
|
||||||
@echo "make install -- generate manpages and copy them to /usr/share/man"
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
install: manpages
|
@echo " pickle to make pickle files"
|
||||||
mkdir -p $(DESTDIR)/usr/share/man/man1
|
@echo " json to make JSON files"
|
||||||
cp $(QVM_DOCS) $(DESTDIR)/usr/share/man/man1/
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
cp $(QUBES_DOCS) $(DESTDIR)/usr/share/man/man1/
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
%.1: %.rst
|
@echo " epub to make an epub"
|
||||||
$(PANDOC) $< > $@
|
@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"
|
||||||
%.1.gz: %.1
|
@echo " text to make text files"
|
||||||
gzip -f $<
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
manpages: $(QVM_DOCS) $(QUBES_DOCS) $(VM_DOCS)
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
preview: $(rst)
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
$(PANDOC) $(rst) | groff -mandoc -Tlatin1 | less -R
|
@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:
|
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/
|
||||||
|
@ -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
9
doc/_static/qubes.css
vendored
Normal 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
4
doc/_templates/layout.html
vendored
Normal 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
279
doc/conf.py
Normal 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
45
doc/example.xml
Normal 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
43
doc/index.rst
Normal 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
114
doc/libvirt.rst
Normal 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
10
doc/manpages/index.rst
Normal 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:
|
||||||
|
|
||||||
|
*
|
38
doc/manpages/qubes-create.rst
Normal file
38
doc/manpages/qubes-create.rst
Normal 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
|
42
doc/manpages/qubesd-query.rst
Normal file
42
doc/manpages/qubesd-query.rst
Normal 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
8
doc/qubes-backup.rst
Normal 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
8
doc/qubes-dochelpers.rst
Normal 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
153
doc/qubes-events.rst
Normal 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
63
doc/qubes-exc.rst
Normal 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
8
doc/qubes-ext.rst
Normal 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
8
doc/qubes-log.rst
Normal 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
87
doc/qubes-policy.rst
Normal 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
144
doc/qubes-tests.rst
Normal 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
32
doc/qubes-tools/index.rst
Normal 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
|
8
doc/qubes-tools/qmemmand.rst
Normal file
8
doc/qubes-tools/qmemmand.rst
Normal 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
|
@ -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>
|
|
@ -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
8
doc/qubes-vm/adminvm.rst
Normal 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
8
doc/qubes-vm/appvm.rst
Normal 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
8
doc/qubes-vm/dispvm.rst
Normal 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
84
doc/qubes-vm/index.rst
Normal 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
8
doc/qubes-vm/qubesvm.rst
Normal 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
|
8
doc/qubes-vm/templatevm.rst
Normal file
8
doc/qubes-vm/templatevm.rst
Normal 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
220
doc/qubes.rst
Normal 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
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
Loading…
Reference in New Issue
Block a user