Переглянути джерело

Merge branch 'devel-6'

* devel-6:
  qvm-ls: fix total VM size reporting
  doc: update manpage of qvm-service
  tools: qvm-service tool
  tests: too much copy&paste
  features: serialize True as '1'
  tools/qvm-start-gui: add --force-stubdomain options
  tools/qvm-shutdown: fix help message
  tools/qvm-shutdown: drop --force option, it isn't supported anymore
Marek Marczykowski-Górecki 7 роки тому
батько
коміт
f48321dd98

+ 16 - 21
doc/manpages/qvm-service.rst

@@ -8,6 +8,7 @@ Synopsis
 ========
 ========
 | :command:`qvm-service` [-l] <*vmname*>
 | :command:`qvm-service` [-l] <*vmname*>
 | :command:`qvm-service` [-e|-d|-D] <*vmname*> <*service*>
 | :command:`qvm-service` [-e|-d|-D] <*vmname*> <*service*>
+| :command:`qvm-service` <*vmname*> <*service*> [on|off]
 
 
 Options
 Options
 =======
 =======
@@ -27,11 +28,19 @@ Options
 
 
     Disable service
     Disable service
 
 
-.. option:: --default, -D
+.. option:: --default, -D, --delete, --unset
 
 
     Reset service to its default state (remove from the list). Default state
     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).
     means "lets VM choose" and can depend on VM type (NetVM, AppVM etc).
 
 
+.. option:: --verbose, -v
+
+   increase verbosity
+
+.. option:: --quiet, -q
+
+   decrease verbosity
+
 Supported services
 Supported services
 ==================
 ==================
 
 
@@ -50,11 +59,6 @@ meminfo-writer
         remove it (reset to default state), will be recreated with the rule: enabled
         remove it (reset to default state), will be recreated with the rule: enabled
         if VM have no PCI devices assigned, otherwise disabled.
         if VM have no PCI devices assigned, otherwise disabled.
 
 
-qubes-dvm
-    Default: disabled
-
-    Used internally when creating DispVM savefile.
-
 qubes-firewall
 qubes-firewall
     Default: enabled only in ProxyVM
     Default: enabled only in ProxyVM
 
 
@@ -72,14 +76,6 @@ qubes-network
     Expose network for other VMs. This includes enabling network forwarding,
     Expose network for other VMs. This includes enabling network forwarding,
     MASQUERADE, DNS redirection and basic firewall.
     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
 qubes-update-check
     Default: enabled
     Default: enabled
 
 
@@ -103,12 +99,12 @@ network-manager
     Enable NetworkManager. Only VM with direct access to network device needs
     Enable NetworkManager. Only VM with direct access to network device needs
     this service, but can be useful in ProxyVM to ease VPN setup.
     this service, but can be useful in ProxyVM to ease VPN setup.
 
 
-ntpd
+clocksync
     Default: disabled
     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.
+    Enable NTPD (or equivalent) service. If disabled, VM will sync clock with
+    selected VM (aka ClockVM) instead. ClockVM for particular VM can be set in
+    policy of qubes.GetDate service, using target= parameter.
 
 
 qubes-yum-proxy
 qubes-yum-proxy
     Deprecated name for qubes-updates-proxy.
     Deprecated name for qubes-updates-proxy.
@@ -130,9 +126,8 @@ updates-proxy-setup
 
 
     .. note::
     .. 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.
+       this service is automatically enabled when you allow VM to access updates
+       proxy and disabled when you deny access to updates proxy.
 
 
 disable-default-route
 disable-default-route
     Default: disabled
     Default: disabled

+ 1 - 6
doc/manpages/qvm-shutdown.rst

@@ -15,7 +15,7 @@
 Synopsis
 Synopsis
 --------
 --------
 
 
-:command:`qvm-shutdown` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--force] [--wait] [--timeout *TIMEOUT*] [*VMNAME*]
+:command:`qvm-shutdown` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--wait] [--timeout *TIMEOUT*] [*VMNAME*]
 
 
 Options
 Options
 -------
 -------
@@ -40,11 +40,6 @@ Options
 
 
    exclude the qube from :option:`--all`
    exclude the qube from :option:`--all`
 
 
