Handle OSError exceptions where appropriate.

Fixes #25.
This commit is contained in:
Florian Bruhin 2014-12-10 18:00:49 +01:00
parent ea76bdfb0f
commit ec07e4f8be
13 changed files with 184 additions and 100 deletions

View File

@ -31,7 +31,7 @@ import functools
import traceback import traceback
import faulthandler import faulthandler
from PyQt5.QtWidgets import QApplication, QDialog from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QStandardPaths, QObject, Qt) QStandardPaths, QObject, Qt)
@ -70,6 +70,7 @@ class Application(QApplication):
Args: Args:
Argument namespace from argparse. Argument namespace from argparse.
""" """
# pylint: disable=too-many-statements
self._quit_status = { self._quit_status = {
'crash': True, 'crash': True,
'tabs': False, 'tabs': False,
@ -114,7 +115,14 @@ class Application(QApplication):
self.setApplicationName("qutebrowser") self.setApplicationName("qutebrowser")
self.setApplicationVersion(qutebrowser.__version__) self.setApplicationVersion(qutebrowser.__version__)
utils.actute_warning() utils.actute_warning()
try:
self._init_modules() self._init_modules()
except OSError as e:
msgbox = QMessageBox(
QMessageBox.Critical, "Error while initializing!",
"Error while initializing: {}".format(e))
msgbox.exec_()
sys.exit(1)
QTimer.singleShot(0, self._open_pages) QTimer.singleShot(0, self._open_pages)
log.init.debug("Initializing eventfilter...") log.init.debug("Initializing eventfilter...")
@ -184,31 +192,36 @@ class Application(QApplication):
"""Handle a segfault from a previous run.""" """Handle a segfault from a previous run."""
path = standarddir.get(QStandardPaths.DataLocation) path = standarddir.get(QStandardPaths.DataLocation)
logname = os.path.join(path, 'crash.log') logname = os.path.join(path, 'crash.log')
try:
# First check if an old logfile exists. # First check if an old logfile exists.
if os.path.exists(logname): if os.path.exists(logname):
with open(logname, 'r', encoding='ascii') as f: with open(logname, 'r', encoding='ascii') as f:
data = f.read() data = f.read()
try:
os.remove(logname) os.remove(logname)
except PermissionError:
log.init.warning("Could not remove crash log!")
else:
self._init_crashlogfile() self._init_crashlogfile()
if data: if data:
# Crashlog exists and has data in it, so something crashed # Crashlog exists and has data in it, so something crashed
# previously. # previously.
self._crashdlg = crash.FatalCrashDialog(self._args.debug, data) self._crashdlg = crash.FatalCrashDialog(self._args.debug,
data)
self._crashdlg.show() self._crashdlg.show()
else: else:
# There's no log file, so we can use this to display crashes to the # There's no log file, so we can use this to display crashes to
# user on the next start. # the user on the next start.
self._init_crashlogfile()
except OSError:
log.init.exception("Error while handling crash log file!")
self._init_crashlogfile() self._init_crashlogfile()
def _init_crashlogfile(self): def _init_crashlogfile(self):
"""Start a new logfile and redirect faulthandler to it.""" """Start a new logfile and redirect faulthandler to it."""
path = standarddir.get(QStandardPaths.DataLocation) path = standarddir.get(QStandardPaths.DataLocation)
logname = os.path.join(path, 'crash.log') logname = os.path.join(path, 'crash.log')
try:
self._crashlogfile = open(logname, 'w', encoding='ascii') self._crashlogfile = open(logname, 'w', encoding='ascii')
except OSError:
log.init.exception("Error while opening crash log file!")
else:
earlyinit.init_faulthandler(self._crashlogfile) earlyinit.init_faulthandler(self._crashlogfile)
def _open_pages(self): def _open_pages(self):
@ -446,10 +459,10 @@ class Application(QApplication):
faulthandler.enable(sys.__stderr__) faulthandler.enable(sys.__stderr__)
else: else:
faulthandler.disable() faulthandler.disable()
self._crashlogfile.close()
try: try:
self._crashlogfile.close()
os.remove(self._crashlogfile.name) os.remove(self._crashlogfile.name)
except (PermissionError, FileNotFoundError): except OSError:
log.destroy.exception("Could not remove crash log!") log.destroy.exception("Could not remove crash log!")
def _exception_hook(self, exctype, excvalue, tb): def _exception_hook(self, exctype, excvalue, tb):
@ -756,6 +769,11 @@ class Application(QApplication):
what, utils.qualname(handler))) what, utils.qualname(handler)))
try: try:
handler() handler()
except OSError as e:
msgbox = QMessageBox(
QMessageBox.Critical, "Error while saving!",
"Error while saving {}: {}".format(what, e))
msgbox.exec_()
except AttributeError as e: except AttributeError as e:
log.destroy.warning("Could not save {}.".format(what)) log.destroy.warning("Could not save {}.".format(what))
log.destroy.debug(e) log.destroy.debug(e)

