├── .gitignore ├── README.md ├── check_dangerout_code.py ├── compile.py ├── config.py ├── db.py ├── db_yield.py ├── deal_data.py ├── faq.md ├── get_code.py ├── judge_main.py ├── judge_one.py ├── judge_result.py ├── judger.png ├── lorun ├── .gitignore ├── README.md ├── demo │ ├── 1498 │ │ ├── 0.in │ │ └── 0.out │ ├── 1602 │ │ ├── 0.in │ │ └── 0.out │ ├── 1602.c │ ├── Main.class │ ├── Main.java │ ├── a+b.c │ ├── a.out │ ├── m │ ├── temp.out │ ├── test.py │ └── testdata │ │ ├── 0.in │ │ ├── 0.out │ │ ├── 1.in │ │ ├── 1.out │ │ ├── 2.in │ │ └── 2.out └── src │ ├── access.c │ ├── access.h │ ├── convert.c │ ├── convert.h │ ├── diff.c │ ├── diff.h │ ├── limit.c │ ├── limit.h │ ├── lorun.c │ ├── lorun.h │ ├── run.c │ ├── run.h │ └── setup.py ├── nohup.out ├── producer.py ├── protect.py ├── result.jpg ├── run_program.py ├── start.sh ├── stop.sh └── worker.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACM在线测评系统评测程序设计与实现 2 | 3 | ### 写此文目的 4 | 5 | - 让外行人了解ACM,重视ACM 6 | - 让ACMer了解评测程序评测原理以便更好得做题 7 | - 让pythoner了解如何使用更好的使用python 8 | 9 | 在讲解之前,先给外行人补充一些关于ACM的知识。 10 | 11 | ### 什么是ACM? 12 | 13 | 我们平常指的ACM是`ACM/ICPC`(国际大学生程序设计竞赛),这是由ACM(Association for Computing Machinery,美国计算机协会)组织的年度性竞赛,始于1970年,是全球大学生计算机程序能力竞赛活动中最有影响的一项赛事。被誉为计算机界奥林匹克。 14 | 15 | 了解更多关于ACM的信息可以参考: 16 | 17 | - 百度百科:[http://baike.baidu.com/view/201684.htm](http://baike.baidu.com/view/201684.htm) 18 | - 维基百科:[http://zh.wikipedia.org/wiki/ACM国际大学生程序设计竞赛](http://zh.wikipedia.org/wiki/ACM国际大学生程序设计竞赛) 19 | - ACM国际大学生程序设计竞赛指南:[http://xinxi.100xuexi.com/view/trend/20120328/47133.html](http://xinxi.100xuexi.com/view/trend/20120328/47133.html) 20 | 21 | ### 什么是ACM测评系统? 22 | 23 | 为了让同学们拥有一个练习和比赛的环境,需要一套系统来提供服务。 24 | 25 | 系统要提供如下功能: 26 | 27 | - 用户管理 28 | - 题目管理 29 | - 比赛管理 30 | - 评测程序 31 | 32 | 典型的ACM评测系统有两种 33 | 34 | - 一种是C/S模式,典型代表是PC^2。主要用在省赛,区预赛,国际赛等大型比赛中。官网:[http://www.ecs.csus.edu/pc2/](http://www.ecs.csus.edu/pc2/) 35 | - 另一种是B/S模式,国内外有几十个类似网站,主要用于平常练习和教学等。国内比较流行的OJ有: 36 | - 杭州电子科技大学:[http://acm.hdu.edu.cn/](http://acm.hdu.edu.cn/) 37 | - 北京大学:[http://poj.org/](http://poj.org/) 38 | - 浙江大学:[http://acm.zju.edu.cn/onlinejudge/](http://acm.zju.edu.cn/onlinejudge/) 39 | - 山东理工大学:[http://acm.sdut.edu.cn/sdutoj/index.php](http://acm.sdut.edu.cn/sdutoj/index.php) 40 | 41 | ### 评测程序是做什么的? 42 | 43 | 评测程序就是对用户提交的代码进行编译,然后执行,将执行结果和OJ后台正确的测试数据进行比较,如果答案和后台数据完全相同就是AC(Accept),也就是你的程序是正确的。否则返回错误信息,稍后会详细讲解。 44 | 45 | ### ACM在线测评系统整体架构 46 | 47 | 为了做到低耦合,我们以数据库为中心,前台页面从数据库获取题目、比赛列表在浏览器上显示,用户通过浏览器提交的代码直接保存到数据库。 48 | 49 | 评测程序负责从数据库中取出用户刚刚提交的代码,保存到文件,然后编译,执行,评判,最后将评判结果写回数据库。 50 | 51 | ### 评测程序架构 52 | 53 | 评测程序要不断扫描数据库,一旦出现没有评判的题目要立即进行评判。为了减少频繁读写数据库造成的内存和CPU以及硬盘开销,可以每隔0.5秒扫描一次。为了提高评测速度,可以开启几个进程或线程共同评测。由于多线程/进程会竞争资源,对于扫描出来的一个题目,如果多个评测进程同时去评测,可能会造成死锁,为了防止这种现象,可以使用了`生产者-消费者`模式,也就是建立一个待评测题目的任务队列,这个队列的`生产者`作用就是扫描数据库,将数据库中没有评测的题目列表增加到任务队列里面。消费者作用就是从队列中取出要评测的数据进行评测。 54 | 55 | ### 为什么任务队列能防止出现资源竞争和死锁现象? 56 | 57 | python里面有个模块叫`Queue`,我们可以使用这个模块建立三种类型的队列: 58 | 59 | - FIFO:先进先出队列 60 | - LIFO:后进先出队列 61 | - 优先队列 62 | 63 | 这里我们用到的是先进先出队列,也就是先被添加到队列的代码先被评测,保持比赛的公平性。 64 | 65 | 队列可以设置大小,默认是无限大。 66 | 67 | 生产者发现数据库中存在没有评测的题目之后,使用put()方法将任务添加到队列中。这时候如果队列设置大小并且已经满了的话,就不能再往里面放了,这时候生产者就进入了等待状态,直到可以继续往里面放任务为止。在等待状态的之后生产者线程已经被阻塞了,也就是说不再去扫描数据库,因此适当设置队列的大小可以减少对数据库的读写次数。 68 | 69 | 消费者需要从任务队列获取任务,使用`get()`方法,一旦某个线程从队列`get`得到某个任务之后,其他线程就不能再次得到这个任务,这样可以防止多个评测线程同时评测同一个程序而造成死锁。如果任务队列为空的话,get()方法不能获得任务,这时候评线程序就会阻塞,等待任务的到来。在被阻塞的时候评测程序可以被看做停止运行了,可以明显减少系统资源消耗。 70 | 71 | 队列还有两个方法: 72 | 73 | 一个是`task_done()`,这个方法是用来标记队列中的某个任务已经处理完毕。 74 | 75 | 另一个是`join()`方法,join方法会阻塞程序直到所有的项目被删除和处理为止,也就是调用`task_done()`方法。 76 | 77 | 这两个方法有什么作用呢?因为评测也需要时间,一个任务从队列中取出来了,并不意味着这个任务被处理完了。如果没有处理完,代码的状态还是未评判,那么生产者会再次将这个代码从数据库取出加到任务队列里面,这将造成代码重复评测,浪费系统资源,影响评测速度。这时候我们需要合理用这两个方法,保证每个代码都被评测并且写回数据库之后才开始下一轮的扫描。后面有代码示例。 78 | 79 | 我们使用如下代码创建一个FIFO队列: 80 | 81 | ```python 82 | #初始化队列 83 | q = Queue(config.queue_size) 84 | ``` 85 | 86 | ### 如何有效得从数据库获取数据? 87 | 88 | 这里我们以mysql为例进行说明。python有数据库相关的模块,使用起来很方便。这里我们需要考虑异常处理。 89 | 90 | 有可能出现的问题是数据库重启了或者偶尔断开了不能正常连接,这时候就需要不断尝试重新连接直到连接成功。然后判断参数,如果是字符串就说明是sql语句,直接执行,如果是列表则依次执行所有的语句,如果执行期间出现错误,则关闭连接,返回错误信息。否则返回sql语句执行结果。 91 | 92 | 下面这个函数专门来处理数据库相关操作 93 | 94 | ```python 95 | def run_sql(sql): 96 | '''执行sql语句,并返回结果''' 97 | con = None 98 | while True: 99 | try: 100 | con = MySQLdb.connect(config.db_host,config.db_user,config.db_password, 101 | config.db_name,charset=config.db_charset) 102 | break 103 | except: 104 | logging.error('Cannot connect to database,trying again') 105 | time.sleep(1) 106 | cur = con.cursor() 107 | try: 108 | if type(sql) == types.StringType: 109 | cur.execute(sql) 110 | elif type(sql) == types.ListType: 111 | for i in sql: 112 | cur.execute(i) 113 | except MySQLdb.OperationalError,e: 114 | logging.error(e) 115 | cur.close() 116 | con.close() 117 | return False 118 | con.commit() 119 | data = cur.fetchall() 120 | cur.close() 121 | con.close() 122 | return data 123 | ``` 124 | 125 | 需要注意的是这里我们每次执行sql语句都要重新连接数据库,能否一次连接,多次操作数据库?答案是肯定的。但是,这里我们需要考虑的问题是如何将数据库的连接共享?可以设置一个全局变量。但是如果数据库的连接突然断开了,在多线程程序里面,问题就比较麻烦了,你需要在每个程序里面去判断是否连接成功,失败的话还要重新连接,多线程情况下如何控制重新连接?这些问题如果在每个sql语句执行的时候都去检查的话太麻烦了。 126 | 127 | 有一种方法可以实现一次连接,多次操作数据库,还能方便的进行数据库重连,那就是使用yield生成器,连接成功之后,通过yield将sql语句传递进去,执行结果通过yield反馈回来。这样听起来很好,但是有个问题容易被忽略,那就是yield在不支持多线程,多个线程同时向yield发送数据,yield接收谁?yield返回一个数据,谁去接收?这样yield就会报错,然后停止执行。当然可以使用特殊方法对yield进行加锁,保证每次都只有一个线程发送数据。 128 | 129 | 通过测试发现,使用yield并不能提高评测效率,而每次连接数据库也并不慢,毕竟现在服务器性能都很高。所以使用上面的每次连接数据库的方法还是比较好的。 130 | 131 | 还有一个问题,当多线程同时对数据库进行操作的时候,也容易出现一些莫名其妙的错误,最好是对数据库操作加锁: 132 | 133 | ```python 134 | #创建数据库锁,保证一个时间只能一个程序都写数据库 135 | dblock = threading.Lock() 136 | # 读写数据库之前加锁 137 | dblock.acquire() 138 | # 执行数据库操作 139 | runsql() 140 | # 执行完毕解锁 141 | dblock.release() 142 | ``` 143 | 144 | ### 生产者如何去实现? 145 | 146 | 为了隐藏服务器信息,保证服务器安全,所有的SQL语句都用五个#代替。 147 | 148 | 生产者就是一个while死循环,不断扫描数据库,扫描到之后就向任务队列添加任务。 149 | 150 | ```python 151 | def put_task_into_queue(): 152 | '''循环扫描数据库,将任务添加到队列''' 153 | while True: 154 | q.join() #阻塞安程序,直到队列里面的任务全部完成 155 | sql = "#####" 156 | data = run_sql(sql) 157 | for i in data: 158 | solution_id,problem_id,user_id,contest_id,pro_lang = i 159 | task = { 160 | "solution_id":solution_id, 161 | "problem_id":problem_id, 162 | "contest_id":contest_id, 163 | "user_id":user_id, 164 | "pro_lang":pro_lang, 165 | } 166 | q.put(task) 167 | time.sleep(0.5) #每次扫面完后等待0.5秒,减少CPU占有率 168 | ``` 169 | 170 | ### 消费者如何实现? 171 | 172 | 基本是按照上面说的来的,先获取任务,然后处理任务,最后标记任务处理完成。 173 | 174 | ```python 175 | def worker(): 176 | '''工作线程,循环扫描队列,获得评判任务并执行''' 177 | while True: 178 | #获取任务,如果队列为空则阻塞 179 | task = q.get() 180 | #获取题目信息 181 | solution_id = task['solution_id'] 182 | problem_id = task['problem_id'] 183 | language = task['pro_lang'] 184 | user_id = task['user_id'] 185 | # 评测 186 | result=run(problem_id,solution_id,language,data_count,user_id) 187 | #将结果写入数据库 188 | dblock.acquire() 189 | update_result(result) 190 | dblock.release() 191 | #标记一个任务完成 192 | q.task_done() 193 | ``` 194 | 195 | ### 如何启动多个评测线程? 196 | 197 | ```python 198 | def start_work_thread(): 199 | '''开启工作线程''' 200 | for i in range(config.count_thread): 201 | t = threading.Thread(target=worker) 202 | t.deamon = True 203 | t.start() 204 | ``` 205 | 206 | 这里要注意`t.deamon=True`,这句的作用是当主线程退出的时候,评测线程也一块退出,不在后台继续执行。 207 | 208 | 209 | ### 消费者获取任务后需要做什么处理? 210 | 211 | 因为代码保存在数据库,所以首先要将代码从数据库取出来,按文件类型命名后保存到相应的评判目录下。然后在评判目录下对代码进行编译,如果编译错误则将错误信息保存到数据库,返回编译错误。编译通过则运行程序,检测程序执行时间和内存,评判程序执行结果。 212 | 213 | ### 如何编译代码? 214 | 215 | 根据不同的编程语言,选择不同的编译器。我的评测程序支持多种编程语言。编译实际上就是调用外部编译器对代码进行编译,我们需要获取编译信息,如果编译错误,需要将错误信息保存到数据库。 216 | 217 | 调用外部程序可以使用python的subprocess模块,这个模块非常强大,比os.system()什么的牛逼多了。里面有个Popen方法,执行外部程序。设置`shell=True`我们就能以shell方式去执行命令。可以使用`cwd`指定工作目录,获取程序的外部输出可以使用管道PIPE,调用`communicate()`方法可以可以获取外部程序的输出信息,也就是编译错误信息。 218 | 219 | 可以根据编译程序的返回值来判断编译是否成功,一般来说,返回值为0表示编译成功。 220 | 221 | 有些语言,比如`ruby`和`perl`是解释型语言,不提供编译选项,因此在这里仅仅加上`-c`参数做简单的代码检查。 222 | 223 | `python`,`lua`,`java`等可以编译成二进制文件然后解释执行。 224 | 225 | ACMer们着重看一下`gcc`和`g++`和`pascal`的编译参数,以后写程序可以以这个参数进行编译,只要在本地编译通过一般在服务器上编译就不会出现编译错误问题。 226 | 227 | 可能有些朋友会有疑问:为什么加这么多语言?正式ACM比赛只让用`C`,`C++`和`JAVA`语言啊!对这个问题,我只想说,做为一个在线测评系统,不能仅仅局限在ACM上。如果能让初学者用这个平台来练习编程语言不是也很好?做ACM是有趣的,用一门新的语言去做ACM题目也是有趣的,快乐的去学习一门语言不是学得很快?我承认,有好多语言不太适合做ACM,因为ACM对时间和内存要求比较严格,好多解释执行的语言可能占内存比较大,运行速度比较慢,只要抱着一种学习编程语言的心态去刷题就好了。此外,对于新兴的`go`语言,我认为是非常适合用来做ACM的。牛逼的`haskell`语言也值得一学,描述高级数据结果也很方便。感兴趣的可以试试。 228 | 229 | 我的评测程序是可以扩展的,如果想再加其他编程语言,只要知道编译参数,知道如何执行,配置好编译器和运行时环境,在评测程序里面加上就能编译和评测。 230 | 231 | ```python 232 | def compile(solution_id,language): 233 | '''将程序编译成可执行文件''' 234 | build_cmd = { 235 | "gcc" : "gcc main.c -o main -Wall -lm -O2 -std=c99 --static -DONLINE_JUDGE", 236 | "g++" : "g++ main.cpp -O2 -Wall -lm --static -DONLINE_JUDGE -o main", 237 | "java" : "javac Main.java", 238 | "ruby" : "ruby -c main.rb", 239 | "perl" : "perl -c main.pl", 240 | "pascal" : 'fpc main.pas -O2 -Co -Ct -Ci', 241 | "go" : '/opt/golang/bin/go build -ldflags "-s -w" main.go', 242 | "lua" : 'luac -o main main.lua', 243 | "python2": 'python2 -m py_compile main.py', 244 | "python3": 'python3 -m py_compile main.py', 245 | "haskell": "ghc -o main main.hs", 246 | } 247 | p = subprocess.Popen(build_cmd[language],shell=True,cwd=dir_work,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 248 | out,err = p.communicate()#获取编译错误信息 249 | if p.returncode == 0: #返回值为0,编译成功 250 | return True 251 | dblock.acquire() 252 | update_compile_info(solution_id,err+out) #编译失败,更新题目的编译错误信息 253 | dblock.release() 254 | return False 255 | ``` 256 | 257 | ### 用户代码在执行过程中是如何进行评判的(ACMer必看)? 258 | 259 | 前面说了,如果出现编译错误(Compile Error),是不会执行的。每个题目都有一个标准的时间和内存限制,例如时间`1000ms`,内存`65536K`,程序在执行的时候会实时检查其花费时间和使用内存信息,如果出现超时和超内存将会分别返回`Time Limit Exceeded`和`Memory Limit Exceeded`错误信息,如果程序执行时出现错误,比如非法指针,数组越界等,将会返回`Runtime Error`信息。如果你的程序没有出现上面的信息,说明程序顺利执行结束了。接下来,就是对你的程序的输出也就是运行结果进行检查,如果你的执行结果和我们的标准答案完全一样,则返回`Accepted`,也就说明你这个题目做对了。如果除去空格,换行,tab外完全相同,则说明你的代码格式错误,将返回`Presentation Error`,如果你输出的内容有一部分和标准答案完全一样,但是还输出了一些其他内容,则说明你多输出了,这时候将返回`Output Limit Exceeded`错误信息,出现其他情况,就说明你的输出结果和标准答案不一样,就是`Wrong Answer`了。 260 | 261 | 总结一下错误的出现顺序: 262 | 263 | `Compile Error` -> `Memory Limit Exceeded` = `Time Limit Exceeded` = `Runtime Error` -> `Wrong Answer` -> `Output Limit Exceeded` -> `Presentation Error` -> `Accepted` 264 | 265 | 直接说难免有些空洞,做了张流程图: 266 | 267 | ![result.jpg](result.jpg) 268 | 269 | 如果你得到了其他信息,比如`System error`,则说明服务器端可能出问题了,我们技术人员会想法解决。如果看到`waiting`,说明等待评测的代码比较多,你需要稍作等待,直到代码被评测。如果你得到了`Judging`结果,说明你的代码正在评测,如果长时间一直是`Judging`,则说明评测程序在评测过程中可能出问题了,没有评判出结果就停止了。技术人员会为你重判的。 270 | 271 | 希望ACMer们能根据上面的评测流程,在看到自己的评判结果的时候,能够分析出你离AC还有多远,以及如何改进你的代码才能AC。 272 | 273 | 评判答案的那部分源码: 274 | 275 | ``` 276 | def judge_result(problem_id,solution_id,data_num): 277 | '''对输出数据进行评测''' 278 | currect_result = os.path.join(config.data_dir,str(problem_id),'data%s.out'%data_num) 279 | user_result = os.path.join(config.work_dir,str(solution_id),'out%s.txt'%data_num) 280 | try: 281 | curr = file(currect_result).read().replace('\r','').rstrip()#删除\r,删除行末的空格和换行 282 | user = file(user_result).read().replace('\r','').rstrip() 283 | except: 284 | return False 285 | if curr == user: #完全相同:AC 286 | return "Accepted" 287 | if curr.split() == user.split(): #除去空格,tab,换行相同:PE 288 | return "Presentation Error" 289 | if curr in user: #输出多了 290 | return "Output limit" 291 | return "Wrong Answer" #其他WA 292 | ``` 293 | 294 | 注意一下,代码中有个`replace('\r','')`方法,它的作用就是将`\r`替换成空字符串。为什么要做这个替换呢?因为在windows下,文本的换行是`"\r\n"`,而在Linux下是`"\n"`。因为不能确定测试数据来源与windows还是Linux,增加一个`\r`,就是增加一个字符,如果不删除的话,两个文本就是不一样的,就会造成`wrong answer`结果。或许你曾经遇到过在windows下用记事本打开一个纯文本文件,格式全乱了,所有文本都在一行内,非常影响阅读。你可以通过用写字板打开来解决这个问题。据说`"\r\n"`来源于比较古老的打印机,每打印完一行,都要先`“回车(\r)”`,再`“换行”(\n)`。同样一个C语言的`printf("\n")`函数,在windows下将生成`"\r\n"`,而在Linux下生成`"\n"`,因为评测程序为你自动处理了,因此你就不必关注这些细节的东西了。 295 | 296 | ### 评测程序是如何检测你的程序的执行时间和内存的? 297 | 298 | 这个问题困扰了我好久,也查了好多资料。 299 | 300 | 用户的程序要在服务器上执行,首先不能让用户的程序无限申请内存,否则容易造成死机现象,需要将程序的内存限制在题目规定的最大内存内。其次要限制用户程序的执行时间,不能让用户的程序无限制运行。 301 | 302 | 一般解决方案是:在用户的程序执行前,先做好资源限制,限制程序能使用的最大内存和CPU占用,当用户的程序一旦超出限制就自动终止了。还有个比较重要的问题是如何获取程序执行期间的最大内存占用率。用户的代码在执行前需要申请内存,执行期间还能动态申请和释放内存,执行完毕释放内存。程序执行时还有可能使用指针等底层操作,这无疑给检测内存造成更大的困难。在windows下,程序执行结束后,可以调用系统函数获取程序执行期间的最大内存,貌似在Linux下没用现成的函数可以调用。 303 | 304 | 在Linux下,我们可以使用`ps`或`top`命令来获取或监视在某个时刻应用程序的内存占用率,要获取程序的最大执行内存,就要不断去检测,不断去比较,直到程序结束,获取最大值就是用户程序执行期间的最大内存。根据这个设想,我写了一个程序来实现这个想法: 305 | 306 | ```python 307 | def get_max_mem(pid): 308 | '''获取进程号为pid的程序的最大内存''' 309 | glan = psutil.Process(pid) 310 | max = 0 311 | while True: 312 | try: 313 | rss,vms = glan.get_memory_info() 314 | if rss > max: 315 | max = rss 316 | except: 317 | print "max rss = %s"%max 318 | return max 319 | 320 | def run(problem_id,solution_id,language,data_count,user_id): 321 | '''获取程序执行时间和内存''' 322 | time_limit = (time_limit+10)/1000.0 323 | mem_limit = mem_limit * 1024 324 | max_rss = 0 325 | max_vms = 0 326 | total_time = 0 327 | for i in range(data_count): 328 | '''依次测试各组测试数据''' 329 | args = shlex.split(cmd) 330 | p = subprocess.Popen(args,env={"PATH":"/nonexistent"},cwd=work_dir,stdout=output_data,stdin=input_data,stderr=run_err_data) 331 | start = time.time() 332 | pid = p.pid 333 | glan = psutil.Process(pid) 334 | while True: 335 | time_to_now = time.time()-start + total_time 336 | if psutil.pid_exists(pid) is False: 337 | program_info['take_time'] = time_to_now*1000 338 | program_info['take_memory'] = max_rss/1024.0 339 | program_info['result'] = result_code["Runtime Error"] 340 | return program_info 341 | rss,vms = glan.get_memory_info() 342 | if p.poll() == 0: 343 | end = time.time() 344 | break 345 | if max_rss < rss: 346 | max_rss = rss 347 | print 'max_rss=%s'%max_rss 348 | if max_vms < vms: 349 | max_vms = vms 350 | if time_to_now > time_limit: 351 | program_info['take_time'] = time_to_now*1000 352 | program_info['take_memory'] = max_rss/1024.0 353 | program_info['result'] = result_code["Time Limit Exceeded"] 354 | glan.terminate() 355 | return program_info 356 | if max_rss > mem_limit: 357 | program_info['take_time'] = time_to_now*1000 358 | program_info['take_memory'] = max_rss/1024.0 359 | program_info['result'] =result_code["Memory Limit Exceeded"] 360 | glan.terminate() 361 | return program_info 362 | 363 | logging.debug("max_rss = %s"%max_rss) 364 | # print "max_rss=",max_rss 365 | logging.debug("max_vms = %s"%max_vms) 366 | # logging.debug("take time = %s"%(end - start)) 367 | program_info['take_time'] = total_time*1000 368 | program_info['take_memory'] = max_rss/1024.0 369 | program_info['result'] = result_code[program_info['result']] 370 | return program_info 371 | ``` 372 | 373 | 上面的程序用到了一些进程控制的一些知识,简单说明一下。 374 | 375 | 程序的基本原理是:先用多进程库subprocess的Popen函数去创建一个新的进程,获取其进程号(pid),然后用主线程去监测这个进程,主要是监测实时的内存信息。通过比较函数,获得程序的执行期间的最大内存。什么时候停止呢?有四种情况: 376 | 377 | 1. 程序运行完正常结束。这个我们可以通过 subprocess.Popen里面的poll方法来检测,如果为0,则代表程序正常结束。 378 | 2. 程序执行时间超过了规定的最大执行时间,用terminate方法强制程序终止 379 | 3. 程序执行内存超过了规定的最大内存,terminate强制终止。 380 | 4. 程序执行期间出现错误,异常退出了,这时候我们通过检查这个pid的时候就会发现不存在。 381 | 382 | 还有一点是值得注意的:上文提到在编译程序的时候,调用`subprocess.Popen`,是通过shell方式调用的,但是这里没有使用这种方式,为什么呢?这两种方式有什么区别?最大的区别就是返回的进程的pid,以shell方式执行,返回的pid并不是子进程的真正pid,而是shell的pid,当我们去检查这个pid的内存使用率的时候得到的并不是用户进程的pid!不通过shell方式去调用外部程序则是直接返回真正程序的pid,而不用去调用shell。官方文档是这么说的:`if shell is true, the specified command will be executed through the shell.` 383 | 384 | 如果不用shell方式去执行命令的话,传递参数的时候就不能直接将字符串传递过去,例如`ls -l`这个命令`ls`和参数`-l`,当`shell=False`时,需要将命令和参数变成一个列表`['ls','-l']`传递过去。当参数比较复杂的时候,将命令分隔成列表就比较麻烦,幸好python为我们提供了`shlex`模块,里面的split方法就是专门用来做这个的,官方文档是这么说的:`Split the string s using shell-like syntax.`,最好不要自己去转换,有可能会导致错误而不能执行。 385 | 386 | ### 上面的检测内存和时间的方法靠谱吗? 387 | 388 | 不靠谱,相当不靠谱!(当然学学python如何对进程控制也没坏处哈!)为什么呢?有点经验的都知道,C语言的运行效率比python高啊!执行速度比python快!这会造成什么后果?一个简单的`hello world`小程序,C语言“瞬间”就执行完了,还没等我的python程序开始检测就执行完了,我的评测程序什么都没检测到,然后返回0,再小的程序内存也不可能是0啊!在OJ上显示内存为0相当不科学! 389 | 390 | 那怎么办?能不能让C语言的程序执行速度慢下来?CPU的频率是固定的,我们没法专门是一个程序的占用的CPU频率降低,在windows下倒是有`变速齿轮`这款软件可以让软件执行速度`变慢`,不知道在`Linux`下有没有。还有没有其他办法?聪明的你也许会想到`gdb`调试,我也曾经想用这种方法,用gdb调试可以使程序单步执行,然后程序执行一步,我检测一次,多好,多完美!研究了好一阵子gdb,发现并不是那么简单。首先,我们以前用gdb调试C/C++的时候,在编译的时候要加上一个`-g`参数,然后执行的时候可以单步执行,此外,还有设置断点什么的。有几个问题: 391 | 392 | 1. 其他语言如何调试?比如java,解释执行的,直接调试java虚拟机吗? 393 | 2. 如何通过python对gdb进行控制?还有获取执行状态等信息。 394 | 395 | 这些问题都不是很好解决。 396 | 397 | 那上面的方法测量的时间准吗?不准!为什么?我们说的程序的执行时间,严格来说是占用CPU的时间。因为CPU采用的是轮转时间片机制,在某个时刻,CPU在忙别的程序。上面的方法用程序执行的结束时间减去开始时间,得到的时间一定比它实际执行的时间要大。如果程序执行速度过快,不到1毫秒,评测程序也不能检测出来,直接返回0了。 398 | 399 | ### 如何解决时间和内存的测量问题? 400 | 401 | 后来在`v2ex`上发了一个帖子提问,得到高人指点,使用`lorun`。`lorun`是github上的一个开源项目,项目地址:[https://github.com/lodevil/Lo-runner](https://github.com/lodevil/Lo-runner),这是用C语言写的一个python扩展模块,让程序在一个类似沙盒的环境下执行,然后精准的获取程序的执行时间和内存,还能对程序进行限制,限制程序的系统调用。原文是这么说的:`We use this python-c library to run program in a sandbox-like environment. With it, we can accurately known the resource using of the program and limit its resource using including system-call interrupt.`。安装使用都非常方便。我主要用它来测量执行时间和内存,后期代码检查还是用我的程序。 402 | 403 | 感兴趣的同学可以将这个模块下载下来,作为本地测试使用,可以预先生成一些测试数据,然后测量你的代码的执行时间和内存,比对你的答案是否正确。 404 | 405 | ### 不同编程语言时间内存如何限定? 406 | 407 | 一般来说,假设C/C++语言的标程是时间限制:1000ms,内存限制32768K,那么java的时间和内存限制都是标准限制的2倍,即2000ms,65536K。 408 | 409 | 由于后来我再OJ增加了好多其他语言,我是这样规定的:编译型的语言和速度较快的解释型语言的时间和内存限制和C/C++是一样的,这样的语言包括:`C、C++、go、haskell、lua、pascal`,其他速度稍慢的解释执行的语言和JAVA是一样的,包括:`java、python2、python3、ruby、perl`。毕竟使用除C,C++,JAVA外的语言的朋友毕竟是少数,如果限制太严格的话可以根据实际情况对其他编程语言放宽限制。 410 | 411 | ### 多组测试数据的题目时间和内存如何测算? 412 | 413 | 多组测试数据是一组一组依次执行,时间和内存取各组的最大值,一旦某组测试数据时间和内存超出限制,则终止代码执行,返回超时或超内存错误信息。 414 | 415 | ### 如何防止恶意代码破坏系统? 416 | 417 | 我们可以使用以下技术来对用户程序进行限制: 418 | 419 | 1. `lorun`模块本身就有限制,防止外部调用 420 | 2. 降低程序的执行权限。在Linux下,目录权限一般为`755`,也就是说,如果换成一个别的用户,只要不是所有者,就没有修改和删除的权限。python里面可以使用`os.setuid(int(os.popen("id -u %s"%"nobody").read()))`来将程序以`nobody`用户的身份执行 421 | 3. 设置沙盒环境,将用户执行环境和外部隔离。Linux下的chroot命令可以实现,python也有相关方法,但是需要提前搭建沙盒环境。用`jailkit`可以快速构建沙盒环境,感兴趣的朋友可以看看 422 | 4. 使用`ACL访问控制列表`进行详细控制,让`nobody`用户只有对某个文件夹的读写权限,其他文件夹禁止访问 423 | 5. 评判机和服务器分离,找单独的机器,只负责评判 424 | 6. 对用户提交的代码预先检查,发现恶意代码直接返回`Runtime Error` 425 | 7. 禁止评测服务器连接外网,或者通过防火墙限制网络访问 426 | 427 | ### 如何启动和停止评测程序以及如何记录错误日志? 428 | 429 | 启动很简单,只要用python执行`protect.py`就行了。 430 | 431 | 如果需要后台执行的话可以使用Linux下的`nohup`命令。 432 | 433 | 为了防止同时开启多个评测程序,需要将以前开启的评测程序关闭。 434 | 435 | 为了方便启动,我写了这样一个启动脚本: 436 | 437 | ```bash 438 | #!/bin/bash 439 | sudo kill `ps aux | egrep "^nobody .*? protect.py" | cut -d " " -f4` 440 | sudo nohup python protect.py & 441 | ``` 442 | 443 | 第一条命令就是杀死多余的评测进程,第二条是启动评测程序。 444 | 445 | 在程序里面使用了logging模块,是专门用来记录日志的,这么模块很好用,也很强大,可定制性很强,对我们分析程序执行状态有很大帮助。下面是一些示例: 446 | 447 | ``` 448 | 2013-03-07 18:19:04,855 --- 321880 result 1 449 | 2013-03-07 18:19:04,857 --- judging 321882 450 | 2013-03-07 18:19:04,881 --- judging 321883 451 | 2013-03-07 18:19:04,899 --- judging 321884 452 | 2013-03-07 18:19:04,924 --- 321867 result 1 453 | 2013-03-07 18:19:04,950 --- 321883 result 7 454 | 2013-03-07 18:19:04,973 --- 321881 result 1 455 | 2013-03-07 18:19:05,007 --- 321884 result 1 456 | 2013-03-07 18:19:05,012 --- 321882 result 4 457 | 2013-03-07 18:19:05,148 --- judging 321885 458 | 2013-03-07 18:19:05,267 --- judging 321886 459 | 2013-03-07 18:19:05,297 --- judging 321887 460 | 2013-03-07 18:19:05,356 --- judging 321888 461 | 2013-03-07 18:19:05,386 --- judging 321889 462 | 2013-03-07 18:19:05,485 --- 321885 result 1 463 | ``` 464 | 465 | ### python的配置文件如何编写? 466 | 467 | 最简单有效的方式就是建立一个`config.py`文件,里面写上配置的内容,就像下面一样: 468 | 469 | ```python 470 | #!/usr/bin/env python 471 | #coding=utf-8 472 | #开启评测线程数目 473 | count_thread = 4 474 | #评测程序队列容量 475 | queue_size = 4 476 | #数据库地址 477 | db_host = "localhost" 478 | #数据库用户名 479 | db_user = "user" 480 | #数据库密码 481 | db_password = "password" 482 | #数据库名字 483 | db_name = "db_name" 484 | ``` 485 | 486 | 使用的时候只需要将这个文件导入,然后直接`config.queue_size`就可以访问配置文件里面的内容,很方便的。 487 | 488 | 489 | ### 评测程序的评测效率如何? 490 | 491 | 自从服务器启用新的评测程序之后,已经经历了两次大的比赛和几次大型考试,在几百个人的比赛和考试中,评测基本没用等待现象,用户提交的代码基本都能立即评测出来。大体测了一下,单服务器平均每秒能判6个题目左右(包括获取代码,编译,运行,检测,数据库写入结果等流程)。评测程序目前已经稳定运行了几个月,没有出现大的问题,应该说技术比较成熟了。 492 | 493 | ### 评测程序还能继续改进吗? 494 | 495 | 当时脑子估计是被驴踢了,居然使用多线程来评测!有经验的python程序猿都知道,python有个全局`GIL`锁,这个锁会将python的多个线程序列化,在一个时刻只允许一个线程执行,无论你的机器有多少个CPU,只能使用一个!这就明显影响评测速度!如果换成多进程方式,一个评测进程占用一个CPU核心,评测速度将会是几倍几十倍的性能提升!到时候弄个上千人的比赛估计问题也不大,最起码评测速度能保证。 496 | 497 | 此外,还可以构建一个分布式的评测服务器集群,大体设想了一下可以这样实现: 498 | 499 | 首先,可以选一台服务器A专门和数据库交互,包括从数据库中获取评测任务以及评测结束将结果写回数据库。然后选择N台普通计算机作为评测机,评测机只和数据库A打交道,也就是从服务器A获取任务,在普通机器上评测,评测完后将结果反馈到服务器A,再由A将结果写入到数据库。服务器A在这里就充当一个任务管理和分配的角色,协调各个评测机去评测。这样可以减少对数据库的操作,评测机就不用去一遍一遍扫数据库了。评测的速度和安全性可以得到进一步提升。 500 | 501 | ![judger.png](judger.png) 502 | 503 | 504 | ### 其他 505 | 506 | - 上面的程序和方法仅供学习和研究用,严禁任何非法用途 507 | - 本人学识有限,如有错误欢迎批评指正 508 | 509 | -------------------------------------------------------------------------------- /check_dangerout_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def check_dangerous_code(solution_id, language): 5 | if language in ['python2', 'python3']: 6 | code = file('/work/%s/main.py' % solution_id).readlines() 7 | support_modules = [ 8 | 're', # 正则表达式 9 | 'sys', # sys.stdin 10 | 'string', # 字符串处理 11 | 'scanf', # 格式化输入 12 | 'math', # 数学库 13 | 'cmath', # 复数数学库 14 | 'decimal', # 数学库,浮点数 15 | 'numbers', # 抽象基类 16 | 'fractions', # 有理数 17 | 'random', # 随机数 18 | 'itertools', # 迭代函数 19 | 'functools', 20 | #Higher order functions and operations on callable objects 21 | 'operator', # 函数操作 22 | 'readline', # 读文件 23 | 'json', # 解析json 24 | 'array', # 数组 25 | 'sets', # 集合 26 | 'queue', # 队列 27 | 'types', # 判断类型 28 | ] 29 | for line in code: 30 | if line.find('import') >= 0: 31 | words = line.split() 32 | tag = 0 33 | for w in words: 34 | if w in support_modules: 35 | tag = 1 36 | break 37 | if tag == 0: 38 | return False 39 | return True 40 | if language in ['gcc', 'g++']: 41 | try: 42 | code = file('/work/%s/main.c' % solution_id).read() 43 | except: 44 | code = file('/work/%s/main.cpp' % solution_id).read() 45 | if code.find('system') >= 0: 46 | return False 47 | return True 48 | # if language == 'java': 49 | # code = file('/work/%s/Main.java'%solution_id).read() 50 | # if code.find('Runtime.')>=0: 51 | # return False 52 | # return True 53 | if language == 'go': 54 | code = file('/work/%s/main.go' % solution_id).read() 55 | danger_package = [ 56 | 'os', 'path', 'net', 'sql', 'syslog', 'http', 'mail', 'rpc', 'smtp', 'exec', 'user', 57 | ] 58 | for item in danger_package: 59 | if code.find('"%s"' % item) >= 0: 60 | return False 61 | return True 62 | 63 | -------------------------------------------------------------------------------- /compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def compile(solution_id, language): 5 | low_level() 6 | '''将程序编译成可执行文件''' 7 | language = language.lower() 8 | dir_work = os.path.join(config.work_dir, str(solution_id)) 9 | build_cmd = { 10 | "gcc": 11 | "gcc main.c -o main -Wall -lm -O2 -std=c99 --static -DONLINE_JUDGE", 12 | "g++": "g++ main.cpp -O2 -Wall -lm --static -DONLINE_JUDGE -o main", 13 | "java": "javac Main.java", 14 | "ruby": "reek main.rb", 15 | "perl": "perl -c main.pl", 16 | "pascal": 'fpc main.pas -O2 -Co -Ct -Ci', 17 | "go": '/opt/golang/bin/go build -ldflags "-s -w" main.go', 18 | "lua": 'luac -o main main.lua', 19 | "python2": 'python2 -m py_compile main.py', 20 | "python3": 'python3 -m py_compile main.py', 21 | "haskell": "ghc -o main main.hs", 22 | } 23 | if language not in build_cmd.keys(): 24 | return False 25 | p = subprocess.Popen( 26 | build_cmd[language], 27 | shell=True, 28 | cwd=dir_work, 29 | stdout=subprocess.PIPE, 30 | stderr=subprocess.PIPE) 31 | out, err = p.communicate() # 获取编译错误信息 32 | err_txt_path = os.path.join(config.work_dir, str(solution_id), 'error.txt') 33 | f = file(err_txt_path, 'w') 34 | f.write(err) 35 | f.write(out) 36 | f.close() 37 | if p.returncode == 0: # 返回值为0,编译成功 38 | return True 39 | dblock.acquire() 40 | update_compile_info(solution_id, err + out) # 编译失败,更新题目的编译错误信息 41 | dblock.release() 42 | return False 43 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | #开启评测线程数目 4 | count_thread = 4 5 | #评测程序队列容量 6 | queue_size = 4 7 | #数据库地址 8 | db_host = "localhost" 9 | #数据库用户名 10 | db_user = "user" 11 | #数据库密码 12 | db_password = "password" 13 | #数据库名字 14 | db_name = "db_name" 15 | #数据库编码 16 | db_charset = "utf8" 17 | #work评判目录 18 | work_dir = "work_dir" 19 | #data测试数据目录 20 | data_dir = "data_dir" 21 | #自动清理评work目录 22 | auto_clean = True 23 | -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | 4 | import time 5 | import config 6 | import MySQLdb 7 | import logging 8 | import types 9 | 10 | def run_sql(sql): 11 | '''执行sql语句,并返回结果''' 12 | con = None 13 | while True: 14 | try: 15 | con = MySQLdb.connect(config.db_host,config.db_user,config.db_password, 16 | config.db_name,charset=config.db_charset) 17 | break 18 | except: 19 | logging.error('Cannot connect to database,trying again') 20 | time.sleep(1) 21 | cur = con.cursor() 22 | try: 23 | if type(sql) == types.StringType: 24 | cur.execute(sql) 25 | elif type(sql) == types.ListType: 26 | for i in sql: 27 | cur.execute(i) 28 | except MySQLdb.OperationalError,e: 29 | logging.error(e) 30 | cur.close() 31 | con.close() 32 | return False 33 | con.commit() 34 | data = cur.fetchall() 35 | cur.close() 36 | con.close() 37 | return data 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /db_yield.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | 4 | import time 5 | import config 6 | import MySQLdb 7 | import logging 8 | import types 9 | import threading 10 | 11 | 12 | class threadsafe_iter: 13 | """Takes an iterator/generator and makes it thread-safe by 14 | serializing call to the `next` method of given iterator/generator. 15 | """ 16 | def __init__(self, it): 17 | self.it = it 18 | self.lock = threading.Lock() 19 | 20 | def __iter__(self): 21 | return self 22 | 23 | def next(self): 24 | with self.lock: 25 | return self.it.next() 26 | def send(self,data): 27 | with self.lock: 28 | return self.it.send(data) 29 | def close(self): 30 | with self.lock: 31 | return self.it.close() 32 | def throw(self,data): 33 | with self.lock: 34 | return self.it.throw(data) 35 | 36 | 37 | def threadsafe_generator(f): 38 | """A decorator that takes a generator function and makes it thread-safe. 39 | """ 40 | def g(*a, **kw): 41 | return threadsafe_iter(f(*a, **kw)) 42 | return g 43 | 44 | def connect_to_db(): 45 | '''连接数据库,连接失败自动重新连接,防止因数据库重启或中断导致评测程序中断''' 46 | con = None 47 | while True: 48 | try: 49 | con = MySQLdb.connect(config.db_host,config.db_user,config.db_password, 50 | config.db_name,charset=config.db_charset) 51 | return con 52 | except: 53 | logging.error('Cannot connect to database,trying again') 54 | time.sleep(1) 55 | 56 | @threadsafe_generator 57 | def run_sql_yield(): 58 | '''执行sql语句,并返回结果''' 59 | con = connect_to_db() 60 | cur = con.cursor() 61 | data = None 62 | while True: 63 | sql = (yield data) 64 | try: 65 | if type(sql) == types.StringType: 66 | cur.execute(sql) 67 | elif type(sql) == types.ListType: 68 | for i in sql: 69 | cur.execute(i) 70 | except MySQLdb.OperationalError,e: 71 | logging.error(e) 72 | cur.close() 73 | con.close() 74 | con = connect_to_db() 75 | cur = con.cursor() 76 | data = False 77 | logging.error("yield db error!!!!!!!!!!!!") 78 | continue 79 | con.commit() 80 | data = cur.fetchall() 81 | cur.close() 82 | con.close() 83 | -------------------------------------------------------------------------------- /deal_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def clean_work_dir(solution_id): 5 | '''清理word目录,删除临时文件''' 6 | dir_name = os.path.join(config.work_dir, str(solution_id)) 7 | shutil.rmtree(dir_name) 8 | 9 | 10 | 11 | def get_data_count(problem_id): 12 | '''获得测试数据的个数信息''' 13 | full_path = os.path.join(config.data_dir, str(problem_id)) 14 | try: 15 | files = os.listdir(full_path) 16 | except OSError as e: 17 | logging.error(e) 18 | return 0 19 | count = 0 20 | for item in files: 21 | if item.endswith(".in") and item.startswith("data"): 22 | count += 1 23 | return count 24 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## 我的程序要在哪里输入和输出数据? 4 | 5 | 你的程序必须从stdin(基本输入)读入数据并且从stdout(基本输出)输出数据。例如,你使用C语言的话,使用scanf读入数据,使用printf输出数据,你使用C++语言的话,还可以使用cin和cout读入和输出数据。 6 | 7 | ** 注意:你提交的程序不能进行任何文件的读写操作,否则会返回“Wrong Answer”。 ** 8 | 9 | ## 本OJ提供哪些编译器?编译环境是怎么样的? 10 | 11 | 本OJ目前提供多种语言编译器,它们的编译参数分别是: 12 | 13 | - `gcc : gcc main.c -o main -Wall -lm -O2 -std=c99 --static -DONLINE_JUDGE` 14 | - `g++ : g++ main.cpp -O2 -Wall -lm --static -DONLINE_JUDGE -o main` 15 | - `java : javac Main.java` 16 | - `ruby : reek main.rb` 17 | - `perl : perl -c main.pl` 18 | - `pascal : fpc main.pas -O2 -Co -Ct -Ci` 19 | - `go : go build -ldflags "-s -w" main.go` 20 | - `lua : luac -o main main.lua` 21 | - `python2 : python2 -m py_compile main.py` 22 | - `python3 : python3 -m py_compile main.py` 23 | - `haskell : ghc -o main main.hs` 24 | 25 | 目前服务器运行在Linux平台下,提供的编译器的版本分别是: 26 | 27 | ``` 28 | gcc 4.4.5 29 | g++ 4.4.5 30 | java 1.6.2 31 | pascal 2.4.0 32 | ruby 2.0 33 | lua 5.1.4 34 | perl 5.10 35 | python2 2.7.3 36 | python3 3.3.0 37 | go 1.0 38 | ghc 7.6.2 39 | ``` 40 | 41 | ## 为什么我提交的程序在OJ运行得到了Compile Error?而在我的电脑上运行得很好? 42 | 43 | 不同的编译器之间有一些语法的差异,如果你电脑上使用的编译器与本OJ提供的编译器不同,请注意它们之间的差异,提交时请使用相应的编译器进行提交。 44 | 45 | 46 | ## 我要怎么使用64-bit整型? 47 | 48 | `C/C++` 评测程序只支持`long long`,不支持`int64` 49 | 50 | 有符号64-bit整型,取值范围为:`-9223372036854775808` 到 `9223372036854775807`。 51 | 52 | 无符号64-bit整型,取值范围为:`0` 到 `18446744073709551615`。 53 | 54 | ## OJ返回的结果分别是什么意思? 55 | 56 | 以下是OJ可能返回的结果和其意义: 57 | 58 | - `Accepted` 59 | 60 | 恭喜! 你的程序是正确的。 61 | 62 | - `Presentation Error` 63 | 64 | 你的程序输出时表达错误,如果看到这样的结果,说明你的程序已经基本正确了,只是多或少打了换行符或者多或少输出了空格,检查一下你的程序和题目要求吧,胜利就在眼前! 65 | 66 | - `Wrong Answer` 67 | 68 | 你的程序输出的结果不正确。 69 | 70 | - `Time Limit Exceeded` 71 | 72 | 你的程序尝试使用超过题目限制的时间,可能是你的程序内存在死循环或者你的程序的算法效率太低。 73 | 74 | - `Memory Limit Exceeded` 75 | 76 | 你的程序尝试使用超过题目限制的内存。 77 | 78 | - `Runtime Error` 79 | 80 | 你的程序发生了运行时错误。可能是由于除以0、数组越界或指针访问出错等运行时问题。 81 | 82 | - `Compile Error` 83 | 84 | 你的程序不能通过编译,请点击该结果可以查看编译器提示。 85 | 86 | - `Output Limit Exceeded` 87 | 88 | 你的程序的输出过多。请检查你的程序是否存在死循环问题。 89 | 90 | - `Waiting` 91 | 92 | 你的程序正在评测中,请稍候。 93 | 94 | 95 | ## 第1000号题目怎么解答? 96 | 97 | 以下是第1000号题目的各种语言的参考程序: 98 | 99 | - C语言 100 | 101 | ```c 102 | #include 103 | int main() 104 | { 105 | int a,b; 106 | scanf("%d%d",&a,&b); 107 | printf("%d\n",a+b); 108 | return 0; 109 | } 110 | ``` 111 | 112 | - C++ 113 | 114 | ```cpp 115 | #include 116 | using namespace std; 117 | int main() 118 | { 119 | int a,b; 120 | cin>>a>>b; 121 | cout<>= print . sum . map read . words 159 | ``` 160 | 161 | - go 162 | 163 | ```go 164 | package main 165 | import "fmt" 166 | func main(){ 167 | var a,b int 168 | fmt.Scanf("%d %d", &a,&b) 169 | fmt.Printf("%d", a+b) 170 | } 171 | ``` 172 | 173 | - lua 174 | 175 | ```lua 176 | a,b = io.read("*number", "*number") 177 | print(a+b) 178 | ``` 179 | 180 | - perl 181 | 182 | ```perl 183 | my ($a,$b) = split(/\D+/,); 184 | print "$a $b " . ($a + $b) . "\n"; 185 | ``` 186 | 187 | - ruby 188 | 189 | ```ruby 190 | puts gets.split.map(&:to_i).inject(&:+) 191 | ``` 192 | 193 | - python2 194 | 195 | ```python 196 | print sum(int(x) for x in raw_input().split()) 197 | ``` 198 | 199 | - python3 200 | 201 | ```python 202 | print(sum(int(x) for x in raw_input().split())) 203 | ``` 204 | -------------------------------------------------------------------------------- /get_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def get_code(solution_id, problem_id, pro_lang): 5 | '''从数据库获取代码并写入work目录下对应的文件''' 6 | file_name = { 7 | "gcc": "main.c", 8 | "g++": "main.cpp", 9 | "java": "Main.java", 10 | 'ruby': "main.rb", 11 | "perl": "main.pl", 12 | "pascal": "main.pas", 13 | "go": "main.go", 14 | "lua": "main.lua", 15 | 'python2': 'main.py', 16 | 'python3': 'main.py', 17 | "haskell": "main.hs" 18 | } 19 | select_code_sql = "#####" 20 | feh = run_sql(select_code_sql) 21 | if feh is not None: 22 | try: 23 | code = feh[0][0] 24 | except: 25 | logging.error("1 cannot get code of runid %s" % solution_id) 26 | return False 27 | else: 28 | logging.error("2 cannot get code of runid %s" % solution_id) 29 | return False 30 | try: 31 | work_path = os.path.join(config.work_dir, str(solution_id)) 32 | low_level() 33 | os.mkdir(work_path) 34 | except OSError as e: 35 | if str(e).find("exist") > 0: # 文件夹已经存在 36 | pass 37 | else: 38 | logging.error(e) 39 | return False 40 | try: 41 | real_path = os.path.join( 42 | config.work_dir, 43 | str(solution_id), 44 | file_name[pro_lang]) 45 | except KeyError as e: 46 | logging.error(e) 47 | return False 48 | try: 49 | low_level() 50 | f = codecs.open(real_path, 'w') 51 | try: 52 | f.write(code) 53 | except: 54 | logging.error("%s not write code to file" % solution_id) 55 | f.close() 56 | return False 57 | f.close() 58 | except OSError as e: 59 | logging.error(e) 60 | return False 61 | return True 62 | 63 | 64 | -------------------------------------------------------------------------------- /judge_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def judge(solution_id, problem_id, data_count, time_limit, 5 | mem_limit, program_info, result_code, language): 6 | low_level() 7 | '''评测编译类型语言''' 8 | max_mem = 0 9 | max_time = 0 10 | if language in ["java", 'python2', 'python3', 'ruby', 'perl']: 11 | time_limit = time_limit * 2 12 | mem_limit = mem_limit * 2 13 | for i in range(data_count): 14 | ret = judge_one_mem_time( 15 | solution_id, 16 | problem_id, 17 | i + 1, 18 | time_limit + 10, 19 | mem_limit, 20 | language) 21 | if ret == False: 22 | continue 23 | if ret['result'] == 5: 24 | program_info['result'] = result_code["Runtime Error"] 25 | return program_info 26 | elif ret['result'] == 2: 27 | program_info['result'] = result_code["Time Limit Exceeded"] 28 | program_info['take_time'] = time_limit + 10 29 | return program_info 30 | elif ret['result'] == 3: 31 | program_info['result'] = result_code["Memory Limit Exceeded"] 32 | program_info['take_memory'] = mem_limit 33 | return program_info 34 | if max_time < ret["timeused"]: 35 | max_time = ret['timeused'] 36 | if max_mem < ret['memoryused']: 37 | max_mem = ret['memoryused'] 38 | result = judge_result(problem_id, solution_id, i + 1) 39 | if result == False: 40 | continue 41 | if result == "Wrong Answer" or result == "Output limit": 42 | program_info['result'] = result_code[result] 43 | break 44 | elif result == 'Presentation Error': 45 | program_info['result'] = result_code[result] 46 | elif result == 'Accepted': 47 | if program_info['result'] != 'Presentation Error': 48 | program_info['result'] = result_code[result] 49 | else: 50 | logging.error("judge did not get result") 51 | program_info['take_time'] = max_time 52 | program_info['take_memory'] = max_mem 53 | return program_info 54 | 55 | -------------------------------------------------------------------------------- /judge_one.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def judge_one_mem_time( 5 | solution_id, problem_id, data_num, time_limit, mem_limit, language): 6 | low_level() 7 | '''评测一组数据''' 8 | input_path = os.path.join( 9 | config.data_dir, str(problem_id), 'data%s.in' % 10 | data_num) 11 | try: 12 | input_data = file(input_path) 13 | except: 14 | return False 15 | output_path = os.path.join( 16 | config.work_dir, str(solution_id), 'out%s.txt' % 17 | data_num) 18 | temp_out_data = file(output_path, 'w') 19 | if language == 'java': 20 | cmd = 'java -cp %s Main' % ( 21 | os.path.join(config.work_dir, 22 | str(solution_id))) 23 | main_exe = shlex.split(cmd) 24 | elif language == 'python2': 25 | cmd = 'python2 %s' % ( 26 | os.path.join(config.work_dir, 27 | str(solution_id), 28 | 'main.pyc')) 29 | main_exe = shlex.split(cmd) 30 | elif language == 'python3': 31 | cmd = 'python3 %s' % ( 32 | os.path.join(config.work_dir, 33 | str(solution_id), 34 | '__pycache__/main.cpython-33.pyc')) 35 | main_exe = shlex.split(cmd) 36 | elif language == 'lua': 37 | cmd = "lua %s" % ( 38 | os.path.join(config.work_dir, 39 | str(solution_id), 40 | "main")) 41 | main_exe = shlex.split(cmd) 42 | elif language == "ruby": 43 | cmd = "ruby %s" % ( 44 | os.path.join(config.work_dir, 45 | str(solution_id), 46 | "main.rb")) 47 | main_exe = shlex.split(cmd) 48 | elif language == "perl": 49 | cmd = "perl %s" % ( 50 | os.path.join(config.work_dir, 51 | str(solution_id), 52 | "main.pl")) 53 | main_exe = shlex.split(cmd) 54 | else: 55 | main_exe = [os.path.join(config.work_dir, str(solution_id), 'main'), ] 56 | runcfg = { 57 | 'args': main_exe, 58 | 'fd_in': input_data.fileno(), 59 | 'fd_out': temp_out_data.fileno(), 60 | 'timelimit': time_limit, # in MS 61 | 'memorylimit': mem_limit, # in KB 62 | } 63 | low_level() 64 | rst = lorun.run(runcfg) 65 | input_data.close() 66 | temp_out_data.close() 67 | logging.debug(rst) 68 | return rst 69 | 70 | -------------------------------------------------------------------------------- /judge_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def judge_result(problem_id, solution_id, data_num): 5 | low_level() 6 | '''对输出数据进行评测''' 7 | logging.debug("Judging result") 8 | correct_result = os.path.join( 9 | config.data_dir, str(problem_id), 'data%s.out' % 10 | data_num) 11 | user_result = os.path.join( 12 | config.work_dir, str(solution_id), 'out%s.txt' % 13 | data_num) 14 | try: 15 | correct = file( 16 | correct_result).read( 17 | ).replace( 18 | '\r', 19 | '').rstrip( 20 | ) # 删除\r,删除行末的空格和换行 21 | user = file(user_result).read().replace('\r', '').rstrip() 22 | except: 23 | return False 24 | if correct == user: # 完全相同:AC 25 | return "Accepted" 26 | if correct.split() == user.split(): # 除去空格,tab,换行相同:PE 27 | return "Presentation Error" 28 | if correct in user: # 输出多了 29 | return "Output limit" 30 | return "Wrong Answer" # 其他WA 31 | 32 | 33 | -------------------------------------------------------------------------------- /judger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ma6174/acmjudger/fd4e4086138db11f34440b27c5598a4e0515b78b/judger.png -------------------------------------------------------------------------------- /lorun/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.so 3 | -------------------------------------------------------------------------------- /lorun/README.md: -------------------------------------------------------------------------------- 1 | Loco program runner core 2 | ======================== 3 | 4 | We use this python-c library to run program in a sandbox-like environment. 5 | With it, we can accurately known the resource using of the program and 6 | limit its resource using including system-call interrupt. 7 | 8 | Usage 9 | ----- 10 | 11 | For run a program without tracing: 12 | 13 | runcfg = { 14 | 'args':['./m'], 15 | 'fd_in':fin.fileno(), 16 | 'fd_out':ftemp.fileno(), 17 | 'timelimit':1000, #in MS 18 | 'memorylimit':20000, #in KB 19 | } 20 | 21 | rst = lorun.run(runcfg) 22 | 23 | For check one output: 24 | 25 | ftemp = file('temp.out') 26 | fout = file(out_path) 27 | crst = lorun.check(fout.fileno(), ftemp.fileno()) 28 | 29 | 30 | -------------------------------------------------------------------------------- /lorun/demo/1498/0.in: -------------------------------------------------------------------------------- 1 | 3 2 | 4 3 | 100 4 | 5 5 | 6 6 | 7 7 | 8 8 | 9 9 | 10 10 | 5000 11 | 10000 12 | -------------------------------------------------------------------------------- /lorun/demo/1498/0.out: -------------------------------------------------------------------------------- 1 | 4 2 | 7 3 | 792070839848372253127 4 | 11 5 | 18 6 | 29 7 | 47 8 | 76 9 | 123 10 | 8673637146589588538368589908373462798874929690826669771891051682960324374579289431940944506593401831180675927640810665787814058157125232229059235218281645431783064280629491569650725960078242863057952720028939990089024379895090539819777933684941022902207563526112894785617862251238365161198717106458219978536414466189285539332995765501129362169276175704890758138083507286277651847383857608988791179038580399414299478740395396330046213577626410102867121422204310059566970650371242226032159193834974183390981056053191784466296360838601553529211439427354854958775453171303532960986983974646831128646591215076588360782975133911292772846054548132095443926108506365685706581019626528726537480711406504349416587347774482073700774346991462484915485821422212130153659288165621672622387749797502013860342414219241457323994743119301421119476902895162578536845735589750721567221575635708594762429967321501370922383411988593808707433511072003769288972478964529424776051329378725374896920539313746733853008293418784938096912910101395864289472119273964080078127 11 | 75231981350698779410846909982393216960476220648711870145930661707737084289078035666010507582244187056765580019275216475394984379018367214615308512822861907963818123866252298604572984963068032998653492787435709471086684503000060815704634212021029848632361813334383359731211074068222783504901961367052576576199215227894301383590817973977920943758603040855496816422652692142153650787401718391832078260672923427573458531837900860573718890061736595980497167294974295757868426785897046543463768645076448653098837550518132047266908696000688921418929069721000052098789462575393560820720985713503419640167853307208066931815813727515077448969711916416916819814046640169579729369763638062228317398264409756897457283556589623944107354528671139569457619351121859407768343388658425604738876740627067984235513453338695564465891569875356982603970196239704602497363524159509410282515641379776717045429837402919325161172437635878533108169501325443619115551165195355494526697215496213166589887299781666807464387957593663732929150823591995895349753862527818809689903391161471162861973977748612485461451938274960493921510534593089391203512573989203899386783893121182995342220740920906756445157916707542627333367583114399919254330738286099940201084924181466260977736187575606957393720409243389065864069155812252238809233364750630700777750135076714877359708612540909202663634634314922760573819343501978179118236688082697878306145109454691947479702283617959353220170352645937163572565746902580480964545570964990308528120613570816590176437391788115421926340428325530695874946624510645772316791139129095076219615025716155100162177919805900866224583987187385757943312996259516523878234097124863300547240283491039121403525888489795306895583373811084931277600007878540838749206968913101529445565801595610834969589831813129330510906516236672549600399156286414491364656139751118671734266631119825420019293679487257709938070421937964203667505930936805558809580438512171165847393676059071656816483767113881118363363143516669828270959637449098668720358751688196246914574335841533124018885073835988714533655280383362423828127 12 | -------------------------------------------------------------------------------- /lorun/demo/1602.c: -------------------------------------------------------------------------------- 1 | #include 2 | void main() 3 | { 4 | int a,b,c,m; 5 | int d=1; 6 | while(scanf("%d %d",&a,&b)!=EOF) 7 | { 8 | 9 | for(c=1;c<=1000;c++) 10 | { 11 | if(a%c==0&&b%c==0) 12 | { 13 | d=d*c; 14 | a=a/c; 15 | b=b/c; 16 | } 17 | } 18 | m=d*a*b; 19 | printf("%d",m); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lorun/demo/Main.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ma6174/acmjudger/fd4e4086138db11f34440b27c5598a4e0515b78b/lorun/demo/Main.class -------------------------------------------------------------------------------- /lorun/demo/Main.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.math.BigInteger; 3 | import java.math.BigDecimal; 4 | 5 | ; 6 | 7 | public class Main { 8 | 9 | public static void main(String[] args) { 10 | Scanner in =new Scanner(System.in); 11 | BigInteger [] a = new BigInteger[10001]; 12 | a[2]=BigInteger.valueOf(3); 13 | a[3]=BigInteger.valueOf(4); 14 | for(int i=4;i<=10000;++i){ 15 | a[i]=a[i-1].add(a[i-2]); 16 | } 17 | while(in.hasNext()){ 18 | int n=in.nextInt(); 19 | System.out.println(a[n]); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lorun/demo/a+b.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int a, b; 5 | 6 | scanf("%d %d", &a, &b); 7 | printf("%d\n", a + b); 8 | 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /lorun/demo/a.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ma6174/acmjudger/fd4e4086138db11f34440b27c5598a4e0515b78b/lorun/demo/a.out -------------------------------------------------------------------------------- /lorun/demo/m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ma6174/acmjudger/fd4e4086138db11f34440b27c5598a4e0515b78b/lorun/demo/m -------------------------------------------------------------------------------- /lorun/demo/temp.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ma6174/acmjudger/fd4e4086138db11f34440b27c5598a4e0515b78b/lorun/demo/temp.out -------------------------------------------------------------------------------- /lorun/demo/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #! -*- coding: utf8 -*- 3 | 4 | import lorun 5 | import os 6 | 7 | RESULT_STR = [ 8 | 'Accepted', 9 | 'Presentation Error', 10 | 'Time Limit Exceeded', 11 | 'Memory Limit Exceeded', 12 | 'Wrong Answer', 13 | 'Runtime Error', 14 | 'Output Limit Exceeded', 15 | 'Compile Error', 16 | 'System Error' 17 | ] 18 | 19 | def compileSrc(src_path): 20 | if os.system('gcc %s -o m'%src_path) != 0: 21 | print 'compile failure!' 22 | return False 23 | return True 24 | 25 | def runone(p_path, in_path, out_path): 26 | fin = file(in_path) 27 | ftemp = file('temp.out', 'w') 28 | 29 | runcfg = { 30 | 'args':['cd','/home/ma6174/111/','&&','java','Main'], 31 | 'fd_in':fin.fileno(), 32 | 'fd_out':ftemp.fileno(), 33 | 'timelimit':2000, #in MS 34 | 'memorylimit':65536, #in KB 35 | } 36 | rst = lorun.run(runcfg) 37 | fin.close() 38 | ftemp.close() 39 | if rst['result'] == 0: 40 | ftemp = file('temp.out') 41 | fout = file(out_path) 42 | crst = lorun.check(fout.fileno(), ftemp.fileno()) 43 | fout.close() 44 | ftemp.close() 45 | # os.remove('temp.out') 46 | if crst != 0: 47 | return {'result':crst} 48 | 49 | return rst 50 | 51 | def judge(src_path, td_path, td_total): 52 | if not compileSrc(src_path): 53 | return 54 | for i in xrange(td_total): 55 | in_path = os.path.join(td_path, '%d.in'%i) 56 | out_path = os.path.join(td_path, '%d.out'%i) 57 | if os.path.isfile(in_path) and os.path.isfile(out_path): 58 | rst = runone('./m', in_path, out_path) 59 | rst['result'] = RESULT_STR[rst['result']] 60 | print rst 61 | else: 62 | print 'testdata:%d incompleted'%i 63 | os.remove('./m') 64 | exit(-1) 65 | os.remove('./m') 66 | 67 | if __name__ == '__main__': 68 | import sys 69 | if len(sys.argv) != 4: 70 | print 'Usage:%s srcfile testdata_pth testdata_total' 71 | exit(-1) 72 | judge(sys.argv[1], sys.argv[2], int(sys.argv[3])) 73 | 74 | -------------------------------------------------------------------------------- /lorun/demo/testdata/0.in: -------------------------------------------------------------------------------- 1 | 11134 2245 2 | -------------------------------------------------------------------------------- /lorun/demo/testdata/0.out: -------------------------------------------------------------------------------- 1 | 13379 2 | -------------------------------------------------------------------------------- /lorun/demo/testdata/1.in: -------------------------------------------------------------------------------- 1 | 1 1 2 | -------------------------------------------------------------------------------- /lorun/demo/testdata/1.out: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /lorun/demo/testdata/2.in: -------------------------------------------------------------------------------- 1 | 123 456 2 | -------------------------------------------------------------------------------- /lorun/demo/testdata/2.out: -------------------------------------------------------------------------------- 1 | 579 2 | -------------------------------------------------------------------------------- /lorun/src/access.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "access.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | int fileAccess(PyObject *files, const char *file, long flags) { 26 | PyObject *perm_obj; 27 | long perm; 28 | 29 | if ((perm_obj = PyDict_GetItemString(files, file)) == NULL) { 30 | return 0; 31 | } 32 | //printf("%s:%d\n",file,flags); 33 | perm = PyInt_AsLong(perm_obj); 34 | if (perm == flags) 35 | return 1; 36 | 37 | return 0; 38 | } 39 | 40 | static long file_temp[100]; 41 | int checkAccess(struct Runobj *runobj, int pid, struct user_regs_struct *regs) { 42 | #if __WORDSIZE == 64 43 | if (!runobj->inttable[regs->orig_rax]) 44 | #else 45 | if (!runobj->inttable[regs->orig_eax]) 46 | #endif 47 | return ACCESS_CALL_ERR; 48 | 49 | #if __WORDSIZE == 64 50 | switch (regs->orig_rax) 51 | #else 52 | switch (regs->orig_eax) 53 | #endif 54 | { 55 | case SYS_open: { 56 | int i, j; 57 | 58 | for (i = 0; i < 100; i++) { 59 | #if __WORDSIZE == 64 60 | long t = ptrace(PTRACE_PEEKDATA, pid, 61 | regs->rdi + i * sizeof(long), NULL); 62 | #else 63 | long t = ptrace(PTRACE_PEEKDATA, pid, regs->ebx + i*sizeof(long), NULL); 64 | #endif 65 | file_temp[i] = t; 66 | const char* test = (const char*) &file_temp[i]; 67 | for (j = 0; j < sizeof(long); j++) { 68 | if (!test[j]) { 69 | goto l_cont; 70 | } 71 | } 72 | } 73 | l_cont: file_temp[99] = 0; 74 | 75 | #if __WORDSIZE == 64 76 | if (fileAccess(runobj->files, (const char *) file_temp, regs->rsi)) 77 | #else 78 | if(fileAccess(runobj->files, (const char *)file_temp, regs->ecx)) 79 | #endif 80 | return ACCESS_OK; 81 | return ACCESS_FILE_ERR; 82 | } 83 | } 84 | 85 | return ACCESS_OK; 86 | } 87 | 88 | const char* lastFileAccess(void) { 89 | file_temp[99] = 0; 90 | return (const char*) file_temp; 91 | } 92 | -------------------------------------------------------------------------------- /lorun/src/access.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef __LO_ACCESS_HEADER 20 | #define __LO_ACCESS_HEADER 21 | 22 | #include "lorun.h" 23 | #include 24 | 25 | #define ACCESS_CALL_ERR 1 26 | #define ACCESS_FILE_ERR 2 27 | #define ACCESS_OK 0 28 | 29 | int checkAccess(struct Runobj *runobj, int pid, struct user_regs_struct *regs); 30 | const char* lastFileAccess(void); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /lorun/src/convert.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "convert.h" 20 | 21 | int initCalls(PyObject *li, u_char calls[]) { 22 | PyObject *t; 23 | Py_ssize_t len, i; 24 | 25 | memset(calls, 0, sizeof(u_char) * CALLS_MAX); 26 | 27 | len = PyList_Size(li); 28 | for (i = 0; i < len; i++) { 29 | t = PyList_GetItem(li, i); 30 | if (PyLong_Check(t)) 31 | RAISE1("calls must be a list of numbers."); 32 | calls[PyInt_AsLong(t)] = 1; 33 | } 34 | 35 | return 0; 36 | } 37 | 38 | PyObject *genResult(struct Result *rst) { 39 | PyObject *rst_obj, *j, *t, *m; 40 | if ((rst_obj = PyDict_New()) == NULL) 41 | RAISE0("new dict failure"); 42 | 43 | j = PyLong_FromLong(rst->judge_result); 44 | t = PyLong_FromLong(rst->time_used); 45 | m = PyLong_FromLong(rst->memory_used); 46 | 47 | if (!j || !t || !m) 48 | RAISE0("set item falure(1)"); 49 | 50 | if (PyDict_SetItemString(rst_obj, "result", j)) { 51 | RAISE0("set item falure"); 52 | } 53 | 54 | if (!rst->judge_result) { 55 | if (PyDict_SetItemString(rst_obj, "timeused", t) 56 | || PyDict_SetItemString(rst_obj, "memoryused", m)) { 57 | RAISE0("set item failure"); 58 | } 59 | } 60 | 61 | if (rst->re_signum) 62 | PyDict_SetItemString(rst_obj, "re_signum", 63 | PyLong_FromLong(rst->re_signum)); 64 | if (rst->re_call != -1) 65 | PyDict_SetItemString(rst_obj, "re_call", PyLong_FromLong(rst->re_call)); 66 | if (rst->re_file) 67 | PyDict_SetItemString(rst_obj, "re_file", 68 | PyString_FromString(rst->re_file)); 69 | 70 | return rst_obj; 71 | } 72 | 73 | char * const * genRunArgs(PyObject *args_obj) { //generate the argsments for exec* 74 | PyObject *arg; 75 | const char **args; 76 | int len, i; 77 | 78 | if (!PyList_Check(args_obj)) 79 | RAISE0("args must be a list") 80 | 81 | len = PyList_GET_SIZE(args_obj); 82 | args = (const char**) malloc(sizeof(char*) * (len + 1)); 83 | 84 | for (i = 0; i < len; i++) { 85 | if ((arg = PyList_GetItem(args_obj, i)) == NULL) { 86 | free(args); 87 | return NULL; 88 | } 89 | args[i] = PyString_AsString(arg); 90 | } 91 | args[i] = NULL; 92 | 93 | return (char * const *) args; 94 | } 95 | -------------------------------------------------------------------------------- /lorun/src/convert.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef __LO_CONVERT_HEADER 20 | #define __LO_CONVERT_HEADER 21 | 22 | #include "lorun.h" 23 | 24 | int initCalls(PyObject *li, u_char calls[]); 25 | PyObject *genResult(struct Result *rst); 26 | char * const * genRunArgs(PyObject *args_obj); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /lorun/src/diff.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "diff.h" 20 | #include 21 | 22 | int equalStr(const char *s, const char *s2) { 23 | const char *c1 = s, *c2 = s2; 24 | while (*c1 && c2 && (*c1++ == *c2++)) 25 | ; 26 | if ((*c1 | *c2) == 0) 27 | return 1; 28 | return 0; 29 | } 30 | 31 | #define RETURN(rst) {*result = rst;return 0;} 32 | int checkDiff(int rightout_fd, int userout_fd, int *result) { 33 | char *userout, *rightout; 34 | 35 | off_t userout_len, rightout_len; 36 | userout_len = lseek(userout_fd, 0, SEEK_END); 37 | rightout_len = lseek(rightout_fd, 0, SEEK_END); 38 | 39 | if (userout_len == -1 || rightout_len == -1) 40 | RAISE1("lseek failure"); 41 | 42 | if (userout_len >= MAX_OUTPUT) 43 | RETURN(OLE); 44 | 45 | lseek(userout_fd, 0, SEEK_SET); 46 | lseek(rightout_fd, 0, SEEK_SET); 47 | 48 | if ((userout_len && rightout_len) == 0) { 49 | if (userout_len || rightout_len) 50 | RETURN(WA) 51 | else 52 | RETURN(AC) 53 | } 54 | 55 | if ((userout = (char*) mmap(NULL, userout_len, PROT_READ | PROT_WRITE, 56 | MAP_PRIVATE, userout_fd, 0)) == MAP_FAILED) { 57 | RAISE1("mmap userout filure"); 58 | } 59 | 60 | if ((rightout = (char*) mmap(NULL, rightout_len, PROT_READ | PROT_WRITE, 61 | MAP_PRIVATE, rightout_fd, 0)) == MAP_FAILED) { 62 | munmap(userout, userout_len); 63 | RAISE1("mmap right filure"); 64 | } 65 | 66 | if ((userout_len == rightout_len) && equalStr(userout, rightout)) { 67 | munmap(userout, userout_len); 68 | munmap(rightout, rightout_len); 69 | RETURN(AC); 70 | } 71 | 72 | const char *cuser = userout, *cright = rightout; 73 | const char *end_user = userout + userout_len; 74 | const char *end_right = rightout + rightout_len; 75 | while ((cuser < end_user) && (cright < end_right)) { 76 | while ((cuser < end_user) 77 | && (*cuser == ' ' || *cuser == '\n' || *cuser == '\r' 78 | || *cuser == '\t')) 79 | cuser++; 80 | while ((cright < end_right) 81 | && (*cright == ' ' || *cright == '\n' || *cright == '\r' 82 | || *cright == '\t')) 83 | cright++; 84 | if (cuser == end_user || cright == end_right) 85 | break; 86 | if (*cuser != *cright) 87 | break; 88 | cuser++; 89 | cright++; 90 | } 91 | while ((cuser < end_user) 92 | && (*cuser == ' ' || *cuser == '\n' || *cuser == '\r' 93 | || *cuser == '\t')) 94 | cuser++; 95 | while ((cright < end_right) 96 | && (*cright == ' ' || *cright == '\n' || *cright == '\r' 97 | || *cright == '\t')) 98 | cright++; 99 | if (cuser == end_user && cright == end_right) { 100 | munmap(userout, userout_len); 101 | munmap(rightout, rightout_len); 102 | RETURN(PE); 103 | } 104 | 105 | munmap(userout, userout_len); 106 | munmap(rightout, rightout_len); 107 | RETURN(WA); 108 | } 109 | 110 | -------------------------------------------------------------------------------- /lorun/src/diff.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef __LO_DIFF_HEADER 20 | #define __LO_DIFF_HEADER 21 | 22 | #include "lorun.h" 23 | 24 | int checkDiff(int rightout_fd, int userout_fd, int *result); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /lorun/src/limit.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "limit.h" 20 | #include 21 | #include 22 | 23 | long time_limit, memory_limit; 24 | const char *last_limit_err; 25 | 26 | int setResLimit(void) { 27 | #define RAISE_EXIT(err) {last_limit_err = err;return -1;} 28 | struct rlimit rl; 29 | 30 | rl.rlim_cur = time_limit / 1000 + 2; 31 | rl.rlim_max = rl.rlim_cur + 1; 32 | if (setrlimit(RLIMIT_CPU, &rl)) 33 | RAISE_EXIT("set RLIMIT_CPU failure"); 34 | 35 | /*rl.rlim_cur = 128 * 1024 * 1024; 36 | rl.rlim_max = rl.rlim_cur + 1024; 37 | if (setrlimit(RLIMIT_AS, &rl)) 38 | RAISE_EXIT("set RLIMIT_AS failure");*/ 39 | 40 | rl.rlim_cur = 256 * 1024 * 1024; 41 | rl.rlim_max = rl.rlim_cur + 1024; 42 | if (setrlimit(RLIMIT_STACK, &rl)) 43 | RAISE_EXIT("set RLIMIT_STACK failure"); 44 | 45 | struct itimerval p_realt; 46 | p_realt.it_interval.tv_sec = time_limit / 1000 + 3; 47 | p_realt.it_interval.tv_usec = 0; 48 | p_realt.it_value = p_realt.it_interval; 49 | if (setitimer(ITIMER_REAL, &p_realt, (struct itimerval *) 0) == -1) 50 | RAISE_EXIT("set ITIMER_REAL failure"); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /lorun/src/limit.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef __LO_LIMIT_HEADER 20 | #define __LO_LIMIT_HEADER 21 | 22 | #include "lorun.h" 23 | 24 | int setResLimit(void); 25 | extern const char *last_limit_err; 26 | #endif 27 | -------------------------------------------------------------------------------- /lorun/src/lorun.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "lorun.h" 20 | #include "convert.h" 21 | #include "run.h" 22 | #include "diff.h" 23 | 24 | 25 | int initRun(struct Runobj *runobj, PyObject *args) 26 | { 27 | PyObject *config, *args_obj, *trace_obj, *time_obj, *memory_obj; 28 | PyObject *calls_obj, *runner_obj, *fd_obj; 29 | 30 | if (!PyArg_ParseTuple(args, "O", &config)) 31 | RAISE1("initRun parseTuple failure"); 32 | if(!PyDict_Check(config)) RAISE1("argument must be a dict"); 33 | 34 | if((args_obj = PyDict_GetItemString(config,"args")) == NULL) 35 | RAISE1("must supply args"); 36 | 37 | if((runobj->args = genRunArgs(args_obj)) == NULL) return -1; 38 | 39 | if((fd_obj = PyDict_GetItemString(config, "fd_in")) == NULL) 40 | runobj->fd_in = -1; 41 | else runobj->fd_in = PyLong_AsLong(fd_obj); 42 | if((fd_obj = PyDict_GetItemString(config, "fd_out")) == NULL) 43 | runobj->fd_out = -1; 44 | else runobj->fd_out = PyLong_AsLong(fd_obj); 45 | if((fd_obj = PyDict_GetItemString(config, "fd_err")) == NULL) 46 | runobj->fd_err = -1; 47 | else runobj->fd_err = PyLong_AsLong(fd_obj); 48 | 49 | if((time_obj = PyDict_GetItemString(config,"timelimit")) == NULL) 50 | RAISE1("must supply timelimit"); 51 | runobj->time_limit = PyLong_AsLong(time_obj); 52 | 53 | if((memory_obj = PyDict_GetItemString(config,"memorylimit")) == NULL) 54 | RAISE1("must supply memorylimit"); 55 | runobj->memory_limit = PyLong_AsLong(memory_obj); 56 | 57 | if((runner_obj = PyDict_GetItemString(config,"runner")) == NULL) 58 | runobj->runner = -1; 59 | else 60 | runobj->runner = PyLong_AsLong(runner_obj); 61 | 62 | if((trace_obj = PyDict_GetItemString(config,"trace")) != NULL){ 63 | if(trace_obj == Py_True){ 64 | runobj->trace = 1; 65 | //trace mode: supply calls and files to access. 66 | if((calls_obj = PyDict_GetItemString(config,"calls")) == NULL) 67 | RAISE1("trace == True, so you must specify calls."); 68 | if(!PyList_Check(calls_obj)) 69 | RAISE1("calls must be a list."); 70 | if(initCalls(calls_obj, runobj->inttable)) return -1; 71 | 72 | if((runobj->files = PyDict_GetItemString(config,"files")) == NULL) 73 | RAISE1("trace == True, so you must specify files."); 74 | if(!PyDict_Check(runobj->files)) 75 | RAISE1("files must be a dcit."); 76 | } else runobj->trace = 0; 77 | } else runobj->trace = 0; 78 | 79 | return 0; 80 | } 81 | 82 | PyObject *run(PyObject *self, PyObject *args) 83 | { 84 | struct Runobj runobj = {0}; 85 | struct Result rst = {0}; 86 | rst.re_call = -1; 87 | 88 | if(initRun(&runobj, args)){ 89 | if(runobj.args) 90 | free((void*)runobj.args); 91 | return NULL; 92 | } 93 | 94 | if(runit(&runobj, &rst) == -1) 95 | return NULL; 96 | if(runobj.args) 97 | free((void*)runobj.args); 98 | 99 | return genResult(&rst); 100 | } 101 | 102 | PyObject* check(PyObject *self, PyObject *args) 103 | { 104 | int user_fd, right_fd, rst; 105 | 106 | if (!PyArg_ParseTuple(args, "ii", &right_fd, &user_fd)) 107 | RAISE0("run parseTuple failure"); 108 | 109 | if(checkDiff(right_fd, user_fd, &rst) == -1) 110 | return NULL; 111 | 112 | return Py_BuildValue("i", rst); 113 | } 114 | 115 | #define run_description "run(argv_dict):\n"\ 116 | "\targv_dict contains:\n"\ 117 | "\t@args : cmd to run\n"\ 118 | "\t@fd_in, fd_out, fd_err : stdin,stdout,stderr fd\n"\ 119 | "\t@timelimit : program time limit\n"\ 120 | "\t@memorylimit : program memory limit\n"\ 121 | "\t@runner : run user\n"\ 122 | "\t@trace : trace?" 123 | 124 | #define check_description "check(right_fd, userout_fd)\n" 125 | 126 | static PyMethodDef lorunMethods[]={ 127 | {"run", run, METH_VARARGS, run_description}, 128 | {"check", check, METH_VARARGS, check_description}, 129 | {NULL, NULL, 0, NULL} 130 | }; 131 | 132 | PyMODINIT_FUNC initlorun(void) 133 | { 134 | (void) Py_InitModule("lorun",lorunMethods); 135 | } 136 | -------------------------------------------------------------------------------- /lorun/src/lorun.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef __LO_GCC_HEADER 20 | #define __LO_GCC_HEADER 21 | #define _GNU_SOURCE 22 | #include 23 | #include 24 | #define CALLS_MAX 400 25 | #define MAX_OUTPUT 100000000 26 | 27 | enum JUDGE_RESULT{ 28 | AC=0, //0 Accepted 29 | PE, //1 Presentation Error 30 | TLE, //2 Time Limit Exceeded 31 | MLE, //3 Memory Limit Exceeded 32 | WA, //4 Wrong Answer 33 | RE, //5 Runtime Error 34 | OLE, //6 Output Limit Exceeded 35 | CE, //7 Compile Error 36 | SE, //8 System Error 37 | }; 38 | 39 | struct Result{ 40 | int judge_result; //JUDGE_RESULT 41 | int time_used, memory_used; 42 | int re_signum; 43 | int re_call; 44 | const char* re_file; 45 | }; 46 | 47 | struct Runobj { 48 | PyObject *files; 49 | u_char inttable[CALLS_MAX]; 50 | char * const* args; 51 | 52 | int fd_in, fd_out, fd_err; 53 | int time_limit, memory_limit; 54 | int runner; 55 | int trace; 56 | }; 57 | 58 | #define RAISE(msg) PyErr_SetString(PyExc_Exception,msg); 59 | 60 | #define RAISE0(msg) \ 61 | {PyErr_SetString(PyExc_Exception,msg); return NULL;} 62 | 63 | #define RAISE1(msg) \ 64 | {PyErr_SetString(PyExc_Exception,msg); return -1;} 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /lorun/src/run.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #define _GNU_SOURCE 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "run.h" 27 | #include "access.h" 28 | #include "limit.h" 29 | 30 | const char *last_run_err; 31 | #define RAISE_RUN(err) {last_run_err = err;return -1;} 32 | 33 | int traceLoop(struct Runobj *runobj, struct Result *rst, pid_t pid) { 34 | int status, incall = 0; 35 | struct rusage ru; 36 | struct user_regs_struct regs; 37 | 38 | while (1) { 39 | if (wait4(pid, &status, WSTOPPED, &ru) == -1) 40 | RAISE_RUN("wait4 [WSTOPPED] failure"); 41 | 42 | if (WIFEXITED(status)) 43 | break; 44 | else if (WSTOPSIG(status) != SIGTRAP) { 45 | ptrace(PTRACE_KILL, pid, NULL, NULL); 46 | waitpid(pid, NULL, 0); 47 | 48 | rst->time_used = ru.ru_utime.tv_sec * 1000 49 | + ru.ru_utime.tv_usec / 1000; 50 | rst->memory_used = ru.ru_minflt * (sysconf(_SC_PAGESIZE) / 1024); 51 | 52 | switch (WSTOPSIG(status)) { 53 | case SIGSEGV: 54 | if (rst->memory_used > runobj->memory_limit) 55 | rst->judge_result = MLE; 56 | else 57 | rst->judge_result = RE; 58 | break; 59 | case SIGALRM: 60 | case SIGXCPU: 61 | rst->judge_result = TLE; 62 | break; 63 | default: 64 | rst->judge_result = RE; 65 | break; 66 | } 67 | 68 | rst->re_signum = WSTOPSIG(status); 69 | 70 | return 0; 71 | } 72 | 73 | if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) 74 | RAISE_RUN("PTRACE_GETREGS failure"); 75 | 76 | if (incall) { 77 | int ret = checkAccess(runobj, pid, ®s); 78 | if (ret != ACCESS_OK) { 79 | ptrace(PTRACE_KILL, pid, NULL, NULL); 80 | waitpid(pid, NULL, 0); 81 | 82 | rst->time_used = ru.ru_utime.tv_sec * 1000 83 | + ru.ru_utime.tv_usec / 1000; 84 | rst->memory_used = ru.ru_minflt 85 | * (sysconf(_SC_PAGESIZE) / 1024); 86 | 87 | rst->judge_result = RE; 88 | if (ret == ACCESS_CALL_ERR) 89 | #if __WORDSIZE == 64 90 | rst->re_call = regs.orig_rax; 91 | #else 92 | rst->re_call = regs.orig_eax; 93 | #endif 94 | else rst->re_file = lastFileAccess(); 95 | return 0; 96 | } 97 | incall = 0; 98 | } else 99 | incall = 1; 100 | 101 | ptrace(PTRACE_SYSCALL, pid, NULL, NULL); 102 | } 103 | 104 | rst->time_used = ru.ru_utime.tv_sec * 1000 + ru.ru_utime.tv_usec / 1000; 105 | rst->memory_used = ru.ru_minflt * (sysconf(_SC_PAGESIZE) / 1024); 106 | 107 | if (rst->time_used > runobj->time_limit) 108 | rst->judge_result = TLE; 109 | else if (rst->memory_used > runobj->memory_limit) 110 | rst->judge_result = MLE; 111 | else 112 | rst->judge_result = AC; 113 | 114 | return 0; 115 | } 116 | 117 | int waitExit(struct Runobj *runobj, struct Result *rst, pid_t pid) { 118 | int status; 119 | struct rusage ru; 120 | 121 | if (wait4(pid, &status, 0, &ru) == -1) 122 | RAISE_RUN("wait4 failure"); 123 | 124 | rst->time_used = ru.ru_utime.tv_sec * 1000 + ru.ru_utime.tv_usec / 1000; 125 | rst->memory_used = ru.ru_minflt * (sysconf(_SC_PAGESIZE) / 1024); 126 | 127 | if (WIFSIGNALED(status)) { 128 | switch (WTERMSIG(status)) { 129 | case SIGSEGV: 130 | if (rst->memory_used > runobj->memory_limit) 131 | rst->judge_result = MLE; 132 | else 133 | rst->judge_result = RE; 134 | break; 135 | case SIGALRM: 136 | case SIGXCPU: 137 | rst->judge_result = TLE; 138 | break; 139 | default: 140 | rst->judge_result = RE; 141 | break; 142 | } 143 | rst->re_signum = WTERMSIG(status); 144 | } else { 145 | if (rst->time_used > runobj->time_limit) 146 | rst->judge_result = TLE; 147 | else if (rst->memory_used > runobj->memory_limit) 148 | rst->judge_result = MLE; 149 | else 150 | rst->judge_result = AC; 151 | } 152 | 153 | return 0; 154 | } 155 | 156 | int runit(struct Runobj *runobj, struct Result *rst) { 157 | pid_t pid; 158 | int fd_err[2]; 159 | 160 | if (pipe2(fd_err, O_NONBLOCK)) 161 | RAISE1("run :pipe2(fd_err) failure"); 162 | 163 | pid = vfork(); 164 | if (pid < 0) { 165 | close(fd_err[0]); 166 | close(fd_err[1]); 167 | RAISE1("run : vfork failure"); 168 | } 169 | 170 | if (pid == 0) { 171 | close(fd_err[0]); 172 | 173 | #define RAISE_EXIT(err) {\ 174 | int r = write(fd_err[1],err,strlen(err));\ 175 | _exit(r);\ 176 | } 177 | 178 | if (runobj->fd_in != -1) 179 | if (dup2(runobj->fd_in, 0) == -1) 180 | RAISE_EXIT("dup2 stdin failure!") 181 | 182 | if (runobj->fd_out != -1) 183 | if (dup2(runobj->fd_out, 1) == -1) 184 | RAISE_EXIT("dup2 stdout failure") 185 | 186 | if (runobj->fd_err != -1) 187 | if (dup2(runobj->fd_err, 2) == -1) 188 | RAISE_EXIT("dup2 stderr failure") 189 | 190 | if (setResLimit() == -1) 191 | RAISE_EXIT(last_limit_err) 192 | 193 | if (runobj->runner != -1) 194 | if (setuid(runobj->runner)) 195 | RAISE_EXIT("setuid failure") 196 | 197 | if (runobj->trace) 198 | if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) 199 | RAISE_EXIT("TRACEME failure") 200 | 201 | execvp(runobj->args[0], (char * const *) runobj->args); 202 | 203 | RAISE_EXIT("execvp failure") 204 | } else { 205 | int r; 206 | char errbuffer[100] = { 0 }; 207 | 208 | close(fd_err[1]); 209 | r = read(fd_err[0], errbuffer, 90); 210 | close(fd_err[0]); 211 | if (r > 0) { 212 | waitpid(pid, NULL, WNOHANG); 213 | RAISE(errbuffer); 214 | return -1; 215 | } 216 | 217 | if (runobj->trace) 218 | r = traceLoop(runobj, rst, pid); 219 | else 220 | r = waitExit(runobj, rst, pid); 221 | 222 | if (r) 223 | RAISE1(last_run_err); 224 | return 0; 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /lorun/src/run.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Loco program runner core 3 | * Copyright (C) 2011 Lodevil(Du Jiong) 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef __LO_RUN_HEADER 20 | #define __LO_RUN_HEADER 21 | 22 | #include "lorun.h" 23 | 24 | int runit(struct Runobj *runobj, struct Result *rst); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /lorun/src/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #-*coding:utf-8*- 3 | 4 | from distutils.core import setup, Extension 5 | 6 | baserun = Extension('lorun', 7 | sources = ['lorun.c','convert.c','access.c','limit.c','run.c','diff.c'], 8 | ) 9 | 10 | setup (name = 'lorun', 11 | version = '1.0', 12 | description = 'loco program runner core', 13 | ext_modules = [baserun] 14 | ) 15 | -------------------------------------------------------------------------------- /nohup.out: -------------------------------------------------------------------------------- 1 | 2013-04-22 21:58:14,073 --- judging 334869 2 | 2013-04-22 21:58:14,092 --- judging 334870 3 | 2013-04-22 21:58:14,135 --- judging 334871 4 | 2013-04-22 21:58:14,151 --- judging 334872 5 | 2013-04-22 21:58:14,167 --- judging 334873 6 | 2013-04-22 21:58:14,202 --- judging 334877 7 | 2013-04-22 21:58:14,228 --- judging 334878 8 | 2013-04-22 21:58:14,306 --- 334869 result 4 9 | 2013-04-22 21:58:14,316 --- judging 334879 10 | 2013-04-22 21:58:14,401 --- 334873 result 7 11 | 2013-04-22 21:58:14,408 --- 334870 result 4 12 | 2013-04-22 21:58:14,541 --- 334877 result 1 13 | 2013-04-22 21:58:14,590 --- Thread-6 idle 14 | 2013-04-22 21:58:14,615 --- judging 334880 15 | 2013-04-22 21:58:14,617 --- 334879 result 1 16 | 2013-04-22 21:58:14,621 --- 334878 result 1 17 | 2013-04-22 21:58:14,638 --- Thread-2 idle 18 | 2013-04-22 21:58:14,648 --- 334871 result 1 19 | 2013-04-22 21:58:14,757 --- Thread-8 idle 20 | 2013-04-22 21:58:14,766 --- judging 334881 21 | 2013-04-22 21:58:14,996 --- 334872 result 4 22 | 2013-04-22 21:58:15,003 --- Thread-7 idle 23 | 2013-04-22 21:58:17,139 --- 334880 result 2 24 | 2013-04-22 21:58:18,942 --- Thread-3 idle 25 | 2013-04-22 21:58:18,949 --- Thread-4 idle 26 | 2013-04-22 21:58:18,981 --- Thread-5 idle 27 | 2013-04-22 21:58:19,010 --- judging 334882 28 | 2013-04-22 21:58:19,120 --- judging 334883 29 | 2013-04-22 21:58:19,121 --- 334881 result 1 30 | 2013-04-22 21:58:19,227 --- 334882 result 1 31 | 2013-04-22 21:58:19,367 --- Thread-1 idle 32 | 2013-04-22 21:58:19,476 --- Thread-6 idle 33 | 2013-04-22 21:58:19,524 --- 334883 result 4 34 | 2013-04-22 21:58:19,670 --- Thread-2 idle 35 | 2013-04-22 21:58:57,501 --- judging 334884 36 | 2013-04-22 21:58:57,637 --- 334884 result 4 37 | 2013-04-22 21:58:57,671 --- Thread-8 idle 38 | 2013-04-22 21:59:10,682 --- judging 334885 39 | 2013-04-22 21:59:10,828 --- 334885 result 1 40 | 2013-04-22 21:59:10,912 --- Thread-7 idle 41 | 2013-04-22 22:06:09,134 --- judging 334886 42 | 2013-04-22 22:06:09,684 --- 334886 result 5 43 | 2013-04-22 22:06:09,728 --- Thread-3 idle 44 | 2013-04-22 22:08:02,993 --- judging 334887 45 | 2013-04-22 22:08:03,750 --- 334887 result 1 46 | 2013-04-22 22:08:03,773 --- Thread-4 idle 47 | 2013-04-22 22:18:27,493 --- judging 334889 48 | 2013-04-22 22:18:27,642 --- 334889 result 1 49 | 2013-04-22 22:18:27,884 --- Thread-5 idle 50 | 2013-04-22 22:21:56,379 --- judging 334890 51 | 2013-04-22 22:21:56,677 --- 334890 result 1 52 | 2013-04-22 22:21:56,798 --- Thread-1 idle 53 | 2013-04-22 22:23:17,526 --- judging 334891 54 | 2013-04-22 22:23:17,678 --- 334891 result 4 55 | 2013-04-22 22:23:17,813 --- Thread-6 idle 56 | 2013-04-22 22:28:18,257 --- judging 334892 57 | 2013-04-22 22:28:18,435 --- 334892 result 1 58 | 2013-04-22 22:28:18,547 --- Thread-2 idle 59 | 2013-04-22 22:36:42,775 --- judging 334893 60 | 2013-04-22 22:36:42,972 --- 334893 result 1 61 | 2013-04-22 22:36:43,185 --- Thread-8 idle 62 | 2013-04-22 22:40:02,217 --- judging 334894 63 | 2013-04-22 22:40:03,033 --- 334894 result 1 64 | 2013-04-22 22:40:03,126 --- Thread-7 idle 65 | 2013-04-22 22:58:57,146 --- judging 334895 66 | 2013-04-22 22:58:57,928 --- 334895 result 1 67 | 2013-04-22 22:58:58,042 --- Thread-3 idle 68 | 2013-04-22 23:09:49,531 --- judging 334896 69 | 2013-04-22 23:09:49,683 --- 334896 result 1 70 | 2013-04-22 23:09:49,902 --- Thread-4 idle 71 | 2013-04-22 23:18:21,437 --- judging 334898 72 | 2013-04-22 23:18:21,580 --- 334898 result 1 73 | 2013-04-22 23:18:21,704 --- Thread-5 idle 74 | 2013-04-22 23:18:34,054 --- judging 334899 75 | 2013-04-22 23:18:34,251 --- 334899 result 1 76 | 2013-04-22 23:18:34,471 --- Thread-1 idle 77 | 2013-04-22 23:31:50,961 --- judging 334900 78 | 2013-04-22 23:31:51,143 --- 334900 result 1 79 | 2013-04-22 23:31:51,254 --- Thread-6 idle 80 | 2013-04-22 23:57:24,250 --- judging 334901 81 | 2013-04-22 23:57:24,500 --- 334901 result 1 82 | 2013-04-22 23:57:24,639 --- Thread-2 idle 83 | 2013-04-23 00:24:59,593 --- judging 334902 84 | 2013-04-23 00:25:00,681 --- 334902 result 8 85 | 2013-04-23 00:25:00,764 --- Thread-8 idle 86 | 2013-04-23 00:30:21,005 --- judging 334903 87 | 2013-04-23 00:30:21,684 --- 334903 result 1 88 | 2013-04-23 00:30:21,814 --- Thread-7 idle 89 | -------------------------------------------------------------------------------- /producer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def put_task_into_queue(): 5 | '''循环扫描数据库,将任务添加到队列''' 6 | while True: 7 | q.join() # 阻塞程序,直到队列里面的任务全部完成 8 | sql = "#####" 9 | data = run_sql(sql) 10 | time.sleep(0.2) # 延时0.2秒,防止因速度太快不能获取代码 11 | for i in data: 12 | solution_id, problem_id, user_id, contest_id, pro_lang = i 13 | dblock.acquire() 14 | ret = get_code(solution_id, problem_id, pro_lang) 15 | dblock.release() 16 | if ret == False: 17 | # 防止因速度太快不能获取代码 18 | time.sleep(0.5) 19 | dblock.acquire() 20 | ret = get_code(solution_id, problem_id, pro_lang) 21 | dblock.release() 22 | if ret == False: 23 | dblock.acquire() 24 | update_solution_status(solution_id, 11) 25 | dblock.release() 26 | clean_work_dir(solution_id) 27 | continue 28 | task = { 29 | "solution_id": solution_id, 30 | "problem_id": problem_id, 31 | "contest_id": contest_id, 32 | "user_id": user_id, 33 | "pro_lang": pro_lang, 34 | } 35 | q.put(task) 36 | dblock.acquire() 37 | update_solution_status(solution_id) 38 | dblock.release() 39 | time.sleep(0.5) 40 | -------------------------------------------------------------------------------- /protect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | """ 4 | 为了服务器安全,隐藏部分sql语句。 5 | 程序执行需要相关数据库和测试数据。 6 | """ 7 | import os 8 | import sys 9 | import shutil 10 | import subprocess 11 | import codecs 12 | import logging 13 | import shlex 14 | import time 15 | import config 16 | import lorun 17 | import threading 18 | import MySQLdb 19 | from db import run_sql 20 | from Queue import Queue 21 | 22 | 23 | def low_level(): 24 | try: 25 | os.setuid(int(os.popen("id -u %s" % "nobody").read())) 26 | except: 27 | pass 28 | try: 29 | # 降低程序运行权限,防止恶意代码 30 | os.setuid(int(os.popen("id -u %s" % "nobody").read())) 31 | except: 32 | logging.error("please run this program as root!") 33 | sys.exit(-1) 34 | # 初始化队列 35 | q = Queue(config.queue_size) 36 | # 创建数据库锁,保证一个时间只能一个程序都写数据库 37 | dblock = threading.Lock() 38 | 39 | 40 | def start_work_thread(): 41 | '''开启工作线程''' 42 | for i in range(config.count_thread): 43 | t = threading.Thread(target=worker) 44 | t.deamon = True 45 | t.start() 46 | 47 | 48 | def start_get_task(): 49 | '''开启获取任务线程''' 50 | t = threading.Thread(target=put_task_into_queue, name="get_task") 51 | t.deamon = True 52 | t.start() 53 | 54 | 55 | def check_thread(): 56 | low_level() 57 | '''检测评测程序是否存在,小于config规定数目则启动新的''' 58 | while True: 59 | try: 60 | if threading.active_count() < config.count_thread + 2: 61 | logging.info("start new thread") 62 | t = threading.Thread(target=worker) 63 | t.deamon = True 64 | t.start() 65 | time.sleep(1) 66 | except: 67 | pass 68 | 69 | 70 | def start_protect(): 71 | '''开启守护进程''' 72 | low_level() 73 | t = threading.Thread(target=check_thread, name="check_thread") 74 | t.deamon = True 75 | t.start() 76 | 77 | 78 | def main(): 79 | low_level() 80 | logging.basicConfig(level=logging.INFO, 81 | format='%(asctime)s --- %(message)s',) 82 | start_get_task() 83 | start_work_thread() 84 | start_protect() 85 | 86 | if __name__ == '__main__': 87 | main() 88 | -------------------------------------------------------------------------------- /result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ma6174/acmjudger/fd4e4086138db11f34440b27c5598a4e0515b78b/result.jpg -------------------------------------------------------------------------------- /run_program.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def run(problem_id, solution_id, language, data_count, user_id): 5 | low_level() 6 | '''获取程序执行时间和内存''' 7 | dblock.acquire() 8 | time_limit, mem_limit = get_problem_limit(problem_id) 9 | dblock.release() 10 | program_info = { 11 | "solution_id": solution_id, 12 | "problem_id": problem_id, 13 | "take_time": 0, 14 | "take_memory": 0, 15 | "user_id": user_id, 16 | "result": 0, 17 | } 18 | result_code = { 19 | "Waiting": 0, 20 | "Accepted": 1, 21 | "Time Limit Exceeded": 2, 22 | "Memory Limit Exceeded": 3, 23 | "Wrong Answer": 4, 24 | "Runtime Error": 5, 25 | "Output limit": 6, 26 | "Compile Error": 7, 27 | "Presentation Error": 8, 28 | "System Error": 11, 29 | "Judging": 12, 30 | } 31 | if check_dangerous_code(solution_id, language) == False: 32 | program_info['result'] = result_code["Runtime Error"] 33 | return program_info 34 | compile_result = compile(solution_id, language) 35 | if compile_result is False: # 编译错误 36 | program_info['result'] = result_code["Compile Error"] 37 | return program_info 38 | if data_count == 0: # 没有测试数据 39 | program_info['result'] = result_code["System Error"] 40 | return program_info 41 | result = judge( 42 | solution_id, 43 | problem_id, 44 | data_count, 45 | time_limit, 46 | mem_limit, 47 | program_info, 48 | result_code, 49 | language) 50 | logging.debug(result) 51 | return result 52 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo kill `ps aux | egrep "^nobody .*? protect.py" | cut -d " " -f4` 3 | sudo nohup python protect.py & 4 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo kill `ps aux | egrep "^nobody .*? protect.py" | cut -d " " -f4` 3 | -------------------------------------------------------------------------------- /worker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | def worker(): 5 | '''工作线程,循环扫描队列,获得评判任务并执行''' 6 | while True: 7 | if q.empty() is True: # 队列为空,空闲 8 | logging.info("%s idle" % (threading.current_thread().name)) 9 | task = q.get() # 获取任务,如果队列为空则阻塞 10 | solution_id = task['solution_id'] 11 | problem_id = task['problem_id'] 12 | language = task['pro_lang'] 13 | user_id = task['user_id'] 14 | data_count = get_data_count(task['problem_id']) # 获取测试数据的个数 15 | logging.info("judging %s" % solution_id) 16 | result = run( 17 | problem_id, 18 | solution_id, 19 | language, 20 | data_count, 21 | user_id) # 评判 22 | logging.info( 23 | "%s result %s" % ( 24 | result[ 25 | 'solution_id'], 26 | result[ 27 | 'result'])) 28 | dblock.acquire() 29 | update_result(result) # 将结果写入数据库 30 | dblock.release() 31 | if config.auto_clean: # 清理work目录 32 | clean_work_dir(result['solution_id']) 33 | q.task_done() # 一个任务完成 34 | --------------------------------------------------------------------------------