diff --git a/doc/manpages/qvm-create.rst b/doc/manpages/qvm-create.rst index 2b09a41e..0e7de1bd 100644 --- a/doc/manpages/qvm-create.rst +++ b/doc/manpages/qvm-create.rst @@ -1,80 +1,62 @@ .. program:: qvm-create -========================================= -:program:`qvm-create` -- Creates a new VM -========================================= +:program:`qvm-create` -- create new domain +========================================== Synopsis -======== -:command:`qvm-create` [*options*] <*vm-name*> +-------- + +:command:`qvm-create` skel-manpage.py [-h] [--xml *XMLFILE*] [--force-root] [--class *CLS*] [--property *NAME*=*VALUE*] [--template *VALUE*] [--label *VALUE*] [--root-copy-from *FILENAME* | --root-move-from *FILENAME*] *VMNAME* Options -======= +------- .. option:: --help, -h - Show this help message and exit + show help message and exit -.. option:: --template=TEMPLATE, -t TEMPLATE +.. option:: --xml=XMLFILE - Specify the TemplateVM to use - -.. option:: --label=LABEL, -l LABEL - - Specify the label to use for the new VM (e.g. red, yellow, green, ...) - -.. option:: --proxy, -p - - Create ProxyVM - -.. option:: --net, -n - - Create NetVM - -.. option:: --hvm, -H - - Create HVM (standalone, unless :option:`--template` option used) - -.. option:: --hvm-template - - Create HVM template - -.. option:: --root-move-from=ROOT_MOVE, -R ROOT_MOVE - - Use provided root.img instead of default/empty one - (file will be *moved*) - -.. option:: --root-copy-from=ROOT_COPY, -r ROOT_COPY - - Use provided root.img instead of default/empty one - (file will be *copied*) - -.. option:: --standalone, -s - - Create standalone VM --- independent of template - -.. option:: --mem=MEM, -m MEM - - Initial memory size (in MB) - -.. option:: --vcpus=VCPUS, -c VCPUS - - VCPUs count - -.. option:: --internal, -i - - Create VM for internal use only (hidden in qubes-manager, no appmenus) + Qubes OS store file .. option:: --force-root - Force to run, even with root privileges + Force to run as root. -.. option:: --quiet, -q +.. option:: --class, -C + + The class of the new domain (default: AppVM). + +.. option:: --prop=NAME=VALUE, --property=NAME=VALUE, -p NAME=VALUE + + Set domain's property, like "internal", "memory" or "vcpus". Any property may + be set this way, even "qid". + +.. option:: --template=VALUE, -t VALUE + + Specify the TemplateVM to use, when applicable. This is an alias for + ``--property template=VALUE``. + +.. option:: --label=VALUE, -l VALUE + + Specify the label to use for the new domain (e.g. red, yellow, green, ...). + This in an alias for ``--property label=VALUE``. + +.. option:: --root-copy-from=FILENAME, -r FILENAME + + Use provided root.img instead of default/empty one (file will be *copied*). + +.. option:: --root-move-from=FILENAME, -R FILENAME + + use provided root.img instead of default/empty one (file will be *moved*). - Be quiet Authors -======= +------- + | Joanna Rutkowska | Rafal Wojtczuk | Marek Marczykowski +| Wojtek Porczyk + +.. vim: ts=3 sw=3 et tw=80 diff --git a/qubes/Makefile b/qubes/Makefile index 59f48a1f..65365a65 100644 --- a/qubes/Makefile +++ b/qubes/Makefile @@ -54,6 +54,7 @@ endif cp \ tools/__init__.py* \ tools/qubes_create.py* \ + tools/qvm_create.py* \ tools/qvm_ls.py* \ $(DESTDIR)$(PYTHON_QUBESPATH)/tools diff --git a/qubes/__init__.py b/qubes/__init__.py index 060242ec..84289966 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -439,9 +439,6 @@ class VMCollection(object): raise TypeError('{} holds only BaseVM instances'.format( self.__class__.__name__)) - if not hasattr(value, 'qid'): - value.qid = self.get_new_unused_qid() - if value.qid in self: raise ValueError('This collection already holds VM that has ' 'qid={!r} ({!r})'.format(value.qid, self[value.qid])) @@ -453,6 +450,8 @@ class VMCollection(object): value.events_enabled = True self.app.fire_event('domain-added', value) + return value + def __getitem__(self, key): if isinstance(key, int): @@ -1296,12 +1295,41 @@ class Qubes(PropertyHolder): return labels - def add_new_vm(self, vm): + def add_new_vm(self, cls, qid=None, **kwargs): '''Add new Virtual Machine to colletion ''' - self.domains.add(vm) + if qid is None: + qid = self.get_new_unused_qid() + + return self.domains.add(cls(self, None, qid=qid, **kwargs)) + + + def get_label(self, label): + '''Get label as identified by index or name + + :throws KeyError: when label is not found + ''' + + # first search for index, verbatim + try: + return self.labels[label] + except KeyError: + pass + + # then search for name + for l in self.labels.values(): + if l.name == label: + return label + + # last call, if label is a number represented as str, search in indices + try: + return self.labels[int(label)] + except (KeyError, ValueError): + pass + + raise KeyError(label) @qubes.events.handler('domain-pre-deleted') diff --git a/qubes/tools/qvm_create.py b/qubes/tools/qvm_create.py new file mode 100644 index 00000000..61851822 --- /dev/null +++ b/qubes/tools/qvm_create.py @@ -0,0 +1,150 @@ +#!/usr/bin/python2 +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010-2015 Joanna Rutkowska +# Copyright (C) 2015 Wojtek Porczyk +# +# 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. +# + +# TODO list available classes +# TODO list labels (maybe in qvm-prefs) +# TODO services, devices, tags + +from __future__ import print_function + +import argparse +import os +import subprocess +import sys + +import qubes +import qubes.tools + + +parser = qubes.tools.get_parser_base(want_force_root=True) + +parser.add_argument('--class', '-C', dest='cls', + default='AppVM', + help='specify the class of the new domain (default: %(default)s)') + +parser.add_argument('--property', '--prop', '-p', + action=qubes.tools.PropertyAction, + help='set domain\'s property, like "internal", "memory" or "vcpus"') + +parser.add_argument('--template', '-t', + action=qubes.tools.SinglePropertyAction, + help='specify the TemplateVM to use') + +parser.add_argument('--label', '-l', + action=qubes.tools.SinglePropertyAction, + help='specify the label to use for the new domain' + ' (e.g. red, yellow, green, ...)') + +parser_root = parser.add_mutually_exclusive_group() +parser_root.add_argument('--root-copy-from', '-r', metavar='FILENAME', + help='use provided root.img instead of default/empty one' + ' (file will be COPIED)') +parser_root.add_argument('--root-move-from', '-R', metavar='FILENAME', + help='use provided root.img instead of default/empty one' + ' (file will be MOVED)') + +parser.add_argument('name', metavar='VMNAME', + action=qubes.tools.SinglePropertyAction, + nargs='?', + help='name of the domain to create') + +#parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) + +def main(): + args = parser.parse_args() + qubes.tools.dont_run_as_root(parser, args) + + if 'label' not in args.properties: + parser.error('--label option is mandatory') + + if 'name' not in args.properties: + parser.error('VMNAME is mandatory') + + app = qubes.Qubes(args.xml) + try: + label = app.get_label(args.properties['label']) + except KeyError: + parser.error('no such label: {!r}; available: {}'.format(args.label, + ', '.join(repr(l.name) for l in app.labels))) + + try: + cls = qubes.vm.BaseVM.register[args.cls] + except KeyError: + parser.error('no such domain class: {!r}'.format(args.cls)) + + if 'template' in args.properties \ + and 'template' not in cls.list_properties(): + parser.error('this domain class does not support template') + + vm = app.add_new_vm(cls, **args.properties) + +# if not options.standalone and any([options.root_copy_from, options.root_move_from]): +# print >> sys.stderr, "root.img can be specified only for standalone VMs" +# exit (1) + +# if options.hvm_template and options.template is not None: +# print >> sys.stderr, "Template VM cannot be based on another template" +# exit (1) + +# if options.root_copy_from is not None and not os.path.exists(options.root_copy_from): +# print >> sys.stderr, "File specified as root.img does not exists" +# exit (1) + +# if options.root_move_from is not None and not os.path.exists(options.root_move_from): +# print >> sys.stderr, "File specified as root.img does not exists" +# exit (1) + +# elif not options.hvm and not options.hvm_template: +# if qvm_collection.get_default_template() is None: +# print >> sys.stderr, "No default TemplateVM defined!" +# exit (1) +# else: +# template = qvm_collection.get_default_template() +# if (options.verbose): +# print('--> Using default TemplateVM: {0}'.format(template.name)) + + try: + vm.create_on_disk() + + if args.root_move_from is not None: +# if (options.verbose): +# print "--> Replacing root.img with provided file" + os.unlink(vm.root_img) + os.rename(options.root_move_from, vm.root_img) + elif args.root_copy_from is not None: +# if (options.verbose): +# print "--> Replacing root.img with provided file" + os.unlink(vm.root_img) + # use 'cp' to preserve sparse file + subprocess.check_call(['cp', options.root_copy_from, vm.root_img]) + + except (IOError, OSError) as err: + parser.error(str(err)) + + app.save() + + return True + + +if __name__ == '__main__': + sys.exit(not main()) diff --git a/qvm-tools/qvm-create b/qvm-tools/qvm-create index 1e2ac66a..265c62d1 100755 --- a/qvm-tools/qvm-create +++ b/qvm-tools/qvm-create @@ -1,205 +1,7 @@ -#!/usr/bin/python2 -# -*- encoding: utf8 -*- -# -# The Qubes OS Project, http://www.qubes-os.org -# -# Copyright (C) 2010 Joanna Rutkowska -# -# 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. -# -# +#!/usr/bin/python2 -O +# vim: fileencoding=utf-8 -from qubes.qubes import QubesVmCollection -from qubes.qubes import QubesVmLabels -from qubes.qubes import QubesException -from optparse import OptionParser; -import subprocess -import re -import os import sys +import qubes.tools.qvm_create -def main(): - usage = "usage: %prog [options] " - parser = OptionParser (usage) - parser.add_option ("-t", "--template", dest="template", - help="Specify the TemplateVM to use") - parser.add_option ("-l", "--label", dest="label", - help="Specify the label to use for the new VM (e.g. red, yellow, green, ...)") - parser.add_option ("-p", "--proxy", action="store_true", dest="proxyvm", default=False, - help="Create ProxyVM") - parser.add_option ("-H", "--hvm", action="store_true", dest="hvm", default=False, - help="Create HVM (standalone unless --template option used)") - parser.add_option ("--hvm-template", action="store_true", dest="hvm_template", default=False, - help="Create HVM template") - parser.add_option ("-n", "--net", action="store_true", dest="netvm", default=False, - help="Create NetVM") - parser.add_option ("-s", "--standalone", action="store_true", dest="standalone", default=False, - help="Create standalone VM - independent of template ") - parser.add_option ("-R", "--root-move-from", dest="root_move", default=None, - help="Use provided root.img instead of default/empty one (file will be MOVED)") - parser.add_option ("-r", "--root-copy-from", dest="root_copy", default=None, - help="Use provided root.img instead of default/empty one (file will be COPIED)") - parser.add_option ("-m", "--mem", dest="mem", default=None, - help="Initial memory size (in MB)") - parser.add_option ("-c", "--vcpus", dest="vcpus", default=None, - help="VCPUs count") - parser.add_option ("-i", "--internal", action="store_true", dest="internal", default=False, - help="Create VM for internal use only (hidden in qubes-manager, no appmenus)") - parser.add_option ("--force-root", action="store_true", dest="force_root", default=False, - help="Force to run, even with root privileges") - - parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) - (options, args) = parser.parse_args () - if (len (args) != 1): - parser.error ("You must specify VM name!") - vmname = args[0] - - if (options.netvm + options.proxyvm + options.hvm + options.hvm_template) > 1: - parser.error ("You must specify at most one VM type switch") - - if hasattr(os, "geteuid") and os.geteuid() == 0: - print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems." - if options.force_root: - print >> sys.stderr, "Continuing as commanded. You have been warned." - else: - print >> sys.stderr, "Retry as unprivileged user." - print >> sys.stderr, "... or use --force-root to continue anyway." - exit(1) - - if options.label is None: - print >> sys.stderr, "You must choose a label for the new VM by passing the --label option." - print >> sys.stderr, "Possible values are:" - for l in QubesVmLabels.values(): - print >> sys.stderr, "* {0}".format(l.name) - exit (1) - - if options.label not in QubesVmLabels: - print >> sys.stderr, "Wrong label name, supported values are the following:" - for l in QubesVmLabels.values(): - print >> sys.stderr, "* {0}".format(l.name) - exit (1) - label = QubesVmLabels[options.label] - - if options.hvm and not options.template: - options.standalone = True - - if options.hvm_template: - options.standalone = True - - if not options.standalone and any([options.root_copy, options.root_move]): - print >> sys.stderr, "root.img can be specified only for standalone VMs" - exit (1) - - if options.hvm_template and options.template is not None: - print >> sys.stderr, "Template VM cannot be based on another template" - exit (1) - - if options.root_copy and options.root_move: - print >> sys.stderr, "Only one of --root-move-from and --root-copy from can be specified" - exit(1) - - if options.root_copy is not None and not os.path.exists(options.root_copy): - print >> sys.stderr, "File specified as root.img does not exists" - exit (1) - - if options.root_move is not None and not os.path.exists(options.root_move): - print >> sys.stderr, "File specified as root.img does not exists" - exit (1) - - qvm_collection = QubesVmCollection() - qvm_collection.lock_db_for_writing() - qvm_collection.load() - - if qvm_collection.get_vm_by_name(vmname) is not None: - print >> sys.stderr, "A VM with the name '{0}' already exists in the system.".format(vmname) - exit(1) - - template = None - if options.template is not None: - template = qvm_collection.get_vm_by_name(options.template) - if template is None: - print >> sys.stderr, "There is no (Template)VM with the name '{0}'".format(options.template) - exit (1) - if not template.is_template(): - print >> sys.stderr, "VM '{0}' is not a TemplateVM".format(options.template) - exit (1) - if (options.verbose): - print "--> Using TemplateVM: {0}".format(template.name) - - elif not options.hvm and not options.hvm_template: - if qvm_collection.get_default_template() is None: - print >> sys.stderr, "No default TemplateVM defined!" - exit (1) - else: - template = qvm_collection.get_default_template() - if (options.verbose): - print "--> Using default TemplateVM: {0}".format(template.name) - - if options.standalone: - new_vm_template = None - else: - new_vm_template = template - - vm = None - if options.netvm: - vmtype = "QubesNetVm" - elif options.proxyvm: - vmtype = "QubesProxyVm" - elif options.hvm: - vmtype = "QubesHVm" - elif options.hvm_template: - vmtype = "QubesTemplateHVm" - else: - vmtype = "QubesAppVm" - - try: - vm = qvm_collection.add_new_vm(vmtype, name=vmname, template=new_vm_template, label = label) - except QubesException as err: - print >> sys.stderr, "ERROR: {0}".format(err) - exit (1) - - if options.internal: - vm.internal = True - - if options.mem is not None: - vm.memory = options.mem - - if options.vcpus is not None: - vm.vcpus = options.vcpus - - try: - vm.create_on_disk(verbose=options.verbose, source_template=template) - if options.root_move: - if (options.verbose): - print "--> Replacing root.img with provided file" - os.unlink(vm.root_img) - os.rename(options.root_move, vm.root_img) - elif options.root_copy: - if (options.verbose): - print "--> Replacing root.img with provided file" - os.unlink(vm.root_img) - # use "cp" to preserve sparse file - subprocess.check_call(["cp", options.root_copy, vm.root_img]) - - except (IOError, OSError) as err: - print >> sys.stderr, "ERROR: {0}".format(err) - exit (1) - - - qvm_collection.save() - qvm_collection.unlock_db() - - -main() +sys.exit(not qubes.tools.qvm_create.main()) diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 69f96c27..0f3b3bf7 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -217,6 +217,7 @@ fi %dir %{python_sitearch}/qubes/tools %{python_sitearch}/qubes/tools/__init__.py* %{python_sitearch}/qubes/tools/qubes_create.py* +%{python_sitearch}/qubes/tools/qvm_create.py* %{python_sitearch}/qubes/tools/qvm_ls.py* %dir %{python_sitearch}/qubes/ext