首先解释下什么是AJC,其实就是alxwvj_judge_core。vj系统本身设计成可以支持多种oj,所以又一个目录下专门放置各个oj的各种采集模块。ajc其实也是写成这种结构的一个模块,只不过它是一个本地的oj模块。没错,vj
+ ajc秒变oj。

这里我借鉴了几个开源项目。其一是lo-runner,一个用python写的oj核心,所以其实我大部分时间是在处理逻辑问题而不是处理安全性问题。其二就是hustoj的源码,因为基本现在大部分学校的校内oj都是基于hustoj搭建的。

这里简单说几下oj核心的实现流程:

  1. 检测数据库提交的Record并且入队列并更新数据库状态为waiting
  2. 多线程worker从队列取任务
  3. 取到任务的worker取相应代码和数据,编译执行对比结果
  4. worker更新相应数据库状态

其中比较关键的两个问题就是多线程和评测安全性:

  1. 直接使用python2的多线程,受限制于GIL,此部分代码移植自amcjudger
  2. 安全性问题,原来的acmjudger里面解决的不好。查看文档发现lo-runner提供了文件白名单和系统调用白名单。参考了hustoj的白名单(写的太乱Orz...),最后才用了这份白名单(32位-static编译):
| sys_call         | id     |
|:----------------:|:------:|
| name             | 122    |
| brk              | 45     |
| set_thread_area  | 243    |
| readlink         | 85     |
| access           | 33     |
| fstat64          | 197    |
| mmap2            | 192    |
| exit_group       | 252    |

这份名单其实很好采集,更改lo-runner的源码syscalls拦截部分,一律放行并且printf(),调用的时候开启白名单即可,如图:
pyuse.png

github代码中的白名单是混合了32位和64位的表只保证两张架构下能够运行但是安全性会有问题!需要具体更改!

直接放地址吧:Github

贴上编译的核心部分,超时自动结束防止 #include</dev/null> 等卡编译的操作:

p = subprocess.Popen(
    build_cmd[language],
    shell=True,
    cwd=dir_work,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    close_fds=True)

kill_proc = lambda p: p.kill()
timer = threading.Timer(5, kill_proc, [p])
try:
    timer.start()
    out, err = p.communicate()  # 获取编译错误信息
finally:
    timer.cancel()

if p.returncode == 0:  # 返回值为0,编译成功
    return True