Merge branch 'devel-1'

* devel-1:
  toos: fix handling default command (qvm-device, qvm-volume, ...)
  events: fix parsing events with empty parameters
  tools: ignore qvm-template-postprocess calls in chroot
  app: close payload_stream in qubesd_call
This commit is contained in:
Marek Marczykowski-Górecki 2017-06-08 22:18:47 +02:00
commit a42dffcb89
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
7 changed files with 61 additions and 17 deletions

View File

@ -350,6 +350,8 @@ class QubesLocal(QubesBase):
:param payload: Payload send to the method :param payload: Payload send to the method
:param payload_stream: file-like object to read payload from :param payload_stream: file-like object to read payload from
:return: Data returned by qubesd (string) :return: Data returned by qubesd (string)
.. warning:: *payload_stream* will get closed by this function
''' '''
if payload and payload_stream: if payload and payload_stream:
raise ValueError( raise ValueError(
@ -369,6 +371,7 @@ class QubesLocal(QubesBase):
qrexec_call_env['QREXEC_REQUESTED_TARGET'] = dest qrexec_call_env['QREXEC_REQUESTED_TARGET'] = dest
proc = subprocess.Popen([method_path, arg], stdin=payload_stream, proc = subprocess.Popen([method_path, arg], stdin=payload_stream,
stdout=subprocess.PIPE, env=qrexec_call_env) stdout=subprocess.PIPE, env=qrexec_call_env)
payload_stream.close()
(return_data, _) = proc.communicate() (return_data, _) = proc.communicate()
return self._parse_qubesd_response(return_data) return self._parse_qubesd_response(return_data)
@ -455,6 +458,8 @@ class QubesRemote(QubesBase):
:param payload: Payload send to the method :param payload: Payload send to the method
:param payload_stream: file-like object to read payload from :param payload_stream: file-like object to read payload from
:return: Data returned by qubesd (string) :return: Data returned by qubesd (string)
.. warning:: *payload_stream* will get closed by this function
''' '''
if payload and payload_stream: if payload and payload_stream:
raise ValueError( raise ValueError(
@ -467,6 +472,8 @@ class QubesRemote(QubesBase):
stdin=(payload_stream or subprocess.PIPE), stdin=(payload_stream or subprocess.PIPE),
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
if payload_stream is not None:
payload_stream.close()
(stdout, stderr) = p.communicate(payload) (stdout, stderr) = p.communicate(payload)
if p.returncode != 0: if p.returncode != 0:
# TODO: use dedicated exception # TODO: use dedicated exception

View File

@ -147,25 +147,32 @@ class EventsDispatcher(object):
some_event_received = False some_event_received = False
while not reader.at_eof(): while not reader.at_eof():
try: try:
event_data = yield from reader.readuntil(b'\0\0') event_header = yield from reader.readuntil(b'\0')
if event_data == b'1\0\0': if event_header != b'1\0':
# event with non-VM subject contains \0\0 inside of raise qubesadmin.exc.QubesDaemonCommunicationError(
# event, need to receive rest of the data 'Non-event received on events connection: '
event_data += yield from reader.readuntil(b'\0\0') + repr(event_header))
subject = (yield from reader.readuntil(b'\0'))[:-1].decode(
'utf-8')
event = (yield from reader.readuntil(b'\0'))[:-1].decode(
'utf-8')
kwargs = {}
while True:
key = (yield from reader.readuntil(b'\0'))[:-1].decode(
'utf-8')
if not key:
break
value = (yield from reader.readuntil(b'\0'))[:-1].\
decode('utf-8')
kwargs[key] = value
except asyncio.IncompleteReadError as err: except asyncio.IncompleteReadError as err:
if err.partial == b'': if err.partial == b'':
break break
else: else:
raise raise
if not event_data.startswith(b'1\0'): if not subject:
raise qubesadmin.exc.QubesDaemonCommunicationError( subject = None
'Non-event received on events connection: '
+ repr(event_data))
event_data = event_data.decode('utf-8')
_, subject, event, *kwargs = event_data.split('\0')
# convert list to dict, remove last empty entry
kwargs = dict(zip(kwargs[:-2:2], kwargs[1:-2:2]))
self.handle(subject, event, **kwargs) self.handle(subject, event, **kwargs)
some_event_received = True some_event_received = True

View File

@ -107,7 +107,7 @@ class TC_00_Events(qubesadmin.tests.QubesTestCase):
events = [ events = [
b'1\0\0some-event\0arg1\0value1\0\0', b'1\0\0some-event\0arg1\0value1\0\0',
b'1\0some-vm\0some-event\0arg1\0value1\0\0', b'1\0some-vm\0some-event\0arg1\0value1\0\0',
b'1\0some-vm\0some-event\0\0', b'1\0some-vm\0some-event\0arg_without_value\0\0arg2\0value\0\0',
b'1\0some-vm\0other-event\0\0', b'1\0some-vm\0other-event\0\0',
] ]
asyncio.ensure_future(self.send_events(stream, events)) asyncio.ensure_future(self.send_events(stream, events))
@ -117,7 +117,9 @@ class TC_00_Events(qubesadmin.tests.QubesTestCase):
unittest.mock.call(None, 'some-event', arg1='value1'), unittest.mock.call(None, 'some-event', arg1='value1'),
unittest.mock.call( unittest.mock.call(
self.app.domains['some-vm'], 'some-event', arg1='value1'), self.app.domains['some-vm'], 'some-event', arg1='value1'),
unittest.mock.call(self.app.domains['some-vm'], 'some-event'), unittest.mock.call(
self.app.domains['some-vm'], 'some-event',
arg_without_value='', arg2='value'),
]) ])
cleanup_func.assert_called_once_with() cleanup_func.assert_called_once_with()
loop.close() loop.close()

