aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLAN-TW <lantw44@gmail.com>2012-09-14 21:30:45 +0800
committerLAN-TW <lantw44@gmail.com>2012-09-14 21:30:45 +0800
commitae5a715f94c4bb2400b466fb1516c1eb3b5a3682 (patch)
treef9d4ab4803f277649e070868143fac3c16788af5
downloadsctjudge-ae5a715f94c4bb2400b466fb1516c1eb3b5a3682.tar.gz
sctjudge-ae5a715f94c4bb2400b466fb1516c1eb3b5a3682.tar.zst
sctjudge-ae5a715f94c4bb2400b466fb1516c1eb3b5a3682.zip
Initial commit - copy project filessctjudge-0.9.1
All files is copied from sctjudge version 0.9.1.
-rw-r--r--ChangeLog41
-rw-r--r--INSTALL26
-rw-r--r--Makefile.am1
-rw-r--r--README29
-rw-r--r--configure.ac134
-rw-r--r--src/Makefile.am18
-rw-r--r--src/checktle.c80
-rw-r--r--src/common.c89
-rw-r--r--src/common.h21
-rw-r--r--src/config2.h12
-rw-r--r--src/core.h91
-rw-r--r--src/disptime.c140
-rw-r--r--src/main.c402
-rw-r--r--src/mkchild.c708
-rw-r--r--src/version.h.in4
15 files changed, 1796 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..ec06f7f
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,41 @@
+
+2012-07-28 藍挺瑋 <lantw44@gmail.com> - 0.91
+
+ * 軟體建置
+ 修改 src/Makefile.am 因不正確的值而導致連結失敗的問題
+
+ * 額外檔案
+ 這是第一個有 ChangeLog 檔案的版本,其他資訊請參考 README
+
+2012-07-21 藍挺瑋 <lantw44@gmail.com> - 0.9
+
+ * 軟體建置
+ 使用 GNU Autotool 取代原先人工編寫的 Makefile
+
+ * 程式
+ 將大多數檢查參數的工作由 sctjudge_makechild 移至 main 函式中
+
+ * 程式
+ 改善原先受測程式超過時間限制無法立即偵測的問題
+
+ * 程式
+ 若使用 verbose 模式,結束後會顯示使用的 CPU 時間
+
+ * 程式
+ 使用 nanosleep 取代所有 usleep
+
+2012-07-19 藍挺瑋 <lantw44@gmail.com> - 0.8
+
+ * 程式
+ 修正上一版本許多程序、檔案權限控制問題
+
+ * 擴充功能
+ 正式啟用 Linux 程序監視器
+
+2012-07-18 藍挺瑋 <lantw44@gmail.com> - 0.7
+
+ * 軟體建置
+ 使用單純的 Makefile 進行管理
+
+ * 程式
+ 此為第一個測試版本
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..86e4500
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,26 @@
+
+編譯需求
+ Bourne Shell (sh)
+ Make 程式 (make)
+ C 編譯器 (cc)
+ C 標準函式庫 (libc)
+ POSIX Thread 函式庫 (libpthread)
+ 若啟用 Linux capability 支援,還需要一個函式庫:
+ Linux Capability 函式庫 (libcap)
+
+安裝
+ ./configure
+ make
+ make install
+
+移除
+ make uninstall
+
+=============================================================================
+
+以下列出 configure 而可用的選項(也可用 ./configure --help 查到)
+ --enable-debug 停用 -O2 選項並加上 -g (但如果你有指定 CFLAGS 就沒效了)
+ --enable-static 靜態連結可執行檔
+ --enable-procmon 使用 Linux 下 /proc 提供的資訊監控程序
+ --enable-cap 使用 Linux capability 支援(而不使用 setuid)
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..af437a6
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = src
diff --git a/README b/README
new file mode 100644
index 0000000..10bcaa6
--- /dev/null
+++ b/README
@@ -0,0 +1,29 @@
+Standalone Common Test Judge
+Simple Common Test Judge
+
+不管其實是哪一個,總之這個程式名稱就定作 sctjudge。
+
+程式的目標:一個可以跨作業系統(但僅限 UNIX-like 系列)的程式設計題目評測程式,
+不論是作為 script 內的工具程式,還是直接操作,都可以很容易的操作。
+
+我希望有以下功能:
+
+ * Linux 下的程序監視器 (0.8)
+ * 判斷 PE 的能力
+ * 使用 pipe 取得輸出,而不依賴輸出檔
+ * 較易操作的使用者界面,例如 TUI,但不需要 GUI
+
+使用注意事項:因為受測程式在執行時,式無法開啟任何額外函式庫檔案的。因此編譯受
+測程式時,請加上 -static 來編譯成可獨立執行的可執行檔,否則無法得到正確的結果!
+(可能需要安裝額外的套件才能編譯成靜態的執行檔,例如說 glibc-static 這類的套件)
+
+===============================================================================
+
+本程式的 ChangeLog 各項目會以底下的方式分類:
+
+ * 軟體建置:Makefile 或 configure 之類方便編譯、安裝、打包的工具
+ * 額外檔案:README、INSTALL、COPYING 之類提供軟體額外資訊但不安裝的文件
+ * 程式 :程式運作必要的檔案或內容
+ * 擴充功能:程式中可選用的功能
+ * 設定檔 :程式執行中會讀取的設定檔
+ * 說明文件:提供軟體資訊且會安裝至系統的文件
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..cb732c4
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,134 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.68])
+AC_INIT([sctjudge], [0.9.1], [lantw44@gmail.com])
+AM_INIT_AUTOMAKE([foreign -Wall])
+AM_SILENT_RULES([yes])
+AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_HEADERS([src/config.h])
+
+releasedate="2012-07-28"
+
+# Checks for programs.
+AC_PROG_CC
+AM_PROG_CC_C_O
+
+# 和預設的 CFLAGS 說再見
+test -n "${CFLAGS}" && CFLAGS="-g"
+
+
+# 偵錯很重要,但使用者應該不需要
+AC_ARG_ENABLE([debug],
+ [AS_HELP_STRING([--enable-debug],
+ [produce debugging information and disable
+ optimization in binary files])],
+ [opt_debug=$enableval], [opt_debug=no])
+
+if test -n "${CFLAGS}"; then
+ if test x"${opt_debug}" = xyes; then
+ CFLAGS="-g"
+ else
+ CFLAGS="-O2"
+ fi
+fi
+
+# 靜態連結,如果想做 portable 版本的話
+AC_ARG_ENABLE([static],
+ [AC_HELP_STRING([--enable-static],
+ [statically link all executables])],
+ [opt_static=$enableval], [opt_static=no])
+
+AM_CONDITIONAL([STATIC_EXEC], [test x"${opt_static}" = xyes])
+
+# 有些功能是選用的......
+AC_ARG_ENABLE([procmon],
+ [AS_HELP_STRING([--enable-procmon],
+ [enable process monitor using Linux /proc filesystem])],
+ [opt_procmon=$enableval], [opt_procmon=no])
+
+test x"${opt_procmon}" = xyes && \
+ AC_DEFINE([HAVE_CONF_PROCMON], [1], [Process Monitor])
+
+AC_ARG_ENABLE([cap],
+ [AS_HELP_STRING([--enable-cap],
+ [using Linux capabilities instead of standard UNIX permission])],
+ [opt_cap=$enableval], [opt_cap=no])
+
+test x"${opt_cap}" = xyes && \
+ AC_DEFINE([HAVE_CONF_CAP], [1], [Linux Capabilities])
+
+
+# Checks for libraries.
+
+checkliblist="pthread_create pthread_exit pthread_cancel pthread_join pthread_attr_init pthread_attr_destroy pthread_attr_setdetachstate pthread_mutex_init pthread_mutex_destroy pthread_mutex_lock pthread_mutex_unlock pthread_setcancelstate pthread_setcanceltype"
+
+for i in $checkliblist; do
+ AC_CHECK_LIB([pthread], $i, [true],
+ [AC_MSG_ERROR(Your POSIX Thread Library may not be properly installed)])
+done
+LIBS="$LIBS -lpthread -lrt"
+
+
+checkliblist="sem_init sem_destroy sem_wait sem_post"
+for i in $checkliblist; do
+ AC_CHECK_LIB([c], $i, [true], [c_sem_notfound=yes])
+ AC_CHECK_LIB([pthread], $i, [true], [pthread_sem_notfound=yes])
+ if test x"${c_sem_notfound}" = xyes && test x"${pthread_sem_notfound}" = xyes
+ then
+ AC_MSG_ERROR(Your semaphore support may be incomplete)
+ fi
+done
+
+
+if test x"${opt_cap}" = xyes; then
+ checkliblist="cap_init cap_clear_flag cap_set_proc"
+ for i in $checkliblist; do
+ AC_CHECK_LIB([cap], $i, [true],
+ AC_MSG_ERROR(You must install libcap-devel or use --disable-cap))
+ done
+# Checks for programs
+ AC_CHECK_PROG([have_prog_setcap],[setcap],[yes],[no])
+ test x"${have_prog_setcap}" = xno &&
+ AC_MSG_ERROR(setcap does not exist in your PATH)
+ LIBS="$LIBS -lcap"
+fi
+
+AM_CONDITIONAL([USING_SETCAP], [test x"${opt_cap}" = xyes])
+
+# 寫入版本資訊
+AC_SUBST([PROGRAM_NAME], [AC_PACKAGE_NAME])
+AC_SUBST([PROGRAM_VERSION], [AC_PACKAGE_VERSION])
+AC_SUBST([PROGRAM_DATE], $releasedate)
+
+# Checks for header files.
+AC_CHECK_HEADERS([fcntl.h locale.h stdlib.h string.h sys/time.h unistd.h])
+
+if test x"${opt_cap}" = xyes; then
+ AC_CHECK_HEADERS([sys/prctl.h sys/capability.h], [],
+ [AC_MSG_ERROR(You must install these files or use --disable-cap)])
+fi
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_UID_T
+AC_TYPE_MODE_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+
+# Checks for library functions.
+AC_FUNC_FORK
+AC_FUNC_MALLOC
+AC_CHECK_FUNCS([clock_gettime dup2 getpagesize memset setlocale strchr strerror strrchr])
+
+
+AC_CONFIG_FILES([Makefile src/Makefile src/version.h])
+AC_OUTPUT
+
+echo ""
+echo "Optional Features:"
+echo "(1) Process monitor using Linux /proc filesystem .... $opt_procmon"
+echo "(2) Linux capabilities support ...................... $opt_cap"
+echo "-------------------------------------------------------------"
+echo "Compiling and Linking Options:"
+echo "(1) Debugging information ........................... $opt_debug"
+echo "(2) Statically linked executable .................... $opt_static"
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..51ab89a
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,18 @@
+bin_PROGRAMS = sctjudge
+sctjudge_CFLAGS = -Wall -pipe -pthread
+sctjudge_LDFLAGS =
+sctjudge_LDADD =
+sctjudge_SOURCES = common.c common.h config.h core.h disptime.c checktle.c\
+ main.c mkchild.c version.h config2.h
+
+if STATIC_EXEC
+ sctjudge_LDFLAGS += -static
+endif
+
+install-exec-hook:
+if USING_SETCAP
+ setcap cap_sys_chroot,cap_setuid,cap_setgid,cap_kill+ep $(bindir)/sctjudge
+else
+ chown 0 $(bindir)/sctjudge
+ chmod u+s $(bindir)/sctjudge
+endif
diff --git a/src/checktle.c b/src/checktle.c
new file mode 100644
index 0000000..2226551
--- /dev/null
+++ b/src/checktle.c
@@ -0,0 +1,80 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "config2.h"
+#include "common.h"
+#include "core.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+volatile sig_atomic_t break_flag;
+
+static void break_handler(int signo){
+ break_flag = 1;
+}
+
+void* sctjudge_checktle(void* arg){
+ pid_t pidcopy;
+ long long sleeptime = (long long)(*(int*)arg) * 1000000;
+ struct sigaction break_catch;
+ struct timespec timelimit, timeinit, timecur, timetmp;
+ const struct timespec nanslparg = {0, SCT_CHECKTLE_INTERVAL};
+
+ pthread_mutex_lock(&tkill_mx);
+ tkill_yes = 1;
+ pthread_mutex_unlock(&tkill_mx);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+ sem_wait(&addthr);
+ clock_gettime(CLOCK_REALTIME, &timeinit);
+
+#ifndef HAVE_CONF_CAP
+ enable_setuid();
+#endif
+
+ break_flag = 0;
+ memset(&break_catch, 0, sizeof(break_catch));
+ break_catch.sa_handler = &break_handler;
+ sigaction(SIGINT, &break_catch, NULL);
+ sigaction(SIGTERM, &break_catch, NULL);
+
+ pthread_mutex_lock(&pidmutex);
+ pidcopy = pidchild;
+ pthread_mutex_unlock(&pidmutex);
+
+ timelimit.tv_sec = timeinit.tv_sec + sleeptime / 1000000000;
+ timelimit.tv_nsec = timeinit.tv_nsec + sleeptime % 1000000000;
+
+ for(clock_gettime(CLOCK_REALTIME, &timecur);
+ comparetimespec(&timecur, &timelimit) < 0 && !break_flag;
+ clock_gettime(CLOCK_REALTIME, &timecur)){
+ difftimespec(&timecur, &timelimit, &timetmp);
+ if(comparetimespec(&timetmp, &nanslparg) > 0){
+ nanosleep(&nanslparg, NULL);
+ }else{
+ nanosleep(&timetmp, NULL);
+ }
+ }
+
+ if(!break_flag){
+ pthread_mutex_lock(&judge_tle_mx);
+ judge_tle = 1;
+ pthread_mutex_unlock(&judge_tle_mx);
+ }
+
+ kill(pidcopy, SIGKILL);
+
+ pthread_mutex_lock(&tkill_mx);
+ tkill_yes = 0;
+ pthread_mutex_unlock(&tkill_mx);
+ return NULL;
+}
+
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..3bc7cdb
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,89 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "common.h"
+
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#ifndef HAVE_CONF_CAP
+static uid_t procrealuid = 0;
+static uid_t proceffuid = 0;
+#endif
+
+void checktimespec(struct timespec* arg){
+ long tominus;
+ if(arg->tv_nsec >= 1000000000){
+ arg->tv_sec += (arg->tv_nsec / 1000000000);
+ arg->tv_nsec %= 1000000000;
+ }else if(arg->tv_nsec < 0){
+ if((-(arg->tv_nsec)) % 1000000000 == 0){
+ arg->tv_sec -= ((-(arg->tv_nsec)) / 1000000000);
+ arg->tv_nsec = 0;
+ }else{
+ tominus = (((-(arg->tv_nsec)) / 1000000000) + 1);
+ arg->tv_sec -= tominus;
+ arg->tv_nsec += tominus * 1000000000;
+ }
+ }
+}
+
+void difftimespec(start, end, out)
+ const struct timespec* start;
+ const struct timespec* end;
+ struct timespec* out;
+{
+ out->tv_sec = end->tv_sec - start->tv_sec;
+ out->tv_nsec = end->tv_nsec - start->tv_nsec;
+ if(out->tv_nsec < 0){
+ out->tv_nsec += 1000000000;
+ out->tv_sec--;
+ }
+}
+
+int comparetimespec(t1, t2)
+ const struct timespec* t1;
+ const struct timespec* t2;
+{
+ if(t1->tv_sec < t2->tv_sec){
+ return -1;
+ }else if(t1->tv_sec > t2->tv_sec){
+ return 1;
+ }else{
+ if(t1->tv_nsec < t2->tv_nsec){
+ return -1;
+ }else if(t1->tv_nsec > t2->tv_nsec){
+ return 1;
+ }else{
+ return 0;
+ }
+ }
+ return 0;
+}
+
+#ifndef HAVE_CONF_CAP
+
+void save_uids(void){ /* 這個一定要先執行,不然預設 uid 就是 0 */
+ procrealuid = getuid();
+ proceffuid = geteuid();
+}
+
+void disable_setuid(void){
+#ifdef _POSIX_SAVED_IDS
+ seteuid(procrealuid);
+#else
+ setreuid(proceffuid, procrealuid);
+#endif
+}
+
+void enable_setuid(void){
+#ifdef _POSIX_SAVED_IDS
+ seteuid(proceffuid);
+#else
+ setreuid(procrealuid, proceffuid);
+#endif
+}
+
+#endif
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..a5317ba
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,21 @@
+#ifndef SCTJUDGE_COMMON_FUNCTIONS
+#define SCTJUDGE_COMMON_FUNCTIONS
+
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+void checktimespec(struct timespec*);
+void difftimespec(const struct timespec*, const struct timespec*,
+ struct timespec*);
+int comparetimespec(const struct timespec*, const struct timespec*);
+
+
+#ifndef HAVE_CONF_CAP
+void save_uids(void); /* 這個一定要先執行,不然預設 uid 就是 0 */
+void disable_setuid(void);
+void enable_setuid(void);
+#endif
+
+
+#endif
diff --git a/src/config2.h b/src/config2.h
new file mode 100644
index 0000000..3f508d5
--- /dev/null
+++ b/src/config2.h
@@ -0,0 +1,12 @@
+/* 你沒有看錯,真的有 config2.h
+ * 這個檔案用來存放人家所說的「程式內定」的常數 (hard coding) */
+
+/* 丟給 nanosleep 用的 */
+#define SCT_CHECKTLE_INTERVAL 128000000
+#define SCT_DISPTIME_INTERVAL 384000000
+
+/* sctjudge 這個程式的 exit status */
+#define SCTJUDGE_EXIT_SUCCESS 0 /* 當然就是正常結束 */
+#define SCTJUDGE_EXIT_SYNTAX 1 /* 命令列語法錯誤 */
+#define SCTJUDGE_EXIT_TOOFEW 2 /* 少了一些必須的選項 */
+#define SCTJUDGE_EXIT_THREAD 3 /* pthread_create 以後才發生錯誤 */
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..94418e5
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,91 @@
+#ifndef SCTJUDGE_CORE_HEADER
+#define SCTJUDGE_CORE_HEADER
+
+#include <signal.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+#define NULL_DEVICE "/dev/null"
+
+
+/* mkchild.c */
+
+/* 子程序 PID 和對應的 mutex */
+extern pid_t pidchild;
+extern pthread_mutex_t pidmutex;
+
+/* 兩個 thread 的資料 */
+extern pthread_t tkill, tdisplay;
+extern char tkill_yes, tdisplay_yes;
+extern pthread_mutex_t tkill_mx, tdisplay_mx;
+
+/* 用來讓另外兩個 thread 卡住的 semaphore */
+extern sem_t mcthr, addthr;
+
+/* 判斷有無 TLE,此變數由 sctjudge_checktle 設定 */
+extern char judge_tle;
+extern pthread_mutex_t judge_tle_mx;
+
+/* 傳給 sctjudge_makechild 作為參數的 struct */
+struct makechildopt{
+ char* executable;
+ char* chrootdir;
+ char* inputfile;
+ char* outputfile;
+ int exectime;
+ int memlimit;
+ int outlimit;
+ int flags;
+ uid_t uid;
+ gid_t gid;
+};
+
+/* struct makechildopt 裡面 flags 的值 */
+#define SCTMC_REDIR_STDERR 0x00000001
+#define SCTMC_NOCOPY 0x00000002
+#define SCTMC_SETUID 0x00000004
+#define SCTMC_SETGID 0x00000008
+#define SCTMC_VERBOSE 0x00000010
+#define SCTMC_DRYRUN 0x00000020
+
+/* sctjudge_makechild 的回傳值,main 會接收到 */
+struct makechildrval{
+ int mc_exitcode; /* 此與評測結果無關!這是 thread 本身的狀態 */
+ int judge_result; /* 就是那個兩三個英文字母的代碼 */
+ int judge_exitcode; /* 程式結束回傳值 */
+ struct timespec judge_time; /* 執行時間 */
+ int judge_signal; /* RE 時的訊號 */
+ struct rusage judge_rusage; /* 子程序結束時拿到的 rusage */
+};
+
+/* struct makechildrval 裡面 mc_exitcode 的值 */
+#define SCTMCRVAL_SUCCESS 0
+#define SCTMCRVAL_PREPARE 1 /* fork 之前的準備工作出錯 */
+#define SCTMCRVAL_FORK 2 /* fork 失敗 */
+#define SCTMCRVAL_INVALID 3 /* 子程序傳回了不正確的資料(會發生嗎?)*/
+#define SCTMCRVAL_CHILDFAIL 4 /* 子程序在 exec 或之前發生錯誤無法繼續 */
+
+/* struct makechildrval 裡面 judge_result 的值 */
+#define SCTRES_OK 0
+#define SCTRES_RE 1
+#define SCTRES_TLE 2
+#define SCTRES_OLE 3
+#define SCTRES_SLE 4 /* 暫停次數太多 */
+#define SCTRES_AB 5 /* 使用者中斷 */
+#define SCTRES_UD 6 /* 未定義的東西,這應該不會出現吧 */
+#define SCTRES_MAX 7
+
+void* sctjudge_makechild(void*);
+
+/* killtle.c */
+extern volatile sig_atomic_t break_flag;
+void* sctjudge_checktle(void*);
+
+/* disptime.c */
+void* sctjudge_dispaytime(void*);
+
+#endif
diff --git a/src/disptime.c b/src/disptime.c
new file mode 100644
index 0000000..bc47ce0
--- /dev/null
+++ b/src/disptime.c
@@ -0,0 +1,140 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "config2.h"
+#include "common.h"
+#include "core.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+void* sctjudge_dispaytime(void* arg){
+ struct timespec timeinit, timecur, timepast;
+ struct timespec timesleep;
+
+ timesleep.tv_sec = 0;
+ timesleep.tv_nsec = (*(long*)arg);
+
+#ifdef HAVE_CONF_PROCMON
+ int i;
+
+ pid_t pidcopy;
+
+ const char* sysstatfile = "/proc/stat";
+ char statfile[25], statusfile[25];
+ FILE *statp, *statusp, *sysstatp;
+
+ int strlencount, cpuinfostore;
+ char firstrun = 1, hdrstore[10];
+
+ unsigned long cpuuser, cpusystem, precpuuser, precpusystem;
+ unsigned long syscpuall, presyscpuall, diffsyscpuall;
+
+ unsigned short res_cpuuser, res_cpusystem;
+ unsigned long vmsize;
+#endif
+
+ pthread_mutex_lock(&tdisplay_mx);
+ tdisplay_yes = 1;
+ pthread_mutex_unlock(&tdisplay_mx);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+ sem_wait(&addthr);
+
+#ifndef HAVE_CONF_CAP
+ enable_setuid();
+#endif
+
+ clock_gettime(CLOCK_REALTIME, &timeinit);
+
+#ifdef HAVE_CONF_PROCMON
+ pthread_mutex_lock(&pidmutex);
+ pidcopy = pidchild;
+ pthread_mutex_unlock(&pidmutex);
+#endif
+
+#ifdef HAVE_CONF_PROCMON
+ sprintf(statfile, "/proc/%d/stat", pidcopy);
+ sprintf(statusfile, "/proc/%d/status", pidcopy);
+#endif
+
+ while(1){
+ clock_gettime(CLOCK_REALTIME, &timecur);
+ difftimespec(&timeinit, &timecur, &timepast);
+ printf("\r%4ld.%03ld 秒", timepast.tv_sec,
+ timepast.tv_nsec / 1000000);
+#ifdef HAVE_CONF_PROCMON
+ statp = fopen(statfile, "r");
+ statusp = fopen(statusfile, "r");
+ sysstatp = fopen(sysstatfile, "r");
+ if(statp != NULL && statusp != NULL && sysstatp != NULL){
+ strlencount = 0;
+ while(getc(statusp) != '\n'){
+ strlencount++;
+ }
+ strlencount -= 8; /* 把前面的「Name: 」這 8 個字去掉 */
+ /* 接下來從 /proc/[PID]/stat 取得 CPU 時間 */
+ fscanf(statp, "%*d"); /* 讀掉 PID */
+ while(getc(statp) != '('); /* 讀掉 command name 的左括弧 */
+ for(i=1; i<= strlencount; i++){ /* 讀掉整個 command name */
+ getc(statp);
+ }
+ while(getc(statp) != ')'); /* 讀掉 command name 的右括弧 */
+ fscanf(statp, "%*s %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u"
+ "%lu %lu %*d %*d %*d %*d %*d %*d %*u %lu",
+ &cpuuser, &cpusystem, &vmsize);
+ syscpuall = 0;
+ while(!feof(sysstatp)){
+ fscanf(sysstatp, "%9s", hdrstore);
+ if(!strcmp(hdrstore, "cpu")){
+ while(fscanf(sysstatp, "%d", &cpuinfostore) == 1){
+ syscpuall += cpuinfostore;
+ }
+ break;
+ }else{
+ while(getchar() != '\n');
+ }
+ }
+ if(!firstrun){
+ diffsyscpuall = syscpuall - presyscpuall;
+ res_cpuuser = 1000 * (cpuuser - precpuuser) / diffsyscpuall;
+ res_cpusystem = 1000 * (cpusystem - precpusystem) /
+ diffsyscpuall;
+ printf(" user%%: %2hd.%hd sys%%: %2hd.%hd",
+ res_cpuuser / 10, res_cpuuser % 10,
+ res_cpusystem / 10, res_cpusystem % 10);
+ }
+ precpuuser = cpuuser;
+ precpusystem = cpusystem;
+ presyscpuall = syscpuall;
+ printf(" VM: %lu KiB", vmsize / 1024);
+ fputs(" ", stdout);
+ }
+ if(statp != NULL){
+ fclose(statp);
+ }
+ if(statusp != NULL){
+ fclose(statusp);
+ }
+ if(sysstatp != NULL){
+ fclose(sysstatp);
+ }
+ firstrun = 0;
+#endif
+ fflush(stdout);
+ nanosleep(&timesleep, NULL);
+ }
+
+ pthread_mutex_lock(&tdisplay_mx);
+ tdisplay_yes = 0;
+ pthread_mutex_unlock(&tdisplay_mx);
+
+ return NULL;
+}
+
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..ef76767
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,402 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "config2.h"
+#include "version.h"
+#include "common.h"
+#include "core.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+#define SCTMAIN_ERRNOARG "%s: 選項「%s」之後需要一個參數"
+#define SCTMAIN_ERRBADARG "%s: 在「%s」之後有個不存在或用"\
+ "法不正確的參數「%s」\n"
+
+static const char* sctres_text[2][SCTRES_MAX] = {
+ {"OK", "RE", "TLE", "OLE", "SLE", "AB", "UD"},
+ {
+ "正常結束",
+ "執行過程中發生錯誤",
+ "超過時間限制",
+ "輸出超過限制",
+ "暫停次數太多",
+ "使用者中斷操作",
+ "未定義的結果"
+ }
+};
+
+int getargvint(int* ip, int argc, const char* prearg, const char* arg,
+ const char* argzero, const char* invalidmsg){
+ int toreturn;
+ (*ip)++;
+ if((*ip) < argc){
+ toreturn = atoi(arg);
+ if(toreturn <= 0){
+ fprintf(stderr, invalidmsg, argzero, arg);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }else{
+ fprintf(stderr, SCTMAIN_ERRNOARG, argzero, prearg);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ return toreturn;
+}
+
+char* getargvstr(int* ip, int argc, const char* prearg, const char* arg,
+ const char* argzero){
+ char* toreturn;
+ (*ip)++;
+ if((*ip) < argc){
+ toreturn = (char*)arg;
+ }else{
+ fprintf(stderr, SCTMAIN_ERRNOARG, argzero, prearg);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ return toreturn;
+}
+
+int getargvmulstr(char* arg, char** key, char** value, int* keylen){
+ /* 警告;這個函式 thread unsafe */
+ static char* prearg = NULL;
+ char *tmpstr, *seppos;
+ if(prearg != arg){
+ tmpstr = strtok(arg, ",");
+ }else{
+ tmpstr = strtok(NULL, ",");
+ }
+ prearg = arg;
+ if(tmpstr == NULL){
+ return -1;
+ }else{
+ seppos = strchr(tmpstr, '=');
+ (*key) = tmpstr;
+ if(seppos == NULL){
+ (*value) = NULL;
+ }else{
+ (*keylen) = seppos - tmpstr;
+ (*value) = seppos + 1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+int main(int argc, char* argv[]){
+ /* 照理說是 zh_TW.utf8,但考慮作業系統差異還是從環境變數抓
+ * (反正出錯是使用者的事) */
+ setlocale(LC_ALL, "");
+
+ int i;
+ struct makechildopt mcopt; /* 送給 sctjudge_makechild 當參數用 */
+ char verbose=0, dryrun=0;
+ char *mulstr_key, *mulstr_value;
+ int mulstr_keylen;
+
+ /* 預設值當然都是 0 */
+ memset(&mcopt, 0 ,sizeof(mcopt));
+ /* 據說有些 NULL 不是 0,所以還是設定一下比較保險 */
+ mcopt.executable = NULL;
+ mcopt.chrootdir = NULL;
+ mcopt.inputfile = NULL;
+ mcopt.outputfile = NULL;
+
+ /* 解析命令列的時間到了 */
+ for(i=1; i<argc; i++){
+ if(argv[i][0] == '-'){
+ if(!strcmp(&argv[i][1], "help") || !strcmp(&argv[i][1], "-help")){
+ printf(SCTJUDGE_TITLEBAR"\n\n"
+ "用法:%s [選項]\n"
+ "選項:\n\n"
+ " -v/-verbose\n"
+ " 顯示詳細執行過程\n\n"
+ " -version\n"
+ " 顯示版本資訊\n\n"
+ " -n/-dryrun\n"
+ " 只列出設定值而不要執行(包含 -v)\n\n"
+ " -t/-time <時間>\n"
+ " 受測程式時間限制為<時間>毫秒\n\n"
+ " -m/-memory <大小>\n"
+ " 受測程式記憶體限制<大小>MiB\n\n"
+ " -i/-input <檔案>\n"
+ " 指定要導向至受測程式標準輸入的檔案,若未指定則為 "
+ NULL_DEVICE"\n\n"
+ " -o/-output file=<檔案>[,stderr][,limit=<大小>]\n"
+ " 設定受測程式輸出選項:\n\n"
+ " file=<檔案>\n"
+ " 指定輸出檔案為<檔案>\n\n"
+ " stderr\n"
+ " 將受測程式的標準錯誤也導向至輸出檔。若未指定,"
+ "則只有標準輸\n"
+ " 出會寫入輸出檔案,標準錯誤則被導向至 "
+ NULL_DEVICE"\n\n"
+ " limit=<大小>\n"
+ " 受測程式最多只能輸出<大小>MiB,若未指定則不限制"
+ "\n"
+ " (無限制時請確定你有足夠的磁碟空間!)\n\n"
+ " -e/-exec file=<檔案>[,nocopy]\n"
+ " 設定受測程式可執行檔位置:\n\n"
+ " file=<檔案>\n"
+ " 指定受測程式檔案名稱\n\n"
+ " nocopy\n"
+ " 如果你啟用了 chroot 功能,預設情況下本程式會自"
+ "動將檔案複製\n"
+ " 到新的根目錄中,並在結束時自動刪除檔案。\n"
+ " 使用此選項則取消自動複製的功能,但請注意:此時"
+ " file 指定的\n"
+ " 檔案名稱是相對於新的根目錄而不是現在的根目錄!"
+ "\n\n"
+ " -chroot <目錄>\n"
+ " 受測程式將以<目錄>為根目錄執行,若無此選項則關"
+ "閉 chroot 功能\n\n"
+ " -u/-uid <UID>\n"
+ " 受測程式將以 UID 為 <UID> 的使用者身份執行\n"
+ " -g/-gid <GID>\n"
+ " 受測程式將以 GID 為 <GID> 的群組身份執行\n"
+ " 此選項會同時設定 real/effective/supplementary "
+ "GID(s)\n"
+ " 原有的 supplementry GIDs 會被清空,只剩下這裡指"
+ "定的數值\n\n"
+ , argv[0]);
+ return SCTJUDGE_EXIT_SUCCESS;
+ }else if(!strcmp(&argv[i][1], "version") ||
+ !strcmp(&argv[i][1], "-version")){
+ puts(SCTJUDGE_TITLEBAR);
+ exit(SCTJUDGE_EXIT_SUCCESS);
+ }else if(!strcmp(&argv[i][1], "v") ||
+ !strcmp(&argv[i][1], "verbose")){
+ verbose = 1;
+ mcopt.flags |= SCTMC_VERBOSE;
+ }else if(!strcmp(&argv[i][1], "n") ||
+ !strcmp(&argv[i][1], "dryrun")){
+ dryrun = 1, verbose = 1;
+ mcopt.flags |= (SCTMC_DRYRUN | SCTMC_VERBOSE);
+ }else if(!strcmp(&argv[i][1], "t") ||
+ !strcmp(&argv[i][1], "time")){
+ mcopt.exectime = getargvint(&i, argc, argv[i], argv[i+1],
+ argv[0], "%s: 「%s」不是正確的時間設定值\n");
+ }else if(!strcmp(&argv[i][1], "m") ||
+ !strcmp(&argv[i][1], "memory")){
+ mcopt.memlimit = getargvint(&i, argc, argv[i], argv[i+1],
+ argv[0], "%s: 「%s」不是正確的記憶體大小設定\n");
+ }else if(!strcmp(&argv[i][1], "i") ||
+ !strcmp(&argv[i][1], "input")){
+ mcopt.inputfile = getargvstr(&i, argc, argv[i], argv[i+1],
+ argv[0]);
+ }else if(!strcmp(&argv[i][1], "o") ||
+ !strcmp(&argv[i][1], "output")){
+ getargvstr(&i, argc, argv[i], argv[i+1], argv[0]);
+ while(getargvmulstr(argv[i], &mulstr_key, &mulstr_value,
+ &mulstr_keylen) >= 0){
+ if(mulstr_value == NULL){
+ if(!strcmp(mulstr_key, "stderr")){
+ mcopt.flags |= SCTMC_REDIR_STDERR;
+ }else{
+ fprintf(stderr, SCTMAIN_ERRBADARG
+ , argv[0], argv[i-1], mulstr_key);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }else{
+ if(!strncmp(mulstr_key, "limit", mulstr_keylen)){
+ mcopt.outlimit = atoi(mulstr_value);
+ if(mcopt.outlimit <= 0){
+ fprintf(stderr, "%s: 「%s」不是正確的輸出限制"
+ "值\n", argv[0], mulstr_value);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }else if(!strncmp(mulstr_key, "file", mulstr_keylen)){
+ mcopt.outputfile = mulstr_value;
+ }else{
+ fprintf(stderr, SCTMAIN_ERRBADARG
+ , argv[0], argv[i-1], mulstr_key);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }
+ }
+ }else if(!strcmp(&argv[i][1], "e") ||
+ !strcmp(&argv[i][1], "exec")){
+ getargvstr(&i, argc, argv[i], argv[i+1], argv[0]);
+ while(getargvmulstr(argv[i], &mulstr_key, &mulstr_value,
+ &mulstr_keylen) >= 0){
+ if(mulstr_value == NULL){
+ if(!strcmp(mulstr_key, "nocopy")){
+ mcopt.flags |= SCTMC_NOCOPY;
+ }else{
+ fprintf(stderr, SCTMAIN_ERRBADARG,
+ argv[0], argv[i-1], mulstr_key);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }else{
+ if(!strncmp(mulstr_key, "file", mulstr_keylen)){
+ mcopt.executable = mulstr_value;
+ }else{
+ fprintf(stderr, SCTMAIN_ERRBADARG
+ , argv[0], argv[i-1], mulstr_key);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }
+ }
+ }else if(!strcmp(&argv[i][1], "chroot")){
+ mcopt.chrootdir = getargvstr(&i, argc, argv[i], argv[i+1],
+ argv[0]);
+ }else if(!strcmp(&argv[i][1], "u") || !strcmp(&argv[i][1], "uid")){
+ mcopt.flags |= SCTMC_SETUID;
+ mcopt.uid = getargvint(&i, argc, argv[i], argv[i+1],
+ argv[0], "%s: 指定 UID 為「%s」不安全或不合理\n");
+ }else if(!strcmp(&argv[i][1], "g") || !strcmp(&argv[i][1], "gid")){
+ mcopt.flags |= SCTMC_SETGID;
+ mcopt.gid = getargvint(&i, argc, argv[i], argv[i+1],
+ argv[0], "%s: 指定 GID 為「%s」不安全或不合理\n");
+ }else{
+ fprintf(stderr, "%s: 不明的選項「%s」\n", argv[0], argv[i]);
+ return SCTJUDGE_EXIT_SYNTAX;
+ }
+ }else{
+ fprintf(stderr, "%s: 參數 %d「%s」是多餘的\n"
+ "請嘗試執行 %s -help 來取得說明\n"
+ , argv[0], i, argv[i], argv[0]);
+ return SCTJUDGE_EXIT_SYNTAX;
+ }
+ }
+ /* 至此可以進入主程式了 */
+ if(verbose){
+ puts(SCTJUDGE_TITLEBAR"\n");
+ }
+
+#ifndef HAVE_CONF_CAP
+ /* 即使有 setuid root,還是不能讓每個使用者拿到 root 權限
+ * 所以說只有在 fork 的時候才要把這個權限開起來,其他就關掉吧 */
+ save_uids();
+ disable_setuid();
+#endif
+
+ /* 檢查與修正設定值 */
+ if(verbose){
+ puts("正在檢查設定值是否正確......");
+ }
+ if(mcopt.executable == NULL){
+ fputs("受測程式可執行檔名稱為必要參數,不可為空白\n", stderr);
+ exit(SCTJUDGE_EXIT_TOOFEW);
+ }
+ if(mcopt.inputfile == NULL){
+ mcopt.inputfile = NULL_DEVICE;
+ }
+ if(mcopt.outputfile == NULL){
+ fputs("輸出檔案名稱必要參數,不可為空白\n", stderr);
+ exit(SCTJUDGE_EXIT_TOOFEW);
+ }
+ if(mcopt.exectime <= 0){
+ fputs("執行時間限制為必要參數,不可為空白!\n", stderr);
+ exit(SCTJUDGE_EXIT_TOOFEW);
+ }
+
+ /* 現在開始建立 thread 了!*/
+ pthread_t tmain; /* 其他 thread 的 ID 放在全域變數 */
+ struct makechildrval* mtreturn; /* 接收 sctjudge_makechild 的回傳值 */
+
+ /* 傳給另外兩個 thread 的參數 */
+ int exectimelimit = mcopt.exectime;
+ /* XXX 抱歉 hard coding,我以後會改進 */
+ long displayinterval = SCT_DISPTIME_INTERVAL;
+
+ pthread_attr_t detstate, joistate;
+ /* 除了主要的 sctjudge_makechild 以外,
+ * 我想其他的 thread 沒有 join 的必要 */
+ pthread_attr_init(&detstate);
+ pthread_attr_setdetachstate(&detstate, PTHREAD_CREATE_DETACHED);
+ pthread_attr_init(&joistate);
+ pthread_attr_setdetachstate(&joistate, PTHREAD_CREATE_JOINABLE);
+ pthread_mutex_init(&pidmutex, NULL);
+ pthread_mutex_init(&tkill_mx, NULL);
+ pthread_mutex_init(&tdisplay_mx, NULL);
+ pthread_mutex_init(&judge_tle_mx, NULL);
+ sem_init(&mcthr, 0, 0);
+ sem_init(&addthr, 0, 0);
+ pthread_create(&tmain, &joistate, &sctjudge_makechild, (void*)&mcopt);
+ if(!dryrun){
+ pthread_create(&tkill, &detstate, &sctjudge_checktle,
+ (void*)&exectimelimit);
+ }
+ if(!dryrun && verbose){
+ pthread_create(&tdisplay, &detstate, &sctjudge_dispaytime,
+ (void*)&displayinterval);
+ }
+ pthread_attr_destroy(&detstate);
+ pthread_attr_destroy(&joistate);
+ sem_post(&mcthr);
+
+ /* 這個 semaphore 到這裡就沒用了,
+ * sctjudge_makechild 一旦開始動就會把它 destroy 掉 */
+ pthread_join(tmain, (void**)&mtreturn);
+ /* XXX 底下這訊息很有可能讓使用者覺得很奇怪 */
+ pthread_mutex_destroy(&pidmutex);
+ pthread_mutex_destroy(&tkill_mx);
+ pthread_mutex_destroy(&tdisplay_mx);
+
+ if(dryrun){
+ return SCTJUDGE_EXIT_SUCCESS;
+ }
+
+ /* 這裡就要顯示結果了 */
+ if(mtreturn == NULL || mtreturn->mc_exitcode != 0){
+ fprintf(stderr, "%s: 因錯誤發生而結束程式\n", argv[0]);
+ free((void*)mtreturn);
+ sem_destroy(&mcthr);
+ exit(SCTJUDGE_EXIT_THREAD);
+ }else{
+ if(verbose){
+ putchar('\r');
+ puts("=======================================================");
+ fputs("評測結果:", stdout);
+ if(mtreturn->judge_result < 0 ||
+ mtreturn->judge_result >= SCTRES_UD){
+ mtreturn->judge_result = SCTRES_UD;
+ }
+ printf("%s (%s)\n",
+ sctres_text[0][mtreturn->judge_result],
+ sctres_text[1][mtreturn->judge_result]);
+ printf("執行時間:%ld.%03ld 秒\n",
+ mtreturn->judge_time.tv_sec,
+ mtreturn->judge_time.tv_nsec / 1000000);
+ printf("程式離開狀態:%d\n", mtreturn->judge_exitcode);
+ if(mtreturn->judge_result == SCTRES_RE){
+ printf("程式因訊號 %d 而終止\n", mtreturn->judge_signal);
+ }
+ puts("-------------------------------------------------------");
+ printf("User CPU time: %ld.%03ld\n"
+ "System CPU time: %ld.%03ld\n",
+ mtreturn->judge_rusage.ru_utime.tv_sec,
+ mtreturn->judge_rusage.ru_utime.tv_usec / 1000,
+ mtreturn->judge_rusage.ru_stime.tv_sec,
+ mtreturn->judge_rusage.ru_stime.tv_usec / 1000);
+ puts("=======================================================");
+ }else{
+ /* 輸出格式:
+ * 第一行是結果代碼、時間、離開狀態
+ * 第二行是收到的訊號 (只在 RE 時會有) */
+ if(mtreturn->judge_result < 0 ||
+ mtreturn->judge_result >= SCTRES_UD){
+ mtreturn->judge_result = SCTRES_UD;
+ }
+ fputs(sctres_text[0][mtreturn->judge_result], stdout);
+ putchar(' ');
+ printf("%ld%03ld %d\n", mtreturn->judge_time.tv_sec,
+ mtreturn->judge_time.tv_nsec / 1000000,
+ mtreturn->judge_exitcode);
+ if(mtreturn->judge_result == SCTRES_RE){
+ printf("%d\n", mtreturn->judge_signal);
+ }
+ }
+ free((void*)mtreturn);
+ }
+ return SCTJUDGE_EXIT_SUCCESS;
+}
diff --git a/src/mkchild.c b/src/mkchild.c
new file mode 100644
index 0000000..1f7f51d
--- /dev/null
+++ b/src/mkchild.c
@@ -0,0 +1,708 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "config2.h"
+#include "common.h"
+#include "core.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+#ifdef HAVE_CONF_CAP
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#endif
+
+#define abort_makechild(val) \
+ do { \
+ sctjudge_makechild_cleanup(&mcopt, chrdir_oldmode, execdestname); \
+ toreturn->mc_exitcode = (val); \
+ pthread_exit((void*)toreturn); \
+ }while(0)
+
+#define SCTCHILD_OPEN_INPUT 0
+#define SCTCHILD_DUP2_INPUT 1
+#define SCTCHILD_OPEN_OUTPUT 2
+#define SCTCHILD_DUP2_OUTPUT 3
+#define SCTCHILD_OPEN_STDERR 4
+#define SCTCHILD_DUP2_STDERR 5
+#define SCTCHILD_CLOSE_OTHERFD 6
+#define SCTCHILD_SETRLIMIT_VM 7
+#define SCTCHILD_SETRLIMIT_FSIZE 8
+#define SCTCHILD_SETRLIMIT_OPENFILE 9
+#define SCTCHILD_SETRLIMIT_PROC 10
+#define SCTCHILD_CHROOT 11
+#define SCTCHILD_SETUID 12
+#define SCTCHILD_SETGID 13
+#define SCTCHILD_SETGROUPS 14
+#define SCTCHILD_CAPSET 15
+#define SCTCHILD_EXEC 16
+#define SCTCHILD_MSGMAX 17 /* 訊息代碼的最大值 */
+#define SCTCHILD_WRITE_SUCCMSG(reason) \
+ do{ \
+ childmsg[0] = (reason); \
+ childmsg[1] = 0; \
+ write(childpipe[1], (void*)childmsg, sizeof(int)*2); \
+ }while(0)
+#define SCTCHILD_WRITE_FAILMSG(reason) \
+ do{ \
+ childmsg[0] = (reason); \
+ childmsg[1] = errno; \
+ write(childpipe[1], (void*)childmsg, sizeof(int)*2); \
+ }while(0)
+
+pid_t pidchild;
+pthread_mutex_t pidmutex, tkill_mx, tdisplay_mx, judge_tle_mx;
+pthread_t tkill, tdisplay;
+char tkill_yes = 0, tdisplay_yes = 0, judge_tle = 0;
+sem_t mcthr, addthr;
+
+static const char* childmsg_text[SCTCHILD_MSGMAX] = {
+ "開啟輸入檔案",
+ "重新導向標準輸入",
+ "開啟輸出檔案",
+ "重新導向標準輸出",
+ "開啟用於導向標準錯誤的檔案",
+ "重新導向標準錯誤",
+ "關閉所有不使用的檔案",
+ "設定記憶體限制",
+ "設定輸出限制",
+ "設定禁止開啟其他檔案",
+ "設定禁止產生新程序",
+ "執行 chroot",
+ "修改 real 和 effective UID",
+ "修改 real 和 effective GID",
+ "修改 supplementary GIDs",
+ "丟棄所有 Linux capabilities",
+ "執行受測程式"
+};
+
+static void sctjudge_makechild_cleanup_p1(void){
+ pthread_mutex_lock(&tkill_mx);
+ if(tkill_yes){
+ tkill_yes = 0;
+ pthread_mutex_unlock(&tkill_mx);
+ pthread_cancel(tkill);
+ }else{
+ pthread_mutex_unlock(&tkill_mx);
+ }
+
+ pthread_mutex_lock(&tdisplay_mx);
+ if(tdisplay_yes){
+ tdisplay_yes = 0;
+ pthread_mutex_unlock(&tdisplay_mx);
+ pthread_cancel(tdisplay);
+ }else{
+ pthread_mutex_unlock(&tdisplay_mx);
+ }
+}
+
+static void sctjudge_makechild_cleanup_p2(mcopt, oldperm, copiedexe)
+ const struct makechildopt* mcopt;
+ const mode_t oldperm;
+ char* copiedexe;
+{
+ if(oldperm != -1 && mcopt->chrootdir != NULL){
+ if(mcopt->flags & SCTMC_VERBOSE){
+ printf("恢復 %s 的權限\n", mcopt->chrootdir);
+ }
+ chmod(mcopt->chrootdir, oldperm);
+ }
+ if(copiedexe != NULL && mcopt->chrootdir != NULL &&
+ !(mcopt->flags & SCTMC_NOCOPY)){
+ if(mcopt->flags & SCTMC_VERBOSE){
+ printf("移除 %s......\n", copiedexe);
+ }
+ unlink(copiedexe);
+ free(copiedexe);
+ }
+}
+
+static void sctjudge_makechild_cleanup(mcopt, oldperm, copiedexe)
+ const struct makechildopt* mcopt;
+ const mode_t oldperm;
+ char* copiedexe;
+{
+ sctjudge_makechild_cleanup_p1();
+ sctjudge_makechild_cleanup_p2(mcopt, oldperm, copiedexe);
+}
+
+static int copyfilebyname(const char* src, const char* destdir
+ , char** srcsn, char** destfn){
+ /* strerror 並不 thread safe,因此必須確定只有一個 thread 會用到 */
+ /* 為了確保安全,要求目的地目錄必須是空的 */
+ DIR* dirp;
+ if((dirp=opendir(destdir)) == NULL){
+ fprintf(stderr, "無法開啟目錄 %s:%s\n", destdir, strerror(errno));
+ return -1;
+ }
+ struct dirent* entryp;
+ while((entryp=readdir(dirp)) != NULL){
+ if(entryp->d_name[0] != '.' ||
+ (entryp->d_name[1] != '.' && entryp->d_name[1] != '\0')){
+ fprintf(stderr, "拒絕複製檔案:目錄 %s 不是空的\n", destdir);
+ return -1;
+ }
+ }
+ int fd, fd2;
+ if((fd=open(src, O_RDONLY)) < 0){
+ fprintf(stderr, "無法讀取檔案 %s:%s\n", src, strerror(errno));
+ return -1;
+ }
+ const int bufsize = 4096;
+ void* buf = malloc(bufsize);
+ char* srcshortname;
+ char* tmp;
+ if((tmp=strrchr(src, '/')) == NULL){
+ srcshortname = (char*)src;
+ }else{
+ srcshortname = tmp + 1;
+ }
+ *srcsn = srcshortname;
+ char* destfilename = (char*)
+ malloc(strlen(srcshortname) + strlen(destdir) + 1);
+ sprintf(destfilename, "%s/%s", destdir, srcshortname);
+ *destfn = destfilename;
+ /* 新檔案權限是 0755,umask 亂弄的我可不管 */
+ if((fd2=open(destfilename, O_CREAT | O_WRONLY | O_EXCL,
+ S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) < 0){
+ fprintf(stderr, "無法建立檔案 %s:%s\n",
+ destfilename, strerror(errno));
+ free(buf);
+ return -1;
+ }
+ int readcount;
+ while((readcount = read(fd, buf, bufsize)) > 0){
+ write(fd2, buf, readcount);
+ }
+ close(fd);
+ close(fd2);
+ free(buf);
+ return 0;
+}
+
+static void sctjudge_list_settings(const struct makechildopt* mcopt){
+ printf("\t受測程式可執行檔名稱:%s", mcopt->executable);
+ if(mcopt->flags & SCTMC_NOCOPY && mcopt->chrootdir != NULL){
+ printf(" (相對於 %s)\n"
+ "\t受測程式執行時的根目錄:%s\n"
+ "\t執行前複製檔案:否\n"
+ , mcopt->chrootdir, mcopt->chrootdir);
+ }else{
+ putchar('\n');
+ if(mcopt->chrootdir != NULL){
+ printf("\t受測程式執行時的根目錄:%s\n"
+ "\t執行前複製檔案:是\n"
+ , mcopt->chrootdir);
+ }else{
+ puts("\t受測程式執行時的根目錄:/\n"
+ "\t執行前複製檔案:否");
+ }
+ }
+ printf("\t輸入檔案:%s\n"
+ "\t輸出檔案:%s"
+ , mcopt->inputfile, mcopt->outputfile);
+ if(mcopt->flags & SCTMC_REDIR_STDERR){
+ puts(" (重新導向標準輸出和標準錯誤)");
+ }else{
+ puts(" (重新導向標準輸出)");
+ }
+ printf("\t執行時間限制:%d 毫秒\n", mcopt->exectime);
+ fputs("\t記憶體使用量限制:", stdout);
+ if(mcopt->memlimit > 0){
+ printf("%d MiB\n", mcopt->memlimit);
+ }else{
+ puts("無限制");
+ }
+ fputs("\t輸出限制:", stdout);
+ if(mcopt->outlimit > 0){
+ printf("%d MiB\n", mcopt->outlimit);
+ }else{
+ puts("無限制");
+ }
+ if(mcopt->flags & SCTMC_SETUID){
+ printf("\t設定受測程式執行時的 UID 為 %d\n", mcopt->uid);
+ }else{
+ puts("\t執行受測程式時維持原有的 real UID 和 effective UID");
+ }
+ if(mcopt->flags & SCTMC_SETGID){
+ printf("\t設定受測程式執行時的 GID 為 %d\n", mcopt->gid);
+ }else{
+ puts("\t執行受測程式時維持原有的 real GID、effective GID "
+ "和 supplementary GID");
+ }
+}
+
+static int read_size(int fd, void* buf, size_t count){
+ /* 一定要讀到 count 位元組的資料才會回傳,所以只要回傳 < count
+ * 就表示已經讀到 EOF 了 */
+ char* bufp = (char*)buf;
+ size_t curcount = 0;
+ int rval;
+ do{
+ rval = read(fd, bufp, count - curcount);
+ if(rval < 0){
+ return rval;
+ }else if(rval == 0){
+ return curcount;
+ }else{
+ bufp += rval;
+ curcount += rval;
+ }
+ }while(curcount < count);
+ return count;
+}
+
+void* sctjudge_makechild(void* arg){
+ /* 首先等所有 thread 都啟動完成再開始動作 */
+ sem_wait(&mcthr);
+ sem_destroy(&mcthr);
+
+ /* 宣告變數囉! */
+ int i;
+
+ /* 函式參數與回傳值,Judge 結果就當作回傳值 */
+ struct makechildopt mcopt = *(struct makechildopt*)arg;
+ struct makechildrval* toreturn =
+ (struct makechildrval*)malloc(sizeof(struct makechildrval));
+ memset(toreturn, 0, sizeof(struct makechildrval));
+
+ /* 可執行檔名稱儲存區 */
+ char* execshortname = NULL; /* 可以說就是 basename,chroot+copy 才會用 */
+ char* execdestname = NULL; /* 複製出來的暫時執行檔,chroot+copy 才會用 */
+ /* 這幾個變數用來儲存 Judge 結果,如果沒有啟用 verbose
+ * 且一切正常,那這些是唯一會輸出的東西了,目的是輸出容
+ * 易被其他程式分析 */
+
+ /* 要 chroot 的目錄的相關資訊 */
+ struct stat chrdirinfo; /* 取得原始的 mode 用的 */
+ mode_t chrdir_oldmode = -1; /* 這樣我們才能在結束時 chmod 回去 */
+
+ /* 子程序的 PID,因為不會變,所以複製一份下來就不用去全域變數拿的 */
+ pid_t pidcopy;
+
+ /* 和子程序溝通用的 */
+ int childpipe[2];
+ int childmsg[2]; /* 每個訊息的大小都是 sizeof(int)*2
+ [0]=發生事情的名稱 [1]=errno */
+ int readsize = sizeof(int) * 2;
+
+ /* 計時用的 */
+ struct timespec execstart, execfinish;
+
+ /* 子程序結束狀態 */
+ int childexitstat;
+ char childterminated = 0;
+
+ /* 子程序才用的到的變數 */
+ int fdinput, fdoutput, fderr, childressize;
+ const int fdtablesize = getdtablesize();
+ struct rlimit childres;
+
+ /* 變數宣告完了 */
+
+ if(mcopt.flags & SCTMC_VERBOSE){
+ puts("列出設定值:");
+ sctjudge_list_settings(&mcopt);
+ fflush(stdout);
+ }
+ if(mcopt.flags & SCTMC_DRYRUN){
+ abort_makechild(SCTMCRVAL_SUCCESS);
+ }
+
+ /* 我們要開始做正事了 */
+ /* 由此開始,我是唯一會用到 strerror() 的 thread
+ * 所以說應該沒有問題 */
+ if(mcopt.chrootdir != NULL && !(mcopt.flags & SCTMC_NOCOPY)){
+ /* 必須要複製可執行檔 */
+ if(mcopt.flags & SCTMC_VERBOSE){
+ puts("開始複製可執行檔......");
+ }
+ if(copyfilebyname(mcopt.executable, mcopt.chrootdir, &execshortname
+ , &execdestname) < 0){
+ abort_makechild(SCTMCRVAL_PREPARE);
+ }
+ }
+ /* 再來是 chroot 的問題,避免受測程式亂開目錄之類的,
+ * 所以把權限設成 0555,不過如果沒有變更 uid 和 gid ,
+ * 受測程式還是可以 chmod 回來 */
+ if(mcopt.chrootdir != NULL){
+ if(mcopt.flags & SCTMC_VERBOSE){
+ puts("取得用於 chroot 的目錄相關資訊");
+ }
+ if(stat(mcopt.chrootdir, &chrdirinfo) < 0){
+ fprintf(stderr, "無法取得目錄 %s 的相關資訊:%s\n",
+ mcopt.chrootdir, strerror(errno));
+ abort_makechild(SCTMCRVAL_PREPARE);
+ }else{
+ if((chrdirinfo.st_mode & S_IFMT) == S_IFDIR){
+ chrdir_oldmode = chrdirinfo.st_mode &
+ (S_ISUID |S_ISGID |S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
+ }else{
+ fprintf(stderr, "%s 並不是目錄\n", mcopt.chrootdir);
+ abort_makechild(SCTMCRVAL_PREPARE);
+ }
+ }
+ if(mcopt.flags & SCTMC_VERBOSE){
+ puts("嘗試變更此目錄的權限");
+ }
+ if(chmod(mcopt.chrootdir,
+ S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0){
+ fprintf(stderr, "無法變更目錄 %s 的權限\n", mcopt.chrootdir);
+ abort_makechild(SCTMCRVAL_PREPARE);
+ }
+ }
+
+ fflush(stdout);
+#ifndef HAVE_CONF_CAP
+ enable_setuid();
+#endif
+ /* 建立用來和 chlid process 溝通的 pipe */
+ pipe(childpipe);
+ pthread_mutex_lock(&pidmutex);
+ pidchild = fork();
+ if(pidchild < 0){
+#ifndef HAVE_CONF_CAP
+ disable_setuid();
+#endif
+ pthread_mutex_unlock(&pidmutex);
+ fprintf(stderr, "子程序建立失敗:%s\n", strerror(errno));
+ abort_makechild(SCTMCRVAL_FORK);
+ }
+ else if(pidchild > 0){
+ /* 我們原本的 thread */
+ /* 從這裡開始,我們需要 setuid 來 kill 子程序,
+ * 所以說不要執行 disable_setuid() */
+ pidcopy = pidchild;
+ pthread_mutex_unlock(&pidmutex);
+ close(childpipe[1]);
+ /* 從 pipe 接收 child process 狀態,每次固定要讀 sizeof(int)*2 */
+ while(read_size(childpipe[0], (void*)childmsg, readsize) == readsize){
+ if(childmsg[0] < 0 || childmsg[0] >= SCTCHILD_MSGMAX){
+ /* 會發生嗎?除非被亂七八糟 ptrace 吧
+ * 這時候先砍掉子程序再說 */
+ kill(pidcopy, SIGKILL);
+ close(childpipe[0]);
+ abort_makechild(SCTMCRVAL_INVALID);
+ }else{
+ if(childmsg[1]){
+ fprintf(stderr, "子程序[%d]發生錯誤:%s:%s\n",
+ pidcopy, childmsg_text[childmsg[0]],
+ strerror(childmsg[1]));
+ kill(pidcopy, SIGKILL);
+ close(childpipe[0]);
+ abort_makechild(SCTMCRVAL_CHILDFAIL);
+ }else{
+ if(mcopt.flags & SCTMC_VERBOSE){
+ printf("子程序[%d]:%s\n", pidcopy,
+ childmsg_text[childmsg[0]]);
+ }
+ }
+ }
+ }
+ clock_gettime(CLOCK_REALTIME, &execstart);
+ close(childpipe[0]);
+
+ /* 現在我們確定受測程式已經在執行了,所以需要記錄一下時間
+ * 通知其他 thread,然後就可以把 semaphore 丟掉了 */
+ sem_post(&addthr);
+ sem_post(&addthr);
+ sem_destroy(&addthr);
+
+ /* 開始要 wait 了,莫名其妙 stop 的程式我最多只會送
+ * 5 次 SIGCONT 給你(避免進入無限迴圈)
+ * 這種奇怪的程式就等著 SLE 吧 */
+ for(i=0; i<=5; i++){
+ wait4(pidcopy, &childexitstat, WUNTRACED, &toreturn->judge_rusage);
+ if(WIFSTOPPED(childexitstat) && i!=5){
+ if(mcopt.flags & SCTMC_VERBOSE){
+ printf("\n子程序因訊號 %d 而暫停,自動傳送 SIGCONT "
+ "(第 %d 次)\n",
+ WSTOPSIG(childexitstat), i+1);
+ }
+ kill(pidcopy, SIGCONT);
+ }else{
+ if(WIFSIGNALED(childexitstat)){
+ clock_gettime(CLOCK_REALTIME, &execfinish);
+ childterminated = 1;
+ toreturn->judge_signal = WTERMSIG(childexitstat);
+ if(toreturn->judge_signal == SIGXFSZ){
+ toreturn->judge_result = SCTRES_OLE;
+ }else{
+ toreturn->judge_result = SCTRES_RE;
+ }
+ toreturn->judge_exitcode = WEXITSTATUS(childexitstat);
+ break;
+ }
+ if(WIFEXITED(childexitstat)){
+ clock_gettime(CLOCK_REALTIME, &execfinish);
+ childterminated = 1;
+ toreturn->judge_result = SCTRES_OK;
+ toreturn->judge_exitcode = WEXITSTATUS(childexitstat);
+ break;
+ }
+ }
+ }
+ if(!childterminated){
+ kill(pidcopy, SIGKILL);
+ wait4(pidcopy, &childexitstat, WUNTRACED, &toreturn->judge_rusage);
+ toreturn->judge_result = SCTRES_SLE;
+ toreturn->judge_exitcode = WEXITSTATUS(childexitstat);
+ clock_gettime(CLOCK_REALTIME, &execfinish);
+ }
+ pthread_mutex_lock(&judge_tle_mx);
+ if(judge_tle && toreturn->judge_result == SCTRES_RE){
+ toreturn->judge_result = SCTRES_TLE;
+ }
+ pthread_mutex_unlock(&judge_tle_mx);
+ difftimespec(&execstart, &execfinish, &toreturn->judge_time);
+ if(break_flag){
+ toreturn->judge_result = SCTRES_AB;
+ }
+ sctjudge_makechild_cleanup_p1();
+ if(mcopt.flags & SCTMC_VERBOSE){
+ /* XXX 有更好的方法洗掉文字嗎? */
+ fputs("\r \r"
+ , stdout);
+ }
+ }else{
+ /* 子程序
+ * 由此開始要很小心,因為我們是在 thread 裡面 fork (?) */
+ close(childpipe[0]);
+ /* close-on-exec 用來關閉這個暫時的資料傳輸通道 */
+ fcntl(childpipe[1], F_SETFD, fcntl(childpipe[1],F_GETFD) | FD_CLOEXEC);
+
+ /* 設為獨立的 process group,不過失敗就算了,這不重要 */
+ setpgid(getpid(), getpid());
+
+ /* 重設所有 signal handler
+ * XXX 我猜 signal 最大值是 32,還有其他更好的寫法嗎? */
+ for(i=1; i<=32; i++){
+ signal(i, SIG_DFL);
+ }
+
+#ifndef HAVE_CONF_CAP
+ disable_setuid();
+#endif
+
+ fdinput = open(mcopt.inputfile, O_RDONLY);
+ if(fdinput < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_INPUT);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_INPUT);
+ }
+ if(dup2(fdinput, STDIN_FILENO) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_INPUT);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_INPUT);
+ }
+
+ /* 注意:開啟輸出檔會直接覆寫現存檔案! */
+ fdoutput = open(mcopt.outputfile, O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ if(fdoutput < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_OUTPUT);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_OUTPUT);
+ }
+ if(dup2(fdoutput, STDOUT_FILENO) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_OUTPUT);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_OUTPUT);
+ }
+
+#ifndef HAVE_CONF_CAP
+ enable_setuid();
+#endif
+ fchown(fdoutput, getuid(), -1);
+
+ /* 再來就是 stderr 了 */
+ if(mcopt.flags & SCTMC_REDIR_STDERR){
+ if(dup2(fdoutput, STDERR_FILENO) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_STDERR);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_STDERR);
+ }
+ }else{
+ fderr = open(NULL_DEVICE, O_RDONLY);
+ if(fderr < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_STDERR);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_STDERR);
+ }
+ if(dup2(fderr, STDERR_FILENO) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_STDERR);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_STDERR);
+ }
+ }
+
+ /* 很暴力地把所有不該留著的 fd 關掉 */
+ for(i=3; i<fdtablesize; i++){
+ if(i != childpipe[1]){
+ close(i);
+ }
+ }
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CLOSE_OTHERFD);
+
+ /* 有人要求我要 chroot 嗎? */
+ if(mcopt.chrootdir != NULL){
+ if(chroot(mcopt.chrootdir) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_CHROOT);
+ exit(1);
+ }else{
+ chdir("/");
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CHROOT);
+ }
+ }
+
+
+#ifdef HAVE_CONF_CAP
+ /* 確保修改身份的時候 capabilities 仍然留著
+ * 等一下再自己把 capabilities 丟掉 */
+ prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
+#endif
+
+ /* 我要 setgid 嗎? */
+ if(mcopt.flags & SCTMC_SETGID){
+ if(setregid(mcopt.gid, mcopt.gid) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETGID);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETGID);
+ }
+ if(setgroups(1, &mcopt.gid) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETGROUPS);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETGROUPS);
+ }
+ }
+
+ /* 我要 setuid 嗎? */
+ if(mcopt.flags & SCTMC_SETUID){
+ if(setreuid(mcopt.uid, mcopt.uid) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETUID);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETUID);
+ }
+ }
+#ifndef HAVE_CONF_CAP
+ else{
+ /* 確保 setuid 可執行檔所造成的 effective UID 改變不會傳給子程序 */
+ setuid(getuid());
+ }
+#endif
+
+ /* 開始設定資源限制 */
+ if(mcopt.memlimit > 0){
+ childressize = mcopt.memlimit * 1024 * 1024;
+ childres.rlim_cur = childressize;
+ childres.rlim_max = childressize;
+ if(setrlimit(RLIMIT_AS, &childres) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_VM);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_VM);
+ }
+ }
+
+ if(mcopt.outlimit > 0){
+ childressize = mcopt.outlimit * 1024 * 1024;
+ childres.rlim_cur = childressize;
+ childres.rlim_max = childressize;
+ if(setrlimit(RLIMIT_FSIZE, &childres) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_FSIZE);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_FSIZE);
+ }
+ }
+
+ /* 從現在開始,不准再開檔案了 */
+ childres.rlim_cur = 0;
+ childres.rlim_max = 0;
+ if(setrlimit(
+#ifdef RLIMIT_NOFILE
+ RLIMIT_NOFILE
+#else
+ RLIMIT_OFILE
+#endif
+ , &childres) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_OPENFILE);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_OPENFILE);
+ }
+
+ /* 從現在開始,不可以再產生新程序了 */
+ if(setrlimit(RLIMIT_NPROC, &childres) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_PROC);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_PROC);
+ }
+
+
+ /* 非 Linux 系統就到此結束了,可以 exec 了 */
+
+#ifdef HAVE_CONF_CAP
+ /* 正在丟掉 capabilities */
+ cap_t nocap = cap_init();
+ cap_clear_flag(nocap, CAP_EFFECTIVE);
+ cap_clear_flag(nocap, CAP_INHERITABLE);
+ cap_clear_flag(nocap, CAP_PERMITTED);
+ if(cap_set_proc(nocap) < 0){
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_CAPSET);
+ exit(1);
+ }else{
+ SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CAPSET);
+ }
+ /* 需要 drop bound 嗎?再考慮看看 */
+#endif
+
+ if(mcopt.chrootdir == NULL){
+ execl(mcopt.executable, mcopt.executable, NULL);
+ }else{
+ if(mcopt.flags & SCTMC_NOCOPY){
+ execl(mcopt.executable, mcopt.executable, NULL);
+ }else{
+ execl(execshortname, execshortname, NULL);
+ }
+ }
+
+ SCTCHILD_WRITE_FAILMSG(SCTCHILD_EXEC);
+ exit(2);
+ }
+ /* Judge 完成,準備離開 */
+ fflush(stdout);
+ sctjudge_makechild_cleanup_p2(&mcopt, chrdir_oldmode, execdestname);
+ fflush(stdout);
+ (toreturn->mc_exitcode) = SCTMCRVAL_SUCCESS;
+ return (void*)toreturn;
+}
diff --git a/src/version.h.in b/src/version.h.in
new file mode 100644
index 0000000..ad2713b
--- /dev/null
+++ b/src/version.h.in
@@ -0,0 +1,4 @@
+#define SCTJUDGE_NAME "@PROGRAM_NAME@"
+#define SCTJUDGE_VERSION "@PROGRAM_VERSION@"
+#define SCTJUDGE_DATE "@PROGRAM_DATE@"
+#define SCTJUDGE_TITLEBAR SCTJUDGE_NAME" "SCTJUDGE_VERSION" ("SCTJUDGE_DATE")"