Merge remote-tracking branch 'origin/pr/389'
* origin/pr/389: app: save qubes.xml with utils.replace_file() app: use suppress() in simple cases firewall: save firewall.xml with utils.replace_file() utils: take tweaked helper functions from storage/reflink storage/reflink: quote logged filenames
This commit is contained in:
commit
e1991d5c33
36
qubes/app.py
36
qubes/app.py
@ -29,10 +29,10 @@ import logging
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from contextlib import suppress
|
||||
|
||||
import asyncio
|
||||
import jinja2
|
||||
@ -280,11 +280,9 @@ class QubesHost:
|
||||
|
||||
self.app.log.debug('QubesHost: no_cpus={} memory_total={}'.format(
|
||||
self.no_cpus, self.memory_total))
|
||||
try:
|
||||
with suppress(NotImplementedError):
|
||||
self.app.log.debug('QubesHost: xen_free_memory={}'.format(
|
||||
self.get_free_xen_memory()))
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def memory_total(self):
|
||||
@ -1103,18 +1101,12 @@ class Qubes(qubes.PropertyHolder):
|
||||
if not self.__locked_fh:
|
||||
self._acquire_lock(for_save=True)
|
||||
|
||||
fh_new = tempfile.NamedTemporaryFile(
|
||||
prefix=self._store, delete=False)
|
||||
with qubes.utils.replace_file(self._store, permissions=0o660,
|
||||
close_on_success=False) as fh_new:
|
||||
lxml.etree.ElementTree(self.__xml__()).write(
|
||||
fh_new, encoding='utf-8', pretty_print=True)
|
||||
fh_new.flush()
|
||||
try:
|
||||
os.chown(fh_new.name, -1, grp.getgrnam('qubes').gr_gid)
|
||||
os.chmod(fh_new.name, 0o660)
|
||||
except KeyError: # group 'qubes' not found
|
||||
# don't change mode if no 'qubes' group in the system
|
||||
pass
|
||||
os.rename(fh_new.name, self._store)
|
||||
with suppress(KeyError): # group not found
|
||||
os.fchown(fh_new.fileno(), -1, grp.getgrnam('qubes').gr_gid)
|
||||
|
||||
# update stored mtime, in case of multiple save() calls without
|
||||
# loading qubes.xml again
|
||||
@ -1324,10 +1316,8 @@ class Qubes(qubes.PropertyHolder):
|
||||
"""
|
||||
|
||||
# first search for index, verbatim
|
||||
try:
|
||||
with suppress(KeyError):
|
||||
return self.labels[label]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# then search for name
|
||||
for i in self.labels.values():
|
||||
@ -1335,10 +1325,8 @@ class Qubes(qubes.PropertyHolder):
|
||||
return i
|
||||
|
||||
# last call, if label is a number represented as str, search in indices
|
||||
try:
|
||||
with suppress(KeyError, ValueError):
|
||||
return self.labels[int(label)]
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
raise qubes.exc.QubesLabelNotFoundError(label)
|
||||
|
||||
@ -1477,7 +1465,7 @@ class Qubes(qubes.PropertyHolder):
|
||||
# allow removed VM to reference itself
|
||||
continue
|
||||
for prop in obj.property_list():
|
||||
try:
|
||||
with suppress(AttributeError):
|
||||
if isinstance(prop, qubes.vm.VMProperty) and \
|
||||
getattr(obj, prop.__name__) == vm:
|
||||
self.log.error(
|
||||
@ -1489,8 +1477,6 @@ class Qubes(qubes.PropertyHolder):
|
||||
"see 'journalctl -u qubesd -e' in dom0 for "
|
||||
'details'.format(
|
||||
vm.name))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
assignments = vm.get_provided_assignments()
|
||||
if assignments:
|
||||
@ -1512,11 +1498,9 @@ class Qubes(qubes.PropertyHolder):
|
||||
'updatevm',
|
||||
'default_template',
|
||||
):
|
||||
try:
|
||||
with suppress(AttributeError):
|
||||
if getattr(self, propname) == vm:
|
||||
delattr(self, propname)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@qubes.events.handler('property-pre-set:clockvm')
|
||||
def on_property_pre_set_clockvm(self, event, name, newvalue, oldvalue=None):
|
||||
|
@ -31,6 +31,7 @@ import asyncio
|
||||
import lxml.etree
|
||||
|
||||
import qubes
|
||||
import qubes.utils
|
||||
import qubes.vm.qubesvm
|
||||
|
||||
|
||||
@ -577,14 +578,13 @@ class Firewall:
|
||||
xml_tree = lxml.etree.ElementTree(xml_root)
|
||||
|
||||
try:
|
||||
old_umask = os.umask(0o002)
|
||||
with open(firewall_conf, 'wb') as firewall_xml:
|
||||
xml_tree.write(firewall_xml, encoding="UTF-8",
|
||||
pretty_print=True)
|
||||
os.umask(old_umask)
|
||||
with qubes.utils.replace_file(firewall_conf,
|
||||
permissions=0o664) as tmp_io:
|
||||
xml_tree.write(tmp_io, encoding='UTF-8', pretty_print=True)
|
||||
except EnvironmentError as err:
|
||||
self.vm.log.error("save error: {}".format(err))
|
||||
raise qubes.exc.QubesException('save error: {}'.format(err))
|
||||
msg='firewall save error: {}'.format(err)
|
||||
self.vm.log.error(msg)
|
||||
raise qubes.exc.QubesException(msg)
|
||||
|
||||
self.vm.fire_event('firewall-changed')
|
||||
|
||||
|
@ -34,7 +34,7 @@ import subprocess
|
||||
import tempfile
|
||||
import platform
|
||||
import sys
|
||||
from contextlib import contextmanager, suppress
|
||||
from contextlib import suppress
|
||||
|
||||
import qubes.storage
|
||||
import qubes.utils
|
||||
@ -234,7 +234,7 @@ class ReflinkVolume(qubes.storage.Volume):
|
||||
def _commit(self, path_from):
|
||||
self._add_revision()
|
||||
self._prune_revisions()
|
||||
_fsync_path(path_from)
|
||||
qubes.utils.fsync_path(path_from)
|
||||
_rename_file(path_from, self._path_clean)
|
||||
|
||||
def _add_revision(self):
|
||||
@ -364,32 +364,16 @@ class ReflinkVolume(qubes.storage.Volume):
|
||||
return 0
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _replace_file(dst):
|
||||
''' Yield a tempfile whose name starts with dst, creating the last
|
||||
directory component if necessary. If the block does not raise
|
||||
an exception, safely rename the tempfile to dst.
|
||||
'''
|
||||
tmp_dir, prefix = os.path.split(dst + '~')
|
||||
_make_dir(tmp_dir)
|
||||
tmp = tempfile.NamedTemporaryFile(dir=tmp_dir, prefix=prefix, delete=False)
|
||||
try:
|
||||
yield tmp
|
||||
tmp.flush()
|
||||
os.fsync(tmp.fileno())
|
||||
tmp.close()
|
||||
_rename_file(tmp.name, dst)
|
||||
except:
|
||||
tmp.close()
|
||||
_remove_file(tmp.name)
|
||||
raise
|
||||
_make_dir(os.path.dirname(dst))
|
||||
return qubes.utils.replace_file(
|
||||
dst, permissions=0o600, log_level=logging.INFO)
|
||||
|
||||
def _fsync_path(path):
|
||||
fd = os.open(path, os.O_RDONLY) # works for a file or a directory
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
_rename_file = functools.partial(
|
||||
qubes.utils.rename_file, log_level=logging.INFO)
|
||||
|
||||
_remove_file = functools.partial(
|
||||
qubes.utils.remove_file, log_level=logging.INFO)
|
||||
|
||||
def _make_dir(path):
|
||||
''' mkdir path, ignoring FileExistsError; return whether we
|
||||
@ -397,35 +381,20 @@ def _make_dir(path):
|
||||
'''
|
||||
with suppress(FileExistsError):
|
||||
os.mkdir(path)
|
||||
_fsync_path(os.path.dirname(path))
|
||||
LOGGER.info('Created directory: %s', path)
|
||||
qubes.utils.fsync_path(os.path.dirname(path))
|
||||
LOGGER.info('Created directory: %r', path)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _remove_file(path):
|
||||
with suppress(FileNotFoundError):
|
||||
os.remove(path)
|
||||
_fsync_path(os.path.dirname(path))
|
||||
LOGGER.info('Removed file: %s', path)
|
||||
|
||||
def _remove_empty_dir(path):
|
||||
try:
|
||||
os.rmdir(path)
|
||||
_fsync_path(os.path.dirname(path))
|
||||
LOGGER.info('Removed empty directory: %s', path)
|
||||
qubes.utils.fsync_path(os.path.dirname(path))
|
||||
LOGGER.info('Removed empty directory: %r', path)
|
||||
except OSError as ex:
|
||||
if ex.errno not in (errno.ENOENT, errno.ENOTEMPTY):
|
||||
raise
|
||||
|
||||
def _rename_file(src, dst):
|
||||
os.rename(src, dst)
|
||||
dst_dir = os.path.dirname(dst)
|
||||
src_dir = os.path.dirname(src)
|
||||
_fsync_path(dst_dir)
|
||||
if src_dir != dst_dir:
|
||||
_fsync_path(src_dir)
|
||||
LOGGER.info('Renamed file: %s -> %s', src, dst)
|
||||
|
||||
def _resize_file(path, size):
|
||||
''' Resize an existing file. '''
|
||||
with open(path, 'rb+') as file:
|
||||
@ -436,7 +405,7 @@ def _create_sparse_file(path, size):
|
||||
''' Create an empty sparse file. '''
|
||||
with _replace_file(path) as tmp:
|
||||
tmp.truncate(size)
|
||||
LOGGER.info('Created sparse file: %s', tmp.name)
|
||||
LOGGER.info('Created sparse file: %r', tmp.name)
|
||||
|
||||
def _update_loopdev_sizes(img):
|
||||
''' Resolve img; update the size of loop devices backed by it. '''
|
||||
@ -466,9 +435,9 @@ def _copy_file(src, dst):
|
||||
with _replace_file(dst) as tmp_io:
|
||||
with open(src, 'rb') as src_io:
|
||||
if _attempt_ficlone(src_io, tmp_io):
|
||||
LOGGER.info('Reflinked file: %s -> %s', src, tmp_io.name)
|
||||
LOGGER.info('Reflinked file: %r -> %r', src, tmp_io.name)
|
||||
return True
|
||||
LOGGER.info('Copying file: %s -> %s', src, tmp_io.name)
|
||||
LOGGER.info('Copying file: %r -> %r', src, tmp_io.name)
|
||||
cmd = 'cp', '--sparse=always', src, tmp_io.name
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
check=False)
|
||||
|
@ -22,12 +22,16 @@
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
from contextlib import contextmanager, suppress
|
||||
|
||||
import pkg_resources
|
||||
|
||||
@ -36,6 +40,8 @@ import docutils.core
|
||||
import docutils.io
|
||||
import qubes.exc
|
||||
|
||||
LOGGER = logging.getLogger('qubes.utils')
|
||||
|
||||
|
||||
def get_timezone():
|
||||
# fc18
|
||||
@ -186,6 +192,58 @@ def match_vm_name_with_special(vm, name):
|
||||
return name[len('@type:'):] == vm.__class__.__name__
|
||||
return name == vm.name
|
||||
|
||||
@contextmanager
|
||||
def replace_file(dst, *, permissions, close_on_success=True,
|
||||
logger=LOGGER, log_level=logging.DEBUG):
|
||||
''' Yield a tempfile whose name starts with dst. If the block does
|
||||
not raise an exception, apply permissions and persist the
|
||||
tempfile to dst (which is allowed to already exist). Otherwise
|
||||
ensure that the tempfile is cleaned up.
|
||||
'''
|
||||
tmp_dir, prefix = os.path.split(dst + '~')
|
||||
tmp = tempfile.NamedTemporaryFile(dir=tmp_dir, prefix=prefix, delete=False)
|
||||
try:
|
||||
yield tmp
|
||||
tmp.flush()
|
||||
os.fchmod(tmp.fileno(), permissions)
|
||||
os.fsync(tmp.fileno())
|
||||
if close_on_success:
|
||||
tmp.close()
|
||||
rename_file(tmp.name, dst, logger=logger, log_level=log_level)
|
||||
except:
|
||||
try:
|
||||
tmp.close()
|
||||
finally:
|
||||
remove_file(tmp.name, logger=logger, log_level=log_level)
|
||||
raise
|
||||
|
||||
def rename_file(src, dst, *, logger=LOGGER, log_level=logging.DEBUG):
|
||||
''' Durably rename src to dst. '''
|
||||
os.rename(src, dst)
|
||||
dst_dir = os.path.dirname(dst)
|
||||
src_dir = os.path.dirname(src)
|
||||
fsync_path(dst_dir)
|
||||
if src_dir != dst_dir:
|
||||
fsync_path(src_dir)
|
||||
logger.log(log_level, 'Renamed file: %r -> %r', src, dst)
|
||||
|
||||
def remove_file(path, *, logger=LOGGER, log_level=logging.DEBUG):
|
||||
''' Durably remove the file at path, if it exists. Return whether
|
||||
we removed it. '''
|
||||
with suppress(FileNotFoundError):
|
||||
os.remove(path)
|
||||
fsync_path(os.path.dirname(path))
|
||||
logger.log(log_level, 'Removed file: %r', path)
|
||||
return True
|
||||
return False
|
||||
|
||||
def fsync_path(path):
|
||||
fd = os.open(path, os.O_RDONLY) # works for a file or a directory
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
@asyncio.coroutine
|
||||
def coro_maybe(value):
|
||||
if asyncio.iscoroutine(value):
|
||||
|
Loading…
Reference in New Issue
Block a user