View File

@ -389,6 +389,8 @@ class QubesArgumentParser(argparse.ArgumentParser):
argparse._SubParsersAction): # pylint: disable=no-member argparse._SubParsersAction): # pylint: disable=no-member
assert hasattr(namespace, 'command') assert hasattr(namespace, 'command')
command = namespace.command command = namespace.command
if command is None:
continue
subparser = action._name_parser_map[command] subparser = action._name_parser_map[command]
for subaction in subparser._actions: for subaction in subparser._actions:
if issubclass(subaction.__class__, QubesAction): if issubclass(subaction.__class__, QubesAction):

View File

@ -201,6 +201,10 @@ def get_parser(device_class=None):
else: else:
parser.add_argument('devclass', metavar='DEVICE_CLASS', action='store', parser.add_argument('devclass', metavar='DEVICE_CLASS', action='store',
help="Device class to manage ('pci', 'usb', etc)") help="Device class to manage ('pci', 'usb', etc)")
# default action
parser.set_defaults(func=list_devices)
sub_parsers = parser.add_subparsers( sub_parsers = parser.add_subparsers(
title='commands', title='commands',
description="For more information see qvm-device command -h", description="For more information see qvm-device command -h",

View File

@ -96,13 +96,12 @@ def import_root_img(vm, source_dir):
tar = subprocess.Popen(['tar', 'xSOf', '-'], tar = subprocess.Popen(['tar', 'xSOf', '-'],
stdin=cat.stdout, stdin=cat.stdout,
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
cat.stdout.close()
vm.volumes['root'].import_data(stream=tar.stdout) vm.volumes['root'].import_data(stream=tar.stdout)
if tar.wait() != 0: if tar.wait() != 0:
raise qubesadmin.exc.QubesException('root.img extraction failed') raise qubesadmin.exc.QubesException('root.img extraction failed')
if cat.wait() != 0: if cat.wait() != 0:
raise qubesadmin.exc.QubesException('root.img extraction failed') raise qubesadmin.exc.QubesException('root.img extraction failed')
cat.stdout.close()
tar.stdout.close()
elif os.path.exists(root_path): elif os.path.exists(root_path):
if vm.app.qubesd_connection_type == 'socket': if vm.app.qubesd_connection_type == 'socket':
# check if root.img was already overwritten, i.e. if the source # check if root.img was already overwritten, i.e. if the source
@ -212,9 +211,29 @@ def pre_remove(args):
return 0 return 0
def is_chroot():
'''Detect if running inside chroot'''
try:
stat_root = os.stat('/')
stat_init_root = os.stat('/proc/1/root/.')
return (
stat_root.st_dev != stat_init_root.st_dev or
stat_root.st_ino != stat_init_root.st_ino)
except IOError:
print('Stat failed, assuming not chroot', file=sys.stderr)
return False
def main(args=None, app=None): def main(args=None, app=None):
'''Main function of qvm-template-postprocess''' '''Main function of qvm-template-postprocess'''
args = parser.parse_args(args, app=app) args = parser.parse_args(args, app=app)
if is_chroot():
print('Running in chroot, ignoring request. Import template with:',
file=sys.stderr)
print(' '.join(sys.argv), file=sys.stderr)
return
if not args.really: if not args.really:
parser.error('Do not call this tool directly.') parser.error('Do not call this tool directly.')
if args.action == 'post-install': if args.action == 'post-install':

View File

@ -192,6 +192,7 @@ def init_extend_parser(sub_parsers):
extend_parser.add_argument('size', help='New size in bytes') extend_parser.add_argument('size', help='New size in bytes')
extend_parser.set_defaults(func=extend_volumes) extend_parser.set_defaults(func=extend_volumes)
def get_parser(): def get_parser():
'''Create :py:class:`argparse.ArgumentParser` suitable for '''Create :py:class:`argparse.ArgumentParser` suitable for
:program:`qvm-block`. :program:`qvm-block`.
@ -207,6 +208,8 @@ def get_parser():
init_extend_parser(sub_parsers) init_extend_parser(sub_parsers)
init_list_parser(sub_parsers) init_list_parser(sub_parsers)
init_revert_parser(sub_parsers) init_revert_parser(sub_parsers)
# default action
parser.set_defaults(func=list_volumes)
return parser return parser