View File

@ -100,9 +100,12 @@ class HostBlocker:
"""Read hosts from the existing blocked-hosts file.""" """Read hosts from the existing blocked-hosts file."""
self.blocked_hosts = set() self.blocked_hosts = set()
if os.path.exists(self._hosts_file): if os.path.exists(self._hosts_file):
try:
with open(self._hosts_file, 'r', encoding='utf-8') as f: with open(self._hosts_file, 'r', encoding='utf-8') as f:
for line in f: for line in f:
self.blocked_hosts.add(line.strip()) self.blocked_hosts.add(line.strip())
except OSError:
log.misc.exception("Failed to read host blocklist!")
else: else:
if config.get('content', 'host-block-lists') is not None: if config.get('content', 'host-block-lists') is not None:
message.info('last-focused', message.info('last-focused',
@ -120,7 +123,10 @@ class HostBlocker:
return return
for url in urls: for url in urls:
if url.scheme() == 'file': if url.scheme() == 'file':
try:
fileobj = open(url.path(), 'rb') fileobj = open(url.path(), 'rb')
except OSError:
log.misc.exception("Failed to open block list!")
download = FakeDownload(fileobj) download = FakeDownload(fileobj)
self._in_progress.append(download) self._in_progress.append(download)
self.on_download_finished(download) self.on_download_finished(download)
@ -145,7 +151,7 @@ class HostBlocker:
line_count = 0 line_count = 0
try: try:
f = get_fileobj(byte_io) f = get_fileobj(byte_io)
except (FileNotFoundError, UnicodeDecodeError, zipfile.BadZipFile, except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
zipfile.LargeZipFile) as e: zipfile.LargeZipFile) as e:
message.error('last-focused', "adblock: Error while reading {}: " message.error('last-focused', "adblock: Error while reading {}: "
"{} - {}".format( "{} - {}".format(
@ -213,4 +219,7 @@ class HostBlocker:
finally: finally:
download.fileobj.close() download.fileobj.close()
if not self._in_progress: if not self._in_progress:
try:
self.on_lists_downloaded() self.on_lists_downloaded()
except OSError:
log.misc.exception("Failed to write host block list!")

View File

@ -206,10 +206,10 @@ class CommandDispatcher:
def _editor_cleanup(self, oshandle, filename): def _editor_cleanup(self, oshandle, filename):
"""Clean up temporary file when the editor was closed.""" """Clean up temporary file when the editor was closed."""
os.close(oshandle)
try: try:
os.close(oshandle)
os.remove(filename) os.remove(filename)
except PermissionError: except OSError:
raise cmdexc.CommandError("Failed to delete tempfile...") raise cmdexc.CommandError("Failed to delete tempfile...")
def _get_selection_override(self, left, right, opposite): def _get_selection_override(self, left, right, opposite):

View File

@ -307,8 +307,11 @@ class DownloadItem(QObject):
self.reply = None self.reply = None
if self.fileobj is not None: if self.fileobj is not None:
self.fileobj.close() self.fileobj.close()
try:
if self._filename is not None and os.path.exists(self._filename): if self._filename is not None and os.path.exists(self._filename):
os.remove(self._filename) os.remove(self._filename)
except OSError:
log.downloads.exception("Failed to remove partial file")
self.finished.emit() self.finished.emit()
def set_filename(self, filename): def set_filename(self, filename):

View File

@ -61,13 +61,18 @@ class _BlockingFIFOReader(QObject):
def read(self): def read(self):
"""Blocking read loop which emits got_line when a new line arrived.""" """Blocking read loop which emits got_line when a new line arrived."""
try:
# We open as R/W so we never get EOF and have to reopen the pipe. # We open as R/W so we never get EOF and have to reopen the pipe.
# See http://www.outflux.net/blog/archives/2008/03/09/using-select-on-a-fifo/ # See http://www.outflux.net/blog/archives/2008/03/09/using-select-on-a-fifo/
# We also use os.open and os.fdopen rather than built-in open so we can # We also use os.open and os.fdopen rather than built-in open so we
# add O_NONBLOCK. # can add O_NONBLOCK.
fd = os.open(self._filepath, os.O_RDWR | fd = os.open(self._filepath, os.O_RDWR |
os.O_NONBLOCK) # pylint: disable=no-member os.O_NONBLOCK) # pylint: disable=no-member
self.fifo = os.fdopen(fd, 'r') self.fifo = os.fdopen(fd, 'r')
except OSError:
log.procs.exception("Failed to read FIFO")
self.finished.emit()
return
while True: while True:
log.procs.debug("thread loop") log.procs.debug("thread loop")
ready_r, _ready_w, _ready_e = select.select([self.fifo], [], [], 1) ready_r, _ready_w, _ready_e = select.select([self.fifo], [], [], 1)
@ -141,7 +146,7 @@ class _BaseUserscriptRunner(QObject):
"""Clean up the temporary file.""" """Clean up the temporary file."""
try: try:
os.remove(self._filepath) os.remove(self._filepath)
except PermissionError as e: except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's # NOTE: Do not replace this with "raise CommandError" as it's
# executed async. # executed async.
message.error(self._win_id, message.error(self._win_id,
@ -196,13 +201,18 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
def run(self, cmd, *args, env=None): def run(self, cmd, *args, env=None):
rundir = standarddir.get(QStandardPaths.RuntimeLocation) rundir = standarddir.get(QStandardPaths.RuntimeLocation)
# tempfile.mktemp is deprecated and discouraged, but we use it here to try:
# create a FIFO since the only other alternative would be to create a # tempfile.mktemp is deprecated and discouraged, but we use it here
# directory and place the FIFO there, which sucks. Since os.kfifo will # to create a FIFO since the only other alternative would be to
# raise an exception anyways when the path doesn't exist, it shouldn't # create a directory and place the FIFO there, which sucks. Since
# be a big issue. # os.kfifo will raise an exception anyways when the path doesn't
# exist, it shouldn't be a big issue.
self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir) self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
os.mkfifo(self._filepath) # pylint: disable=no-member os.mkfifo(self._filepath) # pylint: disable=no-member
except OSError as e:
message.error(self._win_id, "Error while creating FIFO: {}".format(
e))
return
self._reader = _BlockingFIFOReader(self._filepath) self._reader = _BlockingFIFOReader(self._filepath)
self._thread = QThread(self) self._thread = QThread(self)
@ -262,16 +272,22 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
def _cleanup(self): def _cleanup(self):
"""Clean up temporary files after the userscript finished.""" """Clean up temporary files after the userscript finished."""
try:
os.close(self._oshandle) os.close(self._oshandle)
except OSError:
log.procs.exception("Failed to close file handle!")
super()._cleanup() super()._cleanup()
self._oshandle = None self._oshandle = None
def on_proc_finished(self): def on_proc_finished(self):
"""Read back the commands when the process finished.""" """Read back the commands when the process finished."""
log.procs.debug("proc finished") log.procs.debug("proc finished")
try:
with open(self._filepath, 'r', encoding='utf-8') as f: with open(self._filepath, 'r', encoding='utf-8') as f:
for line in f: for line in f:
self.got_cmd.emit(line.rstrip()) self.got_cmd.emit(line.rstrip())
except OSError:
log.procs.exception("Failed to read command file!")
self._cleanup() self._cleanup()
self.finished.emit() self.finished.emit()
@ -282,7 +298,12 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
self.finished.emit() self.finished.emit()
def run(self, cmd, *args, env=None): def run(self, cmd, *args, env=None):
try:
self._oshandle, self._filepath = tempfile.mkstemp(text=True) self._oshandle, self._filepath = tempfile.mkstemp(text=True)
except OSError as e:
message.error(self._win_id, "Error while creating tempfile: "
"{}".format(e))
return
self._run_process(cmd, *args, env=env) self._run_process(cmd, *args, env=env)

View File

@ -572,7 +572,14 @@ class ConfigManager(QObject):
if self._initialized: if self._initialized:
self._after_set(sectname, optname) self._after_set(sectname, optname)
@cmdutils.register(instance='config') @cmdutils.register(instance='config', name='save')
def save_command(self):
"""Save the config file."""
try:
self.save()
except OSError as e:
raise cmdexc.CommandError("Could not save config: {}".format(e))
def save(self): def save(self):
"""Save the config file.""" """Save the config file."""
if self._configdir is None: if self._configdir is None:

View File

@ -210,6 +210,7 @@ class KeyConfigParser(QObject):
def _read(self): def _read(self):
"""Read the config file from disk and parse it.""" """Read the config file from disk and parse it."""
try:
with open(self._configfile, 'r', encoding='utf-8') as f: with open(self._configfile, 'r', encoding='utf-8') as f:
for i, line in enumerate(f): for i, line in enumerate(f):
line = line.rstrip() line = line.rstrip()
@ -218,7 +219,8 @@ class KeyConfigParser(QObject):
continue continue
elif line.startswith('[') and line.endswith(']'): elif line.startswith('[') and line.endswith(']'):
sectname = line[1:-1] sectname = line[1:-1]
self._cur_section = self._normalize_sectname(sectname) self._cur_section = self._normalize_sectname(
sectname)
elif line.startswith((' ', '\t')): elif line.startswith((' ', '\t')):
line = line.strip() line = line.strip()
self._read_keybinding(line) self._read_keybinding(line)
@ -228,6 +230,8 @@ class KeyConfigParser(QObject):
except KeyConfigError as e: except KeyConfigError as e:
e.lineno = i e.lineno = i
raise raise
except OSError:
log.keyboard.exception("Failed to read keybindings!")
for sectname in self.keybindings: for sectname in self.keybindings:
self.changed.emit(sectname) self.changed.emit(sectname)

View File

@ -52,10 +52,10 @@ class ExternalEditor(QObject):
def _cleanup(self): def _cleanup(self):
"""Clean up temporary files after the editor closed.""" """Clean up temporary files after the editor closed."""
os.close(self._oshandle)
try: try:
os.close(self._oshandle)
os.remove(self._filename) os.remove(self._filename)
except PermissionError as e: except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's # NOTE: Do not replace this with "raise CommandError" as it's
# executed async. # executed async.
message.error(self._win_id, message.error(self._win_id,
@ -80,8 +80,15 @@ class ExternalEditor(QObject):
"{})!".format(exitcode)) "{})!".format(exitcode))
return return
encoding = config.get('general', 'editor-encoding') encoding = config.get('general', 'editor-encoding')
try:
with open(self._filename, 'r', encoding=encoding) as f: with open(self._filename, 'r', encoding=encoding) as f:
text = ''.join(f.readlines()) text = ''.join(f.readlines())
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(self._win_id, "Failed to read back edited file: "
"{}".format(e))
return
log.procs.debug("Read back: {}".format(text)) log.procs.debug("Read back: {}".format(text))
self.editing_finished.emit(text) self.editing_finished.emit(text)
finally: finally:
@ -114,11 +121,16 @@ class ExternalEditor(QObject):
if self._text is not None: if self._text is not None:
raise ValueError("Already editing a file!") raise ValueError("Already editing a file!")
self._text = text self._text = text
try:
self._oshandle, self._filename = tempfile.mkstemp(text=True) self._oshandle, self._filename = tempfile.mkstemp(text=True)
if text: if text:
encoding = config.get('general', 'editor-encoding') encoding = config.get('general', 'editor-encoding')
with open(self._filename, 'w', encoding=encoding) as f: with open(self._filename, 'w', encoding=encoding) as f:
f.write(text) f.write(text)
except OSError as e:
message.error(self._win_id, "Failed to create initial file: "
"{}".format(e))
return
self._proc = QProcess(self) self._proc = QProcess(self)
self._proc.finished.connect(self.on_proc_closed) self._proc.finished.connect(self.on_proc_closed)
self._proc.error.connect(self.on_proc_error) self._proc.error.connect(self.on_proc_error)

View File

@ -41,7 +41,7 @@ class Loader(jinja2.BaseLoader):
path = os.path.join(self._subdir, template) path = os.path.join(self._subdir, template)
try: try:
source = utils.read_file(path) source = utils.read_file(path)
except FileNotFoundError: except OSError:
raise jinja2.TemplateNotFound(template) raise jinja2.TemplateNotFound(template)
# Currently we don't implement auto-reloading, so we always return True # Currently we don't implement auto-reloading, so we always return True
# for up-to-date. # for up-to-date.

View File

@ -161,6 +161,7 @@ def deserialize(data, obj):
def savefile_open(filename, binary=False, encoding='utf-8'): def savefile_open(filename, binary=False, encoding='utf-8'):
"""Context manager to easily use a QSaveFile.""" """Context manager to easily use a QSaveFile."""
f = QSaveFile(filename) f = QSaveFile(filename)
new_f = None
try: try:
ok = f.open(QIODevice.WriteOnly) ok = f.open(QIODevice.WriteOnly)
if not ok: # pylint: disable=used-before-assignment if not ok: # pylint: disable=used-before-assignment
@ -174,6 +175,7 @@ def savefile_open(filename, binary=False, encoding='utf-8'):
f.cancelWriting() f.cancelWriting()
raise raise
finally: finally:
if new_f is not None:
new_f.flush() new_f.flush()
ok = f.commit() ok = f.commit()
if not ok: if not ok:

View File

@ -24,6 +24,8 @@ import os.path
from PyQt5.QtCore import QCoreApplication, QStandardPaths from PyQt5.QtCore import QCoreApplication, QStandardPaths
from qutebrowser.utils import log
def _writable_location(typ): def _writable_location(typ):
"""Wrapper around QStandardPaths.writableLocation.""" """Wrapper around QStandardPaths.writableLocation."""
@ -124,9 +126,12 @@ def init():
# http://www.brynosaurus.com/cachedir/spec.html # http://www.brynosaurus.com/cachedir/spec.html
cachedir_tag = os.path.join(cache_dir, 'CACHEDIR.TAG') cachedir_tag = os.path.join(cache_dir, 'CACHEDIR.TAG')
if not os.path.exists(cachedir_tag): if not os.path.exists(cachedir_tag):
try:
with open(cachedir_tag, 'w', encoding='utf-8') as f: with open(cachedir_tag, 'w', encoding='utf-8') as f:
f.write("Signature: 8a477f597d28d172789f06886806bc55\n") f.write("Signature: 8a477f597d28d172789f06886806bc55\n")
f.write("# This file is a cache directory tag created by " f.write("# This file is a cache directory tag created by "
"qutebrowser.\n") "qutebrowser.\n")
f.write("# For information about cache directory tags, see:\n") f.write("# For information about cache directory tags, see:\n")
f.write("# http://www.brynosaurus.com/cachedir/\n") f.write("# http://www.brynosaurus.com/cachedir/\n")
except OSError:
log.misc.exception("Failed to create CACHEDIR.TAG")

View File

@ -130,17 +130,20 @@ def actute_warning():
return return
except ValueError: except ValueError:
pass pass
try:
with open('/usr/share/X11/locale/en_US.UTF-8/Compose', 'r', with open('/usr/share/X11/locale/en_US.UTF-8/Compose', 'r',
encoding='utf-8') as f: encoding='utf-8') as f:
for line in f: for line in f:
if '<dead_actute>' in line: if '<dead_actute>' in line:
if sys.stdout is not None: if sys.stdout is not None:
sys.stdout.flush() sys.stdout.flush()
print("Note: If you got a 'dead_actute' warning above, that " print("Note: If you got a 'dead_actute' warning above, "
"is not a bug in qutebrowser! See " "that is not a bug in qutebrowser! See "
"https://bugs.freedesktop.org/show_bug.cgi?id=69476 for " "https://bugs.freedesktop.org/show_bug.cgi?id=69476 "
"details.") "for details.")
break break
except OSError:
log.misc.exception("Failed to read Compose file")
def _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent): def _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent):

View File

@ -73,7 +73,7 @@ def _git_str():
# If that fails, check the git-commit-id file. # If that fails, check the git-commit-id file.
try: try:
return utils.read_file('git-commit-id') return utils.read_file('git-commit-id')
except (FileNotFoundError, ImportError): except (OSError, ImportError):
return None return None