-.. option:: --force
-
-   force operation, even if may damage other VMs (eg. shutdown of network
-   provider)
-
 .. option:: --wait
 .. option:: --wait
 
 
    wait for the VMs to shut down
    wait for the VMs to shut down

+ 5 - 1
doc/manpages/qvm-start-gui.rst

@@ -15,7 +15,7 @@
 Synopsis
 Synopsis
 --------
 --------
 
 
-:command:`qvm-start-gui` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--watch] [--pidfile *PIDFILE*] [--notify-monitory-layout] [*VMNAME* [*VMNAME* ...]]
+:command:`qvm-start-gui` [-h] [--verbose] [--quiet] [--all] [--exclude *EXCLUDE*] [--watch] [--force-stubdomain] [--pidfile *PIDFILE*] [--notify-monitory-layout] [*VMNAME* [*VMNAME* ...]]
 
 
 Options
 Options
 -------
 -------
@@ -44,6 +44,10 @@ Options
 
 
    Keep watching for further domains startups, must be used with --all
    Keep watching for further domains startups, must be used with --all
 
 
+.. option:: --force-stubdomain
+
+   Start GUI to stubdomain-emulated VGA, even if gui-agent is running in the VM
+
 .. option:: --pidfile
 .. option:: --pidfile
 
 
    Pidfile path to create in --watch mode
    Pidfile path to create in --watch mode

+ 3 - 2
qubesadmin/features.py

@@ -42,9 +42,10 @@ class Features(object):
         self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Remove', key)
         self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Remove', key)
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
-        if value is False:
+        if isinstance(value, bool):
             # False value needs to be serialized as empty string
             # False value needs to be serialized as empty string
-            self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key, b'')
+            self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key,
+                b'1' if value else b'')
         else:
         else:
             self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key,
             self.vm.qubesd_call(self.vm.name, 'admin.vm.feature.Set', key,
                 str(value).encode())
                 str(value).encode())

+ 2 - 0
qubesadmin/tests/backup/backupcompatibility.py

@@ -1350,6 +1350,8 @@ class TC_10_BackupCompatibility(qubesadmin.tests.backup.BackupTestCase):
             for feature, value in vm['features'].items():
             for feature, value in vm['features'].items():
                 if value is False:
                 if value is False:
                     value = ''
                     value = ''
+                elif value is True:
+                    value = '1'
                 self.app.expected_calls[
                 self.app.expected_calls[
                     (name, 'admin.vm.feature.Set', feature,
                     (name, 'admin.vm.feature.Set', feature,
                     str(value).encode())] = b'0\0'
                     str(value).encode())] = b'0\0'

+ 1 - 1
qubesadmin/tests/features.py

@@ -78,7 +78,7 @@ class TC_00_Features(qubesadmin.tests.QubesTestCase):
 
 
     def test_021_set_bool(self):
     def test_021_set_bool(self):
         self.app.expected_calls[
         self.app.expected_calls[
-            ('test-vm', 'admin.vm.feature.Set', 'feature1', b'True')] = \
+            ('test-vm', 'admin.vm.feature.Set', 'feature1', b'1')] = \
             b'0\0'
             b'0\0'
         self.vm.features['feature1'] = True
         self.vm.features['feature1'] = True
         self.assertAllCalled()
         self.assertAllCalled()

+ 0 - 159
qubesadmin/tests/tools/qvm_features.py

@@ -91,162 +91,3 @@ class TC_00_qvm_features(qubesadmin.tests.QubesTestCase):
             self.assertEqual(stdout.getvalue(),
             self.assertEqual(stdout.getvalue(),
                 '')
                 '')
         self.assertAllCalled()
         self.assertAllCalled()
