diff --git a/cms/grading/Sandbox.py b/cms/grading/Sandbox.py index 1cb89e1dfe..07cb23e8df 100644 --- a/cms/grading/Sandbox.py +++ b/cms/grading/Sandbox.py @@ -894,22 +894,27 @@ def __init__(self, file_cacher, name=None, temp_dir=None): box_id = IsolateSandbox.next_id % 10 IsolateSandbox.next_id += 1 - # We create a directory "tmp" inside the outer temporary directory, + # We create a directory "box" inside the outer temporary directory, # because the sandbox will bind-mount the inner one. The sandbox also # runs code as a different user, and so we need to ensure that they can # read and write to the directory. But we don't want everybody on the # system to, which is why the outer directory exists with no read - # permissions. - self.inner_temp_dir = "/tmp" + # permissions to other than the user. self.outer_temp_dir = tempfile.mkdtemp( dir=self.temp_dir, prefix="cms-%s-" % (self.name)) - # Don't use os.path.join here, because the absoluteness of /tmp will - # bite you. - self.path = self.outer_temp_dir + self.inner_temp_dir + self.path = os.path.join(self.outer_temp_dir, "box") os.mkdir(self.path) self.allow_writing_all() + # self.path will be bind-mounted inside the sandbox as inner_temp_dir. + # We use a subdirectory of /tmp so that the sandbox will create a + # /tmp, which is sometimes used by compilers. We don't want /tmp + # itself because some tasktype might decide to bind-mount external + # temp directories to the same name inside the sandbox, and we having + # one mount path as a children of another is a recipe for disaster. + self.inner_temp_dir = "/cms" + self.exec_name = 'isolate' self.box_exec = self.detect_box_executable() self.info_basename = "run.log" # Used for -M @@ -923,7 +928,6 @@ def __init__(self, file_cacher, name=None, temp_dir=None): self.cgroup = config.use_cgroups # --cg self.chdir = self.inner_temp_dir # -c self.dirs = [] # -d - self.dirs += [(self.inner_temp_dir, self.path, "rw")] self.preserve_env = False # -e self.inherit_env = [] # -E self.set_env = {} # -E @@ -938,6 +942,8 @@ def __init__(self, file_cacher, name=None, temp_dir=None): self.wallclock_timeout = None # -w self.extra_timeout = None # -x + self.add_mapped_directory(self.path, inner=self.inner_temp_dir) + # Set common environment variables. # Specifically needed by Python, that searches the home for # packages. @@ -955,6 +961,18 @@ def __init__(self, file_cacher, name=None, temp_dir=None): self.cleanup() self.initialize_isolate() + def add_mapped_directory(self, dir, inner=None, options="rw"): + """Add dir to the external directory visible to the command + + dir (string): directory to make visible. + inner (string|None): if not None, the inner path where to bind dir. + options (string|None): if not None, isolate directory rule options. + + """ + if inner is None: + inner = dir + self.dirs.append((inner, dir, options)) + def add_mapped_directories(self, dirs): """Add dirs to the external dirs visible to the sandboxed command. @@ -962,7 +980,7 @@ def add_mapped_directories(self, dirs): """ for directory in dirs: - self.dirs.append((directory, None, "rw")) + self.add_mapped_directory(directory) def allow_writing_all(self): """Set permissions in such a way that any operation is allowed. @@ -1037,7 +1055,7 @@ def detect_box_executable(self): def build_box_options(self): """Translate the options defined in the instance to a string - that can be postponed to mo-box as an arguments list. + that can be postponed to isolate as an arguments list. return ([string]): the arguments list as strings. @@ -1050,9 +1068,7 @@ def build_box_options(self): if self.chdir is not None: res += ["--chdir=%s" % self.chdir] for in_name, out_name, options in self.dirs: - s = in_name - if out_name is not None: - s += "=" + out_name + s = in_name + "=" + out_name if options is not None: s += ":" + options res += ["--dir=%s" % s] diff --git a/cms/grading/languages/haskell_ghc.py b/cms/grading/languages/haskell_ghc.py index 3c71f0675d..481276cd4c 100644 --- a/cms/grading/languages/haskell_ghc.py +++ b/cms/grading/languages/haskell_ghc.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Contest Management System - http://cms-dev.github.io/ -# Copyright © 2016 Stefano Maggiolo +# Copyright © 2016-2018 Stefano Maggiolo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -60,6 +60,8 @@ def get_compilation_commands(self, for_evaluation=True): """See Language.get_compilation_commands.""" commands = [] + # GHC requires a /tmp directory, so we create it if necessary. + commands.append(["/bin/mkdir", "-p", "/tmp"]) # Haskell module names are capitalized, so we change the source file # names (except for the first one) to match the module's name. # The first source file is, instead, the grader or the standalone diff --git a/cms/grading/languages/rust.py b/cms/grading/languages/rust.py index 8af77589c9..d2693680c8 100644 --- a/cms/grading/languages/rust.py +++ b/cms/grading/languages/rust.py @@ -3,6 +3,7 @@ # Contest Management System - http://cms-dev.github.io/ # Copyright © 2017 Dario Ostuni +# Copyright © 2018 Stefano Maggiolo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -52,7 +53,11 @@ def get_compilation_commands(self, source_filenames, executable_filename, for_evaluation=True): """See Language.get_compilation_commands.""" - # In Rust only the source file containing the main function has - # to be passed to the compiler - return [["/usr/bin/rustc", "-O", "-o", - executable_filename, source_filenames[0]]] + return [ + # rustc requires a /tmp directory, so we create it if necessary. + ["/bin/mkdir", "-p", "/tmp"], + # In Rust only the source file containing the main function has + # to be passed to the compiler + ["/usr/bin/rustc", "-O", "-o", + executable_filename, source_filenames[0]] + ] diff --git a/cms/grading/steps/compilation.py b/cms/grading/steps/compilation.py index a54ecb09ef..b7d5c6542e 100644 --- a/cms/grading/steps/compilation.py +++ b/cms/grading/steps/compilation.py @@ -100,14 +100,14 @@ def compilation_step(sandbox, commands): """ # Set sandbox parameters suitable for compilation. - sandbox.dirs += [("/etc", None, None)] - # We need to add "/var/lib/ghc" to the unrestricted dirs so GHC can access + sandbox.add_mapped_directory("/etc", options=None) + # We need to add "/var/lib/ghc" to the mapped dirs so GHC can access # haskell's package database. # GHC looks for it in "/usr/lib/ghc/package.conf.d", which is only a # symlink to "/var/lib/ghc/package.conf.d" ghc_dir = "/var/lib/ghc" if os.path.exists(ghc_dir): - sandbox.dirs += [("/var/lib/ghc", None, None)] + sandbox.add_mapped_directory(ghc_dir, options=None) sandbox.preserve_env = True sandbox.max_processes = config.compilation_sandbox_max_processes sandbox.timeout = config.compilation_sandbox_max_time_s