diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/About.md b/docs/About.md new file mode 100644 index 00000000000..c7cf8643f21 --- /dev/null +++ b/docs/About.md @@ -0,0 +1,22 @@ +# 关于 + + +HUSTOJ is an [GPL](https://github.com/zhblue/hustoj/blob/master/trunk/web/gpl-2.0.txt) Free Software. + +HUSTOJ 是采用GPL的自由软件。(仅限原创部分代码,其中使用了其他开源项目的组件,请遵循原组件的协议。) + +注意:基于本项目源码从事科研、论文、系统开发,请在文中或系统中表明来自于本项目的内容和创意。 + +论文请引用参考文献[基于开放式云平台的开源在线评测系统设计与实现](http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=CJFQ&dbname=CJFD2012&filename=JSJA2012S3088) + +> PS: GPL保证你可以合法忽略以上注意事项但不能保证你不受鄙视,呵呵。 + +如果这个项目对你有用,请: + +* 挥动鼠标,给个 Star! +* 保留网站页脚的二维码。 +* 访问 [tk题库](http://tk.hustoj.com) ,充值下载题目。 +* 向同学同事推荐这个项目。 +* 每天扫一扫本页底部的支付宝红包。 +* 在您的论文参考文献中写出本项目的网址。 +* 付费 50 元加入官方 QQ 群 :`23361372` 。 diff --git a/docs/Backup.md b/docs/Backup.md new file mode 100644 index 00000000000..a005ecb5949 --- /dev/null +++ b/docs/Backup.md @@ -0,0 +1,31 @@ +# 备份与还原 + +## 备份 + +脚本安装的用户,可以使用 `install` 目录中的 `bak.sh` 进行备份。 + +```bash +sudo bash /home/judge/src/install/bak.sh +``` + +备份后的数据在 `/var/backups/` + +百度学习 `crontab` 的用法后,可以使用 `sudo crontab -e` 定制自动备份计划,部分安装脚本中包含了自动备份,但可能需要运行上面的语句一次来激活。 + +## 还原 + +如果你需要进行跨系统迁移(如从 `Ubuntu` 迁移到 `CentOS`),可以尝试使用下面的脚本进行备份 + +```bash +sudo bash /home/judge/src/install/backup+.sh +``` + +备份后的归档在 `/home/judge/backup` ,命名格式为 `%Y-%m-%d-%H-%M-%S` + +将你需要迁移的归档复制到目标系统的 `/home/judge/backup` 目录下,执行下面的脚本进行恢复 + +```bash +sudo bash /home/judge/src/install/restore+.sh +``` + +脚本的第一个参数为恢复的目标归档,如果没有参数则默认为按名字排序后字典序最大的归档。 \ No newline at end of file diff --git a/docs/Composition-Client.md b/docs/Composition-Client.md new file mode 100644 index 00000000000..1cecf8b989c --- /dev/null +++ b/docs/Composition-Client.md @@ -0,0 +1,20 @@ +# Core 与 Web 的连接方式解析 + +### 简化 ER 图 + +![](images/c2web.jpg) + +### 数据库连接(默认) + +1. `Web` 插入 `Solution` 表。 +2. `core` 轮询 `solution` 表,发现新记录。 +3. `core` 更新 `solution` 表 `result` 等字段 +4. `Web` 端轮询 `soltuion` 显示 `result` 等字段。 + +### HTTP 方式连接 + +1. `Web` 插入 `Solution` 表 +2. `core` 访问 `Web` 端 `admin/problem_judge.php` ,发现新纪录 +3. `core` 向 `Web` 端 `admin/problem_judge.php` 提交数据,`problem_judge.php` 更新 `solution` 表 `result` 等字段。 +4. `Web` 端轮询 `soltuion` 显示 `result` 等字段。 + diff --git a/docs/Composition-Core.md b/docs/Composition-Core.md new file mode 100644 index 00000000000..a823ab6a5d5 --- /dev/null +++ b/docs/Composition-Core.md @@ -0,0 +1,81 @@ +# Core 解析 + +`core` 分 `3` 部分,`judged` 、 `judge_client` 、 `sim` 。源码解读:[点我](/Compostition-Code) + +### judged 解析 + +#### 简介 + +`judged` 为服务进程, `d` 即 `daemon`。负责轮询数据库或 `web` 端,提取判题队列。单个主机运行多个 `judged` ,分别负责不同的 `OJ` 判题。 + +![工作流程图](images/work.jpg) + +#### 基本参数 + +judged 可以接受一个参数作为自己的主目录,默认是 `/home/judge/` 。如: `sudo judged /home/judge/local` + +不指定参数将自动以单进程运行。当指定的参数不为/home/judge 时,就会有多个进程出现。 + +每个主目录可以有自己的 `etc/judge.conf` 数据目录可以共享,runX 目录需要独立。 + +#### judged 调试模式 + +judged 接受参数指定目录的情况下,还可以再接受一个 `debug` 作为调试模式开关。如:`sudo judged /home/judge/local debug` 调试模式的 `judged` 将不会进入后台,并且将输出大量调试信息,其调用的 `judge_client` 也工作在 `debug` 模式。 +### judge_client 解析 + +#### 简介 + +当发现新任务时产生 `judge_client` 进程。 + +`judge_client` 进程为实际判题程序,负责准备运行环境、数据,运行并监控目标程序的系统调用,采集运行指标,判断运行结果。 + +![工作流程图](images/work1.jpg) + +#### 后台安全机制 + +在UNIX 中有上百个[系统调用](http://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html),有一大部分是在用户程序运行过程中不需要的,比如说 `mkdir` `mount` 等,还有一部分会对系统造成安全隐患的,比如`fork` `kill` `exec` 等,还有一些比如 `socket` 等会造成敏感信息,比如测试数据的泄漏等。因为以上情况的存在,所以需要在运行用户程序的时候对用户加以限制,`linux` 下的 `ptrace` 在这里是一个非常好用的工具,它可以在用户态和内核态之间切换之前和之后,将进程暂停,以方便控制进程的处理,控制进程通过 `ptrace` 可以读取到当前进程想要去做什么,这样就可以在用户程序造成破坏之前将程序中止。限制非法系统调用,最好的办法是使用白名单机制,只允许程序使用一个小集合里的调用,对于其它调用,即使它是安全的,也不会被允许,比如 `mkdir` 。由于 `Pascal` `Java` `C/C++`的机制有些区别,因此,三种不同语言的白名单各不相同。 + +#### judge_client 调试模式 + +`judge_client [工作主目录] [调试]` + +如:`judge_client 2001 5 /home/judge/demo debug` + +将在 `/home/judge/demo/run5` 目录中对 `2001` 号提交进行重判,并打开调试模式,输出大量调试信息,运行后不删除中间结果。 + +这个模式可以帮助调试题目数据,发现数据问题和了解提交 RE 的详细错误原因。 + +### sim 简介 + +当配置为启用抄袭检查时, `judge_client` 将调用 `sim`,判断相似性结果,并写回数据库或 `web` 端。 + +`sim` 为第三方应用程序, 可进行语法分析判断文本相似度, 通过检验的程序将由 `judge_client` 复制进题目数据的 `ac` 目录,成为新的参考样本。 + +### 配置文件注释 + +配置文件: `judge.conf` 。 + + +配置 | 注释 +------------------------- | ------------------------------------------ +`OJ_HOST_NAME=127.0.0.1` | 如果用 mysql 连接读取数据库,数据库的主机地址。 +`OJ_USER_NAME=root` | 数据库帐号。 +`OJ_PASSWORD=root` | 数据库密码。 +`OJ_DB_NAME=jol` | 数据库名称。 +`OJ_PORT_NUMBER=3306` | 数据库端口。 +`OJ_RUNNING=4` | `judged` 会启动 `judge_client` 判题,这里规定最多同时运行几个 `judge_client`。 +`OJ_SLEEP_TIME=5` | `judged` 通过轮询数据库发现新任务,轮询间隔的休息时间,单位为秒。 +`OJ_TOTAL=1` | 老式并发处理中总的 `judged` 数量。 +`OJ_MOD=0` | 老式并发处理中,本 `judged` 负责处理 `solution_id` 按照 `TOTAL` 取模后余数为几的任务。 +`OJ_JAVA_TIME_BONUS=2` | Java 等虚拟机语言获得的额外运行时间。 +`OJ_JAVA_MEMORY_BONUS=512` | Java 等虚拟机语言获得的额外内存。 +`OJ_SIM_ENABLE=0` | 是否使用sim 进行代码相似度的检测。 +`OJ_HTTP_JUDGE=0` | 是否使用 `HTTP` 方式连接数据库,如果启用,则前面的 `HOST_NAME` 等设置忽略。 +`OJ_HTTP_BASEURL=http://127.0.0.1/` | 使用 `HTTP` 方式连接数据库的基础地址,就是 `OJ` 的首页地址。 +`OJ_HTTP_USERNAME=admin` | 使用 `HTTP` 方式所用的用户帐号(`HTTP_JUDGE` 权限),该帐号登录时不能启用 `VCODE` 图形验证码,但可以登录成功后启用。 +`OJ_HTTP_PASSWORD=admin` | 使用 `HTTP` 方式所用的用户帐号的密码 +`OJ_OI_MODE=0` | 是否启用 `OI(信息学奥林匹克竞赛)` 模式,即无论是否出错都继续判剩余的数据,在 `ACM` 比赛中一旦出错就停止运行。 +`OJ_SHM_RUN=0` | 是否使用 `/dev/shm` 的共享内存虚拟磁盘来运行答案,如果启用能提高判题速度,但需要较多内存。 +`OJ_USE_MAX_TIME=1` | 是否使用所有测试数据中最大的运行时间作为最后运行时间,如果不启用则以所有测试数据的总时间作为超时判断依据。 +`OJ_LANG_SET=0,1,2,3,4` | 判哪些语言的题目 + diff --git a/docs/Composition-Database.md b/docs/Composition-Database.md new file mode 100644 index 00000000000..4999f46caae --- /dev/null +++ b/docs/Composition-Database.md @@ -0,0 +1,227 @@ +# 数据库解析 + +### 数据库关系图 + +![](images/db.jpg) + +### 数据库表分析 + +序号 | 表名 | 作用 | 备注 +---- | ----------------- | -------------------- | -------------------- +1 | `compileinfo` | 记录编译错误的记录 | +2 | `contest` | 竞赛表 | +3 | `contest_problem` | 竞赛题目 | +4 | `loginlog` | 登入日志 | 记录正确与错误的登入日志 +5 | `mail` | 消息列表 | +6 | `news` | 新闻表 | +7 | `online` | 用户在线数据统计 | +8 | `privilege` | 权限授予 | +9 | `problem` | 题目表 | +10 | `reply` | 论坛(帖子及回复)表 | +11 | `runtimeinfo` | 运行错误信息 | +12 | `sim` | 相似度检测表 | 用于防作弊 +13 | `solution` | 程序运行结果记录 | +14 | `source_code` | 提交的源码 | +15 | `topic` | 论坛帖子表 | +16 | `users` | 用户信息 | +17 | `custominput` | 用于在线IDE | + +### 数据库表详解 + +**`compileinfo` - 记录编译错误的提交号(`id`)及原因** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ------ | -- | ------------- | ------------ +`solution_id` | `int` | 11 | N | 主键(提交id,即RunID) +`error` | `text` | - | Y | 编译错误原因 + +**`contest` - 竞赛表** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ---------- | ---- | ----------- | --- +`contest_id` | `int` | 11 | N | 竞赛id(主键) +`title` | `varchar` | 255 | Y | 竞赛标题 +`start_time` | `datetime` | - | Y | 开始时间(年月日时分) +`end_time` | `datatime` | - | Y | 结束时间(年月日时分) +`defunct` | `char` | 1 | N | 是否屏蔽(Y/N) +`description` | `text` | - | Y | 描述(在此版本中未用) +`private` | `tinyint` | 4 | - | 公开/内部(0/1) +`langmask` | `int` | 11 | - | 语言 + +**`contest_problem` - 竞赛题目** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------ | ------ | ----- | ---------- | --- +`problem_id` | `int` | 11 | N | 题目id +`contest_id` | `int` | 11 | Y | 竞赛id +`title` | `char` | 200 | N | 标题 +`num` | `int` | 11 | N | 竞赛题目编号 + +**`loginlog` - 登入日志(不管是否登入成功都记录)** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +--------- | ---------- | ---- | ----------- | --- +`user_id` | `varchar` | 20 | N | 用户id +`password` | `varchar` | 40 | Y | 密码(不一定正确) +`ip` | `varcahr` | 100 | Y | 登录的ip +`time` | `datetime` | - | Y | 登入时间 + +**`mail` - 站内消息系统** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +----------- | ---------- | ---- | ----------- | --- +`mail_id` | `int` | 11 | N | 消息编号 +`to_user` | `archar` | 20 | N | 接收者 +`from_user` | `varchar` | 20 | N | 发送者 +`title` | `varchar` | 200 | N | 标题 +`content` | `text` | - | Y | 内容 +`new_mail` | `tinyint` | 1 | N | 新消息(1/0) +`reply` | `tinyint` | 4 | Y | 回复 +`in_date` | `datetime` | - | Y | 时间 +`defunct` | `char` | 1 | N | 是否屏蔽(Y/N) + +**news - 新闻(首页显示)** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------ | ---------- | --- | ----------- | ----- +`news_id` | `int` | 11 | N | 新闻编号(主键) +`user_id` | `varchar` | 20 | N | 用户账号 +`title` | `varchar` | 200 | N | 新闻标题 +`content` | `text` | - | N | 内容 +`time` | `datetime` | - | N | 更新时间 +`importance` | `tinyint` | 4 | N | 关键字 +`defunct` | `char` | 1 | N | 是否屏蔽(Y/N) + +**`online`** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +----------- | --------- | ---- | ----------- | ------------ +`hash` | `varchar` | 32 | N | 主键 +`ip` | `varchar` | 20 | N | IP 地址 +`ua` | `varchar` | 255 | N | 浏览器发出的浏览器相关的标识字符串 +`refer` | `varchar` | 255 | Y |浏览器发出的一个表示访问的上个页面的网址。 +`lastmove` | `int` | 10 | N | 最后一次修改时间 +`firsttime` | `int` | 10 | Y | 第一次访问时间 +`uri` | `varchar` | 255 | Y | 统一资源指示器,包括URL(统一资源定位符) 和URN(统一资源名称)两种 + +**`privilege` - 用户分组** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +---------- | ------ | ---- | ----------- | ----------- +`user_id` | `char` | 20 | N | 用户帐号 +`rightstr` | `char` | 30 | N | 分组 +`defunct` | `char` | 1 | N | 是否屏蔽(Y/N) + +**`problem` - 题目表** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +--------------- | ---------- | ---- | ----------- | --------------- +`problem_id` | `int` | 11 | N | 题目编号,主键 +`title` | `varchar` | 200 | N | 标题 +`description` | `text` | - | Y | 题目描述 +`inupt` | `text` | - | Y | 输入说明 +`output` | `text` | - | Y | 输出说明 +`sample_input` | `text` | - | Y | 输入参照 +`sample_output` | `text` | - | Y | 输出参照 +`spj` | `char` | 1 | N | 是否为特别题目 +`hint` | `text` | - | Y | 提示 +`source` | `varchar` | 100 | Y | 来源 +`in_date` | `datetime` | - | Y | 加入时间 +`time_limit` | `int` | 11 | N | 限时(秒) +`memory_limit` | `int` | 11 | N | 空间限制(MByte) +`defunct` | `char` | 1 | N | 是否屏蔽(Y/N) +`accepted` | `int` | 11 | Y | 总ac 次数 +`submit` | `int` | 11 | Y | 总提交次数 +`solved` | `int` | 11 | Y | 解答(未用) + +**`reply` - 论坛(帖子及回复)** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +----------- | ---------- | ---- | ----------- | ----------- +`rid` | `int` | 11 | N | 帖子序号( 主键) +`author_id` | `varchar` | 20 | N | 作者帐号 +`time` | `datetime` | - | N | 发布时间 +`content` | `text` | - | N | 帖子内容 +`topic_id` | `int` | 11 | N | 帖子分组 +`status` | `int` | 2 | N | 状态(0:正常,1:锁定,2:删除) +`ip` | `varchar` | 30 | N | 发帖子者ip + +**`runtimeinfo` - 运行错误信息** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ------ | ---- | ----------- | ----------- +`solution_id` | `int` | 11 | N | 运行id(主键) +`error` | `text` | - | Y | 错误记录 + +**`sim` 相似度检测** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ------ | ---- | ----------- | ----------- +`s_id` | `int` | 11 | N | 提交号 `soltiotn_id`(主键) +`sim_s_id` | `int` | 11 | Y | 与 `s_id` 相似的`soltion_id` +`sim` | `int` | 11 | Y | 相似度(50-100) + +**`solution` 程序运行结果记录** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ---------- | ---- | ----------- | ----------- +`solution_id` | `int` | 11 | N | 运行id(主键) +`problem_id` | `int` | 11 | N | 问题id +`user_id` | `char` | 20 | N | 用户id +`time` | `int` | 11 | N | 用时(秒) +`memory` | `int` | 11 | N | 所用空间 +`in_date` | `datetime` | - | N | 加入时间 +`result` | `smallint` | 6 | N | 结果(4:AC) +`language` | `tinyint` | 4 | N | 语言 +`ip` | `char` | 15 | N | 用户ip +`contest_id` | `int` | 11 | Y | 所属于竞赛组 +`valid` | `tinyint` | 4 | N | 是否有效 +`num` | `tinyint` | 4 | N | 题目在竞赛中的顺序号 +`code_lenght` | `int` | 11 | N | 代码长度 +`judgetime` | `datetime` | - | Y | 判题时间 +`pass_rate` | `decimal` | 2 | N | 通过百分比(OI模式下可用) + +**`source_code` - 源代码** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ------ | ---- | ----------- | ----------- +`solution_id` | `int` | 11 | N | 运行id(主键) +`source` | `text` | - | N | 源代码 + +**`topic` - 论坛帖子主题** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +----------- | ----------- | ----- | ----------- | ----------- +`tid` | `int` | 11 | N | 帖子编号( 主键) +`title` | `varbinary` | 60 | N | 标题 +`status` | `int` | 2 | N | 状态(0:未锁定,1:锁定) +`top_level` | `int` | 2 | N | 置顶等级(0,1:题目置顶,2:分区置顶,3:总置顶) +`cid` | `int` | 11 | Y | 竞赛编号 +`pid` | `int` | 11 | N | 竞赛中题目编号 +`author_id` | `varchar` | 20 | N | 作者id + +**`users` - 用户** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ---------- | ---- | ----------- | ----------- +`user_id` | `varchar` | 20 | N | 用户id(主键) +`email` | `varchar` | 100 | Y | 用户E-mail +`submit` | `int` | 11 | Y | 用户提交次数 +`solved` | `int` | 11 | Y | 成功次数 +`defunct` | `char` | 1 | N | 是否屏蔽(Y/N) +`ip` | `varchar` | 20 | N | 用户注册ip +`accesstime` | `datetime` | - | Y | 用户注册时间 +`volume` | `int` | 11 | N | 页码(表示用户上次看到第几页) +`language` | `int` | 11 | N | 语言 +`password` | `varchar` | 32 | Y | 密码(加密) +`reg_time` | `datetime` | - | Y | 用户注册时间 +`nick` | `varchar` | 100 | N | 昵称 +`school` | `varchar` | 100 | N | 用户所在学校 + +**`custominput` - IDE** + +字段名 | 类型 | 长度 | 是否允许为空 | 备注 +------------- | ------ | ---- | ----------- | ----------- +`solution_id` | `int` | 11 | N | 用户id(主键) +`Input_text` | `text` | - | - | 输入测试数据 + diff --git a/docs/Composition-LiveCD.md b/docs/Composition-LiveCD.md new file mode 100644 index 00000000000..6c2f619bf2f --- /dev/null +++ b/docs/Composition-LiveCD.md @@ -0,0 +1,11 @@ +# LiveCD 解析 + +### LiveCD 的实现 + +通过 `uck` 工具解压出 `Ubuntu LiveCD` 的 `chroot` 环境,并在其中删除 `oo` 、 `gnome` 等大型程序释放空间,然后用 `apt` 工具安装基础环境,安装配置 `lxde` 和 `hustoj` 。再使用 `uck` 重新打包形成 `iso`。 + +### 升级方式 + +利用 `Github` 的 SVN 服务,用 SVN 客户端分别升级 `core` 和 `web` ,再编译 `core` ,并通过 `web` 提供可能的数据库升级。 + +`LiveCD` 中的升级脚本为 `update-hustoj` ,可以用 `which` 命令查找其实际位置。 diff --git a/docs/Composition-web.md b/docs/Composition-web.md new file mode 100644 index 00000000000..2d8f030f0f6 --- /dev/null +++ b/docs/Composition-web.md @@ -0,0 +1,59 @@ +# Web 解析 + +`web` 分两大部分,`前端` 和 `admin` 目录下的管理程序。 + +### 前端 + +无非是数据库的 CRUD 操作,关键功能是将用户提交的程序源码加入数据库的任务队列(`solution` 表、`souce_code` 表)。 + +### 管理程序 + +提供具有 `administrator` 等高级权限的账号管理试题、账号等方面的功能。其中 `FPS` 导入 +导出程序主要为 `XML` 格式的数据处理。 + +特别的, `judged` 可以多重启动, 通过增加基准目录参数指定启动位置( 默认 `/home/judge`),从而确定 `judge.conf` 的位置,并确定其他参数。因此不但可以一个 `web` 服务器下挂多个判题服务器,也可以一台物理机器上同时启动任意多个相互独立的 OnlineJudge 系统。实际使用中,使用开源的 `ispcp` 虚拟主机管理系统搭建多 `Web` 环境与 `hustoj` 协同工作取 +得了良好效果。 + +### 比赛 + +1. 比赛根据数据通过率排名,而不只看 `AC` 数量 + +2. 数据库`solution` 表 `pass_rate` 字段表示该条通过率。 + +3. 把`contestrank.php` 中的 `solved` 字段变成浮点对待。 + +4. 修改积分方式,按照希望的方式积分。可能需要给 TM 增加字段 `$p_wa_best_rate` 记录每题最大通过率。 + +### 配置文件注释 + +配置文件: `db_info.inc.php` 。 + + +配置 | 注释 +------------------------------------ | ------------------------------------------ +`static $DB_HOST="localhost";` | 数据库的服务器地址。 +`static $DB_NAME="jol";` | 数据库名。 +`static $DB_USER="root";` | 数据库用户名。 +`static $DB_PASS="root";` | 数据库密码。 +`static $OJ_NAME="HUSTOJ";` | OJ 的名字,将取代页面标题等位置 `HUSTOJ` 字样。 +`static $OJ_HOME="./";` | OJ 的首页地址。 +`static $OJ_ADMIN="root@localhost";` | 管理员email。 +`static $OJ_DATA="/home/judge/data";` | 测试数据所在目录,实际位置。 +`static $OJ_BBS="discuss";` | 论坛的形式,`discuss3` 为自带的简单论坛,`bbs` 为外挂论坛,参考 `bbs.php` 代码。 +`static $OJ_ONLINE=false;` | 是否使用在线监控,需要消耗一定的内存和计算,因此如果并发大建议关闭。 +`static $OJ_LANG="en";` | 默认的语言,中文为 `cn` 。 +`static $OJ_SIM=true;` | 是否显示相似度检测的结果。 +`static $OJ_DICT=true;` | 是否启用在线英字典。 +`static $OJ_LANGMASK=1008;` | `1mC` `2mCPP` `4mPascal` `8mJava` `16mRuby` `32mBash` 用掩码表示的OJ 接受的提交语言,可以被比赛设定覆盖。1008 为只使用 `C` `CPP` `Pascal` `Java`。 +`static $OJ_EDITE_AREA=true;` | 是否启用高亮语法显示的提交界面,可以在线编程,无须IDE。 +`static $OJ_AUTO_SHARE=false;` | `true`: 自动分享代码,启用的话,做出一道题就可以在该题的 `Status` 中看其他人的答案。 +`static $OJ_CSS="hoj.css";` | 默认的css,可以选择 `dark.css` 和 `gcode.css` , 具有有限的界面制定效果。 +`static $OJ_SAE=false;` | 是否是在新浪的云平台运行web 部分 +`static $OJ_VCODE=true;` | 是否启用图形登录、注册验证码。 +`static $OJ_APPENDCODE=false;` | 是否启用自动添加代码,启用的话,提交时会参考$OJ_DATA 对应目录里是否有 `append.c` 一类的文件,有的话会把其中代码附加到对应语言的答案之后,巧妙使用可以指定 `main` 函数而要求学生编写main 部分调用的函数。 +`static $OJ_MEMCACHE=false;` | 是否使用 `memcache` 作为页面缓存,如果不启用则用 `/cache` 目录 +`static $OJ_MEMSERVER="127.0.0.1";` | `memcached` 的服务器地址 +`static $OJ_MEMPORT=11211;` | `memcached` 的端口 +`static $OJ_RANK_LOCK_PERCENT=0;` | 比赛封榜时间的比率,如 5 小时比赛设为 `0.2` 则最后 1 小时封榜。 +`static $OJ_SHOW_DIFF=false;` | 显示 `WrongAnswer` 时的对比 + diff --git a/docs/Composition.md b/docs/Composition.md new file mode 100644 index 00000000000..e6d46d0d6d1 --- /dev/null +++ b/docs/Composition.md @@ -0,0 +1,20 @@ +# 结构简析 + +### 概述 + +HUSTOJ 分为两大部分,`core` 和 `web` ,分别对应判题和数据管理两大功能。 + +`core` 和 `web` 之间数据交换有两种方式: + +1、通过数据库,轮询。 +2、通过 `wget` 实现的 `HTTP` 请求。 + +两种方式的选择在判题端的配置文件 `/home/judge/etc/judge.conf` 中, `HTTP_JUDGE=1` 则启用后者,默认为前者。 + +### 分类 + +- [Core 解析](/Composition-Core) +- [Web 解析](/Composition-web) +- [Core 与 Web 的连接方式解析](/Composition_Client) +- [数据库解析](/Composition-Database) +- [LiveCD 解析](/Composition-LiveCD) diff --git a/docs/Compostition-Code.md b/docs/Compostition-Code.md new file mode 100644 index 00000000000..f5001363036 --- /dev/null +++ b/docs/Compostition-Code.md @@ -0,0 +1,3136 @@ +# 代码解读 + + +原文地址: + +### 第一部分 + +> 非常感谢 `zhblue` 贡献了这么美丽的代码 +> +> 为了开发适合自己学校的oj,努力研读代码中,不断的百度,调试,测试 +> +> 对 `ubuntu` ,`linux` 的各种文件系统,进程系统,C语言编程都学习了不少 +> +> 给大家分享下,希望能减少重复的工作量 +> +> 注释里有很多不足,不到位的地方,请批评指正 +> +> 应该不侵权吧 + +```c +/* + * Copyright 2008 sempr + * + * Refacted and modified by zhblue + * Bug report email newsclan@gmail.com + * + * This file is part of HUSTOJ. + * + * HUSTOJ is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * HUSTOJ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HUSTOJ. if not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +static int DEBUG = 0; //是否启用调试,来查看日志运行记录,默认0,不启用 +#define BUFFER_SIZE 1024 +#define LOCKFILE "/var/run/judged.pid" +#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) +#define STD_MB 1048576 + +#define OJ_WT0 0 +#define OJ_WT1 1 +#define OJ_CI 2 +#define OJ_RI 3 +#define OJ_AC 4 +#define OJ_PE 5 +#define OJ_WA 6 +#define OJ_TL 7 +#define OJ_ML 8 +#define OJ_OL 9 +#define OJ_RE 10 +#define OJ_CE 11 +#define OJ_CO 12 + +static char host_name[BUFFER_SIZE]; +static char user_name[BUFFER_SIZE]; +static char password[BUFFER_SIZE]; +static char db_name[BUFFER_SIZE]; +static char oj_home[BUFFER_SIZE]; +static char oj_lang_set[BUFFER_SIZE]; +static int port_number; +static int max_running; +static int sleep_time; +static int sleep_tmp; +static int oj_tot; +static int oj_mod; +static int http_judge = 0; +static char http_baseurl[BUFFER_SIZE]; +static char http_username[BUFFER_SIZE]; +static char http_password[BUFFER_SIZE]; + +static bool STOP = false; + +static MYSQL *conn; +static MYSQL_RES *res; //mysql读取结果集,在_get_http/mysql_jobs()中被更新 +static MYSQL_ROW row; +//static FILE *fp_log; +static char query[BUFFER_SIZE];//在init_mysql_conf中更新,固定取2倍最大判题客户端的待评判题目solution_id + +void call_for_exit(int s) { + STOP = true; + printf("Stopping judged...\n"); +} + +void write_log(const char *fmt, ...) { + va_list ap; + char buffer[4096]; +// time_t t = time(NULL); +// int l; + sprintf(buffer, "%s/log/client.log", oj_home); + FILE *fp = fopen(buffer, "a+"); + if (fp == NULL) { + fprintf(stderr, "openfile error!\n"); + system("pwd"); + } + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + fprintf(fp, "%s\n", buffer); + if (DEBUG) + printf("%s\n", buffer); + va_end(ap); + fclose(fp); + +} + +int after_equal(char * c) { + int i = 0; + for (; c[i] != '\0' && c[i] != '='; i++) + ; + return ++i; +} +void trim(char * c) { + char buf[BUFFER_SIZE]; + char * start, *end; + strcpy(buf, c); + start = buf; + while (isspace(*start)) + start++; + end = start; + while (!isspace(*end)) + end++; + *end = '\0'; + strcpy(c, start); +} +bool read_buf(char * buf, const char * key, char * value) { + if (strncmp(buf, key, strlen(key)) == 0) { + strcpy(value, buf + after_equal(buf)); + trim(value); + if (DEBUG) + printf("%s\n", value); + return 1; + } + return 0; +} +void read_int(char * buf, const char * key, int * value) { + char buf2[BUFFER_SIZE]; + if (read_buf(buf, key, buf2)) + sscanf(buf2, "%d", value); + +} +// read the configue file +void init_mysql_conf() { + FILE *fp = NULL; + char buf[BUFFER_SIZE]; + host_name[0] = 0; + user_name[0] = 0; + password[0] = 0; + db_name[0] = 0; + port_number = 3306; + max_running = 3; + sleep_time = 1; + oj_tot = 1; + oj_mod = 0; + strcpy(oj_lang_set, "0,1,2,3,4,5,6,7,8,9,10"); + fp = fopen("./etc/judge.conf", "r"); + if (fp != NULL) { + while (fgets(buf, BUFFER_SIZE - 1, fp)) { + read_buf(buf, "OJ_HOST_NAME", host_name); + read_buf(buf, "OJ_USER_NAME", user_name); + read_buf(buf, "OJ_PASSWORD", password); + read_buf(buf, "OJ_DB_NAME", db_name); + read_int(buf, "OJ_PORT_NUMBER", &port_number); + read_int(buf, "OJ_RUNNING", &max_running); + read_int(buf, "OJ_SLEEP_TIME", &sleep_time); + read_int(buf, "OJ_TOTAL", &oj_tot); + + read_int(buf, "OJ_MOD", &oj_mod); + + read_int(buf, "OJ_HTTP_JUDGE", &http_judge); + read_buf(buf, "OJ_HTTP_BASEURL", http_baseurl); + read_buf(buf, "OJ_HTTP_USERNAME", http_username); + read_buf(buf, "OJ_HTTP_PASSWORD", http_password); + read_buf(buf, "OJ_LANG_SET", oj_lang_set); + + } + sprintf(query, + "SELECT solution_id FROM solution WHERE language in (%s) and result<2 and MOD(solution_id,%d)=%d ORDER BY result ASC,solution_id ASC limit %d", + oj_lang_set, oj_tot, oj_mod, max_running * 2); + sleep_tmp = sleep_time; + // fclose(fp); + } +} + + +//当有代评测提交,并且进程数允许的情况下,创建新的子进程调用该评测函数 +//输入:代评测提交的solution_id, 子进程在ID[]中的保存位置 i +void run_client(int runid, int clientid) { + char buf[BUFFER_SIZE], runidstr[BUFFER_SIZE]; + //在Linux系统中,Resouce limit指在一个进程的执行过程中,它所能得到的资源的限制, + //比如进程的core file的最大值,虚拟内存的最大值等 ,这是运行时间,内存大小实现的关键 + /* + 结构体中 rlim_cur是要取得或设置的资源软限制的值,rlim_max是硬限制 + 这两个值的设置有一个小的约束: + 1) 任何进程可以将软限制改为小于或等于硬限制 + 2)任何进程都可以将硬限制降低,但普通用户降低了就无法提高,该值必须等于或大于软限制 + 3) 只有超级用户可以提高硬限制 + + setrlimit(int resource,const struct rlimit rlptr);返回:若成功为0,出错为非0 + RLIMIT_CPU:CPU时间的最大量值(秒),当超过此软限制时向该进程发送SIGXCPU信号 + RLIMIT_FSIZE:可以创建的文件的最大字节长度,当超过此软限制时向进程发送SIGXFSZ + */ + struct rlimit LIM; + LIM.rlim_max = 800; + LIM.rlim_cur = 800; + setrlimit(RLIMIT_CPU, &LIM);//cpu运行时间限制 + + LIM.rlim_max = 80 * STD_MB; + LIM.rlim_cur = 80 * STD_MB; + setrlimit(RLIMIT_FSIZE, &LIM);//可文件大小限制,防止恶意程序的吗? + + LIM.rlim_max = STD_MB << 11;//左移11 STD_MB是2^20 MB 2^11MB 2GB机器起码的2GB虚拟内存? + LIM.rlim_cur = STD_MB << 11; + setrlimit(RLIMIT_AS, &LIM);//最大运行的虚拟内存大小限制 + + LIM.rlim_cur = LIM.rlim_max = 200; + setrlimit(RLIMIT_NPROC, &LIM);//每个实际用户ID所拥有的最大子进程数,这些都是为了防止恶意程序的吧?? + + //buf[0]=clientid+'0'; buf[1]=0; + sprintf(runidstr, "%d", runid);//转换成字符?还是字符串? + sprintf(buf, "%d", clientid); + + //write_log("sid=%s\tclient=%s\toj_home=%s\n",runidstr,buf,oj_home); + //sprintf(err,"%s/run%d/error.out",oj_home,clientid); + //freopen(err,"a+",stderr); + + if (!DEBUG) + execl("/usr/bin/judge_client", "/usr/bin/judge_client", runidstr, buf, + oj_home, (char *) NULL); + else + + // 返回值:如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno 中. + // execl()其中后缀"l"代表list也就是参数列表的意思,第一参数path字符指针所指向要执行的文件路径, + // 接下来的参数代表执行该文件时传递的参数列表:argv[0],argv[1]... 最后一个参数须用空指针NULL作结束。 + // 执行/bin目录下的ls, 第一参数为程序名ls, 第二个参数为"-al", 第三个参数为"/etc/passwd" + // execl("/bin/ls", "ls", "-al", "/etc/passwd", (char *) 0); + // 这里第一个参数为程序名称 judge_client ,第二个参数为代评测题目 id , 第三个为本进程 pid 保存位置,第四个参数为 oj 目录 + // 默认/home/judge,第五个参数为"debug" + execl("/usr/bin/judge_client", "/usr/bin/judge_client", runidstr, buf, + oj_home, "debug", (char *) NULL); + + // exit(0); +} +//执行sql语句成功返回1,否则返回0 +//并且关闭是否conn,它在init里初始化开始的 +int executesql(const char * sql) { + + if (mysql_real_query(conn, sql, strlen(sql))) { + if (DEBUG) + write_log("%s", mysql_error(conn)); + sleep(20); + conn = NULL; + return 1; + } else + return 0; +} + +int init_mysql() { + if (conn == NULL) { + conn = mysql_init(NULL); // init the database connection + /* connect the database */ + const char timeout = 30; + mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); + + if (!mysql_real_connect(conn, host_name, user_name, password, db_name, + port_number, 0, 0)) { + if (DEBUG) + write_log("%s", mysql_error(conn)); + sleep(2); + return 1; + } else { + return 0; + } + } else { + return executesql("set names utf8"); + } +} +FILE * read_cmd_output(const char * fmt, ...) { + char cmd[BUFFER_SIZE]; + + FILE * ret = NULL; + va_list ap; + + va_start(ap, fmt); + vsprintf(cmd, fmt, ap); + va_end(ap); + //if(DEBUG) printf("%s\n",cmd); + ret = popen(cmd, "r"); + + return ret; +} +int read_int_http(FILE * f) { + char buf[BUFFER_SIZE]; + fgets(buf, BUFFER_SIZE - 1, f); + return atoi(buf); +} +bool check_login() { + const char * cmd = + "wget --post-data=\"checklogin=1\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + int ret = 0; + + FILE * fjobs = read_cmd_output(cmd, http_baseurl); + ret = read_int_http(fjobs); + pclose(fjobs); + + return ret > 0; +} +void login() { + if (!check_login()) { + char cmd[BUFFER_SIZE]; + sprintf(cmd, + "wget --post-data=\"user_id=%s&password=%s\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/login.php\"", + http_username, http_password, http_baseurl); + system(cmd); + } + +} +int _get_jobs_http(int * jobs) { + login(); + int ret = 0; + int i = 0; + char buf[BUFFER_SIZE]; + const char * cmd = + "wget --post-data=\"getpending=1&oj_lang_set=%s&max_running=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, oj_lang_set, max_running, http_baseurl); + while (fscanf(fjobs, "%s", buf) != EOF) { + //puts(buf); + int sid = atoi(buf); + if (sid > 0) + jobs[i++] = sid; + //i++; + } + pclose(fjobs); + ret = i; + while (i <= max_running * 2) + jobs[i++] = 0; + return ret; + return ret; +} +//功能:取得待评测题目信息到jobs数组 +//输入:int * jobs :保存solution_id/runid +//输出:如果查询成功则返回:要评测题目数量 +//如果查询待判题目不成功则返回0 + +int _get_jobs_mysql(int * jobs) { + //mysql.h + //如果查询数据包括二进制或者更快速度 用这个 + //如果执行成功,返回0;不成功非0 + if (mysql_real_query(conn, query, strlen(query))) { + if (DEBUG) + write_log("%s", mysql_error(conn)); + sleep(20); + return 0; + } + + //mysql.h + //返回具有多个结果的MYSQL_RES结果集合。如果出现错误,返回NULL + //具体参见百度 + res = mysql_store_result(conn); + int i = 0; + int ret = 0; + //遍历结果集mysql_fetch_row() + while ((row = mysql_fetch_row(res)) != NULL) { + jobs[i++] = atoi(row[0]); + } + ret = i; //要评测jobs末端 如 0 1 2 有数据,则i=3代表数据 + while (i <= max_running * 2) + jobs[i++] = 0; //设定的最大工作数目为max_running*2,将0-8置位0共9个 max_running*2+1数组开这么大 + return ret; + return ret; +} +int get_jobs(int * jobs) { + if (http_judge) { //web和core默认连接方式:数据库,web插入solution,core轮训/更新solution-result,web轮训solution-result + return _get_jobs_http(jobs); + } else + return _get_jobs_mysql(jobs);//读取要判题的任务数量 + +} + +//更新初始化solution表格 +//更新成功返回1;否则0 +// 疑问:OJ_CI为2,and result < 2这句怎么都是不成立,这个Sql语句怎么都不会执行成功才对啊 +//用limit 1加了一层保障。避免where 条件出现异常时,错误更新影响太多。 +//不知道php初始写多少,但是调用给的参数为2啊,不懂!!!! +bool _check_out_mysql(int solution_id, int result) { + char sql[BUFFER_SIZE]; //sql语句保存 + sprintf(sql, + "UPDATE solution SET result=%d,time=0,memory=0,judgetime=NOW() WHERE solution_id=%d and result<2 LIMIT 1", + result, solution_id); +//执行sql语句,成功返回0;否则非0 + if (mysql_real_query(conn, sql, strlen(sql))) { + syslog(LOG_ERR | LOG_DAEMON, "%s", mysql_error(conn)); + return false; + } else { + //影响行数,更新数大于0,执行成功,返回1,否则0 + if (mysql_affected_rows(conn) > 0ul) + return true; + else + return false; + } + +} + +bool _check_out_http(int solution_id, int result) { + login(); + const char * cmd = + "wget --post-data=\"checkout=1&sid=%d&result=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + int ret = 0; + FILE * fjobs = read_cmd_output(cmd, solution_id, result, http_baseurl); + fscanf(fjobs, "%d", &ret); + pclose(fjobs); + + return ret; +} + +//初始更新solution表 +//依据参数不同执行不同的更新函数 +bool check_out(int solution_id, int result) { + + if (http_judge) { + return _check_out_http(solution_id, result); + } else + return _check_out_mysql(solution_id, result); + +} +int work() { +// char buf[1024]; + static int retcnt = 0;//统计 已经 完成评测次数 + int i = 0; + static pid_t ID[100]; //short类型的宏定义,进程表中的索引项,进程号;保存正在执行的子进程pid + static int workcnt = 0;//统计 现用 judge_client进程数量 + int runid = 0; //solution_id,测试运行编号 + int jobs[max_running * 2 + 1];//max_running 从judge.conf获取,一般为4,这里设置为工作目录:9 + pid_t tmp_pid = 0; + + //for(i=0;i 0; j++) { + runid = jobs[j]; //读取solution_id,待评测提交题目id + //老式并发处理中,默认oj_tot 为 1 oj_mod 为0,在init_sql_conf中设置 所以无用 + if (runid % oj_tot != oj_mod) + continue; + if (DEBUG) //调试用默认0 无用 + write_log("Judging solution %d", runid); + //workcnt 为static 变量,相当于死锁,统计现用run_client进程 数目 + //本if 等待可用 子进程,并且用 i 腾出保存 新子进程的位置 + if (workcnt >= max_running) { // if no more client can running + //如果达到了可用最大进程数目,那么等待一个子进程结束 + //waitpid,参考linux 下 c 语言编程下的 进程管理 + //waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束 + //pid_t waitpid(pid_t pid,int * status,int options); + //pid=-1 代表任意子进程;status 取回子进程识别码,这里不需要所以NULL; + //参数options提供了一些额外的选项来控制waitpid,比如不等待继续执行,这里0代表不使用,进程挂起 + //如果 有子进程已经结束,那么执行到这里的时候会直接跳过,子进程也会由僵尸进程释放 + //返回结束的子进程pid + tmp_pid = waitpid(-1, NULL, 0); // wait 4 one child exit + workcnt--;//子进程结束了个,那么现用judge_client数量减一 + retcnt++;//评测完成数加1 + //清除保存在 ID[]里的已经结束的子进程信息 + for (i = 0; i < max_running; i++) // get the client id + if (ID[i] == tmp_pid) + break; // got the client id + ID[i] = 0; + } else { // have free client + + for (i = 0; i < max_running; i++) // find the client id + if (ID[i] == 0) + break; // got the client id + } + + //其实这里worknct>\n", runid, i); + run_client(runid, i); //在子进程里更新ID[0]=pid // if the process is the son, run it + exit(0);//子进程执行完毕退出0,父进程不会执行这段if ,在run_client里进程会跳转到execl(judge_client) + //执行成功不返回,不成功返回非0,保存在erro里,那么这里又是怎么执行到的,子进程如何退出的?????? + } + + } else {//理论上,在上个if里已经保证了这里为ID[i] = 0,这里估计是为了进一步保证 + ID[i] = 0; + } + } + + //把本次轮训到的代评测题目全部投入评测后 + //在非挂起等待子进程的结束,如果有子进程评测完成结束 + //在上个的for里,当可用进程没有的时候,那么就必须等其中一个进程结束,那么才能继续执行,哪怕在for里已经有 + // 子进程结束是僵尸进程了,只要workcnt 0) { + workcnt--; + retcnt++; + for (i = 0; i < max_running; i++) // get the client id + if (ID[i] == tmp_pid) + break; // got the client id + ID[i] = 0; + printf("tmp_pid = %d\n", tmp_pid); + } + //释放数据库资源 + //这里commit的调用,不知道是为了关闭conn,还是数据库不支持自动commit + //还是彻底缩小日志,不给机会rollback,待学习?????? + if (!http_judge) { + mysql_free_result(res); // free the memory + executesql("commit"); + } + if (DEBUG && retcnt) + write_log("<<%ddone!>>", retcnt); + //free(ID); + //free(jobs); + //返回已经评测的次数 + return retcnt; +} + +int lockfile(int fd) { + struct flock fl; + fl.l_type = F_WRLCK; + fl.l_start = 0; + fl.l_whence = SEEK_SET; + fl.l_len = 0; + return (fcntl(fd, F_SETLK, &fl)); +} + +int already_running() { + int fd; + char buf[16]; + fd = open(LOCKFILE, O_RDWR | O_CREAT, LOCKMODE); + if (fd < 0) { + syslog(LOG_ERR | LOG_DAEMON, "can't open %s: %s", LOCKFILE, + strerror(errno)); + exit(1); + } + if (lockfile(fd) < 0) { + if (errno == EACCES || errno == EAGAIN) { + close(fd); + return 1; + } + syslog(LOG_ERR | LOG_DAEMON, "can't lock %s: %s", LOCKFILE, + strerror(errno)); + exit(1); + } + ftruncate(fd, 0); + sprintf(buf, "%d", getpid()); + write(fd, buf, strlen(buf) + 1); + return (0); +} +int daemon_init(void) + +{ + pid_t pid; + + if ((pid = fork()) < 0) + return (-1); + + else if (pid != 0) + exit(0); /* parent exit */ + + /* child continues */ + + setsid(); /* become session leader */ + + chdir(oj_home); /* change working directory */ + + umask(0); /* clear file mode creation mask */ + + close(0); /* close stdin */ + + close(1); /* close stdout */ + + close(2); /* close stderr */ + + return (0); +} + +int main(int argc, char** argv) { + DEBUG = (argc > 2); + if (argc > 1) + strcpy(oj_home, argv[1]); + else + strcpy(oj_home, "/home/judge"); + chdir(oj_home); // change the dir + + if (!DEBUG) + daemon_init();//创建一个daemon守护进程 + if (strcmp(oj_home, "/home/judge") == 0 && already_running()) { + syslog(LOG_ERR | LOG_DAEMON, + "This daemon program is already running!\n"); + return 1; + } +// struct timespec final_sleep; +// final_sleep.tv_sec=0; +// final_sleep.tv_nsec=500000000; + init_mysql_conf(); // set the database info + signal(SIGQUIT, call_for_exit); + signal(SIGKILL, call_for_exit); + signal(SIGTERM, call_for_exit); + int j = 1; + while (1) { // start to run + // 这个 while 的好处在于,只要一有任务就抓紧占用系统优先把所以任务处理完成,哪怕会空循环几次的可能存在 + // 但是没有任务后,就会进入到"懒散"的 休息sleep(time)后再轮询是不是有任务,释放系统的资源,避免 Damon 一直死循环占用系统 + while (j && (http_judge || !init_mysql())) { + + j = work();//如果读取失败或者没有要评测的数据,那么返回0,利用那么有限的几个进程来评测无限的任务量 + + } + sleep(sleep_time); + j = 1; + } + return 0; +} +``` + + +### 第二部分 + +> 读的痛苦又快乐。。。 + + +```c + +// +// File: main.cc +// Author: sempr +// refacted by zhblue + /* + * Copyright 2008 sempr + * + * Refacted and modified by zhblue + * Bug report email newsclan@gmail.com + * + * + * This file is part of HUSTOJ. + * + * HUSTOJ is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * HUSTOJ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HUSTOJ. if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include "okcalls.h" + +#define STD_MB 1048576 +#define STD_T_LIM 2 +#define STD_F_LIM (STD_MB<<5) +#define STD_M_LIM (STD_MB<<7) +#define BUFFER_SIZE 512 + +#define OJ_WT0 0 +#define OJ_WT1 1 +#define OJ_CI 2 +#define OJ_RI 3 +#define OJ_AC 4 +#define OJ_PE 5 +#define OJ_WA 6 +#define OJ_TL 7 +#define OJ_ML 8 +#define OJ_OL 9 +#define OJ_RE 10 +#define OJ_CE 11 +#define OJ_CO 12 +#define OJ_TR 13 +/*copy from ZOJ + http://code.google.com/p/zoj/source/browse/trunk/judge_client/client/tracer.cc?spec=svn367&r=367#39 + */ +#ifdef __i386 +#define REG_SYSCALL orig_eax +#define REG_RET eax +#define REG_ARG0 ebx +#define REG_ARG1 ecx +#else +#define REG_SYSCALL orig_rax +#define REG_RET rax +#define REG_ARG0 rdi +#define REG_ARG1 rsi + +#endif + +//目录分布 +//home/judge + +//home/judge/data +//home/judge/data/1000 题目编号1000 +//home/judge/data/1000/sample.in 样例数据输入 +//home/judge/data/1000/sample.out 样例数据输出 +//home/judge/data/1000/test.in 测试数据输入 +//home/judge/data/1000/test.out 测试数据输出 + +//home/judge/etc +//home/judge/etc/java0.policy Java编译的配置 +//home/judge/etc/judge.conf 配置文件,core与web为轮询数据库方式下 + +//home/judge/log 如果启用调试,日志保存目录 +//home/judge/run0 可以启用的调试进程0 -3共4个默认的 也就是max_ruuning 默认值 +//home/judge/run1 +//home/judge/run2 +//home/judge/run3 + + + +static int DEBUG = 0; //是否启用调试默认否 +static char host_name[BUFFER_SIZE];//主机地址 +static char user_name[BUFFER_SIZE];//用户名 +static char password[BUFFER_SIZE];//密码 +static char db_name[BUFFER_SIZE];//数据库名称 +static char oj_home[BUFFER_SIZE];//盼题目录 默认/home/judge +static int port_number; //端口 +static int max_running; //最大的进程数 +static int sleep_time; //轮询时间间隔 +static int java_time_bonus = 5; +static int java_memory_bonus = 512; +static char java_xms[BUFFER_SIZE]; +static char java_xmx[BUFFER_SIZE]; +static int sim_enable = 0; //是否启用相似度查重 +static int oi_mode = 0; //编译模式 ,在update_solution中用到了 +static int use_max_time = 0; //设置的最大运行时间 + +static int http_judge = 0; //core与web链接方式,默认,轮询数据库 +static char http_baseurl[BUFFER_SIZE]; + +static char http_username[BUFFER_SIZE]; //网络用户名? +static char http_password[BUFFER_SIZE]; +static int shm_run = 0; //这个事什么意思???? + +static char record_call = 0; + +//static int sleep_tmp; +#define ZOJ_COM +MYSQL *conn; //数据库链接,开始初始化,最终释放资源 + +static char lang_ext[13][8] = { "c", "cc", "pas", "java", "rb", "sh", "py", + "php", "pl", "cs", "m", "bas", "scm" }; //测试语言列表 0 c 1 c++ +//static char buf[BUFFER_SIZE]; + +long get_file_size(const char * filename) { + struct stat f_stat; + + if (stat(filename, &f_stat) == -1) { + return 0; + } + + return (long) f_stat.st_size; +} + +void write_log(const char *fmt, ...) { + va_list ap; + char buffer[4096]; + // time_t t = time(NULL); + //int l; + sprintf(buffer, "%s/log/client.log", oj_home); + FILE *fp = fopen(buffer, "a+"); + if (fp == NULL) { + fprintf(stderr, "openfile error!\n"); + system("pwd"); + } + va_start(ap, fmt); + //l = + vsprintf(buffer, fmt, ap); + fprintf(fp, "%s\n", buffer); + if (DEBUG) + printf("%s\n", buffer); + va_end(ap); + fclose(fp); + +} +int execute_cmd(const char * fmt, ...) { + char cmd[BUFFER_SIZE]; + + int ret = 0; + va_list ap; + + va_start(ap, fmt); + vsprintf(cmd, fmt, ap); + ret = system(cmd); + va_end(ap); + return ret; +} + +const int call_array_size = 512; +int call_counter[call_array_size] = { 0 }; +static char LANG_NAME[BUFFER_SIZE]; +void init_syscalls_limits(int lang) { + int i; + memset(call_counter, 0, sizeof(call_counter)); + if (DEBUG) + write_log("init_call_counter:%d", lang); + if (record_call) { // C & C++ + for (i = 0; i < call_array_size; i++) { + call_counter[i] = 0; + } + } else if (lang <= 1) { // C & C++ + for (i = 0; i==0||LANG_CV[i]; i++) { + call_counter[LANG_CV[i]] = HOJ_MAX_LIMIT; + } + } else if (lang == 2) { // Pascal + for (i = 0; i==0||LANG_PV[i]; i++) + call_counter[LANG_PV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 3) { // Java + for (i = 0; i==0||LANG_JV[i]; i++) + call_counter[LANG_JV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 4) { // Ruby + for (i = 0; i==0||LANG_RV[i]; i++) + call_counter[LANG_RV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 5) { // Bash + for (i = 0; i==0||LANG_BV[i]; i++) + call_counter[LANG_BV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 6) { // Python + for (i = 0; i==0||LANG_YV[i]; i++) + call_counter[LANG_YV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 7) { // php + for (i = 0; i==0||LANG_PHV[i]; i++) + call_counter[LANG_PHV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 8) { // perl + for (i = 0; i==0||LANG_PLV[i]; i++) + call_counter[LANG_PLV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 9) { // mono c# + for (i = 0; i==0||LANG_CSV[i]; i++) + call_counter[LANG_CSV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 10) { //objective c + for (i = 0; i==0||LANG_OV[i]; i++) + call_counter[LANG_OV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 11) { //free basic + for (i = 0; i==0||LANG_BASICV[i]; i++) + call_counter[LANG_BASICV[i]] = HOJ_MAX_LIMIT; + } else if (lang == 12) { //scheme guile + for (i = 0; i==0||LANG_SV[i]; i++) + call_counter[LANG_SV[i]] = HOJ_MAX_LIMIT; + } + +} + +int after_equal(char * c) { + int i = 0; + for (; c[i] != '\0' && c[i] != '='; i++) + ; + return ++i; +} +void trim(char * c) { + char buf[BUFFER_SIZE]; + char * start, *end; + strcpy(buf, c); + start = buf; + while (isspace(*start)) + start++; + end = start; + while (!isspace(*end)) + end++; + *end = '\0'; + strcpy(c, start); +} +bool read_buf(char * buf, const char * key, char * value) { + if (strncmp(buf, key, strlen(key)) == 0) { + strcpy(value, buf + after_equal(buf)); + trim(value); + if (DEBUG) + printf("%s\n", value); + return 1; + } + return 0; +} +void read_int(char * buf, const char * key, int * value) { + char buf2[BUFFER_SIZE]; + if (read_buf(buf, key, buf2)) + sscanf(buf2, "%d", value); + +} +// read the configue file +void init_mysql_conf() { + FILE *fp = NULL; + char buf[BUFFER_SIZE]; + host_name[0] = 0; + user_name[0] = 0; + password[0] = 0; + db_name[0] = 0; + port_number = 3306; + max_running = 3; + sleep_time = 3; + strcpy(java_xms, "-Xms32m"); + strcpy(java_xmx, "-Xmx256m"); + sprintf(buf, "%s/etc/judge.conf", oj_home); + fp = fopen("./etc/judge.conf", "r"); + if (fp != NULL) { + while (fgets(buf, BUFFER_SIZE - 1, fp)) { + read_buf(buf, "OJ_HOST_NAME", host_name); + read_buf(buf, "OJ_USER_NAME", user_name); + read_buf(buf, "OJ_PASSWORD", password); + read_buf(buf, "OJ_DB_NAME", db_name); + read_int(buf, "OJ_PORT_NUMBER", &port_number); + read_int(buf, "OJ_JAVA_TIME_BONUS", &java_time_bonus); + read_int(buf, "OJ_JAVA_MEMORY_BONUS", &java_memory_bonus); + read_int(buf, "OJ_SIM_ENABLE", &sim_enable); + read_buf(buf, "OJ_JAVA_XMS", java_xms); + read_buf(buf, "OJ_JAVA_XMX", java_xmx); + read_int(buf, "OJ_HTTP_JUDGE", &http_judge); + read_buf(buf, "OJ_HTTP_BASEURL", http_baseurl); + read_buf(buf, "OJ_HTTP_USERNAME", http_username); + read_buf(buf, "OJ_HTTP_PASSWORD", http_password); + read_int(buf, "OJ_OI_MODE", &oi_mode); + read_int(buf, "OJ_SHM_RUN", &shm_run); + read_int(buf, "OJ_USE_MAX_TIME", &use_max_time); + + } + //fclose(fp); + } +} + +//功能:判断是否是输入文件 +//输入:/home/judge/data/1000下的文件全名 sample.in sample.out test.in test.out +//输出:如果文件长度<=3或者不是以.in结尾,则返回0; +// 否则返回文件主名长度sample 6 test 4 +int isInFile(const char fname[]) { + int l = strlen(fname); + if (l <= 3 || strcmp(fname + l - 3, ".in") != 0) + return 0; + else + return l - 3;//返回文件名长度 sample 6 test 4 +} + +void find_next_nonspace(int & c1, int & c2, FILE *& f1, FILE *& f2, int & ret) { + // Find the next non-space character or \n. + while ((isspace(c1)) || (isspace(c2))) { + if (c1 != c2) { + if (c2 == EOF) { + do { + c1 = fgetc(f1); + } while (isspace(c1)); + continue; + } else if (c1 == EOF) { + do { + c2 = fgetc(f2); + } while (isspace(c2)); + continue; + } else if ((c1 == '\r' && c2 == '\n')) { + c1 = fgetc(f1); + } else if ((c2 == '\r' && c1 == '\n')) { + c2 = fgetc(f2); + } else { + if (DEBUG) + printf("%d=%c\t%d=%c", c1, c1, c2, c2); + ; + ret = OJ_PE; + } + } + if (isspace(c1)) { + c1 = fgetc(f1); + } + if (isspace(c2)) { + c2 = fgetc(f2); + } + } +} + +/*** + int compare_diff(const char *file1,const char *file2){ + char diff[1024]; + sprintf(diff,"diff -q -B -b -w --strip-trailing-cr %s %s",file1,file2); + int d=system(diff); + if (d) return OJ_WA; + sprintf(diff,"diff -q -B --strip-trailing-cr %s %s",file1,file2); + int p=system(diff); + if (p) return OJ_PE; + else return OJ_AC; + } + */ +const char * getFileNameFromPath(const char * path) { + for (int i = strlen(path); i >= 0; i--) { + if (path[i] == '/') + return &path[i]; + } + return path; +} +void make_diff_out(FILE *f1, FILE *f2, int c1, int c2, const char * path) { + FILE *out; + char buf[45]; + out = fopen("diff.out", "a+"); + fprintf(out, "=================%s\n", getFileNameFromPath(path)); + fprintf(out, "Right:\n%c", c1); + if (fgets(buf, 44, f1)) { + fprintf(out, "%s", buf); + } + fprintf(out, "\n-----------------\n"); + fprintf(out, "Your:\n%c", c2); + if (fgets(buf, 44, f2)) { + fprintf(out, "%s", buf); + } + fprintf(out, "\n=================\n"); + fclose(out); +} + +/* + * translated from ZOJ judger r367 + * http://code.google.com/p/zoj/source/browse/trunk/judge_client/client/text_checker.cc#25 + * + */ +int compare_zoj(const char *file1, const char *file2) { + int ret = OJ_AC; + int c1, c2; + FILE * f1, *f2; + f1 = fopen(file1, "r"); + f2 = fopen(file2, "r"); + if (!f1 || !f2) { + ret = OJ_RE; + } else + for (;;) { + // Find the first non-space character at the beginning of line. + // Blank lines are skipped. + c1 = fgetc(f1); + c2 = fgetc(f2); + find_next_nonspace(c1, c2, f1, f2, ret); + // Compare the current line. + for (;;) { + // Read until 2 files return a space or 0 together. + while ((!isspace(c1) && c1) || (!isspace(c2) && c2)) { + if (c1 == EOF && c2 == EOF) { + goto end; + } + if (c1 == EOF || c2 == EOF) { + break; + } + if (c1 != c2) { + // Consecutive non-space characters should be all exactly the same + ret = OJ_WA; + goto end; + } + c1 = fgetc(f1); + c2 = fgetc(f2); + } + find_next_nonspace(c1, c2, f1, f2, ret); + if (c1 == EOF && c2 == EOF) { + goto end; + } + if (c1 == EOF || c2 == EOF) { + ret = OJ_WA; + goto end; + } + + if ((c1 == '\n' || !c1) && (c2 == '\n' || !c2)) { + break; + } + } + } + end: if (ret == OJ_WA) + make_diff_out(f1, f2, c1, c2, file1); + if (f1) + fclose(f1); + if (f2) + fclose(f2); + return ret; +} + +void delnextline(char s[]) { + int L; + L = strlen(s); + while (L > 0 && (s[L - 1] == '\n' || s[L - 1] == '\r')) + s[--L] = 0; +} + +int compare(const char *file1, const char *file2) { +#ifdef ZOJ_COM + //compare ported and improved from zoj don't limit file size + return compare_zoj(file1, file2); +#endif +#ifndef ZOJ_COM + //the original compare from the first version of hustoj has file size limit + //and waste memory + FILE *f1,*f2; + char *s1,*s2,*p1,*p2; + int PEflg; + s1=new char[STD_F_LIM+512]; + s2=new char[STD_F_LIM+512]; + if (!(f1=fopen(file1,"r"))) + return OJ_AC; + for (p1=s1;EOF!=fscanf(f1,"%s",p1);) + while (*p1) p1++; + fclose(f1); + if (!(f2=fopen(file2,"r"))) + return OJ_RE; + for (p2=s2;EOF!=fscanf(f2,"%s",p2);) + while (*p2) p2++; + fclose(f2); + if (strcmp(s1,s2)!=0) { + // printf("A:%s\nB:%s\n",s1,s2); + delete[] s1; + delete[] s2; + + return OJ_WA; + } else { + f1=fopen(file1,"r"); + f2=fopen(file2,"r"); + PEflg=0; + while (PEflg==0 && fgets(s1,STD_F_LIM,f1) && fgets(s2,STD_F_LIM,f2)) { + delnextline(s1); + delnextline(s2); + if (strcmp(s1,s2)==0) continue; + else PEflg=1; + } + delete [] s1; + delete [] s2; + fclose(f1);fclose(f2); + if (PEflg) return OJ_PE; + else return OJ_AC; + } +#endif +} + +FILE * read_cmd_output(const char * fmt, ...) { + char cmd[BUFFER_SIZE]; + + FILE * ret = NULL; + va_list ap; + + va_start(ap, fmt); + vsprintf(cmd, fmt, ap); + va_end(ap); + if (DEBUG) + printf("%s\n", cmd); + ret = popen(cmd, "r"); + + return ret; +} +bool check_login() { + const char * cmd = + " wget --post-data=\"checklogin=1\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + int ret = 0; + FILE * fjobs = read_cmd_output(cmd, http_baseurl); + fscanf(fjobs, "%d", &ret); + pclose(fjobs); + + return ret; +} +void login() { + if (!check_login()) { + const char * cmd = + "wget --post-data=\"user_id=%s&password=%s\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/login.php\""; + FILE * fjobs = read_cmd_output(cmd, http_username, http_password, + http_baseurl); + //fscanf(fjobs,"%d",&ret); + pclose(fjobs); + } + +} +/* write result back to database */ +void _update_solution_mysql(int solution_id, int result, int time, int memory, + int sim, int sim_s_id, double pass_rate) { + char sql[BUFFER_SIZE]; + if (oi_mode) { + sprintf(sql, + "UPDATE solution SET result=%d,time=%d,memory=%d,judgetime=NOW(),pass_rate=%f WHERE solution_id=%d LIMIT 1%c", + result, time, memory, pass_rate, solution_id, 0); + } else { + sprintf(sql, + "UPDATE solution SET result=%d,time=%d,memory=%d,judgetime=NOW() WHERE solution_id=%d LIMIT 1%c", + result, time, memory, solution_id, 0); + } + // printf("sql= %s\n",sql); + if (mysql_real_query(conn, sql, strlen(sql))) { + // printf("..update failed! %s\n",mysql_error(conn)); + } + if (sim) { + sprintf(sql, + "insert into sim(s_id,sim_s_id,sim) values(%d,%d,%d) on duplicate key update sim_s_id=%d,sim=%d", + solution_id, sim_s_id, sim, sim_s_id, sim); + // printf("sql= %s\n",sql); + if (mysql_real_query(conn, sql, strlen(sql))) { + // printf("..update failed! %s\n",mysql_error(conn)); + } + + } + +} +void _update_solution_http(int solution_id, int result, int time, int memory, + int sim, int sim_s_id, double pass_rate) { + const char * cmd = + " wget --post-data=\"update_solution=1&sid=%d&result=%d&time=%d&memory=%d&sim=%d&simid=%d&pass_rate=%f\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, solution_id, result, time, memory, sim, + sim_s_id, pass_rate, http_baseurl); + //fscanf(fjobs,"%d",&ret); + pclose(fjobs); +} +void update_solution(int solution_id, int result, int time, int memory, int sim, + int sim_s_id, double pass_rate) { + if (result == OJ_TL && memory == 0) + result = OJ_ML; + if (http_judge) { + _update_solution_http(solution_id, result, time, memory, sim, sim_s_id, + pass_rate); + } else { + _update_solution_mysql(solution_id, result, time, memory, sim, sim_s_id, + pass_rate); + } +} +/* write compile error message back to database */ +void _addceinfo_mysql(int solution_id) { + char sql[(1 << 16)], *end; + char ceinfo[(1 << 16)], *cend; + FILE *fp = fopen("ce.txt", "r"); + snprintf(sql, (1 << 16) - 1, "DELETE FROM compileinfo WHERE solution_id=%d", + solution_id); + mysql_real_query(conn, sql, strlen(sql)); + cend = ceinfo; + while (fgets(cend, 1024, fp)) { + cend += strlen(cend); + if (cend - ceinfo > 40000) + break; + } + cend = 0; + end = sql; + strcpy(end, "INSERT INTO compileinfo VALUES("); + end += strlen(sql); + *end++ = '\''; + end += sprintf(end, "%d", solution_id); + *end++ = '\''; + *end++ = ','; + *end++ = '\''; + end += mysql_real_escape_string(conn, end, ceinfo, strlen(ceinfo)); + *end++ = '\''; + *end++ = ')'; + *end = 0; + // printf("%s\n",ceinfo); + if (mysql_real_query(conn, sql, end - sql)) + printf("%s\n", mysql_error(conn)); + fclose(fp); +} +// urlencoded function copied from http://www.geekhideout.com/urlcode.shtml +/* Converts a hex character to its integer value */ +char from_hex(char ch) { + return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; +} + +/* Converts an integer value to its hex character*/ +char to_hex(char code) { + static char hex[] = "0123456789abcdef"; + return hex[code & 15]; +} + +/* Returns a url-encoded version of str */ +/* IMPORTANT: be sure to free() the returned string after use */ +char *url_encode(char *str) { + char *pstr = str, *buf = (char *) malloc(strlen(str) * 3 + 1), *pbuf = buf; + while (*pstr) { + if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' + || *pstr == '~') + *pbuf++ = *pstr; + else if (*pstr == ' ') + *pbuf++ = '+'; + else + *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex( + *pstr & 15); + pstr++; + } + *pbuf = '\0'; + return buf; +} + +void _addceinfo_http(int solution_id) { + + char ceinfo[(1 << 16)], *cend; + char * ceinfo_encode; + FILE *fp = fopen("ce.txt", "r"); + + cend = ceinfo; + while (fgets(cend, 1024, fp)) { + cend += strlen(cend); + if (cend - ceinfo > 40000) + break; + } + fclose(fp); + ceinfo_encode = url_encode(ceinfo); + FILE * ce = fopen("ce.post", "w"); + fprintf(ce, "addceinfo=1&sid=%d&ceinfo=%s", solution_id, ceinfo_encode); + fclose(ce); + free(ceinfo_encode); + + const char * cmd = + " wget --post-file=\"ce.post\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, http_baseurl); + //fscanf(fjobs,"%d",&ret); + pclose(fjobs); + +} +void addceinfo(int solution_id) { + if (http_judge) { + _addceinfo_http(solution_id); + } else { + _addceinfo_mysql(solution_id); + } +} +/* write runtime error message back to database */ +void _addreinfo_mysql(int solution_id, const char * filename) { + char sql[(1 << 16)], *end; + char reinfo[(1 << 16)], *rend; + FILE *fp = fopen(filename, "r"); + snprintf(sql, (1 << 16) - 1, "DELETE FROM runtimeinfo WHERE solution_id=%d", + solution_id); + mysql_real_query(conn, sql, strlen(sql)); + rend = reinfo; + while (fgets(rend, 1024, fp)) { + rend += strlen(rend); + if (rend - reinfo > 40000) + break; + } + rend = 0; + end = sql; + strcpy(end, "INSERT INTO runtimeinfo VALUES("); + end += strlen(sql); + *end++ = '\''; + end += sprintf(end, "%d", solution_id); + *end++ = '\''; + *end++ = ','; + *end++ = '\''; + end += mysql_real_escape_string(conn, end, reinfo, strlen(reinfo)); + *end++ = '\''; + *end++ = ')'; + *end = 0; + // printf("%s\n",ceinfo); + if (mysql_real_query(conn, sql, end - sql)) + printf("%s\n", mysql_error(conn)); + fclose(fp); +} + +void _addreinfo_http(int solution_id, const char * filename) { + + char reinfo[(1 << 16)], *rend; + char * reinfo_encode; + FILE *fp = fopen(filename, "r"); + + rend = reinfo; + while (fgets(rend, 1024, fp)) { + rend += strlen(rend); + if (rend - reinfo > 40000) + break; + } + fclose(fp); + reinfo_encode = url_encode(reinfo); + FILE * re = fopen("re.post", "w"); + fprintf(re, "addreinfo=1&sid=%d&reinfo=%s", solution_id, reinfo_encode); + fclose(re); + free(reinfo_encode); + + const char * cmd = + " wget --post-file=\"re.post\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, http_baseurl); + //fscanf(fjobs,"%d",&ret); + pclose(fjobs); + +} +void addreinfo(int solution_id) { + if (http_judge) { + _addreinfo_http(solution_id, "error.out"); + } else { + _addreinfo_mysql(solution_id, "error.out"); + } +} + +void adddiffinfo(int solution_id) { + + if (http_judge) { + _addreinfo_http(solution_id, "diff.out"); + } else { + _addreinfo_mysql(solution_id, "diff.out"); + } +} +void addcustomout(int solution_id) { + + if (http_judge) { + _addreinfo_http(solution_id, "user.out"); + } else { + _addreinfo_mysql(solution_id, "user.out"); + } +} + +void _update_user_mysql(char * user_id) { + char sql[BUFFER_SIZE]; + sprintf(sql, + "UPDATE `users` SET `solved`=(SELECT count(DISTINCT `problem_id`) FROM `solution` WHERE `user_id`=\'%s\' AND `result`=\'4\') WHERE `user_id`=\'%s\'", + user_id, user_id); + if (mysql_real_query(conn, sql, strlen(sql))) + write_log(mysql_error(conn)); + sprintf(sql, + "UPDATE `users` SET `submit`=(SELECT count(*) FROM `solution` WHERE `user_id`=\'%s\') WHERE `user_id`=\'%s\'", + user_id, user_id); + if (mysql_real_query(conn, sql, strlen(sql))) + write_log(mysql_error(conn)); +} +void _update_user_http(char * user_id) { + + const char * cmd = + " wget --post-data=\"updateuser=1&user_id=%s\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, user_id, http_baseurl); + //fscanf(fjobs,"%d",&ret); + pclose(fjobs); +} +void update_user(char * user_id) { + if (http_judge) { + _update_user_http(user_id); + } else { + _update_user_mysql(user_id); + } +} + +void _update_problem_http(int pid) { + const char * cmd = + " wget --post-data=\"updateproblem=1&pid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, pid, http_baseurl); + //fscanf(fjobs,"%d",&ret); + pclose(fjobs); +} +void _update_problem_mysql(int p_id) { + char sql[BUFFER_SIZE]; + sprintf(sql, + "UPDATE `problem` SET `accepted`=(SELECT count(*) FROM `solution` WHERE `problem_id`=\'%d\' AND `result`=\'4\') WHERE `problem_id`=\'%d\'", + p_id, p_id); + if (mysql_real_query(conn, sql, strlen(sql))) + write_log(mysql_error(conn)); + sprintf(sql, + "UPDATE `problem` SET `submit`=(SELECT count(*) FROM `solution` WHERE `problem_id`=\'%d\') WHERE `problem_id`=\'%d\'", + p_id, p_id); + if (mysql_real_query(conn, sql, strlen(sql))) + write_log(mysql_error(conn)); +} +void update_problem(int pid) { + if (http_judge) { + _update_problem_http(pid); + } else { + _update_problem_mysql(pid); + } +} + + +//核心源码之一,编译程序 +//输入:程序语言类型编码lang +//返回值:编译成功返回0;失败返回ce.txt文件大小 +int compile(int lang) { + int pid; + //设置编译命令 + const char * CP_C[] = { "gcc", "Main.c", "-o", "Main", "-fno-asm", "-Wall", + "-lm", "--static", "-std=c99", "-DONLINE_JUDGE", NULL }; + const char * CP_X[] = { "g++", "Main.cc", "-o", "Main", "-fno-asm", "-Wall", + "-lm", "--static", "-std=c++0x", "-DONLINE_JUDGE", NULL }; + const char * CP_P[] = + { "fpc", "Main.pas", "-O2", "-Co", "-Ct", "-Ci", NULL }; +// const char * CP_J[] = { "javac", "-J-Xms32m", "-J-Xmx256m","-encoding","UTF-8", "Main.java",NULL }; + + const char * CP_R[] = { "ruby", "-c", "Main.rb", NULL }; + const char * CP_B[] = { "chmod", "+rx", "Main.sh", NULL }; + const char * CP_Y[] = { "python", "-c", + "import py_compile; py_compile.compile(r'Main.py')", NULL }; + const char * CP_PH[] = { "php", "-l", "Main.php", NULL }; + const char * CP_PL[] = { "perl", "-c", "Main.pl", NULL }; + const char * CP_CS[] = { "gmcs", "-warn:0", "Main.cs", NULL }; + const char * CP_OC[] = { "gcc", "-o", "Main", "Main.m", + "-fconstant-string-class=NSConstantString", "-I", + "/usr/include/GNUstep/", "-L", "/usr/lib/GNUstep/Libraries/", + "-lobjc", "-lgnustep-base", NULL }; + const char * CP_BS[] = { "fbc", "Main.bas", NULL }; + char javac_buf[7][16]; + char *CP_J[7]; + + for (int i = 0; i < 7; i++) + CP_J[i] = javac_buf[i]; + + sprintf(CP_J[0], "javac"); + sprintf(CP_J[1], "-J%s", java_xms); + sprintf(CP_J[2], "-J%s", java_xmx); + sprintf(CP_J[3], "-encoding"); + sprintf(CP_J[4], "UTF-8"); + sprintf(CP_J[5], "Main.java"); + CP_J[6] = (char *) NULL; + + //判题进程中又创建子进程 + pid = fork(); + if (pid == 0) {//如果是子进程在运行 + //设置运行资源限制 + struct rlimit LIM; + LIM.rlim_max = 60; + LIM.rlim_cur = 60; + setrlimit(RLIMIT_CPU, &LIM); + alarm(60); + LIM.rlim_max = 100 * STD_MB; + LIM.rlim_cur = 100 * STD_MB; + setrlimit(RLIMIT_FSIZE, &LIM); + + if(lang==3){ + LIM.rlim_max = STD_MB << 11; + LIM.rlim_cur = STD_MB << 11; + }else{ + LIM.rlim_max = STD_MB << 10; + LIM.rlim_cur = STD_MB << 10; + } + setrlimit(RLIMIT_AS, &LIM); + //实现重定向,把预定义的标准流文件定向到由path指定的文件中。 + //标准流文件具体是指stdin、stdout和stderr + if (lang != 2 && lang != 11) {//不是pas也不是bas + freopen("ce.txt", "w", stderr); + //freopen("/dev/null", "w", stdout); + } else {//如果是bas或者pas,那么编译信息输出重定向到ce.txt + freopen("ce.txt", "w", stdout); + } + //将/home/judge/run0下所有文件拥有者改为判题用户judge可能为了安全考虑?????? + /* + 可以这样使用 stuid() 函数: +开始时,某个程序需要 root 权限玩成一些工作,但后续的工作不需要 root 权限。 +可以将该可执行程序文件设置 set_uid 位,并使得该文件的属主为 root。 +这样,普通用户执行这个程序时,进程就具有了 root 权限,当不再需要 root 权限时, +调用 setuid( getuid() ) 恢复进程的实际用户 ID 和有效用户 ID 为执行该程序的普通用户的 ID 。 +对于一些提供网络服务的程序,这样做是非常有必要的,否则就可能被攻击者利用,使攻击者控制整个系统。 +对于设置了 set_uid 位的可执行程序也要注意,尤其是对那些属主是 root 的更要注意。因为 Linux 系统中 +root 用户拥有最高权力。黑客们往往喜欢寻找设置了 set_uid 位的可执行程序的漏洞。这样的程序如果存在缓冲区溢出 +漏洞,并且该程序是一个网络程序,那么黑客就可以从远程的地方轻松地利用该漏洞获得运行该漏洞程序的主机的 root + 权限。即使这样的成不是网络程序,那么也可以使本机上的恶意普通用户提升为 root 权限。 + */ + execute_cmd("chown judge *"); + while(setgid(1536)!=0) sleep(1); + while(setuid(1536)!=0) sleep(1); + while(setresuid(1536, 1536, 1536)!=0) sleep(1); + + //执行编译命令,并且输出到ce.txt中 + //默认c:0-c++:1 + /* + execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件, + 然后将第二个参数argv传给该欲执行的文件。 + 返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。 + */ + switch (lang) { + case 0: + execvp(CP_C[0], (char * const *) CP_C); + break; + case 1: + execvp(CP_X[0], (char * const *) CP_X); + break; + case 2: + execvp(CP_P[0], (char * const *) CP_P); + break; + case 3: + execvp(CP_J[0], (char * const *) CP_J); + break; + case 4: + execvp(CP_R[0], (char * const *) CP_R); + break; + case 5: + execvp(CP_B[0], (char * const *) CP_B); + break; + case 6: + execvp(CP_Y[0], (char * const *) CP_Y); + break; + case 7: + execvp(CP_PH[0], (char * const *) CP_PH); + break; + case 8: + execvp(CP_PL[0], (char * const *) CP_PL); + break; + case 9: + execvp(CP_CS[0], (char * const *) CP_CS); + break; + + case 10: + execvp(CP_OC[0], (char * const *) CP_OC); + break; + case 11: + execvp(CP_BS[0], (char * const *) CP_BS); + break; + default: + printf("nothing to do!\n"); + } + //如果调试,在这里输出编译信息, + if (DEBUG) + printf("compile end!\n"); + + //并终止编译子进程的执行 ,这里提供了两种返回父进程的方式 + //一种是查看ce文件,是否进行分析???? + //另一种是直接exit(0);这里可能默认编译子进程一定成功????? + //exit(!system("cat ce.txt")); + exit(0); + } else {//父进程,等待编译子进程的执行 结束,正常结束带回0,为什么采取这种形式???难道是不知道什么时候 + //编译结束????不知道什么时候去读取ce.txt进行解析???所以采用父子进程的方式??? + //如果子进程编译有语法错误,那么会写入ce.txt,如果ce.txt为0,并且是Exit(0),代表子进程正常退出,编译成功 + int status = 0; + //一直等,直到编译子进程结束 + waitpid(pid, &status, 0); + if (lang > 3 && lang < 7)//"rb", "sh", "py"这三种语言状态特殊???? + status = get_file_size("ce.txt"); //那c如果有错误写入ce.txt呢???? + if (DEBUG) + printf("status=%d\n", status); + return status;//默认返回0 + } + +} +/* + int read_proc_statm(int pid){ + FILE * pf; + char fn[4096]; + int ret; + sprintf(fn,"/proc/%d/statm",pid); + pf=fopen(fn,"r"); + fscanf(pf,"%d",&ret); + fclose(pf); + return ret; + } + */ +int get_proc_status(int pid, const char * mark) { + FILE * pf; + char fn[BUFFER_SIZE], buf[BUFFER_SIZE]; + int ret = 0; + sprintf(fn, "/proc/%d/status", pid); + pf = fopen(fn, "r"); + int m = strlen(mark); + while (pf && fgets(buf, BUFFER_SIZE - 1, pf)) { + + buf[strlen(buf) - 1] = 0; + if (strncmp(buf, mark, m) == 0) { + sscanf(buf + m + 1, "%d", &ret); + } + } + if (pf) + fclose(pf); + return ret; +} +int init_mysql_conn() { + + conn = mysql_init(NULL); + //mysql_real_connect(conn,host_name,user_name,password,db_name,port_number,0,0); + const char timeout = 30; + mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); + + if (!mysql_real_connect(conn, host_name, user_name, password, db_name, + port_number, 0, 0)) { + write_log("%s", mysql_error(conn)); + return 0; + } + const char * utf8sql = "set names utf8"; + if (mysql_real_query(conn, utf8sql, strlen(utf8sql))) { + write_log("%s", mysql_error(conn)); + return 0; + } + return 1; +} + +//根据情况读取待评测程序源码 +//并在默认目录work_dir : /home/judge/run0下建立 Main.c +void _get_solution_mysql(int solution_id, char * work_dir, int lang) { + char sql[BUFFER_SIZE], src_pth[BUFFER_SIZE]; + // get the source code + MYSQL_RES *res; + MYSQL_ROW row; + sprintf(sql, "SELECT source FROM source_code WHERE solution_id=%d", + solution_id); + mysql_real_query(conn, sql, strlen(sql)); + res = mysql_store_result(conn); + row = mysql_fetch_row(res); + + // create the src file + sprintf(src_pth, "Main.%s", lang_ext[lang]); + if (DEBUG) + printf("Main=%s", src_pth); + FILE *fp_src = fopen(src_pth, "w"); + fprintf(fp_src, "%s", row[0]); + mysql_free_result(res); + fclose(fp_src); +} +void _get_solution_http(int solution_id, char * work_dir, int lang) { + char src_pth[BUFFER_SIZE]; + + // create the src file + sprintf(src_pth, "Main.%s", lang_ext[lang]); + if (DEBUG) + printf("Main=%s", src_pth); + + //login(); + + const char * cmd2 = + "wget --post-data=\"getsolution=1&sid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O %s \"%s/admin/problem_judge.php\""; + FILE * pout = read_cmd_output(cmd2, solution_id, src_pth, http_baseurl); + + pclose(pout); + +} +void get_solution(int solution_id, char * work_dir, int lang) { + if (http_judge) { + _get_solution_http(solution_id, work_dir, lang); + } else { + _get_solution_mysql(solution_id, work_dir, lang); + } + +} + +void _get_custominput_mysql(int solution_id, char * work_dir) { + char sql[BUFFER_SIZE], src_pth[BUFFER_SIZE]; + // get the source code + MYSQL_RES *res; + MYSQL_ROW row; + sprintf(sql, "SELECT input_text FROM custominput WHERE solution_id=%d", + solution_id); + mysql_real_query(conn, sql, strlen(sql)); + res = mysql_store_result(conn); + row = mysql_fetch_row(res); + if (row != NULL) { + + // create the src file + sprintf(src_pth, "data.in"); + FILE *fp_src = fopen(src_pth, "w"); + fprintf(fp_src, "%s", row[0]); + fclose(fp_src); + + } + mysql_free_result(res); +} +void _get_custominput_http(int solution_id, char * work_dir) { + char src_pth[BUFFER_SIZE]; + + // create the src file + sprintf(src_pth, "data.in"); + + //login(); + + const char * cmd2 = + "wget --post-data=\"getcustominput=1&sid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O %s \"%s/admin/problem_judge.php\""; + FILE * pout = read_cmd_output(cmd2, solution_id, src_pth, http_baseurl); + + pclose(pout); + +} +void get_custominput(int solution_id, char * work_dir) { + if (http_judge) { + _get_custominput_http(solution_id, work_dir); + } else { + _get_custominput_mysql(solution_id, work_dir); + } +} + +void _get_solution_info_mysql(int solution_id, int & p_id, char * user_id, + int & lang) { + + MYSQL_RES *res; + MYSQL_ROW row; + + char sql[BUFFER_SIZE]; + // get the problem id and user id from Table:solution + sprintf(sql, + "SELECT problem_id, user_id, language FROM solution where solution_id=%d", + solution_id); + //printf("%s\n",sql); + mysql_real_query(conn, sql, strlen(sql)); + res = mysql_store_result(conn); + row = mysql_fetch_row(res); + p_id = atoi(row[0]); + strcpy(user_id, row[1]); + lang = atoi(row[2]); + mysql_free_result(res); +} + +void _get_solution_info_http(int solution_id, int & p_id, char * user_id, + int & lang) { + + login(); + + const char * cmd = + "wget --post-data=\"getsolutioninfo=1&sid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * pout = read_cmd_output(cmd, solution_id, http_baseurl); + fscanf(pout, "%d", &p_id); + fscanf(pout, "%s", user_id); + fscanf(pout, "%d", &lang); + pclose(pout); + +} +void get_solution_info(int solution_id, int & p_id, char * user_id, + int & lang) { + + if (http_judge) { + _get_solution_info_http(solution_id, p_id, user_id, lang); + } else { + _get_solution_info_mysql(solution_id, p_id, user_id, lang); + } +} + +void _get_problem_info_mysql(int p_id, int & time_lmt, int & mem_lmt, + int & isspj) { + // get the problem info from Table:problem + char sql[BUFFER_SIZE]; + MYSQL_RES *res; + MYSQL_ROW row; + sprintf(sql, + "SELECT time_limit,memory_limit,spj FROM problem where problem_id=%d", + p_id); + mysql_real_query(conn, sql, strlen(sql)); + res = mysql_store_result(conn); + row = mysql_fetch_row(res); + time_lmt = atoi(row[0]); + mem_lmt = atoi(row[1]); + isspj = (row[2][0] == '1'); + mysql_free_result(res); +} + +void _get_problem_info_http(int p_id, int & time_lmt, int & mem_lmt, + int & isspj) { + //login(); + + const char * cmd = + "wget --post-data=\"getprobleminfo=1&pid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * pout = read_cmd_output(cmd, p_id, http_baseurl); + fscanf(pout, "%d", &time_lmt); + fscanf(pout, "%d", &mem_lmt); + fscanf(pout, "%d", &isspj); + pclose(pout); +} + +void get_problem_info(int p_id, int & time_lmt, int & mem_lmt, int & isspj) { + if (http_judge) { + _get_problem_info_http(p_id, time_lmt, mem_lmt, isspj); + } else { + _get_problem_info_mysql(p_id, time_lmt, mem_lmt, isspj); + } +} + +/* +功能: + 1、把评测的输入数据复制到/home/judge/run0/data.in + 2、给outfile 赋值; /home/judge/data/1000/fname.out + 3、给userfile 赋值: /home/judge/ruun0/user.out +输入: + filename: 输入数据文件名称 不包括扩展名.in 如sample / test + namelen: 输入数据文件主名长度 sample:6 test:4 + infile : char * infile[] 在main()中定义,保存完整测试数据路径:/home/judge/data/1000/sample.in 或 /home/judge/data/1000/test.in + p_id : 问题编号 + work_dir: 默认的评测进程路径 /home/judge/run0 + outfile: char * outfile[] 在main()中定义,保存完整正确结果数据路径:/home/judge/data/1000/ sample.out或/home/judge/data/1000/test.out + userfile:char* userfile[] 在main()中定义,保存完整 用户程序结果数据路径:/home/judge/run0/user.out + runner_id: 判题进程编号run0 run1 run2 run3 取值0-max_running-1 + +输出: + 无 +注释: + 2014-11-4 17:00 by ghf in HNSSYZX ZhengZhou +*/ +/* +在 int main()中调用: +prepare_files(dirp->d_name, namelen, infile, p_id, work_dir, outfile, + userfile, runner_id); +*/ +void prepare_files(char * filename, int namelen, char * infile, int & p_id, + char * work_dir, char * outfile, char * userfile, int runner_id) { + // printf("ACflg=%d %d check a file!\n",ACflg,solution_id); + + char fname[BUFFER_SIZE]; + strncpy(fname, filename, namelen);//保存测试数据主名 + fname[namelen] = 0;//c字符串末尾\0结尾 + sprintf(infile, "%s/data/%d/%s.in", oj_home, p_id, fname);//测试数据完整路径 + execute_cmd("/bin/cp %s %s/data.in", infile, work_dir);//复制测试数据到run0目录下 + execute_cmd("/bin/cp %s/data/%d/*.dic %s/", oj_home, p_id, work_dir);//dic数据复制???? + + sprintf(outfile, "%s/data/%d/%s.out", oj_home, p_id, fname); + sprintf(userfile, "%s/run%d/user.out", oj_home, runner_id); +} + +void copy_shell_runtime(char * work_dir) { + + execute_cmd("/bin/mkdir %s/lib", work_dir); + execute_cmd("/bin/mkdir %s/lib64", work_dir); + execute_cmd("/bin/mkdir %s/bin", work_dir); + execute_cmd("/bin/cp /lib/* %s/lib/", work_dir); + execute_cmd("/bin/cp -a /lib/i386-linux-gnu %s/lib/", work_dir); + execute_cmd("/bin/cp -a /lib/x86_64-linux-gnu %s/lib/", work_dir); + execute_cmd("/bin/cp /lib64/* %s/lib64/", work_dir); + execute_cmd("/bin/cp -a /lib32 %s/", work_dir); + execute_cmd("/bin/cp /bin/busybox %s/bin/", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/sh", work_dir); + execute_cmd("/bin/cp /bin/bash %s/bin/bash", work_dir); + +} +void copy_objc_runtime(char * work_dir) { + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir -p %s/proc", work_dir); + execute_cmd("/bin/mount -o bind /proc %s/proc", work_dir); + execute_cmd("/bin/mkdir -p %s/lib/", work_dir); + execute_cmd( + "/bin/cp -aL /lib/libdbus-1.so.3 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/libgcc_s.so.1 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/libgcrypt.so.11 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/libgpg-error.so.0 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/libz.so.1 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/tls/i686/cmov/libc.so.6 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/tls/i686/cmov/libdl.so.2 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/tls/i686/cmov/libm.so.6 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/tls/i686/cmov/libnsl.so.1 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/tls/i686/cmov/libpthread.so.0 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /lib/tls/i686/cmov/librt.so.1 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libavahi-client.so.3 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libavahi-common.so.3 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libdns_sd.so.1 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libffi.so.5 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libgnustep-base.so.1.19 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libgnutls.so.26 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libobjc.so.2 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libtasn1.so.3 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libxml2.so.2 %s/lib/ ", + work_dir); + execute_cmd( + "/bin/cp -aL /usr/lib/libxslt.so.1 %s/lib/ ", + work_dir); + +} +void copy_bash_runtime(char * work_dir) { + //char cmd[BUFFER_SIZE]; + //const char * ruby_run="/usr/bin/ruby"; + copy_shell_runtime(work_dir); + execute_cmd("/bin/cp `which bc` %s/bin/", work_dir); + execute_cmd("busybox dos2unix Main.sh", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/grep", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/awk", work_dir); + execute_cmd("/bin/cp /bin/sed %s/bin/sed", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/cut", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/sort", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/join", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/wc", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/tr", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/dc", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/dd", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/cat", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/tail", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/head", work_dir); + execute_cmd("/bin/ln -s /bin/busybox %s/bin/xargs", work_dir); + execute_cmd("chmod +rx %s/Main.sh", work_dir); + +} +void copy_ruby_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir %s/usr", work_dir); + execute_cmd("/bin/mkdir %s/usr/lib", work_dir); + execute_cmd("/bin/cp /usr/lib/libruby* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp -a /usr/lib/ruby %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/bin/ruby* %s/", work_dir); + +} +void copy_guile_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir -p %s/proc", work_dir); + execute_cmd("/bin/mount -o bind /proc %s/proc", work_dir); + execute_cmd("/bin/mkdir -p %s/usr/lib", work_dir); + execute_cmd("/bin/mkdir -p %s/usr/share", work_dir); + execute_cmd("/bin/cp -a /usr/share/guile %s/usr/share/", work_dir); + execute_cmd("/bin/cp /usr/lib/libguile* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libgc* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/i386-linux-gnu/libffi* %s/usr/lib/", + work_dir); + execute_cmd("/bin/cp /usr/lib/i386-linux-gnu/libunistring* %s/usr/lib/", + work_dir); + execute_cmd("/bin/cp /usr/lib/*/libgmp* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libgmp* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/*/libltdl* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libltdl* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/bin/guile* %s/", work_dir); + +} + +void copy_python_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir -p %s/usr/include", work_dir); + execute_cmd("/bin/mkdir -p %s/usr/lib", work_dir); + execute_cmd("/bin/cp /usr/bin/python* %s/", work_dir); + execute_cmd("/bin/cp -a /usr/lib/python* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp -a /usr/include/python* %s/usr/include/", work_dir); + execute_cmd("/bin/cp -a /usr/lib/libpython* %s/usr/lib/", work_dir); + +} +void copy_php_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir %s/usr", work_dir); + execute_cmd("/bin/mkdir %s/usr/lib", work_dir); + execute_cmd("/bin/cp /usr/lib/libedit* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libdb* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libgssapi_krb5* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libkrb5* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libk5crypto* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/*/libedit* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/*/libdb* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/*/libgssapi_krb5* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/*/libkrb5* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/*/libk5crypto* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/libxml2* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/lib/x86_64-linux-gnu/libxml2.so* %s/usr/lib/", + work_dir); + execute_cmd("/bin/cp /usr/bin/php* %s/", work_dir); + execute_cmd("chmod +rx %s/Main.php", work_dir); + +} +void copy_perl_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir %s/usr", work_dir); + execute_cmd("/bin/mkdir %s/usr/lib", work_dir); + execute_cmd("/bin/cp /usr/lib/libperl* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /usr/bin/perl* %s/", work_dir); + +} +void copy_freebasic_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir -p %s/usr/local/lib", work_dir); + execute_cmd("/bin/mkdir -p %s/usr/local/bin", work_dir); + execute_cmd("/bin/cp /usr/local/lib/freebasic %s/usr/local/lib/", work_dir); + execute_cmd("/bin/cp /usr/local/bin/fbc %s/", work_dir); + execute_cmd("/bin/cp -a /lib32/* %s/lib/", work_dir); + +} +void copy_mono_runtime(char * work_dir) { + + copy_shell_runtime(work_dir); + execute_cmd("/bin/mkdir %s/usr", work_dir); + execute_cmd("/bin/mkdir %s/proc", work_dir); + execute_cmd("/bin/mkdir -p %s/usr/lib/mono/2.0", work_dir); + execute_cmd("/bin/cp -a /usr/lib/mono %s/usr/lib/", work_dir); + execute_cmd("/bin/mkdir -p %s/usr/lib64/mono/2.0", work_dir); + execute_cmd("/bin/cp -a /usr/lib64/mono %s/usr/lib64/", work_dir); + + execute_cmd("/bin/cp /usr/lib/libgthread* %s/usr/lib/", work_dir); + + execute_cmd("/bin/mount -o bind /proc %s/proc", work_dir); + execute_cmd("/bin/cp /usr/bin/mono* %s/", work_dir); + + execute_cmd("/bin/cp /usr/lib/libgthread* %s/usr/lib/", work_dir); + execute_cmd("/bin/cp /lib/libglib* %s/lib/", work_dir); + execute_cmd("/bin/cp /lib/tls/i686/cmov/lib* %s/lib/tls/i686/cmov/", + work_dir); + execute_cmd("/bin/cp /lib/libpcre* %s/lib/", work_dir); + execute_cmd("/bin/cp /lib/ld-linux* %s/lib/", work_dir); + execute_cmd("/bin/cp /lib64/ld-linux* %s/lib64/", work_dir); + execute_cmd("/bin/mkdir -p %s/home/judge", work_dir); + execute_cmd("/bin/chown judge %s/home/judge", work_dir); + execute_cmd("/bin/mkdir -p %s/etc", work_dir); + execute_cmd("/bin/grep judge /etc/passwd>%s/etc/passwd", work_dir); + +} + + +/* +功能: + 1、设置资源限制,运行时间、内存 + 2、设定子进程运行用户 + 3、修改子进程运行根目录 / 为 run0/ + 4、重定向标准输入输出为文档 + 5、执行程序,生成结果 +输入: + lang: 程序语言类型编码 c:0 c++:1 + work_dir: 判题进程工作目录:/home/judge/runn0,为当前默认目录 + time_lmt: 时间限制 + used_time: 运行时间 + mem_lmt: 运行内存限制 +输出: + 无 +调用: + 在main()中创建新的执行子进程中调用 + run_solution(lang, work_dir, time_lmt, usedtime, mem_lmt); +注释: 2014-11-4 17:30 by ghf in HNSSYZX ZhengZhou +*/ +void run_solution(int & lang, char * work_dir, int & time_lmt, int & usedtime, + int & mem_lmt) { + /* + 在当前程序运行优先级基础之上调整指定值得到新的程序运行优先级, + 用新的程序运行优先级运行命令行"command [arguments...]"。优先级的范围为-20 ~ 19 等40个等级, + 其中数值越小优先级越高,数值越大优先级越低,既-20的优先级最高, 19的优先级最低。 + 若调整后的程序运行优先级高于-20,则就以优先级-20来运行命令行;若调整后的程序运行优先级低于19, + 则就以优先级19来运行命令行。若 nice命令未指定优先级的调整值,则以缺省值10来调整程序运行优先级, + 既在当前程序运行优先级基础之上增加10。 + + 调低运行级,基本用户都能执行成功,将这个执行子进程的优先级降到最低,为了安全????????? + */ + nice(19); + // now the user is "judger" + //这时的工作目录分布: + ///home/judge/run0/data.in + ///home/judge/run0/Main.exe + ///home/judge/run0/ce.txt + ///home/judge/run0/main.c + ///home/judge/run0/user.out + ///home/judge/run0/error.out + chdir(work_dir); + // open the files + //使输入输出重定向到文件里,方便最后的评判 + freopen("data.in", "r", stdin); + freopen("user.out", "w", stdout); + freopen("error.out", "a+", stderr); + // trace me + /* + ptrace系统函数。 ptrace提供了一种使父进程得以监视和控制其它进程的方式, + 它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。 + 使用ptrace,你可以在用户层拦截和修改系统调用(sys call). + 形式:ptrace(PTRACE_TRACEME,0 ,0 ,0) + 描述:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。 + */ + ptrace(PTRACE_TRACEME, 0, NULL, NULL); + // run me + /* + chroot是内核中的一个系统调用,软件可以通过调用库函数chroot,来更改某个进程所能见到的根目录 + 1.限制CHROOT环境下的使用者所能行的程式,如SetUid的程式,或是造成 Load 的 Compiler等等 +2.防止使用者存取某些特定档案及配置文件,如/etc/passwd +3.防止入侵者/bin/rm -rf / +4.提供Guest服务及限制不守规矩的使用者。 +5.增强系系统的安全。 +安全机制????????????????? +可是chroot 需要root权限运行啊,不是已经将该进程权限降低的最低的19了啊 ???????????????? + */ + if (lang != 3) //如果不是java + chroot(work_dir); +/* +???????????????????????????????????????? +这附近的代码涉及到的用户角色和权限的转换不是很理解,判题进程创建的执行子进程,难道有root权限??? +这里又把权限设置到了judge用户1536 + ??????????????????????????????????????????? +*/ + while (setgid(1536) != 0) + sleep(1); + while (setuid(1536) != 0) + sleep(1); + while (setresuid(1536, 1536, 1536) != 0) + sleep(1); + +// char java_p1[BUFFER_SIZE], java_p2[BUFFER_SIZE]; + // child + // set the limit + struct rlimit LIM; // time limit, file limit& memory limit + // time limit + //难道oi_mode是秒和毫秒的区别???????????????? + //看不懂什么意思 + + //设置运行时间、内存限制 + if (oi_mode) + LIM.rlim_cur = time_lmt + 1; + else + LIM.rlim_cur = (time_lmt - usedtime / 1000) + 1; + LIM.rlim_max = LIM.rlim_cur; + //if(DEBUG) printf("LIM_CPU=%d",(int)(LIM.rlim_cur)); + setrlimit(RLIMIT_CPU, &LIM); + alarm(0); + alarm(time_lmt * 10); + + // file limit + LIM.rlim_max = STD_F_LIM + STD_MB; + LIM.rlim_cur = STD_F_LIM; + setrlimit(RLIMIT_FSIZE, &LIM); + // proc limit + switch (lang) { + case 3: //java + case 12: + LIM.rlim_cur = LIM.rlim_max = 50; + break; + case 5: //bash + LIM.rlim_cur = LIM.rlim_max = 3; + break; + case 9: //C# + LIM.rlim_cur = LIM.rlim_max = 50; + break; + default: + LIM.rlim_cur = LIM.rlim_max = 1; + } + + setrlimit(RLIMIT_NPROC, &LIM); + + // set the stack + LIM.rlim_cur = STD_MB << 6; + LIM.rlim_max = STD_MB << 6; + setrlimit(RLIMIT_STACK, &LIM); + // set the memory + LIM.rlim_cur = STD_MB * mem_lmt / 2 * 3; + LIM.rlim_max = STD_MB * mem_lmt * 2; + if (lang < 3) + setrlimit(RLIMIT_AS, &LIM); + + //开始真正的执行 + + switch (lang) { + case 0: + case 1: + case 2: + case 10: + case 11: + execl("./Main", "./Main", (char *) NULL);//因为已经chroot(/home/judge/run0所以可以./Main) + break; + case 3: +// sprintf(java_p1, "-Xms%dM", mem_lmt / 2); +// sprintf(java_p2, "-Xmx%dM", mem_lmt); + + execl("/usr/bin/java", "/usr/bin/java", java_xms, java_xmx, + "-Djava.security.manager", + "-Djava.security.policy=./java.policy", "Main", (char *) NULL); + break; + case 4: + //system("/ruby Main.rb time_lmt * 1000 * (use_max_time ? 1 : num_of_test)) + ACflg = OJ_TL; + if (topmemory > mem_lmt * STD_MB) + ACflg = OJ_ML; //issues79 + // compare + if (ACflg == OJ_AC) { + if (isspj) { + comp_res = special_judge(oj_home, p_id, infile, outfile, userfile); + + if (comp_res == 0) + comp_res = OJ_AC; + else { + if (DEBUG) + printf("fail test %s\n", infile); + comp_res = OJ_WA; + } + } else { + comp_res = compare(outfile, userfile); + } + if (comp_res == OJ_WA) { + ACflg = OJ_WA; + if (DEBUG) + printf("fail test %s\n", infile); + } else if (comp_res == OJ_PE) + PEflg = OJ_PE; + ACflg = comp_res; + } + //jvm popup messages, if don't consider them will get miss-WrongAnswer + if (lang == 3) { + comp_res = fix_java_mis_judge(work_dir, ACflg, topmemory, mem_lmt); + } +} + +int get_page_fault_mem(struct rusage & ruse, pid_t & pidApp) { + //java use pagefault + int m_vmpeak, m_vmdata, m_minflt; + m_minflt = ruse.ru_minflt * getpagesize(); + if (0 && DEBUG) { + m_vmpeak = get_proc_status(pidApp, "VmPeak:"); + m_vmdata = get_proc_status(pidApp, "VmData:"); + printf("VmPeak:%d KB VmData:%d KB minflt:%d KB\n", m_vmpeak, m_vmdata, + m_minflt >> 10); + } + return m_minflt; +} +void print_runtimeerror(char * err) { + FILE *ferr = fopen("error.out", "a+"); + fprintf(ferr, "Runtime Error:%s\n", err); + fclose(ferr); +} +void clean_session(pid_t p) { + //char cmd[BUFFER_SIZE]; + const char *pre = "ps awx -o \"\%p \%P\"|grep -w "; + const char *post = " | awk \'{ print $1 }\'|xargs kill -9"; + execute_cmd("%s %d %s", pre, p, post); + execute_cmd("ps aux |grep \\^judge|awk '{print $2}'|xargs kill"); +} + + +/* +功能: + 1、ACflg初始为OJ_AC,在这里各种跟踪调试,时刻判断各种可能的不合理情况 + 修改标志位,结束执行子进程 ---这段好底层。。。。调试器原理???高端 +输入: + pidApp: 执行子进程的pid + infile: char * infile[] 在main()中定义,保存完整测试数据路径:/home/judge/data/1000/sample.in 或 /home/judge/data/1000/test.in + ACflg : 程序执行结果标志,初始OJ_AC + isspj : 不知什么意思??????????? + solution_id: 代评测题目id + lang : 程序语言类型 c 0 c++ 1 + topmemory: 在main()中定义 + mem_lmt : 内存限制 + usedtime : 执行所耗时间 + time_lmt : 时间限制 + p_id : 问题id + PEFlg : 未知 初始为OJ_AC + work_dir : /home/judge/run0 +输出: + 无 +调用: + 在main()中调用 + watch_solution(pidApp, infile, ACflg, isspj, userfile, outfile, + solution_id, lang, topmemory, mem_lmt, usedtime, time_lmt, + p_id, PEflg, work_dir); +注释:2014-11-04 18:30 by ghf in HNSSYZX ZhengZhou +*/ +void watch_solution(pid_t pidApp, char * infile, int & ACflg, int isspj, + char * userfile, char * outfile, int solution_id, int lang, + int & topmemory, int mem_lmt, int & usedtime, int time_lmt, int & p_id, + int & PEflg, char * work_dir) { + // parent + int tempmemory; + + if (DEBUG) + printf("pid=%d judging %s\n", pidApp, infile); + + int status, sig, exitcode; + //与ptrace 相互配合,来进行跟踪调试执行子进程 + struct user_regs_struct reg; + struct rusage ruse; + while (1) { + // check the usage + //跟踪调试 + wait4(pidApp, &status, 0, &ruse); + +//jvm gc ask VM before need,so used kernel page fault times and page size + if (lang == 3) { + tempmemory = get_page_fault_mem(ruse, pidApp); + } else { //other use VmPeak + tempmemory = get_proc_status(pidApp, "VmPeak:") << 10; + } + if (tempmemory > topmemory) + topmemory = tempmemory; + //内存超了就退出,并修改ACflg + if (topmemory > mem_lmt * STD_MB) { + if (DEBUG) + printf("out of memory %d\n", topmemory); + if (ACflg == OJ_AC) + ACflg = OJ_ML; + ptrace(PTRACE_KILL, pidApp, NULL, NULL);//杀死子进程,停止执行 + break; + } + //sig = status >> 8;/*status >> 8 脙楼脗路脗庐脙陇脗赂脗聧脙楼脗陇脜隆脙娄脣聹脗炉EXITCODE*/ + + if (WIFEXITED(status)) + break; + if ((lang < 4 || lang == 9) && get_file_size("error.out") && !oi_mode) { + ACflg = OJ_RE; + //addreinfo(solution_id); + ptrace(PTRACE_KILL, pidApp, NULL, NULL); + break; + } + + if (!isspj + && get_file_size(userfile) + > get_file_size(outfile) * 2 + 1024) { + ACflg = OJ_OL; + ptrace(PTRACE_KILL, pidApp, NULL, NULL); + break; + } + + exitcode = WEXITSTATUS(status); + /*exitcode == 5 waiting for next CPU allocation * ruby using system to run,exit 17 ok + * */ + if ((lang >= 3 && exitcode == 17) || exitcode == 0x05 || exitcode == 0) + //go on and on + ; + else { + + if (DEBUG) { + printf("status>>8=%d\n", exitcode); + + } + //psignal(exitcode, NULL); + + if (ACflg == OJ_AC) { + switch (exitcode) { + case SIGCHLD: + case SIGALRM: + alarm(0); + case SIGKILL: + case SIGXCPU: + ACflg = OJ_TL; + break; + case SIGXFSZ: + ACflg = OJ_OL; + break; + default: + ACflg = OJ_RE; + } + print_runtimeerror(strsignal(exitcode)); + } + ptrace(PTRACE_KILL, pidApp, NULL, NULL); + + break; + } + if (WIFSIGNALED(status)) { + /* WIFSIGNALED: if the process is terminated by signal + * + * psignal(int sig, char *s)锛宭ike perror(char *s)锛宲rint out s, with error msg from system of sig + * sig = 5 means Trace/breakpoint trap + * sig = 11 means Segmentation fault + * sig = 25 means File size limit exceeded + */ + sig = WTERMSIG(status); + + if (DEBUG) { + printf("WTERMSIG=%d\n", sig); + psignal(sig, NULL); + } + if (ACflg == OJ_AC) { + switch (sig) { + case SIGCHLD: + case SIGALRM: + alarm(0); + case SIGKILL: + case SIGXCPU: + ACflg = OJ_TL; + break; + case SIGXFSZ: + ACflg = OJ_OL; + break; + + default: + ACflg = OJ_RE; + } + print_runtimeerror(strsignal(sig)); + } + break; + } + /* comment from http://www.felix021.com/blog/read.php?1662 + WIFSTOPPED: return true if the process is paused or stopped while ptrace is watching on it + WSTOPSIG: get the signal if it was stopped by signal + */ + + // check the system calls + ptrace(PTRACE_GETREGS, pidApp, NULL, ®); + if (call_counter[reg.REG_SYSCALL] ){ + //call_counter[reg.REG_SYSCALL]--; + }else if (record_call) { + call_counter[reg.REG_SYSCALL] = 1; + + }else { //do not limit JVM syscall for using different JVM + ACflg = OJ_RE; + char error[BUFFER_SIZE]; + sprintf(error, + "[ERROR] A Not allowed system call: runid:%d callid:%ld\n TO FIX THIS , ask admin to add the CALLID into corresponding LANG_XXV[] located at okcalls32/64.h ,and recompile judge_client", + solution_id, (long)reg.REG_SYSCALL); + write_log(error); + print_runtimeerror(error); + ptrace(PTRACE_KILL, pidApp, NULL, NULL); + } + + + ptrace(PTRACE_SYSCALL, pidApp, NULL, NULL); + } + usedtime += (ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000); + usedtime += (ruse.ru_stime.tv_sec * 1000 + ruse.ru_stime.tv_usec / 1000); + + //clean_session(pidApp); +} + +//清空目录 +void clean_workdir(char * work_dir) { + //卸载 /home/judge/run0 下的文件系统 /proc + execute_cmd("/bin/umount %s/proc", work_dir); + if (DEBUG) {//如果调试则删除日志文件 + execute_cmd("/bin/mv %s/* %slog/", work_dir, work_dir); + } else {//没调试则删除/home/judge/run0 下所有文件 -rf 递归强制删除所有目录与文件,不提示 + execute_cmd("/bin/rm -Rf %s/*", work_dir); + + } + +} + + +//在judge.cc中调用参数 +//runidstr : solution_id 的字符串 参数1 +//buf : clientid 也就是进程id字符串 +//oj_home : /home/judge +//execl("/usr/bin/judge_client", "/usr/bin/judge_client", runidstr, buf, +// oj_home, (char *) NULL); +//argv[0] 指向程序运行的全路径名 +//argv[1] 指向在DOS命令行中执行程序名后的第一个字符串 +//argv[2] 指向执行程序名后的第二个字符串 +void init_parameters(int argc, char ** argv, int & solution_id, + int & runner_id) { + if (argc < 3) {//如果参数小于3个,则报错退出 + fprintf(stderr, "Usage:%s solution_id runner_id.\n", argv[0]); + fprintf(stderr, "Multi:%s solution_id runner_id judge_base_path.\n", + argv[0]); + fprintf(stderr, + "Debug:%s solution_id runner_id judge_base_path debug.\n", + argv[0]); + exit(1); + } + //参数大于4个则启用调试???? ,默认4个所以0 + DEBUG = (argc > 4); + record_call = (argc > 5);//这个不懂??? + if (argc > 5) {//第6个参数,评测语言 + strcpy(LANG_NAME, argv[5]); + } + if (argc > 3) //第4个参数,评测目录 + strcpy(oj_home, argv[3]); + else//否则默认目录设置为/home/judge + strcpy(oj_home, "/home/judge"); + //切换当前默认目录为/home/judge + chdir(oj_home); // change the dir// init our work + + solution_id = atoi(argv[1]); + runner_id = atoi(argv[2]); +} +int get_sim(int solution_id, int lang, int pid, int &sim_s_id) { + char src_pth[BUFFER_SIZE]; + //char cmd[BUFFER_SIZE]; + sprintf(src_pth, "Main.%s", lang_ext[lang]); + + int sim = execute_cmd("/usr/bin/sim.sh %s %d", src_pth, pid); + if (!sim) { + execute_cmd("/bin/mkdir ../data/%d/ac/", pid); + + execute_cmd("/bin/cp %s ../data/%d/ac/%d.%s", src_pth, pid, solution_id, + lang_ext[lang]); + //c cpp will + if (lang == 0) + execute_cmd("/bin/ln ../data/%d/ac/%d.%s ../data/%d/ac/%d.%s", pid, + solution_id, lang_ext[lang], pid, solution_id, + lang_ext[lang + 1]); + if (lang == 1) + execute_cmd("/bin/ln ../data/%d/ac/%d.%s ../data/%d/ac/%d.%s", pid, + solution_id, lang_ext[lang], pid, solution_id, + lang_ext[lang - 1]); + + } else { + + FILE * pf; + pf = fopen("sim", "r"); + if (pf) { + fscanf(pf, "%d%d", &sim, &sim_s_id); + fclose(pf); + } + + } + if (solution_id <= sim_s_id) + sim = 0; + return sim; +} +void mk_shm_workdir(char * work_dir) { + char shm_path[BUFFER_SIZE]; + sprintf(shm_path, "/dev/shm/hustoj/%s", work_dir); + execute_cmd("/bin/mkdir -p %s", shm_path); + execute_cmd("/bin/rm -rf %s", work_dir); + execute_cmd("/bin/ln -s %s %s/", shm_path, oj_home); + execute_cmd("/bin/chown judge %s ", shm_path); + execute_cmd("chmod 755 %s ", shm_path); + //sim need a soft link in shm_dir to work correctly + sprintf(shm_path, "/dev/shm/hustoj/%s/", oj_home); + execute_cmd("/bin/ln -s %s/data %s", oj_home, shm_path); + +} +int count_in_files(char * dirpath) { + const char * cmd = "ls -l %s/*.in|wc -l"; + int ret = 0; + FILE * fjobs = read_cmd_output(cmd, dirpath); + fscanf(fjobs, "%d", &ret); + pclose(fjobs); + + return ret; +} + +int get_test_file(char* work_dir, int p_id) { + char filename[BUFFER_SIZE]; + char localfile[BUFFER_SIZE]; + int ret = 0; + const char * cmd = + " wget --post-data=\"gettestdatalist=1&pid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * fjobs = read_cmd_output(cmd, p_id, http_baseurl); + while (fgets(filename, BUFFER_SIZE - 1, fjobs) != NULL) { + sscanf(filename, "%s", filename); + sprintf(localfile, "%s/data/%d/%s", oj_home, p_id, filename); + if (DEBUG) + printf("localfile[%s]\n", localfile); + + const char * check_file_cmd = + " wget --post-data=\"gettestdatadate=1&filename=%d/%s\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s/admin/problem_judge.php\""; + FILE * rcop = read_cmd_output(check_file_cmd, p_id, filename, + http_baseurl); + time_t remote_date, local_date; + fscanf(rcop, "%ld", &remote_date); + fclose(rcop); + struct stat fst; + stat(localfile, &fst); + local_date = fst.st_mtime; + + if (access(localfile, 0) == -1 || local_date < remote_date) { + + if (strcmp(filename, "spj") == 0) + continue; + execute_cmd("/bin/mkdir -p %s/data/%d", oj_home, p_id); + const char * cmd2 = + " wget --post-data=\"gettestdata=1&filename=%d/%s\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O \"%s\" \"%s/admin/problem_judge.php\""; + execute_cmd(cmd2, p_id, filename, localfile, http_baseurl); + ret++; + + if (strcmp(filename, "spj.c") == 0) { + // sprintf(localfile,"%s/data/%d/spj.c",oj_home,p_id); + if (access(localfile, 0) == 0) { + const char * cmd3 = "gcc -o %s/data/%d/spj %s/data/%d/spj.c"; + execute_cmd(cmd3, oj_home, p_id, oj_home, p_id); + } + + } + if (strcmp(filename, "spj.cc") == 0) { + // sprintf(localfile,"%s/data/%d/spj.cc",oj_home,p_id); + if (access(localfile, 0) == 0) { + const char * cmd4 = + "g++ -o %s/data/%d/spj %s/data/%d/spj.cc"; + execute_cmd(cmd4, oj_home, p_id, oj_home, p_id); + } + } + } + + } + pclose(fjobs); + + return ret; +} +void print_call_array() { + printf("int LANG_%sV[256]={", LANG_NAME); + int i = 0; + for (i = 0; i < call_array_size; i++) { + if (call_counter[i]) { + printf("%d,", i); + } + } + printf("0};\n"); + + printf("int LANG_%sC[256]={", LANG_NAME); + for (i = 0; i < call_array_size; i++) { + if (call_counter[i]) { + printf("HOJ_MAX_LIMIT,"); + } + } + printf("0};\n"); + +} + +//在judge.cc中调用参数 +//runidstr : solution_id 的字符串 参数1 +//buf : clientid 也就是进程id字符串 +//oj_home : /home/judge +//execl("/usr/bin/judge_client", "/usr/bin/judge_client", runidstr, buf, +// oj_home, (char *) NULL); +//argv[0] 指向程序运行的全路径名 +//argv[1] 指向在DOS命令行中执行程序名后的第一个字符串 +//argv[2] 指向执行程序名后的第二个字符串 +int main(int argc, char** argv) { + + char work_dir[BUFFER_SIZE]; //工作目录 + //char cmd[BUFFER_SIZE]; + char user_id[BUFFER_SIZE]; //用户id + int solution_id = 1000; //题目id + int runner_id = 0; //进程id + int p_id, time_lmt, mem_lmt, lang, isspj, sim, sim_s_id, max_case_time = 0; +//初始化参数,代评测id, 进程id ,评测目录 ,DEBUG,RECORD_CALL + init_parameters(argc, argv, solution_id, runner_id); +//初始化数据库链接信息,读取/home/judge/etc/judge.conf,初始化conn + init_mysql_conf(); +//如果是轮询数据库,并且数据库链接失败,那么退出 + if (!http_judge && !init_mysql_conn()) { + exit(0); //exit if mysql is down + } + //set work directory to start running & judging + //work_dir : /home/judge/run0 + sprintf(work_dir, "%s/run%s/", oj_home, argv[2]); + + //shm_run默认0 不知道什么作用还???? + if (shm_run) + mk_shm_workdir(work_dir); + //进入/home/judge/run0 并设置为当前工作 目录 + chdir(work_dir); + if (!DEBUG)//因为DEBUG = (argc>4) :0 所以工作前先清除下该目录 + clean_workdir(work_dir); + + if (http_judge) + system("/bin/ln -s ../cookie ./"); + //读取数据库solution表依据solution_id,初始化p_id问题id,user_id用户id,lang编程语言编码 + get_solution_info(solution_id, p_id, user_id, lang); + //get the limit + //读取运行时间、内存限制,如果是test则默认值 + if (p_id == 0) { + time_lmt = 5; + mem_lmt = 128; + isspj = 0; + } else { + get_problem_info(p_id, time_lmt, mem_lmt, isspj); + } + //copy source file + //根据情况读取源程序代码 + //默认是将源码从数据库中读取到/home/judge/run0/ 建立Main.c + get_solution(solution_id, work_dir, lang); + + //独立处理java + //java is lucky + if (lang >= 3) { + // the limit for java + time_lmt = time_lmt + java_time_bonus; + mem_lmt = mem_lmt + java_memory_bonus; + // copy java.policy + execute_cmd("/bin/cp %s/etc/java0.policy %s/java.policy", oj_home, + work_dir); + + } + //对时间内存做出最大上限 + //never bigger than judged set value; + if (time_lmt > 300 || time_lmt < 1) + time_lmt = 300; + if (mem_lmt > 1024 || mem_lmt < 1) + mem_lmt = 1024; + + if (DEBUG) + printf("time: %d mem: %d\n", time_lmt, mem_lmt); + + // compile + // printf("%s\n",cmd); + // set the result to compiling + //编译程序 + int Compile_OK; + + Compile_OK = compile(lang);//编译是否完成,成功默认返回0;否则返回错误文件ce.txt的大小 + if (Compile_OK != 0) {//编译失败则推出判题进程不需后续执行了 + addceinfo(solution_id); + update_solution(solution_id, OJ_CE, 0, 0, 0, 0, 0.0); + update_user(user_id); + update_problem(p_id); + if (!http_judge) + mysql_close(conn); + if (!DEBUG) + clean_workdir(work_dir); + else + write_log("compile error"); + exit(0); + } else {//如果是除了"rb", "sh", "py",这三种以外的语言,更新表solution + update_solution(solution_id, OJ_RI, 0, 0, 0, 0, 0.0); + } + //exit(0); + // run + //执行编译后程序 + char fullpath[BUFFER_SIZE];///home/judge/data/1000 完整的测试数据目录 + char infile[BUFFER_SIZE]; + char outfile[BUFFER_SIZE]; + char userfile[BUFFER_SIZE]; + sprintf(fullpath, "%s/data/%d", oj_home, p_id); // the fullpath of data dir + + // open DIRs + DIR *dp; + dirent *dirp; + // using http to get remote test data files + if (p_id > 0 && http_judge) + get_test_file(work_dir, p_id); + //默认采用的这种,读取目录文件失败则判题子程序退出,-1 + if (p_id > 0 && (dp = opendir(fullpath)) == NULL) { + + write_log("No such dir:%s!\n", fullpath); + if (!http_judge) + mysql_close(conn); + exit(-1); + } + + int ACflg, PEflg; + ACflg = PEflg = OJ_AC; + int namelen; + int usedtime = 0, topmemory = 0; + + //create chroot for ruby bash python + if (lang == 4) + copy_ruby_runtime(work_dir); + if (lang == 5) + copy_bash_runtime(work_dir); + if (lang == 6) + copy_python_runtime(work_dir); + if (lang == 7) + copy_php_runtime(work_dir); + if (lang == 8) + copy_perl_runtime(work_dir); + if (lang == 9) + copy_mono_runtime(work_dir); + if (lang == 10) + copy_objc_runtime(work_dir); + if (lang == 11) + copy_freebasic_runtime(work_dir); + if (lang == 12) + copy_guile_runtime(work_dir); + // read files and run + // read files and run + // read files and run + double pass_rate = 0.0; + int num_of_test = 0; + int finalACflg = ACflg; + //网页点击的测试, + if (p_id == 0) {} //custom input runnin + printf("running a custom input...\n"); + get_custominput(solution_id, work_dir); + init_syscalls_limits(lang); + pid_t pidApp = fork(); + + if (pidApp == 0) { + run_solution(lang, work_dir, time_lmt, usedtime, mem_lmt); + } else { + watch_solution(pidApp, infile, ACflg, isspj, userfile, outfile, + solution_id, lang, topmemory, mem_lmt, usedtime, time_lmt, + p_id, PEflg, work_dir); + + } + if (ACflg == OJ_TL) { + usedtime = time_lmt * 1000; + } + if (ACflg == OJ_RE) { + if (DEBUG) + printf("add RE info of %d..... \n", solution_id); + addreinfo(solution_id); + } else { + addcustomout(solution_id); + } + update_solution(solution_id, OJ_TR, usedtime, topmemory >> 10, 0, 0, 0); + + exit(0); + } + //真正的submit执行测试 + for (; (oi_mode || ACflg == OJ_AC) && (dirp = readdir(dp)) != NULL;) { + //遍历/home/judge/data/1000下的sample.in sample.out test.in test.out + namelen = isInFile(dirp->d_name); // check if the file is *.in or not + if (namelen == 0) + continue; + //准备好测试数据,初始化,outfile,userfile + prepare_files(dirp->d_name, namelen, infile, p_id, work_dir, outfile, + userfile, runner_id); + //这是什么限制??????? + init_syscalls_limits(lang); + //又要创建执行子进程了。。。。 + pid_t pidApp = fork(); + + if (pidApp == 0) {//如果是子进程在执行 + //执行程序,在/run0/下生成user.out结果文件 + run_solution(lang, work_dir, time_lmt, usedtime, mem_lmt); + } else { + //父进程,创建的执行子进程数量+1,这里是不是没有处理如果子进程创建失败该如何????????? + num_of_test++; + //这里没有采用 waitpid()方式,而是采用监视子进程方式ptrace() + //为什么这样用呢?????????? + //监视子进程,看齐是否能完整无误执行下来,随时修改ACflg + watch_solution(pidApp, infile, ACflg, isspj, userfile, outfile, + solution_id, lang, topmemory, mem_lmt, usedtime, time_lmt, + p_id, PEflg, work_dir); + //到这里执行子进程应该是结束了,开始进行结果的判断匹配 + judge_solution(ACflg, usedtime, time_lmt, isspj, p_id, infile, + outfile, userfile, PEflg, lang, work_dir, topmemory, + mem_lmt, solution_id, num_of_test); + if (use_max_time) { + max_case_time = + usedtime > max_case_time ? usedtime : max_case_time; + usedtime = 0; + } + //clean_session(pidApp); + } + if (oi_mode) { + if (ACflg == OJ_AC) { + ++pass_rate; + } + if (finalACflg < ACflg) { + finalACflg = ACflg; + } + + ACflg = OJ_AC; + } + } + if (ACflg == OJ_AC && PEflg == OJ_PE) + ACflg = OJ_PE; + if (sim_enable && ACflg == OJ_AC && (!oi_mode || finalACflg == OJ_AC) + && lang < 5) { //bash don't supported + sim = get_sim(solution_id, lang, p_id, sim_s_id); + } else { + sim = 0; + } + //if(ACflg == OJ_RE)addreinfo(solution_id); + + if ((oi_mode && finalACflg == OJ_RE) || ACflg == OJ_RE) { + if (DEBUG) + printf("add RE info of %d..... \n", solution_id); + addreinfo(solution_id); + } + if (use_max_time) { + usedtime = max_case_time; + } + if (ACflg == OJ_TL) { + usedtime = time_lmt * 1000; + } + if (oi_mode) { + if (num_of_test > 0) + pass_rate /= num_of_test; + update_solution(solution_id, finalACflg, usedtime, topmemory >> 10, sim, + sim_s_id, pass_rate); + } else { + update_solution(solution_id, ACflg, usedtime, topmemory >> 10, sim, + sim_s_id, 0); + } + if ((oi_mode && finalACflg == OJ_WA) || ACflg == OJ_WA) { + if (DEBUG) + printf("add diff info of %d..... \n", solution_id); + if (!isspj) + adddiffinfo(solution_id); + } + update_user(user_id); + update_problem(p_id); + clean_workdir(work_dir); + + if (DEBUG) + write_log("result=%d", oi_mode ? finalACflg : ACflg); + if (!http_judge) + mysql_close(conn); + if (record_call) { + print_call_array(); + } + closedir(dp); + return 0; +} +``` + diff --git a/docs/Contributors.md b/docs/Contributors.md new file mode 100644 index 00000000000..52cb0f8751a --- /dev/null +++ b/docs/Contributors.md @@ -0,0 +1,18 @@ +# 贡献者名单 + +### 源码 + +请前往 [Github](https://github.com/zhblue/hustoj/graphs/contributors) 查看。 + +### 文档 + ++ [zhblue](https://www.hustoj.com/) + ++ 夏夏 + ++ qq2663797538@gmail.com + ++ bigballon + ++ [宝硕](https://baoshuo.ren/) + diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 00000000000..0549c5bc134 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,179 @@ +# HUSTOJ FAQ + +#### 改坏了代码怎么办? + +自己不小心改坏了 web 代码,可以使用 install 目录中的 fixing.sh 进行系统修复。 + +#### 如何进入后台? + +以管理员身份登录,点击 `Admin/管理` 进入后台。 + +#### 如何添加题目? + +进入后台,点击左侧NewProblem。 + +#### 如何添加测试数据? + +添加题目时,可以在 `test input` `test output` 添加一组测试数据,大规模的数据(10KB+)和更多的数据,可以在添加完题目后,通过 `ftp/sftp`,上传到题目对应目录,通常是 `/home/judge/data/题号` 。命名规则是输入数据以 `.in` 结尾,输出数据以 `.out` 结尾,主文件名相同。 + +#### 如何编辑题目? + +后台中点击 `Problem List` ,找到需要编辑的题目,点击 `Edit` 。编辑时不能修改测试数据,测试数据请使用 `ftp` 工具修改。 + +#### 如何启用题目? + +题目添加后,默认是停用状态,以防比赛提前漏题,后台中点击 `ProblemList` ,找到题目,点击 `Resume` 启用题目,或者组织比赛,比赛中的题目将自动启用。 + +#### 如何组织比赛? + +在题目列表 `ProblemList` 中选择使用的题目, 在 `PID` 一栏打钩, 点击 `CheckToNewContest` 按钮,进入到比赛添加页面,输入比赛名称,设定 `比赛时间`、`语言类型`、`访问权限`,然后提交即可。 + +也可以使用管理菜单中的 `NewContest` ,需要手动输入题目编号,用英文逗号分隔。 + +#### 如何修改、删除比赛? + +点击比赛列表 ContestList,选择 `Edit` 或 `Delete`。 + +#### 如何修改公告信息? + +点击 `SetMessage`。 + +#### 如何修改用户密码? + +点击 `ChangePassWord`。 + +#### 如何重新判题? + +点击 `Rejudge` ,输入题号或运行编号。 + +#### 如何增加用户权限? + +`Addprivilege` `administrator` 为管理员,`source_browser` 为代码审查,`contest_creator`为比赛组织者。 + +通常给使用系统的老师分配代码审查和比赛组织者权限即可。 + +#### 如何导入、导出题目? + +使用 ImportProblem ,上传 FPS 文件。 + +使用 ExportProblem ,输入起始编号,结束编号,或题号列表,如果输入了列表,起始结束将不起作用。 + +#### 如何更新数据库结构? + +系统升级中,有对数据库的修改,这些修改不能通过 SVN 实现自动更新,如果发现升级 `web/core` 代码后系统报错,可以执行 `update database` 操作,进行数据库升级。因为脚本中有测试代码,所以重复执行不会造成影响。 + +#### 如何下载新题目? + ++ 访问 `FreeProblemSet` ,查看 `Downloads` 列表。 ++ 访问 [TK题库](http://tk.hustoj.com/) 下载题目 + + +#### 如何让判题程序忽略行尾的空白字符? + +在 `judge_client.cc` 头部增加宏定义 `IGNORE_ESOL` 。 + +#### 如何上传多组数据? + +加好题目后在题目列表找 `TestData` ,点击上传。主文件名一样的 `*.in` `*.out`,如 `test1.in` `test1.out` + +#### 无法正常判题,一直 `pending` + +可能是 `judged` 服务未能正常启动,执行如下命令即可。 +```bash +sudo judged +``` + +或者检查 `/home/judge/etc/judge.conf` 中的数据库账号配置,修正后再次重启服务器或执行 `sudo pkill -9 judged` 。 +等待一会儿再执行 `sudo judged` + +#### Runtime error + +若出现 `Runtime Error:[ERROR] A Not allowed system call: runid:10735 CALLID:20` 时,编辑 `okcalls64.h` 或 `okcalls32.h`(取决于您使用的 `Linux` 版本 `uname -a` 出现 `x64` 字样则 `64` 位,`i686` 字样则 `32` 位),在对应的语言数组里增加内容。如 `C` 或 `C++` : + +```cpp +int LANG_CV[256] = { 85, 8,140, SYS_time, SYS_read, SYS_uname, SYS_write, SYS_open, +SYS_close, SYS_execve, SYS_access, SYS_brk, SYS_munmap, SYS_mprotect, SYS_mmap2, +SYS_fstat64, SYS_set_thread_area, 252, 0 }; +``` + +将上述报错中 `CALLID:` 后的数字,增加到数组中非末尾的位置,如果这个数字是0,请加在首位。 + +```cpp +int LANG_CV[256] = { 20, 85, 8,140, SYS_time, SYS_read, SYS_uname, SYS_write, +SYS_open,SYS_close, SYS_execve, SYS_access, SYS_brk, SYS_munmap, +SYS_mprotect,SYS_mmap2, SYS_fstat64, SYS_set_thread_area, 252, 0 }; +``` + +修改完成,重新在 `core` 目录执行 `sudo ./make.sh` 然后重新测试,如果发现再次出现类似错误,请留意 `CALLID` 数字变化,重复上述步骤直至问题消失。 + +#### Ubuntu 下 `Apache2` 报错(针对旧版本,新版本使用 `nginx`) + +Ubuntu 环境,当 `apache2` 重启提示时: + +``` +* Starting web server apache2 + +apache2: Could not reliably determine the server's fully qualified domain name, + +... waiting apache2: Could not reliably determine the server's fully qualified domain +name, using 127.0.1.1 for ServerName +``` + +解决的方法是: + +```bash +sudo vim /etc/apache2/sites-available/default +``` + +打开 `default` 文件后,在 `default` 文件顶端加入:`ServerName 127.0.0.1` + +重启 `apache2` 就不会提示上述错误了。 +```bash +sudo /etc/init.d/apache2 restart +``` + +#### 页面总是需要刷新才能显示 + +如果您使用的是 `ie6` 浏览器,请禁用服务器上的 `deflate` 模块。 + +```bash +sudo rm /etc/apache2/mods-enabled/deflate.* +sudo /etc/init.d/apache2 restart +``` + +21.添加题目时出现 `warning/` 题目目录下数据没有自动生成 + +需要修改系统 `php.ini` ,给予 `php` 操作数据目录的权限。以下是推荐修改的设置。 +``` +sudo gedit /etc/php5/apache2/php.ini open_basedir=/home/judge/data:/var/www/JudgeOnline:/tmp +``` + +```ini +max_execution_time = 300 +; Maximum execution time of each script, in seconds +max_input_time = 600 +memory_limit = 128M +; Maximum amount of memory a script may consume +post_max_size = 64M +upload_tmp_dir =/tmp +upload_max_filesize = 64M +``` +重启 `Apache` 即可。 + +#### 添加的题目普通用户看不到 + +题目默认为删除状态,只有管理员能访问,当管理员确认题目没有问题后,可以点击 `ProblemList` 中的 `Resume` 启用题目。 + +#### Java 总是CE/RE + +目前只支持 `sun` 原版 `jdk` 和 `openjdk`,其他 `jdk` 暂不能保证支持。 + +#### 管理员不能查看别人的源码 + +请给自己增加 `source_browser` 权限。 + +#### fckeditor 上传的图片在题目中无法显示 + +如果 `web` 安装位置不在 `/JudgeOnline` ,需要手工修改 `/fckeditor/editor/filemanager/connectors/php/config.php` 的第 37 行: `$Config['UserFilesPath'] ='/JudgeOnline/upload/'.date("Ym")."/" ;` 将 `JudgeOnline` 修改为对应的 `OJ web` 路径,如 `oj`。 + + diff --git a/docs/FPS.md b/docs/FPS.md new file mode 100644 index 00000000000..c67bf1dcab4 --- /dev/null +++ b/docs/FPS.md @@ -0,0 +1,21 @@ +# FPS + +## 简介 + +`fps` 是一种开放式 `ACM/ICPC/NOIP` 题目存储、转换标准,目前有 `HUSTOJ` `OpenJudger` `QDUOJ` 等系统兼容这种格式的导入导出。 + +## 引言 + +> +> 目前有许多开源的 `ACM/ICPC Online Judge` 系统,都各有特色,他们都有自己独特的体系结构。 +> +> 但似乎比较难找的是相应的练习题目,各大学校的 OJ 都对自己的题目数据严加保密,不轻易与人分享。 +> +> 出题是很辛苦的事情,抄题也是如此,而且抄来的题目数据很难制作,标程就更难寻找,特别裁判更加难上加难。 +> +> 这个项目希望建立一个交流平台,使得学校之间交流题目更加容易。 +> +> 所以这个项目的目标是,建立一种通用题目交换格式,基于 `XML` 来实现。 +> + +?> *TODO* \ No newline at end of file diff --git a/docs/Install.md b/docs/Install.md new file mode 100644 index 00000000000..fb6b759e123 --- /dev/null +++ b/docs/Install.md @@ -0,0 +1,90 @@ +# 安装 + +### 注意事项 + +根据你选择的发行版不同,从下面三个脚本里选一个来用。 + +**不要相信百度来的长篇大论的所谓教程,那些都是好几年前的老皇历了,会导致不判题,不显示,不好升级等等问题。** + +尤其**别装 `Apache`** ,如果已经安装,请先停用或卸载,以免 80 端口冲突。 + +**不要**使用 `LNMP` `LAMP` `Cpanel` `宝塔` 等其他面板程序提供的 `MySQL` `Nginx` `Apache` `PHP` 环境,安装脚本已经包含所有必须环境的安装。 + +**腾讯云用户请[换软件源](https://developer.aliyun.com/mirror/ubuntu),增加 `multiverse` 。** + +阿里云用户请百度 `阿里云 80端口`。 + +!> **安装完成,用admin作为用户名注册一个用户,自动成为管理员。** + +### 自动识别系统安装脚本 + +```bash +wget https://github.com/zhblue/hustoj/raw/master/trunk/install/install+.sh -O install+.sh +sudo bash install+.sh +``` + +### 基于 Ubuntu 18.04 安装 + +**腾讯云用户请[换软件源](https://developer.aliyun.com/mirror/ubuntu)** + +```bash +wget https://github.com/zhblue/hustoj/raw/master/trunk/install/install-ubuntu18.04.sh -O install-ubuntu18.04.sh +sudo bash install-ubuntu18.04.sh +``` + +#### 演示视频(基于 Ubuntu Server 18.04 LTS) + +Bilibili: [av967577292](https://www.bilibili.com/video/BV1Mp4y1C7Xx) + +### 基于 Deepin15+ 安装 + +国内桌面用户 Deepin15.9+ (内置QQ微信WPS方便出题人本地测试,最新15.11测试通过) + +```bash +wget https://github.com/zhblue/hustoj/raw/master/trunk/install/install-deepin15.9.sh -O install-deepin15.9.sh +sudo bash install-deepin15.9.sh +``` + +### 基于 CentOS 安装 + +假如你不得已非要用 CentOS7 (有的语言可能不支持,但是某些机架式服务器的 Raid 卡 Ubuntu 不认只能装 CentOS ),可以用下面脚本快速安装 OJ : + +```bash +wget https://raw.githubusercontent.com/zhblue/hustoj/master/trunk/install/install-centos7.sh -O install-centos7.sh +sudo bash install-centos7.sh +``` + + +### 基于 Docker 安装 + +Docker 安装,可用于快速体验 HUSTOJ 的全部功能,**可能存在未知的魔法问题,请慎重考虑用于生产环境!!!** +使用构建好的 Docker 镜像(GitLab CI/CD系统自动构建) + +```shell +docker run -d \ + --name hustoj \ + -p 8080:80 \ + -v ~/volume:/volume \ + registry.gitlab.com/mgdream/hustoj +``` + +由于 Web 端、数据库、判题机全部被打包在同一个镜像,无法扩展,不推荐使用此镜像做分布式判题,另外请**不要**在 Docker 中使用 SHM 文件系统,会由于内存空间不足无法挂载沙箱环境而导致莫名其妙的运行错误。 + +部署后使用浏览器访问 [http://localhost:8080](http://localhost:8080) 。 + +### 基于其他发行版安装 + +其他的发行版,如树莓派的 `raspbian 8/9` `Ubuntu 14.04` 的安装脚本在 `install` 目录可以找到,但是不完善,安装后需要部分手工修复调整。 + +[脚本列表传送门](https://github.com/zhblue/hustoj/tree/master/trunk/install) + +参考视频: + +### LiveCD 下载安装 + +Linux不熟悉的用户推荐使用: `HUSTOJ_LiveCD` (发送 `livecd` 到微信公众号 `onlinejudge` ,获得百度云下载链接) + +`HUSTOJ_Windows` (仅支持 XP , QQ 群 `23361372` 共享文件)进行安装。 + +使用说明见 `iso` 中 `README` ,也可以参考 [LiveCD简介](/LiveCD) + diff --git a/docs/MoodleIntegration.md b/docs/MoodleIntegration.md new file mode 100644 index 00000000000..01c3898c2a6 --- /dev/null +++ b/docs/MoodleIntegration.md @@ -0,0 +1,139 @@ +# 集成 Moodle + +### 基础要求 + +由于 Moodle 需要新版的 PHP 和 MySQL,所以需要安装在 Ubuntu18.04 以上的机器上。 + +### 预备工作 + +首先用脚本安装HUSTOJ,然后在 `/etc/nginx/sites-enbaled/default` 文件中 `location / { ` 这一行之前添加下面的内容: + +```conf +if (!-e $request_filename) { + rewrite "^(.*\.php)(/)(.*)$" $1?file=/$3 last; +} +``` + +查阅 `judge.conf` 获得数据库账户 + +``` +cat /home/judge/etc/judge.conf +``` + + +重新加载 `nginx` 配置文件 + +``` +sudo service nginx reload +``` + +### 安装moodle + +从 [这里](https://download.moodle.org/releases/latest/) 下载最新的moodle源码文件。 + +把下载到的 `moodle` 安装文件解压到 `/home/judge/src/web` 目录下,得到 `/home/judge/src/web/moodle` 目录 + +修改所有目录的属主为 `www-data` + +``` +chown -R www-data /home/judge/src/ +``` + +刷新 OJ 页,得到右上角的 Moodle 入口,点击开始安装流程。 + +**moodle 的数据库默认 `moodle` ,表前缀 `mdl_` ,请不要修改** + + +### 关联两个系统 + +让学生在两个系统中用相同的用户名注册。 + + + +在HUSTOJ中添加一个比赛,获得比赛编号1000 + + +在moodle中添加一个文本作业,标题命名为[OJ]-C1000,其中的1000表示OJ中对应的比赛编号。 + +### 添加触发器 + +``` +DELIMITER $$ +DROP trigger IF EXISTS `jol`.`tri_moodle` $$ +create trigger tri_moodle +after update on solution +for each row +begin + declare mark int; + declare total int; + + select count(1) into total from contest_problem where contest_id=new.contest_id; + if total>0 then + select sum(ac)/total*100 into mark + from (select max(pass_rate) ac from solution where user_id=new.user_id and contest_id=new.contest_id and problem_id>0 group by problem_id) s; + + call update_moodle(new.contest_id,new.user_id,mark); + + end if; +end $$ +DELIMITER ; +``` + +### 添加存储过程 + +``` +DELIMITER $$ +CREATE PROCEDURE `update_moodle`(IN `cid` INT, IN `user_id` VARCHAR(20), IN `mark` INT) +top:BEGIN + declare as_id int; + declare u_id int; + declare nowtime int; + declare oldid int; + set nowtime=UNIX_TIMESTAMP(now()); + set as_id=0; + select m.id into as_id from + moodle.mdl_assign m + where m.name = concat('[OJ]-C',cid); + if as_id=0 then + leave top; + end if; + set u_id =-1; + select m.id into u_id from moodle.mdl_user m where username=user_id; + select mag.grade into oldid from moodle.mdl_assign_grades mag + where assignment=as_id and userid=u_id; + + set oldid=-1; + + select id into oldid from moodle.mdl_assign_submission m + where assignment=as_id and userid=u_id; + if oldid =-1 then + + insert into moodle.mdl_assign_submission + (assignment,userid,timecreated,timemodified,status,attemptnumber) + values( as_id ,u_id ,nowtime ,nowtime ,'new' ,0); + insert into moodle.mdl_assign_grades(assignment,userid,timecreated,timemodified,grader,grade,attemptnumber) + select ma.id,mas.userid,UNIX_TIMESTAMP( NOW( ) ),UNIX_TIMESTAMP( NOW( ) ),2,mark,0 + from moodle.mdl_assign ma + inner join moodle.mdl_assign_submission mas on + mas.assignment=ma.id and mas.status='new' + where concat(ma.id,',',mas.userid) not in (select concat(assignment,',',userid) from moodle.mdl_assign_grades); + + else + + + update moodle.mdl_assign_grades + set grade=mark,timemodified=nowtime where + assignment=as_id and userid=u_id; + + end if; + + +END$$ +DELIMITER ; +``` + +### 预期效果 + +当学生在hustoj中提交代码,moodle将在作业里同步显示他们的最新成绩。 + +![](images/moddle.png) diff --git a/docs/MultiJudge.md b/docs/MultiJudge.md new file mode 100644 index 00000000000..2159905755f --- /dev/null +++ b/docs/MultiJudge.md @@ -0,0 +1,85 @@ +# 建立分布式判题系统 + +HUSTOJ 支持一台数据库服务器,多台 `web` 服务器和多台判题服务器,以承担较高的访问负荷。 + +### 创建用于从远程连接数据库的帐号 + +```sql +GRANT ALL PRIVILEGES ON jol.* TO ‘judge’@’%’ +IDENTIFIED BY ‘judge_pass’ WITHOUT GRANT OPTION; +flush privileges; +``` +其中 `jol` 为数据库, `judge` 为帐号,`judge_pass` 为密码。注意检查 `/etc/mysql/my.cnf` 确保 `bind-address = 0.0.0.0` 。高负载的情况下最好设置更多的连接数:`max_connections = 512` 。 + +### 配置各 web 程序连接到数据库 + +修改 `include/db_info.inc.php` : +```php +static $DB_HOST="数据库服务器ip"; +static $DB_NAME="jol"; +static $DB_USER="judge"; +static $DB_PASS="judge_pass"; +``` + +### 配置各判题程序连接到数据库,分配任务 + +```conf +OJ_HOST_NAME=数据库服务器ip +OJ_USER_NAME=judge +OJ_PASSWORD=judge_pass +OJ_DB_NAME=jol +``` + +```conf +OJ_TOTAL=判题机总数 +OJ_MOD=本机编号,从0 开始 +``` + +> 自R784 版本开始,不再需要分别配置 `OJ_TOTAL` 和 `OJ_MOD` ,全部设为 `1` 和 `0` 即可,所有正常工作的 `judge` 节点将自动分配当前任务。 + +### 复制测试数据目录到各判题机 + +> 从r1520 开始,使用 `HTTP_JUDGE` 方式不必单独复制数据,数据将从 `web` 服务器按需下载。 + +先要准备好远程访问,在主服务器执行: + +```bash +sudo apt-get install openssh-server +``` + +!> 下列两种方法只需选择一种即可。 + +#### 主机分发 + +从主机向判题机复制: + +```bash +scp -r /home/judge/data root@判题机ip:/home/judge/ +``` + +或用同步命令: + +```bash +rsync -vzrtopgh –delete /home/judge/data root@判题机ip:/home/judge/ +``` + +#### 判题机获取 + +判题机从主机复制: + +```bash +scp -r root@主机ip:/home/judge/data /home/judge/ +``` + +或用同步命令: + +```bash +rsync -vzrtopg –progress –delete root@主机ip:/home/judge/data /home/judge/ +``` + +### 在各判题机重启判题程序 + +```bash +sudo pkill judged && sudo judged +``` + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..db8fc8ff25b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,27 @@ +# HUSTOJ 使用文档 + +> 流行的OJ系统,跨平台、易安装、有题库。 + +#### 链接 + +* [项目](https://github.com/zhblue/hustoj) +* [安装](/Install) +* [题库](http://tk.hustoj.com) +* [使用教程](/) + +#### 用户 + +太多了写不过来... + +#### 硬件需求 + +
+ + +> 10 人版:树莓派 Zero +> +> 50 人版:老爷机 +> +> 100 人版:云服务器(1 vCPU 1GiB) +> +> 3000 人版:E5-2670 机架式服务器 diff --git a/docs/SpecialJudge.md b/docs/SpecialJudge.md new file mode 100644 index 00000000000..62cc7e8b54d --- /dev/null +++ b/docs/SpecialJudge.md @@ -0,0 +1,178 @@ +# Special Judge + +### 说明 + +通常的 ACM 题目包括以下几项内容:题目描述(Description)、输入描述(Input)、输出描述(Output)、样例输入(Sample Input)、样例输出(Sample Out),在后台则包括测试输入(Input Data)和测试输出(Output Data)两项。在评测用户提交的程序正确与否时,系统会将样例输入和测试输入重定向作为程序的标准输入,通过判断程序对应的输出是否与期待的输出完全相同,来判断解答是否正确。 + +对于同一道题目,用户可能使用各种不同的方法来解答,所以对于某些特殊的题目,其结果可能不唯一,但都符合题目要求。此类题目就需要进行特判(Special Judge)。HUSTOJ 便提供了特判功能。 + +这些题目主要有: + +1、答案不唯一。见下例一。 + +2、控制精度。题目要求输出精度在某 `eps` 之内。见下例二。 + +3、其他。 + +`spj` 的文件可以是 `c` 或 `cpp` 编写的程序,编译为 `spj` 文件,并设执行权限。 + +`spj.c` 或 `spj.cc`,需编译为 `spj` ,执行 `spj` 时传 3 个文件名参数:输入,参考输出,用户输出。 + +`spj.c` 或 `spj.cc` 应放置于对应题目测试数据目录下,导出题目时将自动导出。执行判定是自动的。 + +`spj` 的退出值决定判断结果,成功退出 `0` 表示 `AC` ,其余表示 `WA` 。 + +把 `spj` 本身看做一道题目,输入是前述三个文件,输出是程序退出时的返回值,返回值是 `0` 表示 `AC` ,非零表示 `WA`。 + +可以到 `freepeoblemset` 找到标有 `spj` 的题目下载。 + +### Special Judge 在 HUSTOJ 中的使用 + +1.在题目后台编辑页面启用该选项 + +![](images/spj.jpg) + +2.编制 `data.in` 、`data.out`、`spj.cc`(或 `spj.c`)。 + +3.编译出spj,设执行权限,连同2 中的三个文件一起上传至服务器。 + +### 例子 + +**1.A+B spj.c** + +```c +#include +int main(int argc, char *args[]) { + FILE * f_in = fopen(args[1],"r"); + FILE * f_out = fopen(args[2],"r"); + FILE * f_user = fopen(args[3],"r"); + int ret = 0; + int a, b, c; + while(fscanf(f_in,"%d %d",&a, &b) != EOF) { + fscanf(f_user, "%d", &c); + if(a + b != c) { + ret = 1; + break; + } + } + fclose(f_in); + fclose(f_out); + fclose(f_user); + return ret; +} +``` + +**2.给出一个不小于 12 的正整数n,请你输出两个合数,使他们的和等于n。** + +分析题意,可以得出系统判断用户输出是否正确的两个条件是:输出的两个数是否均为合数;输出的两个数之和是否为n。 + +首先分别制定data.in(测试输入)、data.out(测试输出)如下: + +filename: data.in +``` +1 +2 +3 +4 +3 +12 +15 +1000 +``` + +filename: data.out +``` +1 +2 +3 +8 4 +9 6 +500 500 +``` + +然后编写 `spj.cc` (特判程序)。HUSTOJ 中的 `spj.cc` 需要有 3 个文件名参数传入:测试输入、测试输出、用户输出。`spj` 的返回值决定着判断结果,成功返回 `0` 表示 `AC` ,其他非零值表示 `WA` 。 + +下面给出一个本题 `spj.cc` 的示例代码。 + +```c +#include +bool is_prime(int x) { + for(int i = 2; i <= n / 2; i++) { + if(n % i == 0) { //符合该条件不是素数 + return true; + } + } +} +int main(int argc, char *args[]) { + FILE * f_in = fopen(args[1], "r"); //测试输入 + FILE * f_out = fopen(args[2], "r"); //测试输出 + FILE * f_user = fopen(args[3], "r"); //用户输出 + int ret = 0; //返回值 + int T, n, a, b; + fscanf(f_in, "%d", &T); //从输入中读取数据组数T + while(T--) { + fscanf(f_in , "%d" , &n ); + fscanf(f_user, "%d%d", &a, &b); + if(a + b != n || is_prime(a) || is_prime(b)) + ret = 1; //Wrong Answer + } + fclose(f_in); + fclose(f_out); + fclose(f_user); + return ret; +} +``` + +编制完 `spj.cc` 后,将这些文件上传至服务器。 + +登录服务器以后进入 `/home/judge/data/[题号]` 文件夹,执行以下操作,以 `/home/judge/data/1000` 为例: + +```bash +sudo vim spj.cc #Edit the file spj.cc +sudo g++ -o spj spj.cc # gcc -o spj spj.c +sudo chmod +x spj +``` + +测试: + +```bash +acm@acm:/home/judge/data/1000$ ./spj data.in data.out data.out +acm@acm:/home/judge/data/1000$ echo $? +0 #Accepted +acm@acm:/home/judge/data/1000$ ./spj data.in data.out data.in +acm@acm:/home/judge/data/1000$ echo $? +1 #Wrong Answer +``` + +在本地 HUSTOJ 测试,通过网页提交,通过。 + +**3.经过一定复杂计算后输出一个实数结果,精度要求控制在10−4 内。** + +分析题意,可知 `spj` 需要判断测试输出 `a` 与用户输出 `x` 之差是否在 `eps` 之内。 + +代码如下: + +```c +#include +const double eps = 1e-4; +int main(int argc,char *args[]) { + FILE * f_in = fopen(args[1], "r"); //测试输入 + FILE * f_out = fopen(args[2], "r"); //测试输出 + FILE * f_user = fopen(args[3], "r"); //用户输出 + int ret = 0; //返回值 + int T; + double a, x; + fscanf(f_in, "%d", &T); //从输入中读取数据组数T + while(T--) { + fscanf(f_out , "%lf", &a); + fscanf(f_user, "%lf", &x); + if(fabs(a-x) > eps) { + ret = 1; //Wrong Answer + } + } + fclose(f_in); + fclose(f_out); + fclose(f_user); + return ret; +} +``` \ No newline at end of file diff --git a/docs/Update.md b/docs/Update.md new file mode 100644 index 00000000000..16bcd73b2c0 --- /dev/null +++ b/docs/Update.md @@ -0,0 +1,56 @@ +# 升级 + +### 使用脚本安装的用户 + +可以使用 `install` 目录中的 `update-hustoj` 进行升级。 + +```bash +sudo bash /home/judge/src/install/update-hustoj +``` + +升级脚本执行后,可能需要登陆 web 端管理后台,执行一次更新数据库。 + +### 通过 SVN 方式安装的 + +检出最新 `web` ,复制原 `upload` 目录到新目录,测试后切换 + +```bash +sudo svn checkout https://github.com/zhblue/hustoj/trunk/trunk/web /var/www/new/ +sudo cp -a /var/www/JudgeOnline/include/db_info.inc.php /var/www/new/include/ +sudo cp -a /var/www/JudgeOnline/upload /var/www/new/ +``` + +#### 检出最新 `core` `./make.sh` + +```bash +sudo svn checkout https://github.com/zhblue/hustoj/trunk/trunk/core core +cd core +sudo pkill -9 judged +sudo ./make.sh +sudo judged +``` + +#### 管理员登陆后台,更新数据库 + +访问 `http://原 OJ 地址/new` -> 登陆后台,更新数据库。 -> 测试无误后执行下面命令: + +```bash +sudo mv /var/www/JudgeOnline /var/www/oldOJ +sudo mv /var/www/new /var/www/JudgeOnline +``` + +### 通过 `.tar.gz` 源码安装的 + +1.到安装文件目录找到 `hustoj-read-only` 目录。 + +2.执行命令 + +```bash +sudo svn up hustoj-read-only +cd hustoj-read-only/core +sudo ./make.sh +sudo svn up /var/www/JudgeOnline +``` + + + diff --git a/docs/_coverpage.md b/docs/_coverpage.md new file mode 100644 index 00000000000..1d0180bc917 --- /dev/null +++ b/docs/_coverpage.md @@ -0,0 +1,6 @@ +# HUSTOJ + +> 流行的OJ系统,跨平台、易安装、有题库。 + +[Github](https://github.com/zhblue/hustoj) +[Get Started](/README) diff --git a/docs/_navbar.md b/docs/_navbar.md new file mode 100644 index 00000000000..abd5fa49de9 --- /dev/null +++ b/docs/_navbar.md @@ -0,0 +1,2 @@ +* [项目](https://github.com/zhblue/hustoj) +* 文档 diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000000..78552d84730 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,29 @@ + +- 用户指南 + - [简介](/README) + - [安装](/Install) + - 使用帮助 + - [升级](/Update) + - [备份与还原](/Backup) + - [Special Judge](/SpecialJudge) + - [建立分布式判题系统](/MultiJudge) + - [集成 Moodle](/MoodleIntegration) + - [常见问题](/FAQ) + +- 源码相关 + - [结构简析](/Composition) + - [Core 解析](/Composition-Core) + - [judged 解析](Composition-Core?id=judged-解析) + - [judge_client 解析](/Composition-Core?id=judge_client-解析) + - [sim 简介](/Composition-Core?id=sim-简介) + - [配置文件注释](/Composition-Core?id=配置文件注释) + - [Web 解析](/Composition-web) + - [Core 与 Web 的连接方式解析](/Composition-Client) + - [数据库解析](/Composition-Database) + - [LiveCD 解析](/Composition-LiveCD) + - [FPS 格式解析](/FPS) + +- 关于 + - [关于本项目](/About) + - [贡献者](/Contributors) + diff --git a/docs/images/c2web.jpg b/docs/images/c2web.jpg new file mode 100644 index 00000000000..2dff4a4aa79 Binary files /dev/null and b/docs/images/c2web.jpg differ diff --git a/docs/images/db.jpg b/docs/images/db.jpg new file mode 100644 index 00000000000..6f51efd6993 Binary files /dev/null and b/docs/images/db.jpg differ diff --git a/docs/images/fps.md b/docs/images/fps.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/images/hardware.png b/docs/images/hardware.png new file mode 100644 index 00000000000..06f6ad2ecca Binary files /dev/null and b/docs/images/hardware.png differ diff --git a/docs/images/moodle.png b/docs/images/moodle.png new file mode 100644 index 00000000000..21669dede78 Binary files /dev/null and b/docs/images/moodle.png differ diff --git a/docs/images/spj.jpg b/docs/images/spj.jpg new file mode 100644 index 00000000000..bdbb862f2c4 Binary files /dev/null and b/docs/images/spj.jpg differ diff --git a/docs/images/work.jpg b/docs/images/work.jpg new file mode 100644 index 00000000000..f0206f9773b Binary files /dev/null and b/docs/images/work.jpg differ diff --git a/docs/images/work1.jpg b/docs/images/work1.jpg new file mode 100644 index 00000000000..ff171a34027 Binary files /dev/null and b/docs/images/work1.jpg differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000000..f6a2ee2ec01 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + HUSTOJ 文档 + + + +
加载中...
+ + + + + + + + \ No newline at end of file