CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

159 lines
4.6 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2020 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import os
  17. import sys
  18. import time
  19. import uuid
  20. import fcntl
  21. import shutil
  22. import tempfile
  23. import resource
  24. import subprocess
  25. default_ro_bind = [
  26. ("/usr", "/usr"),
  27. ("/lib", "/lib"),
  28. ("/lib64", "/lib64"),
  29. ("/bin", "/bin"),
  30. ("/sbin", "/sbin"),
  31. ]
  32. def run_code(
  33. context,
  34. code,
  35. options,
  36. count_opcodes=False,
  37. opcode_limit=None,
  38. result_as_string=False,
  39. ):
  40. def limiter():
  41. os.setsid()
  42. context["csm_process"].set_pdeathsig()()
  43. tmpdir = context.get("csq_sandbox_dir", "/tmp/sandbox")
  44. this_one = "_%s" % uuid.uuid4().hex
  45. tmpdir = os.path.join(tmpdir, this_one)
  46. with open(
  47. os.path.join(
  48. context["cs_fs_root"],
  49. "__QTYPES__",
  50. "pythoncode",
  51. "__SANDBOXES__",
  52. "_template.py",
  53. )
  54. ) as f:
  55. template = f.read()
  56. template %= {
  57. "enable_opcode_count": count_opcodes,
  58. "result_as_string": result_as_string,
  59. "test_module": this_one,
  60. "opcode_limit": opcode_limit or float("inf"),
  61. }
  62. os.makedirs(tmpdir, 0o777)
  63. with open(os.path.join(tmpdir, "run_catsoop_test.py"), "w") as f:
  64. f.write(template)
  65. for f in options["FILES"]:
  66. typ = f[0].strip().lower()
  67. if typ == "copy":
  68. shutil.copyfile(f[1], os.path.join(tmpdir, f[2]))
  69. elif typ == "string":
  70. with open(os.path.join(tmpdir, f[1]), "w") as fileobj:
  71. fileobj.write(f[2])
  72. ofname = fname = "%s.py" % this_one
  73. with open(os.path.join(tmpdir, fname), "w") as fileobj:
  74. fileobj.write(code.replace("\r\n", "\n"))
  75. interp = context.get(
  76. "csq_python_interpreter", context.get("cs_python_interpreter", "python3")
  77. )
  78. args = ["bwrap", "--bind", tmpdir, "/run"]
  79. supplied_args = context.get("csq_bwrap_arguments", None)
  80. if supplied_args is None:
  81. args.extend(
  82. [
  83. "--unshare-all",
  84. "--chdir",
  85. "/run",
  86. "--hostname",
  87. "sandbox-runner",
  88. "--die-with-parent",
  89. ]
  90. )
  91. for i in default_ro_bind + context.get("csq_bwrap_extra_ro_binds", []):
  92. args.append("--ro-bind")
  93. args.extend(i)
  94. args.extend(context.get("csq_bwrap_extra_arguments", []))
  95. else:
  96. args.extend(supplied_args)
  97. p = subprocess.Popen(
  98. args + [interp, "-E", "-B", "run_catsoop_test.py"],
  99. preexec_fn=limiter,
  100. bufsize=0,
  101. stdin=subprocess.PIPE,
  102. stdout=subprocess.PIPE,
  103. stderr=subprocess.PIPE,
  104. )
  105. out = ""
  106. err = ""
  107. try:
  108. out, err = p.communicate(options["STDIN"] or "", timeout=options["CLOCKTIME"])
  109. except subprocess.TimeoutExpired:
  110. p.kill()
  111. p.wait()
  112. out, err = p.communicate()
  113. out = out.decode()
  114. err = err.decode()
  115. files = []
  116. for root, _, fs in os.walk(tmpdir):
  117. lr = len(root)
  118. for f in fs:
  119. fname = os.path.join(root, f)
  120. with open(fname, "rb") as f:
  121. files.append((fname[lr:], f.read()))
  122. shutil.rmtree(tmpdir, True)
  123. n = out.rsplit("---", 1)
  124. log = {}
  125. if len(n) == 2: # should be this
  126. out, log = n
  127. try:
  128. log = context["csm_util"].literal_eval(log)
  129. except:
  130. log = {}
  131. if log == {} or log.get("opcode_limit_reached", False):
  132. if err.strip() == "":
  133. err = (
  134. "Your code did not run to completion, "
  135. "but no error message was returned."
  136. "\nThis normally means that your code contains an "
  137. "infinite loop or otherwise took too long to run."
  138. )
  139. if len(n) > 2: # ???
  140. out = ""
  141. log = {}
  142. err = "BAD CODE - this will be logged"
  143. return {"fname": fname, "out": out, "err": err, "info": log}