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.
 
 
 

161 lines
4.8 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 ast
  18. import sys
  19. import time
  20. import uuid
  21. import fcntl
  22. import shutil
  23. import hashlib
  24. import logging
  25. import tempfile
  26. import resource
  27. import subprocess
  28. LOGGER = logging.getLogger("cs")
  29. _resource_mapper = {
  30. "CPUTIME": (resource.RLIMIT_CPU, lambda x: (x, x + 1)),
  31. "MEMORY": (resource.RLIMIT_AS, lambda x: (x, x)),
  32. "FILESIZE": (resource.RLIMIT_FSIZE, lambda x: (x, x)),
  33. }
  34. def run_code(
  35. context,
  36. code,
  37. options,
  38. count_opcodes=False,
  39. opcode_limit=None,
  40. result_as_string=False,
  41. ):
  42. if options.get("do_rlimits", True):
  43. rlimits = [(resource.RLIMIT_NPROC, (0, 0))]
  44. for key, val in _resource_mapper.items():
  45. if key == "MEMORY" and options[key] <= 0:
  46. continue
  47. rlimits.append((val[0], val[1](options[key])))
  48. else:
  49. rlimits = []
  50. def limiter():
  51. os.setsid()
  52. for i in rlimits:
  53. resource.setrlimit(*i)
  54. context["csm_process"].set_pdeathsig()()
  55. tmpdir = context.get("csq_sandbox_dir", "/tmp/sandbox")
  56. this_one = "_%s" % uuid.uuid4().hex
  57. tmpdir = os.path.join(tmpdir, this_one)
  58. with open(
  59. os.path.join(
  60. context["cs_fs_root"],
  61. "__QTYPES__",
  62. "pythoncode",
  63. "__SANDBOXES__",
  64. "_template.py",
  65. )
  66. ) as f:
  67. template = f.read()
  68. template %= {
  69. "enable_opcode_count": count_opcodes,
  70. "result_as_string": result_as_string,
  71. "test_module": this_one,
  72. "opcode_limit": opcode_limit or float("inf"),
  73. }
  74. os.makedirs(tmpdir, 0o777)
  75. with open(os.path.join(tmpdir, "run_catsoop_test.py"), "w") as f:
  76. f.write(template)
  77. for f in options["FILES"]:
  78. typ = f[0].strip().lower()
  79. if typ == "copy":
  80. shutil.copyfile(f[1], os.path.join(tmpdir, f[2]))
  81. elif typ == "string":
  82. with open(os.path.join(tmpdir, f[1]), "w") as fileobj:
  83. fileobj.write(f[2])
  84. fname = "%s.py" % this_one
  85. with open(os.path.join(tmpdir, fname), "w") as fileobj:
  86. fileobj.write(code.replace("\r\n", "\n"))
  87. LOGGER.debug(
  88. "[pythoncode.sandbox.python] context cs_version=%s, cs_python_interpreter=%s"
  89. % (context.get("cs_version"), context.get("cs_python_interpreter"))
  90. )
  91. interp = context.get(
  92. "csq_python_interpreter", context.get("cs_python_interpreter", "python3")
  93. )
  94. try:
  95. p = subprocess.Popen(
  96. [interp, "-E", "-B", "run_catsoop_test.py"],
  97. cwd=tmpdir,
  98. preexec_fn=limiter,
  99. bufsize=0,
  100. stdin=subprocess.PIPE,
  101. stdout=subprocess.PIPE,
  102. stderr=subprocess.PIPE,
  103. )
  104. except Exception as err:
  105. LOGGER.error(
  106. "[pythoncode.sandbox.python] error executing subprocess, interp=%s, fname=%s, tmpdir=%s, preexec_fn=%s"
  107. % (interp, fname, tmpdir, limiter)
  108. )
  109. raise Exception(
  110. "[cs.qtypes.pythoncode.python] Failed to execute subprocess interp=%s (need to set csq_python_interpreter?), err=%s"
  111. % (interp, err)
  112. )
  113. out = ""
  114. err = ""
  115. try:
  116. out, err = p.communicate(options["STDIN"] or "", timeout=options["CLOCKTIME"])
  117. except subprocess.TimeoutExpired:
  118. p.kill()
  119. p.wait()
  120. out, err = p.communicate()
  121. out = out.decode()
  122. err = err.decode()
  123. shutil.rmtree(tmpdir, True)
  124. n = out.rsplit("---", 1)
  125. log = {}
  126. if len(n) == 2: # should be this
  127. out, log = n
  128. try:
  129. log = context["csm_util"].literal_eval(log)
  130. except:
  131. log = {}
  132. if log == {} or log.get("opcode_limit_reached", False):
  133. if err.strip() == "":
  134. err = (
  135. "Your code did not run to completion, "
  136. "but no error message was returned."
  137. "\nThis normally means that your code contains an "
  138. "infinite loop or otherwise took too long to run."
  139. )
  140. if len(n) > 2: # ???
  141. out = ""
  142. log = {}
  143. err = "BAD CODE - this will be logged"
  144. return {"fname": fname, "out": out, "err": err, "info": log}