-
-    def test_004_running_verbose(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Running\n' \
-            b'some-vm3 class=AppVM state=Halted\n'
-        self.app.expected_calls[
-            ('some-vm', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--running',
-                    'some-vm'], app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VM some-vm is running\n')
-        self.assertAllCalled()
-
-    def test_005_running_multi_verbose(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Running\n' \
-            b'some-vm3 class=AppVM state=Halted\n'
-        self.app.expected_calls[
-            ('some-vm', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n'
-        self.app.expected_calls[
-            ('some-vm2', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm2 class=AppVM state=Running\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--running',
-                    'some-vm', 'some-vm2'],
-                    app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VMs some-vm, some-vm2 are running\n')
-        self.assertAllCalled()
-
-    def test_006_running_multi_verbose2(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Running\n' \
-            b'some-vm3 class=AppVM state=Halted\n'
-        self.app.expected_calls[
-            ('some-vm', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n'
-        self.app.expected_calls[
-            ('some-vm2', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm2 class=AppVM state=Running\n'
-        self.app.expected_calls[
-            ('some-vm3', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm3 class=AppVM state=Halted\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--running',
-                    '--all'],
-                    app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VMs some-vm, some-vm2 are running\n')
-        self.assertAllCalled()
-
-    def test_007_not_running_verbose(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Running\n' \
-            b'some-vm3 class=AppVM state=Halted\n'
-        self.app.expected_calls[
-            ('some-vm3', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm3 class=AppVM state=Halted\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--running',
-                    'some-vm3'],
-                    app=self.app),
-                1)
-            self.assertEqual(stdout.getvalue(),
-                'None of given VM is running\n')
-        self.assertAllCalled()
-
-    def test_008_paused(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Paused\n' \
-            b'some-vm3 class=AppVM state=Halted\n'
-        self.app.expected_calls[
-            ('some-vm2', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm2 class=AppVM state=Paused\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--paused',
-                    'some-vm2'],
-                    app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VM some-vm2 is paused\n')
-        self.assertAllCalled()
-
-    def test_009_paused_multi(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Paused\n' \
-            b'some-vm3 class=AppVM state=Halted\n'
-        self.app.expected_calls[
-            ('some-vm2', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm2 class=AppVM state=Paused\n'
-        self.app.expected_calls[
-            ('some-vm', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--paused',
-                    'some-vm2', 'some-vm'],
-                    app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VM some-vm2 is paused\n')
-        self.assertAllCalled()
-
-
-    def test_010_template(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Paused\n' \
-            b'some-vm3 class=TemplateVM state=Halted\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--template',
-                    'some-vm3'],
-                    app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VM some-vm3 is a template\n')
-        self.assertAllCalled()
-
-    def test_011_template_multi(self):
-        self.app.expected_calls[
-            ('dom0', 'admin.vm.List', None, None)] = \
-            b'0\x00some-vm class=AppVM state=Running\n' \
-            b'some-vm2 class=AppVM state=Paused\n' \
-            b'some-vm3 class=TemplateVM state=Halted\n'
-        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
-            self.assertEqual(
-                qubesadmin.tools.qvm_check.main(['--template',
-                    'some-vm2', 'some-vm3'],
-                    app=self.app),
-                0)
-            self.assertEqual(stdout.getvalue(),
-                'VM some-vm3 is a template\n')
-        self.assertAllCalled()
-

+ 203 - 0
qubesadmin/tests/tools/qvm_service.py

@@ -0,0 +1,203 @@
+# -*- encoding: utf8 -*-
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2017 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 Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+import unittest
+
+import qubesadmin.tests
+import qubesadmin.tools.qvm_service
+
+
+class TC_00_qvm_service(qubesadmin.tests.QubesTestCase):
+    def test_000_list(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.List', None, None)] = \
+            b'0\x00feature1\nservice.service1\nservice.service2\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Get', 'service.service1', None)] = \
+            b'0\x00value1'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Get', 'service.service2', None)] = \
+            b'0\x00'
+        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(['some-vm'], app=self.app),
+                0)
+            self.assertEqual(stdout.getvalue(),
+                'service1  on\n'
+                'service2  off\n')
+        self.assertAllCalled()
+
+    def test_001_list_l(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.List', None, None)] = \
+            b'0\x00feature1\nservice.service1\nservice.service2\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Get', 'service.service1', None)] = \
+            b'0\x00value1'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Get', 'service.service2', None)] = \
+            b'0\x00'
+        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(['-l', 'some-vm'],
+                    app=self.app),
+                0)
+            self.assertEqual(stdout.getvalue(),
+                'service1  on\n'
+                'service2  off\n')
+        self.assertAllCalled()
+
+    def test_002_enable(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Set',
+             'service.service1', b'1')] = b'0\x00'
+        self.assertEqual(
+            qubesadmin.tools.qvm_service.main(['some-vm', 'service1',
+                'on'],
+                app=self.app),
+            0)
+        self.assertAllCalled()
+
+    def test_003_enable_opt(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Set',
+             'service.service1', b'1')] = b'0\x00'
+        self.assertEqual(
+            qubesadmin.tools.qvm_service.main(['--enable', 'some-vm',
+                'service1'],
+                app=self.app),
+            0)
+        self.assertAllCalled()
+
+    @unittest.expectedFailure
+    def test_004_enable_opt_mixed(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Set',
+             'service.service1', b'1')] = b'0\x00'
+        with self.assertNotRaises(SystemExit):
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(
+                    ['some-vm', '--enable', 'service1'],
+                    app=self.app),
+                0)
+        self.assertAllCalled()
+
+    def test_005_disable(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Set',
+             'service.service1', b'')] = b'0\x00'
+        self.assertEqual(
+            qubesadmin.tools.qvm_service.main(
+                ['some-vm', 'service1', 'off'],
+                app=self.app),
+            0)
+        self.assertAllCalled()
+
+    def test_006_disable_opt(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Set',
+             'service.service1', b'')] = b'0\x00'
+        with self.assertNotRaises(SystemExit):
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(
+                    ['--disable', 'some-vm', 'service1'],
+                    app=self.app),
+                0)
+        self.assertAllCalled()
+
+    def test_007_get(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Get', 'service.service3', None)] = \
+            b'0\x00value2 with spaces'
+        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(['some-vm', 'service3'],
+                    app=self.app),
+                0)
+            self.assertEqual(stdout.getvalue(),
+                'on\n')
+        self.assertAllCalled()
+
+    def test_008_del(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Remove', 'service.srv4', None)] = \
+            b'0\x00'
+        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(
+                    ['--unset', 'some-vm', 'srv4'],
+                    app=self.app),
+                0)
+            self.assertEqual(stdout.getvalue(),
+                '')
+        self.assertAllCalled()
+
+    def test_009_del_legacy(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('some-vm', 'admin.vm.feature.Remove', 'service.srv4', None)] = \
+            b'0\x00'
+        with qubesadmin.tests.tools.StdoutBuffer() as stdout:
+            self.assertEqual(
+                qubesadmin.tools.qvm_service.main(
+                    ['--default', 'some-vm', 'srv4'],
+                    app=self.app),
+                0)
+            self.assertEqual(stdout.getvalue(),
+                '')
+        self.assertAllCalled()
+
+    def test_010_set_invalid(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00some-vm class=AppVM state=Running\n'
+        with self.assertRaises(SystemExit):
+            qubesadmin.tools.qvm_service.main(
+                ['some-vm', 'service1', 'invalid-value'],
+                app=self.app),
+        self.assertAllCalled()

+ 2 - 2
qubesadmin/tests/tools/qvm_template_postprocess.py

@@ -243,7 +243,7 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
         self.app.expected_calls[
             ('test-vm', 'admin.vm.property.Reset', 'netvm', None)] = b'0\0'
             ('test-vm', 'admin.vm.property.Reset', 'netvm', None)] = b'0\0'
         self.app.expected_calls[
         self.app.expected_calls[
-            ('test-vm', 'admin.vm.feature.Set', 'qrexec', b'True')] = b'0\0'
+            ('test-vm', 'admin.vm.feature.Set', 'qrexec', b'1')] = b'0\0'
         self.app.expected_calls[
         self.app.expected_calls[
             ('test-vm', 'admin.vm.Start', None, None)] = b'0\0'
             ('test-vm', 'admin.vm.Start', None, None)] = b'0\0'
         self.app.expected_calls[
         self.app.expected_calls[
@@ -293,7 +293,7 @@ class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
         self.app.expected_calls[
             ('test-vm', 'admin.vm.property.Reset', 'netvm', None)] = b'0\0'
             ('test-vm', 'admin.vm.property.Reset', 'netvm', None)] = b'0\0'
         self.app.expected_calls[
         self.app.expected_calls[
-            ('test-vm', 'admin.vm.feature.Set', 'qrexec', b'True')] = b'0\0'
+            ('test-vm', 'admin.vm.feature.Set', 'qrexec', b'1')] = b'0\0'
         self.app.expected_calls[
         self.app.expected_calls[
             ('test-vm', 'admin.vm.Start', None, None)] = b'0\0'
             ('test-vm', 'admin.vm.Start', None, None)] = b'0\0'
         self.app.expected_calls[
         self.app.expected_calls[

+ 1 - 1
qubesadmin/tools/qvm_ls.py

@@ -351,7 +351,7 @@ Column('MEMORY',
     doc='Memory currently used by VM')
     doc='Memory currently used by VM')
 
 
 Column('DISK',
 Column('DISK',
-    attr=(lambda vm: vm.storage.get_disk_utilization() / 1024 / 1024),
+    attr=(lambda vm: vm.get_disk_utilization() // 1024 // 1024),
     doc='Total disk utilisation.')
     doc='Total disk utilisation.')
 
 
 
 

+ 136 - 0
qubesadmin/tools/qvm_service.py

@@ -0,0 +1,136 @@
+# coding=utf-8
+#
+# The Qubes OS Project, https://www.qubes-os.org/
+#
+# Copyright (C) 2010-2016  Joanna Rutkowska <joanna@invisiblethingslab.com>
+# Copyright (C) 2016       Wojtek Porczyk <woju@invisiblethingslab.com>
+# Copyright (C) 2017       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 Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser 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.
+#
+
+'''qvm-service - Manage domain's services'''
+
+from __future__ import print_function
+
+import argparse
+import sys
+
+import qubesadmin
+import qubesadmin.exc
+import qubesadmin.tools
+
+parser = qubesadmin.tools.QubesArgumentParser(
+    vmname_nargs=1,
+    argument_default=argparse.SUPPRESS,
+    description='manage domain\'s services')
+
+parser.add_argument('service', metavar='FEATURE',
+    action='store', nargs='?',
+    help='name of the feature')
+
+parser.add_argument('value', metavar='VALUE',
+    action='store', nargs='?',
+    help='new value of the service')
+
+parser.add_argument('--unset', '--default', '--delete', '-D',
+    dest='delete', default=False,
+    action='store_true',
+    help='unset service (default to VM preference)')
+
+parser.add_argument('--list', '-l',
+    dest='list',
+    action='store_true',
+    help='list services (default action)')
+
+parser.add_argument('--enable', '-e',
+    dest='value',
+    action='store_const', const='1',
+    help='enable service')
+
+parser.add_argument('--disable', '-d',
+    dest='value',
+    action='store_const', const='0',
+    help='disable service')
+
+def parse_bool(value):
+    '''Convert string value to bool according to well known representations
+
+    It accepts (case-insensitive) ``'0'``, ``'no'`` and ``false`` as
+        :py:obj:`False` and ``'1'``, ``'yes'`` and ``'true'`` as
+        :py:obj:`True`.
+    '''
+    if isinstance(value, str):
+        lcvalue = value.lower()
+        if lcvalue in ('0', 'no', 'false', 'off'):
+            return False
+        if lcvalue in ('1', 'yes', 'true', 'on'):
+            return True
+        raise qubesadmin.exc.QubesValueError(
+            'Invalid literal for boolean value: {!r}'.format(value))
+
+    return bool(value)
+
+
+def main(args=None, app=None):
+    '''Main routine of :program:`qvm-features`.
+
+    :param list args: Optional arguments to override those delivered from \
+        command line.
+    '''
+
+    args = parser.parse_args(args, app=app)
+    vm = args.domains[0]
+
+    if not hasattr(args, 'service'):
+        if args.delete:
+            parser.error('--unset requires a feature')
+
+        services = [(feat[len('service.'):],
+            'on' if vm.features[feat] else 'off') for feat in
+            vm.features if feat.startswith('service.')]
+        qubesadmin.tools.print_table(services)
+
+    elif args.delete:
+        if hasattr(args, 'value'):
+            parser.error('cannot both set and unset a value')
+        try:
+            del vm.features['service.' + args.service]
+        except KeyError:
+            pass
+        except qubesadmin.exc.QubesException as err:
+            parser.error_runtime(str(err))
+
+    elif hasattr(args, 'value'):
+        try:
+            vm.features['service.' + args.service] = parse_bool(args.value)
+        except qubesadmin.exc.QubesException as err:
+            parser.error_runtime(str(err))
+
+    else:
+        try:
+            print('on' if vm.features['service.' + args.service] else 'off')
+            return 0
+        except KeyError:
+            return 1
+        except qubesadmin.exc.QubesException as err:
+            parser.error_runtime(str(err))
+
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())

+ 1 - 6
qubesadmin/tools/qvm_shutdown.py

@@ -40,11 +40,6 @@ import qubesadmin.exc
 parser = qubesadmin.tools.QubesArgumentParser(
 parser = qubesadmin.tools.QubesArgumentParser(
     description=__doc__, vmname_nargs='+')
     description=__doc__, vmname_nargs='+')
 
 
-parser.add_argument('--force',
-    action='store_true', default=False,
-    help='force operation, even if may damage other VMs (eg. shutdown of'
-        ' network provider)')
-
 parser.add_argument('--wait',
 parser.add_argument('--wait',
     action='store_true', default=False,
     action='store_true', default=False,
     help='wait for the VMs to shut down')
     help='wait for the VMs to shut down')
@@ -53,7 +48,7 @@ parser.add_argument('--timeout',
     action='store', type=float,
     action='store', type=float,
     default=60,
     default=60,
     help='timeout after which domains are killed when using --wait'
     help='timeout after which domains are killed when using --wait'
-        ' (default: %d)')
+        ' (default: %(default)d)')
 
 
 
 
 def main(args=None, app=None):  # pylint: disable=missing-docstring
 def main(args=None, app=None):  # pylint: disable=missing-docstring

+ 5 - 1
qubesadmin/tools/qvm_start_gui.py

@@ -347,6 +347,9 @@ parser = qubesadmin.tools.QubesArgumentParser(
     description='start GUI for qube(s)', vmname_nargs='*')
     description='start GUI for qube(s)', vmname_nargs='*')
 parser.add_argument('--watch', action='store_true',
 parser.add_argument('--watch', action='store_true',
     help='Keep watching for further domains startups, must be used with --all')
     help='Keep watching for further domains startups, must be used with --all')
+parser.add_argument('--force-stubdomain', action='store_true',
+    help='Start GUI to stubdomain-emulated VGA, even if gui-agent is running '
+         'in the VM')
 parser.add_argument('--pidfile', action='store', default=pidfile_path,
 parser.add_argument('--pidfile', action='store', default=pidfile_path,
     help='Pidfile path to create in --watch mode')
     help='Pidfile path to create in --watch mode')
 parser.add_argument('--notify-monitor-layout', action='store_true',
 parser.add_argument('--notify-monitor-layout', action='store_true',
@@ -400,7 +403,8 @@ def main(args=None):
         tasks = []
         tasks = []
         for vm in args.domains:
         for vm in args.domains:
             if vm.is_running():
             if vm.is_running():
-                tasks.append(asyncio.ensure_future(launcher.start_gui(vm)))
+                tasks.append(asyncio.ensure_future(launcher.start_gui(
+                    vm, force_stubdom=args.force_stubdomain)))
         if tasks:
         if tasks:
             loop.run_until_complete(asyncio.wait(tasks))
             loop.run_until_complete(asyncio.wait(tasks))
         loop.stop()
         loop.stop()

+ 4 - 0
qubesadmin/vm/__init__.py

@@ -257,6 +257,10 @@ class QubesVM(qubesadmin.base.PropertyHolder):
                     vm=self.name, vm_name=volname)
                     vm=self.name, vm_name=volname)
         return self._volumes
         return self._volumes
 
 
+    def get_disk_utilization(self):
+        '''Get total disk usage of the VM'''
+        return sum(vol.usage for vol in self.volumes.values())
+
     def run_service(self, service, **kwargs):
     def run_service(self, service, **kwargs):
         '''Run service on this VM
         '''Run service on this VM