瀏覽代碼

Refactor, now able to build via docker compose (powered by dsv4f)

ryp 3 周之前
父節點
當前提交
4710b95f71
共有 100 個文件被更改,包括 9971 次插入 和 176 次删除
  1. 79 0
      .config.local.php
  2. 27 0
      .dockerignore
  3. 37 8
      .gitignore
  4. 0 33
      Dockerfile
  5. 10 0
      README.md
  6. 12 0
      db/Dockerfile
  7. 14 0
      db/add_judger.sql
  8. 793 0
      db/app_uoj233.sql
  9. 48 0
      db/install.sh
  10. 24 0
      docker-compose.seed.yml
  11. 112 0
      docker-compose.yml
  12. 0 116
      install.sh
  13. 47 0
      judger/.dockerignore
  14. 49 0
      judger/Dockerfile
  15. 43 0
      judger/docker-entrypoint.sh
  16. 437 0
      judger/judge_client
  17. 39 0
      judger/register-judger.sh
  18. 1 0
      judger/uoj_judger/.gitignore
  19. 49 0
      judger/uoj_judger/Makefile
  20. 19 0
      judger/uoj_judger/builtin/checker/acmp.cpp
  21. 55 0
      judger/uoj_judger/builtin/checker/bcmp.cpp
  22. 95 0
      judger/uoj_judger/builtin/checker/caseicmp.cpp
  23. 124 0
      judger/uoj_judger/builtin/checker/casencmp.cpp
  24. 113 0
      judger/uoj_judger/builtin/checker/casewcmp.cpp
  25. 19 0
      judger/uoj_judger/builtin/checker/dcmp.cpp
  26. 36 0
      judger/uoj_judger/builtin/checker/fcmp.cpp
  27. 35 0
      judger/uoj_judger/builtin/checker/hcmp.cpp
  28. 16 0
      judger/uoj_judger/builtin/checker/icmp.cpp
  29. 54 0
      judger/uoj_judger/builtin/checker/lcmp.cpp
  30. 57 0
      judger/uoj_judger/builtin/checker/ncmp.cpp
  31. 19 0
      judger/uoj_judger/builtin/checker/rcmp.cpp
  32. 32 0
      judger/uoj_judger/builtin/checker/rcmp4.cpp
  33. 32 0
      judger/uoj_judger/builtin/checker/rcmp6.cpp
  34. 32 0
      judger/uoj_judger/builtin/checker/rcmp9.cpp
  35. 24 0
      judger/uoj_judger/builtin/checker/rncmp.cpp
  36. 52 0
      judger/uoj_judger/builtin/checker/uncmp.cpp
  37. 38 0
      judger/uoj_judger/builtin/checker/wcmp.cpp
  38. 27 0
      judger/uoj_judger/builtin/checker/yesno.cpp
  39. 265 0
      judger/uoj_judger/builtin/judger/judger.cpp
  40. 1 0
      judger/uoj_judger/data
  41. 3499 0
      judger/uoj_judger/include/testlib.h
  42. 38 0
      judger/uoj_judger/include/uoj_env.h
  43. 1590 0
      judger/uoj_judger/include/uoj_judger.h
  44. 4 0
      judger/uoj_judger/include/uoj_work_path.h
  45. 22 0
      judger/uoj_judger/main_judger.cpp
  46. 55 0
      judger/uoj_judger/run/formatter.cpp
  47. 263 0
      judger/uoj_judger/run/run_interaction.cpp
  48. 584 0
      judger/uoj_judger/run/run_program.cpp
  49. 795 0
      judger/uoj_judger/run/run_program_conf.h
  50. 二進制
      upload/article-1.webp
  51. 二進制
      upload/article-2.webp
  52. 二進制
      upload/article-3.jpg
  53. 二進制
      upload/statement395.pdf
  54. 二進制
      upload/statement396.pdf
  55. 二進制
      upload/statement404.pdf
  56. 二進制
      upload/statement405.pdf
  57. 二進制
      upload/statement406.pdf
  58. 二進制
      upload/statement407.pdf
  59. 0 0
      web/.htaccess
  60. 0 0
      web/.php-cs-fixer.php
  61. 114 0
      web/Dockerfile
  62. 0 0
      web/app/.default-config.php
  63. 0 0
      web/app/.htaccess
  64. 0 0
      web/app/cli.php
  65. 0 0
      web/app/controllers/add_contest.php
  66. 0 0
      web/app/controllers/announcements.php
  67. 0 0
      web/app/controllers/blog_show.php
  68. 0 0
      web/app/controllers/blogs.php
  69. 1 0
      web/app/controllers/certs_manage.php
  70. 0 0
      web/app/controllers/change_user_info.php
  71. 1 0
      web/app/controllers/click_zan.php
  72. 0 0
      web/app/controllers/cloud.php
  73. 0 0
      web/app/controllers/contest_inside.php
  74. 0 0
      web/app/controllers/contest_manage.php
  75. 0 0
      web/app/controllers/contest_members.php
  76. 0 0
      web/app/controllers/contest_registration.php
  77. 0 0
      web/app/controllers/contests.php
  78. 0 0
      web/app/controllers/datum.php
  79. 0 0
      web/app/controllers/datums.php
  80. 0 0
      web/app/controllers/download.php
  81. 0 0
      web/app/controllers/export_contest_standings_table.php
  82. 0 0
      web/app/controllers/faq.php
  83. 0 0
      web/app/controllers/forgot_pw.php
  84. 1 1
      web/app/controllers/group_info.php
  85. 0 0
      web/app/controllers/group_manage.php
  86. 1 1
      web/app/controllers/groups_manage.php
  87. 0 0
      web/app/controllers/hack.php
  88. 0 0
      web/app/controllers/hack_list.php
  89. 3 3
      web/app/controllers/index.php
  90. 0 0
      web/app/controllers/judge/download.php
  91. 29 9
      web/app/controllers/judge/submit.php
  92. 0 0
      web/app/controllers/judge/sync_judge_client.php
  93. 0 0
      web/app/controllers/login.php
  94. 0 0
      web/app/controllers/logout.php
  95. 0 0
      web/app/controllers/map_visualizer.php
  96. 0 0
      web/app/controllers/paste_post.php
  97. 0 0
      web/app/controllers/paste_view.php
  98. 3 3
      web/app/controllers/problem.php
  99. 1 1
      web/app/controllers/problem_data_manage.php
  100. 1 1
      web/app/controllers/problem_data_manage_back.php

+ 79 - 0
.config.local.php

@@ -0,0 +1,79 @@
+<?php
+return array (
+  'profile' => 
+  array (
+    'oj-name' => 'Universal Online Judge',
+    'oj-name-short' => 'UOJ',
+    'administrator' => 'root',
+    'admin-email' => 'admin@local_uoj.ac',
+    'QQ-group' => '',
+    'ICP-license' => '',
+  ),
+  'database' => 
+  array (
+    'database' => 'app_uoj233',
+    'username' => 'root',
+    'password' => 'root',
+    'host' => 'uoj-db',
+  ),
+  'web' => 
+  array (
+    'domain' => NULL,
+    'main' => 
+    array (
+      'protocol' => 'http',
+      'host' => UOJContext::httpHost(),
+      'port' => 80,
+    ),
+    'blog' => 
+    array (
+      'protocol' => 'http',
+      'host' => UOJContext::httpHost(),
+      'port' => 80,
+    ),
+  ),
+  'security' => 
+  array (
+    'user' => 
+    array (
+      'client_salt' => '_salt_0_',
+    ),
+    'cookie' => 
+    array (
+      'checksum_salt' => 
+      array (
+        0 => '_salt_1_',
+        1 => '_salt_2_',
+        2 => '_salt_3_',
+      ),
+    ),
+  ),
+  'mail' => 
+  array (
+    'noreply' => 
+    array (
+      'username' => 'noreply@local_uoj.ac',
+      'password' => '_mail_noreply_password_',
+      'host' => 'smtp.local_uoj.ac',
+      'secure' => 'tls',
+      'port' => 587,
+    ),
+  ),
+  'judger' => 
+  array (
+    'socket' => 
+    array (
+      'port' => '2333',
+      'password' => '_judger_socket_password_',
+    ),
+  ),
+  'switch' => 
+  array (
+    'web-analytics' => false,
+    'blog-domain-mode' => 3,
+  ),
+  'tools' => 
+  array (
+    'map-copy-enabled' => false,
+  ),
+);

+ 27 - 0
.dockerignore

@@ -0,0 +1,27 @@
+# This is the .dockerignore for the REPOSITORY ROOT context, used by
+# web/Dockerfile (see docker-compose.yml).  db/ and judger/ have their own
+# contexts and their own .dockerignore files.
+#
+# The whole point here is to keep the huge, private, runtime-only stuff out of
+# the build context.
+
+.git
+.gitignore
+
+# runtime state
+/uoj_data/
+
+# database dumps: 100+ MB and private (real user data)
+/wyoj-*.sql
+/backup*.sql
+/backup.sql
+
+# scratch
+/.tmp-build/
+
+# docs
+/DEPLOY.md
+
+# generated at runtime inside the web tree
+/web/app/.config.php
+/web/app/storage

+ 37 - 8
.gitignore

@@ -1,10 +1,39 @@
-v8js.zip
+# --- editor / OS ---
 .idea
 .php_cs.cache
-app/.config.php
-app/storage/tmp/*
-app/storage/submission/*
-app/storage/paste/*
-!app/storage/tmp/.gitkeep
-!app/storage/submission/.gitkeep
-!app/storage/paste/.gitkeep
+*.swp
+*.swo
+*~
+
+# --- deployment state (never commit) ---
+/uoj_data/
+
+# --- database dumps: real user data, keep them local ---
+/wyoj-*.sql
+/backup*.sql
+/backup.sql
+
+# scratch space used while regenerating the schema
+/.tmp-build/
+
+# patches produced for other deployments
+/*.patch
+
+# --- generated at runtime ---
+/web/app/.config.php
+/web/app/storage/tmp/*
+/web/app/storage/submission/*
+/web/app/storage/paste/*
+
+# --- judger: runtime state and build output ---
+/judger/.conf.json
+/judger/log/
+/judger/uoj_judger/work/
+/judger/uoj_judger/result/
+/judger/uoj_judger/main_judger
+/judger/uoj_judger/run/run_program
+/judger/uoj_judger/run/formatter
+/judger/uoj_judger/run/run_interaction
+/judger/uoj_judger/builtin/judger/judger
+/judger/uoj_judger/builtin/checker/*
+!/judger/uoj_judger/builtin/checker/*.cpp

+ 0 - 33
Dockerfile

@@ -1,33 +0,0 @@
-FROM ubuntu:20.04
-MAINTAINER Baoshuo <i@baoshuo.ren>
-LABEL org.opencontainers.image.source=https://github.com/UniversalOJ/UOJ-System
-LABEL org.opencontainers.image.description="UOJ Web"
-LABEL org.opencontainers.image.licenses=MIT
-
-ARG CLONE_ADDFLAG
-
-ENV DEBIAN_FRONTEND=noninteractive
-COPY v8js.zip /tmp/pear/download/
-RUN dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg) &&\
-echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF &&\
-apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev &&\
-yes | pecl install yaml && export OJ_PUSHD=`pwd` && cd /tmp/pear/download &&\
-unzip v8js.zip && cd v8js &&\
-phpize && ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && make install && cd $OJ_PUSHD && unset OJ_PUSHD
-
-ADD . /opt/uoj
-WORKDIR /opt/uoj
-
-# Install environment and set startup script
-RUN sh web/install.sh -p && echo "\
-#!/bin/sh\n\
-if [ ! -f \"/var/uoj_data/.UOJSetupDone\" ]; then\n\
-  cd /opt/uoj/web && sh install.sh -i\n\
-fi\n\
-service ntp start\n\
-service apache2 start\n\
-exec bash\n" >/opt/up && chmod +x /opt/up
-
-ENV LANG=C.UTF-8 TZ=Asia/Shanghai
-EXPOSE 80
-CMD /opt/up

+ 10 - 0
README.md

@@ -0,0 +1,10 @@
+这是 WyOJ 的完整仓库。
+
+部署方式很简单,使用 `docker compose build` 与 `docker compose up -d` 就可以了。
+
+有可能会有大量需要微调的修改,这是因为我们只针对 WyOJ 的特性进行了调整。
+
+因为不会有多少人部署,所以不再特殊修复。
+
+如果有人遇到了相关的问题,请联系 ryp 2681243014 (Ruiyang Peng)
+

+ 12 - 0
db/Dockerfile

@@ -0,0 +1,12 @@
+FROM mysql:5.7
+
+LABEL org.opencontainers.image.source=https://git.ryp.org.cn/wyoj/wyoj-raw
+LABEL org.opencontainers.image.description="WyOJ Database"
+LABEL org.opencontainers.image.licenses=MIT
+
+ADD . /opt/uoj_db
+WORKDIR /opt/uoj_db
+
+RUN bash install.sh
+
+ENV LANG=C.UTF-8 TZ=Asia/Shanghai

+ 14 - 0
db/add_judger.sql

@@ -0,0 +1,14 @@
+USE `app_uoj233`;
+
+-- Registers the compose judger.  Upsert, so re-running is harmless, and
+-- ./judger/register-judger.sh can be used later to change the password/ip
+-- without rebuilding this image.
+--
+-- judger_name must match JUDGER_NAME in docker-compose.yml, and ip must be
+-- reachable from the web container (`uoj-judger` is the compose service name)
+-- because /judge/sync-judge-client dials it on judger.socket.port.
+--
+-- _judger_password_ is a placeholder published in this repository: change it.
+insert into judger_info (judger_name, password, ip)
+values ('main_judger', '_judger_password_', 'uoj-judger')
+on duplicate key update password = values(password), ip = values(ip);

+ 793 - 0
db/app_uoj233.sql

@@ -0,0 +1,793 @@
+-- MySQL dump 10.13  Distrib 5.7.44, for Linux (x86_64)
+--
+-- Host: localhost    Database: app_uoj233
+-- ------------------------------------------------------
+-- Server version	5.7.44
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Current Database: `app_uoj233`
+--
+
+CREATE DATABASE /*!32312 IF NOT EXISTS*/ `app_uoj233` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
+
+USE `app_uoj233`;
+
+--
+-- Table structure for table `best_ac_submissions`
+--
+
+DROP TABLE IF EXISTS `best_ac_submissions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `best_ac_submissions` (
+  `problem_id` int(11) NOT NULL,
+  `submitter` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `submission_id` int(11) NOT NULL,
+  `used_time` int(11) NOT NULL,
+  `used_memory` int(11) NOT NULL,
+  `tot_size` int(11) NOT NULL,
+  `shortest_id` int(11) NOT NULL,
+  `shortest_used_time` int(11) NOT NULL,
+  `shortest_used_memory` int(11) NOT NULL,
+  `shortest_tot_size` int(11) NOT NULL,
+  PRIMARY KEY (`problem_id`,`submitter`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `best_ac_submissions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `best_ac_submissions` ENABLE KEYS */;
+
+--
+-- Table structure for table `blogs`
+--
+
+DROP TABLE IF EXISTS `blogs`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `blogs` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `title` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `content` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
+  `post_time` datetime NOT NULL,
+  `poster` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `content_md` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
+  `zan` int(11) NOT NULL,
+  `is_hidden` tinyint(1) NOT NULL,
+  `type` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'B',
+  `is_draft` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `blogs` DISABLE KEYS */;
+/*!40000 ALTER TABLE `blogs` ENABLE KEYS */;
+
+--
+-- Table structure for table `blogs_comments`
+--
+
+DROP TABLE IF EXISTS `blogs_comments`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `blogs_comments` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `blog_id` int(11) NOT NULL,
+  `content` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `post_time` datetime NOT NULL,
+  `poster` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `zan` int(11) NOT NULL,
+  `reply_id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `blogs_comments` DISABLE KEYS */;
+/*!40000 ALTER TABLE `blogs_comments` ENABLE KEYS */;
+
+--
+-- Table structure for table `blogs_tags`
+--
+
+DROP TABLE IF EXISTS `blogs_tags`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `blogs_tags` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `blog_id` int(11) NOT NULL,
+  `tag` varchar(30) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `blog_id` (`blog_id`),
+  KEY `tag` (`tag`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `blogs_tags` DISABLE KEYS */;
+/*!40000 ALTER TABLE `blogs_tags` ENABLE KEYS */;
+
+--
+-- Table structure for table `click_zans`
+--
+
+DROP TABLE IF EXISTS `click_zans`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `click_zans` (
+  `type` char(2) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `username` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `target_id` int(11) NOT NULL,
+  `val` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`type`,`target_id`,`username`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `click_zans` DISABLE KEYS */;
+/*!40000 ALTER TABLE `click_zans` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests`
+--
+
+DROP TABLE IF EXISTS `contests`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  `start_time` datetime NOT NULL,
+  `last_min` int(11) NOT NULL,
+  `player_num` int(11) NOT NULL,
+  `status` varchar(50) NOT NULL,
+  `extra_config` varchar(500) DEFAULT NULL,
+  `zan` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests_asks`
+--
+
+DROP TABLE IF EXISTS `contests_asks`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests_asks` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `contest_id` int(11) NOT NULL,
+  `username` varchar(20) NOT NULL,
+  `question` text NOT NULL,
+  `answer` text NOT NULL,
+  `post_time` datetime NOT NULL,
+  `reply_time` datetime NOT NULL,
+  `is_hidden` tinyint(1) DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests_asks` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests_asks` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests_notice`
+--
+
+DROP TABLE IF EXISTS `contests_notice`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests_notice` (
+  `contest_id` int(11) NOT NULL,
+  `title` varchar(30) NOT NULL,
+  `content` varchar(500) NOT NULL,
+  `time` datetime NOT NULL,
+  KEY `contest_id` (`contest_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests_notice` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests_notice` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests_permissions`
+--
+
+DROP TABLE IF EXISTS `contests_permissions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests_permissions` (
+  `username` varchar(20) NOT NULL,
+  `contest_id` int(11) NOT NULL,
+  PRIMARY KEY (`username`,`contest_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests_permissions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests_permissions` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests_problems`
+--
+
+DROP TABLE IF EXISTS `contests_problems`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests_problems` (
+  `problem_id` int(11) NOT NULL,
+  `contest_id` int(11) NOT NULL,
+  PRIMARY KEY (`problem_id`,`contest_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests_problems` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests_problems` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests_registrants`
+--
+
+DROP TABLE IF EXISTS `contests_registrants`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests_registrants` (
+  `username` varchar(20) NOT NULL,
+  `user_rating` int(11) NOT NULL,
+  `contest_id` int(11) NOT NULL,
+  `has_participated` tinyint(1) NOT NULL,
+  `rank` int(11) NOT NULL,
+  `user_rd` double DEFAULT NULL,
+  `user_vol` double DEFAULT NULL,
+  PRIMARY KEY (`contest_id`,`username`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests_registrants` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests_registrants` ENABLE KEYS */;
+
+--
+-- Table structure for table `contests_submissions`
+--
+
+DROP TABLE IF EXISTS `contests_submissions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests_submissions` (
+  `contest_id` int(11) NOT NULL,
+  `submitter` varchar(20) NOT NULL,
+  `problem_id` int(11) NOT NULL,
+  `submission_id` int(11) NOT NULL,
+  `score` int(11) NOT NULL,
+  `penalty` int(11) NOT NULL,
+  PRIMARY KEY (`contest_id`,`submitter`,`problem_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `contests_submissions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `contests_submissions` ENABLE KEYS */;
+
+--
+-- Table structure for table `custom_test_submissions`
+--
+
+DROP TABLE IF EXISTS `custom_test_submissions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `custom_test_submissions` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `problem_id` int(10) unsigned NOT NULL,
+  `submit_time` datetime NOT NULL,
+  `submitter` varchar(20) NOT NULL,
+  `content` text NOT NULL,
+  `judge_time` datetime DEFAULT NULL,
+  `result` blob NOT NULL,
+  `status` varchar(20) NOT NULL,
+  `status_details` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `custom_test_submissions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `custom_test_submissions` ENABLE KEYS */;
+
+--
+-- Table structure for table `datum_requests`
+--
+
+DROP TABLE IF EXISTS `datum_requests`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `datum_requests` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `pid` int(10) unsigned NOT NULL,
+  `request_time` datetime NOT NULL,
+  `finish_time` datetime DEFAULT NULL,
+  `std` mediumtext COLLATE utf8mb4_unicode_ci,
+  `timelimit` int(11) DEFAULT NULL,
+  `memlimit` int(11) DEFAULT NULL,
+  `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `generator` mediumtext COLLATE utf8mb4_unicode_ci,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `datum_requests` DISABLE KEYS */;
+/*!40000 ALTER TABLE `datum_requests` ENABLE KEYS */;
+
+--
+-- Table structure for table `datum_streams`
+--
+
+DROP TABLE IF EXISTS `datum_streams`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `datum_streams` (
+  `id` int(11) NOT NULL,
+  `stream` mediumtext COLLATE utf8mb4_unicode_ci,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `id` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `datum_streams` DISABLE KEYS */;
+/*!40000 ALTER TABLE `datum_streams` ENABLE KEYS */;
+
+--
+-- Table structure for table `hacks`
+--
+
+DROP TABLE IF EXISTS `hacks`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `hacks` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `problem_id` int(10) unsigned NOT NULL,
+  `contest_id` int(10) unsigned DEFAULT NULL,
+  `submission_id` int(10) unsigned NOT NULL,
+  `hacker` varchar(20) NOT NULL,
+  `owner` varchar(20) NOT NULL,
+  `input` varchar(150) NOT NULL,
+  `input_type` char(20) NOT NULL,
+  `submit_time` datetime NOT NULL,
+  `judge_time` datetime DEFAULT NULL,
+  `success` tinyint(1) DEFAULT NULL,
+  `details` blob NOT NULL,
+  `is_hidden` tinyint(1) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `submission_id` (`submission_id`),
+  KEY `is_hidden` (`is_hidden`,`problem_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `hacks` DISABLE KEYS */;
+/*!40000 ALTER TABLE `hacks` ENABLE KEYS */;
+
+--
+-- Table structure for table `important_blogs`
+--
+
+DROP TABLE IF EXISTS `important_blogs`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `important_blogs` (
+  `blog_id` int(11) NOT NULL,
+  `level` int(11) NOT NULL,
+  PRIMARY KEY (`blog_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `important_blogs` DISABLE KEYS */;
+/*!40000 ALTER TABLE `important_blogs` ENABLE KEYS */;
+
+--
+-- Table structure for table `judger_info`
+--
+
+DROP TABLE IF EXISTS `judger_info`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `judger_info` (
+  `judger_name` varchar(50) NOT NULL,
+  `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+  `ip` char(20) NOT NULL,
+  PRIMARY KEY (`judger_name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `judger_info` DISABLE KEYS */;
+/*!40000 ALTER TABLE `judger_info` ENABLE KEYS */;
+
+--
+-- Table structure for table `pastes`
+--
+
+DROP TABLE IF EXISTS `pastes`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `pastes` (
+  `index` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `creator` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `created_at` datetime DEFAULT NULL,
+  `content` text COLLATE utf8mb4_unicode_ci,
+  PRIMARY KEY (`index`),
+  UNIQUE KEY `pastes_index_uindex` (`index`),
+  KEY `pastes_created_at_index` (`created_at`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `pastes` DISABLE KEYS */;
+/*!40000 ALTER TABLE `pastes` ENABLE KEYS */;
+
+--
+-- Table structure for table `problems`
+--
+
+DROP TABLE IF EXISTS `problems`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `problems` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `title` text NOT NULL,
+  `is_hidden` tinyint(1) NOT NULL DEFAULT '0',
+  `submission_requirement` text,
+  `hackable` tinyint(1) NOT NULL DEFAULT '0',
+  `extra_config` varchar(500) NOT NULL DEFAULT '{"view_content_type":"ALL","view_details_type":"ALL"}',
+  `zan` int(11) NOT NULL,
+  `ac_num` int(11) NOT NULL DEFAULT '0',
+  `submit_num` int(11) NOT NULL DEFAULT '0',
+  `ctrl` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `problems` DISABLE KEYS */;
+/*!40000 ALTER TABLE `problems` ENABLE KEYS */;
+
+--
+-- Table structure for table `problems_contents`
+--
+
+DROP TABLE IF EXISTS `problems_contents`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `problems_contents` (
+  `id` int(11) NOT NULL,
+  `statement` mediumtext NOT NULL,
+  `statement_md` mediumtext NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `problems_contents` DISABLE KEYS */;
+/*!40000 ALTER TABLE `problems_contents` ENABLE KEYS */;
+
+--
+-- Table structure for table `problems_permissions`
+--
+
+DROP TABLE IF EXISTS `problems_permissions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `problems_permissions` (
+  `username` varchar(20) NOT NULL,
+  `problem_id` int(11) NOT NULL,
+  PRIMARY KEY (`username`,`problem_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `problems_permissions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `problems_permissions` ENABLE KEYS */;
+
+--
+-- Table structure for table `problems_tags`
+--
+
+DROP TABLE IF EXISTS `problems_tags`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `problems_tags` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `problem_id` int(11) NOT NULL,
+  `tag` varchar(30) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `problem_id` (`problem_id`),
+  KEY `tag` (`tag`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `problems_tags` DISABLE KEYS */;
+/*!40000 ALTER TABLE `problems_tags` ENABLE KEYS */;
+
+--
+-- Table structure for table `rp`
+--
+
+DROP TABLE IF EXISTS `rp`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rp` (
+  `pid` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `title` text COLLATE utf8mb4_unicode_ci NOT NULL,
+  `statement` text COLLATE utf8mb4_unicode_ci,
+  PRIMARY KEY (`pid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `rp` DISABLE KEYS */;
+/*!40000 ALTER TABLE `rp` ENABLE KEYS */;
+
+--
+-- Table structure for table `search_requests`
+--
+
+DROP TABLE IF EXISTS `search_requests`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `search_requests` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `created_at` datetime NOT NULL,
+  `remote_addr` varchar(50) NOT NULL,
+  `type` enum('search','autocomplete') NOT NULL,
+  `cache_id` int(11) NOT NULL,
+  `q` varchar(100) NOT NULL,
+  `content` text NOT NULL,
+  `result` mediumtext NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `remote_addr` (`remote_addr`,`created_at`),
+  KEY `created_at` (`created_at`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `search_requests` DISABLE KEYS */;
+/*!40000 ALTER TABLE `search_requests` ENABLE KEYS */;
+
+--
+-- Table structure for table `songs`
+--
+
+DROP TABLE IF EXISTS `songs`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `songs` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `writer` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `album` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `cover` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `click` varchar(256) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  `user` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `songs` DISABLE KEYS */;
+/*!40000 ALTER TABLE `songs` ENABLE KEYS */;
+
+--
+-- Table structure for table `submissions`
+--
+
+DROP TABLE IF EXISTS `submissions`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `submissions` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `problem_id` int(10) unsigned NOT NULL,
+  `contest_id` int(10) unsigned DEFAULT NULL,
+  `submit_time` datetime NOT NULL,
+  `submitter` varchar(20) NOT NULL,
+  `content` text NOT NULL,
+  `language` varchar(15) NOT NULL,
+  `tot_size` int(11) NOT NULL,
+  `judge_time` datetime DEFAULT NULL,
+  `result` blob NOT NULL,
+  `status` varchar(20) NOT NULL,
+  `result_error` varchar(20) DEFAULT NULL,
+  `score` int(11) DEFAULT NULL,
+  `used_time` int(11) NOT NULL DEFAULT '0',
+  `used_memory` int(11) NOT NULL DEFAULT '0',
+  `is_hidden` tinyint(1) NOT NULL,
+  `status_details` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `is_hidden` (`is_hidden`,`problem_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `submissions` DISABLE KEYS */;
+/*!40000 ALTER TABLE `submissions` ENABLE KEYS */;
+
+--
+-- Table structure for table `user_info`
+--
+
+DROP TABLE IF EXISTS `user_info`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_info` (
+  `usergroup` char(1) NOT NULL DEFAULT 'U',
+  `username` varchar(20) NOT NULL,
+  `email` varchar(50) NOT NULL,
+  `password` char(32) NOT NULL,
+  `svn_password` char(10) NOT NULL,
+  `rating` int(11) NOT NULL DEFAULT '1500',
+  `qq` bigint(20) NOT NULL,
+  `sex` char(1) NOT NULL DEFAULT 'U',
+  `ac_num` int(11) NOT NULL,
+  `register_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `remote_addr` varchar(50) NOT NULL,
+  `http_x_forwarded_for` varchar(50) NOT NULL,
+  `remember_token` char(60) NOT NULL,
+  `motto` varchar(200) NOT NULL,
+  `extgroups` varchar(128) DEFAULT NULL,
+  `privs` text,
+  `ctrls` varchar(128) DEFAULT NULL,
+  `childgroup` varchar(20) DEFAULT NULL,
+  `certs` varchar(64) DEFAULT NULL,
+  `certslist` varchar(255) DEFAULT NULL,
+  `aboutme_blogid` int(11) DEFAULT NULL,
+  `luogu_clientid` char(60) DEFAULT NULL,
+  `luogu_uid` int(11) DEFAULT NULL,
+  `rating_rd` double DEFAULT NULL,
+  `rating_vol` double DEFAULT NULL,
+  `bgurl` text,
+  PRIMARY KEY (`username`),
+  KEY `rating` (`rating`,`username`),
+  KEY `ac_num` (`ac_num`,`username`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `user_info` DISABLE KEYS */;
+/*!40000 ALTER TABLE `user_info` ENABLE KEYS */;
+
+--
+-- Table structure for table `user_msg`
+--
+
+DROP TABLE IF EXISTS `user_msg`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_msg` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `sender` varchar(20) NOT NULL,
+  `receiver` varchar(20) NOT NULL,
+  `message` varchar(5000) NOT NULL,
+  `send_time` datetime NOT NULL,
+  `read_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `user_msg` DISABLE KEYS */;
+/*!40000 ALTER TABLE `user_msg` ENABLE KEYS */;
+
+--
+-- Table structure for table `user_system_msg`
+--
+
+DROP TABLE IF EXISTS `user_system_msg`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_system_msg` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `content` varchar(300) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `receiver` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
+  `send_time` datetime NOT NULL,
+  `read_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+--
+
+/*!40000 ALTER TABLE `user_system_msg` DISABLE KEYS */;
+/*!40000 ALTER TABLE `user_system_msg` ENABLE KEYS */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2026-09-24 19:40:11

+ 48 - 0
db/install.sh

@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# uoj-db image preparation.  Runs at *build* time and only stages files for
+# the mysql entrypoint, which executes /docker-entrypoint-initdb.d/* on the
+# first start (i.e. when ./uoj_data/db/mysql is empty).
+set -eu
+
+setLAMPConf(){
+    printf "\n\n==> Setting mysqld config\n"
+    # NOTE: the previous version wrote `init_connect` twice; mysqld keeps only
+    # the last occurrence, so the connection charset was never applied.
+    cat >/etc/mysql/conf.d/uoj_mysqld.cnf <<UOJEOF
+[mysqld]
+default-time-zone='+8:00'
+character-set-server=utf8mb4
+collation-server=utf8mb4_unicode_ci
+init_connect='SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'
+skip-character-set-client-handshake
+sql-mode=ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
+UOJEOF
+}
+
+setWebConf(){
+    printf "\n\n==> Staging database init scripts\n"
+
+    cat >/docker-entrypoint-initdb.d/000-native_password.sql <<UOJEOF
+ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';
+UOJEOF
+
+    for f in app_uoj233.sql add_judger.sql; do
+        if [ ! -s "$f" ]; then
+            echo "ERROR: $f is missing or empty" >&2
+            exit 1
+        fi
+    done
+
+    # 001 is the current schema (30 tables, includes the WyOJ additions:
+    # songs / pastes / rp / datum_* and the extra user_info columns).
+    # See ../docker-compose.seed.yml to import a full dump instead.
+    cp app_uoj233.sql /docker-entrypoint-initdb.d/001-app_uoj233.sql
+    cp add_judger.sql /docker-entrypoint-initdb.d/002-add_judger.sql
+
+    ls -l /docker-entrypoint-initdb.d/
+}
+
+echo 'Preparing UOJ System db environment...'
+setLAMPConf
+setWebConf

+ 24 - 0
docker-compose.seed.yml

@@ -0,0 +1,24 @@
+# Optional overlay: seed the database with a mysqldump instead of starting
+# from the empty schema shipped in ./db/app_uoj233.sql.
+#
+#   docker compose -f docker-compose.yml -f docker-compose.seed.yml up -d --build
+#
+# IMPORTANT
+#   * Init scripts only run when the mysql datadir is empty.  To re-seed:
+#         docker compose down
+#         rm -rf ./uoj_data/db/mysql
+#         docker compose -f docker-compose.yml -f docker-compose.seed.yml up -d
+#   * The dump contains real user data (emails, QQ numbers, password hashes,
+#     private messages).  It is gitignored on purpose -- do not commit it, and
+#     think twice before putting a seeded instance on a public network.
+#   * The dump's own DROP/CREATE statements replace the schema from
+#     001-app_uoj233.sql, which also wipes the judger row inserted by
+#     002-add_judger.sql -- hence the extra 004 mount below.
+#   * Adjust the source path if your dump has another name.
+version: '3'
+
+services:
+  uoj-db:
+    volumes:
+      - ./wyoj-0924.sql:/docker-entrypoint-initdb.d/003-wyoj-0924.sql:ro
+      - ./db/add_judger.sql:/docker-entrypoint-initdb.d/004-add_judger.sql:ro

+ 112 - 0
docker-compose.yml

@@ -0,0 +1,112 @@
+# WyOJ -- one-command deployment.
+#
+#   docker compose up -d --build          # fresh install (creates ./uoj_data)
+#   ./judger/register-judger.sh           # register the judger in judger_info
+#   docker compose -f docker-compose.yml -f docker-compose.seed.yml up -d
+#                                         # ... and seed the bundled production dump
+#   docker compose logs -f uoj-web        # apache + install output
+#   docker compose logs -f uoj-judger     # judge_client output
+#   docker exec -it uoj-web bash          # shell inside the web container
+#
+# Layout:
+#   ./db/     uoj-db image: MySQL 5.7 + the current schema (app_uoj233.sql)
+#   ./web/    uoj-web image: apache + php 7.4 + the WyOJ sources (+ judger tree)
+#   ./judger/ uoj-judger image: python judge_client + the C++ sandbox
+#   ./uoj_data/  runtime state (mysql datadir, /var/uoj_data, app/storage)
+#
+# `version` is kept so this still parses with the legacy python docker-compose;
+# service readiness is handled by install.sh's own wait loop, not by
+# depends_on conditions, for the same reason.
+#
+# SECURITY: the three secrets below are placeholders that are published in this
+# repository.  Change JUDGER_PASSWORD / JUDGER_SOCKET_PASSWORD / the database
+# password together (and re-run ./judger/register-judger.sh) before putting this
+# anywhere public.
+version: '3'
+
+services:
+  uoj-db:
+    image: uoj-db:latest
+    build:
+      context: ./db/
+      dockerfile: Dockerfile
+    container_name: uoj-db
+    restart: always
+    volumes:
+      - ./uoj_data/db/mysql:/var/lib/mysql
+    environment:
+      - MYSQL_DATABASE=app_uoj233
+      - MYSQL_ROOT_PASSWORD=root
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
+      interval: 10s
+      timeout: 5s
+      retries: 12
+      start_period: 30s
+
+  uoj-web:
+    image: uoj-web:latest
+    build:
+      # root context: the web image also needs ./judger (see web/Dockerfile)
+      context: ./
+      dockerfile: web/Dockerfile
+    container_name: uoj-web
+    restart: always
+    depends_on:
+      - uoj-db
+    volumes:
+      # uploads and .UOJSetupDone -- must persist across recreates
+      - ./uoj_data/web/data:/var/uoj_data
+      # submissions / pastes / tmp / judge_client.zip
+      - ./uoj_data/web/storage:/opt/uoj/web/app/storage
+      # generated on first boot from the environment below; kept on the host so
+      # that rebuilding the image does not lose it
+      - type: bind
+        source: ./.config.local.php
+        target: /opt/uoj/web/app/.config.php
+    ports:
+      - "80:80"
+      # 3690 was published by the original file but nothing listens on it.
+      # - "3690:3690"
+    environment:
+      - DATABASE_HOST=uoj-db
+      - DATABASE_NAME=app_uoj233
+      - DATABASE_USER=root
+      - DATABASE_PASSWORD=root
+      - JUDGER_SOCKET_PORT=2333
+      - JUDGER_SOCKET_PASSWORD=_judger_socket_password_
+      - SALT_0=_salt_0_
+      - SALT_1=_salt_1_
+      - SALT_2=_salt_2_
+      - SALT_3=_salt_3_
+      - UOJ_PROTOCOL=http
+
+  uoj-judger:
+    image: uoj-judger:latest
+    build:
+      context: ./judger/
+      dockerfile: Dockerfile
+    container_name: uoj-judger
+    restart: always
+    depends_on:
+      - uoj-web
+    # run_program.cpp drives the sandbox with ptrace(); without SYS_PTRACE the
+    # first submission fails with "Judgement Failed".  If the sandbox still
+    # misbehaves under docker's default seccomp profile, add:
+    #   security_opt: ["seccomp:unconfined"]
+    cap_add:
+      - SYS_PTRACE
+    volumes:
+      # main_judger reads problem data directly instead of downloading it
+      # (judge_client skips update_problem_data for judger_name == main_judger),
+      # so share the web's data directory with it.
+      - ./uoj_data/web/data:/var/uoj_data_copy:ro
+    environment:
+      - UOJ_PROTOCOL=http
+      - UOJ_HOST=uoj-web
+      - JUDGER_NAME=main_judger
+      - JUDGER_PASSWORD=_judger_password_
+      - JUDGER_SOCKET_PORT=2333
+      - JUDGER_SOCKET_PASSWORD=_judger_socket_password_
+    # No ports: the web reaches this container over the compose network via the
+    # service name (`uoj-judger`), which is also what judger_info.ip must hold.

+ 0 - 116
install.sh

@@ -1,116 +0,0 @@
-#!/bin/bash
-genRandStr(){
-    cat /dev/urandom | tr -dc [:alnum:] | head -c $1
-}
-#Set some vars
-_judger_socket_port_="${JUDGER_SOCKET_PORT:-2333}"
-_uoj_protocol_="${UOJ_PROTOCOL:-http}"
-
-getAptPackage(){
-    printf "\n\n==> Getting environment packages\n"
-    # Update apt sources and install
-    export DEBIAN_FRONTEND=noninteractive
-    dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg)
-    echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF
-    apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev
-    # Install PHP extensions
-    yes | pecl install yaml
-    git clone https://github.com/phpv8/v8js.git --depth=1 -b 4c026f3fb291797c109adcabda6aeba6491fe44f /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master
-    phpize && ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && make install && cd -
-}
-
-setLAMPConf(){
-    printf "\n\n==> Setting LAMP configs\n"
-    #Set Apache UOJ site conf
-    cat >/etc/apache2/sites-available/000-uoj.conf <<UOJEOF
-<VirtualHost *:80>
-    #ServerName local_uoj.ac
-    ServerAdmin opensource@uoj.ac
-    DocumentRoot /var/www/uoj
-
-    SetEnvIf Request_URI "^/judge/.*$" judgelog
-    #LogLevel info ssl:warn
-    ErrorLog \${APACHE_LOG_DIR}/uoj_error.log
-    CustomLog \${APACHE_LOG_DIR}/uoj_judge.log common env=judgelog
-    CustomLog \${APACHE_LOG_DIR}/uoj_access.log combined env=!judgelog
-
-    XSendFile On
-    XSendFilePath /var/uoj_data
-    XSendFilePath /var/www/uoj/app/storage
-    XSendFilePath /opt/uoj/judger/uoj_judger/include
-</VirtualHost>
-UOJEOF
-    #Enable modules and make UOJ site conf enabled
-    a2ensite 000-uoj.conf && a2dissite 000-default.conf
-    a2enmod rewrite headers && sed -i -e '172s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
-    #Create UOJ session save dir and make PHP extensions available
-    mkdir --mode=733 /var/lib/php/uoj_sessions && chmod +t /var/lib/php/uoj_sessions
-    sed -i -e '912a\extension=v8js.so\nextension=yaml.so' /etc/php/7.4/apache2/php.ini
-	sed -i 's|;sys_temp_dir = "/tmp"|sys_temp_dir = "/tmp"|g' /etc/php/7.4/apache2/php.ini
-}
-
-setWebConf(){
-    printf "\n\n==> Setting web files\n"
-    # Set webroot path
-    ln -sf /opt/uoj/web /var/www/uoj
-    chown -R www-data /var/www/uoj/app/storage
-    # Set web config file
-    php -a <<UOJEOF
-\$config = include '/var/www/uoj/app/.default-config.php';
-\$config['judger']['socket']['port']='$_judger_socket_port_';
-file_put_contents('/var/www/uoj/app/.config.php', "<?php\nreturn ".str_replace('\'_httpHost_\'','UOJContext::httpHost()',var_export(\$config, true)).";\n");
-UOJEOF
-    # Prepare local sandbox
-    cd /opt/uoj/judger/uoj_judger
-    cat >include/uoj_work_path.h <<UOJEOF
-#define UOJ_WORK_PATH "/opt/uoj/judger/uoj_judger"
-#define UOJ_JUDGER_BASESYSTEM_UBUNTU1804
-#define UOJ_JUDGER_PYTHON3_VERSION "3.6"
-#define UOJ_JUDGER_FPC_VERSION "3.0.4"
-UOJEOF
-    make runner -j$(($(nproc) + 1)) && cd /opt/uoj/web
-}
-
-initProgress(){
-    printf "\n\n==> Doing initial config and start service\n"
-    #Set uoj_data path
-    mkdir -p /var/uoj_data/upload
-    chown -R www-data:www-data /var/uoj_data
-    #Using cli upgrade to latest
-    php /var/www/uoj/app/cli.php upgrade:latest
-    #Start services
-    service ntp restart
-    service apache2 restart
-    #Touch SetupDone flag file
-    touch /var/uoj_data/.UOJSetupDone
-	mkdir -p /opt/uoj/web/app/storage/submission
-	mkdir -p /opt/uoj/web/app/storage/tmp
-	mkdir -p /opt/uoj/web/app/storage/paste
-	chmod -R 777 /opt/uoj/web/app/storage
-    printf "\n\n***Installation complete. Enjoy!***\n"
-}
-
-prepProgress(){
-    setLAMPConf;setWebConf
-}
-
-if [ $# -le 0 ]; then
-    echo 'Installing UOJ System web...'
-    getAptPackage;prepProgress;initProgress
-fi
-while [ $# -gt 0 ]; do
-    case "$1" in
-        -p | --prep)
-            echo 'Preparing UOJ System web environment...'
-            prepProgress
-        ;;
-        -i | --init)
-            echo 'Initing UOJ System web...'
-            initProgress
-        ;;
-        -? | --*)
-            echo "Illegal option $1"
-        ;;
-    esac
-    shift $(( $#>0?1:0 ))
-done

+ 47 - 0
judger/.dockerignore

@@ -0,0 +1,47 @@
+# Build context for the judger image is judger/ -- see ../docker-compose.yml.
+.git
+.gitignore
+.dockerignore
+Dockerfile
+
+# generated at runtime from the environment
+.conf.json
+
+# runtime state: never bake a 30 MB judge.log or cached problem data into the image
+log/
+uoj_judger/data
+uoj_judger/work/
+uoj_judger/result/
+
+# build output (rebuilt inside the image by `make all`)
+uoj_judger/main_judger
+uoj_judger/run/run_program
+uoj_judger/run/formatter
+uoj_judger/run/run_interaction
+uoj_judger/builtin/judger/judger
+uoj_judger/builtin/checker/acmp
+uoj_judger/builtin/checker/bcmp
+uoj_judger/builtin/checker/caseicmp
+uoj_judger/builtin/checker/casencmp
+uoj_judger/builtin/checker/casewcmp
+uoj_judger/builtin/checker/dcmp
+uoj_judger/builtin/checker/fcmp
+uoj_judger/builtin/checker/hcmp
+uoj_judger/builtin/checker/icmp
+uoj_judger/builtin/checker/lcmp
+uoj_judger/builtin/checker/ncmp
+uoj_judger/builtin/checker/rcmp
+uoj_judger/builtin/checker/rcmp4
+uoj_judger/builtin/checker/rcmp6
+uoj_judger/builtin/checker/rcmp9
+uoj_judger/builtin/checker/rncmp
+uoj_judger/builtin/checker/uncmp
+uoj_judger/builtin/checker/wcmp
+uoj_judger/builtin/checker/yesno
+*.o
+
+# editor / OS cruft
+**/*.swp
+**/*.swo
+**/*~
+.DS_Store

+ 49 - 0
judger/Dockerfile

@@ -0,0 +1,49 @@
+# WyOJ judger image.
+#
+# Build context is this directory:
+#     docker build -t wyoj-judger ./judger
+#
+# The base image MUST stay ubuntu:18.04.  uoj_judger/include/uoj_work_path.h
+# selects the sandbox's syscall/readable-file whitelist with
+# UOJ_JUDGER_BASESYSTEM_UBUNTU1804 and UOJ_JUDGER_PYTHON3_VERSION "3.6", and
+# bionic is the release that actually ships python3.6 (see
+# uoj_judger/run/run_program_conf.h around line 362-410).  A 20.04 base would
+# silently break Python submissions.
+#
+# This runs with judge_client's default (foreground) mode, so the container
+# logs go to `docker logs`; the `start` mode of judge_client daemonises into
+# log/judge.log and is not used here.
+FROM ubuntu:18.04
+
+LABEL org.opencontainers.image.source=https://git.ryp.org.cn/wyoj/wyoj-raw
+LABEL org.opencontainers.image.description="WyOJ Judger"
+LABEL org.opencontainers.image.licenses=MIT
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+# python3-requests is used by judge_client; g++/make are needed both to compile
+# submissions and to rebuild uoj_judger during self-update (socket cmd=update).
+RUN apt-get update && apt-get install -y \
+        python3 python3-requests \
+        make g++ binutils \
+        unzip zip \
+        ca-certificates procps \
+    && rm -rf /var/lib/apt/lists/*
+
+# /opt/uoj/judger must match UOJ_WORK_PATH in include/uoj_work_path.h -- the
+# compiled main_judger uses that absolute path at runtime.
+COPY . /opt/uoj/judger
+WORKDIR /opt/uoj/judger
+RUN set -eux; \
+    chmod +x judge_client docker-entrypoint.sh register-judger.sh; \
+    make -C uoj_judger all -j"$(nproc)"; \
+    mkdir -p uoj_judger/work uoj_judger/result uoj_judger/data log /var/uoj_data_copy; \
+    chmod -R 777 uoj_judger/work uoj_judger/result log
+
+ENV LANG=C.UTF-8 TZ=Asia/Shanghai
+
+# judge_client listens here for {password, cmd} tasks from
+# /judge/sync-judge-client (the port comes from the web's judger.socket.port).
+EXPOSE 2333
+
+CMD ["/opt/uoj/judger/docker-entrypoint.sh"]

+ 43 - 0
judger/docker-entrypoint.sh

@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# WyOJ judger entrypoint.
+#
+# Writes .conf.json (which judge_client/init() requires) from the environment,
+# makes sure the runtime directories exist, then runs judge_client in the
+# foreground so that its output lands in `docker logs`.
+set -e
+
+cd /opt/uoj/judger
+
+mkdir -p uoj_judger/work uoj_judger/result uoj_judger/data log /var/uoj_data_copy
+
+# data/ is expected to be a symlink to /var/uoj_data_copy.  The build context
+# normally excludes it, so recreate it here.
+if [ ! -e uoj_judger/data ]; then
+    ln -s /var/uoj_data_copy uoj_judger/data
+fi
+
+chmod -R 777 uoj_judger/work uoj_judger/result log 2>/dev/null || true
+
+python3 - <<'PY'
+import json, os
+
+conf = {
+    'uoj_protocol':    os.environ.get('UOJ_PROTOCOL', 'http'),
+    'uoj_host':        os.environ.get('UOJ_HOST', 'uoj-web'),
+    'judger_name':     os.environ.get('JUDGER_NAME', 'main_judger'),
+    'judger_password': os.environ.get('JUDGER_PASSWORD', '_judger_password_'),
+    'socket_port':     int(os.environ.get('JUDGER_SOCKET_PORT', '2333')),
+    'socket_password': os.environ.get('JUDGER_SOCKET_PASSWORD', '_judger_socket_password_'),
+}
+
+with open('.conf.json', 'w') as f:
+    json.dump(conf, f, indent=2)
+    f.write('\n')
+
+print('wrote .conf.json: judger=%s -> %s://%s, socket :%d'
+      % (conf['judger_name'], conf['uoj_protocol'], conf['uoj_host'], conf['socket_port']))
+PY
+
+echo "==> starting judge_client (foreground)"
+exec ./judge_client

+ 437 - 0
judger/judge_client

@@ -0,0 +1,437 @@
+#!/usr/bin/python3
+
+import json
+import sys
+import os
+import pipes
+import socket
+from threading import Thread
+import fcntl
+import shutil
+
+import traceback
+import time
+from contextlib import closing
+
+import requests
+
+import queue as queue
+from queue import Queue, Empty
+
+taskQ = Queue()
+submission = None
+
+# path related function
+
+def uoj_url(uri):
+	return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
+def uoj_judger_path(path = ''):
+	return "uoj_judger" + path
+
+# os related funciton
+def clean_up_folder(path):
+	for f in os.listdir(path):
+		f_path = os.path.join(path, f)
+		if os.path.isfile(f_path):
+			os.unlink(f_path)
+		else:
+			shutil.rmtree(f_path)
+
+def execute(cmd):
+	if os.system(cmd):
+		raise Exception('failed to execute: %s' % cmd)
+
+def freopen(f, g):
+	os.dup2(g.fileno(), f.fileno())
+	g.close()
+
+# init
+def init():
+	global jconf
+	os.chdir(os.path.dirname(os.path.realpath(__file__)))
+	with open('.conf.json', 'r') as fp:
+		jconf = json.load(fp)
+		assert 'uoj_protocol' in jconf
+		assert 'uoj_host' in jconf
+		assert 'judger_name' in jconf
+		assert 'judger_password' in jconf
+		assert 'socket_port' in jconf
+		assert 'socket_password' in jconf
+
+# socket server
+def socket_server_loop():
+	SOCK_CLOEXEC = 524288
+	with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
+		s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+		s.bind(('', jconf['socket_port']))
+		s.listen(5)
+		
+		while True:
+			try:
+				conn, addr = s.accept()
+				with closing(conn) as conn:
+					data = conn.recv(1024).decode()
+					assert data != None
+					task = json.loads(data)
+					assert task['password'] == jconf['socket_password']
+					assert 'cmd' in task
+					
+					taskQ.put(task)
+					
+					if task['cmd'] == 'stop':
+						print('the judge client is closing...')
+						taskQ.join()
+						conn.sendall(b'ok')
+						return 'stop'
+			except Exception:
+				print('['+time.asctime()+']', 'connection rejected', file=sys.stderr)
+				traceback.print_exc()
+			else:
+				print('['+time.asctime()+']', 'a new task accomplished', file=sys.stderr)
+
+def start_judger_server():
+	global socket_server_thread
+	
+	print_judge_client_status()
+	print('hello!', file=sys.stderr)
+	
+	socket_server_thread = Thread(target = socket_server_loop)
+	socket_server_thread.setDaemon(True)
+	socket_server_thread.start()
+	
+	judger_loop()
+
+# report thread
+def report_loop():
+	if 'is_hack' in submission:
+		return
+	while not submission_judged:
+		try:
+			with open(uoj_judger_path('/result/cur_status.txt'), 'r') as f:
+				fcntl.flock(f, fcntl.LOCK_SH)
+				try:
+					status = f.read(100)
+				except Exception:
+					status = None
+				finally:
+					fcntl.flock(f, fcntl.LOCK_UN)
+			
+			if status != None:
+				data = {}
+				data['update-status'] = True
+				data['id'] = submission['id']
+				if 'is_custom_test' in submission:
+					data['is_custom_test'] = True
+				data['status'] = status
+				uoj_interact(data)
+			time.sleep(0.2)
+		except Exception:
+			pass
+
+# handle task in main thread
+def handle_task():
+	need_restart = False
+	try:
+		while True:
+			task = taskQ.get_nowait()
+			
+			if task['cmd'] == 'update':
+				try:
+					uoj_download('/judger', 'judger_update.zip')
+					execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
+				except:
+					print(sys.stderr, "error when update")
+				if jconf['judger_name'] == 'main_judger':
+					uoj_sync_judge_client()
+				need_restart = True
+			elif task['cmd'] == 'stop':
+				taskQ.task_done()
+				socket_server_thread.join()
+				
+				print_judge_client_status()
+				print(sys.stderr, "goodbye!")
+				
+				sys.exit(0)
+			
+			taskQ.task_done()
+	except Empty:
+		pass
+	
+	if need_restart:
+		os.execl('./judge_client', './judge_client')
+
+def print_judge_client_status():
+	print('[' + time.asctime() + ']', end=' ', file=sys.stderr)
+	if submission != None:
+		print(submission, end=' ', file=sys.stderr)
+	print(file=sys.stderr)
+
+# interact with uoj_judger
+def get_judger_result():
+	res = {}
+	with open(uoj_judger_path('/result/result.txt'), 'r') as fres:
+		res['score'] = 0
+		res['time'] = 0
+		res['memory'] = 0
+		while True:
+			line = fres.readline()
+			if line == '':
+				break
+			line = line.strip()
+			if line == 'details':
+				res['details'] = fres.read()
+				break
+			
+			sp = line.split()
+			assert len(sp) >= 1
+			if sp[0] == 'error':
+				res['error'] = line[len('error') + 1:]
+			else:
+				assert len(sp) == 2
+				res[sp[0]] = sp[1]
+		res['score'] = int(res['score'])
+		res['time'] = int(res['time'])
+		res['memory'] = int(res['memory'])
+	res['status'] = 'Judged'
+	return res
+
+def update_problem_data(problem_id, problem_mtime):
+	try:
+		if jconf['judger_name'] == 'main_judger':
+			return
+		copy_name = uoj_judger_path('/data/%d' % problem_id)
+		copy_zip_name = uoj_judger_path('/data/%d.zip' % problem_id)
+		if os.path.isdir(copy_name):
+			quoted_copy_name = pipes.quote(copy_name)
+			if os.path.getmtime(copy_name) >= problem_mtime:
+				execute('touch -a %s' % quoted_copy_name)
+				return
+			else:
+				execute('chmod 700 %s -R && rm -rf %s' % (quoted_copy_name, quoted_copy_name))
+		del_list = sorted(os.listdir(uoj_judger_path('/data')), key=lambda fname: os.path.getatime(uoj_judger_path('/data/%s' % fname)))[:-99]
+		for fname in del_list:
+			quoted_fname = pipes.quote(uoj_judger_path('/data/%s' % fname))
+			os.system('chmod 700 %s -R && rm -rf %s' % (quoted_fname, quoted_fname))
+		
+		uoj_download('/problem/%d' % problem_id, copy_zip_name)
+		execute('cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R' % (uoj_judger_path('/data'), problem_id, problem_id, problem_id))
+	except Exception:
+		print_judge_client_status()
+		traceback.print_exc()
+		raise Exception('failed to update problem data of #%d' % problem_id)
+	else:
+		print_judge_client_status()
+		print('updated problem data of #%d successfully' % problem_id, file=sys.stderr)
+
+def judge():
+	global report_thread
+	global submission_judged
+	
+	clean_up_folder(uoj_judger_path('/work'))
+	clean_up_folder(uoj_judger_path('/result'))
+	update_problem_data(submission['problem_id'], submission['problem_mtime'])
+	
+	with open(uoj_judger_path('/work/submission.conf'), 'w') as fconf:
+		uoj_download(submission['content']['file_name'], uoj_judger_path('/work/all.zip'))
+		execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path('/work')))
+		for k, v in submission['content']['config']:
+			print(k, v, file=fconf)
+		
+		if 'is_hack' in submission:
+			if submission['hack']['input_type'] == 'USE_FORMATTER':
+				uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input_raw.txt'))
+				execute('%s <%s >%s' % (
+					pipes.quote(uoj_judger_path('/run/formatter')),
+					pipes.quote(uoj_judger_path('/work/hack_input_raw.txt')),
+					pipes.quote(uoj_judger_path('/work/hack_input.txt'))))
+			else:
+				uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input.txt'))
+			print('test_new_hack_only on', file=fconf)
+		elif 'is_custom_test' in submission:
+			print('custom_test on', file=fconf)
+	
+	report_thread = Thread(target = report_loop)
+	report_thread.setDaemon(True)
+	
+	submission_judged = False
+	report_thread.start()
+	execute(pipes.quote(uoj_judger_path('/main_judger')))
+	submission_judged = True
+	report_thread.join()
+	
+	return get_judger_result()
+
+# interact with uoj web server
+def uoj_interact(data, files = {}):
+	data = data.copy()
+	data.update({
+		'judger_name': jconf['judger_name'],
+		'password': jconf['judger_password']
+	})
+	return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
+def uoj_download(uri, filename):
+	data = {
+		'judger_name': jconf['judger_name'],
+		'password': jconf['judger_password']
+	}
+	with open(filename, 'wb') as f:
+		r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
+		for chunk in r.iter_content(chunk_size=65536):
+			if chunk:
+				f.write(chunk)
+def uoj_sync_judge_client():
+	data = {
+		'judger_name': jconf['judger_name'],
+		'password': jconf['judger_password']
+	}
+	ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
+	if ret != "ok":
+		raise Exception('failed to sync judge clients: %s' % ret)
+
+def send_and_fetch(result = None, fetch_new = True):
+	global submission
+	
+	"""send judgement result, and fetch new submission to judge"""
+	
+	data = {}
+	files = {}
+	
+	if not fetch_new:
+		data['fetch_new'] = False
+	
+	if result != None:
+		data['submit'] = True
+		if 'is_hack' in submission:
+			data['is_hack'] = True
+			data['id'] = submission['hack']['id']
+			if result != False and result['score']:
+				try:
+					print("succ hack!", file=sys.stderr)
+					files = {
+						('hack_input', open('uoj_judger/work/hack_input.txt', 'r')),
+						('std_output', open('uoj_judger/work/std_output.txt', 'r'))
+					}
+				except Exception:
+					print_judge_client_status()
+					traceback.print_exc()
+					result = False
+		elif 'is_custom_test' in submission:
+			data['is_custom_test'] = True
+			data['id'] = submission['id']
+		else:
+			data['id'] = submission['id']
+		
+		if result == False:
+			result = {
+				'score': 0,
+				'error': 'Judgement Failed',
+				'details': 'Unknown Error'
+			}
+		result['status'] = 'Judged'
+		data['result'] = json.dumps(result, ensure_ascii=False)
+	
+	while True:
+		try:
+			ret = uoj_interact(data, files)
+			print(ret)
+		except Exception:
+			print_judge_client_status()
+			traceback.print_exc()
+		else:
+			break
+		time.sleep(2)
+	
+	try:
+		submission = json.loads(ret)
+	except Exception as e:
+		submission = None
+		return False
+	else:
+		return True
+
+# judge client
+def judger_loop():
+	ok = False
+	while True:
+		fetch_new = True
+		
+		if ok and not (taskQ.empty() and socket_server_thread.isAlive()):
+			fetch_new = False
+				
+		if not ok:
+			while True:
+				if not taskQ.empty():
+					handle_task()
+				if not socket_server_thread.isAlive():
+					raise Exception('socket server exited unexpectedly')
+				
+				if send_and_fetch():
+					break
+				
+				print('['+time.asctime()+']', 'Nothing to judge...')
+				time.sleep(2)
+			
+			ok = True
+			print_judge_client_status()
+			print('judging', file=sys.stderr)
+		
+		try:
+			res = judge()
+		except Exception:
+			print_judge_client_status()
+			traceback.print_exc()
+			res = False
+		
+		ok = send_and_fetch(result=res,fetch_new=fetch_new)
+
+# main function
+def main():
+	init()
+	
+	if len(sys.argv) == 1:
+		start_judger_server()
+	if len(sys.argv) == 2:
+		if sys.argv[1] == 'start':
+			pid = os.fork()
+			if pid == -1:
+				raise Exception('fork failed')
+			elif pid > 0:
+				return
+			else:
+				freopen(sys.stdout, open(os.devnull, 'wb'))
+				freopen(sys.stderr, open('log/judge.log', 'ab', buffering=0))
+				start_judger_server()
+		elif sys.argv[1] == 'update':
+			try:
+				with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
+					s.connect(('127.0.0.1', jconf['socket_port']))
+					s.sendall(json.dumps({
+						'password': jconf['socket_password'],
+						'cmd': 'update'
+					}).encode())
+				return
+			except Exception:
+				traceback.print_exc()
+				raise Exception('update failed')
+		elif sys.argv[1] == 'stop':
+			try:
+				with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
+					s.connect(('127.0.0.1', jconf['socket_port']))
+					s.sendall(json.dumps({
+						'password': jconf['socket_password'],
+						'cmd': 'stop'
+					}).encode())
+					if s.recv(10).decode() != 'ok':
+						raise Exception('stop failed')
+				return
+			except Exception:
+				traceback.print_exc()
+				raise Exception('stop failed')
+	raise Exception('invalid argument')
+
+try:
+	main()
+except Exception:
+	print_judge_client_status()
+	traceback.print_exc()
+	sys.exit(1)

+ 39 - 0
judger/register-judger.sh

@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# Register (or update) this judger in the web database.  Run it on the *host*,
+# after `docker compose up`, whenever JUDGER_PASSWORD / JUDGER_SOCKET_* change.
+#
+#     ./judger/register-judger.sh
+#     ./judger/register-judger.sh main_judger 'my-secret' uoj-judger
+#
+# It is an upsert, so it is safe to re-run.
+#
+# judger_name must equal JUDGER_NAME in the judger container (see
+# docker-compose.yml); ip is what /judge/sync-judge-client dials, so for
+# compose that is the service name `uoj-judger`.
+set -eu
+
+JUDGER_NAME="${1:-${JUDGER_NAME:-main_judger}}"
+JUDGER_PASSWORD="${2:-${JUDGER_PASSWORD:-_judger_password_}}"
+JUDGER_IP="${3:-${JUDGER_IP:-uoj-judger}}"
+
+DB_CONTAINER="${DB_CONTAINER:-uoj-db}"
+DB_NAME="${DATABASE_NAME:-app_uoj233}"
+DB_USER="${DATABASE_USER:-root}"
+DB_PASSWORD="${DATABASE_PASSWORD:-root}"
+
+# Keep passwords free of single quotes: they are embedded in SQL below.
+case "$JUDGER_NAME$JUDGER_PASSWORD$JUDGER_IP" in
+    *"'"*)
+        echo "ERROR: passwords/names must not contain a single quote" >&2
+        exit 1
+        ;;
+esac
+
+docker exec -i "$DB_CONTAINER" \
+    mysql -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" <<SQL
+insert into judger_info (judger_name, password, ip)
+values ('$JUDGER_NAME', '$JUDGER_PASSWORD', '$JUDGER_IP')
+on duplicate key update password = values(password), ip = values(ip);
+select judger_name, ip from judger_info;
+SQL

+ 1 - 0
judger/uoj_judger/.gitignore

@@ -0,0 +1 @@
+data/*

+ 49 - 0
judger/uoj_judger/Makefile

@@ -0,0 +1,49 @@
+INCLUDE_PATH = include
+CXXFLAGS = -I./include -O2
+
+EXE_CHECKER = \
+	builtin/checker/bcmp \
+	builtin/checker/acmp \
+	builtin/checker/caseicmp \
+	builtin/checker/casencmp \
+	builtin/checker/casewcmp \
+	builtin/checker/dcmp \
+	builtin/checker/fcmp \
+	builtin/checker/hcmp \
+	builtin/checker/icmp \
+	builtin/checker/lcmp \
+	builtin/checker/ncmp \
+	builtin/checker/rcmp \
+	builtin/checker/rcmp4 \
+	builtin/checker/rcmp6 \
+	builtin/checker/rcmp9 \
+	builtin/checker/rncmp \
+	builtin/checker/uncmp \
+	builtin/checker/wcmp \
+	builtin/checker/yesno
+
+EXE = main_judger \
+	run/formatter \
+	run/run_program \
+	run/run_interaction \
+	builtin/judger/judger
+
+all: $(EXE) $(EXE_CHECKER)
+runner: $(EXE)
+checker: $(EXE_CHECKER)
+
+% : %.cpp
+	$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) $< -o $@
+
+run/run_program: include/uoj_env.h run/run_program_conf.h
+run/formatter  : include/testlib.h
+run/run_interaction: run/run_interaction.cpp include/uoj_env.h
+	$(CXX) $(CXXFLAGS) --std=c++11 -pthread $< -o $@
+
+builtin/judger/judger: include
+main_judger: include
+
+$(EXE_CHECKER): include/testlib.h
+
+clean:
+	rm -f $(EXE) $(EXE_CHECKER)

+ 19 - 0
judger/uoj_judger/builtin/checker/acmp.cpp

@@ -0,0 +1,19 @@
+#include "testlib.h"
+#include <stdio.h>
+#include <math.h>
+
+const double EPS = 1.5E-6;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two doubles, maximal absolute error = %.10lf", EPS);
+    registerTestlibCmd(argc, argv);
+    
+    double ja = ans.readDouble();
+    double pa = ouf.readDouble();
+
+    if (fabs(ja - pa) > EPS + 1E-15)
+        quitf(_wa, "expected %.10lf, found %.10lf", ja, pa);
+    
+    quitf(_ok, "answer is %.10lf", ja);
+}

+ 55 - 0
judger/uoj_judger/builtin/checker/bcmp.cpp

@@ -0,0 +1,55 @@
+#include <cstdio>
+
+inline const char *englishEnding(int x)
+{
+	x %= 100;
+	if (x / 10 == 1)
+		return "th";
+	if (x % 10 == 1)
+		return "st";
+	if (x % 10 == 2)
+		return "nd";
+	if (x % 10 == 3)
+		return "rd";
+	return "th";
+}
+
+int main(int argc, char * argv[])
+{
+	if (argc != 4)
+		return 1;
+	FILE *fout = fopen(argv[2], "r");
+	FILE *fans = fopen(argv[3], "r");
+
+	if (fout == NULL || fans == NULL)
+		return 1;
+
+	int n = 0;
+	while (true)
+	{
+		n++;
+		int c, d;
+		c = fgetc(fout);
+		d = fgetc(fans);
+		if (c == EOF && d == EOF)
+			break;
+		if (c != d)
+		{
+			if (c == EOF)
+				fprintf(stderr, "wrong answer %d%s byte differ - expected EOF found '%c'\n", n, englishEnding(n), (char)d);
+			else if (d == EOF)
+				fprintf(stderr, "wrong answer %d%s byte differ - expected '%c' found EOF\n", n, englishEnding(n), (char)c);
+			else
+				fprintf(stderr, "wrong answer %d%s byte differ - expected '%c' found '%c'\n", n, englishEnding(n), (char)c, (char)d);
+			return 1;
+		}
+	}
+
+	if (n == 0)
+		fprintf(stderr, "ok empty file\n");
+	else if (n == 1)
+		fprintf(stderr, "ok single byte\n");
+	else
+		fprintf(stderr, "ok %d byte\n", n);
+	return 0;
+}

+ 95 - 0
judger/uoj_judger/builtin/checker/caseicmp.cpp

@@ -0,0 +1,95 @@
+/**
+ * Checker to compare output and answer in the form:
+ *
+ * Case 1: <number>
+ * Case 2: <number>
+ * ...
+ * Case n: <number>
+ *
+ */
+
+#include "testlib.h"
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <iomanip>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cmath>
+#include <ctime>
+#include <climits>
+#include <cassert>
+#include <vector>
+#include <queue>
+#include <stack>
+#include <deque>
+#include <set>
+#include <map>
+#include <bitset>
+#include <utility>
+#include <algorithm>
+
+using namespace std;
+
+#define forn(i, n) for (int i = 0; i < int(n); i++)
+
+vector<long long> readStream(InStream& in, TResult pe)
+{
+    vector<long long> result;
+
+    for (int testCase = 1; !in.seekEof(); testCase++)
+    {
+        string caseStr = in.readToken();
+        if (caseStr != "Case")
+            quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), testCase);
+
+        string numExpStr;
+        stringstream ss;
+        ss << testCase;
+        ss >> numExpStr;
+        numExpStr += ":";
+        string numStr = in.readToken();
+        if (numExpStr != numStr)
+            quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), compress(numStr).c_str(), testCase);
+
+        result.push_back(in.readLong());
+    }
+
+    return result;
+}
+
+int main(int argc, char* argv[])
+{
+    setName("Single int64 checker with testcase-support");
+    registerTestlibCmd(argc, argv);
+
+    vector<long long> ja = readStream(ans, _fail);
+    vector<long long> pa = readStream(ouf, _pe);
+
+    forn(i, min(ja.size(), pa.size()))
+        if (ja[i] != pa[i])
+            quitf(_wa, "Expected %s found %s [test case %d]", vtos(ja[i]).c_str(), vtos(pa[i]).c_str(), i + 1);
+
+    if (ja.size() != pa.size())
+        quitf(_pe, "Expected %d test case(s) but found %d", (int)ja.size(), (int)pa.size());
+
+    string message = format("%d case(s):", (int)ja.size());
+    if (ja.size() <= 5)
+    {
+        forn(i, ja.size())
+            message += " " + vtos(ja[i]);
+    }
+    else
+    {
+        forn(i, 3)
+            message += " " + vtos(ja[i]);
+        message += " ...";
+        forn(i, 2)
+            message += " " + vtos(ja[ja.size() - 2 + i]);
+    }
+
+    quitf(_ok, "%s", message.c_str());
+}

+ 124 - 0
judger/uoj_judger/builtin/checker/casencmp.cpp

@@ -0,0 +1,124 @@
+/**
+ * Checker to compare output and answer in the form:
+ *
+ * Case 1: <number> <number> <number> ... <number>
+ * Case 2: <number> <number> <number> ... <number>
+ * ...
+ * Case n: <number> <number> <number> ... <number>
+ *
+ */
+
+#include "testlib.h"
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <iomanip>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cmath>
+#include <ctime>
+#include <climits>
+#include <cassert>
+#include <vector>
+#include <queue>
+#include <stack>
+#include <deque>
+#include <set>
+#include <map>
+#include <bitset>
+#include <utility>
+#include <algorithm>
+
+using namespace std;
+
+#define forn(i, n) for (int i = 0; i < int(n); i++)
+
+string token;
+
+vector<long long> readStreamCase(InStream& in, TResult pe, int testCase, bool& prereadCase)
+{
+    if (!prereadCase)
+    {
+        string caseStr = in.readToken();
+        if (caseStr != "Case")
+            quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), testCase);
+    }
+
+    string numExpStr;
+    stringstream ss;
+    ss << testCase;
+    ss >> numExpStr;
+    numExpStr += ":";
+    string numStr = in.readToken();
+    if (numExpStr != numStr)
+        quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), compress(numStr).c_str(), testCase);
+
+    vector<long long> result;
+    while (!in.seekEof())
+    {
+        in.readTokenTo(token);
+        if (token == "Case")
+        {
+            prereadCase = true;
+            break;
+        }
+
+        result.push_back(stringToLongLong(in, token.c_str()));
+    }
+
+    return result;
+}
+
+string longLongsToString(const vector<long long>& a)
+{
+    if (a.empty())
+        return "\"\" [size=0]";
+    
+    string elems;
+    if (a.size() <= 5)
+    {
+        forn(i, a.size())
+            elems += vtos(a[i]) + " ";
+    }
+    else
+    {
+        forn(i, 3)
+            elems += vtos(a[i]) + " ";
+        elems += "... ";
+        forn(i, 2)
+            elems += vtos(a[a.size() - 2 + i]) + " ";
+    }
+
+    return format("\"%s\" [size=%d]", trim(elems).c_str(), (int)a.size());
+}
+
+int main(int argc, char* argv[])
+{
+    setName("Many int64s checker with testcase-support");
+    registerTestlibCmd(argc, argv);
+
+    int testCase = 0;
+
+    bool ansPrereadCase = false;
+    bool oufPrereadCase = false;
+
+    while (!ans.seekEof())
+    {
+        testCase++;
+
+        vector<long long> ja = readStreamCase(ans, _fail, testCase, ansPrereadCase);
+        vector<long long> pa = readStreamCase(ouf, _pe, testCase, oufPrereadCase);
+
+        if (ja != pa)
+        {
+            string js = longLongsToString(ja);
+            string ps = longLongsToString(pa);
+            quitf(_wa, "Sequences differ: jury has %s, but participant has %s [test case %d]", js.c_str(), ps.c_str(), testCase);
+        }
+    }
+
+    quitf(_ok, "%d test cases(s)", testCase);
+}

+ 113 - 0
judger/uoj_judger/builtin/checker/casewcmp.cpp

@@ -0,0 +1,113 @@
+/**
+ * Checker to compare output and answer in the form:
+ *
+ * Case 1: <token> <token> ... <token>
+ * Case 2: <token> <token> ... <token>
+ * ...
+ * Case n: <token> <token> ... <token>
+ *
+ */
+
+#include "testlib.h"
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <iomanip>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cmath>
+#include <ctime>
+#include <climits>
+#include <cassert>
+#include <vector>
+#include <queue>
+#include <stack>
+#include <deque>
+#include <set>
+#include <map>
+#include <bitset>
+#include <utility>
+#include <algorithm>
+
+using namespace std;
+
+#define forn(i, n) for (int i = 0; i < int(n); i++)
+
+string token;
+
+vector<string> readStreamCase(InStream& in, TResult pe, int testCase, bool& prereadCase)
+{
+    if (!prereadCase)
+    {
+        string caseStr = in.readToken();
+        if (caseStr != "Case")
+            quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), testCase);
+    }
+
+    string numExpStr;
+    stringstream ss;
+    ss << testCase;
+    ss >> numExpStr;
+    numExpStr += ":";
+    string numStr = in.readToken();
+    if (numExpStr != numStr)
+        quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), compress(numStr).c_str(), testCase);
+
+    vector<string> result;
+    while (!in.seekEof())
+    {
+        in.readTokenTo(token);
+        if (token == "Case")
+        {
+            prereadCase = true;
+            break;
+        }
+
+        result.push_back(token);
+    }
+
+    return result;
+}
+
+string stringsToString(const vector<string>& a)
+{
+    if (a.empty())
+        return "\"\" [size=0]";
+    
+    string elems;
+    forn(i, a.size())
+        elems += a[i] + " ";
+
+    return format("\"%s\" [size=%d]", compress(trim(elems)).c_str(), (int)a.size());
+}
+
+int main(int argc, char* argv[])
+{
+    setName("Tokens checker with testcase-support");
+    registerTestlibCmd(argc, argv);
+
+    int testCase = 0;
+
+    bool ansPrereadCase = false;
+    bool oufPrereadCase = false;
+
+    while (!ans.seekEof())
+    {
+        testCase++;
+
+        vector<string> ja = readStreamCase(ans, _fail, testCase, ansPrereadCase);
+        vector<string> pa = readStreamCase(ouf, _pe, testCase, oufPrereadCase);
+
+        if (ja != pa)
+        {
+            string js = stringsToString(ja);
+            string ps = stringsToString(pa);
+            quitf(_wa, "Sequences differ: jury has %s, but participant has %s [test case %d]", js.c_str(), ps.c_str(), testCase);
+        }
+    }
+
+    quitf(_ok, "%d test cases(s)", testCase);
+}

+ 19 - 0
judger/uoj_judger/builtin/checker/dcmp.cpp

@@ -0,0 +1,19 @@
+#include "testlib.h"
+#include <stdio.h>
+#include <math.h>
+
+const double EPS = 1E-6;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two doubles, maximal absolute or relative error = %.10lf", EPS);
+    registerTestlibCmd(argc, argv);
+    
+    double ja = ans.readDouble();
+    double pa = ouf.readDouble();
+    
+    if (!doubleCompare(ja, pa, EPS))
+        quitf(_wa, "expected %.10lf, found %.10lf", ja, pa);
+    
+    quitf(_ok, "answer is %.10lf", ja);
+}

+ 36 - 0
judger/uoj_judger/builtin/checker/fcmp.cpp

@@ -0,0 +1,36 @@
+#include "testlib.h"
+#include <string>
+#include <vector>
+#include <sstream>
+
+using namespace std;
+
+int main(int argc, char * argv[])
+{
+    setName("compare files as sequence of lines");
+    registerTestlibCmd(argc, argv);
+
+    std::string strAnswer;
+
+    int n = 0;
+    while (!ans.eof()) 
+    {
+        std::string j = ans.readString();
+
+        if (j == "" && ans.eof())
+          break;
+
+        strAnswer = j;
+        std::string p = ouf.readString();
+
+        n++;
+
+        if (j != p)
+            quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str());
+    }
+    
+    if (n == 1)
+        quitf(_ok, "single line: '%s'", compress(strAnswer).c_str());
+    
+    quitf(_ok, "%d lines", n);
+}

+ 35 - 0
judger/uoj_judger/builtin/checker/hcmp.cpp

@@ -0,0 +1,35 @@
+#include "testlib.h"
+
+#include <string>
+
+using namespace std;
+
+pattern pnum("0|-?[1-9][0-9]*");
+
+bool isNumeric(const string& p)
+{
+    return pnum.matches(p);
+}
+
+int main(int argc, char * argv[])
+{
+    setName("compare two signed huge integers");
+    registerTestlibCmd(argc, argv);
+    
+    string ja = ans.readWord();
+    string pa = ouf.readWord();
+
+    if (!isNumeric(ja))
+        quitf(_fail, "%s is not a valid integer", compress(ja).c_str());
+
+    if (!ans.seekEof())
+        quitf(_fail, "expected exactly one token in the answer file");
+    
+    if (!isNumeric(pa))
+        quitf(_pe, "%s is not a valid integer", compress(pa).c_str());
+
+    if (ja != pa)
+        quitf(_wa, "expected '%s', found '%s'", compress(ja).c_str(), compress(pa).c_str());
+    
+    quitf(_ok, "answer is '%s'", compress(ja).c_str());
+}

+ 16 - 0
judger/uoj_judger/builtin/checker/icmp.cpp

@@ -0,0 +1,16 @@
+#include "testlib.h"
+#include <stdio.h>
+
+int main(int argc, char * argv[])
+{
+    setName("compare two signed int%d's", (int)(8 * sizeof(int)));
+    registerTestlibCmd(argc, argv);
+    
+    int ja = ans.readInt();
+    int pa = ouf.readInt();
+    
+    if (ja != pa)
+        quitf(_wa, "expected %d, found %d", ja, pa);
+    
+    quitf(_ok, "answer is %d", ja);
+}

+ 54 - 0
judger/uoj_judger/builtin/checker/lcmp.cpp

@@ -0,0 +1,54 @@
+#include "testlib.h"
+#include <string>
+#include <vector>
+#include <sstream>
+
+using namespace std;
+
+bool compareWords(string a, string b)
+{
+    vector<string> va, vb;
+    stringstream sa;
+    
+    sa << a;
+    string cur;
+    while (sa >> cur)
+        va.push_back(cur);
+
+    stringstream sb;
+    sb << b;
+    while (sb >> cur)
+        vb.push_back(cur);
+
+    return (va == vb);
+}
+
+int main(int argc, char * argv[])
+{
+    setName("compare files as sequence of tokens in lines");
+    registerTestlibCmd(argc, argv);
+
+    std::string strAnswer;
+
+    int n = 0;
+    while (!ans.eof()) 
+    {
+        std::string j = ans.readString();
+
+        if (j == "" && ans.eof())
+          break;
+        
+        std::string p = ouf.readString();
+        strAnswer = p;
+
+        n++;
+
+        if (!compareWords(j, p))
+            quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str());
+    }
+    
+    if (n == 1)
+        quitf(_ok, "single line: '%s'", compress(strAnswer).c_str());
+    
+    quitf(_ok, "%d lines", n);
+}

+ 57 - 0
judger/uoj_judger/builtin/checker/ncmp.cpp

@@ -0,0 +1,57 @@
+#include "testlib.h"
+#include <sstream>
+
+using namespace std;
+
+int main(int argc, char * argv[])
+{
+    setName("compare ordered sequences of signed int%d numbers", (int)(8 * sizeof(long long)));
+
+    registerTestlibCmd(argc, argv);
+
+    int n = 0;
+    string firstElems;
+
+    while (!ans.seekEof() && !ouf.seekEof())
+    {
+        n++;
+        long long j = ans.readLong();
+        long long p = ouf.readLong();
+        if (j != p)
+            quitf(_wa, "%d%s numbers differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), vtos(j).c_str(), vtos(p).c_str());
+        else
+            if (n <= 5)
+            {
+                if (firstElems.length() > 0)
+                    firstElems += " ";
+                firstElems += vtos(j);
+            }
+    }
+
+    int extraInAnsCount = 0;
+
+    while (!ans.seekEof())
+    {
+        ans.readLong();
+        extraInAnsCount++;
+    }
+    
+    int extraInOufCount = 0;
+
+    while (!ouf.seekEof())
+    {
+        ouf.readLong();
+        extraInOufCount++;
+    }
+
+    if (extraInAnsCount > 0)
+        quitf(_wa, "Answer contains longer sequence [length = %d], but output contains %d elements", n + extraInAnsCount, n);
+    
+    if (extraInOufCount > 0)
+        quitf(_wa, "Output contains longer sequence [length = %d], but answer contains %d elements", n + extraInOufCount, n);
+    
+    if (n <= 5)
+        quitf(_ok, "%d number(s): \"%s\"", n, compress(firstElems).c_str());
+    else
+        quitf(_ok, "%d numbers", n);
+}

+ 19 - 0
judger/uoj_judger/builtin/checker/rcmp.cpp

@@ -0,0 +1,19 @@
+#include "testlib.h"
+#include <stdio.h>
+#include <math.h>
+
+const double EPS = 1.5E-6;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two doubles, maximal absolute error = %.10lf", EPS);
+    registerTestlibCmd(argc, argv);
+    
+    double ja = ans.readDouble();
+    double pa = ouf.readDouble();
+
+    if (fabs(ja - pa) > EPS + 1E-15)
+        quitf(_wa, "expected %.10lf, found %.10lf", ja, pa);
+    
+    quitf(_ok, "answer is %.10lf", ja);
+}

+ 32 - 0
judger/uoj_judger/builtin/checker/rcmp4.cpp

@@ -0,0 +1,32 @@
+#include "testlib.h"
+#include <cmath>
+
+using namespace std;
+
+const double EPS = 1E-4;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two sequences of doubles, max absolute or relative error = %.5lf", EPS);
+    registerTestlibCmd(argc, argv);
+
+    int n = 0;
+    double j, p;
+
+    while (!ans.seekEof()) 
+    {
+        n++;
+        j = ans.readDouble();
+        p = ouf.readDouble();
+        if (!doubleCompare(j, p, EPS))
+        {
+            quitf(_wa, "%d%s numbers differ - expected: '%.5lf', found: '%.5lf', error = '%.5lf'",
+                n, englishEnding(n).c_str(), j, p, doubleDelta(j, p));
+        }
+    }
+
+    if (n == 1)
+        quitf(_ok, "found '%.5lf', expected '%.5lf', error '%.5lf'", p, j, doubleDelta(j, p));
+    
+    quitf(_ok, "%d numbers", n);
+}

+ 32 - 0
judger/uoj_judger/builtin/checker/rcmp6.cpp

@@ -0,0 +1,32 @@
+#include "testlib.h"
+#include <cmath>
+
+using namespace std;
+
+const double EPS = 1E-6;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two sequences of doubles, max absolute or relative  error = %.7lf", EPS);
+    registerTestlibCmd(argc, argv);
+
+    int n = 0;
+    double j, p;
+
+    while (!ans.seekEof()) 
+    {
+        n++;
+        j = ans.readDouble();
+        p = ouf.readDouble();
+        if (!doubleCompare(j, p, EPS))
+        {
+            quitf(_wa, "%d%s numbers differ - expected: '%.7lf', found: '%.7lf', error = '%.7lf'",
+                n, englishEnding(n).c_str(), j, p, doubleDelta(j, p));
+        }
+    }
+
+    if (n == 1)
+        quitf(_ok, "found '%.7lf', expected '%.7lf', error '%.7lf'", p, j, doubleDelta(j, p));
+
+    quitf(_ok, "%d numbers", n);
+}

+ 32 - 0
judger/uoj_judger/builtin/checker/rcmp9.cpp

@@ -0,0 +1,32 @@
+#include "testlib.h"
+#include <cmath>
+
+using namespace std;
+
+const double EPS = 1E-9;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two sequences of doubles, max absolute or relative error = %.10lf", EPS);
+    registerTestlibCmd(argc, argv);
+
+    int n = 0;
+    double j, p;
+
+    while (!ans.seekEof()) 
+    {
+        n++;
+        j = ans.readDouble();
+        p = ouf.readDouble();
+        if (!doubleCompare(j, p, EPS))
+        {
+            quitf(_wa, "%d%s numbers differ - expected: '%.7lf', found: '%.7lf', error = '%.7lf'",
+                n, englishEnding(n).c_str(), j, p, doubleDelta(j, p));
+        }
+    }
+
+    if (n == 1)
+        quitf(_ok, "found '%.9lf', expected '%.9lf', error '%.9lf'", p, j, doubleDelta(j, p));
+
+    quitf(_ok, "%d numbers", n);
+}

+ 24 - 0
judger/uoj_judger/builtin/checker/rncmp.cpp

@@ -0,0 +1,24 @@
+#include "testlib.h"
+#include <cmath>
+
+using namespace std;
+
+const double EPS = 1.5E-5;
+
+int main(int argc, char * argv[])
+{
+    setName("compare two sequences of doubles, maximal absolute error = %.10lf", EPS);
+    registerTestlibCmd(argc, argv);
+
+    int n = 0;
+    while (!ans.seekEof()) 
+    {
+        n++;
+        double j = ans.readDouble();
+        double p = ouf.readDouble();
+        if (fabs(j - p) > EPS + 1E-15)
+            quitf(_wa, "%d%s numbers differ - expected: '%.10lf', found: '%.10lf'", n, englishEnding(n).c_str(), j, p);
+    }
+
+    quitf(_ok, "%d numbers", n);
+}

+ 52 - 0
judger/uoj_judger/builtin/checker/uncmp.cpp

@@ -0,0 +1,52 @@
+#include "testlib.h"
+#include <vector>
+
+using namespace std;
+
+int main(int argc, char * argv[])
+{
+    setName("compare unordered sequences of signed int%d numbers", (int)(8 * sizeof(long long)));
+
+    registerTestlibCmd(argc, argv);
+
+    vector<long long> ja, pa;
+
+    while (!ans.seekEof())
+        ja.push_back(ans.readLong());
+
+    while (!ouf.seekEof())
+        pa.push_back(ouf.readLong());
+
+    if (ja.size() != pa.size())
+        quitf(_wa, "Expected %d elements, but %d found", (int)ja.size(), (int)pa.size());
+
+    sort(ja.begin(), ja.end());
+    sort(pa.begin(), pa.end());
+
+    if (ja != pa)
+        quitf(_wa, "Expected sequence and output are different (as unordered sequences) [size=%d]", (int)ja.size());
+
+    string message;
+    
+    if (ja.size() != 1)
+        if (ja.empty())
+            message = "empty sequence";
+        else
+            message = vtos(ja.size()) + " numbers (in increasing order):";
+    else
+        message = vtos(ja.size()) + " number:";
+    
+    if (ja.size() <= 5)
+        for (int i = 0; i < min(int(ja.size()), 5); i++)
+            message += " " + vtos(ja[i]);
+    else
+    {
+        for (int i = 0; i < 2; i++)
+            message += " " + vtos(ja[i]);
+        message += " ...";
+        for (int i = 0; i < 2; i++)
+            message += " " + vtos(ja[ja.size() - 2 + i]);
+    }
+        
+    quitf(_ok, "%s", message.c_str());
+}

+ 38 - 0
judger/uoj_judger/builtin/checker/wcmp.cpp

@@ -0,0 +1,38 @@
+#include "testlib.h"
+
+using namespace std;
+
+int main(int argc, char * argv[])
+{
+    setName("compare sequences of tokens");
+    registerTestlibCmd(argc, argv);
+
+    int n = 0;
+    string j, p;
+
+    while (!ans.seekEof() && !ouf.seekEof()) 
+    {
+        n++;
+
+        ans.readWordTo(j);
+        ouf.readWordTo(p);
+        
+        if (j != p)
+            quitf(_wa, "%d%s words differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str());
+    }
+
+    if (ans.seekEof() && ouf.seekEof())
+    {
+        if (n == 1)
+            quitf(_ok, "\"%s\"", compress(j).c_str());
+        else
+            quitf(_ok, "%d tokens", n);
+    }
+    else
+    {
+        if (ans.seekEof())
+            quitf(_wa, "Participant output contains extra tokens");
+        else
+            quitf(_wa, "Unexpected EOF in the participants output");
+    }
+}

+ 27 - 0
judger/uoj_judger/builtin/checker/yesno.cpp

@@ -0,0 +1,27 @@
+#include "testlib.h"
+#include <string>
+
+using namespace std;
+
+const string YES = "YES";
+const string NO = "NO";
+
+int main(int argc, char * argv[])
+{
+    setName("YES or NO (case insensetive)");
+    registerTestlibCmd(argc, argv);
+
+    std::string ja = upperCase(ans.readWord());
+    std::string pa = upperCase(ouf.readWord());
+
+    if (ja != YES && ja != NO)
+        quitf(_fail, "%s or %s expected in answer, but %s found", YES.c_str(), NO.c_str(), compress(ja).c_str());
+
+    if (pa != YES && pa != NO)
+        quitf(_pe, "%s or %s expected, but %s found", YES.c_str(), NO.c_str(), compress(pa).c_str());
+
+    if (ja != pa)
+        quitf(_wa, "expected %s, found %s", compress(ja).c_str(), compress(pa).c_str());
+
+    quitf(_ok, "answer is %s", ja.c_str());
+}

+ 265 - 0
judger/uoj_judger/builtin/judger/judger.cpp

@@ -0,0 +1,265 @@
+#include "uoj_judger.h"
+
+struct SubtaskInfo {
+	bool passed;
+	int score;
+
+	SubtaskInfo() {
+	}
+	SubtaskInfo(const bool &_p, const int &_s)
+		: passed(_p), score(_s){}
+};
+
+void ordinary_test() {
+	int n = conf_int("n_tests", 10);
+	int m = conf_int("n_ex_tests", 0);
+	int nT = conf_int("n_subtasks", 0);
+
+
+	if (conf_is ("allow_proc", "on"))
+		setenv ("UOJ_ALLOW_PROC", "1", 1);
+
+	if (!conf_is("submit_answer", "on")) {
+		report_judge_status_f("Compiling");
+		RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
+		if (!c_ret.succeeded) {
+			end_judge_compile_error(c_ret);
+		}
+	}
+
+	bool passed = true;
+	if (nT == 0) {
+		for (int i = 1; i <= n; i++) {
+			report_judge_status_f("Judging Test #%d", i);
+			PointInfo po = test_point("answer", i);
+			if (po.scr != 100) {
+				passed = false;
+			}
+			po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
+			add_point_info(po);
+		}
+	} else if (nT == 1) {
+		for (int i = 1; i <= n; i++) {
+			report_judge_status_f("Judging Test #%d", i);
+			PointInfo po = test_point("answer", i);
+			if (po.scr != 100) {
+				passed = false;
+				po.scr = i == 1 ? 0 : -100;
+				add_point_info(po);
+				break;
+			} else {
+				po.scr = i == 1 ? 100 : 0;
+				add_point_info(po);
+			}
+		}
+	} else {
+		map<int, SubtaskInfo> subtasks;
+		map<int,int> minScore;
+		for (int t = 1; t <= nT; t++) {
+			string subtaskType = conf_str("subtask_type", t, "packed");
+			int startI = conf_int("subtask_end", t - 1, 0) + 1;
+			int endI = conf_int("subtask_end", t, 0);
+
+			vector<PointInfo> points;
+			minScore[t] = 100;
+
+			vector<int> dependences;
+			if (conf_str("subtask_dependence", t, "none") == "many") {
+				string cur = "subtask_dependence_" + vtos(t);
+				int p = 1;
+				while (conf_int(cur, p, 0) != 0) {
+					dependences.push_back(conf_int(cur, p, 0));
+					p++;
+				}
+			} else if (conf_int("subtask_dependence", t, 0) != 0) {
+				dependences.push_back(conf_int("subtask_dependence", t, 0));
+			}
+			bool skipped = false;
+			for (vector<int>::iterator it = dependences.begin(); it != dependences.end(); it++) {
+				if (subtaskType == "packed") {
+					if (!subtasks[*it].passed) {
+						skipped = true;
+						break;
+					}
+				} else if (subtaskType == "min") {
+					minScore[t] = min(minScore[t], minScore[*it]);
+				}
+			}
+			if (skipped) {
+				add_subtask_info(t, 0, "Skipped", points);
+				continue;
+			}
+
+			int tfull = conf_int("subtask_score", t, 100 / nT);
+			int tscore = scale_score(minScore[t], tfull);
+			string info = "Accepted";
+			for (int i = startI; i <= endI; i++) {
+				report_judge_status_f("Judging Test #%d of Subtask #%d", i, t);
+				PointInfo po = test_point("answer", i);
+				if (subtaskType == "packed") {
+					if (po.scr != 100) {
+						passed = false;
+						po.scr = i == startI ? 0 : -tfull;
+						tscore = 0;
+						points.push_back(po);
+						info = po.info;
+						break;
+					} else {
+						po.scr = i == startI ? tfull : 0;
+						tscore = tfull;
+						points.push_back(po);
+					}
+				} else if (subtaskType == "min") {
+					minScore[t] = min(minScore[t], po.scr);
+					if (po.scr != 100) {
+						passed = false;
+					}
+					po.scr = scale_score(po.scr, tfull);
+					if (po.scr <= tscore) {
+						tscore = po.scr;
+						points.push_back(po);
+						info = po.info;
+					} else {
+						points.push_back(po);
+					}
+				}
+			}
+
+			subtasks[t] = SubtaskInfo(info == "Accepted", tscore);
+
+			add_subtask_info(t, tscore, info, points);
+		}
+	}
+	if (conf_is("submit_answer", "on") || !passed) {
+		end_judge_ok();
+	}
+
+	tot_score = 100;
+	for (int i = 1; i <= m; i++) {
+		report_judge_status_f("Judging Extra Test #%d", i);
+		PointInfo po = test_point("answer", -i);
+		if (po.scr != 100) {
+			po.num = -1;
+			po.info = "Extra Test Failed : " + po.info + " on " + vtos(i);
+			po.scr = -3;
+			add_point_info(po);
+			end_judge_ok();
+		}
+	}
+	if (m != 0) {
+		PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", "");
+		add_point_info(po);
+	}
+	end_judge_ok();
+}
+
+void hack_test() {
+	if (conf_is("submit_answer", "on")) {
+		end_judge_judgement_failed("Hack is not supported in this problem.");
+	} else {
+		RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
+		if (!c_ret.succeeded) {
+			end_judge_compile_error(c_ret);
+		}
+		TestPointConfig tpc;
+		tpc.input_file_name = work_path + "/hack_input.txt";
+		tpc.output_file_name = work_path + "/pro_output.txt";
+		tpc.answer_file_name = work_path + "/std_output.txt";
+
+		PointInfo po = test_hack_point("answer", tpc);
+		add_point_info(po);
+		end_judge_ok();
+	}
+}
+
+void sample_test() {
+	if (conf_is("submit_answer", "on")) {
+		int n = conf_int("n_tests", 10);
+		for (int i = 1; i <= n; i++) {
+			report_judge_status_f("Judging Test #%d", i);
+			if (conf_is("check_existence_only_in_sample_test", "on")) {
+				TestPointConfig tpc = TestPointConfig();
+				tpc.auto_complete(i);
+
+				string usrout = file_preview(tpc.output_file_name);
+				if (usrout == "") {
+					add_point_info(PointInfo(i, 0, -1, -1,
+							"default",
+							file_preview(tpc.input_file_name), usrout,
+							"wrong answer empty file\n"));
+				} else {
+					PointInfo po = PointInfo(i, 100, -1, -1,
+							"default",
+							file_preview(tpc.input_file_name), usrout,
+							"ok nonempty file\n");
+					po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
+					add_point_info(po);
+				}
+			} else {
+				PointInfo po = test_point("answer", i);
+				if (po.scr != 0) {
+					po.info = "Accepted";
+					po.scr = 100;
+				}
+				po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
+				po.res = "no comment";
+				add_point_info(po);
+			}
+		}
+		end_judge_ok();
+	} else {
+		report_judge_status_f("Compiling");
+		RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
+		if (!c_ret.succeeded) {
+			end_judge_compile_error(c_ret);
+		}
+
+		int n = conf_int("n_sample_tests", 0);
+		bool passed = true;
+		for (int i = 1; i <= n; i++) {
+			report_judge_status_f("Judging Sample Test #%d", i);
+			PointInfo po = test_point("answer", -i);
+			po.num = i;
+			if (po.scr != 100) {
+				passed = false;
+			}
+			po.scr = scale_score(po.scr, 100 / n);
+			add_point_info(po);
+		}
+		if (passed) {
+			tot_score = 100;
+		}
+		end_judge_ok();
+	}
+}
+
+void custom_test() {
+	if (conf_is("submit_answer", "on")) {
+		end_judge_judgement_failed("Custom test is not supported in this problem.");
+	} else {
+		report_judge_status_f("Compiling");
+		RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
+		if (!c_ret.succeeded) {
+			end_judge_compile_error(c_ret);
+		}
+		
+		report_judge_status_f("Judging");
+		add_custom_test_info(ordinary_custom_test("answer"));
+		
+		end_judge_ok();
+	}
+}
+
+int main(int argc, char **argv) {
+	judger_init(argc, argv);
+
+	if (conf_is("test_new_hack_only", "on")) {
+		hack_test();
+	} else if (conf_is("test_sample_only", "on")) {
+		sample_test();
+	} else if (conf_is("custom_test", "on")) {
+		custom_test();
+	} else {
+		ordinary_test();
+	}
+}

+ 1 - 0
judger/uoj_judger/data

@@ -0,0 +1 @@
+/var/uoj_data_copy

+ 3499 - 0
judger/uoj_judger/include/testlib.h

@@ -0,0 +1,3499 @@
+/* 
+ * It is strictly recommended to include "testlib.h" before any other include 
+ * in your code. In this case testlib overrides compiler specific "random()".
+ *
+ * If you can't compile your code and compiler outputs something about 
+ * ambiguous call of "random_shuffle", "rand" or "srand" it means that 
+ * you shouldn't use them. Use "shuffle", and "rnd.next()" instead of them
+ * because these calls produce stable result for any C++ compiler. Read 
+ * sample generator sources for clarification.
+ *
+ * Please read the documentation for class "random_t" and use "rnd" instance in
+ * generators. Probably, these sample calls will be usefull for you:
+ *              rnd.next(); rnd.next(100); rnd.next(1, 2); 
+ *              rnd.next(3.14); rnd.next("[a-z]{1,100}").
+ *
+ * Also read about wnext() to generate off-center random distribution.
+ *
+ * See http://code.google.com/p/testlib/ to get latest version or bug tracker.
+ */
+
+#ifndef _TESTLIB_H_
+#define _TESTLIB_H_
+
+/*
+ * Copyright (c) 2005-2013
+ */
+
+#define VERSION "0.9.5"
+
+/* 
+ * Mike Mirzayanov
+ *
+ * This material is provided "as is", with absolutely no warranty expressed
+ * or implied. Any use is at your own risk.
+ *
+ * Permission to use or copy this software for any purpose is hereby granted 
+ * without fee, provided the above notices are retained on all copies.
+ * Permission to modify the code and to distribute modified code is granted,
+ * provided the above notices are retained, and a notice that the code was
+ * modified is included with the above copyright notice.
+ *
+ */
+
+/* NOTE: This file contains testlib library for C++.
+ *
+ *   Check, using testlib running format:
+ *     check.exe <Input_File> <Output_File> <Answer_File> [<Result_File> [-appes]],
+ *   If result file is specified it will contain results.
+ *
+ *   Validator, using testlib running format:                                          
+ *     validator.exe < input.txt,
+ *   It will return non-zero exit code and writes message to standard output.
+ *
+ *   Generator, using testlib running format:                                          
+ *     gen.exe [parameter-1] [parameter-2] [... paramerter-n]
+ *   You can write generated test(s) into standard output or into the file(s).
+ *
+ *   Interactor, using testlib running format:                                          
+ *     interactor.exe <Input_File> <Output_File> [<Answer_File> [<Result_File> [-appes]]],
+ *   Reads test from inf (mapped to args[1]), writes result to tout (mapped to argv[2],
+ *   can be judged by checker later), reads program output from ouf (mapped to stdin),
+ *   writes output to program via stdout (use cout, printf, etc).
+ */
+
+const char* latestFeatures[] = {
+                          "Removed disable buffers for interactive problems, because it works unexpectedly in wine",
+                          "InStream over string: constructor of InStream from base InStream to inherit policies and std::string",
+                          "Added expectedButFound quit function, examples: expectedButFound(_wa, 10, 20), expectedButFound(_fail, ja, pa, \"[n=%d,m=%d]\", n, m)",
+                          "Fixed incorrect interval parsing in patterns",
+                          "Use registerGen(argc, argv, 1) to develop new generator, use registerGen(argc, argv, 0) to compile old generators (originally created for testlib under 0.8.7)",
+                          "Introduced disableFinalizeGuard() to switch off finalization checkings",
+                          "Use join() functions to format a range of items as a single string (separated by spaces or other separators)",
+                          "Use -DENABLE_UNEXPECTED_EOF to enable special exit code (by default, 8) in case of unexpected eof. It is good idea to use it in interactors",
+                          "Use -DUSE_RND_AS_BEFORE_087 to compile in compatibility mode with random behavior of versions before 0.8.7",
+                          "Fixed bug with nan in stringToDouble", 
+                          "Fixed issue around overloads for size_t on x64",  
+                          "Added attribute 'points' to the XML output in case of result=_points",  
+                          "Exit codes can be customized via macros, e.g. -DPE_EXIT_CODE=14",  
+                          "Introduced InStream function readWordTo/readTokenTo/readStringTo/readLineTo for faster reading",  
+                          "Introduced global functions: format(), englishEnding(), upperCase(), lowerCase(), compress()",  
+                          "Manual buffer in InStreams, some IO speed improvements",  
+                          "Introduced quitif(bool, const char* pattern, ...) which delegates to quitf() in case of first argument is true",  
+                          "Introduced guard against missed quitf() in checker or readEof() in validators",  
+                          "Supported readStrictReal/readStrictDouble - to use in validators to check strictly float numbers",  
+                          "Supported registerInteraction(argc, argv)",  
+                          "Print checker message to the stderr instead of stdout",  
+                          "Supported TResult _points to output calculated score, use quitp(...) functions",  
+                          "Fixed to be compilable on Mac",  
+                          "PC_BASE_EXIT_CODE=50 in case of defined TESTSYS",
+                          "Fixed issues 19-21, added __attribute__ format printf",  
+                          "Some bug fixes",  
+                          "ouf.readInt(1, 100) and similar calls return WA",  
+                          "Modified random_t to avoid integer overflow",  
+                          "Truncated checker output [patch by Stepan Gatilov]",  
+                          "Renamed class random -> class random_t",  
+                          "Supported name parameter for read-and-validation methods, like readInt(1, 2, \"n\")",  
+                          "Fixed bug in readDouble()",  
+                          "Improved ensuref(), fixed nextLine to work in case of EOF, added startTest()",  
+                          "Supported \"partially correct\", example: quitf(_pc(13), \"result=%d\", result)",  
+                          "Added shuffle(begin, end), use it instead of random_shuffle(begin, end)",  
+                          "Added readLine(const string& ptrn), fixed the logic of readLine() in the validation mode",  
+                          "Package extended with samples of generators and validators",  
+                          "Written the documentation for classes and public methods in testlib.h",
+                          "Implemented random routine to support generators, use registerGen() to switch it on",
+                          "Implemented strict mode to validate tests, use registerValidation() to switch it on",
+                          "Now ncmp.cpp and wcmp.cpp are return WA if answer is suffix or prefix of the output",
+                          "Added InStream::readLong() and removed InStream::readLongint()",
+                          "Now no footer added to each report by default (use directive FOOTER to switch on)",
+                          "Now every checker has a name, use setName(const char* format, ...) to set it",
+                          "Now it is compatible with TTS (by Kittens Computing)",
+                          "Added \'ensure(condition, message = \"\")\' feature, it works like assert()",
+                          "Fixed compatibility with MS C++ 7.1",
+                          "Added footer with exit code information",
+                          "Added compatibility with EJUDGE (compile with EJUDGE directive)"
+                         };
+
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_DEPRECATE
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+/* Overrides random() for Borland C++. */
+#ifdef __EMSCRIPTEN__
+#define srand __srand_deprecated
+#define rand __rand_deprecated
+#endif
+#define random __random_deprecated
+#include <stdlib.h>
+#include <cstdlib>
+#include <climits>
+#include <algorithm>
+#undef random
+#ifdef __EMSCRIPTEN__
+#undef rand
+#undef srand
+#endif
+
+#include <cstdio>
+#include <cctype>
+#include <string>
+#include <vector>
+#include <cmath>
+#include <sstream>
+#include <fstream>
+#include <cstring>
+#include <limits>
+#include <stdarg.h>
+
+#include <fcntl.h>
+
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
+#if ( _WIN32 || __WIN32__ || _WIN64 || __WIN64__ )
+#   if !defined(_MSC_VER) || _MSC_VER>1400
+#       include <windows.h>
+#   else
+#       define WORD unsigned short
+#   endif
+#   include <io.h>
+#   define ON_WINDOWS
+#else
+#   define WORD unsigned short
+#endif
+
+#ifdef linux
+#include <unistd.h>
+#endif
+
+#ifndef LLONG_MIN
+#define LLONG_MIN   (-9223372036854775807LL - 1)
+#endif
+
+#define LF ((char)10)
+#define CR ((char)13)
+#define TAB ((char)9)
+#define SPACE ((char)' ')
+#define EOFC ((char)26)
+
+#ifndef OK_EXIT_CODE
+#   define OK_EXIT_CODE 0
+#endif
+
+#ifndef WA_EXIT_CODE
+#   ifdef EJUDGE
+#       define WA_EXIT_CODE 5
+#   else
+#       define WA_EXIT_CODE 1
+#   endif
+#endif
+
+#ifndef PE_EXIT_CODE
+#   ifdef EJUDGE
+#       define PE_EXIT_CODE 4
+#   else
+#       define PE_EXIT_CODE 2
+#   endif
+#endif
+
+#ifndef FAIL_EXIT_CODE
+#   ifdef EJUDGE
+#       define FAIL_EXIT_CODE 6
+#   else
+#       define FAIL_EXIT_CODE 3
+#   endif
+#endif
+
+#ifndef DIRT_EXIT_CODE
+#   ifdef EJUDGE
+#       define DIRT_EXIT_CODE 6
+#   else
+#       define DIRT_EXIT_CODE 4
+#   endif
+#endif
+
+#ifndef POINTS_EXIT_CODE
+#   define POINTS_EXIT_CODE 0
+#endif
+
+#ifndef UNEXPECTED_EOF_EXIT_CODE
+#   define UNEXPECTED_EOF_EXIT_CODE 8
+#endif
+
+#ifndef PC_BASE_EXIT_CODE
+#   ifdef TESTSYS
+#       define PC_BASE_EXIT_CODE 50
+#   else
+#       define PC_BASE_EXIT_CODE 0
+#   endif
+#endif
+
+#ifdef __GNUC__
+#    define __TESTLIB_STATIC_ASSERT(condition) typedef void* __testlib_static_assert_type[(condition) ? 1 : -1] __attribute__((unused))
+#else
+#    define __TESTLIB_STATIC_ASSERT(condition) typedef void* __testlib_static_assert_type[(condition) ? 1 : -1]
+#endif
+
+#ifdef ON_WINDOWS
+#define I64 "%I64d"
+#define U64 "%I64u"
+#else
+#define I64 "%lld"
+#define U64 "%llu"
+#endif
+
+#ifdef _MSC_VER
+#   define NORETURN __declspec(noreturn)
+#elif defined __GNUC__
+#   define NORETURN __attribute__ ((noreturn))
+#else
+#   define NORETURN
+#endif
+
+static char __testlib_format_buffer[1024];
+static int __testlib_format_buffer_usage_count = 0;
+
+#define FMT_TO_RESULT(fmt, cstr, result)  std::string result;                              \
+            if (__testlib_format_buffer_usage_count != 0)                                  \
+                __testlib_fail("FMT_TO_RESULT::__testlib_format_buffer_usage_count != 0"); \
+            __testlib_format_buffer_usage_count++;                                         \
+            va_list ap;                                                                    \
+            va_start(ap, fmt);                                                             \
+            std::vsprintf(__testlib_format_buffer, cstr, ap);                              \
+            va_end(ap);                                                                    \
+            result = std::string(__testlib_format_buffer);                                 \
+            __testlib_format_buffer_usage_count--;                                         \
+
+const long long __TESTLIB_LONGLONG_MAX = 9223372036854775807LL;
+
+NORETURN static void __testlib_fail(const std::string& message);
+
+template<typename T>
+static inline T __testlib_abs(const T& x)
+{
+    return x > 0 ? x : -x;
+}
+
+template<typename T>
+static inline T __testlib_min(const T& a, const T& b)
+{
+    return a < b ? a : b;
+}
+
+template<typename T>
+static inline T __testlib_max(const T& a, const T& b)
+{
+    return a > b ? a : b;
+}
+
+static bool __testlib_prelimIsNaN(double r)
+{
+    volatile double ra = r;
+#ifndef __BORLANDC__
+    return ((ra != ra) == true) && ((ra == ra) == false) && ((1.0 > ra) == false) && ((1.0 < ra) == false);
+#else
+    return std::_isnan(ra);
+#endif
+}
+
+static std::string removeDoubleTrailingZeroes(std::string value)
+{
+    while (!value.empty() && value[value.length() - 1] == '0' && value.find('.') != std::string::npos)
+        value = value.substr(0, value.length() - 1);
+    return value + '0';
+}
+
+#ifdef __GNUC__
+__attribute__ ((format (printf, 1, 2)))
+#endif
+std::string format(const char* fmt, ...)
+{
+    FMT_TO_RESULT(fmt, fmt, result);
+    return result;
+}
+
+std::string format(const std::string& fmt, ...)
+{
+    FMT_TO_RESULT(fmt, fmt.c_str(), result);
+    return result;
+}
+
+static std::string __testlib_part(const std::string& s);
+
+static bool __testlib_isNaN(double r)
+{
+    __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long));
+    volatile double ra = r;
+    long long llr1, llr2;
+    std::memcpy((void*)&llr1, (void*)&ra, sizeof(double)); 
+    ra = -ra;
+    std::memcpy((void*)&llr2, (void*)&ra, sizeof(double)); 
+    long long llnan = 0xFFF8000000000000ll;
+    return __testlib_prelimIsNaN(r) || llnan == llr1 || llnan == llr2;
+}
+
+static double __testlib_nan()
+{
+    __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long));
+    long long llnan = 0xFFF8000000000000ll;
+    double nan;
+    std::memcpy(&nan, &llnan, sizeof(double));
+    return nan;
+}
+
+static bool __testlib_isInfinite(double r)
+{
+    volatile double ra = r;
+    return (ra > 1E100 || ra < -1E100);
+}
+
+static void __testlib_set_binary(std::FILE* file)
+{
+#ifdef O_BINARY
+    if (NULL != file)
+    {
+#ifndef __BORLANDC__
+        _setmode(_fileno(file), O_BINARY);
+#else
+        setmode(fileno(file), O_BINARY);
+#endif
+    }
+#endif
+}
+
+static FILE *__testlib_fopen(const char *name, const char *mode)
+{
+#ifdef __EMSCRIPTEN__
+	EM_ASM(
+		try {
+			FS.stat('/cwd');
+		} catch (e) {
+		    FS.mkdir('/cwd');
+		    FS.mount(NODEFS, { root: '.' }, '/cwd');
+		}
+	);
+	return fopen((std::string("/cwd/") + name).c_str(), mode);
+#else
+	return fopen(name, mode);
+#endif
+}
+
+/*
+ * Very simple regex-like pattern.
+ * It used for two purposes: validation and generation.
+ * 
+ * For example, pattern("[a-z]{1,5}").next(rnd) will return
+ * random string from lowercase latin letters with length 
+ * from 1 to 5. It is easier to call rnd.next("[a-z]{1,5}") 
+ * for the same effect. 
+ * 
+ * Another samples:
+ * "mike|john" will generate (match) "mike" or "john";
+ * "-?[1-9][0-9]{0,3}" will generate (match) non-zero integers from -9999 to 9999;
+ * "id-([ac]|b{2})" will generate (match) "id-a", "id-bb", "id-c";
+ * "[^0-9]*" will match sequences (empty or non-empty) without digits, you can't 
+ * use it for generations.
+ *
+ * You can't use pattern for generation if it contains meta-symbol '*'. Also it
+ * is not recommended to use it for char-sets with meta-symbol '^' like [^a-z].
+ *
+ * For matching very simple greedy algorithm is used. For example, pattern
+ * "[0-9]?1" will not match "1", because of greedy nature of matching.
+ * Alternations (meta-symbols "|") are processed with brute-force algorithm, so 
+ * do not use many alternations in one expression.
+ *
+ * If you want to use one expression many times it is better to compile it into
+ * a single pattern like "pattern p("[a-z]+")". Later you can use 
+ * "p.matches(std::string s)" or "p.next(random_t& rd)" to check matching or generate
+ * new string by pattern.
+ * 
+ * Simpler way to read token and check it for pattern matching is "inf.readToken("[a-z]+")".
+ */
+class random_t;
+
+class pattern
+{
+public:
+    /* Create pattern instance by string. */
+    pattern(std::string s);
+    /* Generate new string by pattern and given random_t. */
+    std::string next(random_t& rnd) const;
+    /* Checks if given string match the pattern. */
+    bool matches(const std::string& s) const;
+    /* Returns source string of the pattern. */
+    std::string src() const;
+private:
+    bool matches(const std::string& s, size_t pos) const;
+
+    std::string s;
+    std::vector<pattern> children;
+    std::vector<char> chars;
+    int from;
+    int to;
+};
+
+/* 
+ * Use random_t instances to generate random values. It is preffered
+ * way to use randoms instead of rand() function or self-written 
+ * randoms.
+ *
+ * Testlib defines global variable "rnd" of random_t class.
+ * Use registerGen(argc, argv, 1) to setup random_t seed be command
+ * line (to use latest random generator version).
+ *
+ * Random generates uniformly distributed values if another strategy is
+ * not specified explicitly.
+ */
+class random_t
+{
+private:
+    unsigned long long seed;
+    static const unsigned long long multiplier;
+    static const unsigned long long addend;
+    static const unsigned long long mask;
+    static const int lim;
+
+    long long nextBits(int bits) 
+    {
+        if (bits <= 48)
+        {
+            seed = (seed * multiplier + addend) & mask;
+            return (long long)(seed >> (48 - bits));
+        }
+        else
+        {
+            if (bits > 63)
+                __testlib_fail("random_t::nextBits(int bits): n must be less than 64");
+
+            int lowerBitCount = (random_t::version == 0 ? 31 : 32);
+            return ((nextBits(31) << 32) ^ nextBits(lowerBitCount));
+        }
+    }
+
+public:
+    static int version;
+
+    /* New random_t with fixed seed. */
+    random_t()
+        : seed(3905348978240129619LL)
+    {
+    }
+
+    /* Sets seed by command line. */
+    void setSeed(int argc, char* argv[])
+    {
+        random_t p;
+
+        seed = 3905348978240129619LL;
+        for (int i = 1; i < argc; i++)
+        {
+            std::size_t le = std::strlen(argv[i]);
+            for (std::size_t j = 0; j < le; j++)
+                seed = seed * multiplier + (unsigned int)(argv[i][j]) + addend;
+            seed += multiplier / addend;
+        }
+
+        seed = seed & mask;
+    }
+
+    /* Sets seed by given value. */ 
+    void setSeed(long long _seed)
+    {
+        _seed = (_seed ^ multiplier) & mask;
+        seed = _seed;
+    }
+
+#ifndef __BORLANDC__
+    /* Random string value by given pattern (see pattern documentation). */
+    std::string next(const std::string& ptrn)
+    {
+        pattern p(ptrn);
+        return p.next(*this);
+    }
+#else
+    /* Random string value by given pattern (see pattern documentation). */
+    std::string next(std::string ptrn)
+    {
+        pattern p(ptrn);
+        return p.next(*this);
+    }
+#endif
+
+    /* Random value in range [0, n-1]. */
+    int next(int n)
+    {
+        if (n <= 0)
+            __testlib_fail("random_t::next(int n): n must be positive");
+
+        if ((n & -n) == n)  // n is a power of 2
+            return (int)((n * (long long)nextBits(31)) >> 31);
+
+        const long long limit = INT_MAX / n * n;
+        
+        long long bits;
+        do {
+            bits = nextBits(31);
+        } while (bits >= limit);
+
+        return int(bits % n);
+    }
+
+    /* Random value in range [0, n-1]. */
+    unsigned int next(unsigned int n)
+    {
+        if (n >= INT_MAX)
+            __testlib_fail("random_t::next(unsigned int n): n must be less INT_MAX");
+        return (unsigned int)next(int(n));
+    }
+
+    /* Random value in range [0, n-1]. */
+    long long next(long long n) 
+    {
+        if (n <= 0)
+            __testlib_fail("random_t::next(long long n): n must be positive");
+
+        const long long limit = __TESTLIB_LONGLONG_MAX / n * n;
+        
+        long long bits;
+        do {
+            bits = nextBits(63);
+        } while (bits >= limit);
+
+        return bits % n;
+    }
+
+    /* Random value in range [0, n-1]. */
+    unsigned long long next(unsigned long long n)
+    {
+        if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX))
+            __testlib_fail("random_t::next(unsigned long long n): n must be less LONGLONG_MAX");
+        return (unsigned long long)next((long long)(n));
+    }
+
+    /* Random value in range [0, n-1]. */
+    long next(long n)
+    {
+        return (long)next((long long)(n));
+    }
+
+    /* Random value in range [0, n-1]. */
+    unsigned long next(unsigned long n)
+    {
+        if (n >= (unsigned long)(LONG_MAX))
+            __testlib_fail("random_t::next(unsigned long n): n must be less LONG_MAX");
+        return (unsigned long)next((unsigned long long)(n));
+    }
+
+    /* Returns random value in range [from,to]. */
+    int next(int from, int to)
+    {
+        return int(next((long long)to - from + 1) + from);
+    }
+
+    /* Returns random value in range [from,to]. */
+    unsigned int next(unsigned int from, unsigned int to)
+    {
+        return (unsigned int)(next((long long)to - from + 1) + from);
+    }
+
+    /* Returns random value in range [from,to]. */
+    long long next(long long from, long long to)
+    {
+        return next(to - from + 1) + from;
+    }
+
+    /* Returns random value in range [from,to]. */
+    unsigned long long next(unsigned long long from, unsigned long long to)
+    {
+        if (from > to)
+            __testlib_fail("random_t::next(unsigned long long from, unsigned long long to): from can't not exceed to");
+        return next(to - from + 1) + from;
+    }
+
+    /* Returns random value in range [from,to]. */
+    long next(long from, long to)
+    {
+        return next(to - from + 1) + from;
+    }
+
+    /* Returns random value in range [from,to]. */
+    unsigned long next(unsigned long from, unsigned long to)
+    {
+        if (from > to)
+            __testlib_fail("random_t::next(unsigned long from, unsigned long to): from can't not exceed to");
+        return next(to - from + 1) + from;
+    }
+
+    /* Random double value in range [0, 1). */
+    double next() 
+    {
+        return (double)(((long long)(nextBits(26)) << 27) + nextBits(27)) / (double)(1LL << 53);
+    }
+
+    /* Random double value in range [0, n). */
+    double next(double n)
+    {
+        return n * next();
+    }
+
+    /* Random double value in range [from, to). */
+    double next(double from, double to)
+    {
+        return next(to - from) + from;
+    }
+
+    /* Returns random element from container. */
+    template <typename Container>
+    typename Container::value_type any(const Container& c)
+    {
+        size_t size = c.size();
+        if (size <= 0)
+            __testlib_fail("random_t::any(const Container& c): c.size() must be positive");
+        return *(c.begin() + next(size));
+    }
+
+    /* Returns random element from iterator range. */
+    template <typename Iter>
+    typename Iter::value_type any(const Iter& begin, const Iter& end)
+    {
+        int size = int(end - begin);
+        if (size <= 0)
+            __testlib_fail("random_t::any(const Iter& begin, const Iter& end): range must have positive length");
+        return *(begin + next(size));
+    }
+
+    /* Random string value by given pattern (see pattern documentation). */
+#ifdef __GNUC__
+    __attribute__ ((format (printf, 2, 3)))
+#endif
+    std::string next(const char* format, ...)
+    {
+        FMT_TO_RESULT(format, format, ptrn);
+        return next(ptrn);
+    }
+
+    /* 
+     * Weighted next. If type == 0 than it is usual "next()".
+     *
+     * If type = 1, than it returns "max(next(), next())"
+     * (the number of "max" functions equals to "type").
+     *
+     * If type < 0, than "max" function replaces with "min".
+     */
+    int wnext(int n, int type)
+    {
+        if (n <= 0)
+            __testlib_fail("random_t::wnext(int n, int type): n must be positive");
+        
+        if (abs(type) < random_t::lim)
+        {
+            int result = next(n);
+
+            for (int i = 0; i < +type; i++)
+                result = __testlib_max(result, next(n));
+            
+            for (int i = 0; i < -type; i++)
+                result = __testlib_min(result, next(n));
+
+            return result;
+        }
+        else
+        {
+            double p;
+            
+            if (type > 0)
+                p = std::pow(next() + 0.0, 1.0 / (type + 1));
+            else
+                p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1));
+
+            return int(n * p);
+        }
+    }
+    
+    /* See wnext(int, int). It uses the same algorithms. */
+    long long wnext(long long n, int type)
+    {
+        if (n <= 0)
+            __testlib_fail("random_t::wnext(long long n, int type): n must be positive");
+        
+        if (abs(type) < random_t::lim)
+        {
+            long long result = next(n);
+
+            for (int i = 0; i < +type; i++)
+                result = __testlib_max(result, next(n));
+            
+            for (int i = 0; i < -type; i++)
+                result = __testlib_min(result, next(n));
+
+            return result;
+        }
+        else
+        {
+            double p;
+            
+            if (type > 0)
+                p = std::pow(next() + 0.0, 1.0 / (type + 1));
+            else
+                p = std::pow(next() + 0.0, - type + 1);
+
+            return __testlib_min(__testlib_max((long long)(double(n) * p), 0LL), n - 1LL);
+        }
+    }
+    
+    /* See wnext(int, int). It uses the same algorithms. */
+    double wnext(int type)
+    {
+        if (abs(type) < random_t::lim)
+        {
+            double result = next();
+
+            for (int i = 0; i < +type; i++)
+                result = __testlib_max(result, next());
+            
+            for (int i = 0; i < -type; i++)
+                result = __testlib_min(result, next());
+
+            return result;
+        }
+        else
+        {
+            double p;
+            
+            if (type > 0)
+                p = std::pow(next() + 0.0, 1.0 / (type + 1));
+            else
+                p = std::pow(next() + 0.0, - type + 1);
+
+            return p;
+        }
+    }
+    
+    /* See wnext(int, int). It uses the same algorithms. */
+    double wnext(double n, int type)
+    {
+        if (n <= 0)
+            __testlib_fail("random_t::wnext(double n, int type): n must be positive");
+
+        if (abs(type) < random_t::lim)
+        {
+            double result = next();
+
+            for (int i = 0; i < +type; i++)
+                result = __testlib_max(result, next());
+            
+            for (int i = 0; i < -type; i++)
+                result = __testlib_min(result, next());
+
+            return n * result;
+        }
+        else
+        {
+            double p;
+            
+            if (type > 0)
+                p = std::pow(next() + 0.0, 1.0 / (type + 1));
+            else
+                p = std::pow(next() + 0.0, - type + 1);
+
+            return n * p;
+        }
+    }
+
+    /* See wnext(int, int). It uses the same algorithms. */
+    unsigned int wnext(unsigned int n, int type)
+    {
+        if (n >= INT_MAX)
+            __testlib_fail("random_t::wnext(unsigned int n, int type): n must be less INT_MAX");
+        return (unsigned int)wnext(int(n), type);
+    }
+    
+    /* See wnext(int, int). It uses the same algorithms. */
+    unsigned long long wnext(unsigned long long n, int type)
+    {
+        if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX))
+            __testlib_fail("random_t::wnext(unsigned long long n, int type): n must be less LONGLONG_MAX");
+
+        return (unsigned long long)wnext((long long)(n), type);
+    }
+
+    /* See wnext(int, int). It uses the same algorithms. */
+    long wnext(long n, int type)
+    {
+        return (long)wnext((long long)(n), type);
+    }
+    
+    /* See wnext(int, int). It uses the same algorithms. */
+    unsigned long wnext(unsigned long n, int type)
+    {
+        if (n >= (unsigned long)(LONG_MAX))
+            __testlib_fail("random_t::wnext(unsigned long n, int type): n must be less LONG_MAX");
+
+        return (unsigned long)wnext((unsigned long long)(n), type);
+    }
+
+    /* Returns weighted random value in range [from, to]. */
+    int wnext(int from, int to, int type)
+    {
+        return wnext(to - from + 1, type) + from;
+    }
+    
+    /* Returns weighted random value in range [from, to]. */
+    int wnext(unsigned int from, unsigned int to, int type)
+    {
+        return int(wnext(to - from + 1, type) + from);
+    }
+    
+    /* Returns weighted random value in range [from, to]. */
+    long long wnext(long long from, long long to, int type)
+    {
+        return wnext(to - from + 1, type) + from;
+    }
+    
+    /* Returns weighted random value in range [from, to]. */
+    unsigned long long wnext(unsigned long long from, unsigned long long to, int type)
+    {
+        if (from > to)
+            __testlib_fail("random_t::wnext(unsigned long long from, unsigned long long to, int type): from can't not exceed to");
+        return wnext(to - from + 1, type) + from;
+    }
+    
+    /* Returns weighted random value in range [from, to]. */
+    long wnext(long from, long to, int type)
+    {
+        return wnext(to - from + 1, type) + from;
+    }
+    
+    /* Returns weighted random value in range [from, to]. */
+    unsigned long wnext(unsigned long from, unsigned long to, int type)
+    {
+        if (from > to)
+            __testlib_fail("random_t::wnext(unsigned long from, unsigned long to, int type): from can't not exceed to");
+        return wnext(to - from + 1, type) + from;
+    }
+    
+    /* Returns weighted random double value in range [from, to). */
+    double wnext(double from, double to, int type)
+    {
+        return wnext(to - from, type) + from;
+    }
+
+    /* Returns weighted random element from container. */
+    template <typename Container>
+    typename Container::value_type wany(const Container& c, int type)
+    {
+        size_t size = c.size();
+        if (size <= 0)
+            __testlib_fail("random_t::wany(const Container& c, int type): c.size() must be positive");
+        return *(c.begin() + wnext(size, type));
+    }
+
+    /* Returns weighted random element from iterator range. */
+    template <typename Iter>
+    typename Iter::value_type wany(const Iter& begin, const Iter& end, int type)
+    {
+        int size = int(end - begin);
+        if (size <= 0)
+            __testlib_fail("random_t::any(const Iter& begin, const Iter& end, int type): range must have positive length");
+        return *(begin + wnext(size, type));
+    }
+};
+
+const int random_t::lim = 25;
+const unsigned long long random_t::multiplier = 0x5DEECE66DLL;
+const unsigned long long random_t::addend = 0xBLL;
+const unsigned long long random_t::mask = (1LL << 48) - 1;
+int random_t::version = -1;
+
+/* Pattern implementation */
+bool pattern::matches(const std::string& s) const
+{
+    return matches(s, 0);
+}
+
+static bool __pattern_isSlash(const std::string& s, size_t pos)
+{
+    return s[pos] == '\\';
+}
+
+#ifdef __GNUC__
+__attribute__((pure))
+#endif
+static bool __pattern_isCommandChar(const std::string& s, size_t pos, char value)
+{
+    if (pos >= s.length())
+        return false;
+
+    int slashes = 0;
+
+    int before = int(pos) - 1;
+    while (before >= 0 && s[before] == '\\')
+        before--, slashes++;
+
+    return slashes % 2 == 0 && s[pos] == value;
+}
+
+static char __pattern_getChar(const std::string& s, size_t& pos)
+{
+    if (__pattern_isSlash(s, pos))
+        pos += 2;
+    else
+        pos++;
+
+    return s[pos - 1];
+}
+
+#ifdef __GNUC__
+__attribute__((pure))
+#endif
+static int __pattern_greedyMatch(const std::string& s, size_t pos, const std::vector<char> chars)
+{
+    int result = 0;
+
+    while (pos < s.length())
+    {
+        char c = s[pos++];
+        if (!std::binary_search(chars.begin(), chars.end(), c))
+            break;
+        else
+            result++;
+    }
+
+    return result;
+}
+
+std::string pattern::src() const
+{
+    return s;
+}
+
+bool pattern::matches(const std::string& s, size_t pos) const
+{
+    std::string result;
+
+    if (to > 0)
+    {
+        int size = __pattern_greedyMatch(s, pos, chars);
+        if (size < from)
+            return false;
+        if (size > to)
+            size = to;
+        pos += size;
+    }
+
+    if (children.size() > 0)
+    {
+        for (size_t child = 0; child < children.size(); child++)
+            if (children[child].matches(s, pos))
+                return true;
+        return false;
+    }
+    else
+        return pos == s.length();
+}
+
+std::string pattern::next(random_t& rnd) const
+{
+    std::string result;
+    result.reserve(20);
+
+    if (to == INT_MAX)
+        __testlib_fail("pattern::next(random_t& rnd): can't process character '*' for generation");
+
+    if (to > 0)
+    {
+        int count = rnd.next(to - from + 1) + from;
+        for (int i = 0; i < count; i++)
+            result += chars[rnd.next(int(chars.size()))];
+    }
+
+    if (children.size() > 0)
+    {
+        int child = rnd.next(int(children.size()));
+        result += children[child].next(rnd);
+    }
+
+    return result;
+}
+
+static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, int& to)
+{
+    if (pos >= s.length())
+    {
+        from = to = 1;
+        return;
+    }
+        
+    if (__pattern_isCommandChar(s, pos, '{'))
+    {
+        std::vector<std::string> parts;
+        std::string part;
+
+        pos++;
+
+        while (pos < s.length() && !__pattern_isCommandChar(s, pos, '}'))
+        {
+            if (__pattern_isCommandChar(s, pos, ','))
+                parts.push_back(part), part = "", pos++;
+            else
+                part += __pattern_getChar(s, pos);
+        }
+
+        if (part != "")
+            parts.push_back(part);
+
+        if (!__pattern_isCommandChar(s, pos, '}'))
+            __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+        pos++;
+
+        if (parts.size() < 1 || parts.size() > 2)
+            __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+        std::vector<int> numbers;
+
+        for (size_t i = 0; i < parts.size(); i++)
+        {
+            if (parts[i].length() == 0)
+                __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+            int number;
+            if (std::sscanf(parts[i].c_str(), "%d", &number) != 1)
+                __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+            numbers.push_back(number);
+        }
+
+        if (numbers.size() == 1)
+            from = to = numbers[0];
+        else
+            from = numbers[0], to = numbers[1];
+
+        if (from > to)
+            __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+    }
+    else
+    {
+        if (__pattern_isCommandChar(s, pos, '?'))
+        {
+            from = 0, to = 1, pos++;
+            return;
+        }
+
+        if (__pattern_isCommandChar(s, pos, '*'))
+        {
+            from = 0, to = INT_MAX, pos++;
+            return;
+        }
+
+        if (__pattern_isCommandChar(s, pos, '+'))
+        {
+            from = 1, to = INT_MAX, pos++;
+            return;
+        }
+        
+        from = to = 1;
+    }
+}
+
+static std::vector<char> __pattern_scanCharSet(const std::string& s, size_t& pos)
+{
+    if (pos >= s.length())
+        __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+    std::vector<char> result;
+
+    if (__pattern_isCommandChar(s, pos, '['))
+    {
+        pos++;
+        bool negative = __pattern_isCommandChar(s, pos, '^');
+
+        char prev = 0;
+
+        while (pos < s.length() && !__pattern_isCommandChar(s, pos, ']'))
+        {
+            if (__pattern_isCommandChar(s, pos, '-') && prev != 0)
+            {
+                pos++;
+
+                if (pos + 1 == s.length() || __pattern_isCommandChar(s, pos, ']'))
+                {
+                    result.push_back(prev);
+                    prev = '-';
+                    continue;
+                }
+
+                char next = __pattern_getChar(s, pos);
+                if (prev > next)
+                    __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+                for (char c = prev; c != next; c++)
+                    result.push_back(c);
+                result.push_back(next);
+
+                prev = 0;
+            }
+            else
+            {
+                if (prev != 0)
+                    result.push_back(prev);
+                prev = __pattern_getChar(s, pos);
+            }
+        }
+
+        if (prev != 0)
+            result.push_back(prev);
+
+        if (!__pattern_isCommandChar(s, pos, ']'))
+            __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+        pos++;
+
+        if (negative)
+        {
+            std::sort(result.begin(), result.end());
+            std::vector<char> actuals;
+            for (int code = 0; code < 255; code++)
+            {
+                char c = char(code);
+                if (!std::binary_search(result.begin(), result.end(), c))
+                    actuals.push_back(c);
+            }
+            result = actuals;
+        }
+
+        std::sort(result.begin(), result.end());
+    }
+    else
+        result.push_back(__pattern_getChar(s, pos));
+
+    return result;
+}
+
+pattern::pattern(std::string s): s(s), from(0), to(0)
+{
+    std::string t;
+    for (size_t i = 0; i < s.length(); i++)
+        if (!__pattern_isCommandChar(s, i, ' '))
+            t += s[i];
+    s = t;
+
+    int opened = 0;
+    int firstClose = -1;
+    std::vector<int> seps;
+
+    for (size_t i = 0; i < s.length(); i++)
+    {
+        if (__pattern_isCommandChar(s, i, '('))
+        {
+            opened++;
+            continue;
+        }
+
+        if (__pattern_isCommandChar(s, i, ')'))
+        {
+            opened--;
+            if (opened == 0 && firstClose == -1)
+                firstClose = int(i);
+            continue;
+        }
+        
+        if (opened < 0)
+            __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+        if (__pattern_isCommandChar(s, i, '|') && opened == 0)
+            seps.push_back(int(i));
+    }
+
+    if (opened != 0)
+        __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\"");
+
+    if (seps.size() == 0 && firstClose + 1 == (int)s.length() 
+            && __pattern_isCommandChar(s, 0, '(') && __pattern_isCommandChar(s, s.length() - 1, ')'))
+    {
+        children.push_back(pattern(s.substr(1, s.length() - 2)));
+    }
+    else
+    {
+        if (seps.size() > 0)
+        {
+            seps.push_back(s.length());
+            int last = 0;
+
+            for (size_t i = 0; i < seps.size(); i++)
+            {
+                children.push_back(pattern(s.substr(last, seps[i] - last)));
+                last = seps[i] + 1;
+            }
+        }
+        else
+        {
+            size_t pos = 0;
+            chars = __pattern_scanCharSet(s, pos);
+            __pattern_scanCounts(s, pos, from, to);
+            if (pos < s.length())
+                children.push_back(pattern(s.substr(pos)));
+        }
+    }
+}
+/* End of pattern implementation */
+
+template <typename C>
+inline bool isEof(C c)
+{
+    return (c == EOF || c == EOFC);
+}
+
+template <typename C>
+inline bool isEoln(C c)
+{
+    return (c == LF || c == CR);
+}
+
+template<typename C>
+inline bool isBlanks(C c)
+{
+    return (c == LF || c == CR || c == SPACE || c == TAB);
+}
+
+enum TMode
+{
+    _input, _output, _answer
+};
+
+/* Outcomes 6-15 are reserved for future use. */
+enum TResult
+{
+    _ok = 0,
+    _wa = 1,
+    _pe = 2,
+    _fail = 3,
+    _dirt = 4,
+    _points = 5,
+    _unexpected_eof = 8,
+    _partially = 16
+};
+
+enum TTestlibMode
+{
+    _unknown, _checker, _validator, _generator, _interactor
+};
+
+#define _pc(exitCode) (TResult(_partially + (exitCode)))
+
+/* Outcomes 6-15 are reserved for future use. */
+const std::string outcomes[] = {
+    "accepted",
+    "wrong-answer",
+    "presentation-error",
+    "fail",
+    "fail",
+#ifndef PCMS2
+    "points",
+#else
+    "relative-scoring",
+#endif
+    "reserved",
+    "reserved",
+    "unexpected-eof",
+    "reserved",
+    "reserved",
+    "reserved",
+    "reserved",
+    "reserved",
+    "reserved",
+    "reserved",
+    "partially-correct"
+};
+
+class InputStreamReader
+{
+public:
+    virtual int curChar() = 0;    
+    virtual int nextChar() = 0;    
+    virtual void skipChar() = 0;
+    virtual void unreadChar(int c) = 0;
+    virtual std::string getName() = 0;
+    virtual bool eof() = 0;
+    virtual ~InputStreamReader() = 0;
+};
+
+InputStreamReader::~InputStreamReader()
+{
+    // No operations.
+}
+
+class StringInputStreamReader: public InputStreamReader
+{
+private:
+    std::string s;
+    size_t pos;
+
+public:
+    StringInputStreamReader(const std::string& content): s(content), pos(0)
+    {
+        // No operations.
+    }
+
+    int curChar()
+    {
+        if (pos >= s.length())
+            return EOF;
+        else
+        {
+            return s[pos];
+        }
+    }
+
+    int nextChar()
+    {
+        if (pos >= s.length())
+            return EOF;
+        else
+            return s[pos++];
+    }
+
+    void skipChar()
+    {
+        pos++;
+    }
+
+    void unreadChar(int c)
+    {   
+        if (pos == 0)
+            __testlib_fail("FileFileInputStreamReader::unreadChar(int): pos == 0.");
+        pos--;
+        if (pos < s.length())
+            s[pos] = char(c);
+    }
+
+    std::string getName()
+    {
+        return __testlib_part(s);
+    }
+
+    bool eof()
+    {
+        return pos >= s.length();
+    }
+};
+
+class FileInputStreamReader: public InputStreamReader
+{
+private:
+    std::FILE* file;
+    std::string name;
+
+public:
+    FileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name)
+    {
+        // No operations.
+    }
+
+    int curChar()
+    {
+        if (feof(file))
+            return EOF;
+        else
+        {
+            int c = getc(file);
+            ungetc(c, file);
+            return c;
+        }
+    }
+
+    int nextChar()
+    {
+        if (feof(file))
+            return EOF;
+        else
+            return getc(file);
+    }
+
+    void skipChar()
+    {
+        getc(file);
+    }
+
+    void unreadChar(int c)
+    {   
+        ungetc(c, file);
+    }
+
+    std::string getName()
+    {
+        return name;
+    }
+
+    bool eof()
+    {
+        if (feof(file))
+            return true;
+        else
+        {
+            int c = getc(file);
+            bool result = (c == EOF);
+            ungetc(c, file);
+            return result;
+        }
+    }
+};
+
+class BufferedFileInputStreamReader: public InputStreamReader
+{
+private:
+    static const size_t BUFFER_SIZE;
+    static const size_t MAX_UNREAD_COUNT; 
+    
+    std::FILE* file;
+    char* buffer;
+    bool* isEof;
+    int bufferPos;
+    size_t bufferSize;
+
+    std::string name;
+
+    bool refill()
+    {
+        if (NULL == file)
+            __testlib_fail("BufferedFileInputStreamReader: file == NULL (" + getName() + ")");
+
+        if (bufferPos >= int(bufferSize))
+        {
+            size_t readSize = fread(
+                buffer + MAX_UNREAD_COUNT,
+                1,
+                BUFFER_SIZE - MAX_UNREAD_COUNT,
+                file
+            );
+
+            if (readSize < BUFFER_SIZE - MAX_UNREAD_COUNT
+                    && ferror(file))
+                __testlib_fail("BufferedFileInputStreamReader: unable to read (" + getName() + ")");
+
+            bufferSize = MAX_UNREAD_COUNT + readSize;
+            bufferPos = int(MAX_UNREAD_COUNT);
+            std::memset(isEof + MAX_UNREAD_COUNT, 0, sizeof(isEof[0]) * readSize);
+
+            return readSize > 0;
+        }
+        else
+            return true;
+    }
+
+public:
+    BufferedFileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name)
+    {
+        buffer = new char[BUFFER_SIZE];
+        isEof = new bool[BUFFER_SIZE];
+        bufferSize = MAX_UNREAD_COUNT;
+        bufferPos = int(MAX_UNREAD_COUNT);
+    }
+
+    ~BufferedFileInputStreamReader()
+    {
+        if (NULL != buffer)
+        {
+            delete[] buffer;
+            buffer = NULL;
+        }
+        if (NULL != isEof)
+        {
+            delete[] isEof;
+            isEof = NULL;
+        }
+    }
+
+    int curChar()
+    {
+        if (!refill())
+            return EOF;
+
+        return isEof[bufferPos] ? EOF : buffer[bufferPos];
+    }
+
+    int nextChar()
+    {
+        if (!refill())
+            return EOF;
+
+        return isEof[bufferPos] ? EOF : buffer[bufferPos++];
+    }
+
+    void skipChar()
+    {
+        bufferPos++;
+    }
+
+    void unreadChar(int c)
+    {   
+        bufferPos--;
+        if (bufferPos < 0)
+            __testlib_fail("BufferedFileInputStreamReader::unreadChar(int): bufferPos < 0");
+        isEof[bufferPos] = (c == EOF);
+        buffer[bufferPos] = char(c == EOF ? EOFC : c);
+    }
+
+    std::string getName()
+    {
+        return name;
+    }
+    
+    bool eof()
+    {
+        return !refill() || EOF == curChar();
+    }
+};
+
+const size_t BufferedFileInputStreamReader::BUFFER_SIZE = 1000000;
+const size_t BufferedFileInputStreamReader::MAX_UNREAD_COUNT = 100; 
+
+/*
+ * Streams to be used for reading data in checkers or validators.
+ * Each read*() method moves pointer to the next character after the
+ * read value.
+ */
+struct InStream
+{
+    /* Do not use them. */
+    InStream();
+    ~InStream();
+
+    /* Wrap std::string with InStream. */
+    InStream(const InStream& baseStream, std::string content);
+
+    InputStreamReader* reader;
+
+    std::FILE* file;
+    std::string name;
+    TMode mode;
+    bool opened;
+    bool stdfile;
+    bool strict;
+
+    int wordReserveSize;
+    std::string _tmpReadToken;
+
+    void init(std::string fileName, TMode mode);
+    void init(std::FILE* f, TMode mode);
+
+    /* Moves stream pointer to the first non-white-space character or EOF. */ 
+    void skipBlanks();
+    
+    /* Returns current character in the stream. Doesn't remove it from stream. */
+    char curChar();
+    /* Moves stream pointer one character forward. */
+    void skipChar();
+    /* Returns current character and moves pointer one character forward. */
+    char nextChar();
+    
+    /* Returns current character and moves pointer one character forward. */
+    char readChar();
+    /* As "readChar()" but ensures that the result is equal to given parameter. */
+    char readChar(char c);
+    /* As "readChar()" but ensures that the result is equal to the space (code=32). */
+    char readSpace();
+    /* Puts back the character into the stream. */
+    void unreadChar(char c);
+
+    /* Reopens stream, you should not use it. */
+    void reset();
+    /* Checks that current position is EOF. If not it doesn't move stream pointer. */
+    bool eof();
+    /* Moves pointer to the first non-white-space character and calls "eof()". */
+    bool seekEof();
+
+    /* 
+     * Checks that current position contains EOLN. 
+     * If not it doesn't move stream pointer. 
+     * In strict mode expects "#13#10" for windows or "#10" for other platforms.
+     */
+    bool eoln();
+    /* Moves pointer to the first non-space and non-tab character and calls "eoln()". */
+    bool seekEoln();
+
+    /* Moves stream pointer to the first character of the next line (if exists). */
+    void nextLine();
+
+    /* 
+     * Reads new token. Ignores white-spaces into the non-strict mode 
+     * (strict mode is used in validators usually). 
+     */
+    std::string readWord();
+    /* The same as "readWord()", it is preffered to use "readToken()". */
+    std::string readToken();
+    /* The same as "readWord()", but ensures that token matches to given pattern. */
+    std::string readWord(const std::string& ptrn, const std::string& variableName = "");
+    std::string readWord(const pattern& p, const std::string& variableName = "");
+    /* The same as "readToken()", but ensures that token matches to given pattern. */
+    std::string readToken(const std::string& ptrn, const std::string& variableName = "");
+    std::string readToken(const pattern& p, const std::string& variableName = "");
+
+    void readWordTo(std::string& result);
+    void readWordTo(std::string& result, const pattern& p, const std::string& variableName = "");
+    void readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName = "");
+
+    void readTokenTo(std::string& result);
+    void readTokenTo(std::string& result, const pattern& p, const std::string& variableName = "");
+    void readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName = "");
+
+    /* 
+     * Reads new long long value. Ignores white-spaces into the non-strict mode 
+     * (strict mode is used in validators usually). 
+     */
+    long long readLong();
+    /* 
+     * Reads new int. Ignores white-spaces into the non-strict mode 
+     * (strict mode is used in validators usually). 
+     */
+    int readInteger();
+    /* 
+     * Reads new int. Ignores white-spaces into the non-strict mode 
+     * (strict mode is used in validators usually). 
+     */
+    int readInt();
+
+    /* As "readLong()" but ensures that value in the range [minv,maxv]. */
+    long long readLong(long long minv, long long maxv, const std::string& variableName = "");
+    /* As "readInteger()" but ensures that value in the range [minv,maxv]. */
+    int readInteger(int minv, int maxv, const std::string& variableName = "");
+    /* As "readInt()" but ensures that value in the range [minv,maxv]. */
+    
+	int readInt(int minv, int maxv, const std::string& variableName = "");
+    /* 
+     * Reads new double. Ignores white-spaces into the non-strict mode 
+     * (strict mode is used in validators usually). 
+     */
+    double readReal();
+    /* 
+     * Reads new double. Ignores white-spaces into the non-strict mode 
+     * (strict mode is used in validators usually). 
+     */
+    double readDouble();
+    
+    /* As "readReal()" but ensures that value in the range [minv,maxv]. */
+    double readReal(double minv, double maxv, const std::string& variableName = "");
+    /* As "readDouble()" but ensures that value in the range [minv,maxv]. */
+    double readDouble(double minv, double maxv, const std::string& variableName = "");
+    
+    /* 
+     * As "readReal()" but ensures that value in the range [minv,maxv] and
+     * number of digit after the decimal point is in range [minAfterPointDigitCount,maxAfterPointDigitCount]
+     * and number is in the form "[-]digit(s)[.digit(s)]".
+     */
+    double readStrictReal(double minv, double maxv,
+            int minAfterPointDigitCount, int maxAfterPointDigitCount,
+            const std::string& variableName = "");
+    /* 
+     * As "readDouble()" but ensures that value in the range [minv,maxv] and
+     * number of digit after the decimal point is in range [minAfterPointDigitCount,maxAfterPointDigitCount]
+     * and number is in the form "[-]digit(s)[.digit(s)]".
+     */
+    double readStrictDouble(double minv, double maxv,
+            int minAfterPointDigitCount, int maxAfterPointDigitCount,
+            const std::string& variableName = "");
+    
+    /* As readLine(). */
+    std::string readString();
+    /* See readLine(). */
+    void readStringTo(std::string& result);
+    /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */
+    std::string readString(const pattern& p, const std::string& variableName = "");
+    /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */
+    std::string readString(const std::string& ptrn, const std::string& variableName = "");
+    /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */
+    void readStringTo(std::string& result, const pattern& p, const std::string& variableName = "");
+    /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */
+    void readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName = "");
+
+    /* 
+     * Reads line from the current position to EOLN or EOF. Moves stream pointer to 
+     * the first character of the new line (if possible). 
+     */
+    std::string readLine();
+    /* See readLine(). */
+    void readLineTo(std::string& result);
+    /* The same as "readLine()", but ensures that line matches to the given pattern. */
+    std::string readLine(const pattern& p, const std::string& variableName = "");
+    /* The same as "readLine()", but ensures that line matches to the given pattern. */
+    std::string readLine(const std::string& ptrn, const std::string& variableName = "");
+    /* The same as "readLine()", but ensures that line matches to the given pattern. */
+    void readLineTo(std::string& result, const pattern& p, const std::string& variableName = "");
+    /* The same as "readLine()", but ensures that line matches to the given pattern. */
+    void readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName = "");
+
+    /* Reads EOLN or fails. Use it in validators. Calls "eoln()" method internally. */
+    void readEoln();
+    /* Reads EOF or fails. Use it in validators. Calls "eof()" method internally. */
+    void readEof();
+
+    /* 
+     * Quit-functions aborts program with <result> and <message>:
+     * input/answer streams replace any result to FAIL.
+     */
+    NORETURN void quit(TResult result, const char* msg);
+    /* 
+     * Quit-functions aborts program with <result> and <message>:
+     * input/answer streams replace any result to FAIL.
+     */
+    NORETURN void quitf(TResult result, const char* msg, ...);
+    /* 
+     * Quit-functions aborts program with <result> and <message>:
+     * input/answer streams replace any result to FAIL.
+     */
+    NORETURN void quits(TResult result, std::string msg);
+
+    void close();
+
+    const static WORD LightGray = 0x07;    
+    const static WORD LightRed = 0x0c;    
+    const static WORD LightCyan = 0x0b;    
+    const static WORD LightGreen = 0x0a;    
+    const static WORD LightYellow = 0x0e;    
+
+    static void textColor(WORD color);
+    static void quitscr(WORD color, const char* msg);
+    static void quitscrS(WORD color, std::string msg);
+    void xmlSafeWrite(std::FILE * file, const char* msg);
+
+private:
+    InStream(const InStream&);
+    InStream& operator =(const InStream&);
+};
+
+InStream inf;
+InStream ouf;
+InStream ans;
+bool appesMode;
+std::string resultName;
+std::string checkerName = "untitled checker";
+random_t rnd;
+TTestlibMode testlibMode = _unknown;
+double __testlib_points = std::numeric_limits<float>::infinity();
+
+struct TestlibFinalizeGuard
+{
+    static bool alive;
+    int quitCount, readEofCount;
+
+    TestlibFinalizeGuard() : quitCount(0), readEofCount(0)
+    {
+        // No operations.
+    }
+
+    ~TestlibFinalizeGuard()
+    {
+        bool _alive = alive;
+        alive = false;
+
+        if (_alive)
+        {
+            if (testlibMode == _checker && quitCount == 0)
+                __testlib_fail("Checker must end with quit or quitf call.");
+
+            if (testlibMode == _validator && readEofCount == 0 && quitCount == 0)
+                __testlib_fail("Validator must end with readEof call.");
+        }
+    }
+};
+
+bool TestlibFinalizeGuard::alive = true;
+TestlibFinalizeGuard testlibFinalizeGuard;
+
+/*
+ * Call it to disable checks on finalization.
+ */
+void disableFinalizeGuard()
+{
+    TestlibFinalizeGuard::alive = false;
+}
+
+/* Interactor streams.
+ */
+std::fstream tout;
+
+/* implementation
+ */
+
+template <typename T>
+static std::string vtos(const T& t)
+{
+    std::string s;
+    std::stringstream ss;
+    ss << t;
+    ss >> s;
+    return s;
+}
+
+template <typename T>
+static std::string toString(const T& t)
+{
+    return vtos(t);
+}
+
+InStream::InStream()
+{
+    file = NULL;
+    name = "";
+    mode = _input;
+    strict = false;
+    stdfile = false;
+    wordReserveSize = 4;
+}
+
+InStream::InStream(const InStream& baseStream, std::string content)
+{
+	file = (FILE*)0xbadfeed;
+	stdfile = false;
+    reader = new StringInputStreamReader(content);
+    opened = true;
+    strict = baseStream.strict;
+    mode = baseStream.mode;
+    name = "based on " + baseStream.name;
+}
+
+InStream::~InStream()
+{
+    if (NULL != reader)
+    {
+        delete reader;
+        reader = NULL;
+    }
+}
+
+#ifdef __GNUC__
+__attribute__((const))
+#endif
+int resultExitCode(TResult r)
+{
+    if (r == _ok)
+        return OK_EXIT_CODE;
+    if (r == _wa)
+        return WA_EXIT_CODE;
+    if (r == _pe)
+        return PE_EXIT_CODE;
+    if (r == _fail)
+        return FAIL_EXIT_CODE;
+    if (r == _dirt)
+        return DIRT_EXIT_CODE;
+    if (r == _points)
+        return POINTS_EXIT_CODE;
+    if (r == _unexpected_eof)
+#ifdef ENABLE_UNEXPECTED_EOF
+        return UNEXPECTED_EOF_EXIT_CODE;
+#else
+        return PE_EXIT_CODE;
+#endif
+    if (r >= _partially)
+        return PC_BASE_EXIT_CODE + (r - _partially);
+    return FAIL_EXIT_CODE;
+}
+
+void InStream::textColor(WORD color)
+{
+#if defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER>1400)
+    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
+    SetConsoleTextAttribute(handle, color);
+#endif
+
+#ifdef linux
+	char *shell_path = getenv("SHELL");
+	if (shell_path && strcmp(shell_path, "/bin/bash") == 0 && isatty(2))
+	{
+		switch (color)
+		{
+			case 0:
+				fprintf(stderr, "\e[0m");
+				break;
+			case LightRed:
+				fprintf(stderr, "\e[0;31m");
+				break;
+			case LightGreen:
+				fprintf(stderr, "\e[0;32m");
+				break;
+			case LightYellow:
+				fprintf(stderr, "\e[0;33m");
+				break;
+    		case LightCyan:
+				fprintf(stderr, "\e[0;36m");
+				break;
+			case LightGray:
+				fprintf(stderr, "\e[0m");
+				break;
+		}
+	}
+#endif
+}
+
+NORETURN void halt(int exitCode)
+{
+#ifdef FOOTER
+    InStream::textColor(InStream::LightGray);
+    std::fprintf(stderr, "Checker: \"%s\"\n", checkerName.c_str());
+    std::fprintf(stderr, "Exit code: %d\n", exitCode);
+    InStream::textColor(InStream::LightGray);
+#endif
+#ifdef linux
+	InStream::textColor(0);
+#endif
+    std::exit(exitCode);
+}
+
+NORETURN void InStream::quit(TResult result, const char* msg)
+{
+    if (TestlibFinalizeGuard::alive)
+        testlibFinalizeGuard.quitCount++;
+
+    if (mode != _output && result != _fail)
+        quits(_fail, std::string(msg) + " (" + name + ")");
+
+    std::FILE * resultFile;
+    std::string errorName;
+    
+    if (result == _ok)
+    {
+        if (testlibMode != _interactor && !ouf.seekEof())
+            quit(_dirt, "Extra information in the output file");
+    }
+
+    int pctype = result - _partially;
+    bool isPartial = false;
+
+    switch (result)
+    {
+    case _ok:
+        errorName = "ok ";
+        quitscrS(LightGreen, errorName);
+        break;
+    case _wa:
+        errorName = "wrong answer ";
+        quitscrS(LightRed, errorName);
+        break;
+    case _pe:
+        errorName = "wrong output format ";
+        quitscrS(LightRed, errorName);
+        break;
+    case _fail:
+        errorName = "FAIL ";
+        quitscrS(LightRed, errorName);
+        break;
+    case _dirt:
+        errorName = "wrong output format ";
+        quitscrS(LightCyan, errorName);
+        result = _pe;
+        break;
+    case _points:
+        errorName = "points ";
+        quitscrS(LightYellow, errorName);
+        break;
+    case _unexpected_eof:
+#ifdef ENABLE_UNEXPECTED_EOF
+        errorName = "unexpected eof ";
+#else
+        errorName = "wrong output format ";
+#endif
+        quitscrS(LightCyan, errorName);
+        break;
+    default:
+        if (result >= _partially)
+        {
+            errorName = format("partially correct (%d) ", pctype);
+            isPartial = true;
+            quitscrS(LightYellow, errorName);
+        }
+        else
+            quit(_fail, "What is the code ??? ");
+    }
+
+    if (resultName != "")
+    {
+        resultFile = __testlib_fopen(resultName.c_str(), "w");
+        if (resultFile == NULL)
+            quit(_fail, "Can not write to the result file");
+        if (appesMode)
+        {
+            std::fprintf(resultFile, "<?xml version=\"1.0\" encoding=\"windows-1251\"?>");
+            if (isPartial)
+                std::fprintf(resultFile, "<result outcome = \"%s\" pctype = \"%d\">", outcomes[(int)_partially].c_str(), pctype);
+            else
+            {
+                if (result != _points)
+                    std::fprintf(resultFile, "<result outcome = \"%s\">", outcomes[(int)result].c_str());
+                else
+                {
+                    if (__testlib_points == std::numeric_limits<float>::infinity())
+                        quit(_fail, "Expected points, but infinity found");
+                    std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", __testlib_points));
+                    std::fprintf(resultFile, "<result outcome = \"%s\" points = \"%s\">", outcomes[(int)result].c_str(), stringPoints.c_str());
+                }
+            }
+            xmlSafeWrite(resultFile, msg);
+            std::fprintf(resultFile, "</result>\n");
+        }
+        else
+             std::fprintf(resultFile, "%s", msg);
+        if (NULL == resultFile || fclose(resultFile) != 0)
+            quit(_fail, "Can not write to the result file");
+    }
+
+    quitscr(LightGray, msg);
+    std::fprintf(stderr, "\n");
+
+    if (inf.file)
+        fclose(inf.file);
+    if (ouf.file)
+        fclose(ouf.file);
+    if (ans.file)
+        fclose(ans.file);
+    if (tout.is_open())
+        tout.close();
+
+    textColor(LightGray);
+
+    if (resultName != "")
+        std::fprintf(stderr, "See file to check exit message\n");
+
+    halt(resultExitCode(result));
+}
+
+#ifdef __GNUC__
+    __attribute__ ((format (printf, 3, 4)))
+#endif
+NORETURN void InStream::quitf(TResult result, const char* msg, ...)
+{
+    FMT_TO_RESULT(msg, msg, message);
+    InStream::quit(result, message.c_str());
+}
+
+NORETURN void InStream::quits(TResult result, std::string msg)
+{
+    InStream::quit(result, msg.c_str());
+}
+
+void InStream::xmlSafeWrite(std::FILE * file, const char* msg)
+{
+    size_t lmsg = strlen(msg);
+    for (size_t i = 0; i < lmsg; i++)
+    {
+        if (msg[i] == '&')
+        {
+            std::fprintf(file, "%s", "&amp;");
+            continue;
+        }
+        if (msg[i] == '<')
+        {
+            std::fprintf(file, "%s", "&lt;");
+            continue;
+        }
+        if (msg[i] == '>')
+        {
+            std::fprintf(file, "%s", "&gt;");
+            continue;
+        }
+        if (msg[i] == '"')
+        {
+            std::fprintf(file, "%s", "&quot;");
+            continue;
+        }
+        if (0 <= msg[i] && msg[i] <= 31)
+        {
+            std::fprintf(file, "%c", '.');
+            continue;
+        }
+        std::fprintf(file, "%c", msg[i]);
+    }
+}
+
+void InStream::quitscrS(WORD color, std::string msg)
+{
+    quitscr(color, msg.c_str());
+}
+
+void InStream::quitscr(WORD color, const char* msg)
+{
+    if (resultName == "")
+    {
+        textColor(color);
+        std::fprintf(stderr, "%s", msg);
+        textColor(LightGray);
+    }
+}
+
+void InStream::reset()
+{
+    if (opened && stdfile)
+        quit(_fail, "Can't reset standard handle");
+
+    if (opened)
+        close();
+
+    if (!stdfile)
+        if (NULL == (file = __testlib_fopen(name.c_str(), "rb")))
+        {
+            if (mode == _output)
+                quits(_pe, std::string("File not found: \"") + name + "\"");
+        }
+
+    opened = true;
+
+    __testlib_set_binary(file);
+
+    if (stdfile)
+        reader = new FileInputStreamReader(file, name);
+    else
+        reader = new BufferedFileInputStreamReader(file, name);
+}
+
+void InStream::init(std::string fileName, TMode mode)
+{
+    opened = false;
+	if (fileName == "/dev/stdin")
+	{
+		name = "stdin";
+		this->file = stdin;
+		stdfile = true;
+	}
+	else
+	{
+		name = fileName;
+		stdfile = false;
+	}
+    this->mode = mode;
+    reset();
+}
+
+void InStream::init(std::FILE* f, TMode mode)
+{
+    opened = false;
+    
+    name = "untitled";
+    
+    if (f == stdin)
+        name = "stdin", stdfile = true;
+    
+    if (f == stdout)
+        name = "stdout", stdfile = true;
+    
+    if (f == stderr)
+        name = "stderr", stdfile = true;
+
+    this->file = f;
+    this->mode = mode;
+    
+    reset();
+}
+
+char InStream::curChar()
+{
+    int c = reader->curChar();
+    return char(c != EOF ? c : EOFC);
+}
+
+char InStream::nextChar()
+{
+    int c = reader->nextChar();
+    return char(c != EOF ? c : EOFC);
+}
+
+char InStream::readChar()
+{
+    return nextChar();
+}
+
+char InStream::readChar(char c)
+{
+    char found = readChar();
+    if (c != found)
+    {
+        if (!isEoln(found))
+            quit(_pe, ("Unexpected character '" + std::string(1, found) + "', but '" + std::string(1, c) + "' expected").c_str());
+        else
+            quit(_pe, ("Unexpected character " + ("#" + vtos(int(found))) + ", but '" + std::string(1, c) + "' expected").c_str());
+    }
+    return found;
+}
+
+char InStream::readSpace()
+{
+    return readChar(' ');
+}
+
+void InStream::unreadChar(char c)
+{
+    reader->unreadChar(c);
+}
+
+void InStream::skipChar()
+{
+    reader->skipChar();
+}
+
+void InStream::skipBlanks()
+{
+    while (isBlanks(reader->curChar()))
+        reader->skipChar();
+}
+
+std::string InStream::readWord()
+{
+    readWordTo(_tmpReadToken);
+    return _tmpReadToken;
+}
+
+void InStream::readWordTo(std::string& result)
+{
+    if (!strict)
+        skipBlanks();
+
+    int cur = reader->nextChar();
+
+    if (cur == EOF)
+        quit(_unexpected_eof, "Unexpected end of file - token expected");
+
+    if (isBlanks(cur))
+        quit(_pe, "Unexpected white-space - token expected");
+
+    result.clear();
+
+    while (!(isBlanks(cur) || cur == EOF))
+    {
+        result += char(cur);
+        cur = reader->nextChar();
+    }
+
+    reader->unreadChar(cur);
+
+    if (result.length() == 0)
+        quit(_unexpected_eof, "Unexpected end of file or white-space - token expected");
+}
+
+std::string InStream::readToken()
+{
+    return readWord();
+}
+
+void InStream::readTokenTo(std::string& result)
+{
+    readWordTo(result);
+}
+
+static std::string __testlib_part(const std::string& s)
+{
+    if (s.length() <= 64)
+        return s;
+    else
+        return s.substr(0, 30) + "..." + s.substr(s.length() - 31, 31);
+}
+
+std::string InStream::readWord(const pattern& p, const std::string& variableName)
+{
+    readWordTo(_tmpReadToken);
+    if (!p.matches(_tmpReadToken))
+    {
+        if (variableName.empty())
+            quit(_wa, ("Token \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str());
+        else
+            quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str());
+    }
+    return _tmpReadToken;
+}
+
+std::string InStream::readWord(const std::string& ptrn, const std::string& variableName)
+{
+    return readWord(pattern(ptrn), variableName);
+}
+
+std::string InStream::readToken(const pattern& p, const std::string& variableName)
+{
+    return readWord(p, variableName);
+}
+
+std::string InStream::readToken(const std::string& ptrn, const std::string& variableName)
+{
+    return readWord(ptrn, variableName);
+}
+
+void InStream::readWordTo(std::string& result, const pattern& p, const std::string& variableName)
+{
+    readWordTo(result);
+    if (!p.matches(result))
+    {
+        if (variableName.empty())
+            quit(_wa, ("Token \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str());
+        else
+            quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str());
+    }
+}
+
+void InStream::readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName)
+{
+    return readWordTo(result, pattern(ptrn), variableName);
+}
+
+void InStream::readTokenTo(std::string& result, const pattern& p, const std::string& variableName)
+{
+    return readWordTo(result, p, variableName);
+}
+
+void InStream::readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName)
+{
+    return readWordTo(result, ptrn, variableName);
+}
+
+#ifdef __GNUC__
+__attribute__((pure))
+#endif
+static inline bool equals(long long integer, const char* s)
+{
+    if (integer == LLONG_MIN)
+        return strcmp(s, "-9223372036854775808") == 0;
+
+    if (integer == 0LL)
+        return strcmp(s, "0") == 0;
+
+    size_t length = strlen(s);
+
+    if (length == 0)
+        return false;
+
+    if (integer < 0 && s[0] != '-')
+        return false;
+
+    if (integer < 0)
+        s++, length--, integer = -integer;
+
+    if (length == 0)
+        return false;
+
+    while (integer > 0)
+    {
+        int digit = int(integer % 10);
+
+        if (s[length - 1] != '0' + digit)
+            return false;
+
+        length--;
+        integer /= 10;
+    }
+
+    return length == 0;
+}
+
+static inline double stringToDouble(InStream& in, const char* buffer)
+{
+    double retval;
+
+    size_t length = strlen(buffer);
+
+    int minusCount = 0;
+    int plusCount = 0;
+    int decimalPointCount = 0;
+    int digitCount = 0;
+    int eCount = 0;
+
+    for (size_t i = 0; i < length; i++)
+    {
+        if (('0' <= buffer[i] && buffer[i] <= '9') || buffer[i] == '.'
+                || buffer[i] == 'e' || buffer[i] == 'E'
+                || buffer[i] == '-' || buffer[i] == '+')
+        {
+            if ('0' <= buffer[i] && buffer[i] <= '9')
+                digitCount++;
+            if (buffer[i] == 'e' || buffer[i] == 'E')
+                eCount++;
+            if (buffer[i] == '-')
+                minusCount++;
+            if (buffer[i] == '+')
+                plusCount++;
+            if (buffer[i] == '.')
+                decimalPointCount++;
+        }
+        else
+            in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+    }
+
+    // If for sure is not a number in standard notation or in e-notation.
+    if (digitCount == 0 || minusCount > 2 || plusCount > 2 || decimalPointCount > 1 || eCount > 1)
+        in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    char* suffix = new char[length + 1];
+    int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix);
+    bool empty = strlen(suffix) == 0;
+    delete[] suffix;
+
+    if (scanned == 1 || (scanned == 2 && empty))
+    {
+        if (__testlib_isNaN(retval) || __testlib_isInfinite(retval))
+            in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+        return retval;
+    }
+    else
+        in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+}
+
+static inline double stringToStrictDouble(InStream& in, const char* buffer, int minAfterPointDigitCount, int maxAfterPointDigitCount)
+{
+    if (minAfterPointDigitCount < 0)
+        in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be non-negative.");
+    
+    if (minAfterPointDigitCount > maxAfterPointDigitCount)
+        in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be less or equal to maxAfterPointDigitCount.");
+
+    double retval;
+
+    size_t length = strlen(buffer);
+
+    if (length == 0 || length > 1000)
+        in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    if (buffer[0] != '-' && (buffer[0] < '0' || buffer[0] > '9'))
+        in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    int pointPos = -1; 
+    for (size_t i = 1; i + 1 < length; i++)
+    {
+        if (buffer[i] == '.')
+        {
+            if (pointPos > -1)
+                in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+            pointPos = int(i);
+        }
+        if (buffer[i] != '.' && (buffer[i] < '0' || buffer[i] > '9'))
+            in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+    }
+
+    if (buffer[length - 1] < '0' || buffer[length - 1] > '9')
+        in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    int afterDigitsCount = (pointPos == -1 ? 0 : int(length) - pointPos - 1);
+    if (afterDigitsCount < minAfterPointDigitCount || afterDigitsCount > maxAfterPointDigitCount)
+        in.quit(_pe, ("Expected strict double with number of digits after point in range ["
+            + vtos(minAfterPointDigitCount)
+            + ","
+            + vtos(maxAfterPointDigitCount)
+            + "], but \"" + __testlib_part(buffer) + "\" found").c_str()
+        );
+
+    int firstDigitPos = -1;
+    for (size_t i = 0; i < length; i++)
+        if (buffer[i] >= '0' && buffer[i] <= '9')
+        {
+            firstDigitPos = int(i);
+            break;
+        }
+
+    if (firstDigitPos > 1 || firstDigitPos == -1) 
+        in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    if (buffer[firstDigitPos] == '0' && firstDigitPos + 1 < int(length)
+            && buffer[firstDigitPos + 1] >= '0' && buffer[firstDigitPos + 1] <= '9')
+        in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    char* suffix = new char[length + 1];
+    int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix);
+    bool empty = strlen(suffix) == 0;
+    delete[] suffix;
+
+    if (scanned == 1 || (scanned == 2 && empty))
+    {
+        if (__testlib_isNaN(retval) || __testlib_isInfinite(retval))
+            in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+        return retval;
+    }
+    else
+        in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str());
+}
+
+static inline long long stringToLongLong(InStream& in, const char* buffer)
+{
+    if (strcmp(buffer, "-9223372036854775808") == 0)
+        return LLONG_MIN;
+
+    bool minus = false;
+    size_t length = strlen(buffer);
+    
+    if (length > 1 && buffer[0] == '-')
+        minus = true;
+
+    if (length > 20)
+        in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    long long retval = 0LL;
+
+    int zeroes = 0;
+    int processingZeroes = true;
+    
+    for (int i = (minus ? 1 : 0); i < int(length); i++)
+    {
+        if (buffer[i] == '0' && processingZeroes)
+            zeroes++;
+        else
+            processingZeroes = false;
+
+        if (buffer[i] < '0' || buffer[i] > '9')
+            in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str());
+        retval = retval * 10 + (buffer[i] - '0');
+    }
+
+    if (retval < 0)
+        in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str());
+    
+    if ((zeroes > 0 && (retval != 0 || minus)) || zeroes > 1)
+        in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str());
+
+    retval = (minus ? -retval : +retval);
+
+    if (length < 19)
+        return retval;
+
+    if (equals(retval, buffer))
+        return retval;
+    else
+        in.quit(_pe, ("Expected int64, but \"" + __testlib_part(buffer) + "\" found").c_str());
+}
+
+int InStream::readInteger()
+{
+    if (!strict && seekEof())
+        quit(_unexpected_eof, "Unexpected end of file - int32 expected");
+
+    readWordTo(_tmpReadToken);
+    
+    long long value = stringToLongLong(*this, _tmpReadToken.c_str());
+    if (value < INT_MIN || value > INT_MAX)
+        quit(_pe, ("Expected int32, but \"" + __testlib_part(_tmpReadToken) + "\" found").c_str());
+    
+    return int(value);
+}
+
+long long InStream::readLong()
+{
+    if (!strict && seekEof())
+        quit(_unexpected_eof, "Unexpected end of file - int64 expected");
+
+    readWordTo(_tmpReadToken);
+    return stringToLongLong(*this, _tmpReadToken.c_str());
+}
+
+long long InStream::readLong(long long minv, long long maxv, const std::string& variableName)
+{
+    long long result = readLong();
+
+    if (result < minv || result > maxv)
+    {
+        if (variableName.empty())
+            quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+        else
+            quit(_wa, ("Integer parameter [name=" + variableName + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+    }
+
+    return result;
+}
+
+int InStream::readInt()
+{
+    return readInteger();
+}
+
+int InStream::readInt(int minv, int maxv, const std::string &variableName)
+{
+    int result = readInt();
+
+    if (result < minv || result > maxv)
+	{
+		if (variableName.empty())
+			quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+		else
+			quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+	}
+
+    return result;
+}
+
+int InStream::readInteger(int minv, int maxv, const std::string& variableName)
+{
+    return readInt(minv, maxv, variableName.c_str());
+}
+
+double InStream::readReal()
+{
+    if (!strict && seekEof())
+        quit(_unexpected_eof, "Unexpected end of file - double expected");
+
+    return stringToDouble(*this, readWord().c_str());
+}
+
+double InStream::readDouble()
+{
+    return readReal();
+}
+
+double InStream::readReal(double minv, double maxv, const std::string& variableName)
+{
+    double result = readReal();
+
+    if (result < minv || result > maxv)
+    {
+        if (variableName.empty())
+            quit(_wa, ("Double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+        else
+            quit(_wa, ("Double parameter [name=" + variableName + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+    }
+
+    return result;
+}
+
+double InStream::readDouble(double minv, double maxv, const std::string& variableName)
+{
+    return readReal(minv, maxv, variableName);
+}                                           
+
+double InStream::readStrictReal(double minv, double maxv,
+        int minAfterPointDigitCount, int maxAfterPointDigitCount,
+        const std::string& variableName)
+{
+    if (!strict && seekEof())
+        quit(_unexpected_eof, "Unexpected end of file - strict double expected");
+
+    double result = stringToStrictDouble(*this, readWord().c_str(),
+            minAfterPointDigitCount, maxAfterPointDigitCount);
+
+    if (result < minv || result > maxv)
+    {
+        if (variableName.empty())
+            quit(_wa, ("Strict double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+        else
+            quit(_wa, ("Strict double parameter [name=" + variableName + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str());
+    }
+
+    return result;
+}
+
+double InStream::readStrictDouble(double minv, double maxv,
+        int minAfterPointDigitCount, int maxAfterPointDigitCount,
+        const std::string& variableName)
+{
+    return readStrictReal(minv, maxv,
+            minAfterPointDigitCount, maxAfterPointDigitCount,
+            variableName);
+}
+
+bool InStream::eof()
+{
+    if (!strict && NULL == file)
+        return true;
+
+    return reader->eof();
+}
+
+bool InStream::seekEof()
+{
+    if (NULL == file)
+        return true;
+    skipBlanks();
+    return eof();
+}
+
+bool InStream::eoln()
+{
+    if (!strict && NULL == file)
+        return true;
+
+    int c = reader->nextChar();
+
+    if (!strict)
+    {
+        if (c == EOF)
+            return true;
+
+        if (c == CR)
+        {
+            c = reader->nextChar();
+
+            if (c != LF)
+            {
+                reader->unreadChar(c);
+                reader->unreadChar(CR);
+                return false;
+            }
+            else
+                return true;
+        }
+        
+        if (c == LF)
+            return true;
+
+        reader->unreadChar(c);
+        return false;
+    }
+    else
+    {
+        bool returnCr = false;
+
+#ifdef CR_MUST_IN_EOL
+        if (c != CR)
+        {
+            reader->unreadChar(c);
+            return false;
+        }
+        else
+        {
+            if (!returnCr)
+                returnCr = true;
+            c = reader->nextChar();
+        }
+#endif        
+        if (c != LF)
+        {
+            reader->unreadChar(c);
+            if (returnCr)
+                reader->unreadChar(CR);
+            return false;
+        }
+
+        return true;
+    }
+}
+
+void InStream::readEoln()
+{
+    if (!eoln())
+        quit(_pe, "Expected EOLN");
+}
+
+void InStream::readEof()
+{
+    if (!eof())
+        quit(_pe, "Expected EOF");
+
+    if (TestlibFinalizeGuard::alive && this == &inf)
+        testlibFinalizeGuard.readEofCount++;
+}
+
+bool InStream::seekEoln()
+{
+    if (NULL == file)
+        return true;
+    
+    int cur;
+    do
+    {
+        cur = reader->nextChar();
+    } 
+    while (cur == SPACE || cur == TAB);
+
+    reader->unreadChar(cur);
+    return eoln();
+}
+
+void InStream::nextLine()
+{
+    readLine();
+}
+
+void InStream::readStringTo(std::string& result)
+{
+    if (NULL == file)
+        quit(_pe, "Expected line");
+
+    result.clear();
+    int cur;
+
+    for (;;)
+    {
+        cur = reader->curChar();
+
+        if (isEoln(cur))
+            break;
+
+        if (cur == EOF)
+            break;
+
+        result += char(reader->nextChar());
+    }
+
+    if (strict)
+        readEoln();
+    else
+        eoln();
+}
+
+std::string InStream::readString()
+{
+    readStringTo(_tmpReadToken);
+    return _tmpReadToken;
+}
+
+void InStream::readStringTo(std::string& result, const pattern& p, const std::string& variableName)
+{
+    readStringTo(result);
+    if (!p.matches(result))
+    {
+        if (variableName.empty())
+            quit(_wa, ("Line \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str());
+        else
+            quit(_wa, ("Line [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str());
+    }
+}
+
+void InStream::readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName)
+{
+    readStringTo(result, pattern(ptrn), variableName);
+}
+
+std::string InStream::readString(const pattern& p, const std::string& variableName)
+{
+    readStringTo(_tmpReadToken, p, variableName);
+    return _tmpReadToken;
+}
+
+std::string InStream::readString(const std::string& ptrn, const std::string& variableName)
+{
+    readStringTo(_tmpReadToken, ptrn, variableName);
+    return _tmpReadToken;
+}
+
+void InStream::readLineTo(std::string& result)
+{
+    readStringTo(result);
+}
+
+std::string InStream::readLine()
+{
+    return readString();
+}
+
+void InStream::readLineTo(std::string& result, const pattern& p, const std::string& variableName)
+{
+    readStringTo(result, p, variableName);
+}
+
+void InStream::readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName)
+{
+    readStringTo(result, ptrn, variableName);
+}
+
+std::string InStream::readLine(const pattern& p, const std::string& variableName)
+{
+    return readString(p, variableName);
+}
+
+std::string InStream::readLine(const std::string& ptrn, const std::string& variableName)
+{
+    return readString(ptrn, variableName);
+}
+
+void InStream::close()
+{
+    if (opened)
+        fclose(file);
+    
+    if (NULL != reader)
+    {
+        delete reader;
+        reader = NULL;
+    }
+    
+    opened = false;
+}
+
+NORETURN void quit(TResult result, const std::string& msg)
+{
+    ouf.quit(result, msg.c_str());
+}
+
+NORETURN void quit(TResult result, const char* msg)
+{
+    ouf.quit(result, msg);
+}
+
+NORETURN void __testlib_quitp(double points, const char* message)
+{
+    __testlib_points = points;
+    std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", points));
+
+    std::string quitMessage;
+    if (NULL == message || 0 == strlen(message))
+        quitMessage = stringPoints;
+    else
+        quitMessage = stringPoints + " " + message;
+
+    quit(_points, quitMessage.c_str());
+}
+
+NORETURN void quitp(float points, const std::string& message = "")
+{
+    __testlib_quitp(double(points), message.c_str());
+}
+
+NORETURN void quitp(double points, const std::string& message = "")
+{
+    __testlib_quitp(points, message.c_str());
+}
+
+NORETURN void quitp(long double points, const std::string& message = "")
+{
+    __testlib_quitp(double(points), message.c_str());
+}
+
+template<typename F>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 2, 3)))
+#endif
+NORETURN void quitp(F points, const char* format, ...)
+{
+    FMT_TO_RESULT(format, format, message);
+    quitp(points, message);
+}
+
+#ifdef __GNUC__
+__attribute__ ((format (printf, 2, 3)))
+#endif
+NORETURN void quitf(TResult result, const char* format, ...)
+{
+    FMT_TO_RESULT(format, format, message);
+    quit(result, message);
+}
+
+#ifdef __GNUC__
+__attribute__ ((format (printf, 3, 4)))
+#endif
+void quitif(bool condition, TResult result, const char* format, ...)
+{
+    if (condition)
+    {
+        FMT_TO_RESULT(format, format, message);
+        quit(result, message);
+    }
+}
+
+NORETURN void __testlib_help()
+{
+    InStream::textColor(InStream::LightCyan);
+    std::fprintf(stderr, "TESTLIB %s, http://code.google.com/p/testlib/ ", VERSION);
+    std::fprintf(stderr, "by Mike Mirzayanov, copyright(c) 2005-2013\n");
+    std::fprintf(stderr, "Checker name: \"%s\"\n", checkerName.c_str());
+    InStream::textColor(InStream::LightGray);
+
+    std::fprintf(stderr, "\n");
+    std::fprintf(stderr, "Latest features: \n");
+    for (size_t i = 0; i < sizeof(latestFeatures) / sizeof(char*); i++)
+    {
+        std::fprintf(stderr, "*) %s\n", latestFeatures[i]);
+    }
+    std::fprintf(stderr, "\n");
+
+    std::fprintf(stderr, "Program must be run with the following arguments: \n");
+    std::fprintf(stderr, "    <input-file> <output-file> <answer-file> [<report-file> [<-appes>]]\n\n");
+
+    std::exit(FAIL_EXIT_CODE);
+}
+
+static void __testlib_ensuresPreconditions()
+{
+    // testlib assumes: sizeof(int) = 4.
+    __TESTLIB_STATIC_ASSERT(sizeof(int) == 4);
+
+    // testlib assumes: INT_MAX == 2147483647.
+    __TESTLIB_STATIC_ASSERT(INT_MAX == 2147483647);
+
+    // testlib assumes: sizeof(long long) = 8.
+    __TESTLIB_STATIC_ASSERT(sizeof(long long) == 8);
+
+    // testlib assumes: no -ffast-math.
+    if (!__testlib_isNaN(+__testlib_nan()))
+        quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'");
+    if (!__testlib_isNaN(-__testlib_nan()))
+        quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'");
+}
+
+void registerGen(int argc, char* argv[], int randomGeneratorVersion)
+{
+    if (randomGeneratorVersion < 0 || randomGeneratorVersion > 1)
+        quitf(_fail, "Random generator version is expected to be 0 or 1.");
+    random_t::version = randomGeneratorVersion;
+
+    __testlib_ensuresPreconditions();
+
+    testlibMode = _generator;
+    __testlib_set_binary(stdin);
+    rnd.setSeed(argc, argv);
+}
+
+#ifdef USE_RND_AS_BEFORE_087
+void registerGen(int argc, char* argv[])
+{
+    registerGen(argc, argv, 0);
+}
+#else
+void registerGen(int argc, char* argv[])
+{
+    std::fprintf(stderr, "Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)."
+            " The third parameter stands for the random generator version."
+            " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)."
+            " Version 1 has been released on Spring, 2013. Use it to write new generators.\n\n");
+    registerGen(argc, argv, 0);
+}
+#endif
+
+void registerInteraction(int argc, char* argv[])
+{
+    __testlib_ensuresPreconditions();
+
+    testlibMode = _interactor;
+    __testlib_set_binary(stdin);
+
+    if (argc > 1 && !strcmp("--help", argv[1]))
+        __testlib_help();
+    
+    if (argc < 3 || argc > 6)
+    {
+        quit(_fail, std::string("Program must be run with the following arguments: ") +
+            std::string("<input-file> <output-file> [<answer-file> [<report-file> [<-appes>]]]") + 
+            "\nUse \"--help\" to get help information");
+    }
+
+    if (argc <= 4)
+    {
+        resultName = "";
+        appesMode = false;
+    }
+
+    if (argc == 5)
+    {
+        resultName = argv[4];
+        appesMode = false;
+    }
+
+    if (argc == 6)
+    {
+        if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5]))
+        {
+            quit(_fail, std::string("Program must be run with the following arguments: ") +
+                        "<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]");
+        }
+        else
+        {
+            resultName = argv[4];
+            appesMode = true;
+        }
+    }
+
+    inf.init(argv[1], _input);
+
+	if (strcmp(argv[2], "stdout") != 0)
+	{
+	    tout.open(argv[2], std::ios_base::out);
+	    if (tout.fail() || !tout.is_open())
+	    	quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'"));
+	}
+
+    ouf.init(stdin, _output);
+    
+    if (argc >= 4)
+        ans.init(argv[3], _answer);
+    else
+        ans.name = "unopened answer stream";
+}
+
+void registerValidation()
+{
+    __testlib_ensuresPreconditions();
+
+    testlibMode = _validator;
+    __testlib_set_binary(stdin);
+
+    inf.init(stdin, _input);
+    inf.strict = true;
+}
+
+void registerTestlibCmd(int argc, char* argv[])
+{
+    __testlib_ensuresPreconditions();
+
+    testlibMode = _checker;
+    __testlib_set_binary(stdin);    
+
+    if (argc > 1 && !strcmp("--help", argv[1]))
+        __testlib_help();
+
+    if (argc < 4 || argc > 6)
+    {
+        quit(_fail, std::string("Program must be run with the following arguments: ") +
+            std::string("<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]") + 
+            "\nUse \"--help\" to get help information");
+    }
+
+    if (argc == 4)
+    {
+        resultName = "";
+        appesMode = false;
+    }
+
+    if (argc == 5)
+    {
+        resultName = argv[4];
+        appesMode = false;
+    }
+
+    if (argc == 6)
+    {
+        if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5]))
+        {
+            quit(_fail, std::string("Program must be run with the following arguments: ") +
+                        "<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]");
+        }
+        else
+        {
+            resultName = argv[4];
+            appesMode = true;
+        }
+    }
+
+    inf.init(argv[1], _input);
+    ouf.init(argv[2], _output);
+    ans.init(argv[3], _answer);
+}
+
+void registerTestlib(int argc, ...)
+{
+    if (argc  < 3 || argc > 5)
+        quit(_fail, std::string("Program must be run with the following arguments: ") +
+            "<input-file> <output-file> <answer-file> [<report-file> [<-appes>]]");
+
+    char** argv = new char*[argc + 1];
+    
+    va_list ap;
+    va_start(ap, argc);
+    argv[0] = NULL;
+    for (int i = 0; i < argc; i++)
+    {
+        argv[i + 1] = va_arg(ap, char*);
+    }
+    va_end(ap);
+
+    registerTestlibCmd(argc + 1, argv);
+    delete[] argv;
+}
+
+#ifdef __GNUC__
+__attribute__((const))
+#endif
+inline bool doubleCompare(double expected, double result, double MAX_DOUBLE_ERROR)
+{
+        if (__testlib_isNaN(expected))
+        {
+            return __testlib_isNaN(result);
+        }
+        else 
+            if (__testlib_isInfinite(expected))
+            {
+                if (expected > 0)
+                {
+                    return result > 0 && __testlib_isInfinite(result);
+                }
+                else
+                {
+                    return result < 0 && __testlib_isInfinite(result);
+                }
+            }
+            else 
+                if (__testlib_isNaN(result) || __testlib_isInfinite(result))
+                {
+                    return false;
+                }
+                else 
+                if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR + 1E-15)
+                {
+                    return true;
+                }
+                else
+                {
+                    double minv = __testlib_min(expected * (1.0 - MAX_DOUBLE_ERROR),
+                                 expected * (1.0 + MAX_DOUBLE_ERROR));
+                    double maxv = __testlib_max(expected * (1.0 - MAX_DOUBLE_ERROR),
+                                  expected * (1.0 + MAX_DOUBLE_ERROR));
+                    return result + 1E-15 >= minv && result <= maxv + 1E-15;
+                }
+}
+
+#ifdef __GNUC__
+__attribute__((const))
+#endif
+inline double doubleDelta(double expected, double result)
+{
+    double absolute = __testlib_abs(result - expected);
+    
+    if (__testlib_abs(expected) > 1E-9)
+    {
+        double relative = __testlib_abs(absolute / expected);
+        return __testlib_min(absolute, relative);
+    }
+    else
+        return absolute;
+}
+
+static inline void __testlib_ensure(bool cond, const std::string& msg)
+{
+    if (!cond)
+        quit(_fail, msg.c_str());
+}
+
+static inline void __testlib_ensure(bool cond, const char* msg)
+{
+    if (!cond)
+        quit(_fail, msg);
+}
+
+#define ensure(cond) __testlib_ensure(cond, "Condition failed: \"" #cond "\"")
+
+#ifdef __GNUC__
+__attribute__ ((format (printf, 2, 3)))
+#endif
+inline void ensuref(bool cond, const char* format, ...)
+{
+    if (!cond)
+    {
+        FMT_TO_RESULT(format, format, message);
+        __testlib_ensure(cond, message);
+    }
+}
+
+NORETURN static void __testlib_fail(const std::string& message)
+{
+    quitf(_fail, "%s", message.c_str());
+}
+
+#ifdef __GNUC__
+__attribute__ ((format (printf, 1, 2)))
+#endif
+void setName(const char* format, ...)
+{
+    FMT_TO_RESULT(format, format, name);
+    checkerName = name;
+}
+
+/* 
+ * Do not use random_shuffle, because it will produce different result
+ * for different C++ compilers.
+ *
+ * This implementation uses testlib random_t to produce random numbers, so
+ * it is stable.
+ */ 
+template<typename _RandomAccessIter>
+void shuffle(_RandomAccessIter __first, _RandomAccessIter __last)
+{
+    if (__first == __last) return;
+    for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i)
+        std::iter_swap(__i, __first + rnd.next(int(__i - __first) + 1));
+}
+
+
+template<typename _RandomAccessIter>
+void random_shuffle(_RandomAccessIter , _RandomAccessIter )
+{
+    quitf(_fail, "Don't use random_shuffle(), use shuffle() instead");
+}
+
+#ifdef __GLIBC__
+#  define RAND_THROW_STATEMENT throw()
+#else
+#  define RAND_THROW_STATEMENT
+#endif
+
+int rand() RAND_THROW_STATEMENT
+{
+    quitf(_fail, "Don't use rand(), use rnd.next() instead");
+    
+    /* This line never runs. */
+    //throw "Don't use rand(), use rnd.next() instead";
+}
+
+void srand(unsigned int seed) RAND_THROW_STATEMENT
+{
+    quitf(_fail, "Don't use srand(), you should use " 
+        "'registerGen(argc, argv, 1);' to initialize generator seed "
+        "by hash code of the command line params. The third parameter "
+        "is randomGeneratorVersion (currently the latest is 1) [ignored seed=%d].", seed);
+}
+
+void startTest(int test)
+{
+    const std::string testFileName = vtos(test);
+    if (NULL == freopen(testFileName.c_str(), "wt", stdout))
+        __testlib_fail("Unable to write file '" + testFileName + "'");
+}
+
+inline std::string upperCase(std::string s)
+{
+    for (size_t i = 0; i < s.length(); i++)
+        if ('a' <= s[i] && s[i] <= 'z')
+            s[i] = char(s[i] - 'a' + 'A');
+    return s;
+}
+
+inline std::string lowerCase(std::string s)
+{
+    for (size_t i = 0; i < s.length(); i++)
+        if ('A' <= s[i] && s[i] <= 'Z')
+            s[i] = char(s[i] - 'A' + 'a');
+    return s;
+}
+
+inline std::string compress(const std::string& s)
+{
+    return __testlib_part(s);
+}
+
+inline std::string englishEnding(int x)
+{
+    x %= 100;
+    if (x / 10 == 1)
+        return "th";
+    if (x % 10 == 1)
+        return "st";
+    if (x % 10 == 2)
+        return "nd";
+    if (x % 10 == 3)
+        return "rd";
+    return "th";
+}
+
+inline std::string trim(const std::string& s)
+{
+    if (s.empty())
+        return s;
+
+    int left = 0;
+    while (left < int(s.length()) && isBlanks(s[left]))
+        left++;
+    if (left >= int(s.length()))
+        return "";
+
+    int right = int(s.length()) - 1;
+    while (right >= 0 && isBlanks(s[right]))
+        right--;
+    if (right < 0)
+        return "";
+
+    return s.substr(left, right - left + 1);
+}
+
+template <typename _ForwardIterator, typename _Separator>
+std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separator)
+{
+    std::stringstream ss;
+    bool repeated = false;
+    for (_ForwardIterator i = first; i != last; i++)
+    {
+        if (repeated)
+            ss << separator;
+        else
+            repeated = true;
+        ss << *i;
+    }
+    return ss.str();
+}
+
+template <typename _ForwardIterator>
+std::string join(_ForwardIterator first, _ForwardIterator last)
+{
+    return join(first, last, ' ');
+}
+
+template <typename _Collection, typename _Separator>
+std::string join(const _Collection& collection, _Separator separator)
+{
+    return join(collection.begin(), collection.end(), separator);
+}
+
+template <typename _Collection>
+std::string join(const _Collection& collection)
+{
+    return join(collection, ' ');
+}
+
+NORETURN void __testlib_expectedButFound(TResult result, std::string expected, std::string found, const char* prepend)
+{
+    std::string message;
+    if (strlen(prepend) != 0)
+        message = format("%s: expected '%s', but found '%s'",
+            compress(prepend).c_str(), compress(expected).c_str(), compress(found).c_str());
+    else
+        message = format("expected '%s', but found '%s'",
+            compress(expected).c_str(), compress(found).c_str());
+    quit(result, message);
+}
+
+NORETURN void __testlib_expectedButFound(TResult result, double expected, double found, const char* prepend)
+{
+    std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected));
+    std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found));
+    __testlib_expectedButFound(result, expectedString, foundString, prepend);
+}
+
+template <typename T>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 4, 5)))
+#endif
+NORETURN void expectedButFound(TResult result, T expected, T found, const char* prependFormat = "", ...)
+{
+    FMT_TO_RESULT(prependFormat, prependFormat, prepend);
+    std::string expectedString = vtos(expected);
+    std::string foundString = vtos(found);
+    __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str());
+}
+
+template <>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 4, 5)))
+#endif
+NORETURN void expectedButFound<std::string>(TResult result, std::string expected, std::string found, const char* prependFormat, ...)
+{
+    FMT_TO_RESULT(prependFormat, prependFormat, prepend);
+    __testlib_expectedButFound(result, expected, found, prepend.c_str());
+}
+
+template <>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 4, 5)))
+#endif
+NORETURN void expectedButFound<double>(TResult result, double expected, double found, const char* prependFormat, ...)
+{
+    FMT_TO_RESULT(prependFormat, prependFormat, prepend);
+    std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected));
+    std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found));
+    __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str());
+}
+
+template <>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 4, 5)))
+#endif
+NORETURN void expectedButFound<const char*>(TResult result, const char* expected, const char* found, const char* prependFormat, ...)
+{
+    FMT_TO_RESULT(prependFormat, prependFormat, prepend);
+    __testlib_expectedButFound(result, std::string(expected), std::string(found), prepend.c_str());
+}
+
+template <>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 4, 5)))
+#endif
+NORETURN void expectedButFound<float>(TResult result, float expected, float found, const char* prependFormat, ...)
+{
+    FMT_TO_RESULT(prependFormat, prependFormat, prepend);
+    __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str());
+}
+
+template <>
+#ifdef __GNUC__
+__attribute__ ((format (printf, 4, 5)))
+#endif
+NORETURN void expectedButFound<long double>(TResult result, long double expected, long double found, const char* prependFormat, ...)
+{
+    FMT_TO_RESULT(prependFormat, prependFormat, prepend);
+    __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str());
+}
+
+#endif

+ 38 - 0
judger/uoj_judger/include/uoj_env.h

@@ -0,0 +1,38 @@
+#include "uoj_work_path.h"
+
+#define UOJ_DATA_PATH UOJ_WORK_PATH "/data"
+#define UOJ_RESULT_PATH UOJ_WORK_PATH "/result"
+
+#define RS_SPJ_BASE 1000
+#define failed_spj RS_SPJ_BASE
+#define successed_hack 100000
+#define RS_SPJ RS_SPJ_BASE
+#define RS_HACK successed_hack
+#define RS_AC 0
+#define RS_WA 1
+#define RS_RE 2
+#define RS_MLE 3
+#define RS_TLE 4
+#define RS_OLE 5
+#define RS_DGS 6
+#define RS_JGF 7
+#define RS_SPJ_AC (RS_SPJ + RS_AC)
+#define RS_SPJ_RE (RS_SPJ_BASE + RS_RE)
+#define RS_SPJ_MLE (RS_SPJ_BASE + RS_MLE)
+#define RS_SPJ_TLE (RS_SPJ_BASE + RS_TLE)
+#define RS_SPJ_OLE (RS_SPJ_BASE + RS_OLE)
+#define RS_SPJ_DGS (RS_SPJ_BASE + RS_DGS)
+#define RS_SPJ_JGF (RS_SPJ_BASE + RS_JGF)
+#define RS_HK_RE (successed_hack + RS_RE)
+#define RS_HK_MLE (successed_hack + RS_MLE)
+#define RS_HK_TLE (successed_hack + RS_TLE)
+#define RS_HK_OLE (successed_hack + RS_OLE)
+#define RS_HK_DGS (successed_hack + RS_DGS)
+#define RS_HK_JGF (successed_hack + RS_JGF)
+#define RS_HK_SPJ_RE (successed_hack + RS_SPJ_RE)
+#define RS_HK_SPJ_MLE (successed_hack + RS_SPJ_MLE)
+#define RS_HK_SPJ_TLE (successed_hack + RS_SPJ_TLE)
+#define RS_HK_SPJ_OLE (successed_hack + RS_SPJ_OLE)
+#define RS_HK_SPJ_DGS (successed_hack + RS_SPJ_DGS)
+#define RS_HK_SPJ_JGF (successed_hack + RS_SPJ_JGF)
+

+ 1590 - 0
judger/uoj_judger/include/uoj_judger.h

@@ -0,0 +1,1590 @@
+#include <iostream>
+#include <algorithm>
+#include <sstream>
+#include <fstream>
+#include <vector>
+#include <set>
+#include <map>
+#include <cstdio>
+#include <cstdlib>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <ctime>
+#include <cstring>
+#include <string>
+#include <cstdarg>
+
+#include <unistd.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+
+#include "uoj_env.h"
+using namespace std;
+
+/*========================== execute ====================== */
+
+string escapeshellarg(const string &arg) {
+	string res = "'";
+	for (size_t i = 0; i < arg.size(); i++) {
+		if (arg[i] == '\'') {
+			res += "'\\''";
+		} else {
+			res += arg[i];
+		}
+	}
+	res += "'";
+	return res;
+}
+
+string realpath(const string &path) {
+	char real[PATH_MAX + 1];
+	if (realpath(path.c_str(), real) == NULL) {
+		return "";
+	}
+	return real;
+}
+
+
+int execute(const char *cmd) {
+	return system(cmd);
+}
+
+int executef(const char *fmt, ...) {
+	const int MaxL = 512;
+	char cmd[MaxL];
+	va_list ap;
+	va_start(ap, fmt);
+	int res = vsnprintf(cmd, MaxL, fmt, ap);
+	if (res < 0 || res >= MaxL) {
+		return -1;
+	}
+	res = execute(cmd);
+	va_end(ap);
+	return res;
+}
+
+/*======================== execute End ==================== */
+
+/*========================= file ====================== */
+
+string file_preview(const string &name, const size_t &len = 100) {
+	FILE *f = fopen(name.c_str(), "r");
+	if (f == NULL) {
+		return "";
+	}
+	string res = "";
+	int c;
+	while (c = fgetc(f), c != EOF && res.size() < len + 4) {
+		res += c;
+	}
+	if (res.size() > len + 3) {
+		res.resize(len);
+		res += "...";
+	}
+	fclose(f);
+	return res;
+}
+void file_hide_token(const string &name, const string &token) {
+	executef("cp %s %s.bak", name.c_str(), name.c_str());
+
+	FILE *rf = fopen((name + ".bak").c_str(), "r");
+	FILE *wf = fopen(name.c_str(), "w");
+	int c;
+	for (int i = 0; i <= (int)token.length(); i++)
+	{
+		c = fgetc(rf);
+		if (c != (i < (int)token.length() ? token[i] : '\n'))
+		{
+			fprintf(wf, "Unauthorized output\n");
+			fclose(rf);
+			fclose(wf);
+			return;
+		}
+	}
+	while (c = fgetc(rf), c != EOF) {
+		fputc(c, wf);
+	}
+	fclose(rf);
+	fclose(wf);
+}
+
+/*======================= file End ==================== */
+
+/*====================== parameter ==================== */
+
+struct RunLimit {
+	int time;
+	int real_time;
+	int memory;
+	int output;
+
+	RunLimit() {
+	}
+	RunLimit(const int &_time, const int &_memory, const int &_output)
+			: time(_time), memory(_memory), output(_output), real_time(-1) {
+	}
+};
+
+const RunLimit RL_DEFAULT = RunLimit(1, 256, 64);
+const RunLimit RL_JUDGER_DEFAULT = RunLimit(600, 1024, 128);
+const RunLimit RL_CHECKER_DEFAULT = RunLimit(5, 256, 64);
+const RunLimit RL_INTERACTOR_DEFAULT = RunLimit(1, 256, 64);
+const RunLimit RL_VALIDATOR_DEFAULT = RunLimit(5, 256, 64);
+const RunLimit RL_MARKER_DEFAULT = RunLimit(5, 256, 64);
+const RunLimit RL_COMPILER_DEFAULT = RunLimit(15, 512, 64);
+
+struct PointInfo  {
+	int num;
+	int scr;
+	int ust, usm;
+	string info, in, out, res;
+
+	PointInfo(const int &_num, const int &_scr,
+			const int &_ust, const int &_usm, const string &_info,
+			const string &_in, const string &_out, const string &_res)
+			: num(_num), scr(_scr),
+			ust(_ust), usm(_usm), info(_info),
+			in(_in), out(_out), res(_res) {
+		if (info == "default") {
+			if (scr == 0) {
+				info = "Wrong Answer";
+			} else if (scr == 100) {
+				info = "Accepted";
+			} else {
+				info = "Acceptable Answer";
+			}
+		}
+	}
+};
+
+struct CustomTestInfo  {
+	int ust, usm;
+	string info, exp, out;
+
+	CustomTestInfo(const int &_ust, const int &_usm, const string &_info,
+			const string &_exp, const string &_out)
+			: ust(_ust), usm(_usm), info(_info),
+			exp(_exp), out(_out) {
+	}
+};
+
+struct RunResult {
+	int type;
+	int ust, usm;
+	int exit_code;
+
+	static RunResult failed_result() {
+		RunResult res;
+		res.type = RS_JGF;
+		res.ust = -1;
+		res.usm = -1;
+		return res;
+	}
+
+	static RunResult from_file(const string &file_name) {
+		RunResult res;
+		FILE *fres = fopen(file_name.c_str(), "r");
+		if (fres == NULL || fscanf(fres, "%d %d %d %d", &res.type, &res.ust, &res.usm, &res.exit_code) != 4) {
+			return RunResult::failed_result();
+		}
+		fclose(fres);
+		return res;
+	}
+};
+struct RunCheckerResult {
+	int type;
+	int ust, usm;
+	int scr;
+	string info;
+
+	static RunCheckerResult from_file(const string &file_name, const RunResult &rres) {
+		RunCheckerResult res;
+		res.type = rres.type;
+		res.ust = rres.ust;
+		res.usm = rres.usm;
+
+		if (rres.type != RS_AC) {
+			res.scr = 0;
+		} else {
+			FILE *fres = fopen(file_name.c_str(), "r");
+			char type[21];
+			if (fres == NULL || fscanf(fres, "%20s", type) != 1) {
+				return RunCheckerResult::failed_result();
+			}
+			if (strcmp(type, "ok") == 0) {
+				res.scr = 100;
+			} else if (strcmp(type, "points") == 0) {
+				double d;
+				if (fscanf(fres, "%lf", &d) != 1) {
+					return RunCheckerResult::failed_result();
+				} else {
+					res.scr = (int)floor(100 * d + 0.5);
+				}
+			} else {
+				res.scr = 0;
+			}
+			fclose(fres);
+		}
+		res.info = file_preview(file_name);
+		return res;
+	}
+	
+	static RunCheckerResult failed_result() {
+		RunCheckerResult res;
+		res.type = RS_JGF;
+		res.ust = -1;
+		res.usm = -1;
+		res.scr = 0;
+		res.info = "Checker Judgement Failed";
+		return res;
+	}
+};
+struct RunValidatorResult {
+	int type;
+	int ust, usm;
+	bool succeeded;
+	string info;
+	
+	static RunValidatorResult failed_result() {
+		RunValidatorResult res;
+		res.type = RS_JGF;
+		res.ust = -1;
+		res.usm = -1;
+		res.succeeded = 0;
+		res.info = "Validator Judgement Failed";
+		return res;
+	}
+};
+struct RunCompilerResult {
+	int type;
+	int ust, usm;
+	bool succeeded;
+	string info;
+
+	static RunCompilerResult failed_result() {
+		RunCompilerResult res;
+		res.type = RS_JGF;
+		res.ust = -1;
+		res.usm = -1;
+		res.succeeded = false;
+		res.info = "Compile Failed";
+		return res;
+	}
+};
+
+// see also: run_simple_interaction
+struct RunSimpleInteractionResult {
+	RunResult res; // prog
+	RunCheckerResult ires; // interactor
+};
+
+int problem_id;
+string main_path;
+string work_path;
+string data_path;
+string result_path;
+
+int tot_time   = 0;
+int max_memory = 0;
+int tot_score  = 0;
+ostringstream details_out;
+//vector<PointInfo> points_info;
+map<string, string> config;
+
+/*==================== parameter End ================== */
+
+/*====================== config set =================== */
+
+void print_config() {
+	for (map<string, string>::iterator it = config.begin(); it != config.end(); ++it) {
+		cerr << it->first << " = " << it->second << endl;
+	}
+}
+void load_config(const string &filename) {
+	ifstream fin(filename.c_str());
+	if (!fin) {
+		return;
+	}
+	string key;
+	string val;
+	while (fin >> key >> val) {
+		config[key] = val;
+	}
+}
+string conf_str(const string &key, int num, const string &val) {
+	ostringstream sout;
+	sout << key << "_" << num;
+	if (config.count(sout.str()) == 0) {
+		return val;
+	}
+	return config[sout.str()];
+}
+string conf_str(const string &key, const string &val) {
+	if (config.count(key) == 0) {
+		return val;
+	}
+	return config[key];
+}
+string conf_str(const string &key) {
+	return conf_str(key, "");
+}
+int conf_int(const string &key, const int &val) {
+	if (config.count(key) == 0) {
+		return val;
+	}
+	return atoi(config[key].c_str());
+}
+int conf_int(const string &key, int num, const int &val) {
+	ostringstream sout;
+	sout << key << "_" << num;
+	if (config.count(sout.str()) == 0) {
+		return conf_int(key, val);
+	}
+	return atoi(config[sout.str()].c_str());
+}
+int conf_int(const string &key)  {
+	return conf_int(key, 0);
+}
+string conf_input_file_name(int num) {
+	ostringstream name;
+	if (num < 0) {
+		name << "ex_";
+	}
+	name << conf_str("input_pre", "input") << abs(num) << "." << conf_str("input_suf", "txt");
+	return name.str();
+}
+string conf_output_file_name(int num) {
+	ostringstream name;
+	if (num < 0) {
+		name << "ex_";
+	}
+	name << conf_str("output_pre", "output") << abs(num) << "." << conf_str("output_suf", "txt");
+	return name.str();
+}
+RunLimit conf_run_limit(string pre, const int &num, const RunLimit &val) {
+	if (!pre.empty()) {
+		pre += "_";
+	}
+	RunLimit limit;
+	limit.time = conf_int(pre + "time_limit", num, val.time);
+	limit.memory = conf_int(pre + "memory_limit", num, val.memory);
+	limit.output = conf_int(pre + "output_limit", num, val.output);
+	return limit;
+}
+RunLimit conf_run_limit(const int &num, const RunLimit &val) {
+	return conf_run_limit("", num, val);
+}
+void conf_add(const string &key, const string &val)  {
+	if (config.count(key))  return;
+	config[key] = val;
+}
+bool conf_has(const string &key)  {
+	return config.count(key) != 0;
+}
+bool conf_is(const string &key, const string &val)  {
+	if (config.count(key) == 0)  return false;
+	return config[key] == val;
+}
+
+/*==================== config set End ================= */
+
+/*====================== info print =================== */
+
+template <class T>
+inline string vtos(const T &v) {
+	ostringstream sout;
+	sout << v;
+	return sout.str();
+}
+
+inline string htmlspecialchars(const string &s) {
+	string r;
+	for (int i = 0; i < (int)s.length(); i++) {
+		switch (s[i]) {
+		case '&' : r += "&amp;"; break;
+		case '<' : r += "&lt;"; break;
+		case '>' : r += "&gt;"; break;
+		case '"' : r += "&quot;"; break;
+		case '\0': r += "<b>\\0</b>"; break;
+		default  : r += s[i]; break;
+		}
+	}
+	return r;
+}
+
+inline string info_str(int id)  {
+	switch (id) {
+	case RS_MLE: return "Memory Limit Exceeded";
+	case RS_TLE: return "Time Limit Exceeded";
+	case RS_OLE: return "Output Limit Exceeded";
+	case RS_RE : return "Runtime Error";
+	case RS_DGS: return "Dangerous Syscalls";
+	case RS_JGF: return "Judgement Failed";
+	default    : return "Unknown Result";
+	}
+}
+inline string info_str(const RunResult &p)  {
+	return info_str(p.type);
+}
+
+void add_point_info(const PointInfo &info, bool update_tot_score = true) {
+	if (info.num >= 0) {
+		if(info.ust >= 0) {
+			tot_time += info.ust;
+		}
+		if(info.usm >= 0) {
+			max_memory = max(max_memory, info.usm);
+		}
+	}
+	if (update_tot_score) {
+		tot_score += info.scr;
+	}
+
+	details_out << "<test num=\"" << info.num << "\""
+		<< " score=\"" << info.scr << "\""
+		<< " info=\"" << htmlspecialchars(info.info) << "\""
+		<< " time=\"" << info.ust << "\""
+		<< " memory=\"" << info.usm << "\">" << endl;
+	if (conf_str("show_in", "on") == "on") {
+		details_out << "<in>" << htmlspecialchars(info.in) << "</in>" << endl;
+	}
+	if (conf_str("show_out", "on") == "on") {
+		details_out << "<out>" << htmlspecialchars(info.out) << "</out>" << endl;
+	}
+	if (conf_str("show_res", "on") == "on") {
+		details_out << "<res>" << htmlspecialchars(info.res) << "</res>" << endl;
+	}
+	details_out << "</test>" << endl;
+}
+void add_custom_test_info(const CustomTestInfo &info) {
+	if(info.ust >= 0) {
+		tot_time += info.ust;
+	}
+	if(info.usm >= 0) {
+		max_memory = max(max_memory, info.usm);
+	}
+
+	details_out << "<custom-test info=\"" << htmlspecialchars(info.info) << "\""
+		<< " time=\"" << info.ust << "\""
+		<< " memory=\"" << info.usm << "\">" << endl;
+	if (!info.exp.empty()) {
+		details_out << info.exp << endl;
+	}
+	details_out << "<out>" << htmlspecialchars(info.out) << "</out>" << endl;
+	details_out << "</custom-test>" << endl;
+}
+void add_subtask_info(const int &num, const int &scr, const string &info, const vector<PointInfo> &points) {
+	details_out << "<subtask num=\"" << num << "\""
+		<< " score=\"" << scr << "\""
+		<< " info=\"" << htmlspecialchars(info) << "\">" << endl;
+	tot_score += scr;
+	for (vector<PointInfo>::const_iterator it = points.begin(); it != points.end(); it++) {
+		add_point_info(*it, false);
+	}
+	details_out << "</subtask>" << endl;
+}
+void end_judge_ok() {
+	FILE *fres = fopen((result_path + "/result.txt").c_str(), "w");
+	fprintf(fres, "score %d\n", tot_score);
+	fprintf(fres, "time %d\n", tot_time);
+	fprintf(fres, "memory %d\n", max_memory);
+	fprintf(fres, "details\n");
+	fprintf(fres, "<tests>\n");
+	fprintf(fres, "%s", details_out.str().c_str());
+	fprintf(fres, "</tests>\n");
+	fclose(fres);
+	exit(0);
+}
+void end_judge_judgement_failed(const string &info) {
+	FILE *fres = fopen((result_path + "/result.txt").c_str(), "w");
+	fprintf(fres, "error Judgement Failed\n");
+	fprintf(fres, "details\n");
+	fprintf(fres, "<error>%s</error>\n", htmlspecialchars(info).c_str());
+	fclose(fres);
+	exit(1);
+}
+void end_judge_compile_error(const RunCompilerResult &res) {
+	FILE *fres = fopen((result_path + "/result.txt").c_str(), "w");
+	fprintf(fres, "error Compile Error\n");
+	fprintf(fres, "details\n");
+	fprintf(fres, "<error>%s</error>\n", htmlspecialchars(res.info).c_str());
+	fclose(fres);
+	exit(0);
+}
+
+void report_judge_status(const char *status) {
+	FILE *f = fopen((result_path + "/cur_status.txt").c_str(), "a");
+	if (f == NULL) {
+		return;
+	}
+	if (flock(fileno(f), LOCK_EX) != -1) {
+		if (ftruncate(fileno(f), 0) != -1) {
+			fprintf(f, "%s\n", status);
+			fflush(f);
+		}
+		flock(fileno(f), LOCK_UN);
+	}
+	fclose(f);
+}
+bool report_judge_status_f(const char *fmt, ...) {
+	const int MaxL = 512;
+	char status[MaxL];
+	va_list ap;
+	va_start(ap, fmt);
+	int res = vsnprintf(status, MaxL, fmt, ap);
+	if (res < 0 || res >= MaxL) {
+		return false;
+	}
+	report_judge_status(status);
+	va_end(ap);
+	return true;
+}
+
+/*==================== info print End ================= */
+
+/*========================== run ====================== */
+
+struct RunProgramConfig {
+	vector<string> readable_file_names; // other than stdin
+	string result_file_name;
+	string input_file_name;
+	string output_file_name;
+	string error_file_name;
+	string type;
+	string work_path;
+	RunLimit limit;
+	vector<string> argv;
+
+	RunProgramConfig() {
+		int p = 1;
+		while (conf_str("readable", p, "") != "") {
+			readable_file_names.push_back(conf_str("readable", p, ""));
+			p++;
+		}
+		result_file_name = result_path + "/run_program_result.txt";
+		type = "default";
+		work_path = ::work_path;
+
+		for (vector<string>::iterator it = readable_file_names.begin(); it != readable_file_names.end(); it++) {
+			if (!it->empty() && (*it)[0] != '/') {
+				*it = ::work_path + "/" + *it;
+			}
+		}
+	}
+
+	void set_argv(const char *program_name, ...) {
+		argv.clear();
+		argv.push_back(program_name);
+		va_list vl;
+		va_start(vl, program_name);
+		for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) {
+			argv.push_back(arg);
+		}
+		va_end(vl);
+	}
+
+	void set_submission_program_name(string name) {
+		string lang = conf_str(string(name) + "_language");
+		type = "default";
+		string program_name = name;
+		if (lang == "Python2") {
+			type = "python2";
+		} else if (lang == "Python3") {
+			type = "python3";
+		} else if (lang == "Java8") {
+			program_name += "." + conf_str(name + "_main_class");
+			type = "java8";
+		} else if (lang == "Java11") {
+			program_name += "." + conf_str(name + "_main_class");
+			type = "java11";
+		}
+
+		set_argv(program_name.c_str(), NULL);
+	}
+
+	string get_cmd() const {
+		ostringstream sout;
+		sout << main_path << "/run/run_program"
+			<< " " << "--res=" << escapeshellarg(result_file_name)
+			<< " " << "--in=" << escapeshellarg(input_file_name)
+			<< " " <<"--out=" << escapeshellarg(output_file_name)
+			<< " " << "--err=" << escapeshellarg(error_file_name)
+			<< " " << "--tl=" << limit.time
+			<< " " << "--ml=" << limit.memory
+			<< " " << "--ol=" << limit.output
+			<< " " << "--type=" << type
+			<< " " << "--work-path=" << work_path
+			/*<< " " << "--show-trace-details"*/;
+		for (vector<string>::const_iterator it = readable_file_names.begin(); it != readable_file_names.end(); it++) {
+			sout << " " << "--add-readable=" << escapeshellarg(*it);
+		}
+		for (vector<string>::const_iterator it = argv.begin(); it != argv.end(); it++) {
+			sout << " " << escapeshellarg(*it);
+		}
+		return sout.str();
+	}
+};
+
+struct PipeConfig {
+	int from, to;
+	int from_fd, to_fd;
+
+	string saving_file_name;
+
+	PipeConfig() {
+	}
+	PipeConfig(int _from, int _from_fd, int _to, int _to_fd, const string &_saving_file_name = "")
+			: from(_from), from_fd(_from_fd), to(_to), to_fd(_to_fd), saving_file_name(_saving_file_name) {
+	}
+};
+struct RunInteractionConfig {
+	vector<string> cmds;
+	vector<PipeConfig> pipes;
+
+	string get_cmd() const {
+		ostringstream sout;
+		sout << main_path << "/run/run_interaction";
+		for (int i = 0; i < (int)cmds.size(); i++) {
+			sout << " " << escapeshellarg(cmds[i]);
+		}
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			sout << " " << "-p";
+			sout << " " << pipes[i].from << ":" << pipes[i].from_fd;
+			sout << "-" << pipes[i].to << ":" << pipes[i].to_fd;
+
+			if (!pipes[i].saving_file_name.empty()) {
+				sout << " " << "-s";
+				sout << " " << escapeshellarg(pipes[i].saving_file_name);
+			}
+		}
+		return sout.str();
+	}
+};
+
+// @deprecated
+// will be removed in the future
+RunResult vrun_program(
+		const char *run_program_result_file_name,
+		const char *input_file_name,
+		const char *output_file_name,
+		const char *error_file_name,
+		const RunLimit &limit,
+		const vector<string> &rest) {
+	ostringstream sout;
+	sout << main_path << "/run/run_program"
+		<< " " << "--res=" << escapeshellarg(run_program_result_file_name)
+		<< " " << "--in=" << escapeshellarg(input_file_name)
+		<< " " <<"--out=" << escapeshellarg(output_file_name)
+		<< " " << "--err=" << escapeshellarg(error_file_name)
+		<< " " << "--tl=" << limit.time
+		<< " " << "--ml=" << limit.memory
+		<< " " << "--ol=" << limit.output
+		/*<< " " << "--show-trace-details"*/;
+	for (vector<string>::const_iterator it = rest.begin(); it != rest.end(); it++) {
+		sout << " " << escapeshellarg(*it);
+	}
+
+	if (execute(sout.str().c_str()) != 0) {
+		return RunResult::failed_result();
+	}
+	return RunResult::from_file(run_program_result_file_name);
+}
+
+RunResult run_program(const RunProgramConfig &rpc) {
+	if (execute(rpc.get_cmd().c_str()) != 0) {
+		return RunResult::failed_result();
+	}
+	return RunResult::from_file(rpc.result_file_name);
+}
+
+// @return interaction return value
+int run_interaction(const RunInteractionConfig &ric) {
+	return execute(ric.get_cmd().c_str());
+}
+
+RunResult run_program(
+		const char *run_program_result_file_name,
+		const char *input_file_name,
+		const char *output_file_name,
+		const char *error_file_name,
+		const RunLimit &limit, ...) {
+	vector<string> argv;
+	va_list vl;
+	va_start(vl, limit);
+	for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) {
+		argv.push_back(arg);
+	}
+	va_end(vl);
+	return vrun_program(run_program_result_file_name,
+			input_file_name,
+			output_file_name,
+			error_file_name,
+			limit,
+			argv);
+}
+
+RunValidatorResult run_validator(
+		const string &input_file_name,
+		const RunLimit &limit,
+		const string &program_name) {
+	RunResult ret = run_program(
+			(string(result_path) + "/run_validator_result.txt").c_str(),
+			input_file_name.c_str(),
+			"/dev/null",
+			(string(result_path) + "/validator_error.txt").c_str(),
+			limit,
+			program_name.c_str(),
+			NULL);
+
+	RunValidatorResult res;
+	res.type = ret.type;
+	res.ust = ret.ust;
+	res.usm = ret.usm;
+
+	if (ret.type != RS_AC || ret.exit_code != 0) {
+		res.succeeded = false;
+		res.info = file_preview(result_path + "/validator_error.txt");
+	} else {
+		res.succeeded = true;
+	}
+	return res;
+}
+RunCheckerResult run_checker(
+		const RunLimit &limit,
+		const string &program_name,
+		const string &input_file_name,
+		const string &output_file_name,
+		const string &answer_file_name) {
+	RunResult ret = run_program(
+			(string(result_path) + "/run_checker_result.txt").c_str(),
+			"/dev/null",
+			"/dev/null",
+			(string(result_path) + "/checker_error.txt").c_str(),
+			limit,
+			("--add-readable=" + input_file_name).c_str(),
+			("--add-readable=" + output_file_name).c_str(),
+			("--add-readable=" + answer_file_name).c_str(),
+			program_name.c_str(),
+			realpath(input_file_name).c_str(),
+			realpath(output_file_name).c_str(),
+			realpath(answer_file_name).c_str(),
+			NULL);
+
+	return RunCheckerResult::from_file(result_path + "/checker_error.txt", ret);
+}
+
+RunCompilerResult run_compiler(const char *path, ...) {
+	vector<string> argv;
+	argv.push_back("--type=compiler");
+	argv.push_back(string("--work-path=") + path);
+	va_list vl;
+	va_start(vl, path);
+	for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) {
+		argv.push_back(arg);
+	}
+	va_end(vl);
+
+	RunResult ret = vrun_program(
+			(result_path + "/run_compiler_result.txt").c_str(),
+			"/dev/null",
+			"stderr",
+			(result_path + "/compiler_result.txt").c_str(),
+			RL_COMPILER_DEFAULT,
+			argv);
+	RunCompilerResult res;
+	res.type = ret.type;
+	res.ust = ret.ust;
+	res.usm = ret.usm; 
+	res.succeeded = ret.type == RS_AC && ret.exit_code == 0;
+	if (!res.succeeded) {
+		if (ret.type == RS_AC) {
+			res.info = file_preview(result_path + "/compiler_result.txt", 500);
+		} else if (ret.type == RS_JGF) {
+			res.info = "No Comment";
+		} else {
+			res.info = "Compiler " + info_str(ret.type);
+		}
+	}
+	return res;
+}
+
+RunResult run_submission_program(
+		const string &input_file_name,
+		const string &output_file_name,
+		const RunLimit &limit,
+		const string &name,
+		RunProgramConfig rpc = RunProgramConfig()) {
+	rpc.result_file_name = result_path + "/run_submission_program.txt";
+
+        if (!getenv("ONLINE_JUDGE_UBSAN")) {
+                rpc.output_file_name = output_file_name;
+                rpc.error_file_name = "/dev/null";
+        }
+        else {
+                rpc.output_file_name = "/dev/null";
+                rpc.error_file_name = output_file_name;
+        }
+	
+	rpc.input_file_name = input_file_name;
+	rpc.limit = limit;
+	rpc.set_submission_program_name(name);
+
+	RunResult res = run_program(rpc);
+	if (res.type == RS_AC && res.exit_code != 0) {
+		res.type = RS_RE;
+	}
+	return res;
+}
+
+void prepare_interactor() {
+	static bool prepared = false;
+	if (prepared) {
+		return;
+	}
+	string data_path_std = data_path + "/interactor";
+	string work_path_std = work_path + "/interactor";
+	executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str());
+	conf_add("interactor_language", "C++");
+	prepared = true;
+}
+
+// simple: prog <---> interactor <---> data
+RunSimpleInteractionResult run_simple_interation(
+		const string &input_file_name,
+		const string &answer_file_name,
+		const string &real_input_file_name,
+		const string &real_output_file_name,
+		const RunLimit &limit,
+		const RunLimit &ilimit,
+		const string &name,
+		RunProgramConfig rpc = RunProgramConfig(),
+		RunProgramConfig irpc = RunProgramConfig()) {
+	prepare_interactor();
+
+	rpc.result_file_name = result_path + "/run_submission_program.txt";
+	rpc.input_file_name = "stdin";
+	rpc.output_file_name = "stdout";
+	rpc.error_file_name = "/dev/null";
+	rpc.limit = limit;
+	rpc.set_submission_program_name(name);
+
+	irpc.result_file_name = result_path + "/run_interactor_program.txt";
+	irpc.readable_file_names.push_back(input_file_name);
+	irpc.readable_file_names.push_back(answer_file_name);
+	irpc.input_file_name = "stdin";
+	irpc.output_file_name = "stdout";
+	irpc.error_file_name = result_path + "/interactor_error.txt";
+	irpc.limit = ilimit;
+	irpc.set_submission_program_name("interactor");
+	irpc.argv.push_back(input_file_name);
+	irpc.argv.push_back("/dev/stdin");
+	irpc.argv.push_back(answer_file_name);
+
+	irpc.limit.real_time = rpc.limit.real_time = rpc.limit.time + irpc.limit.time + 1;
+
+	RunInteractionConfig ric;
+	ric.cmds.push_back(rpc.get_cmd());
+	ric.cmds.push_back(irpc.get_cmd());
+
+	// from:fd, to:fd
+	ric.pipes.push_back(PipeConfig(2, 1, 1, 0, real_input_file_name));
+	ric.pipes.push_back(PipeConfig(1, 1, 2, 0, real_output_file_name));
+	
+	run_interaction(ric);
+
+	RunSimpleInteractionResult rires;
+	RunResult res = RunResult::from_file(rpc.result_file_name);
+	RunCheckerResult ires = RunCheckerResult::from_file(irpc.error_file_name, RunResult::from_file(irpc.result_file_name));
+
+	if (res.type == RS_AC && res.exit_code != 0) {
+		res.type = RS_RE;
+	}
+
+	if (ires.type == RS_JGF) {
+		ires.info = "Interactor Judgement Failed";
+	}
+	if (ires.type == RS_TLE) {
+		ires.type = RS_AC;
+		res.type = RS_TLE;
+	}
+
+	rires.res = res;
+	rires.ires = ires;
+	return rires;
+}
+
+void prepare_run_standard_program() {
+	static bool prepared = false;
+	if (prepared) {
+		return;
+	}
+	string data_path_std = data_path + "/std";
+	string work_path_std = work_path + "/std";
+	executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str());
+	conf_add("std_language", "C++");
+	prepared = true;
+}
+
+// @deprecated
+// will be removed in the future
+RunResult run_standard_program(
+		const string &input_file_name,
+		const string &output_file_name,
+		const RunLimit &limit,
+		RunProgramConfig rpc = RunProgramConfig()) {
+	prepare_run_standard_program();
+	rpc.result_file_name = result_path + "/run_standard_program.txt";
+	return run_submission_program(
+			input_file_name,
+			output_file_name,
+			limit,
+			"std",
+			rpc);
+}
+
+/*======================== run End ==================== */
+
+/*======================== compile ==================== */
+
+bool is_illegal_keyword(const string &name) {
+	if (name == "__asm" || name == "__asm__" || name == "asm")
+		return true;
+	return false;
+}
+
+bool has_illegal_keywords_in_file(const string &name) {
+	FILE *f = fopen(name.c_str(), "r");
+
+	int c;
+	string key;
+	while ((c = fgetc(f)) != EOF)
+	{
+		if (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_')
+		{
+			if (key.size() < 20)
+				key += c;
+			else
+			{
+				if (is_illegal_keyword(key))
+					return true;
+				key.erase(key.begin());
+				key += c;
+			}
+		}
+		else
+		{
+			if (is_illegal_keyword(key))
+				return true;
+			key.clear();
+		}
+	}
+	if (is_illegal_keyword(key))
+		return true;
+	fclose(f);
+	return false;
+}
+
+RunCompilerResult prepare_java_source(const string &name, const string &path = work_path) {
+	FILE *f = fopen((path + "/" + name + ".code").c_str(), "r");
+
+	const int L = 1024;
+
+	std::string s;
+	char buf[L + 1];
+
+	int mode = 0;
+
+	while (!feof(f)) {
+		buf[fread(buf, 1, L, f)] = '\0';
+
+		for (char *p = buf; *p; p++) {
+			s += *p;
+			switch (mode) {
+				case 0:
+					switch (*p) {
+						case '/':
+							mode = 1;
+							break;
+						case '\'':
+							mode = 5;
+							break;
+						case '\"':
+							mode = 6;
+							break;
+					}
+					break;
+				case 1:
+					switch (*p) {
+						case '/':
+							mode = 2;
+							s.resize(s.length() - 2);
+							break;
+						case '*':
+							mode = 3;
+							s.resize(s.length() - 2);
+							break;
+					}
+					break;
+				case 2:
+					s.resize(s.length() - 1);
+					switch (*p) {
+						case '\n':
+							s += '\n';
+							mode = 0;
+							break;
+					}
+					break;
+				case 3:
+					s.resize(s.length() - 1);
+					switch (*p) {
+						case '*':
+							mode = 4;
+							break;
+					}
+					break;
+				case 4:
+					s.resize(s.length() - 1);
+					switch (*p) {
+						case '/':
+							s += ' ';
+							mode = 0;
+							break;
+					}
+					break;
+				case 5:
+					switch (*p) {
+						case '\'':
+							mode = 0;
+							break;
+						case '\\':
+							mode = 7;
+							break;
+					}
+				case 6:
+					switch (*p) {
+						case '\"':
+							mode = 0;
+							break;
+						case '\\':
+							mode = 8;
+							break;
+					}
+				case 7:
+					mode = 5;
+					break;
+				case 8:
+					mode = 6;
+					break;
+			}
+		}
+	}
+
+	bool valid[256];
+	fill(valid, valid + 256, false);
+	for (int c = 'a'; c <= 'z'; c++)
+		valid[c] = true;
+	for (int c = 'A'; c <= 'Z'; c++)
+		valid[c] = true;
+	valid['.'] = true;
+	valid['_'] = true;
+
+	vector<string> tokens;
+	for (int p = 0, np = 0; p < (int)s.length(); p = np) {
+		while (np < (int)s.length() && valid[(unsigned char)s[np]]) {
+			np++;
+		}
+		if (np == p) {
+			np++;
+		} else {
+			tokens.push_back(s.substr(p, np - p));
+		}
+	}
+	if (tokens.size() > 0 && tokens[0] == "package") {
+		RunCompilerResult res;
+		res.type = RS_WA;
+		res.ust = -1;
+		res.usm = -1;
+		res.succeeded = false;
+		res.info = "Please don't specify the package.";
+		return res;
+	}
+
+	for (int i = 0; i + 1 < (int)tokens.size(); i++) {
+		if (tokens[i] == "class") {
+			if (tokens[i + 1].length() <= 100) {
+				config[name + "_main_class"] = tokens[i + 1];
+
+				RunCompilerResult res;
+				res.type = RS_AC;
+				res.ust = 0;
+				res.usm = 0;
+				res.succeeded = true;
+				return res;
+			} else {
+				RunCompilerResult res;
+				res.type = RS_WA;
+				res.ust = -1;
+				res.usm = -1;
+				res.succeeded = false;
+				res.info = "The name of the main class is too long.";
+				return res;
+			}
+		}
+	}
+
+	RunCompilerResult res;
+	res.type = RS_WA;
+	res.ust = -1;
+	res.usm = -1;
+	res.succeeded = false;
+	res.info = "Can't find the main class.";
+	return res;
+}
+
+RunCompilerResult compile_c(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(), 
+			"/usr/bin/gcc", "-o", name.c_str(), "-x", "c", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL);
+}
+RunCompilerResult compile_pas(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/fpc", (name + ".code").c_str(), "-O2", NULL);
+}
+RunCompilerResult compile_cpp14(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/g++", "-std=c++14", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL);
+}
+RunCompilerResult compile_cpp17(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/g++", "-std=c++17", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL);
+}
+RunCompilerResult compile_cpp17ubsan(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/g++", "-fsanitize=undefined", "-std=c++17", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL);
+}
+RunCompilerResult compile_python2(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/python2", "-E", "-s", "-B", "-O", "-c",
+			("import py_compile\nimport sys\ntry:\n    py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n    sys.exit(0)\nexcept Exception as e:\n    print e\n    sys.exit(1)").c_str(), NULL);
+}
+RunCompilerResult compile_python3(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/python3", "-I", "-B", "-O", "-c", ("import py_compile\nimport sys\ntry:\n    py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n    sys.exit(0)\nexcept Exception as e:\n    print(e)\n    sys.exit(1)").c_str(), NULL);
+}
+RunCompilerResult compile_java8(const string &name, const string &path = work_path) {
+	RunCompilerResult ret = prepare_java_source(name, path);
+	if (!ret.succeeded)
+		return ret;
+
+	string main_class = conf_str(name + "_main_class");
+
+	executef("rm %s/%s -rf 2>/dev/null; mkdir %s/%s", path.c_str(), name.c_str(), path.c_str(), name.c_str());
+	executef("echo package %s\\; | cat - %s/%s.code >%s/%s/%s.java", name.c_str(), path.c_str(), name.c_str(), path.c_str(), name.c_str(), main_class.c_str());
+
+	return run_compiler((path + "/" + name).c_str(),
+			"/usr/lib/jvm/java-8-openjdk-amd64/bin/javac", (main_class + ".java").c_str(), NULL);
+}
+RunCompilerResult compile_java11(const string &name, const string &path = work_path) {
+	RunCompilerResult ret = prepare_java_source(name, path);
+	if (!ret.succeeded)
+		return ret;
+
+	string main_class = conf_str(name + "_main_class");
+
+	executef("rm %s/%s -rf 2>/dev/null; mkdir %s/%s", path.c_str(), name.c_str(), path.c_str(), name.c_str());
+	executef("echo package %s\\; | cat - %s/%s.code >%s/%s/%s.java", name.c_str(), path.c_str(), name.c_str(), path.c_str(), name.c_str(), main_class.c_str());
+
+	return run_compiler((path + "/" + name).c_str(),
+			"/usr/lib/jvm/java-11-openjdk-amd64/bin/javac", (main_class + ".java").c_str(), NULL);
+}
+
+// ryp
+RunCompilerResult compile(const char *name)  {
+        string lang = conf_str(string(name) + "_language");
+
+        if (lang == "C++17ubsan")
+                setenv ("ONLINE_JUDGE_UBSAN", "1", 1);
+
+        if ((lang == "C++14" || lang == "C++17" || lang == "C++17ubsan" || lang == "C") && has_illegal_keywords_in_file(work_path + "/" + name + ".code"))
+        {
+                RunCompilerResult res;
+                res.type = RS_DGS;
+                res.ust = -1;
+                res.usm = -1;
+                res.succeeded = false;
+                res.info = "Compile Failed";
+                return res;
+        }
+
+        if (lang == "C++14") {
+                return compile_cpp14(name);
+        }
+        if (lang == "C++17") {
+                return compile_cpp17(name);
+        }
+        if (lang == "C++17ubsan") {
+                return compile_cpp17ubsan(name);
+        }
+	if (lang == "Python2") {
+		return compile_python2(name);
+	}
+	if (lang == "Python3") {
+		return compile_python3(name);
+	}
+	if (lang == "Java8") {
+		return compile_java8(name);
+	}
+	if (lang == "Java11") {
+		return compile_java11(name);
+	}
+	if (lang == "C") {
+		return compile_c(name);
+	}
+	if (lang == "Pascal") {
+		return compile_pas(name);
+	}
+
+	RunCompilerResult res = RunCompilerResult::failed_result();
+	res.info = "This language is not supported yet.";
+	return res;
+}
+
+RunCompilerResult compile_c_with_implementer(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(), 
+			"/usr/bin/gcc", "-o", name.c_str(), "implementer.c", "-x", "c", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL);
+}
+RunCompilerResult compile_pas_with_implementer(const string &name, const string &path = work_path) {
+	executef("cp %s %s", (path + "/" + name + ".code").c_str(), (path + "/" + conf_str(name + "_unit_name") + ".pas").c_str());
+	return run_compiler(path.c_str(),
+			"/usr/bin/fpc", "implementer.pas", ("-o" + name).c_str(), "-O2", NULL);
+}
+RunCompilerResult compile_cpp14_with_implementer(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/g++", "-std=c++14", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL);
+}
+RunCompilerResult compile_cpp17_with_implementer(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/g++", "-std=c++17", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++11", NULL);
+}
+/*
+RunCompilerResult compile_python2(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/python2", "-E", "-s", "-B", "-O", "-c",
+			("import py_compile\nimport sys\ntry:\n    py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n    sys.exit(0)\nexcept Exception as e:\n    print e\n    sys.exit(1)").c_str(), NULL);
+}
+RunCompilerResult compile_python3(const string &name, const string &path = work_path) {
+	return run_compiler(path.c_str(),
+			"/usr/bin/python3", "-I", "-B", "-O", "-c", ("import py_compile\nimport sys\ntry:\n    py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n    sys.exit(0)\nexcept Exception as e:\n    print(e)\n    sys.exit(1)").c_str(), NULL);
+}
+*/
+RunCompilerResult compile_with_implementer(const char *name)  {
+	string lang = conf_str(string(name) + "_language");
+
+	if (has_illegal_keywords_in_file(work_path + "/" + name + ".code"))
+	{
+		RunCompilerResult res;
+		res.type = RS_DGS;
+		res.ust = -1;
+		res.usm = -1;
+		res.succeeded = false;
+		res.info = "Compile Failed";
+		return res;
+	}
+
+	if (lang == "C++14") {
+		return compile_cpp14_with_implementer(name);
+	}
+	if (lang == "C++17") {
+		return compile_cpp17_with_implementer(name);
+	}
+	if (lang == "C") {
+		return compile_c_with_implementer(name);
+	}
+	if (lang == "Pascal") {
+		return compile_pas_with_implementer(name);
+	}
+
+	RunCompilerResult res = RunCompilerResult::failed_result();
+	res.info = "This language is not supported yet.";
+	return res;
+}
+
+/*====================== compile End ================== */
+
+/*======================    test     ================== */
+
+struct TestPointConfig {
+	int submit_answer;
+
+	int validate_input_before_test;
+	string input_file_name;
+	string output_file_name;
+	string answer_file_name;
+
+	TestPointConfig()
+			: submit_answer(-1), validate_input_before_test(-1) {
+	}
+
+	void auto_complete(int num) {
+		if (submit_answer == -1) {
+			submit_answer = conf_is("submit_answer", "on");
+		}
+		if (validate_input_before_test == -1) {
+			validate_input_before_test = conf_is("validate_input_before_test", "on");
+		}
+		if (input_file_name.empty()) {
+			input_file_name = data_path + "/" + conf_input_file_name(num);
+		}
+		if (output_file_name.empty()) {
+			output_file_name = work_path + "/" + conf_output_file_name(num);
+		}
+		if (answer_file_name.empty()) {
+			answer_file_name = data_path + "/" + conf_output_file_name(num);
+		}
+	}
+};
+
+PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = TestPointConfig()) {
+	tpc.auto_complete(num);
+
+	if (tpc.validate_input_before_test) {
+		RunValidatorResult val_ret = run_validator(
+				tpc.input_file_name,
+				conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT),
+				conf_str("validator"));
+		if (val_ret.type != RS_AC) {
+			return PointInfo(num, 0, -1, -1,
+					"Validator " + info_str(val_ret.type),
+					file_preview(tpc.input_file_name), "",
+					"");
+		} else if (!val_ret.succeeded) {
+			return PointInfo(num, 0, -1, -1,
+					"Invalid Input",
+					file_preview(tpc.input_file_name), "",
+					val_ret.info);
+		}
+	}
+
+	if (!conf_is("interaction_mode", "on")) {
+		RunResult pro_ret;
+		if (!tpc.submit_answer) {
+			pro_ret = run_submission_program(
+					tpc.input_file_name.c_str(),
+					tpc.output_file_name.c_str(),
+					conf_run_limit(num, RL_DEFAULT),
+					name);
+			if (conf_has("token")) {
+				file_hide_token(tpc.output_file_name, conf_str("token", ""));
+			}
+			if (pro_ret.type != RS_AC) {
+				return PointInfo(num, 0, -1, -1,
+						info_str(pro_ret.type),
+						file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
+						"");
+			}
+		} else {
+			pro_ret.type = RS_AC;
+			pro_ret.ust = -1;
+			pro_ret.usm = -1;
+			pro_ret.exit_code = 0;
+		}
+
+		RunCheckerResult chk_ret = run_checker(
+				conf_run_limit("checker", num, RL_CHECKER_DEFAULT),
+				conf_str("checker"),
+				tpc.input_file_name,
+				tpc.output_file_name,
+				tpc.answer_file_name);
+		if (chk_ret.type != RS_AC) {
+			return PointInfo(num, 0, -1, -1,
+					"Checker " + info_str(chk_ret.type),
+					file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
+					"");
+		}
+
+		return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, 
+				"default",
+				file_preview(tpc.input_file_name), file_preview(tpc.output_file_name),
+				chk_ret.info);
+	} else {
+		string real_output_file_name = tpc.output_file_name + ".real_input.txt";
+		string real_input_file_name = tpc.output_file_name + ".real_output.txt";
+		RunSimpleInteractionResult rires = run_simple_interation(
+				tpc.input_file_name,
+				tpc.answer_file_name,
+				real_input_file_name,
+				real_output_file_name,
+				conf_run_limit(num, RL_DEFAULT),
+				conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT),
+				name);
+
+		if (rires.ires.type != RS_AC) {
+			return PointInfo(num, 0, -1, -1,
+					"Interactor " + info_str(rires.ires.type),
+					file_preview(real_input_file_name), file_preview(real_output_file_name),
+					"");
+		}
+		if (rires.res.type != RS_AC) {
+			return PointInfo(num, 0, -1, -1,
+					info_str(rires.res.type),
+					file_preview(real_input_file_name), file_preview(real_output_file_name),
+					"");
+		}
+
+		return PointInfo(num, rires.ires.scr, rires.res.ust, rires.res.usm, 
+				"default",
+				file_preview(real_input_file_name), file_preview(real_output_file_name),
+				rires.ires.info);
+	}
+}
+
+PointInfo test_hack_point(const string &name, TestPointConfig tpc) {
+	tpc.submit_answer = false;
+	tpc.validate_input_before_test = false;
+	tpc.auto_complete(0);
+	RunValidatorResult val_ret = run_validator(
+			tpc.input_file_name,
+			conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT),
+			conf_str("validator"));
+	if (val_ret.type != RS_AC) {
+		return PointInfo(0, 0, -1, -1,
+				"Validator " + info_str(val_ret.type),
+				file_preview(tpc.input_file_name), "",
+				"");
+	} else if (!val_ret.succeeded) {
+		return PointInfo(0, 0, -1, -1,
+				"Invalid Input",
+				file_preview(tpc.input_file_name), "",
+				val_ret.info);
+	}
+
+	RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT);
+
+	prepare_run_standard_program();
+	if (!conf_is("interaction_mode", "on")) {
+		RunProgramConfig rpc;
+		rpc.result_file_name = result_path + "/run_standard_program.txt";
+		RunResult std_ret = run_submission_program(
+				tpc.input_file_name,
+				tpc.answer_file_name,
+				conf_run_limit("standard", 0, default_std_run_limit),
+				"std",
+				rpc);
+		if (std_ret.type != RS_AC) {
+			return PointInfo(0, 0, -1, -1,
+					"Standard Program " + info_str(std_ret.type),
+					file_preview(tpc.input_file_name), "",
+					"");
+		}
+		if (conf_has("token")) {
+			file_hide_token(tpc.answer_file_name, conf_str("token", ""));
+		}
+	} else {
+		RunProgramConfig rpc;
+		rpc.result_file_name = result_path + "/run_standard_program.txt";
+		string real_output_file_name = tpc.answer_file_name;
+		string real_input_file_name = tpc.output_file_name + ".real_output.txt";
+		RunSimpleInteractionResult rires = run_simple_interation(
+				tpc.input_file_name,
+				tpc.answer_file_name,
+				real_input_file_name,
+				real_output_file_name,
+				conf_run_limit("standard", 0, default_std_run_limit),
+				conf_run_limit("interactor", 0, RL_INTERACTOR_DEFAULT),
+				"std",
+				rpc);
+
+		if (rires.ires.type != RS_AC) {
+			return PointInfo(0, 0, -1, -1,
+					"Interactor " + info_str(rires.ires.type) + " (Standard Program)",
+					file_preview(real_input_file_name), "",
+					"");
+		}
+		if (rires.res.type != RS_AC) {
+			return PointInfo(0, 0, -1, -1,
+					"Standard Program " + info_str(rires.res.type),
+					file_preview(real_input_file_name), "",
+					"");
+		}
+	}
+
+	PointInfo po = test_point(name, 0, tpc);
+	po.scr = po.scr != 100;
+	return po;
+}
+
+CustomTestInfo ordinary_custom_test(const string &name) {
+	RunLimit lim = conf_run_limit(0, RL_DEFAULT);
+	lim.time += 2;
+
+	string input_file_name = work_path + "/input.txt";
+	string output_file_name = work_path + "/output.txt";
+
+	RunResult pro_ret = run_submission_program(
+			input_file_name,
+			output_file_name,
+			lim,
+			name);
+	if (conf_has("token")) {
+		file_hide_token(output_file_name, conf_str("token", ""));
+	}
+	string info;
+	if (pro_ret.type == RS_AC) {
+		info = "Success";
+	} else {
+		info = info_str(pro_ret.type);
+	}
+	string exp;
+	if (pro_ret.type == RS_TLE) {
+		exp = "<p>[<strong>time limit:</strong> " + vtos(lim.time) + "s]</p>";
+	}
+	return CustomTestInfo(pro_ret.ust, pro_ret.usm, 
+			info, exp, file_preview(output_file_name, 2048));
+}
+
+int scale_score(int scr100, int full) {
+	return scr100 * full / 100;
+}
+
+/*======================  test End   ================== */
+
+/*======================= conf init =================== */
+
+void main_judger_init(int argc, char **argv)  {
+	main_path = UOJ_WORK_PATH;
+	work_path = main_path + "/work";
+	result_path = string(UOJ_RESULT_PATH);
+	load_config(work_path + "/submission.conf");
+	problem_id = conf_int("problem_id");
+	data_path = string(UOJ_DATA_PATH) + "/" + conf_str("problem_id");
+	load_config(data_path + "/problem.conf");
+
+	executef("cp %s/require/* %s 2>/dev/null", data_path.c_str(), work_path.c_str());
+
+	if (conf_is("use_builtin_judger", "on")) {
+		config["judger"] = string(UOJ_WORK_PATH) + "/builtin/judger/judger";
+	} else {
+		config["judger"] = data_path + "/judger";
+	}
+}
+void judger_init(int argc, char **argv) {
+	if (argc != 5) {
+		exit(1);
+	}
+	main_path = argv[1];
+	work_path = argv[2];
+	result_path = argv[3];
+	data_path = argv[4];
+	load_config(work_path + "/submission.conf");
+	problem_id = conf_int("problem_id");
+	load_config(data_path + "/problem.conf");
+
+	if (config.count("use_builtin_checker")) {
+		config["checker"] = main_path + "/builtin/checker/" + config["use_builtin_checker"];
+	} else {
+		config["checker"] = data_path + "/chk";
+	}
+	config["validator"] = data_path + "/val";
+}
+
+/*===================== conf init End ================= */

+ 4 - 0
judger/uoj_judger/include/uoj_work_path.h

@@ -0,0 +1,4 @@
+#define UOJ_WORK_PATH "/opt/uoj/judger/uoj_judger"
+#define UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+#define UOJ_JUDGER_PYTHON3_VERSION "3.6"
+#define UOJ_JUDGER_FPC_VERSION "3.0.4"

+ 22 - 0
judger/uoj_judger/main_judger.cpp

@@ -0,0 +1,22 @@
+#include "uoj_judger.h"
+
+int main(int argc, char **argv)  {
+	main_judger_init(argc, argv);
+	RunResult res = run_program(
+			(result_path + "/run_judger_result.txt").c_str(),
+			"/dev/null",
+			"/dev/null",
+			"stderr",
+			conf_run_limit("judger", 0, RL_JUDGER_DEFAULT),
+			"--unsafe",
+			conf_str("judger").c_str(),
+			main_path.c_str(),
+			work_path.c_str(),
+			result_path.c_str(),
+			data_path.c_str(),
+			NULL);
+	if (res.type != RS_AC) {
+		end_judge_judgement_failed("Judgement Failed : Judger " + info_str(res));
+	}
+	return 0;
+}

+ 55 - 0
judger/uoj_judger/run/formatter.cpp

@@ -0,0 +1,55 @@
+#include<cstdio>
+#include<cstdlib>
+#include<cstring>
+#include<cmath>
+#include<algorithm>
+using namespace std;
+typedef long long LL;
+int main()
+{
+    //freopen("1.in","r",stdin);
+    char c,last;
+    int nSpace=0,nR=0,first;
+    while(1)
+    {
+        last=c,c=getchar();
+        if(c==EOF)
+        {
+            if(last!='\n')
+                putchar('\n');
+            break;
+        }
+        else if(c!='\r'&&c!=' ')
+        {
+            if(c!='\n'&&first==0)
+            {
+                for(int j=1;j<=nSpace;++j)
+                    putchar(' ');
+                for(int j=1;j<=nR;++j)
+                    putchar('\r');
+            }
+            else if(c!='\n')
+            {
+                for(int j=1;j<=nR;++j)
+                    putchar('\r');
+                for(int j=1;j<=nSpace;++j)
+                    putchar(' ');
+            }
+            nSpace=nR=0;
+            putchar(c);
+        }
+        else if(c==' ')
+        {
+            ++nSpace;
+            if(nR==0)
+                first=0;
+        }
+        else
+        {
+            ++nR;
+            if(nSpace==0)
+                first=1;
+        }
+    }
+    return 0;
+}

+ 263 - 0
judger/uoj_judger/run/run_interaction.cpp

@@ -0,0 +1,263 @@
+#include <iostream>
+#include <cstdio>
+#include <cstdlib>
+#include <exception>
+#include <system_error>
+#include <thread>
+#include <unistd.h>
+#include <asm/unistd.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/user.h>
+#include <fcntl.h>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <set>
+#include <argp.h>
+#include "uoj_env.h"
+using namespace std;
+
+struct RunResult {
+	int result;
+	int ust;
+	int usm;
+	int exit_code;
+
+	RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1)
+			: result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) {
+		if (result != RS_AC) {
+			ust = -1, usm = -1;
+		}
+	}
+};
+
+struct PipeConfig {
+	int from, to;
+	int from_fd, to_fd;
+
+	string saving_file_name; // empty for none
+
+	PipeConfig() {
+	}
+	PipeConfig(string str) {
+		if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) {
+			throw invalid_argument("bad init str for PipeConfig");
+		}
+		from -= 1;
+		to -= 1;
+	}
+};
+
+struct RunInteractionConfig {
+	vector<string> cmds;
+	vector<PipeConfig> pipes;
+};
+
+struct RunCmdData {
+	string cmd;
+	pid_t pid;
+
+	vector<int> ipipes, opipes;
+};
+struct PipeData {
+	PipeConfig config;
+	int ipipefd[2], opipefd[2];
+	thread io_thread;
+	exception_ptr eptr;
+};
+
+class RunInteraction {
+private:
+	vector<RunCmdData> cmds;
+	vector<PipeData> pipes;
+
+	void prepare_fd() { // me
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			close(pipes[i].ipipefd[1]);
+			close(pipes[i].opipefd[0]);
+		}
+	}
+	void prepare_fd_for_cmd(int id) {
+		freopen("/dev/null", "r", stdin);
+		freopen("/dev/null", "w", stdout);
+		freopen("/dev/null", "w", stderr);
+
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			if (pipes[i].config.from == id) {
+				dup2(pipes[i].ipipefd[1], 128 + pipes[i].ipipefd[1]);
+			}
+			if (pipes[i].config.to == id) {
+				dup2(pipes[i].opipefd[0], 128 + pipes[i].opipefd[0]);
+			}
+			close(pipes[i].ipipefd[0]);
+			close(pipes[i].ipipefd[1]);
+			close(pipes[i].opipefd[0]);
+			close(pipes[i].opipefd[1]);
+		}
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			if (pipes[i].config.from == id) {
+				dup2(128 + pipes[i].ipipefd[1], pipes[i].config.from_fd);
+				close(128 + pipes[i].ipipefd[1]);
+			}
+			if (pipes[i].config.to == id) {
+				dup2(128 + pipes[i].opipefd[0], pipes[i].config.to_fd);
+				close(128 + pipes[i].opipefd[0]);
+			}
+		}
+	}
+
+	void wait_pipe_io(int pipe_id) {
+		FILE *sf = NULL;
+		if (!pipes[pipe_id].config.saving_file_name.empty())
+			sf = fopen(pipes[pipe_id].config.saving_file_name.c_str(), "w");
+
+		int ifd = pipes[pipe_id].ipipefd[0];
+		int ofd = pipes[pipe_id].opipefd[1];
+
+		FILE *inf = fdopen(ifd, "r");
+		FILE *ouf = fdopen(ofd, "w");
+
+		try {
+			pipes[pipe_id].eptr = nullptr;
+
+			const int L = 4096;
+			char buf[L];
+
+			while (true) {
+				int c = fgetc(inf);
+				if (c == EOF) {
+					if (errno) {
+						throw system_error(errno, system_category());
+					}
+					break;
+				}
+
+				if (fputc(c, ouf) == EOF) {
+					throw system_error(errno, system_category());
+				}
+				fflush(ouf);
+
+				if (fputc(c, sf) == EOF) {
+					throw system_error(errno, system_category());
+				}
+			}
+		} catch (exception &e) {
+			cerr << e.what() << endl;
+			pipes[pipe_id].eptr = current_exception();
+		}
+
+		fclose(sf);
+		fclose(inf);
+		fclose(ouf);
+	}
+public:
+	RunInteraction(const RunInteractionConfig &config) {
+		cmds.resize(config.cmds.size());
+		for (int i = 0; i < (int)config.cmds.size(); i++) {
+			cmds[i].cmd = config.cmds[i];
+		}
+		
+		pipes.resize(config.pipes.size());
+		for (int i = 0; i < (int)config.pipes.size(); i++) {
+			pipes[i].config = config.pipes[i];
+			cmds[pipes[i].config.from].opipes.push_back(i);
+			cmds[pipes[i].config.to].ipipes.push_back(i);
+		}
+
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			if (pipe(pipes[i].ipipefd) == -1 || pipe(pipes[i].opipefd) == -1) {
+				throw system_error(errno, system_category());
+			}
+		}
+		for (int i = 0; i < (int)cmds.size(); i++) {
+			cmds[i].pid = fork();
+			if (cmds[i].pid == 0) {
+				prepare_fd_for_cmd(i);
+				system(cmds[i].cmd.c_str());
+				exit(0);
+			} else if (cmds[i].pid == -1) {
+				throw system_error(errno, system_category());
+			}
+		}
+		
+		prepare_fd();
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			pipes[i].io_thread = thread(&RunInteraction::wait_pipe_io, this, i);
+		}
+	}
+
+	void join() {
+		int status;
+		while (wait(&status) > 0);
+
+		for (int i = 0; i < (int)pipes.size(); i++) {
+			pipes[i].io_thread.join();
+		}
+	}
+};
+
+error_t run_interaction_argp_parse_opt (int key, char *arg, struct argp_state *state) {
+	RunInteractionConfig *config = (RunInteractionConfig*)state->input;
+
+	try {
+		switch (key) {
+			case 'p':
+				config->pipes.push_back(PipeConfig(arg));
+				break;
+			case 's':
+				if (config->pipes.empty()) {
+					argp_usage(state);
+				}
+				config->pipes.back().saving_file_name = arg;
+				break;
+			case ARGP_KEY_ARG:
+				config->cmds.push_back(arg);
+				break;
+			case ARGP_KEY_END:
+				if (state->arg_num == 0) {
+					argp_usage(state);
+				}
+				break;
+			default:
+				return ARGP_ERR_UNKNOWN;
+		}
+	} catch (exception &e) {
+		argp_usage(state);
+	}
+
+	return 0;
+}
+
+RunInteractionConfig parse_args(int argc, char **argv) {
+	argp_option run_interaction_argp_options[] = {
+		{"add-pipe"           , 'p', "PIPE"        , 0, "Add a pipe <from>:<fd>-<to>:<fd> (fd < 128)"           ,  1},
+		{"save-pipe"          , 's', "FILE"        , 0, "Set last pipe saving file"                             ,  2},
+		{0}
+	};
+	char run_interaction_argp_args_doc[] = "cmd1 cmd2 ...";
+	char run_interaction_argp_doc[] = "run_interaction: a tool to run multiple commands with interaction";
+
+	argp run_interaction_argp = {
+		run_interaction_argp_options,
+		run_interaction_argp_parse_opt,
+		run_interaction_argp_args_doc,
+		run_interaction_argp_doc
+	};
+
+	RunInteractionConfig config;
+
+	argp_parse(&run_interaction_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config);
+
+	return config;
+}
+
+int main(int argc, char **argv) {
+	signal(SIGPIPE, SIG_IGN);
+
+	RunInteraction ri(parse_args(argc, argv));
+	ri.join();
+
+	return 0;
+}

+ 584 - 0
judger/uoj_judger/run/run_program.cpp

@@ -0,0 +1,584 @@
+#include <iostream>
+#include <cstdio>
+#include <cstdlib>
+#include <unistd.h>
+#include <asm/unistd.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/user.h>
+#include <fcntl.h>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <set>
+#include <argp.h>
+#include "uoj_env.h"
+using namespace std;
+
+struct RunResult {
+	int result;
+	int ust;
+	int usm;
+	int exit_code;
+
+	RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1)
+			: result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) {
+		if (result != RS_AC) {
+			ust = -1, usm = -1;
+		}
+	}
+};
+
+struct RunProgramConfig
+{
+	int time_limit;
+	int real_time_limit;
+	int memory_limit;
+	int output_limit;
+	int stack_limit;
+	string input_file_name;
+	string output_file_name;
+	string error_file_name;
+	string result_file_name;
+	string work_path;
+	string type;
+	vector<string> extra_readable_files;
+	vector<string> extra_writable_files;
+	bool allow_proc;
+	bool safe_mode;
+	bool need_show_trace_details;
+
+	string program_name;
+	string program_basename;
+	vector<string> argv;
+
+};
+
+int put_result(string result_file_name, RunResult res) {
+	FILE *f;
+	if (result_file_name == "stdout") {
+		f = stdout;
+	} else if (result_file_name == "stderr") {
+		f = stderr;
+	} else {
+		f = fopen(result_file_name.c_str(), "w");
+	}
+	fprintf(f, "%d %d %d %d\n", res.result, res.ust, res.usm, res.exit_code);
+	if (f != stdout && f != stderr) {
+		fclose(f);
+	}
+	if (res.result == RS_JGF) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+char self_path[PATH_MAX + 1] = {};
+
+#include "run_program_conf.h"
+
+argp_option run_program_argp_options[] =
+{
+	{"tl"                 , 'T', "TIME_LIMIT"  , 0, "Set time limit (in second)"                            ,  1},
+	{"rtl"                , 'R', "TIME_LIMIT"  , 0, "Set real time limit (in second)"                       ,  2},
+	{"ml"                 , 'M', "MEMORY_LIMIT", 0, "Set memory limit (in mb)"                              ,  3},
+	{"ol"                 , 'O', "OUTPUT_LIMIT", 0, "Set output limit (in mb)"                              ,  4},
+	{"sl"                 , 'S', "STACK_LIMIT" , 0, "Set stack limit (in mb)"                               ,  5},
+	{"in"                 , 'i', "IN"          , 0, "Set input file name"                                   ,  6},
+	{"out"                , 'o', "OUT"         , 0, "Set output file name"                                  ,  7},
+	{"err"                , 'e', "ERR"         , 0, "Set error file name"                                   ,  8},
+	{"work-path"          , 'w', "WORK_PATH"   , 0, "Set the work path of the program"                      ,  9},
+	{"type"               , 't', "TYPE"        , 0, "Set the program type (for some program such as python)", 10},
+	{"res"                , 'r', "RESULT_FILE" , 0, "Set the file name for outputing the result            ", 10},
+	{"add-readable"       , 500, "FILE"        , 0, "Add a readable file"                                   , 11},
+	{"add-writable"       , 505, "FILE"        , 0, "Add a writable file"                                   , 11},
+	{"unsafe"             , 501, 0             , 0, "Don't check dangerous syscalls"                        , 12},
+	{"show-trace-details" , 502, 0             , 0, "Show trace details"                                    , 13},
+	{"allow-proc"         , 503, 0             , 0, "Allow fork, exec... etc."                              , 14},
+	{"add-readable-raw"   , 504, "FILE"        , 0, "Add a readable (don't transform to its real path)"     , 15},
+	{"add-writable-raw"   , 506, "FILE"        , 0, "Add a writable (don't transform to its real path)"     , 15},
+	{0}
+};
+error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state)
+{
+	RunProgramConfig *config = (RunProgramConfig*)state->input;
+
+	switch (key)
+	{
+		case 'T':
+			config->time_limit = atoi(arg);
+			break;
+		case 'R':
+			config->real_time_limit = atoi(arg);
+			break;
+		case 'M':
+			config->memory_limit = atoi(arg);
+			break;
+		case 'O':
+			config->output_limit = atoi(arg);
+			break;
+		case 'S':
+			config->stack_limit = atoi(arg);
+			break;
+		case 'i':
+			config->input_file_name = arg;
+			break;
+		case 'o':
+			config->output_file_name = arg;
+			break;
+		case 'e':
+			config->error_file_name = arg;
+			break;
+		case 'w':
+			config->work_path = realpath(arg);
+			if (config->work_path.empty()) {
+				argp_usage(state);
+			}
+			break;
+		case 'r':
+			config->result_file_name = arg;
+			break;
+		case 't':
+			config->type = arg;
+			break;
+		case 500:
+			config->extra_readable_files.push_back(realpath(arg));
+			break;
+		case 501:
+			config->safe_mode = false;
+			break;
+		case 502:
+			config->need_show_trace_details = true;
+			break;
+		case 503:
+			config->allow_proc = true;
+			break;
+		case 504:
+			config->extra_readable_files.push_back(arg);
+			break;
+		case 505:
+			config->extra_writable_files.push_back(realpath(arg));
+			break;
+		case 506:
+			config->extra_writable_files.push_back(arg);
+			break;
+		case ARGP_KEY_ARG:
+			config->argv.push_back(arg);
+			for (int i = state->next; i < state->argc; i++) {
+				config->argv.push_back(state->argv[i]);
+			}
+			state->next = state->argc;
+			break;
+		case ARGP_KEY_END:
+			if (state->arg_num == 0) {
+				argp_usage(state);
+			}
+			break;
+		default:
+			return ARGP_ERR_UNKNOWN;
+	}
+
+	if (getenv ("UOJ_ALLOW_PROC"))
+		config->allow_proc = true;
+	return 0;
+}
+char run_program_argp_args_doc[] = "program arg1 arg2 ...";
+char run_program_argp_doc[] = "run_program: a tool to run program safely";
+
+argp run_program_argp = {
+	run_program_argp_options,
+	run_program_argp_parse_opt,
+	run_program_argp_args_doc,
+	run_program_argp_doc
+};
+
+RunProgramConfig run_program_config;
+
+void parse_args(int argc, char **argv) {
+	run_program_config.time_limit = 1;
+	run_program_config.real_time_limit = -1;
+	run_program_config.memory_limit = 256;
+	run_program_config.output_limit = 64;
+	run_program_config.stack_limit = 1024;
+	run_program_config.input_file_name = "stdin";
+	run_program_config.output_file_name = "stdout";
+	run_program_config.error_file_name = "stderr";
+	run_program_config.work_path = "";
+	run_program_config.result_file_name = "stdout";
+	run_program_config.type = "default";
+	run_program_config.safe_mode = true;
+	run_program_config.need_show_trace_details = false;
+	run_program_config.allow_proc = false;
+
+	argp_parse(&run_program_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &run_program_config);
+
+	if (run_program_config.real_time_limit == -1)
+		run_program_config.real_time_limit = run_program_config.time_limit + 2;
+	run_program_config.stack_limit = min(run_program_config.stack_limit, run_program_config.memory_limit);
+
+	if (!run_program_config.work_path.empty()) {
+		if (chdir(run_program_config.work_path.c_str()) == -1) {
+			exit(put_result(run_program_config.result_file_name, RS_JGF));
+		}
+	}
+
+	if (run_program_config.type == "java8" || run_program_config.type == "java11") {
+		run_program_config.program_name = run_program_config.argv[0];
+	} else {
+		run_program_config.program_name = realpath(run_program_config.argv[0]);
+	}
+	if (run_program_config.work_path.empty()) {
+		run_program_config.work_path = dirname(run_program_config.program_name);
+		run_program_config.program_basename = basename(run_program_config.program_name);
+		run_program_config.argv[0] = "./" + run_program_config.program_basename;
+
+		if (chdir(run_program_config.work_path.c_str()) == -1) {
+			exit(put_result(run_program_config.result_file_name, RS_JGF));
+		}
+	}
+
+	if (run_program_config.type == "python2") {
+		string pre[4] = {"/usr/bin/python2", "-E", "-s", "-B"};
+		run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 4);
+	} else if (run_program_config.type == "python3") {
+		string pre[3] = {"/usr/bin/python3", "-I", "-B"};
+		run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3);
+	} else if (run_program_config.type == "java8") {
+		string pre[3] = {"/usr/lib/jvm/java-8-openjdk-amd64/bin/java", "-Xmx1024m", "-Xss1024m"};
+		run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3);
+	} else if (run_program_config.type == "java11") {
+		string pre[3] = {"/usr/lib/jvm/java-11-openjdk-amd64/bin/java", "-Xmx1024m", "-Xss1024m"};
+		run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3);
+	}
+}
+
+void set_limit(int r, int rcur, int rmax = -1)  {
+	if (rmax == -1)
+		rmax = rcur;
+	struct rlimit l;
+	if (getrlimit(r, &l) == -1) {
+		exit(55);
+	}
+	l.rlim_cur = rcur;
+	l.rlim_max = rmax;
+	if (setrlimit(r, &l) == -1) {
+		exit(55);
+	}
+}
+void run_child() {
+	set_limit(RLIMIT_CPU, run_program_config.time_limit, run_program_config.real_time_limit);
+	set_limit(RLIMIT_FSIZE, run_program_config.output_limit << 20);
+	set_limit(RLIMIT_STACK, run_program_config.stack_limit << 20);
+
+	if (run_program_config.input_file_name != "stdin") {
+		if (freopen(run_program_config.input_file_name.c_str(), "r", stdin) == NULL) {
+			exit(11);
+		}
+	}
+	if (run_program_config.output_file_name != "stdout" && run_program_config.output_file_name != "stderr") {
+		if (freopen(run_program_config.output_file_name.c_str(), "w", stdout) == NULL) {
+			exit(12);
+		}
+	}
+	if (run_program_config.error_file_name != "stderr") {
+		if (run_program_config.error_file_name == "stdout") {
+			if (dup2(1, 2) == -1) {
+				exit(13);
+			}
+		} else {
+			if (freopen(run_program_config.error_file_name.c_str(), "w", stderr) == NULL) {
+				exit(14);
+			}
+		}
+		
+		if (run_program_config.output_file_name == "stderr") {
+			if (dup2(2, 1) == -1) {
+				exit(15);
+			}
+		}
+	}
+
+	char *env_path_str = getenv("PATH");
+	char *env_lang_str = getenv("LANG");
+	char *env_shell_str = getenv("SHELL");
+	string env_path = env_path_str ? env_path_str : "";
+	string env_lang = env_lang_str ? env_lang_str : "";
+	string env_shell = env_shell_str ? env_shell_str : "";
+
+	clearenv();
+	setenv("USER", "poor_program", 1);
+	setenv("LOGNAME", "poor_program", 1);
+	setenv("HOME", run_program_config.work_path.c_str(), 1);
+	if (env_lang_str) {
+		setenv("LANG", env_lang.c_str(), 1);
+	}
+	if (env_path_str) {
+		setenv("PATH", env_path.c_str(), 1);
+	}
+	setenv("PWD", run_program_config.work_path.c_str(), 1);
+	if (env_shell_str) {
+		setenv("SHELL", env_shell.c_str(), 1);
+	}
+
+	char **program_c_argv = new char*[run_program_config.argv.size() + 1];
+	for (size_t i = 0; i < run_program_config.argv.size(); i++) {
+		program_c_argv[i] = new char[run_program_config.argv[i].size() + 1];
+		strcpy(program_c_argv[i], run_program_config.argv[i].c_str());
+	}
+	program_c_argv[run_program_config.argv.size()] = NULL;
+
+	if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
+		exit(16);
+	}
+	if (execv(program_c_argv[0], program_c_argv) == -1) {
+		exit(17);
+	}
+}
+
+const int MaxNRPChildren = 50;
+struct rp_child_proc {
+	pid_t pid;
+	int mode;
+};
+int n_rp_children;
+pid_t rp_timer_pid;
+rp_child_proc rp_children[MaxNRPChildren];
+
+int rp_children_pos(pid_t pid) {
+	for (int i = 0; i < n_rp_children; i++) {
+		if (rp_children[i].pid == pid) {
+			return i;
+		}
+	}
+	return -1;
+}
+int rp_children_add(pid_t pid) {
+	if (n_rp_children == MaxNRPChildren) {
+		return -1;
+	}
+	rp_children[n_rp_children].pid = pid;
+	rp_children[n_rp_children].mode = -1;
+	n_rp_children++;
+	return 0;
+}
+void rp_children_del(pid_t pid) {
+	int new_n = 0;
+	for (int i = 0; i < n_rp_children; i++) {
+		if (rp_children[i].pid != pid) {
+			rp_children[new_n++] = rp_children[i];
+		}
+	}
+	n_rp_children = new_n;
+}
+
+void stop_child(pid_t pid) {
+	kill(pid, SIGKILL);
+}
+void stop_all() {
+	kill(rp_timer_pid, SIGKILL);
+	for (int i = 0; i < n_rp_children; i++) {
+		kill(rp_children[i].pid, SIGKILL);
+	}
+}
+
+RunResult trace_children() {
+	rp_timer_pid = fork();
+	if (rp_timer_pid == -1) {
+		stop_all();
+		return RunResult(RS_JGF);
+	} else if (rp_timer_pid == 0) {
+		struct timespec ts;
+		ts.tv_sec = run_program_config.real_time_limit;
+		ts.tv_nsec = 0;
+		nanosleep(&ts, NULL);
+		exit(0);
+	}
+
+	if (run_program_config.need_show_trace_details) {
+		cerr << "timerpid " << rp_timer_pid << endl;
+	}
+
+	pid_t prev_pid = -1;
+	while (true) {
+		int stat = 0;
+		int sig = 0;
+		struct rusage ruse;
+		
+		pid_t pid = wait4(-1, &stat, __WALL, &ruse);
+		if (run_program_config.need_show_trace_details) {
+			if (prev_pid != pid) {
+				cerr << "----------" << pid << "----------" << endl;
+			}
+			prev_pid = pid;
+		}
+		if (pid == rp_timer_pid) {
+			if (WIFEXITED(stat) || WIFSIGNALED(stat)) {
+				stop_all();
+				return RunResult(RS_TLE);
+			}
+			continue;
+		}
+		
+		int p = rp_children_pos(pid);
+		if (p == -1) {
+			if (run_program_config.need_show_trace_details) {
+				fprintf(stderr, "new_proc  %lld\n", (long long int)pid);
+			}
+			if (rp_children_add(pid) == -1) {
+				stop_child(pid);
+				stop_all();
+				return RunResult(RS_DGS);
+			}
+			p = n_rp_children - 1;
+		}
+
+		int usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000;
+		int usermem = ruse.ru_maxrss;
+		if (usertim > run_program_config.time_limit * 1000) {
+			stop_all();
+			return RunResult(RS_TLE);
+		}
+		if (usermem > run_program_config.memory_limit * 1024) {
+			stop_all();
+			return RunResult(RS_MLE);
+		}
+
+		if (WIFEXITED(stat)) {
+			if (run_program_config.need_show_trace_details) {
+				fprintf(stderr, "exit     : %d\n", WEXITSTATUS(stat));
+			}
+			if (rp_children[0].mode == -1) {
+				stop_all();
+				return RunResult(RS_JGF, -1, -1, WEXITSTATUS(stat));
+			} else {
+				if (pid == rp_children[0].pid) {
+					stop_all();
+					return RunResult(RS_AC, usertim, usermem, WEXITSTATUS(stat));
+				} else {
+					rp_children_del(pid);
+					continue;
+				}
+			}
+		}
+
+		if (WIFSIGNALED(stat)) {
+			if (run_program_config.need_show_trace_details) {
+				fprintf(stderr, "sig exit : %d\n", WTERMSIG(stat));
+			}
+			if (pid == rp_children[0].pid) {
+				switch(WTERMSIG(stat)) {
+				case SIGXCPU: // nearly impossible
+					stop_all();
+					return RunResult(RS_TLE);
+				case SIGXFSZ:
+					stop_all();
+					return RunResult(RS_OLE);
+				default:
+					stop_all();
+					return RunResult(RS_RE);
+				}
+			} else {
+				rp_children_del(pid);
+				continue;
+			}
+		}
+		
+		if (WIFSTOPPED(stat)) {
+			sig = WSTOPSIG(stat);
+			
+			if (rp_children[p].mode == -1) {
+				if ((p == 0 && sig == SIGTRAP) || (p != 0 && sig == SIGSTOP)) {
+					if (p == 0) {
+						int ptrace_opt = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD;
+						if (run_program_config.safe_mode) {
+							ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK;
+							ptrace_opt |= PTRACE_O_TRACEEXEC;
+						}
+						if (ptrace(PTRACE_SETOPTIONS, pid, NULL, ptrace_opt) == -1) {
+							stop_all();
+							return RunResult(RS_JGF);
+						}
+					}
+					sig = 0;
+				}
+				rp_children[p].mode = 0;
+			} else if (sig == (SIGTRAP | 0x80)) {
+				if (rp_children[p].mode == 0) {
+					if (run_program_config.safe_mode) {
+						if (!check_safe_syscall(pid, run_program_config.need_show_trace_details)) {
+							stop_all();
+							return RunResult(RS_DGS);
+						}
+					}
+					rp_children[p].mode = 1;
+				} else {
+					if (run_program_config.safe_mode) {
+						on_syscall_exit(pid, run_program_config.need_show_trace_details);
+					}
+					rp_children[p].mode = 0;
+				}
+				
+				sig = 0;
+			} else if (sig == SIGTRAP) {
+				switch ((stat >> 16) & 0xffff) {
+					case PTRACE_EVENT_CLONE:
+					case PTRACE_EVENT_FORK:
+					case PTRACE_EVENT_VFORK:
+						sig = 0;
+						break;
+					case PTRACE_EVENT_EXEC:
+						rp_children[p].mode = 1;
+						sig = 0;
+						break;
+					case 0:
+						break;
+					default:
+						stop_all();
+						return RunResult(RS_JGF);
+				}
+			}
+
+			if (sig != 0) {
+				if (run_program_config.need_show_trace_details) {
+					fprintf(stderr, "sig      : %d\n", sig);
+				}
+			}
+			
+			switch(sig) {
+			case SIGXCPU:
+				stop_all();
+				return RunResult(RS_TLE);
+			case SIGXFSZ:
+				stop_all();
+				return RunResult(RS_OLE);
+			}
+		}
+		
+		ptrace(PTRACE_SYSCALL, pid, NULL, sig);
+	}
+}
+
+RunResult run_parent(pid_t pid) {
+	init_conf(run_program_config);
+	
+	n_rp_children = 0;
+
+	rp_children_add(pid);
+	return trace_children();
+}
+int main(int argc, char **argv) {
+	self_path[readlink("/proc/self/exe", self_path, PATH_MAX)] = '\0';
+	parse_args(argc, argv);
+
+	pid_t pid = fork();
+	if (pid == -1) {
+		return put_result(run_program_config.result_file_name, RS_JGF);
+	} else if (pid == 0) {
+		run_child();
+	} else {
+		return put_result(run_program_config.result_file_name, run_parent(pid));
+	}
+	return put_result(run_program_config.result_file_name, RS_JGF);
+}

+ 795 - 0
judger/uoj_judger/run/run_program_conf.h

@@ -0,0 +1,795 @@
+#ifdef __x86_64__
+typedef unsigned long long int reg_val_t;
+#define REG_SYSCALL orig_rax
+#define REG_RET rax
+#define REG_ARG0 rdi
+#define REG_ARG1 rsi
+#define REG_ARG2 rdx
+#define REG_ARG3 rcx
+#else
+typedef long int reg_val_t;
+#define REG_SYSCALL orig_eax
+#define REG_RET eax
+#define REG_ARG0 ebx
+#define REG_ARG1 ecx
+#define REG_ARG2 edx
+#define REG_ARG3 esx
+#endif
+
+const size_t MaxPathLen = 200;
+
+set<string> writable_file_name_set;
+set<string> readable_file_name_set;
+set<string> statable_file_name_set;
+set<string> soft_ban_file_name_set;
+int syscall_max_cnt[1000];
+bool syscall_should_soft_ban[1000];
+
+string basename(const string &path) {
+	size_t p = path.rfind('/');
+	if (p == string::npos) {
+		return path;
+	} else {
+		return path.substr(p + 1);
+	}
+}
+string dirname(const string &path) {
+	size_t p = path.rfind('/');
+	if (p == string::npos) {
+		return "";
+	} else {
+		return path.substr(0, p);
+	}
+}
+string getcwdp(pid_t pid) {
+	char s[20];
+	char cwd[MaxPathLen + 1];
+	if (pid != 0) {
+		sprintf(s, "/proc/%lld/cwd", (long long int)pid);
+	} else {
+		sprintf(s, "/proc/self/cwd");
+	}
+	int l = readlink(s, cwd, MaxPathLen);
+	if (l == -1) {
+		return "";
+	}
+	cwd[l] = '\0';
+	return cwd;
+}
+string abspath(pid_t pid, const string &path) {
+	if (path.size() > MaxPathLen) {
+		return "";
+	}
+	if (path.empty()) {
+		return path;
+	} 
+	string s;
+	string b;
+	size_t st;
+	if (path[0] == '/') {
+		s = "/";
+		st = 1;
+	} else {
+		s = getcwdp(pid) + "/";
+		st = 0;
+	}
+	for (size_t i = st; i < path.size(); i++) {
+		b += path[i];
+		if (path[i] == '/') {
+			if (b == "../" && !s.empty()) {
+				if (s == "./") {
+					s = "../";
+				} else if (s != "/") {
+					size_t p = s.size() - 1;
+					while (p > 0 && s[p - 1] != '/') {
+						p--;
+					}
+					if (s.size() - p == 3 && s[p] == '.' && s[p + 1] == '.' && s[p + 2] == '/') {
+						s += b;
+					} else {
+						s.resize(p);
+					}
+				}
+			} else if (b != "./" && b != "/") {
+				s += b;
+			}
+			b.clear();
+		}
+	}
+	if (b == ".." && !s.empty()) {
+		if (s == "./") {
+			s = "..";
+		} else if (s != "/") {
+			size_t p = s.size() - 1;
+			while (p > 0 && s[p - 1] != '/') {
+				p--;
+			}
+			if (s.size() - p == 3 && s[p] == '.' && s[p + 1] == '.' && s[p + 2] == '/') {
+				s += b;
+			} else {
+				s.resize(p);
+			}
+		}
+	} else if (b != ".") {
+		s += b;
+	}
+	if (s.size() >= 2 && s[s.size() - 1] == '/') {
+		s.resize(s.size() - 1);
+	}
+	return s;
+}
+string realpath(const string &path) {
+	char real[PATH_MAX + 1] = {};
+	if (realpath(path.c_str(), real) == NULL) {
+		return "";
+	}
+	return real;
+}
+
+inline bool is_in_set_smart(string name, const set<string> &s) {
+	if (name.size() > MaxPathLen) {
+		return false;
+	}
+	if (s.count(name)) {
+		return true;
+	}
+	for (size_t i = 0; i + 1 < name.size(); i++) {
+		if ((i == 0 || name[i - 1] == '/') && name[i] == '.' && name[i + 1] == '.' && (i + 2 == name.size() || name[i + 2] == '.')) {
+			return false;
+		}
+	}
+	int level;
+	for (level = 0; !name.empty(); name = dirname(name), level++) {
+		if (level == 1 && s.count(name + "/*")) {
+			return true;
+		}
+		if (s.count(name + "/")) {
+			return true;
+		}
+	}
+	if (level == 1 && s.count("/*")) {
+		return true;
+	}
+	if (s.count("/")) {
+		return true;
+	}
+	return false;
+}
+
+inline bool is_writable_file(string name) {
+	if (name == "/") {
+		return writable_file_name_set.count("system_root");
+	}
+	return is_in_set_smart(name, writable_file_name_set) || is_in_set_smart(realpath(name), readable_file_name_set);
+}
+inline bool is_readable_file(const string &name) {
+	if (is_writable_file(name)) {
+		return true;
+	}
+	if (name == "/") {
+		return readable_file_name_set.count("system_root");
+	}
+	return is_in_set_smart(name, readable_file_name_set) || is_in_set_smart(realpath(name), readable_file_name_set);
+}
+inline bool is_statable_file(const string &name) {
+	if (is_readable_file(name)) {
+		return true;
+	}
+	if (name == "/") {
+		return statable_file_name_set.count("system_root");
+	}
+	return is_in_set_smart(name, statable_file_name_set) || is_in_set_smart(realpath(name), statable_file_name_set);
+}
+inline bool is_soft_ban_file(const string &name) {
+	if (name == "/") {
+		return soft_ban_file_name_set.count("system_root");
+	}
+	return is_in_set_smart(name, soft_ban_file_name_set) || is_in_set_smart(realpath(name), soft_ban_file_name_set);
+}
+#ifdef __x86_64__
+int syscall_max_cnt_list_default[][2] = {
+	{__NR_read          , -1},
+	{__NR_write         , -1},
+	{__NR_readv         , -1},
+	{__NR_writev        , -1},
+	{__NR_open          , -1},
+	{__NR_unlink        , -1},
+	{__NR_close         , -1},
+	{__NR_readlink      , -1},
+	{__NR_openat        , -1},
+	{__NR_unlinkat      , -1},
+	{__NR_readlinkat    , -1},
+	{__NR_stat          , -1},
+	{__NR_fstat         , -1},
+	{__NR_lstat         , -1},
+	{__NR_lseek         , -1},
+	{__NR_access        , -1},
+	{__NR_dup           , -1},
+	{__NR_dup2          , -1},
+	{__NR_dup3          , -1},
+	{__NR_ioctl         , -1},
+	{__NR_fcntl         , -1},
+
+	{__NR_mmap          , -1},
+	{__NR_mprotect      , -1},
+	{__NR_munmap        , -1},
+	{__NR_brk           , -1},
+	{__NR_mremap        , -1},
+	{__NR_msync         , -1},
+	{__NR_mincore       , -1},
+	{__NR_madvise       , -1},
+	
+	{__NR_rt_sigaction  , -1},
+	{__NR_rt_sigprocmask, -1},
+	{__NR_rt_sigreturn  , -1},
+	{__NR_rt_sigpending , -1},
+	{__NR_sigaltstack   , -1},
+
+	{__NR_getcwd        , -1},
+
+	{__NR_exit          , -1},
+	{__NR_exit_group    , -1},
+
+	{__NR_arch_prctl    , -1},
+
+	{__NR_gettimeofday  , -1},
+	{__NR_getrlimit     , -1},
+	{__NR_getrusage     , -1},
+	{__NR_times         , -1},
+	{__NR_time          , -1},
+	{__NR_clock_gettime , -1},
+
+	{__NR_restart_syscall, -1},
+
+	{__NR_set_tid_address, -1},
+	{__NR_set_robust_list, -1},
+	{__NR_prlimit64      , -1},
+	{__NR_futex          , -1},
+
+	{-1                 , -1}
+};
+
+int syscall_soft_ban_list_default[] = {
+	-1
+};
+
+const char *readable_file_name_list_default[] = {
+	"/etc/ld.so.nohwcap",
+	"/etc/ld.so.preload",
+	"/etc/ld.so.cache",
+	"/lib/x86_64-linux-gnu/",
+	"/usr/lib/x86_64-linux-gnu/",
+	"/usr/lib/locale/locale-archive",
+	"/proc/self/exe",
+	"/etc/timezone",
+	"/usr/share/zoneinfo/",
+	"/dev/random",
+	"/dev/urandom",
+	"/proc/meminfo",
+	"/etc/localtime",
+	"/proc/self/environ",
+	"/proc/self/cmdline",
+	NULL
+};
+
+#else
+#error T_T
+#endif
+
+void add_file_permission(const string &file_name, char mode) {
+	if (mode == 'w') {
+		writable_file_name_set.insert(file_name);
+	} else if (mode == 'r') {
+		readable_file_name_set.insert(file_name);
+	} else if (mode == 's') {
+		statable_file_name_set.insert(file_name);
+	}
+	for (string name = dirname(file_name); !name.empty(); name = dirname(name)) {
+		statable_file_name_set.insert(name);
+	}
+}
+
+void init_conf(const RunProgramConfig &config) {
+	for (int i = 0; syscall_max_cnt_list_default[i][0] != -1; i++) {
+		syscall_max_cnt[syscall_max_cnt_list_default[i][0]] = syscall_max_cnt_list_default[i][1];
+	}
+	for (int i = 0; syscall_soft_ban_list_default[i] != -1; i++) {
+		syscall_should_soft_ban[syscall_soft_ban_list_default[i]] = true;
+	}
+
+	for (int i = 0; readable_file_name_list_default[i]; i++) {
+		readable_file_name_set.insert(readable_file_name_list_default[i]);
+	}
+	statable_file_name_set.insert(config.work_path + "/");
+
+	if (config.type != "java8" && config.type != "java11") {
+		add_file_permission(config.program_name, 'r');
+	} else {
+		int p = config.program_name.find('.');
+		if (p == string::npos) {
+			readable_file_name_set.insert(config.work_path + "/");
+		} else {
+			readable_file_name_set.insert(config.work_path + "/" + config.program_name.substr(0, p) + "/");
+		}
+	}
+	add_file_permission(config.work_path, 'r');
+
+	for (vector<string>::const_iterator it = config.extra_readable_files.begin(); it != config.extra_readable_files.end(); it++) {
+		add_file_permission(*it, 'r');
+	}
+	for (vector<string>::const_iterator it = config.extra_writable_files.begin(); it != config.extra_writable_files.end(); it++) {
+		add_file_permission(*it, 'w');
+	}
+
+	writable_file_name_set.insert("/dev/null");
+
+	if (config.allow_proc) {
+		syscall_max_cnt[__NR_clone          ] = -1;
+		syscall_max_cnt[__NR_fork           ] = -1;
+		syscall_max_cnt[__NR_vfork          ] = -1;
+		syscall_max_cnt[__NR_nanosleep      ] = -1;
+		syscall_max_cnt[__NR_execve         ] = -1;
+	}
+
+	if (config.type == "python2") {
+		syscall_max_cnt[__NR_set_tid_address] = 1;
+		syscall_max_cnt[__NR_set_robust_list] = 1;
+		syscall_max_cnt[__NR_futex          ] = -1;
+
+		syscall_max_cnt[__NR_getdents       ] = -1;
+		syscall_max_cnt[__NR_getdents64     ] = -1;
+
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		syscall_max_cnt[__NR_prlimit64      ] = -1;
+		syscall_max_cnt[__NR_getpid         ] = -1;
+		syscall_max_cnt[__NR_sysinfo        ] = -1;
+		# endif
+
+		readable_file_name_set.insert("/usr/bin/python2.7");
+		readable_file_name_set.insert("/usr/lib/python2.7/");
+		readable_file_name_set.insert("/usr/bin/lib/python2.7/");
+		readable_file_name_set.insert("/usr/local/lib/python2.7/");
+		readable_file_name_set.insert("/usr/lib/pymodules/python2.7/");
+		readable_file_name_set.insert("/usr/bin/Modules/");
+		readable_file_name_set.insert("/usr/bin/pybuilddir.txt");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		readable_file_name_set.insert("/usr/lib/locale/");
+		readable_file_name_set.insert(config.work_path + "/answer.code");
+		# endif
+
+		statable_file_name_set.insert("/usr");
+		statable_file_name_set.insert("/usr/bin");
+	} else if (config.type == "python3") {
+		syscall_max_cnt[__NR_set_tid_address] = 1;
+		syscall_max_cnt[__NR_set_robust_list] = 1;
+		syscall_max_cnt[__NR_futex          ] = -1;
+
+		syscall_max_cnt[__NR_getdents       ] = -1;
+		syscall_max_cnt[__NR_getdents64     ] = -1;
+
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		syscall_max_cnt[__NR_prlimit64      ] = -1;
+		syscall_max_cnt[__NR_getrandom      ] = -1;
+		syscall_max_cnt[__NR_sysinfo        ] = -1;
+		syscall_max_cnt[__NR_getpid         ] = -1;
+		# endif
+
+		readable_file_name_set.insert("/usr/bin/python3");
+		readable_file_name_set.insert("/usr/lib/python3/");
+		# ifdef UOJ_JUDGER_PYTHON3_VERSION
+		readable_file_name_set.insert("/usr/bin/python" UOJ_JUDGER_PYTHON3_VERSION);
+		readable_file_name_set.insert("/usr/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/");
+		readable_file_name_set.insert("/usr/bin/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/");
+		readable_file_name_set.insert("/usr/local/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/");
+		# else
+		readable_file_name_set.insert("/usr/bin/python3.4");
+		readable_file_name_set.insert("/usr/lib/python3.4/");
+		readable_file_name_set.insert("/usr/bin/lib/python3.4/");
+		readable_file_name_set.insert("/usr/local/lib/python3.4/");
+		# endif
+		readable_file_name_set.insert("/usr/bin/pyvenv.cfg");
+		readable_file_name_set.insert("/usr/pyvenv.cfg");
+		readable_file_name_set.insert("/usr/bin/Modules/");
+		readable_file_name_set.insert("/usr/bin/pybuilddir.txt");
+		readable_file_name_set.insert("/usr/lib/dist-python");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		readable_file_name_set.insert("/usr/lib/locale/");
+		readable_file_name_set.insert(config.work_path + "/answer.code");
+		# endif
+
+		statable_file_name_set.insert("/usr");
+		statable_file_name_set.insert("/usr/bin");
+		statable_file_name_set.insert("/usr/lib");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		statable_file_name_set.insert("/usr/lib/python36.zip");
+		# endif
+	} else if (config.type == "java8") {
+		syscall_max_cnt[__NR_gettid         ] = -1;
+		syscall_max_cnt[__NR_set_tid_address] = 1;
+		syscall_max_cnt[__NR_set_robust_list] = 14;
+		syscall_max_cnt[__NR_futex          ] = -1;
+
+		syscall_max_cnt[__NR_uname          ] = 1;
+
+		syscall_max_cnt[__NR_clone          ] = 13;
+
+		syscall_max_cnt[__NR_getdents       ] = 4;
+
+		syscall_max_cnt[__NR_clock_getres   ] = 2;
+
+		syscall_max_cnt[__NR_setrlimit      ] = 1;
+
+		syscall_max_cnt[__NR_sched_getaffinity] = -1;
+		syscall_max_cnt[__NR_sched_yield    ] = -1;
+
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		syscall_max_cnt[__NR_prlimit64      ] = -1;
+		syscall_max_cnt[__NR_getpid         ] = -1;
+		syscall_max_cnt[__NR_sysinfo        ] = -1;
+		syscall_max_cnt[__NR_clone          ] = -1;
+		syscall_max_cnt[__NR_set_robust_list] = -1;
+		syscall_max_cnt[__NR_prctl          ] = -1;
+		# endif
+
+		syscall_should_soft_ban[__NR_socket   ] = true;
+		syscall_should_soft_ban[__NR_connect  ] = true;
+		syscall_should_soft_ban[__NR_geteuid  ] = true;
+		syscall_should_soft_ban[__NR_getuid   ] = true;
+
+		soft_ban_file_name_set.insert("/etc/nsswitch.conf");
+		soft_ban_file_name_set.insert("/etc/passwd");
+
+		add_file_permission("/usr/lib/jvm/java-8-openjdk-amd64/", 'r');
+		readable_file_name_set.insert("/sys/devices/system/cpu/");
+		readable_file_name_set.insert("/proc/");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		readable_file_name_set.insert("/sys/fs/cgroup/cpu/");
+		readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/");
+		readable_file_name_set.insert("/sys/fs/cgroup/memory/");
+		readable_file_name_set.insert("/usr/lib/locale/");
+		# endif
+		statable_file_name_set.insert("/usr/java/");
+		statable_file_name_set.insert("/tmp/");
+	} else if (config.type == "java11") {
+		syscall_max_cnt[__NR_gettid         ] = -1;
+		syscall_max_cnt[__NR_set_tid_address] = 1;
+		syscall_max_cnt[__NR_set_robust_list] = 15;
+		syscall_max_cnt[__NR_futex          ] = -1;
+
+		syscall_max_cnt[__NR_uname          ] = 1;
+
+		syscall_max_cnt[__NR_clone          ] = 14;
+
+		syscall_max_cnt[__NR_getdents       ] = 4;
+
+		syscall_max_cnt[__NR_clock_getres   ] = 2;
+
+		syscall_max_cnt[__NR_setrlimit      ] = 1;
+
+		syscall_max_cnt[__NR_sched_getaffinity] = -1;
+		syscall_max_cnt[__NR_sched_yield    ] = -1;
+
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		syscall_max_cnt[__NR_prlimit64      ] = -1;
+		syscall_max_cnt[__NR_getpid         ] = -1;
+		syscall_max_cnt[__NR_sysinfo        ] = -1;
+		syscall_max_cnt[__NR_clone          ] = -1;
+		syscall_max_cnt[__NR_set_robust_list] = -1;
+		syscall_max_cnt[__NR_uname          ] = -1;
+		syscall_max_cnt[__NR_clock_getres   ] = -1;
+		syscall_max_cnt[__NR_pread64        ] = -1;
+		syscall_max_cnt[__NR_prctl          ] = -1;
+		syscall_max_cnt[__NR_nanosleep      ] = -1;
+		# endif
+
+		syscall_should_soft_ban[__NR_socket   ] = true;
+		syscall_should_soft_ban[__NR_connect  ] = true;
+		syscall_should_soft_ban[__NR_geteuid  ] = true;
+		syscall_should_soft_ban[__NR_getuid   ] = true;
+
+		soft_ban_file_name_set.insert("/etc/nsswitch.conf");
+		soft_ban_file_name_set.insert("/etc/passwd");
+
+		add_file_permission("/usr/lib/jvm/java-11-openjdk-amd64/", 'r');
+		readable_file_name_set.insert("/sys/devices/system/cpu/");
+		readable_file_name_set.insert("/proc/");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		readable_file_name_set.insert("/sys/fs/cgroup/cpu/");
+		readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/");
+		readable_file_name_set.insert("/sys/fs/cgroup/memory/");
+		readable_file_name_set.insert("/usr/share/java/");
+		readable_file_name_set.insert("/usr/lib/locale/");
+		readable_file_name_set.insert("/etc/oracle/java/usagetracker.properties");
+		# endif
+		statable_file_name_set.insert("/usr/java/");
+		statable_file_name_set.insert("/tmp/");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		statable_file_name_set.insert("/usr/share/");
+		# endif
+	} else if (config.type == "compiler") {
+//		syscall_max_cnt[__NR_rseq           ] = -1;
+//		syscall_max_cnt[__NR_newfstatat	    ] = -1;
+//		syscall_max_cnt[__NR_clone3         ] = -1;
+		readable_file_name_set.insert("/etc/ld.so.conf");
+		readable_file_name_set.insert("/etc/ld.so.conf.d/");
+
+		syscall_max_cnt[__NR_gettid         ] = -1;
+		syscall_max_cnt[__NR_set_tid_address] = -1;
+		syscall_max_cnt[__NR_set_robust_list] = -1;
+		syscall_max_cnt[__NR_futex          ] = -1;
+
+		syscall_max_cnt[__NR_getpid         ] = -1;
+		syscall_max_cnt[__NR_vfork          ] = -1;
+		syscall_max_cnt[__NR_fork           ] = -1;
+		syscall_max_cnt[__NR_clone          ] = -1;
+		syscall_max_cnt[__NR_execve         ] = -1;
+		syscall_max_cnt[__NR_wait4          ] = -1;
+
+		syscall_max_cnt[__NR_clock_gettime  ] = -1;
+		syscall_max_cnt[__NR_clock_getres   ] = -1;
+
+		syscall_max_cnt[__NR_setrlimit      ] = -1;
+		syscall_max_cnt[__NR_pipe           ] = -1;
+
+		syscall_max_cnt[__NR_getdents64     ] = -1;
+		syscall_max_cnt[__NR_getdents       ] = -1;
+
+		syscall_max_cnt[__NR_umask          ] = -1;
+		syscall_max_cnt[__NR_rename         ] = -1;
+		syscall_max_cnt[__NR_chmod          ] = -1;
+		syscall_max_cnt[__NR_mkdir          ] = -1;
+
+		syscall_max_cnt[__NR_chdir          ] = -1;
+		syscall_max_cnt[__NR_fchdir         ] = -1;
+
+		syscall_max_cnt[__NR_ftruncate      ] = -1; // for javac = =
+
+		syscall_max_cnt[__NR_sched_getaffinity] = -1; // for javac = =
+		syscall_max_cnt[__NR_sched_yield      ] = -1; // for javac = =
+
+		syscall_max_cnt[__NR_uname          ] = -1; // for javac = =
+		syscall_max_cnt[__NR_sysinfo        ] = -1; // for javac = =
+
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		syscall_max_cnt[__NR_prlimit64      ] = -1;
+		syscall_max_cnt[__NR_getrandom      ] = -1;
+		syscall_max_cnt[__NR_pread64        ] = -1;
+		syscall_max_cnt[__NR_prctl          ] = -1;
+		syscall_max_cnt[__NR_nanosleep      ] = -1;
+		syscall_max_cnt[__NR_socketpair     ] = -1;
+		# endif
+
+		syscall_should_soft_ban[__NR_socket   ] = true; // for javac
+		syscall_should_soft_ban[__NR_connect  ] = true; // for javac
+		syscall_should_soft_ban[__NR_geteuid  ] = true; // for javac
+		syscall_should_soft_ban[__NR_getuid  ] = true; // for javac
+
+		writable_file_name_set.insert("/tmp/");
+
+		readable_file_name_set.insert(config.work_path);
+		writable_file_name_set.insert(config.work_path + "/");
+
+		readable_file_name_set.insert(abspath(0, string(self_path) + "/../runtime") + "/");
+		# ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804
+		readable_file_name_set.insert("/sys/fs/cgroup/cpu/");
+		readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/");
+		readable_file_name_set.insert("/sys/fs/cgroup/memory/");
+		readable_file_name_set.insert("/etc/oracle/java/usagetracker.properties");
+		# endif
+
+		readable_file_name_set.insert("system_root");
+		readable_file_name_set.insert("/usr/");
+		readable_file_name_set.insert("/lib/");
+		readable_file_name_set.insert("/lib64/");
+		readable_file_name_set.insert("/bin/");
+		readable_file_name_set.insert("/sbin/");
+		// readable_file_name_set.insert("/proc/meminfo");
+		// readable_file_name_set.insert("/proc/self/");
+
+		readable_file_name_set.insert("/sys/devices/system/cpu/");
+		readable_file_name_set.insert("/proc/");
+		soft_ban_file_name_set.insert("/etc/nsswitch.conf"); // for javac = =
+		soft_ban_file_name_set.insert("/etc/passwd"); // for javac = =
+
+		readable_file_name_set.insert("/etc/timezone");
+		# ifdef UOJ_JUDGER_FPC_VERSION
+		readable_file_name_set.insert("/etc/fpc-" UOJ_JUDGER_FPC_VERSION ".cfg.d/");
+		# else
+		readable_file_name_set.insert("/etc/fpc-2.6.2.cfg.d/");
+		# endif
+		readable_file_name_set.insert("/etc/fpc.cfg");
+
+		statable_file_name_set.insert("/*");
+	}
+}
+
+
+string read_string_from_regs(reg_val_t addr, pid_t pid) {
+	char res[MaxPathLen + 1], *ptr = res;
+	while (ptr != res + MaxPathLen) {
+		*(reg_val_t*)ptr = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
+		for (int i = 0; i < sizeof(reg_val_t); i++, ptr++, addr++) {
+			if (*ptr == 0) {
+				return res;
+			}
+		}
+	}
+	res[MaxPathLen] = 0;
+	return res;
+}
+string read_abspath_from_regs(reg_val_t addr, pid_t pid) {
+	return abspath(pid, read_string_from_regs(addr, pid));
+}
+
+inline void soft_ban_syscall(pid_t pid, user_regs_struct reg) {
+	reg.REG_SYSCALL += 1024;
+	ptrace(PTRACE_SETREGS, pid, NULL, &reg);
+}
+
+inline bool on_dgs_file_detect(pid_t pid, user_regs_struct reg, const string &fn) {
+	if (is_soft_ban_file(fn)) {
+		soft_ban_syscall(pid, reg);
+		return true;
+	} else {
+		return false;
+	}
+}
+
+inline bool check_safe_syscall(pid_t pid, bool need_show_trace_details) {
+	struct user_regs_struct reg;
+	ptrace(PTRACE_GETREGS, pid, NULL, &reg);
+
+	int cur_instruction = ptrace(PTRACE_PEEKTEXT, pid, reg.rip - 2, NULL) & 0xffff;
+	if (cur_instruction != 0x050f) {
+		if (need_show_trace_details) {
+			fprintf(stderr, "informal syscall  %d\n", cur_instruction);
+		}
+		return false;
+	}
+
+	int syscall = (int)reg.REG_SYSCALL;
+	if (0 > syscall || syscall >= 1000)  {
+		return false;
+	}
+	if (need_show_trace_details) {
+		fprintf(stderr, "syscall  %d\n", (int)syscall);
+	}
+
+	if (syscall_should_soft_ban[syscall]) {
+		soft_ban_syscall(pid, reg);
+	} else if (syscall_max_cnt[syscall]-- == 0) {
+		if (need_show_trace_details) {
+			fprintf(stderr, "dgs      %d\n", (int)syscall);
+		}
+		return false;
+	}
+
+	if (syscall == __NR_open || syscall == __NR_openat) {
+		reg_val_t fn_addr;
+		reg_val_t flags;
+		if (syscall == __NR_open) {
+			fn_addr = reg.REG_ARG0;
+			flags = reg.REG_ARG1;
+		} else {
+			fn_addr = reg.REG_ARG1;
+			flags = reg.REG_ARG2;
+		}
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "open  ");
+
+			switch (flags & O_ACCMODE) {
+			case O_RDONLY:
+				fprintf(stderr, "r ");
+				break;
+			case O_WRONLY:
+				fprintf(stderr, "w ");
+				break;
+			case O_RDWR:
+				fprintf(stderr, "rw");
+				break;
+			default:
+				fprintf(stderr, "??");
+				break;
+			}
+			fprintf(stderr, " %s\n", fn.c_str());
+		}
+
+		bool is_read_only = (flags & O_ACCMODE) == O_RDONLY &&
+			(flags & O_CREAT) == 0 &&
+			(flags & O_EXCL) == 0 &&
+			(flags & O_TRUNC) == 0;
+		if (is_read_only) {
+			if (realpath(fn) != "" && !is_readable_file(fn)) {
+				return on_dgs_file_detect(pid, reg, fn);
+			}
+		} else {
+			if (!is_writable_file(fn)) {
+				return on_dgs_file_detect(pid, reg, fn);
+			}
+		}
+	} else if (syscall == __NR_readlink || syscall == __NR_readlinkat) {
+		reg_val_t fn_addr;
+		if (syscall == __NR_readlink) {
+			fn_addr = reg.REG_ARG0;
+		} else {
+			fn_addr = reg.REG_ARG1;
+		}
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "readlink %s\n", fn.c_str());
+		}
+		if (!is_readable_file(fn)) {
+			return on_dgs_file_detect(pid, reg, fn);
+		}
+	} else if (syscall == __NR_unlink || syscall == __NR_unlinkat) {
+		reg_val_t fn_addr;
+		if (syscall == __NR_unlink) {
+			fn_addr = reg.REG_ARG0;
+		} else {
+			fn_addr = reg.REG_ARG1;
+		}
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "unlink   %s\n", fn.c_str());
+		}
+		if (!is_writable_file(fn)) {
+			return on_dgs_file_detect(pid, reg, fn);
+		}
+	} else if (syscall == __NR_access) {
+		reg_val_t fn_addr = reg.REG_ARG0;
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "access   %s\n", fn.c_str());
+		}
+		if (!is_statable_file(fn)) {
+			return on_dgs_file_detect(pid, reg, fn);
+		}
+	} else if (syscall == __NR_stat || syscall == __NR_lstat) {
+		reg_val_t fn_addr = reg.REG_ARG0;
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "stat     %s\n", fn.c_str());
+		}
+		if (!is_statable_file(fn)) {
+			return on_dgs_file_detect(pid, reg, fn);
+		}
+	} else if (syscall == __NR_execve) {
+		reg_val_t fn_addr = reg.REG_ARG0;
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "execve   %s\n", fn.c_str());
+		}
+		if (!is_readable_file(fn)) {
+			return on_dgs_file_detect(pid, reg, fn);
+		}
+	} else if (syscall == __NR_chmod || syscall == __NR_rename) {
+		reg_val_t fn_addr = reg.REG_ARG0;
+		string fn = read_abspath_from_regs(fn_addr, pid);
+		if (need_show_trace_details) {
+			fprintf(stderr, "change   %s\n", fn.c_str());
+		}
+		if (!is_writable_file(fn)) {
+			return on_dgs_file_detect(pid, reg, fn);
+		}
+	}
+	return true;
+}
+
+inline void on_syscall_exit(pid_t pid, bool need_show_trace_details) {
+	struct user_regs_struct reg;
+	ptrace(PTRACE_GETREGS, pid, NULL, &reg);
+	if (need_show_trace_details) {
+		if ((long long int)reg.REG_SYSCALL >= 1024) {
+			fprintf(stderr, "ban sys  %lld\n", (long long int)reg.REG_SYSCALL - 1024);
+		} else {
+			fprintf(stderr, "exitsys  %lld (ret %d)\n", (long long int)reg.REG_SYSCALL, (int)reg.REG_RET);
+		}
+	}
+
+	if ((long long int)reg.REG_SYSCALL >= 1024) {
+		reg.REG_SYSCALL -= 1024;
+		reg.REG_RET = -EACCES;
+		ptrace(PTRACE_SETREGS, pid, NULL, &reg);
+	}
+}

二進制
upload/article-1.webp


二進制
upload/article-2.webp


二進制
upload/article-3.jpg


二進制
upload/statement395.pdf


二進制
upload/statement396.pdf


二進制
upload/statement404.pdf


二進制
upload/statement405.pdf


二進制
upload/statement406.pdf


二進制
upload/statement407.pdf


+ 0 - 0
.htaccess → web/.htaccess


+ 0 - 0
.php-cs-fixer.php → web/.php-cs-fixer.php


+ 114 - 0
web/Dockerfile

@@ -0,0 +1,114 @@
+# WyOJ web image.
+#
+# Build context is the REPOSITORY ROOT (see ../docker-compose.yml):
+#     docker build -t wyoj-web -f web/Dockerfile .
+#
+# The context has to be the root because the web image also needs the judger
+# tree:
+#   * uoj_judger/run/formatter      -- used by dataSyncProblemData() on the web
+#   * uoj_judger/include/testlib.h  -- served by /download.php
+#   * judge_client.zip              -- served by /judge/download/judger
+# ../.dockerignore keeps the database dump and ./uoj_data out of the context.
+#
+# v8js is needed by app/models/UOJBlogEditor.php (markdown -> HTML), and the
+# yaml extension by yaml_parse() in that same file.
+#
+# NOTE on web/v8js.zip: it is a 397 KB local tarball (upstream phpv8/v8js,
+# patched build used in production) and is tracked by git on purpose, so that a
+# clean clone builds without network access to github.com.  If you would rather
+# not track it: add it back to .gitignore and replace the COPY below with
+#     RUN git clone --depth=1 https://github.com/phpv8/v8js.git /tmp/pear/download/v8js
+# (note: `git clone -b <sha>` does not work with a raw commit id).
+FROM ubuntu:20.04
+
+LABEL org.opencontainers.image.source=https://git.ryp.org.cn/wyoj/wyoj-raw
+LABEL org.opencontainers.image.description="WyOJ Web"
+LABEL org.opencontainers.image.licenses=MIT
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+# ---------------------------------------------------------------------------
+# System packages
+#
+# libv8 7.5 only exists in the bionic PPA, hence the mixed release.  Importing
+# the PPA key from a keyserver is flaky (and keyserver.ubuntu.com may be
+# unreachable); if it fails we mark just that one repo trusted so the build
+# does not depend on a keyserver being up.
+# ---------------------------------------------------------------------------
+RUN dpkg -s gnupg >/dev/null 2>&1 || (apt-get update && apt-get install -y gnupg); \
+    echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" \
+        > /etc/apt/sources.list.d/stesie-libv8.list; \
+    if ! apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF; then \
+        echo "WARN: could not import the libv8 PPA key; marking that repo trusted"; \
+        sed -i 's|^deb |deb [trusted=yes] |' /etc/apt/sources.list.d/stesie-libv8.list; \
+    fi; \
+    apt-get update && apt-get install -y \
+        git vim ntp zip unzip curl wget \
+        apache2 libapache2-mod-xsendfile libapache2-mod-php \
+        php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl \
+        g++ make re2c libv8-7.5-dev libyaml-dev && \
+    yes | pecl install yaml && \
+    rm -rf /var/lib/apt/lists/*
+
+# ---------------------------------------------------------------------------
+# v8js + yaml extensions
+# ---------------------------------------------------------------------------
+COPY web/v8js.zip /tmp/pear/download/
+RUN set -eux; \
+    cd /tmp/pear/download; \
+    unzip -q v8js.zip; \
+    cd v8js; \
+    phpize; \
+    ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5; \
+    make -j"$(nproc)"; \
+    make install; \
+    cd /; \
+    rm -rf /tmp/pear/download/v8js
+
+# Enable the extensions for both SAPIs instead of patching php.ini by line
+# number (the old `sed -i '912a\...' /etc/php/7.4/apache2/php.ini` broke the
+# moment the base image changed).
+RUN set -eux; \
+    for sapi in apache2 cli; do \
+        printf 'extension=v8js.so\nextension=yaml.so\n' > "/etc/php/7.4/${sapi}/conf.d/20-uoj.ini"; \
+    done; \
+    php -m | grep -qx v8js; \
+    php -m | grep -qx yaml
+
+# ---------------------------------------------------------------------------
+# Judger tree
+#
+# The web needs this too (see the header comment).  Only run/formatter is
+# actually executed here; the judge clients' self-update zip is built from the
+# sources so that they recompile it themselves (judge_client runs
+# `make clean && make` after unzipping).
+#
+# The zip is written to /opt/uoj/judge_client.zip, *not* into app/storage --
+# app/storage is a bind mount at runtime and would shadow it.  install.sh -i
+# copies it into place.
+# ---------------------------------------------------------------------------
+COPY judger /opt/uoj/judger
+RUN set -eux; \
+    make -C /opt/uoj/judger/uoj_judger run/formatter -j"$(nproc)"; \
+    cd /opt/uoj/judger; \
+    zip -q -r -y /opt/uoj/judge_client.zip judge_client uoj_judger \
+        -x 'uoj_judger/data' 'uoj_judger/data/*' \
+           'uoj_judger/work/*' 'uoj_judger/result/*' \
+           'uoj_judger/run/formatter' \
+           'log/*' '*.o'; \
+    unzip -l /opt/uoj/judge_client.zip | tail -2
+
+# ---------------------------------------------------------------------------
+# Application
+#
+# ADD comes last so that editing PHP/CSS only invalidates the cheap layers.
+# `install.sh -p` prepares apache/php config and the /var/www/uoj symlink; it
+# does not touch the database (that happens at container start).
+# ---------------------------------------------------------------------------
+ADD web /opt/uoj/web
+WORKDIR /opt/uoj/web
+RUN chmod +x install.sh docker-entrypoint.sh && bash install.sh -p
+
+ENV LANG=C.UTF-8 TZ=Asia/Shanghai
+EXPOSE 80
+CMD ["/bin/sh", "/opt/uoj/web/docker-entrypoint.sh"]

+ 0 - 0
app/.default-config.php → web/app/.default-config.php


+ 0 - 0
app/.htaccess → web/app/.htaccess


+ 0 - 0
app/cli.php → web/app/cli.php


+ 0 - 0
app/controllers/add_contest.php → web/app/controllers/add_contest.php


+ 0 - 0
app/controllers/announcements.php → web/app/controllers/announcements.php


+ 0 - 0
app/controllers/blog_show.php → web/app/controllers/blog_show.php


+ 0 - 0
app/controllers/blogs.php → web/app/controllers/blogs.php


+ 1 - 0
app/controllers/certs_manage.php → web/app/controllers/certs_manage.php

@@ -33,6 +33,7 @@
 	}
 
 	if ($_POST['certs'] != '') {
+		crsf_defend();
 		$certs = DB::escape($_POST['certs']);
 		$username = $myUser['username'];
 		if (!in_array($certs, explode(',', $myUser['certslist'])))

+ 0 - 0
app/controllers/change_user_info.php → web/app/controllers/change_user_info.php


+ 1 - 0
app/controllers/click_zan.php → web/app/controllers/click_zan.php

@@ -17,6 +17,7 @@
 	if (!validateZan()) {
 		die('<div class="text-danger">failed</div>');
 	}
+	crsf_defend();
 	if ($myUser == null) {
 		die('<div class="text-danger">please <a href="'.HTML::url('/login').'">log in</a></div>');
 	}

+ 0 - 0
app/controllers/cloud.php → web/app/controllers/cloud.php


+ 0 - 0
app/controllers/contest_inside.php → web/app/controllers/contest_inside.php


+ 0 - 0
app/controllers/contest_manage.php → web/app/controllers/contest_manage.php


+ 0 - 0
app/controllers/contest_members.php → web/app/controllers/contest_members.php


+ 0 - 0
app/controllers/contest_registration.php → web/app/controllers/contest_registration.php


+ 0 - 0
app/controllers/contests.php → web/app/controllers/contests.php


+ 0 - 0
app/controllers/datum.php → web/app/controllers/datum.php


+ 0 - 0
app/controllers/datums.php → web/app/controllers/datums.php


+ 0 - 0
app/controllers/download.php → web/app/controllers/download.php


+ 0 - 0
app/controllers/export_contest_standings_table.php → web/app/controllers/export_contest_standings_table.php


+ 0 - 0
app/controllers/faq.php → web/app/controllers/faq.php


+ 0 - 0
app/controllers/forgot_pw.php → web/app/controllers/forgot_pw.php


+ 1 - 1
app/controllers/group_info.php → web/app/controllers/group_info.php

@@ -6,7 +6,7 @@
 <?php if (validateUsername($username) && ($user = queryUser($username))): ?>
 	<?php echoUOJPageHeader($user['username'] . ' - ' . '组信息') ?>
 	<?php
-	$email = $user['email'];
+	$email = HTML::escape($user['email']);
 	$esc_motto = HTML::escape($user['motto']);
 	$quota = printSize(queryGroupQuota($user));
 	$problemquota = queryGroupProblemQuota($user);

+ 0 - 0
app/controllers/group_manage.php → web/app/controllers/group_manage.php


+ 1 - 1
app/controllers/groups_manage.php → web/app/controllers/groups_manage.php

@@ -81,7 +81,7 @@
 					}
 					else {
 						$grp = queryUser($myUser['childgroup']);
-						$grpnick = $grp['email'];
+						$grpnick = HTML::escape($grp['email']);
 						$grpname = $grp['username'];
 						echo "<div class='row mt-2'>";
 						echo "<div class='col-sm-6'><h4>$grpnick</h4></div>";

+ 0 - 0
app/controllers/hack.php → web/app/controllers/hack.php


+ 0 - 0
app/controllers/hack_list.php → web/app/controllers/hack_list.php


+ 3 - 3
app/controllers/index.php → web/app/controllers/index.php

@@ -696,7 +696,7 @@ $gk28daysLeft  = max(0, (int)floor(($gk28Ts - $todayMidnight) / 86400));
                                 <?= getUserLink($user['username']) ?>
                             </div>
                             <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
-                                <?= $user['motto'] ?>
+                                <?= HTML::escape($user['motto']) ?>
                             </div>
                         </div>
                     </div>
@@ -727,7 +727,7 @@ $gk28daysLeft  = max(0, (int)floor(($gk28Ts - $todayMidnight) / 86400));
                                 </strong>
                             </div>
                             <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
-                                <?= $user['motto'] ?>
+                                <?= HTML::escape($user['motto']) ?>
                             </div>
                         </div>
                     </div>
@@ -754,7 +754,7 @@ $gk28daysLeft  = max(0, (int)floor(($gk28Ts - $todayMidnight) / 86400));
                                 </strong>
                             </div>
                             <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
-                                <?= $user['motto'] ?>
+                                <?= HTML::escape($user['motto']) ?>
                             </div>
                         </div>
                     </div>

+ 0 - 0
app/controllers/judge/download.php → web/app/controllers/judge/download.php


+ 29 - 9
app/controllers/judge/submit.php → web/app/controllers/judge/submit.php

@@ -6,6 +6,17 @@
 		become404Page();
 	}
 	
+	// $_POST['result'] is attacker-controllable whenever the judger password is
+	// weak or leaked (the repo used to ship a placeholder), so decode it once
+	// and refuse anything that is not an object.
+	function judgeResultFromPost() {
+		$result = json_decode($_POST['result'] ?? '', true);
+		if (!is_array($result)) {
+			die("Wow! hacker! T_T....");
+		}
+		return $result;
+	}
+
 	function submissionJudged() {
 		$submission = DB::selectFirst("select submitter, status, content, result, problem_id from submissions where id = {$_POST['id']}");
 		if ($submission == null) {
@@ -18,7 +29,7 @@
 		
 		if (isset($content['first_test_config'])) {
 			$result = json_decode($submission['result'], true);
-			$result['final_result'] = json_decode($_POST['result'], true);
+			$result['final_result'] = judgeResultFromPost();
 			$result['final_result']['details'] = uojTextEncode($result['final_result']['details']);
 			$esc_result = DB::escape(json_encode($result, JSON_UNESCAPED_UNICODE));
 			
@@ -29,13 +40,20 @@
 			
 			DB::update("update submissions set status = 'Judged', result = '$esc_result', content = '$esc_content' where id = {$_POST['id']}");
 		} else {
-			$result = json_decode($_POST['result'], true);
+			$result = judgeResultFromPost();
 			$result['details'] = uojTextEncode($result['details']);
 			$esc_result = DB::escape(json_encode($result, JSON_UNESCAPED_UNICODE));
+			// status/result_error are varchar and score/used_* are int, so
+			// escape the former and cast the latter.
+			$status = DB::escape($result['status']);
 			if (isset($result["error"])) {
-				DB::update("update submissions set status = '{$result['status']}', result_error = '{$result['error']}', result = '$esc_result', score = null, used_time = null, used_memory = null where id = {$_POST['id']}");
+				$error = DB::escape($result['error']);
+				DB::update("update submissions set status = '$status', result_error = '$error', result = '$esc_result', score = null, used_time = null, used_memory = null where id = {$_POST['id']}");
 			} else {
-				DB::update("update submissions set status = '{$result['status']}', result_error = null, result = '$esc_result', score = {$result['score']}, used_time = {$result['time']}, used_memory = {$result['memory']} where id = {$_POST['id']}");
+				$score = (int)$result['score'];
+				$time = (int)$result['time'];
+				$memory = (int)$result['memory'];
+				DB::update("update submissions set status = '$status', result_error = null, result = '$esc_result', score = $score, used_time = $time, used_memory = $memory where id = {$_POST['id']}");
 			}
 			
 			if (isset($content['final_test_config'])) {
@@ -60,21 +78,23 @@
 			return;
 		}
 		$content = json_decode($submission['content'], true);
-		$result = json_decode($_POST['result'], true);
+		$result = judgeResultFromPost();
 		$result['details'] = uojTextEncode($result['details']);
 		$esc_result = DB::escape(json_encode($result, JSON_UNESCAPED_UNICODE));
+		$status = DB::escape($result['status']);
 		if (isset($result["error"])) {
-			DB::update("update custom_test_submissions set status = '{$result['status']}', result = '$esc_result' where id = {$_POST['id']}");
+			DB::update("update custom_test_submissions set status = '$status', result = '$esc_result' where id = {$_POST['id']}");
 		} else {
-			DB::update("update custom_test_submissions set status = '{$result['status']}', result = '$esc_result' where id = {$_POST['id']}");
+			DB::update("update custom_test_submissions set status = '$status', result = '$esc_result' where id = {$_POST['id']}");
 		}
 		DB::update("update custom_test_submissions set status_details = '' where id = {$_POST['id']}");
 	}
 	
 	function hackJudged() {
-		$result = json_decode($_POST['result'], true);
+		$result = judgeResultFromPost();
 		$esc_details = DB::escape(uojTextEncode($result['details']));
-		$ok = DB::update("update hacks set success = {$result['score']}, details = '$esc_details' where id = {$_POST['id']}");
+		$success = (int)$result['score'];
+		$ok = DB::update("update hacks set success = $success, details = '$esc_details' where id = {$_POST['id']}");
 		
 		if ($ok) {
 			list($hack_input) = DB::fetch(DB::query("select input from hacks where id = {$_POST['id']}"), MYSQLI_NUM);

+ 0 - 0
app/controllers/judge/sync_judge_client.php → web/app/controllers/judge/sync_judge_client.php


+ 0 - 0
app/controllers/login.php → web/app/controllers/login.php


+ 0 - 0
app/controllers/logout.php → web/app/controllers/logout.php


+ 0 - 0
app/controllers/map_visualizer.php → web/app/controllers/map_visualizer.php


+ 0 - 0
app/controllers/paste_post.php → web/app/controllers/paste_post.php


+ 0 - 0
app/controllers/paste_view.php → web/app/controllers/paste_view.php


+ 3 - 3
app/controllers/problem.php → web/app/controllers/problem.php

@@ -217,7 +217,7 @@ EOD
 <?php if ($contest): ?>
 <div class="page-header row">
 	<h1 class="col-md-3 text-left"><small><?= $contest['name'] ?></small></h1>
-	<h1 class="col-md-7 text-center"><?= $problem_letter ?>. <?= $problem['title'] ?></h1>
+	<h1 class="col-md-7 text-center"><?= $problem_letter ?>. <?= HTML::escape($problem['title']) ?></h1>
 	<div class="col-md-2 text-right" id="contest-countdown"></div>
 </div>
 <?php if ($contest['cur_progress'] <= CONTEST_IN_PROGRESS): ?>
@@ -227,7 +227,7 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
 </script>
 <?php endif ?>
 <?php else: ?>
-<h1 class="page-header text-center">#<?= $problem['id']?>. <?= $problem['title'] ?></h1>
+<h1 class="page-header text-center">#<?= $problem['id']?>. <?= HTML::escape($problem['title']) ?></h1>
 <?php endif ?>
 
 <div class="row">
@@ -292,7 +292,7 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
 							<?php if ($problem['ctrl'] == ''): ?>
 							<span>默认</span>
 							<?php else: ?>
-							<a href="<?= HTML::url('/user/profile/' . $problem['ctrl']) ?>"><?= queryUser($problem['ctrl'])['email'] ?></a>
+							<a href="<?= HTML::url('/user/profile/' . $problem['ctrl']) ?>"><?= HTML::escape(queryUser($problem['ctrl'])['email']) ?></a>
 							<?php endif ?>
 						</li>
 						<li class="d-flex justify-content-between">

+ 1 - 1
app/controllers/problem_data_manage.php → web/app/controllers/problem_data_manage.php

@@ -618,7 +618,7 @@ EOD
 	$REQUIRE_LIB['dialog'] = '';
 ?>
 <?php echoUOJPageHeader(HTML::stripTags($problem['title']) . ' - 数据 - 题目管理') ?>
-<h1 class="page-header" align="center">#<?=$problem['id']?> : <?=$problem['title']?> 管理</h1>
+<h1 class="page-header" align="center">#<?=$problem['id']?> : <?=HTML::escape($problem['title'])?> 管理</h1>
 <ul class="nav nav-tabs" role="tablist">
 	<li class="nav-item"><a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/statement" role="tab">编辑</a></li>
 	<li class="nav-item"><a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/managers" role="tab">管理者</a></li>

+ 1 - 1
app/controllers/problem_data_manage_back.php → web/app/controllers/problem_data_manage_back.php

@@ -618,7 +618,7 @@ EOD
 	$REQUIRE_LIB['dialog'] = '';
 ?>
 <?php echoUOJPageHeader(HTML::stripTags($problem['title']) . ' - 数据 - 题目管理') ?>
-<h1 class="page-header" align="center">#<?=$problem['id']?> : <?=$problem['title']?> 管理</h1>
+<h1 class="page-header" align="center">#<?=$problem['id']?> : <?=HTML::escape($problem['title'])?> 管理</h1>
 <ul class="nav nav-tabs" role="tablist">
 	<li class="nav-item"><a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/statement" role="tab">编辑</a></li>
 	<li class="nav-item"><a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/managers" role="tab">管理者</a></li>

部分文件因文件數量過多而無法顯示