From b592cb85c7e8f3580e75dff531d44ec1bed84dc1 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Tue, 8 Oct 2019 18:33:48 +0100 Subject: _fuse/mount.py: Monitor the fuse process while waiting for the mount In some cases, users might not have permissions to use fuse, or fuse might crash. This was previously leading to a hanged process and, with chance an error message on the UI, which could be overwritten. This ensures we are explicitely monitoring the fuse process while waiting and adds better reporting of the fuse errors. --- src/buildstream/_fuse/mount.py | 49 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/src/buildstream/_fuse/mount.py b/src/buildstream/_fuse/mount.py index ac5fb2295..52d3ed65a 100644 --- a/src/buildstream/_fuse/mount.py +++ b/src/buildstream/_fuse/mount.py @@ -27,7 +27,7 @@ from multiprocessing import Process from .fuse import FUSE from .._exceptions import ImplError -from .. import _signals +from .. import _signals, utils # Just a custom exception to raise here, for identifying possible @@ -82,6 +82,7 @@ class Mount(): __mountpoint = None __operations = None __process = None + __logfile = None ################################################ # User Facing API # @@ -102,7 +103,7 @@ class Mount(): assert self.__process is None self.__mountpoint = mountpoint - self.__process = Process(target=self.__run_fuse) + self.__process = Process(target=self.__run_fuse, args=(self.__logfile.name,)) # Ensure the child process does not inherit our signal handlers, if the # child wants to handle a signal then it will first set its own @@ -110,8 +111,12 @@ class Mount(): with _signals.blocked([signal.SIGTERM, signal.SIGTSTP, signal.SIGINT], ignore=False): self.__process.start() - # This is horrible, we're going to wait until mountpoint is mounted and that's it. while not os.path.ismount(mountpoint): + if not self.__process.is_alive(): + self.__logfile.seek(0) + stderr = self.__logfile.read() + raise FuseMountError("Unable to mount {}: {}".format(mountpoint, stderr.decode().strip())) + time.sleep(1 / 100) # unmount(): @@ -127,8 +132,11 @@ class Mount(): # Report an error if ever the underlying operations crashed for some reason. if self.__process.exitcode != 0: - raise FuseMountError("{} reported exit code {} when unmounting" - .format(type(self).__name__, self.__process.exitcode)) + self.__logfile.seek(0) + stderr = self.__logfile.read() + + raise FuseMountError("{} reported exit code {} when unmounting: {}" + .format(type(self).__name__, self.__process.exitcode, stderr)) self.__mountpoint = None self.__process = None @@ -145,12 +153,17 @@ class Mount(): @contextmanager def mounted(self, mountpoint): - self.mount(mountpoint) - try: - with _signals.terminator(self.unmount): - yield - finally: - self.unmount() + with utils._tempnamedfile() as logfile: + self.__logfile = logfile + + self.mount(mountpoint) + try: + with _signals.terminator(self.unmount): + yield + finally: + self.unmount() + + self.__logfile = None ################################################ # Abstract Methods # @@ -169,7 +182,11 @@ class Mount(): ################################################ # Child Process # ################################################ - def __run_fuse(self): + def __run_fuse(self, filename): + # Override stdout/stderr to the file given as a pointer, that way our parent process can get our output + out = open(filename, "w") + os.dup2(out.fileno(), sys.stdout.fileno()) + os.dup2(out.fileno(), sys.stderr.fileno()) # First become session leader while signals are still blocked # @@ -187,8 +204,12 @@ class Mount(): # Run fuse in foreground in this child process, internally libfuse # will handle SIGTERM and gracefully exit its own little main loop. # - FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True, nonempty=True, - **self._fuse_mount_options) + try: + FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True, nonempty=True, + **self._fuse_mount_options) + except RuntimeError as exc: + # FUSE will throw a RuntimeError with the exit code of libfuse as args[0] + sys.exit(exc.args[0]) # Explicit 0 exit code, if the operations crashed for some reason, the exit # code will not be 0, and we want to know about it. -- cgit v1.2.1