aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--INSTALL7
-rw-r--r--README20
-rw-r--r--configure.ac14
-rw-r--r--src/CliCommon.h18
-rw-r--r--src/CliMain.c74
-rw-r--r--src/JudgeCommon.h48
-rw-r--r--src/JudgeMain.c85
-rw-r--r--src/Makefile.am7
-rw-r--r--src/ProcCheckTime.c (renamed from src/checktle.c)53
-rw-r--r--src/ProcCommon.h121
-rw-r--r--src/ProcMain.c1634
-rw-r--r--src/ProcMonitor.c (renamed from src/disptime.c)15
-rw-r--r--src/SctCommon.c (renamed from src/common.c)80
-rw-r--r--src/SctCommon.h88
-rw-r--r--src/SctConst.h11
-rw-r--r--src/SctMain.c645
-rw-r--r--src/SctVersion.h.in (renamed from src/version.h.in)0
-rw-r--r--src/common.h24
-rw-r--r--src/config2.h14
-rw-r--r--src/main.c456
-rw-r--r--src/mkchild.c711
-rw-r--r--src/sctcore.h90
23 files changed, 2879 insertions, 1345 deletions
diff --git a/.gitignore b/.gitignore
index bf6f29f..91d8e2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@
*.so
*.gz
*.swp
+tags
aclocal.m4
autom4te.cache
autoscan.log
@@ -22,14 +23,16 @@ configure.scan
depcomp
missing
install-sh
+/.cproject
+/.project
/Makefile
/Makefile.in
/sctjudge.spec
/src/Makefile
/src/Makefile.in
-/src/config.h
-/src/config.h.in
-/src/version.h
+/src/SctConfig.h
+/src/SctConfig.h.in
+/src/SctVersion.h
/src/stamp-h1
/src/sctjudge
/l4basic/l4arg/Makefile
diff --git a/INSTALL b/INSTALL
index 86e4500..cd7d1c0 100644
--- a/INSTALL
+++ b/INSTALL
@@ -18,9 +18,4 @@
=============================================================================
-以下列出 configure 而可用的選項(也可用 ./configure --help 查到)
- --enable-debug 停用 -O2 選項並加上 -g (但如果你有指定 CFLAGS 就沒效了)
- --enable-static 靜態連結可執行檔
- --enable-procmon 使用 Linux 下 /proc 提供的資訊監控程序
- --enable-cap 使用 Linux capability 支援(而不使用 setuid)
-
+可用的選項,請參考 ./configure --help 的輸出!
diff --git a/README b/README
index 180d264..85eaa8c 100644
--- a/README
+++ b/README
@@ -11,8 +11,26 @@ Simple Common Test Judge
* Linux 下的程序監視器 (0.8)
* 判斷 PE 的能力
* 使用 pipe 取得輸出,而不依賴輸出檔
- * 較易操作的使用者界面,例如 TUI,但不需要 GUI
+ * 較易操作的使用者界面,例如 TUI 和 GUI
使用注意事項:因為受測程式在執行時,式無法開啟任何額外函式庫檔案的。因此編譯受
測程式時,請加上 -static 來編譯成可獨立執行的可執行檔,否則無法得到正確的結果!
(可能需要安裝額外的套件才能編譯成靜態的執行檔,例如說 glibc-static 這類的套件)
+
+=============================================================================
+
+程式(預計)命名規則:
+ * 檔名 Sct 開頭 -> 通用函式,沒有特定命名規則
+ * 檔名 Judge 開頭 -> 最重要的程式,與檢測相關,函式以 sctjudge_ 開頭
+ * 檔名 Proc 開頭 -> 與子程序相關,函式以 sctproc_ 開頭
+ * 含有 Main 的檔案或函式,表示該類型函式的進入點
+ * 目前計畫中的 UI 有 Cli、Tui、Gtk 三種,以後看能不能再加
+
+程式(預計)架構:
+ Sct -> UI -> Judge -> Proc
+
+函式呼叫順序:
+ main -> UI -> sctjudge_main -> sctproc_main
+
+基本原則:
+ 下層的函式不需要與不會知道上層的資料,上層的函式可以會需要下層的資料
diff --git a/configure.ac b/configure.ac
index 3309e70..12f6e77 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,8 +4,8 @@
AC_INIT([sctjudge], [1.0], [lantw44@gmail.com])
AM_INIT_AUTOMAKE([foreign -Wall])
AM_SILENT_RULES([yes])
-AC_CONFIG_SRCDIR([src/main.c])
-AC_CONFIG_HEADERS([src/config.h])
+AC_CONFIG_SRCDIR([src/SctMain.c])
+AC_CONFIG_HEADERS([src/SctConfig.h])
AC_CONFIG_FILES([
Makefile
sctjudge.spec
@@ -13,7 +13,7 @@ AC_CONFIG_FILES([
l4basic/l4bds/Makefile
l4basic/l4darr/Makefile
src/Makefile
- src/version.h
+ src/SctVersion.h
])
releasedate="2013-01-27"
@@ -222,6 +222,14 @@ if test x"${opt_procmon}" = xyes; then
;;
esac
fi
+
+case "${opt_procmon}" in
+ linux|freebsd|yes|no|guess)
+ ;;
+ *)
+ AC_MSG_ERROR([--enable-procmon="${opt_procmon}" is invalid])
+ ;;
+esac
if test x"${opt_procmon}" = xlinux || test x"${opt_procmon}" = xguess; then
diff --git a/src/CliCommon.h b/src/CliCommon.h
new file mode 100644
index 0000000..27e0502
--- /dev/null
+++ b/src/CliCommon.h
@@ -0,0 +1,18 @@
+#ifndef SCTJUDGE_CLI_COMMON
+#define SCTJUDGE_CLI_COMMON
+
+#include "ProcCommon.h"
+#include "JudgeCommon.h"
+
+#include <stdarg.h>
+
+/* --- CliMain.c --- */
+void sctcli_main(void*, JUDGEINFO*);
+void sctcli_logger(int, const char*, ...);
+void sctcli_err(const char*, ...);
+void sctcli_normal_backend(void*, const char*, va_list);
+void sctcli_debug_backend(void*, const char*, va_list);
+void sctcli_err_backend(void*, const char*, va_list);
+void sctcli_monitor(void*, const PROCMONINFO*);
+
+#endif
diff --git a/src/CliMain.c b/src/CliMain.c
new file mode 100644
index 0000000..050d947
--- /dev/null
+++ b/src/CliMain.c
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+# include "SctConfig.h"
+#endif
+
+#include "SctVersion.h"
+#include "ProcCommon.h"
+#include "JudgeCommon.h"
+#include "CliCommon.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+static int sctcli_verbose;
+
+/* TODO: 實作 configfile 的功能 */
+void sctcli_main(void* configfile, JUDGEINFO* sjdata){
+ sctcli_verbose = sjdata->procinfo.i_verbose;
+ sjdata->procinfo.i_func_arg = NULL;
+ sjdata->procinfo.f_debug = sctcli_debug_backend;
+ sjdata->procinfo.f_normal = sctcli_normal_backend;
+ sjdata->procinfo.f_err = sctcli_err_backend;
+ sjdata->procinfo.f_monitor = sctcli_monitor;
+ if(configfile == NULL){
+ sctcli_logger(1, "開始執行檢測工作......\n");
+ sctcli_logger(2, "%s: Call sctjudge_main() [Judge function]\n",
+ __func__);
+ if(sctjudge_main(sjdata) < 0){ /* 執行檢測工作 */
+ sctcli_err(NULL, "檢測工作失敗");
+ return;
+ }
+
+ }else{
+ sctcli_err(NULL, "抱歉,設定檔功能尚未實作\n");
+ return;
+ }
+}
+
+void sctcli_logger(int level, const char* format, ...){
+ va_list ap;
+ va_start(ap, format);
+ if(sctcli_verbose >= level){
+ if(level >= 2){
+ sctcli_debug_backend(NULL, format, ap);
+ }else{
+ sctcli_normal_backend(NULL, format, ap);
+ }
+ }
+ va_end(ap);
+}
+
+void sctcli_normal_backend(void* data, const char* format, va_list varlist){
+ vfprintf(stdout, format, varlist);
+}
+
+void sctcli_debug_backend(void* data, const char* format, va_list varlist){
+ fputs("DEBUG: ", stdout);
+ vfprintf(stdout, format, varlist);
+}
+
+void sctcli_err_backend(void* data, const char* format, va_list varlist){
+ fputs("錯誤: ", stderr);
+ vfprintf(stderr, format, varlist);
+}
+
+void sctcli_err(const char* format, ...){
+ va_list ap;
+ va_start(ap, format);
+ sctcli_err_backend(NULL, format, ap);
+ va_end(ap);
+}
+
+void sctcli_monitor(void* data, const PROCMONINFO* moninfo){
+ return;
+}
diff --git a/src/JudgeCommon.h b/src/JudgeCommon.h
new file mode 100644
index 0000000..19b4eef
--- /dev/null
+++ b/src/JudgeCommon.h
@@ -0,0 +1,48 @@
+#ifndef SCTJUDGE_JUDGE_COMMON
+#define SCTJUDGE_JUDGE_COMMON
+
+#include "ProcCommon.h"
+
+/* 這裡的東西記得和 ProcCommon.h 與 SctMain.c 連動 */
+#define JUDGEINFO_RESULT_OK PROCINFO_RESULT_OK
+#define JUDGEINFO_RESULT_RE PROCINFO_RESULT_RE
+#define JUDGEINFO_RESULT_TLE PROCINFO_RESULT_TLE
+#define JUDGEINFO_RESULT_OLE PROCINFO_RESULT_OLE
+#define JUDGEINFO_RESULT_SLE PROCINFO_RESULT_SLE
+#define JUDGEINFO_RESULT_AB PROCINFO_RESULT_AB
+#define JUDGEINFO_RESULT_UD PROCINFO_RESULT_UD
+#define JUDGEINFO_RESULT_WA (PROCINFO_RESULT_MAX + 0)
+#define JUDGEINFO_RESULT_PE (PROCINFO_RESULT_MAX + 1)
+#define JUDGEINFO_RESULT_AC (PROCINFO_RESULT_MAX + 2)
+#define JUDGEINFO_RESULT_MAX (PROCINFO_RESULT_MAX + 3)
+
+/* --- JudgeMain.c --- */
+
+/* 全域常數,表示 JUDGEINFO 中 result 的值 */
+extern const char* sctjudge_result_text[][2];
+#define JUDGEINFO_GET_RESULT_ABBR(n) (sctjudge_result_text[(n)][0])
+#define JUDGEINFO_GET_RESULT_TEXT(n) (sctjudge_result_text[(n)][1])
+
+typedef struct sctjudge_judgeinfo{
+ PROCINFO procinfo; /* 交給 Proc 系列函式的參數和結果 */
+ int procmain; /* ProcMain 回傳的東西 */
+ int flag; /* 與 Judge 相關的參數,但不會傳到 Proc */
+ int pedetect; /* 偵測 PE 的方法 */
+ int result; /* Judge 的結果 */
+ /* 未完待續...... (special judge ?) */
+} JUDGEINFO;
+
+/* JUDGEINFO 裡面 judgeflag 的值 */
+#define JUDGEINFO_FLAG_COPY 0x00000001 /* chroot 所以要複製執行檔 */
+
+/* sctjudge_main 的回傳值,0 表示正常 */
+#define SCTJUDGE_JUDGE_EXIT_SUCCESS 0
+#define SCTJUDGE_JUDGE_EXIT_MAX (SCTJUDGE_PROC_EXIT_MAX + 0)
+
+int sctjudge_main(JUDGEINFO*);
+void sctjudge_setdefault(JUDGEINFO*);
+void sctjudge_freestring(JUDGEINFO*);
+void sctjudge_logger(int, const char*, ...);
+void sctjudge_err(const char*, ...);
+
+#endif
diff --git a/src/JudgeMain.c b/src/JudgeMain.c
new file mode 100644
index 0000000..5d42c8a
--- /dev/null
+++ b/src/JudgeMain.c
@@ -0,0 +1,85 @@
+#ifdef HAVE_CONFIG_H
+# include "SctConfig.h"
+#endif
+
+#include "SctCommon.h"
+#include "SctVersion.h"
+#include "JudgeCommon.h"
+#include <stdarg.h>
+
+const char* sctjudge_result_text[JUDGEINFO_RESULT_MAX][2] = {
+ {"OK" , "正常結束"},
+ {"RE" , "執行過程中發生錯誤"},
+ {"TLE", "超過時間限制"},
+ {"OLE", "輸出超過限制"},
+ {"SLE", "暫停次數太多"},
+ {"AB" , "使用者中斷操作"},
+ {"UD" , "未定義的結果"},
+ {"WA" , "錯誤的答案"},
+ {"PE" , "程式大致正確但輸出格式不符"},
+ {"AC" , "已通過檢測"}
+};
+
+static void (*sctjudge_output_debug)(void*, const char*, va_list);
+static void (*sctjudge_output_normal)(void*, const char*, va_list);
+static void (*sctjudge_output_err)(void*, const char*, va_list);
+static void* sctjudge_output_arg;
+static int sctjudge_verbose;
+
+int sctjudge_main(JUDGEINFO* judgeinfo){
+ sctjudge_output_debug = judgeinfo->procinfo.f_debug;
+ sctjudge_output_normal = judgeinfo->procinfo.f_normal;
+ sctjudge_output_err = judgeinfo->procinfo.f_err;
+ sctjudge_output_arg = judgeinfo->procinfo.i_func_arg;
+ sctjudge_verbose = judgeinfo->procinfo.i_verbose;
+
+ /* 列出設定值,僅供偵錯使用 */
+ sctjudge_logger(2, "%s: 列出 JUDGEINFO 中的設定值\n", __func__);
+ sctjudge_logger(2, "%s: flag: COPY [自動複製可執行檔]: %s\n", __func__,
+ DISPLAY_YESNO(judgeinfo->flag, JUDGEINFO_FLAG_COPY));
+ sctjudge_logger(2, "%s: petect: 此功能尚未實作\n", __func__);
+
+ int rval;
+
+ /* 準備執行程式 */
+ sctjudge_logger(1, "開始執行受測程式......\n");
+ sctjudge_logger(2, "%s: Call sctproc_main() [Process creator]\n", __func__);
+ if((rval = sctproc_main(&(judgeinfo->procinfo)))){
+ sctjudge_err("受測程式執行失敗\n");
+ return rval;
+ }
+ return 0;
+}
+
+void sctjudge_setdefault(JUDGEINFO* judgeinfo){
+ sctproc_setdefault(&(judgeinfo->procinfo));
+ judgeinfo->procmain = 0;
+ judgeinfo->pedetect = 0;
+ judgeinfo->result = 0;
+ judgeinfo->flag =
+ JUDGEINFO_FLAG_COPY;
+}
+
+void sctjudge_freestring(JUDGEINFO* judgeinfo){
+ sctproc_freestring(&(judgeinfo->procinfo));
+}
+
+void sctjudge_logger(int level, const char* format, ...){
+ va_list ap;
+ va_start(ap, format);
+ if(sctjudge_verbose >= level){
+ if(level >= 2){
+ (*sctjudge_output_debug)(sctjudge_output_arg, format, ap);
+ }else{
+ (*sctjudge_output_normal)(sctjudge_output_arg, format, ap);
+ }
+ }
+ va_end(ap);
+}
+
+void sctjudge_err(const char* format, ...){
+ va_list ap;
+ va_start(ap, format);
+ (*sctjudge_output_err)(sctjudge_output_arg, format, ap);
+ va_end(ap);
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index b85889a..1c95d9c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,8 +3,11 @@ sctjudge_CFLAGS = -Wall -pipe -pthread -I../l4basic/l4darr -I../l4basic/l4arg\
-D_REENTRANT -D_THREAD_SAFE
sctjudge_LDFLAGS = -L../l4basic/l4darr -L../l4basic/l4arg
sctjudge_LDADD = -ll4arg -ll4darr
-sctjudge_SOURCES = common.c common.h config.h sctcore.h disptime.c checktle.c\
- main.c mkchild.c version.h config2.h
+sctjudge_SOURCES = SctMain.c SctCommon.c SctCommon.h SctVersion.h \
+ ProcMain.c ProcCommon.h ProcCheckTime.c ProcMonitor.c \
+ JudgeMain.c JudgeCommon.h \
+ CliMain.c CliCommon.h \
+ SctConfig.h SctConst.h
if STATIC_EXEC
sctjudge_LDFLAGS += -static
diff --git a/src/checktle.c b/src/ProcCheckTime.c
index 126a5e3..9efbe05 100644
--- a/src/checktle.c
+++ b/src/ProcCheckTime.c
@@ -1,10 +1,10 @@
#ifdef HAVE_CONFIG_H
-# include "config.h"
+# include "SctConfig.h"
#endif
-#include "config2.h"
-#include "common.h"
-#include "sctcore.h"
+#include "SctConst.h"
+#include "SctCommon.h"
+#include "ProcCommon.h"
#include <string.h>
#include <unistd.h>
@@ -13,40 +13,35 @@
#include <pthread.h>
#include <semaphore.h>
-volatile sig_atomic_t break_flag;
+volatile sig_atomic_t sctproc_abort;
static void break_handler(int signo){
- break_flag = 1;
- sem_post(&tlethr);
+ sctproc_abort = 1;
+ sem_post(&checktime_sem);
}
-void* sctjudge_checktle(void* arg){
- pid_t pidcopy;
- long long sleeptime = (long long)(*(int*)arg) * 1000000;
+void* sctproc_checktime(void* arg){
+ PROCINFO* procinfo = arg;
struct sigaction break_catch;
struct timespec timelimit, timeinit, timecur, timeexpire;
+ long long sleeptime = (long long)(procinfo->i_limit_time) * 1000000;
- pthread_mutex_lock(&tkill_mx);
- tkill_yes = 1;
- pthread_mutex_unlock(&tkill_mx);
+ pthread_mutex_lock(&checktime_mutex);
+ checktime_exist = 1;
+ pthread_mutex_unlock(&checktime_mutex);
- sem_wait(&tlethr);
clock_gettime(CLOCK_REALTIME, &timeinit);
#ifndef HAVE_CONF_CAP
enable_setuid();
#endif
- break_flag = 0;
+ sctproc_abort = 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;
checktimespec(&timelimit);
@@ -54,22 +49,22 @@ void* sctjudge_checktle(void* arg){
do{
clock_gettime(CLOCK_REALTIME, &timecur);
timeexpire.tv_sec = timecur.tv_sec;
- timeexpire.tv_nsec = timecur.tv_nsec + SCT_CHECKTLE_INTERVAL;
+ timeexpire.tv_nsec = timecur.tv_nsec + SCTJUDGE_PROC_CHECKTIME_INTERVAL;
checktimespec(&timeexpire);
}while(comparetimespec(&timecur, &timelimit) < 0 &&
- sem_timedwait(&tlethr, &timeexpire));
+ sem_timedwait(&checktime_sem, &timeexpire));
- if(!break_flag){
- pthread_mutex_lock(&judge_tle_mx);
- judge_tle = 1;
- pthread_mutex_unlock(&judge_tle_mx);
+ if(!sctproc_abort){
+ pthread_mutex_lock(&sctproc_tle_mutex);
+ sctproc_tle = 1;
+ pthread_mutex_unlock(&sctproc_tle_mutex);
}
- kill(pidcopy, SIGKILL);
+ kill(child_pid, SIGKILL);
- pthread_mutex_lock(&tkill_mx);
- tkill_yes = 0;
- pthread_mutex_unlock(&tkill_mx);
+ pthread_mutex_lock(&checktime_mutex);
+ checktime_exist = 0;
+ pthread_mutex_unlock(&checktime_mutex);
return NULL;
}
diff --git a/src/ProcCommon.h b/src/ProcCommon.h
new file mode 100644
index 0000000..19aa250
--- /dev/null
+++ b/src/ProcCommon.h
@@ -0,0 +1,121 @@
+#ifndef SCTJUDGE_PROC_COMMON
+#define SCTJUDGE_PROC_COMMON
+
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <l4darr.h>
+
+/* --- ProcCheckTime.c --- */
+extern volatile sig_atomic_t sctproc_abort;
+void* sctproc_checktime(void*);
+
+/* --- ProcMonitor.c --- */
+void* sctproc_monitor(void*);
+typedef struct sctjudge_procmoninfo{
+ unsigned long time;
+ unsigned long cpuuser;
+ unsigned long cpusys;
+ unsigned long vm;
+} PROCMONINFO;
+
+/* --- ProcMain.c --- */
+
+/* 子程序 PID 和對應的 mutex */
+extern pid_t child_pid;
+extern pthread_mutex_t child_mutex;
+
+/* 兩個 thread 的資料 */
+extern pthread_t checktime_thread, monitor_thread;
+extern _Bool checktime_exist, monitor_exist;
+extern pthread_mutex_t checktime_mutex, monitor_mutex;
+
+/* 用來讓另外兩個 thread 卡住的 semaphore */
+extern sem_t checktime_sem, monitor_sem;
+
+/* 判斷有無 TLE,此變數由 sctjudge_checktle 設定 */
+extern _Bool sctproc_tle;
+extern pthread_mutex_t sctproc_tle_mutex;
+
+/* 同時只能有一個函式執行輸出 */
+extern pthread_mutex_t output_mutex;
+
+/* ProcMain 作為參數和回傳的 struct,i 表示輸入,o 表示輸出 */
+typedef struct sctjudge_procinfo{
+ int i_verbose; /* quiet=0, normal=1, debug=2 */
+ char* i_exec; /* 輸入:可執行檔的路徑 */
+ L4DA* i_argv; /* 輸入:執行時的 argv,會取出 char** 指標 */
+ char* i_chroot; /* 輸入:要作為 chroot 的目錄 */
+ char* i_infile; /* 輸入:要作為 stdin 的檔案 */
+ char* i_outfile; /* 輸入:要作為 stdout 的檔案 */
+ char* i_errfile; /* 輸入:要作為 stderr 的檔案 */
+ uid_t i_uid; /* 輸入:要切換成哪個使用者 */
+ gid_t i_gid; /* 輸入:要切換成哪個群組 */
+ unsigned i_flag; /* 輸入:額外的設定值 */
+ unsigned i_limit_time; /* 輸入:執行時間限制 */
+ unsigned i_limit_mem; /* 輸入:虛擬記憶體上限 (RLIMIT_AS) */
+ unsigned i_limit_core; /* 輸入:core 檔案大小上限 (RLIMIT_CORE) */
+ unsigned i_limit_cpu; /* 輸入:CPU 時間上限 (RLIMIT_CPU) */
+ unsigned i_limit_out; /* 輸入:檔案大小上限 (RLIMIT_FSIZE) */
+ unsigned i_limit_file; /* 輸入:檔案開啟數量上限 (RLIMIT_NOFILE) */
+ unsigned i_limit_proc; /* 輸入:process 數量上限 (RLIMIT_NPROC) */
+ unsigned i_limit_stack; /* 輸入:stack 大小上限 (RLIMIT_STACK) */
+ void* i_func_arg; /* 輸入:送入 f_ 系列函式的第一個參數 */
+ int o_result; /* 輸出:檢測結果 */
+ int o_procexit; /* 輸出:child process 結束的回傳值 */
+ int o_signal; /* 輸出:受測程式因哪個 signal 結束 */
+ struct timespec o_runtime; /* 輸出:受測程式執行時間 */
+ struct rusage o_rusage; /* 輸出:child process 的 rusage */
+ void (*f_debug) (void*, const char*, va_list);
+ void (*f_normal) (void*, const char*, va_list);
+ void (*f_err) (void*, const char*, va_list);
+ void (*f_monitor) (void*, const PROCMONINFO*);
+} PROCINFO;
+
+/* PROCINFO 裡面 i_flag 的值 */
+#define PROCINFO_FLAG_FORCE 0x00000001 /* 強制執行 */
+#define PROCINFO_FLAG_DRYRUN 0x00000002 /* 只檢查設定而不執行 */
+#define PROCINFO_FLAG_SECURE_CHECK 0x00000004 /* 檢查所有「建議的」必要選項 */
+#define PROCINFO_FLAG_REDIR_STDERR 0x00000008 /* stderr 也導入輸出檔 */
+#define PROCINFO_FLAG_SETUID 0x00000010 /* 要修改 uid */
+#define PROCINFO_FLAG_SETGID 0x00000020 /* 要修改 gid */
+#define PROCINFO_FLAG_CLOSE_FD 0x00000040 /* 不要導到/dev/null,直接關閉fd*/
+#define PROCINFO_FLAG_LIMIT_MEM 0x00000080
+#define PROCINFO_FLAG_LIMIT_CORE 0x00000100
+#define PROCINFO_FLAG_LIMIT_CPU 0x00000200
+#define PROCINFO_FLAG_LIMIT_OUT 0x00000400
+#define PROCINFO_FLAG_LIMIT_FILE 0x00000800
+#define PROCINFO_FLAG_LIMIT_PROC 0x00001000
+#define PROCINFO_FLAG_LIMIT_STACK 0x00002000
+
+/* PROCINFO 裡面 o_result 的值 */
+#define PROCINFO_RESULT_OK 0
+#define PROCINFO_RESULT_RE 1
+#define PROCINFO_RESULT_TLE 2
+#define PROCINFO_RESULT_OLE 3
+#define PROCINFO_RESULT_SLE 4 /* 暫停次數太多 */
+#define PROCINFO_RESULT_AB 5 /* 使用者中斷 */
+#define PROCINFO_RESULT_UD 6 /* 未定義的東西,這應該不會出現吧 */
+#define PROCINFO_RESULT_MAX 7
+
+/* setproc_main 的回傳值 */
+#define SCTJUDGE_PROC_EXIT_SUCCESS 0
+#define SCTJUDGE_PROC_EXIT_INVALID 2 /* 不合理的設定值 */
+#define SCTJUDGE_PROC_EXIT_INSECURE 3 /* 不安全的設定值 */
+#define SCTJUDGE_PROC_EXIT_REJECT 4 /* 拒絕不符權限的操作 */
+#define SCTJUDGE_PROC_EXIT_FAILED 5 /* 有些操作失敗了 */
+#define SCTJUDGE_PROC_EXIT_MAX 6
+
+int sctproc_main(PROCINFO*);
+void sctproc_setdefault(PROCINFO*);
+void sctproc_freestring(PROCINFO*);
+void sctproc_logger(int, const char*, ...);
+void sctproc_err(const char*, ...);
+
+#endif
diff --git a/src/ProcMain.c b/src/ProcMain.c
new file mode 100644
index 0000000..d28853c
--- /dev/null
+++ b/src/ProcMain.c
@@ -0,0 +1,1634 @@
+#ifdef HAVE_CONFIG_H
+# include "SctConfig.h"
+#endif
+
+#include "SctVersion.h"
+#include "SctConst.h"
+#include "SctCommon.h"
+#include "ProcCommon.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 SCTJUDGE_PROC_CHILD_CLOSE_STDIN 0
+#define SCTJUDGE_PROC_CHILD_OPEN_STDIN_SINK 1
+#define SCTJUDGE_PROC_CHILD_OPEN_STDIN_FILE 2
+#define SCTJUDGE_PROC_CHILD_DUP2_STDIN 3
+#define SCTJUDGE_PROC_CHILD_CLOSE_STDOUT 4
+#define SCTJUDGE_PROC_CHILD_OPEN_STDOUT_SINK 5
+#define SCTJUDGE_PROC_CHILD_OPEN_STDOUT_FILE 6
+#define SCTJUDGE_PROC_CHILD_DUP2_STDOUT 7
+#define SCTJUDGE_PROC_CHILD_CLOSE_STDERR 8
+#define SCTJUDGE_PROC_CHILD_REDIR_STDERR 9
+#define SCTJUDGE_PROC_CHILD_OPEN_STDERR_FILE 10
+#define SCTJUDGE_PROC_CHILD_DUP2_STDERR 11
+#define SCTJUDGE_PROC_CHILD_CLOSE_OTHER_FDS 12
+#define SCTJUDGE_PROC_CHILD_CHROOT 13
+#define SCTJUDGE_PROC_CHILD_SETGID 14
+#define SCTJUDGE_PROC_CHILD_SETGROUPS 15
+#define SCTJUDGE_PROC_CHILD_SETUID 16
+#define SCTJUDGE_PROC_CHILD_RESET_UID 17
+#define SCTJUDGE_PROC_CHILD_CAP_SET_PROC 18
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_MEM 19
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_CORE 20
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_CPU 21
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_OUT 22
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE 23
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_PROC 24
+#define SCTJUDGE_PROC_CHILD_SETRLIMIT_STACK 25
+#define SCTJUDGE_PROC_CHILD_EXEC 26
+#define SCTJUDGE_PROC_CHILD_MAX 27
+
+
+/* 變數用途在 ProcCommon.h 解釋過了 */
+pid_t child_pid;
+pthread_mutex_t child_mutex;
+
+_Bool checktime_exist;
+sem_t checktime_sem;
+pthread_t checktime_thread;
+pthread_mutex_t checktime_mutex;
+
+_Bool monitor_exist;
+sem_t monitor_sem;
+pthread_t monitor_thread;
+pthread_mutex_t monitor_mutex;
+
+_Bool sctproc_tle;
+pthread_mutex_t sctproc_tle_mutex;
+
+pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* 內部供 logger 和 err 使用的變數 */
+static void (*sctproc_output_debug)(void*, const char*, va_list);
+static void (*sctproc_output_normal)(void*, const char*, va_list);
+static void (*sctproc_output_err)(void*, const char*, va_list);
+static void* sctproc_output_arg;
+static int sctproc_verbose;
+
+/* 如果回傳負數表示失敗,就用這個 */
+static void sctproc_childonly_fail_if_negative(int fd, int rval, int workname){
+ int childonly_mesg[2];
+ childonly_mesg[0] = workname;
+ childonly_mesg[1] = (rval < 0) ? errno : 0;
+ write(fd, &childonly_mesg, sizeof(int) * 2);
+ if(rval < 0){
+ exit(1);
+ }
+}
+
+/* 如果回傳 NULL 表示失敗,就用這個 */
+static void sctproc_childonly_fail_if_null(int fd, void* rval, int workname){
+ int childonly_mesg[2];
+ childonly_mesg[0] = workname;
+ childonly_mesg[1] = (rval == NULL) ? errno : 0;
+ write(fd, &childonly_mesg, sizeof(int) * 2);
+ if(rval == NULL){
+ exit(2);
+ }
+}
+
+void sctproc_setdefault(PROCINFO* procinfo){
+ procinfo->i_verbose = 1;
+ procinfo->i_exec = NULL;
+ procinfo->i_argv = NULL;
+ procinfo->i_chroot = NULL;
+ procinfo->i_infile = NULL;
+ procinfo->i_outfile = NULL;
+ procinfo->i_errfile = NULL;
+ procinfo->i_uid = 65535;
+ procinfo->i_gid = 65535;
+ procinfo->i_limit_time = 10 * 10000;
+ procinfo->i_limit_mem = 64 * 1024 * 1024;
+ procinfo->i_limit_core = 0;
+ procinfo->i_limit_cpu = 0;
+ procinfo->i_limit_out = 0;
+ procinfo->i_limit_file = 0;
+ procinfo->i_limit_proc = 0;
+ procinfo->i_limit_stack = 8 * 1024 * 1024;
+ procinfo->i_func_arg = NULL;
+ procinfo->i_flag =
+ PROCINFO_FLAG_SECURE_CHECK |
+ PROCINFO_FLAG_LIMIT_FILE |
+ PROCINFO_FLAG_LIMIT_PROC |
+ PROCINFO_FLAG_LIMIT_CORE;
+}
+
+void sctproc_freestring(PROCINFO* procinfo){
+ int i;
+
+ if(procinfo->i_exec != NULL){
+ free(procinfo->i_exec);
+ procinfo->i_exec = NULL;
+ }
+
+ if(procinfo->i_argv != NULL){
+ for(i=0; i<l4da_getlen(procinfo->i_argv); i++){
+ if(l4da_v(procinfo->i_argv, char*, i) != NULL){
+ free(l4da_v(procinfo->i_argv, char*, i));
+ }
+ }
+ l4da_free(procinfo->i_argv);
+ procinfo->i_argv = NULL;
+ }
+
+ if(procinfo->i_chroot != NULL){
+ free(procinfo->i_chroot);
+ procinfo->i_chroot = NULL;
+ }
+
+ if(procinfo->i_infile != NULL){
+ free(procinfo->i_infile);
+ procinfo->i_infile = NULL;
+ }
+ if(procinfo->i_outfile != NULL){
+ free(procinfo->i_outfile);
+ procinfo->i_outfile = NULL;
+ }
+ if(procinfo->i_errfile != NULL){
+ free(procinfo->i_errfile);
+ procinfo->i_chroot = NULL;
+ }
+}
+
+int sctproc_main(PROCINFO* procinfo){
+
+ /* private defines */
+#define private_BUFMAX 256
+#define private_READLEN ((sizeof(int))*2)
+
+ sctproc_output_debug = procinfo->f_debug;
+ sctproc_output_normal = procinfo->f_normal;
+ sctproc_output_err = procinfo->f_err;
+ sctproc_output_arg = procinfo->i_func_arg;
+ sctproc_verbose = procinfo->i_verbose;
+
+ int i;
+
+ int rerr; /* 接收錯誤碼的地方 */
+ char errbuf[private_BUFMAX]; /* 系統錯誤訊息緩衝區 */
+
+ int child_pipe[2]; /* 接收 child process 狀態用的 pipe */
+ int child_mesg[2]; /* child process 傳來的訊息,[0]是動作,[1]是 errno */
+ int child_status; /* wait 時回傳的 child process 狀態 */
+ _Bool child_terminated;/* child process 終止了沒? */
+ char* child_workstr; /* 指向 child process 目前狀態訊息的指標 */
+
+ struct timespec exec_begin;
+ struct timespec exec_end;
+
+ /* 一些 child process 中專用的變數 */
+ int childonly_fdopt;
+ int childonly_fdcount;
+ int childonly_fdin;
+ int childonly_fdout;
+ int childonly_fderr;
+ pid_t childonly_pid;
+ struct rlimit childonly_rlimit;
+#ifdef HAVE_CONF_CAP
+ cap_t childonly_cap;
+#endif
+
+ /* 列出設定值,僅供偵錯使用 */
+ sctproc_logger(2, "%s: 列出 PROCINFO 中的設定值\n", __func__);
+ sctproc_logger(2, "%s: i_exec: %s\n",
+ __func__, STRING_NULL(procinfo->i_exec));
+ for(i=0; l4da_v(procinfo->i_argv, char*, i) != NULL; i++){
+ sctproc_logger(2, "%s: i_argv[%d]: %s\n",
+ __func__, i, STRING_NULL(l4da_v(procinfo->i_argv, char*, i)));
+ }
+ sctproc_logger(2, "%s: i_chroot: %s\n",
+ __func__, STRING_NULL(procinfo->i_chroot));
+ sctproc_logger(2, "%s: i_infile: %s\n",
+ __func__, STRING_NULL(procinfo->i_infile));
+ sctproc_logger(2, "%s: i_outfile: %s\n",
+ __func__, STRING_NULL(procinfo->i_outfile));
+ sctproc_logger(2, "%s: i_errfile: %s\n",
+ __func__, STRING_NULL(procinfo->i_errfile));
+ sctproc_logger(2, "%s: i_uid: %u\n", __func__, procinfo->i_uid);
+ sctproc_logger(2, "%s: i_gid: %u\n", __func__, procinfo->i_gid);
+ sctproc_logger(2, "%s: i_flag: FORCE: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_FORCE));
+ sctproc_logger(2, "%s: i_flag: DRYRUN: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_DRYRUN));
+ sctproc_logger(2, "%s: i_flag: SECURE_CHECK: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_SECURE_CHECK));
+ sctproc_logger(2, "%s: i_flag: REDIR_STDERR: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_REDIR_STDERR));
+ sctproc_logger(2, "%s: i_flag: SETUID: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_SETUID));
+ sctproc_logger(2, "%s: i_flag: SETGID: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_SETGID));
+ sctproc_logger(2, "%s: i_flag: CLOSE_FD: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_CLOSE_FD));
+ sctproc_logger(2, "%s: i_flag: LIMIT_MEM: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_MEM));
+ sctproc_logger(2, "%s: i_flag: LIMIT_CORE: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_CORE));
+ sctproc_logger(2, "%s: i_flag: LIMIT_CPU: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_CPU));
+ sctproc_logger(2, "%s: i_flag: LIMIT_OUT: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_OUT));
+ sctproc_logger(2, "%s: i_flag: LIMIT_FILE: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_FILE));
+ sctproc_logger(2, "%s: i_flag: LIMIT_PROC: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_PROC));
+ sctproc_logger(2, "%s: i_flag: LIMIT_STACK: %s\n", __func__,
+ DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_STACK));
+ sctproc_logger(2, "%s: i_limit_time: %u\n",
+ __func__, procinfo->i_limit_time);
+ sctproc_logger(2, "%s: i_limit_mem: %u\n",
+ __func__, procinfo->i_limit_mem);
+ sctproc_logger(2, "%s: i_limit_core: %u\n",
+ __func__, procinfo->i_limit_core);
+ sctproc_logger(2, "%s: i_limit_cpu: %u\n",
+ __func__, procinfo->i_limit_cpu);
+ sctproc_logger(2, "%s: i_limit_out: %u\n",
+ __func__, procinfo->i_limit_out);
+ sctproc_logger(2, "%s: i_limit_file: %u\n",
+ __func__, procinfo->i_limit_file);
+ sctproc_logger(2, "%s: i_limit_proc: %u\n",
+ __func__, procinfo->i_limit_proc);
+ sctproc_logger(2, "%s: i_limit_stack: %u\n",
+ __func__, procinfo->i_limit_stack);
+
+ /* 檢查 UID == 0 的狀況 */
+ sctproc_logger(2, "%s: Check for user ID setting\n", __func__);
+ if(procinfo->i_uid == 0 && (procinfo->i_flag & PROCINFO_FLAG_SETUID)){
+ if(procrealuid == 0){ /* 我們不用 getuid(),因為可能被 swap 過 */
+ if(!(procinfo->i_flag & PROCINFO_FLAG_FORCE)){
+ sctproc_err("拒絕使用 root 執行受測程式\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }else{
+ sctproc_logger(1,
+ "警告:使用 root 身份執行受測程式,所有限制都可能被忽略\n");
+ }
+ }else{
+ sctproc_err("非超級使用者不能把 UID 設定為 0\n");
+ return SCTJUDGE_PROC_EXIT_REJECT;
+ }
+ }
+
+ /* 檢查安全設定值,如果有要求的話。我們不檢查 chroot 目錄是不是空的,
+ * 因為執行檔應該會複製進去 */
+ if(procinfo->i_flag & PROCINFO_FLAG_SECURE_CHECK){
+ sctproc_logger(2, "%s: Check for security\n", __func__);
+ if(procinfo->i_chroot == NULL){
+ sctproc_err("您沒有指定 chroot 目錄\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }
+ if(!(procinfo->i_flag & PROCINFO_FLAG_SETUID)){
+ sctproc_err("您沒有修改執行受測程式時的使用者\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }
+ if(!(procinfo->i_flag & PROCINFO_FLAG_SETGID)){
+ sctproc_err("您沒有修改執行受測程式時的群組\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }
+ if(!(procinfo->i_flag & PROCINFO_FLAG_LIMIT_MEM)){
+ sctproc_err("您沒有設定記憶體使用量上限\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }
+ if(!(procinfo->i_flag & PROCINFO_FLAG_LIMIT_CORE)){
+ sctproc_err("您沒有設定 core 檔案大小上限\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }
+ if(!(procinfo->i_flag & PROCINFO_FLAG_LIMIT_PROC)){
+ sctproc_err("您沒有設定 process 數量上限\n");
+ return SCTJUDGE_PROC_EXIT_INSECURE;
+ }
+ }
+
+ /* 檢查參數,若強制執行就忽略 */
+ if(!(procinfo->i_flag & PROCINFO_FLAG_FORCE)){
+ sctproc_logger(2, "%s: Validate process configuration\n", __func__);
+ if(procinfo->i_exec == NULL){
+ sctproc_err("沒有指定受測程式可執行檔名稱\n");
+ return SCTJUDGE_PROC_EXIT_INVALID;
+ }
+ if(procinfo->i_argv == NULL){
+ sctproc_err("沒有指定執行程式時的命令列\n");
+ return SCTJUDGE_PROC_EXIT_INVALID;
+ }
+ if(procinfo->i_outfile == NULL){
+ sctproc_err("沒有指定輸出檔案\n");
+ return SCTJUDGE_PROC_EXIT_INVALID;
+ }
+ if(procinfo->i_limit_time <= 0){
+ sctproc_err("沒有指定執行時間限制\n");
+ return SCTJUDGE_PROC_EXIT_INVALID;
+ }
+ if((procinfo->i_flag & PROCINFO_FLAG_REDIR_STDERR) &&
+ (procinfo->i_errfile != NULL)){
+ sctproc_err("重新導向 stderr 時,不能指定 stderr 要導向的檔案\n");
+ return SCTJUDGE_PROC_EXIT_INVALID;
+ }
+ }
+
+
+ /* 產生 child process */
+ sctproc_logger(2, "%s: Creating a pipe for status reporting\n", __func__);
+ pipe(child_pipe);
+ sctproc_logger(2, "%s: Creating a child process\n", __func__);
+ child_pid = fork();
+
+ if(child_pid < 0){
+ rerr = errno;
+ strerror_threadsafe(rerr, errbuf, private_BUFMAX);
+ sctproc_logger(2, "%s: fork() = %d\n", __func__, child_pid);
+ sctproc_logger(2, "%s: errno = %d (%s)\n", __func__, rerr, errbuf);
+ sctproc_err("無法建立新程序:%s\n", errbuf);
+ close(child_pipe[0]);
+ close(child_pipe[1]);
+ return SCTJUDGE_PROC_EXIT_FAILED;
+
+ }else if(child_pid > 0){
+ sctproc_logger(2, "%s: fork() = %d\n", __func__, child_pid);
+ close(child_pipe[1]);
+
+ /* 持續監測 child process 的狀態 */
+ while(read_complete(child_pipe[0], (void*)child_mesg, private_READLEN)
+ == private_READLEN){
+
+ strerror_threadsafe(child_mesg[1], errbuf, private_BUFMAX);
+
+ switch(child_mesg[0]){
+ case SCTJUDGE_PROC_CHILD_CLOSE_STDIN:
+ sctproc_logger(2, "%s: child: close(%d), "
+ "errno = %d (%s)\n",
+ __func__, STDIN_FILENO, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "關閉標準輸入");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_OPEN_STDIN_SINK:
+ sctproc_logger(2, "%s: child: open(%s, O_RDONLY), "
+ "errno = %d (%s)\n",
+ __func__, WITH_NULL, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "開啟輸入檔案 "WITH_NULL);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_OPEN_STDIN_FILE:
+ sctproc_logger(2, "%s: child: open(%s, O_RDONLY), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_infile, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "開啟輸入檔案 %s",
+ procinfo->i_infile);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_DUP2_STDIN:
+ sctproc_logger(2, "%s: child: dup2(<input file>, %d), "
+ "errno = %d (%s)\n",
+ __func__, STDIN_FILENO, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "重新導向標準輸入");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_CLOSE_STDOUT:
+ sctproc_logger(2, "%s: child: close(%d), "
+ "errno = %d (%s)\n",
+ __func__, STDOUT_FILENO, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "關閉標準輸出");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_OPEN_STDOUT_SINK:
+ sctproc_logger(2, "%s: child: open(%s, "
+ "O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), "
+ "errno = %d (%s)\n",
+ __func__, WITH_NULL, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "開啟輸出檔案"WITH_NULL);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_OPEN_STDOUT_FILE:
+ sctproc_logger(2, "%s: child: open(%s, "
+ "O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_outfile, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "開啟輸出檔案 %s",
+ procinfo->i_outfile);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_DUP2_STDOUT:
+ sctproc_logger(2, "%s: child: dup2(<output file>, %d), "
+ "errno = %d (%s)\n",
+ __func__, STDOUT_FILENO, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "重新導向標準輸出");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_CLOSE_STDERR:
+ sctproc_logger(2, "%s: child: close(%d), "
+ "errno = %d (%s)\n",
+ __func__, STDERR_FILENO, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "關閉標準錯誤");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_REDIR_STDERR:
+ sctproc_logger(2, "%s: child: dup2(<output file>, %d), "
+ "errno = %d (%s)\n",
+ __func__, STDERR_FILENO, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "將標準輸出導向標準錯誤");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_OPEN_STDERR_FILE:
+ sctproc_logger(2, "%s: child: open(%s, "
+ "O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_errfile, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "開啟錯誤檔案 %s",
+ procinfo->i_errfile);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_DUP2_STDERR:
+ sctproc_logger(2, "%s: child: dup2(<err file>, %d), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_errfile, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "重新導向標準錯誤");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_CLOSE_OTHER_FDS:
+ sctproc_logger(2, "%s: child: for(i=3; i<getdtablesize();"
+ " i++){ close(i); }, errno = %d (%s)\n",
+ __func__, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "關閉所有其他已開啟的檔案");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_CHROOT:
+ sctproc_logger(2, "%s: child: chroot(\"%s\"), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_chroot, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "切換根目錄至 %s",
+ procinfo->i_chroot);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETGID:
+ sctproc_logger(2, "%s: child: setgid(%u), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_gid, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定 GID 為 %u",
+ procinfo->i_gid);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETGROUPS:
+ sctproc_logger(2, "%s: child: setgroups(1, { %u }), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_gid, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定 supplementary GID 為 "
+ "%u", procinfo->i_gid);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETUID:
+ case SCTJUDGE_PROC_CHILD_RESET_UID:
+ sctproc_logger(2, "%s: child: setuid(%u), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_uid, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定 UID 為 %d\n",
+ procinfo->i_uid);
+ break;
+
+ case SCTJUDGE_PROC_CHILD_CAP_SET_PROC:
+ sctproc_logger(2, "%s: child: cap_set_proc(), "
+ "errno = %d (%s)\n",
+ __func__, child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "Drop Linux capabilites");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_MEM:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_AS, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_mem, procinfo->i_limit_mem,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定記憶體用量限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_CORE:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_CORE, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_core,
+ procinfo->i_limit_core,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定 core dump 大小限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_CPU:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_CPU, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_cpu, procinfo->i_limit_cpu,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定 CPU 時間限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_OUT:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_FSIZE, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_out, procinfo->i_limit_out,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定輸出大小限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_NOFILE, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_file,
+ procinfo->i_limit_file,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定檔案開啟數量限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_PROC:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_NPROC, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_proc,
+ procinfo->i_limit_proc,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定程序數量限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_SETRLIMIT_STACK:
+ sctproc_logger(2, "%s: child: setrlimit(RLIMIT_STACK, "
+ "{ %u, %u }), errno = %d (%s)\n",
+ __func__, procinfo->i_limit_stack,
+ procinfo->i_limit_stack,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "設定 stack 大小限制");
+ break;
+
+ case SCTJUDGE_PROC_CHILD_EXEC:
+ sctproc_logger(2, "%s: child: execv(\"%s\", ...), "
+ "errno = %d (%s)\n",
+ __func__, procinfo->i_exec, procinfo->i_exec,
+ child_mesg[1], errbuf);
+ sprintf_malloc(&child_workstr, "執行受測程式 %s",
+ procinfo->i_exec);
+ break;
+
+ default:
+ sctproc_logger(2, "%s: child: unknown message\n", __func__);
+ sprintf_malloc(&child_workstr, "不明的訊息");
+ }
+
+ if(child_mesg[1]){
+ sctproc_err("子程序傳回錯誤:%s:%s\n", child_workstr, errbuf);
+ free(child_workstr);
+ sctproc_logger(2, "%s: Kill the child process NOW!", __func__);
+ kill(child_pid, SIGKILL);
+ sctproc_logger(2, "%s: Wait for the child process", __func__);
+ waitpid(child_pid, NULL, 0);
+ close(child_pipe[0]);
+ return SCTJUDGE_PROC_EXIT_FAILED;
+
+ }else{
+ free(child_workstr);
+ }
+ }
+
+ /* exec 成功,正式啟動所有 judge 所需的東西 */
+ sctproc_logger(2, "%s: 受測程式已成功執行\n", __func__);
+ clock_gettime(CLOCK_REALTIME, &exec_begin);
+ sctproc_logger(2, "%s: 現在時間是 (timespec) %d.%ld\n", __func__,
+ exec_begin.tv_sec, exec_begin.tv_nsec);
+ close(child_pipe[0]);
+
+ /* 初始化所有同步用的東西 */
+ sctproc_logger(2, "%s: Initialize mutexes and semaphores\n", __func__);
+ pthread_mutex_init(&checktime_mutex, NULL);
+ pthread_mutex_init(&sctproc_tle_mutex, NULL);
+ sem_init(&checktime_sem, 0, 0);
+ if(procinfo->i_verbose > 0){
+ pthread_mutex_init(&monitor_mutex, NULL);
+ sem_init(&monitor_sem, 0, 0);
+ }
+
+ /* 產生 thread,一樣要有 verbose > 0 才有 monitor */
+ sctproc_logger(2, "%s: Create thread: checktime\n", __func__);
+ pthread_create(&checktime_thread, NULL, &sctproc_checktime, procinfo);
+ pthread_mutex_lock(&checktime_mutex);
+ checktime_exist = 1;
+ pthread_mutex_unlock(&checktime_mutex);
+ if(procinfo->i_verbose > 0){
+ sctproc_logger(2, "%s: Create thread: monitor\n", __func__);
+ pthread_create(&monitor_thread, NULL, &sctproc_monitor, procinfo);
+ pthread_mutex_lock(&monitor_mutex);
+ monitor_exist = 1;
+ pthread_mutex_unlock(&monitor_mutex);
+ }
+
+ /* 開始要 wait 了,莫名其妙 stop 的程式我最多只會送
+ * SCTJUDGE_PROC_MAX_STOP_TIMES 次 SIGCONT 給你(避免進入無限迴圈)
+ * 這種奇怪的程式就等著 SLE 吧
+ * (這個常數定義在 SctConst.h,我想這不是很重要的東西)*/
+ for(i=0, child_terminated=0; i<= SCTJUDGE_PROC_MAX_STOP_TIMES; i++){
+ wait4(child_pid, &child_status, WUNTRACED, &procinfo->o_rusage);
+ if(WIFSTOPPED(child_status) && i!=SCTJUDGE_PROC_MAX_STOP_TIMES){
+ sctproc_logger(1, "受測程式因訊號 %d 而暫停,自動傳送 SIGCONT "
+ "(第 %d 次,最多可傳 %d 次)\n", WSTOPSIG(child_status),
+ i+1, SCTJUDGE_PROC_MAX_STOP_TIMES);
+ kill(child_pid, SIGCONT);
+ }else{
+ if(WIFSIGNALED(child_status)){
+ clock_gettime(CLOCK_REALTIME, &exec_end);
+ child_terminated = 1;
+ procinfo->o_signal = WTERMSIG(child_status);
+ if(procinfo->o_signal == SIGXFSZ){
+ procinfo->o_result = PROCINFO_RESULT_OLE;
+ }else{
+ procinfo->o_result = PROCINFO_RESULT_RE;
+ }
+ procinfo->o_procexit = WEXITSTATUS(child_status);
+ break;
+ }
+ if(WIFEXITED(child_status)){
+ clock_gettime(CLOCK_REALTIME, &exec_end);
+ child_terminated = 1;
+ procinfo->o_result = PROCINFO_RESULT_OK;
+ procinfo->o_procexit = WEXITSTATUS(child_status);
+ break;
+ }
+ sctproc_logger(2, "%s: The child process is terminated\n",
+ __func__);
+ }
+ }
+
+ /* 暫停次數超過限制 */
+ if(!child_terminated){
+ sctproc_logger(2, "%s: Sending SIGKILL\n", __func__);
+ kill(child_pid, SIGKILL);
+ wait4(child_pid, &child_status, WUNTRACED, &procinfo->o_rusage);
+ procinfo->o_result = PROCINFO_RESULT_SLE;
+ procinfo->o_procexit = WEXITSTATUS(child_status);
+ clock_gettime(CLOCK_REALTIME, &exec_end);
+ }
+
+
+ /* 通知兩個 thread,請他們結束,也等他們結束 */
+ pthread_mutex_lock(&checktime_mutex);
+ if(checktime_exist){
+ checktime_exist = 0;
+ pthread_mutex_unlock(&checktime_mutex);
+ sctproc_logger(2, "%s: Wait for thread checktime to exit\n",
+ __func__);
+ sem_post(&checktime_sem);
+ pthread_join(checktime_thread, NULL);
+ }else{
+ pthread_mutex_unlock(&checktime_mutex);
+ }
+
+ pthread_mutex_lock(&monitor_mutex);
+ if(monitor_exist){
+ monitor_exist = 0;
+ pthread_mutex_unlock(&monitor_mutex);
+ sctproc_logger(2, "%s: Wait for thread monitor to exit\n",
+ __func__);
+ sem_post(&monitor_sem);
+ pthread_join(monitor_thread, NULL);
+ }else{
+ pthread_mutex_unlock(&monitor_mutex);
+ }
+
+ /* 判斷 TLE 和執行時間 */
+ difftimespec(&exec_begin, &exec_end, &(procinfo->o_runtime));
+ checktimespec(&(procinfo->o_runtime));
+
+ pthread_mutex_lock(&sctproc_tle_mutex);
+ if(sctproc_tle){
+ procinfo->o_result = PROCINFO_RESULT_TLE;
+ }
+ pthread_mutex_unlock(&sctproc_tle_mutex);
+
+ if(sctproc_abort){
+ procinfo->o_result = PROCINFO_RESULT_AB;
+ }
+
+ /* 移除掉所有同步用的東西 */
+ sctproc_logger(2, "%s: Destroy mutexes and semaphores\n", __func__);
+ pthread_mutex_destroy(&child_mutex);
+ pthread_mutex_destroy(&checktime_mutex);
+ pthread_mutex_destroy(&monitor_mutex);
+ pthread_mutex_destroy(&sctproc_tle_mutex);
+ sem_destroy(&checktime_sem);
+ sem_destroy(&monitor_sem);
+
+ }else{
+ /* 這裡是 child process,也是要用到 setuid 的地方
+ * 因為我們在 main() 就已經停用 setuid 了
+ * 所以我們必須在這段中需要的地方啟用他
+ * 如果是用 Linux capabilites 舊部需要做而外處理
+ * 如 */
+
+ close(child_pipe[0]);
+
+ /* close-on-exec 用來關閉這個暫時的資料傳輸通道 */
+ childonly_fdopt = fcntl(child_pipe[1], F_GETFD);
+ fcntl(child_pipe[1], F_SETFD, childonly_fdopt | FD_CLOEXEC);
+
+ /* 設為獨立的 process group,不過失敗就算了,這不重要 */
+ childonly_pid = getpid();
+ setpgid(childonly_pid, childonly_pid);
+
+ /* 重設所有 signal handler
+ * XXX 我猜 signal 最大值是 31,還有其他更好的寫法嗎? */
+ for(i=1; i<=31; i++){
+ signal(i, SIG_DFL);
+ }
+
+
+ /* 開啟或關閉輸入檔案 */
+ if(procinfo->i_infile == NULL &&
+ (procinfo->i_flag & PROCINFO_FLAG_CLOSE_FD)){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ close(STDIN_FILENO),
+ SCTJUDGE_PROC_CHILD_CLOSE_STDIN);
+ }else{
+ if(procinfo->i_infile == NULL){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ childonly_fdin = open(WITH_NULL, O_RDONLY),
+ SCTJUDGE_PROC_CHILD_OPEN_STDIN_SINK);
+ }else{
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ childonly_fdin = open(procinfo->i_infile, O_RDONLY),
+ SCTJUDGE_PROC_CHILD_OPEN_STDIN_FILE);
+ }
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ dup2(childonly_fdin, STDIN_FILENO),
+ SCTJUDGE_PROC_CHILD_DUP2_STDIN);
+ }
+
+ /* 開啟或關閉輸出檔案 */
+ childonly_fdout = -1;
+ if(procinfo->i_outfile == NULL &&
+ (procinfo->i_flag & PROCINFO_FLAG_CLOSE_FD)){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ close(STDOUT_FILENO),
+ SCTJUDGE_PROC_CHILD_CLOSE_STDOUT);
+ }else{
+ if(procinfo->i_outfile == NULL){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ childonly_fdout = open(WITH_NULL,
+ O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR),
+ SCTJUDGE_PROC_CHILD_OPEN_STDOUT_SINK);
+ }else{
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ childonly_fdout = open(procinfo->i_outfile,
+ O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR),
+ SCTJUDGE_PROC_CHILD_OPEN_STDOUT_FILE);
+ }
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ dup2(childonly_fdout, STDOUT_FILENO),
+ SCTJUDGE_PROC_CHILD_DUP2_STDOUT);
+ }
+
+ /* 開啟或關閉標準錯誤檔案 */
+ if(procinfo->i_errfile == NULL){
+ if(procinfo->i_flag & PROCINFO_FLAG_CLOSE_FD){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ close(STDERR_FILENO),
+ SCTJUDGE_PROC_CHILD_CLOSE_STDERR);
+ }
+ if(procinfo->i_flag & PROCINFO_FLAG_REDIR_STDERR &&
+ childonly_fdout >= 0){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ dup2(childonly_fdout, STDERR_FILENO),
+ SCTJUDGE_PROC_CHILD_REDIR_STDERR);
+ }
+ }else{
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ childonly_fderr = open(procinfo->i_errfile,
+ O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR),
+ SCTJUDGE_PROC_CHILD_OPEN_STDERR_FILE);
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ dup2(childonly_fderr, STDERR_FILENO),
+ SCTJUDGE_PROC_CHILD_DUP2_STDERR);
+ }
+
+ /* 很暴力地把所有不該留著的 fd 關掉 */
+ childonly_fdcount = getdtablesize();
+ for(i=3; i<childonly_fdcount; i++){
+ close(i);
+ }
+ child_mesg[0] = SCTJUDGE_PROC_CHILD_CLOSE_OTHER_FDS;
+ child_mesg[1] = 0;
+ write(child_pipe[1], &child_mesg, sizeof(int) * 2);
+
+ /* 接下來的操作可能需要提升的權限 */
+#ifndef HAVE_CONF_CAP
+ enable_setuid();
+#endif
+
+ /* 進行 chroot */
+ if(procinfo->i_chroot != NULL){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ chroot(procinfo->i_chroot),
+ SCTJUDGE_PROC_CHILD_CHROOT);
+ chdir("/");
+ }
+
+#ifdef HAVE_CONF_CAP
+ /* 確保修改身份的時候 capabilities 仍然留著
+ * 等一下再自己把 capabilities 丟掉 */
+ prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
+#endif
+
+ /* 我要 setgid 嗎? */
+ if(procinfo->i_flag & PROCINFO_FLAG_SETGID){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setgid(procinfo->i_gid),
+ SCTJUDGE_PROC_CHILD_SETGID);
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setgroups(1, &(procinfo->i_gid)),
+ SCTJUDGE_PROC_CHILD_SETGROUPS);
+ }
+
+ /* 我要 setuid 嗎? */
+ if(procinfo->i_flag & PROCINFO_FLAG_SETGID){
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setuid(procinfo->i_uid),
+ SCTJUDGE_PROC_CHILD_SETUID);
+ }
+ else{
+ /* 即使不要求 setuid,也不能讓受測程式拿到 setuid root */
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setuid(procrealuid),
+ SCTJUDGE_PROC_CHILD_RESET_UID);
+ }
+
+
+#ifdef HAVE_CONF_CAP
+ /* 丟掉所有 Linux capabilities */
+ childonly_cap = cap_init();
+ cap_clear_flag(childonly_cap, CAP_EFFECTIVE);
+ cap_clear_flag(childonly_cap, CAP_INHERITABLE);
+ cap_clear_flag(childonly_cap, CAP_PERMITTED);
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ cap_set_proc(childonly_cap),
+ SCTJUDGE_PROC_CHILD_CAP_SET_PROC);
+ /* 需要 drop bound 嗎?再考慮看看 */
+#endif
+
+ /* 至此所有特殊權力都已丟棄 */
+
+
+ /* 開始設定資源限制 */
+
+ /* -- MEM -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_MEM){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_mem;
+ childonly_rlimit.rlim_max = procinfo->i_limit_mem;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(RLIMIT_AS, &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_MEM);
+ }
+
+ /* -- CORE -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_CORE){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_core;
+ childonly_rlimit.rlim_max = procinfo->i_limit_core;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(RLIMIT_CORE, &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_CORE);
+ }
+
+ /* -- CPU -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_CPU){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_cpu;
+ childonly_rlimit.rlim_max = procinfo->i_limit_cpu;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(RLIMIT_CPU, &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_CPU);
+ }
+
+ /* -- OUT -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_OUT){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_out;
+ childonly_rlimit.rlim_max = procinfo->i_limit_out;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(RLIMIT_FSIZE, &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE);
+ }
+
+ /* -- FILE -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_FILE){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_file;
+ childonly_rlimit.rlim_max = procinfo->i_limit_file;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(
+#ifdef RLIMIT_NOFILE
+ RLIMIT_NOFILE
+#else
+ RLIMIT_OFILE
+#endif
+ , &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE);
+ }
+
+ /* -- PROC -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_PROC){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_proc;
+ childonly_rlimit.rlim_max = procinfo->i_limit_proc;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(RLIMIT_NPROC, &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_PROC);
+ }
+
+ /* -- STACK -- */
+ if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_STACK){
+ childonly_rlimit.rlim_cur = procinfo->i_limit_stack;
+ childonly_rlimit.rlim_max = procinfo->i_limit_stack;
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ setrlimit(RLIMIT_STACK, &childonly_rlimit),
+ SCTJUDGE_PROC_CHILD_SETRLIMIT_STACK);
+ }
+
+ /* 一切都完成了,現在要 exec 了! */
+ sctproc_childonly_fail_if_negative(child_pipe[1],
+ execv(procinfo->i_exec, l4da_data(procinfo->i_argv)),
+ SCTJUDGE_PROC_CHILD_EXEC);
+ }
+
+
+ return SCTJUDGE_PROC_EXIT_SUCCESS;
+
+ /* drop private defines */
+#undef private_BUFMAX
+#undef private_READLEN
+
+}
+
+void sctproc_logger(int level, const char* format, ...){
+ va_list ap;
+ va_start(ap, format);
+ if(sctproc_verbose >= level){
+ pthread_mutex_lock(&output_mutex);
+ if(level >= 2){
+ (*sctproc_output_debug)(sctproc_output_arg, format, ap);
+ }else{
+ (*sctproc_output_normal)(sctproc_output_arg, format, ap);
+ }
+ pthread_mutex_unlock(&output_mutex);
+ }
+ va_end(ap);
+}
+
+void sctproc_err(const char* format, ...){
+ va_list ap;
+ va_start(ap, format);
+ pthread_mutex_lock(&output_mutex);
+ (*sctproc_output_err)(sctproc_output_arg, format, ap);
+ pthread_mutex_unlock(&output_mutex);
+ va_end(ap);
+}
+
+#if 0
+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);
+ sem_post(&tlethr);
+ }else{
+ pthread_mutex_unlock(&tkill_mx);
+ }
+
+ pthread_mutex_lock(&tdisplay_mx);
+ if(tdisplay_yes){
+ tdisplay_yes = 0;
+ pthread_mutex_unlock(&tdisplay_mx);
+ sem_post(&dispthr);
+ }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 位元組\n", mcopt->memlimit);
+ }else{
+ puts("無限制");
+ }
+ fputs("\t輸出限制:", stdout);
+ if(mcopt->outlimit > 0){
+ printf("%d 位元組\n", mcopt->outlimit);
+ }else{
+ puts("無限制");
+ }
+ if(mcopt->flags & SCTMC_SETUID){
+ printf("\t設定受測程式執行時的 UID 為 %u\n", mcopt->uid);
+ }else{
+ puts("\t執行受測程式時維持原有的 real UID 和 effective UID");
+ }
+ if(mcopt->flags & SCTMC_SETGID){
+ printf("\t設定受測程式執行時的 GID 為 %u\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);
+
+ /* 宣告變數囉! */
+ 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);
+#ifdef HAVE_WORKING_FORK
+ pidchild = fork();
+#else
+#error "This program requires a working fork function."
+#endif
+ 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 */
+ sem_post(&tlethr);
+ sem_post(&dispthr);
+
+ /* 開始要 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, procrealuid, -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(WITH_NULL, 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(procrealuid);
+ }
+#endif
+
+ /* 開始設定資源限制 */
+ if(mcopt.memlimit > 0){
+ childressize = mcopt.memlimit;
+ 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;
+ 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;
+}
+#endif
diff --git a/src/disptime.c b/src/ProcMonitor.c
index 4f64cd7..05a0315 100644
--- a/src/disptime.c
+++ b/src/ProcMonitor.c
@@ -1,10 +1,10 @@
#ifdef HAVE_CONFIG_H
-# include "config.h"
+# include "SctConfig.h"
#endif
-#include "config2.h"
-#include "common.h"
-#include "sctcore.h"
+#include "SctConst.h"
+#include "SctCommon.h"
+#include "ProcCommon.h"
#include <stdio.h>
#include <string.h>
@@ -12,6 +12,12 @@
#include <pthread.h>
#include <semaphore.h>
+
+void* sctproc_monitor(void* arg){
+ return NULL;
+}
+
+#if 0
void* sctjudge_displaytime(void* arg){
struct timespec timeinit, timecur, timepast;
struct timespec timesleep;
@@ -147,3 +153,4 @@ void* sctjudge_displaytime(void* arg){
return NULL;
}
+#endif
diff --git a/src/common.c b/src/SctCommon.c
index 6e7c550..27019e0 100644
--- a/src/common.c
+++ b/src/SctCommon.c
@@ -1,16 +1,92 @@
#ifdef HAVE_CONFIG_H
-# include "config.h"
+# include "SctConfig.h"
#endif
-#include "common.h"
+#include "SctCommon.h"
#include <time.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
#include <unistd.h>
#include <sys/types.h>
+#include <pthread.h>
+
uid_t procrealuid = 0;
uid_t proceffuid = 0;
+int str_to_u(const char* s, unsigned* u){
+ char* p;
+ unsigned o;
+ errno = 0;
+ o = strtoul(s, &p, 0);
+ if(errno || *p){
+ return -1;
+ }
+ *u = o;
+ return 0;
+}
+
+int read_complete(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;
+}
+
+int strerror_threadsafe(int errnum, char* buf, size_t buflen){
+ static pthread_mutex_t strerror_call_mutex = PTHREAD_MUTEX_INITIALIZER;
+ char* rval;
+ int rlen;
+ pthread_mutex_lock(&strerror_call_mutex);
+ rval = strerror(errnum);
+ rlen = strlen(rval);
+ if(buflen < rlen){
+ pthread_mutex_unlock(&strerror_call_mutex );
+ return -1;
+ }
+ strcpy(buf, rval);
+ pthread_mutex_unlock(&strerror_call_mutex);
+ return 0;
+}
+
+int sprintf_malloc(char** strstore, const char* format, ...){
+ int len;
+ char* newstr;
+ va_list ap;
+
+ va_start(ap, format);
+ len = vsnprintf(NULL, 0, format, ap) + 1;
+ va_end(ap);
+
+ newstr = malloc(len);
+ if(newstr == NULL){
+ return -1;
+ }
+
+ va_start(ap, format);
+ len = vsnprintf(newstr, len, format, ap);
+ va_end(ap);
+
+ *strstore = newstr;
+ return len;
+}
+
void checktimespec(struct timespec* arg){
long tominus;
if(arg->tv_nsec >= 1000000000){
diff --git a/src/SctCommon.h b/src/SctCommon.h
new file mode 100644
index 0000000..a26d5ac
--- /dev/null
+++ b/src/SctCommon.h
@@ -0,0 +1,88 @@
+#ifndef SCTJUDGE_SCT_COMMON
+#define SCTJUDGE_SCT_COMMON
+
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+/* --- SctCommon.c --- */
+
+/* 字串轉整數 wrapper */
+int str_to_u(const char*, unsigned*);
+
+/* 一定要讀到 count 位元組的資料才會回傳,所以只要回傳 < count
+ * 就表示已經讀到 EOF 了 */
+int read_complete(int fd, void* buf, size_t count);
+
+
+
+/* 安全的 strerror */
+int strerror_threadsafe(int, char*, size_t);
+
+/* 會自動配置空間的 sprintf */
+int sprintf_malloc(char**, const char*, ...);
+
+
+/* 將某個 timespec 調整成合理的數值 */
+void checktimespec(struct timespec*);
+
+/* 計算前兩個 timespec 的數值差異,存到第三個參數 */
+void difftimespec(const struct timespec*, const struct timespec*,
+ struct timespec*);
+
+/* 比較兩個 timespec 的大小,使用方法與 strcmp 相同 */
+int comparetimespec(const struct timespec*, const struct timespec*);
+
+
+
+/* 這個一定要先執行,不然預設 uid 就是 0
+ * 注意這函式只應該在 main() 一開始執行,不然就不知道最初的 uid 了! */
+void save_uids(void);
+
+#ifndef HAVE_CONF_CAP
+void disable_setuid(void);
+void enable_setuid(void);
+#endif
+
+/* 避免 disable_setuid() 之後 getuid() 得到錯誤的值,所以用這些變數把最初的
+ * UID 給記下來!*/
+extern uid_t procrealuid;
+extern uid_t proceffuid;
+
+
+
+
+
+/* --- SctMain.c --- */
+
+/* 如果不是 NULL 就 free 掉它 */
+#define FREE_IF_NOT_NULL(p) \
+ if((p) != NULL){ \
+ free(p); \
+ } \
+ (p) = NULL
+
+/* 如果是 NULL 就替換成 "(NULL)" */
+#define STRING_NULL(p) \
+ ((p) == NULL) ? ("(NULL)") : (p)
+
+/* 測試 flag 是 yes 還是 no 並轉成字串 */
+#define DISPLAY_YESNO(flag, value) \
+ (((flag) & (value)) ? ("Yes") : ("No"))
+
+/* sctjudge 的使用者界面選擇 */
+#define SCTJUDGE_UI_CLI 0
+#define SCTJUDGE_UI_TUI 1
+#define SCTJUDGE_UI_GUI 2
+
+/* sctjudge 這個程式的 exit status
+ * 注意,檢測 malloc 失敗的東西已經去掉了 */
+#define SCTJUDGE_EXIT_SUCCESS 0 /* 當然就是正常結束 */
+#define SCTJUDGE_EXIT_SYNTAX 1 /* 解析階段:命令列語法錯誤 */
+#define SCTJUDGE_EXIT_INVALID 2 /* 配置無效或需要強制執行 */
+#define SCTJUDGE_EXIT_INSECURE 3 /* 不安全的設定值 */
+#define SCTJUDGE_EXIT_REJECT 4 /* 拒絕此參數或需要強制執行 */
+#define SCTJUDGE_EXIT_NA 255 /* 尚未實作的功能 */
+
+#endif
diff --git a/src/SctConst.h b/src/SctConst.h
new file mode 100644
index 0000000..1a7a060
--- /dev/null
+++ b/src/SctConst.h
@@ -0,0 +1,11 @@
+/* 這個檔案用來存放人家所說的「程式內定」的常數 (hard coding) */
+
+/* sctproc_checktime 每隔幾奈秒檢查一次時間 */
+#define SCTJUDGE_PROC_CHECKTIME_INTERVAL 128000000
+
+/* sctproc_monitor 每個幾奈秒更新一次程序監視器 */
+#define SCTJUDGE_PROC_MONITOR_INTERVAL 384000000
+
+/* sctproc_main 中,受測程式最多可以暫停幾次 */
+#define SCTJUDGE_PROC_MAX_STOP_TIMES 5
+
diff --git a/src/SctMain.c b/src/SctMain.c
new file mode 100644
index 0000000..05adc52
--- /dev/null
+++ b/src/SctMain.c
@@ -0,0 +1,645 @@
+#ifdef HAVE_CONFIG_H
+# include "SctConfig.h"
+#endif
+
+#include "SctConst.h"
+#include "SctVersion.h"
+#include "SctCommon.h"
+#include "ProcCommon.h"
+#include "JudgeCommon.h"
+#include "CliCommon.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+
+#include <pthread.h>
+#include <semaphore.h>
+
+#ifdef HAVE_CONF_UGIDNAME
+#include <grp.h>
+#include <pwd.h>
+#endif
+
+#include <l4darr.h>
+#include <l4arg.h>
+
+#define SCTJUDGE_CLIARG_CHECK_MISSING \
+ if(++i >= argc){ \
+ fprintf(stderr, "%s: 選項「%s」之後需要一個參數\n", \
+ argv[0], argv[i-1]); \
+ exit(SCTJUDGE_EXIT_SYNTAX); \
+ }
+
+#define SCTJUDGE_CLIARG_CHECK_QARG_MISSING \
+ if(!l4qarg_hasvalue(qarglist[j])){ \
+ fprintf(stderr, "%s: 在「%s」之後缺少一個值\n", \
+ argv[0], qarglist[j].arg_name); \
+ exit(SCTJUDGE_EXIT_SYNTAX); \
+ }
+
+#define SCTJUDGE_CLIARG_CHECK_QARG_EXTRA \
+ if(l4qarg_hasvalue(qarglist[j])){ \
+ fprintf(stderr, "%s: 在「%s」之後並不需要參數\n", \
+ argv[0], qarglist[j].arg_name); \
+ exit(SCTJUDGE_EXIT_SYNTAX); \
+ } \
+
+typedef struct misc_option_flag_and_number {
+ char* id;
+ unsigned* num;
+ unsigned flag;
+} MISCOPT;
+
+int main(int argc, char* argv[]){
+ /* 照理說是 zh_TW.utf8,但考慮作業系統差異還是從環境變數抓
+ * (反正出錯是使用者的事) */
+ setlocale(LC_ALL, "");
+
+ int i, j, k;
+ int verbose = 0; /* 0 是安靜模式(機器模式)、1 是 verbose、2 是 debug */
+ int ui = SCTJUDGE_UI_CLI;
+ _Bool parseok = 0;
+ char* tmpstr;
+ JUDGEINFO sjopt; /* 無設定檔模式下,傳送這個給 CliMain() */
+ L4QARG* qarglist;
+
+#ifdef HAVE_CONF_UGIDNAME
+ struct passwd* pwinfo;
+ struct group* grinfo;
+#endif
+
+ save_uids();
+
+#ifndef HAVE_CONF_CAP
+ /* 即使有 setuid root,還是不能讓每個使用者拿到 root 權限
+ * 所以說只有在 fork 的時候才要把這個權限開起來,其他就關掉吧 */
+ disable_setuid();
+#endif
+
+ /* 定義 -m 時的選項清單
+ * 當然有些特殊的像是 errfile 和 uid/gid 以及 arg 這裡沒辦法 */
+ const MISCOPT miscopt[] = {
+ {"time", &(sjopt.procinfo.i_limit_time), 0},
+ {"timelimit", &(sjopt.procinfo.i_limit_time), 0},
+ {"time-limit", &(sjopt.procinfo.i_limit_time), 0},
+ {"tle", &(sjopt.procinfo.i_limit_time), 0},
+
+ {"stderr", NULL, PROCINFO_FLAG_REDIR_STDERR},
+ {"redir-stderr", NULL, PROCINFO_FLAG_REDIR_STDERR},
+
+ {"secure", NULL, PROCINFO_FLAG_SECURE_CHECK},
+ {"secure-check", NULL, PROCINFO_FLAG_SECURE_CHECK},
+
+ {"mem", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"memory", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"ram", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"as", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"addr-space", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"address-space", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"vm", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+ {"virtual-memory", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM},
+
+ {"core", &(sjopt.procinfo.i_limit_core), PROCINFO_FLAG_LIMIT_CORE},
+ {"coredump", &(sjopt.procinfo.i_limit_core), PROCINFO_FLAG_LIMIT_CORE},
+ {"corefile", &(sjopt.procinfo.i_limit_core), PROCINFO_FLAG_LIMIT_CORE},
+
+ {"cputime", &(sjopt.procinfo.i_limit_cpu), PROCINFO_FLAG_LIMIT_CPU},
+ {"cputime-limit", &(sjopt.procinfo.i_limit_cpu), PROCINFO_FLAG_LIMIT_CPU},
+
+ {"outlimit", &(sjopt.procinfo.i_limit_out), PROCINFO_FLAG_LIMIT_OUT},
+ {"out-limit", &(sjopt.procinfo.i_limit_out), PROCINFO_FLAG_LIMIT_OUT},
+ {"output-limit", &(sjopt.procinfo.i_limit_out), PROCINFO_FLAG_LIMIT_OUT},
+
+ {"openfile", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE},
+ {"fdlimit", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE},
+ {"fdmax", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE},
+ {"filelimit", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE},
+ {"file-limit", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE},
+
+ {"process", &(sjopt.procinfo.i_limit_proc), PROCINFO_FLAG_LIMIT_PROC},
+ {"proc", &(sjopt.procinfo.i_limit_proc), PROCINFO_FLAG_LIMIT_PROC},
+ {"proc-limit", &(sjopt.procinfo.i_limit_proc), PROCINFO_FLAG_LIMIT_PROC},
+
+ {"stack", &(sjopt.procinfo.i_limit_stack), PROCINFO_FLAG_LIMIT_STACK},
+ {"stack-size", &(sjopt.procinfo.i_limit_stack), PROCINFO_FLAG_LIMIT_STACK},
+ {"stack-limit", &(sjopt.procinfo.i_limit_stack), PROCINFO_FLAG_LIMIT_STACK}
+
+ };
+
+
+
+ /* 將無設定檔模式的結構設定為預設值 */
+ sctjudge_setdefault(&sjopt);
+ sjopt.procinfo.i_argv = l4da_create(sizeof(char*), 2);
+ tmpstr = NULL;
+ l4da_pushback(sjopt.procinfo.i_argv, &tmpstr);
+
+ /* 解析命令列的時間到了
+ * 為了方便與安全起見,所有 sjopt 裡面的字串都會用 strdup() 複製一次 */
+ 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"
+ " 顯示更多訊息(第二個 -v 可以顯示所有重要指令執行過"
+ "程)\n\n"
+ " -V/version\n"
+ " 顯示版本資訊並查看可使用的額外功能列表\n\n"
+ " -n/-dryrun\n"
+ " 只列出設定值而不要執行(包含 -v)\n\n"
+ " -f/-force\n"
+ " 忽略警告訊息並強制執行\n\n"
+ " -t/-time <時間>\n"
+ " 受測程式時間限制為<時間>毫秒\n\n"
+ " -e/-exec <檔案>\n"
+ " 指定受測程式可執行檔名稱(務必靜態連結!)\n\n"
+ " -i/-input <檔案>\n"
+ " 指定要導向至受測程式標準輸入的檔案,若未指定則為 "
+ WITH_NULL "\n\n"
+ " -o/-output <檔案>\n"
+ " 指定要導向至受測程式標準輸出的檔案,若未指定則為 "
+ WITH_NULL "\n\n"
+ " -r/-root <目錄>\n"
+ " 受測程式將以<目錄>為根目錄執行,若無此選項則關"
+ "閉 chroot 功能\n\n"
+ " -m/-misc 選項1[=值],選項2[=值],選項3[=值]...\n"
+ " 設定額外參數:\n\n"
+ " mem=<大小>\n"
+ " 設定受測程式記憶體上限為<大小>位元組\n\n"
+ " outlimit=<大小>\n"
+ " 受測程式最多只能輸出<大小>位元組,若未指定則不"
+ "限制\n"
+ " (無限制時請確定有足夠的磁碟空間!)\n\n"
+ " stderr\n"
+ " 將受測程式的標準錯誤也導向至輸出檔。若未指定,"
+ "則只有標準輸\n"
+ " 出會寫入輸出檔案,標準錯誤則被導向至 "
+ WITH_NULL "\n\n"
+ " nocopy\n"
+ " 如果啟用了 chroot 功能,預設情況下本程式會自動"
+ "將檔案複製\n"
+ " 到新的根目錄中,並在結束時自動刪除檔案。\n"
+ " 使用此選項則取消自動複製的功能,但請注意:此時"
+ " file 指定的\n"
+ " 檔案名稱是相對於新的根目錄而不是現在的根目錄!"
+ "\n\n"
+ " uid=<UID>\n"
+ " 受測程式將以 UID 為 <UID> 的使用者身份執行\n\n"
+ " 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], "V") ||
+ !strcmp(&argv[i][1], "version") ||
+ !strcmp(&argv[i][1], "-version")){
+ fputs(SCTJUDGE_ABOUT_STRING, stdout);
+ exit(SCTJUDGE_EXIT_SUCCESS);
+
+ }else if(!strcmp(&argv[i][1], "v") ||
+ !strcmp(&argv[i][1], "verbose")){
+ verbose++;
+ sjopt.procinfo.i_verbose = verbose;
+
+ }else if(!strcmp(&argv[i][1], "d") ||
+ !strcmp(&argv[i][1], "debug")){
+ verbose += 2;
+ sjopt.procinfo.i_verbose = verbose;
+
+ }else if(!strcmp(&argv[i][1], "q") ||
+ !strcmp(&argv[i][1], "quiet") ||
+ !strcmp(&argv[i][1], "silent")){
+ verbose = 0;
+ sjopt.procinfo.i_verbose = verbose;
+
+ }else if(!strcmp(&argv[i][1], "n") ||
+ !strcmp(&argv[i][1], "dryrun")){
+ verbose = (verbose < 1) ? 1 : verbose;
+ sjopt.procinfo.i_flag |= PROCINFO_FLAG_DRYRUN;
+
+ }else if(!strcmp(&argv[i][1], "f") ||
+ !strcmp(&argv[i][1], "force")){
+ sjopt.procinfo.i_flag |= PROCINFO_FLAG_FORCE;
+
+ }else if(!strcmp(&argv[i][1], "t") ||
+ !strcmp(&argv[i][1], "time")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ if(str_to_u(argv[i], &sjopt.procinfo.i_limit_time) < 0){
+ fprintf(stderr, "%s: 「%s」不是正確的時間設定值\n",
+ argv[0], argv[i]);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+
+ }else if(!strcmp(&argv[i][1], "e") ||
+ !strcmp(&argv[i][1], "exec")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ FREE_IF_NOT_NULL(sjopt.procinfo.i_exec);
+ sjopt.procinfo.i_exec = strdup(argv[i]);
+
+ }else if(!strcmp(&argv[i][1], "i") ||
+ !strcmp(&argv[i][1], "in") ||
+ !strcmp(&argv[i][1], "input")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ FREE_IF_NOT_NULL(sjopt.procinfo.i_infile);
+ sjopt.procinfo.i_infile = strdup(argv[i]);
+
+ }else if(!strcmp(&argv[i][1], "o") ||
+ !strcmp(&argv[i][1], "out") ||
+ !strcmp(&argv[i][1], "output")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ FREE_IF_NOT_NULL(sjopt.procinfo.i_outfile);
+ sjopt.procinfo.i_outfile = strdup(argv[i]);
+
+ }else if(!strcmp(&argv[i][1], "r") ||
+ !strcmp(&argv[i][1], "root") ||
+ !strcmp(&argv[i][1], "chroot")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ FREE_IF_NOT_NULL(sjopt.procinfo.i_chroot);
+ sjopt.procinfo.i_chroot = strdup(argv[i]);
+
+ }else if(!strcmp(&argv[i][1], "u") ||
+ !strcmp(&argv[i][1], "ui") ||
+ !strcmp(&argv[i][1], "interface")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ if(!strcmp(argv[i], "c") ||
+ !strcmp(argv[i], "cli") ||
+ !strcmp(argv[i], "cmd") ||
+ !strcmp(argv[i], "cmdln") ||
+ !strcmp(argv[i], "command") ||
+ !strcmp(argv[i], "commandline")){
+ ui = SCTJUDGE_UI_CLI;
+ }else if(!strcmp(argv[i], "t") ||
+ !strcmp(argv[i], "tui") ||
+ !strcmp(argv[i], "text") ||
+ !strcmp(argv[i], "term") ||
+ !strcmp(argv[i], "terminal") ||
+ !strcmp(argv[i], "curses") ||
+ !strcmp(argv[i], "ncurses")){
+ ui = SCTJUDGE_UI_TUI;
+ }else if(!strcmp(argv[i], "g") ||
+ !strcmp(argv[i], "gui") ||
+ !strcmp(argv[i], "graph") ||
+ !strcmp(argv[i], "graphic") ||
+ !strcmp(argv[i], "graphics") ||
+ !strcmp(argv[i], "graphical") ||
+ !strcmp(argv[i], "x") ||
+ !strcmp(argv[i], "xorg") ||
+ !strcmp(argv[i], "xwin") ||
+ !strcmp(argv[i], "xwindow")){
+ ui = SCTJUDGE_UI_GUI;
+ }else{
+ fprintf(stderr, "%s: 不明的使用者界面「%s」\n",
+ argv[0], argv[i]);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+
+ }else if(!strcmp(&argv[i][1], "m") ||
+ !strcmp(&argv[i][1], "misc") ||
+ !strcmp(&argv[i][1], "opt") ||
+ !strcmp(&argv[i][1], "option")){
+ SCTJUDGE_CLIARG_CHECK_MISSING;
+ qarglist = l4qarg_parse(argv[i]);
+ for(j=0; !l4qarg_end(qarglist[j]); j++){
+ parseok = 0;
+ for(k=0; k < sizeof(miscopt) / sizeof(MISCOPT); k++){
+ if(!strcmp(miscopt[k].id, qarglist[j].arg_name)){
+ if(miscopt[k].num == NULL){
+ SCTJUDGE_CLIARG_CHECK_QARG_EXTRA;
+ sjopt.procinfo.i_flag |= miscopt[k].flag;
+ parseok = 1;
+ break;
+ }else{
+ SCTJUDGE_CLIARG_CHECK_QARG_MISSING;
+ sjopt.procinfo.i_flag |= miscopt[k].flag;
+ if(str_to_u(
+ qarglist[j].arg_value, miscopt[k].num) < 0)
+ {
+ fprintf(stderr, "%s: 「%s」並不是整數\n",
+ argv[0], qarglist[j].arg_value);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ parseok = 1;
+ break;
+ }
+ }
+
+ /* 這裡是有加上 no 的選項 */
+ if(strlen(qarglist[j].arg_name) >= 2 &&
+ !strncmp(qarglist[j].arg_name, "no", 2) &&
+ !strcmp((qarglist[j].arg_name)+2, miscopt[k].id)){
+ SCTJUDGE_CLIARG_CHECK_QARG_EXTRA;
+ sjopt.procinfo.i_flag &= ~(miscopt[k].flag);
+ parseok = 1;
+ break;
+ }
+
+ }
+
+ if(parseok){
+ continue;
+ }
+
+ /* copy/nocopy 設在 JUDGEINFO 但不在 PROCINFO */
+ if(!strcmp(qarglist[j].arg_name, "copy")){
+ SCTJUDGE_CLIARG_CHECK_QARG_EXTRA;
+ sjopt.flag |= JUDGEINFO_FLAG_COPY;
+ }else if(!strcmp(qarglist[j].arg_name, "nocopy")){
+ SCTJUDGE_CLIARG_CHECK_QARG_EXTRA;
+ sjopt.flag &= ~(JUDGEINFO_FLAG_COPY);
+ }
+
+ /* 修改 uid / gid 的功能 */
+ else if(!strcmp(qarglist[j].arg_name, "uid") ||
+ !strcmp(qarglist[j].arg_name, "user")){
+ SCTJUDGE_CLIARG_CHECK_QARG_MISSING;
+ sjopt.procinfo.i_flag |= PROCINFO_FLAG_SETUID;
+ if(str_to_u(
+ qarglist[j].arg_value, &sjopt.procinfo.i_uid) < 0){
+#ifdef HAVE_CONF_UGIDNAME
+ pwinfo = getpwnam(qarglist[j].arg_value);
+ if(pwinfo == NULL){
+ fprintf(stderr, "%s: 「%s」不是正確的 UID 或"
+ "使用者名稱\n",
+ argv[0], qarglist[j].arg_value);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ sjopt.procinfo.i_uid = pwinfo->pw_uid;
+#else
+ fprintf(stderr, "%s: 「%s」並不是整數\n",
+ argv[0], qarglist[j].arg_value);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+#endif
+ }
+ }else if(!strcmp(qarglist[j].arg_name, "gid") ||
+ !strcmp(qarglist[j].arg_name, "group")){
+ SCTJUDGE_CLIARG_CHECK_QARG_MISSING;
+ sjopt.procinfo.i_flag |= PROCINFO_FLAG_SETGID;
+ if(str_to_u(
+ qarglist[j].arg_value, &sjopt.procinfo.i_gid) < 0){
+#ifdef HAVE_CONF_UGIDNAME
+ grinfo = getgrnam(qarglist[j].arg_value);
+ if(grinfo == NULL){
+ fprintf(stderr, "%s: 「%s」不是正確的 GID 或"
+ "群組名稱\n",
+ argv[0], qarglist[j].arg_value);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ sjopt.procinfo.i_gid = grinfo->gr_gid;
+#else
+ fprintf(stderr, "%s: 「%s」並不是整數\n",
+ argv[0], qarglist[j].arg_value);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+#endif
+ }
+ }
+
+ /* stderr 也可以指定個別的檔案 */
+ else if(!strcmp(qarglist[j].arg_name, "err") ||
+ !strcmp(qarglist[j].arg_name, "errfile")){
+ SCTJUDGE_CLIARG_CHECK_QARG_MISSING;
+ FREE_IF_NOT_NULL(sjopt.procinfo.i_errfile);
+ sjopt.procinfo.i_errfile =
+ strdup(qarglist[j].arg_value);
+ }
+
+ /* 可以指定程式執行時的參數 */
+ else if(!strcmp(qarglist[j].arg_name, "arg") ||
+ !strcmp(qarglist[j].arg_name, "argv") ||
+ !strcmp(qarglist[j].arg_name, "addarg") ||
+ !strcmp(qarglist[j].arg_name, "appendarg")){
+ SCTJUDGE_CLIARG_CHECK_QARG_MISSING;
+ tmpstr = strdup(qarglist[j].arg_value);
+ l4da_pushback(sjopt.procinfo.i_argv, &tmpstr);
+ }else if(!strcmp(qarglist[j].arg_name, "arg0") ||
+ !strcmp(qarglist[j].arg_name, "argv0")){
+ SCTJUDGE_CLIARG_CHECK_QARG_MISSING;
+ tmpstr = strdup(qarglist[j].arg_value);
+ l4da_v(sjopt.procinfo.i_argv, char*, 0) = tmpstr;
+ }
+ else{
+ fprintf(stderr, "%s: 「%s」是不明的選項\n", argv[0],
+ qarglist[j].arg_name);
+ exit(SCTJUDGE_EXIT_SYNTAX);
+ }
+ }
+ l4qarg_free(qarglist);
+ }else{
+ fprintf(stderr, "%s: 不明的選項「%s」\n"
+ "請嘗試執行 %s -help 來取得說明\n"
+ , argv[0], argv[i], argv[0]);
+ return SCTJUDGE_EXIT_SYNTAX;
+ }
+ }else{
+ fprintf(stderr, "%s: 抱歉,設定檔功能尚未實作\n", argv[0]);
+ return SCTJUDGE_EXIT_NA;
+ }
+ }
+
+ if(l4da_v(sjopt.procinfo.i_argv, char*, 0) == NULL){
+ /* 未明確指定 argv[0] 則直接將執行檔名稱複製過去 */
+ if(sjopt.procinfo.i_exec != NULL){
+ l4da_v(sjopt.procinfo.i_argv, char*, 0) =
+ strdup(sjopt.procinfo.i_exec);
+ }
+ }
+
+ /* argv 尾端必須是 NULL */
+ tmpstr = NULL;
+ l4da_pushback(sjopt.procinfo.i_argv, &tmpstr);
+
+ /* 至此可以進入主程式了 */
+ if(verbose >= 1){
+ puts(SCTJUDGE_TITLEBAR"\n");
+ }
+
+ switch(ui){
+ case SCTJUDGE_UI_CLI:
+ if(verbose >= 2){
+ printf("DEBUG: %s: Call sctcli_main() [Entering CLI mode]\n",
+ __func__);
+ }
+ sctcli_main(NULL, &sjopt);
+ break;
+
+ case SCTJUDGE_UI_TUI:
+ fprintf(stderr, "%s: 抱歉,curses 界面尚未實作\n", argv[0]);
+ exit(SCTJUDGE_EXIT_NA);
+ break;
+
+ case SCTJUDGE_UI_GUI:
+ fprintf(stderr, "%s: 抱歉,圖形化使用者界面尚未實作\n", argv[0]);
+ exit(SCTJUDGE_EXIT_NA);
+ break;
+
+ default:
+ fprintf(stderr, "%s: internal error: invalid ui %d\n",
+ argv[0], ui);
+ exit(SCTJUDGE_EXIT_NA);
+ }
+
+ sctjudge_freestring(&sjopt);
+
+ /**********************************************************************
+ * 以下都要清掉!!!
+ * ********************************************************************/
+
+#if 0
+ /* 檢查與修正設定值 */
+ if(verbose){
+ puts("正在檢查設定值是否正確......");
+ }
+ if(mcopt.executable == NULL){
+ fputs("受測程式可執行檔名稱為必要參數,不可為空白\n", stderr);
+ exit(SCTEXIT_TOOFEW);
+ }
+ if(mcopt.inputfile == NULL){
+ mcopt.inputfile = WITH_NULL;
+ }
+ if(mcopt.outputfile == NULL){
+ fputs("輸出檔案名稱必要參數,不可為空白\n", stderr);
+ exit(SCTEXIT_TOOFEW);
+ }
+ if(mcopt.exectime <= 0){
+ fputs("執行時間限制為必要參數,不可為空白!\n", stderr);
+ exit(SCTEXIT_TOOFEW);
+ }
+ if((mcopt.flags & SCTMC_SETUID)){
+ if(mcopt.uid == 0){
+ if(!force){
+ fputs("將 UID 設為 0 並不安全!(加上 -f 來強制執行)\n",
+ stderr);
+ exit(SCTEXIT_BADID);
+ }else{
+ if(procrealuid != 0){
+ fputs("只有 root 可以將 UID 設定 0\n", stderr);
+ exit(SCTEXIT_BADID);
+ }
+ }
+ }
+ }else{
+ if(procrealuid == 0 && !force){
+ fputs("不允許以 root 身份執行本程式(加上 -f 來強制執行)\n",
+ stderr);
+ exit(SCTEXIT_BADID);
+ }
+ }
+
+ /* 現在開始建立 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(&tlethr, 0, 0);
+ sem_init(&dispthr, 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_displaytime,
+ (void*)&displayinterval);
+ }
+ pthread_attr_destroy(&detstate);
+ pthread_attr_destroy(&joistate);
+ sem_post(&mcthr);
+
+ pthread_join(tmain, (void**)&mtreturn);
+ /* XXX 底下這訊息很有可能讓使用者覺得很奇怪 */
+ pthread_mutex_destroy(&pidmutex);
+ pthread_mutex_destroy(&tkill_mx);
+ pthread_mutex_destroy(&tdisplay_mx);
+
+ sem_destroy(&mcthr);
+ sem_destroy(&tlethr);
+ sem_destroy(&dispthr);
+
+ if(dryrun){
+ return SCTEXIT_SUCCESS;
+ }
+
+ /* 這裡就要顯示結果了 */
+ if(mtreturn == NULL || mtreturn->mc_exitcode != 0){
+ fprintf(stderr, "%s: 因錯誤發生而結束程式\n", argv[0]);
+ free((void*)mtreturn);
+ sem_destroy(&mcthr);
+ exit(SCTEXIT_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);
+ }
+
+#endif
+ return SCTJUDGE_EXIT_SUCCESS;
+}
diff --git a/src/version.h.in b/src/SctVersion.h.in
index 7d03bc4..7d03bc4 100644
--- a/src/version.h.in
+++ b/src/SctVersion.h.in
diff --git a/src/common.h b/src/common.h
deleted file mode 100644
index 5d45377..0000000
--- a/src/common.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#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*);
-void save_uids(void); /* 這個一定要先執行,不然預設 uid 就是 0 */
-
-#ifndef HAVE_CONF_CAP
-void disable_setuid(void);
-void enable_setuid(void);
-#endif
-
-/* 避免 disable_setuid() 之後 getuid() 得到錯誤的值,所以用這些變數把最初的
- * UID 給記下來!*/
-extern uid_t procrealuid;
-extern uid_t proceffuid;
-
-#endif
diff --git a/src/config2.h b/src/config2.h
deleted file mode 100644
index 41ae605..0000000
--- a/src/config2.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/* 你沒有看錯,真的有 config2.h
- * 這個檔案用來存放人家所說的「程式內定」的常數 (hard coding) */
-
-/* 丟給 nanosleep 用的 */
-#define SCT_CHECKTLE_INTERVAL 128000000
-#define SCT_DISPTIME_INTERVAL 384000000
-
-/* sctjudge 這個程式的 exit status */
-#define SCTEXIT_SUCCESS 0 /* 當然就是正常結束 */
-#define SCTEXIT_SYNTAX 1 /* 解析階段:命令列語法錯誤 */
-#define SCTEXIT_TOOFEW 2 /* 檢查階段:少了一些必須的選項 */
-#define SCTEXIT_BADID 3 /* 檢查階段:錯誤或不允許的 uid/gid */
-#define SCTEXIT_THREAD 4 /* 實作階段:thread 回傳錯誤 */
-#define SCTEXIT_MALLOC 5 /* 配置記憶體發生錯誤,這個可能發生在程式任何地方 */
diff --git a/src/main.c b/src/main.c
deleted file mode 100644
index 3b48b18..0000000
--- a/src/main.c
+++ /dev/null
@@ -1,456 +0,0 @@
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include "config2.h"
-#include "version.h"
-#include "common.h"
-#include "sctcore.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <locale.h>
-#include <time.h>
-#include <grp.h>
-#include <pwd.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <sys/types.h>
-
-#include <pthread.h>
-#include <semaphore.h>
-
-#include <l4darr.h>
-#include <l4arg.h>
-
-#define SCTMAIN_ERRNOARG "%s: 選項「%s」之後需要一個參數\n"
-#define SCTMAIN_ERRNOQARG "%s: 在「%s」之後缺少一個值\n"
-#define SCTMAIN_ERREXTRAQARG "%s: 在「%s」之後並不需要參數\n"
-
-#define SCTMAIN_CHECKARGEXIST \
- do{ \
- i++; \
- if(i >= argc){ \
- fprintf(stderr, SCTMAIN_ERRNOARG, argv[0], argv[i-1]); \
- exit(SCTEXIT_SYNTAX); \
- } \
- }while(0)
-
-#define SCTMAIN_CHECKQARGHAVEVALUE \
- do{ \
- if(!l4qarg_hasvalue(qarglist[j])){ \
- fprintf(stderr, SCTMAIN_ERRNOQARG, \
- argv[0], qarglist[j].arg_name); \
- exit(SCTEXIT_SYNTAX); \
- } \
- }while(0)
-
-#define SCTMAIN_CHECKQARGNOVALUE \
- do{ \
- if(l4qarg_hasvalue(qarglist[j])){ \
- fprintf(stderr, SCTMAIN_ERREXTRAQARG, \
- argv[0], qarglist[j].arg_name); \
- exit(SCTEXIT_SYNTAX); \
- } \
- }while(0)
-
-static const char* sctres_text[2][SCTRES_MAX] = {
- {"OK", "RE", "TLE", "OLE", "SLE", "AB", "UD"},
- {
- "正常結束",
- "執行過程中發生錯誤",
- "超過時間限制",
- "輸出超過限制",
- "暫停次數太多",
- "使用者中斷操作",
- "未定義的結果"
- }
-};
-
-int main(int argc, char* argv[]){
- /* 照理說是 zh_TW.utf8,但考慮作業系統差異還是從環境變數抓
- * (反正出錯是使用者的事) */
- setlocale(LC_ALL, "");
-
- int i, j;
- struct makechildopt mcopt; /* 送給 sctjudge_makechild 當參數用 */
- char verbose=0; /* 0 是安靜模式(機器模式)、1 是 verbose、2 是 debug */
- char dryrun=0, force=0;
- L4QARG* qarglist;
-#ifdef HAVE_CONF_UGIDNAME
- struct passwd* pwinfo;
- struct group* grinfo;
-#endif
-
- save_uids();
-
-#ifndef HAVE_CONF_CAP
- /* 即使有 setuid root,還是不能讓每個使用者拿到 root 權限
- * 所以說只有在 fork 的時候才要把這個權限開起來,其他就關掉吧 */
- disable_setuid();
-#endif
-
- /* 預設值當然都是 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"
- " 顯示更多訊息(第二個 -v 可以顯示所有重要指令執行過"
- "程)\n\n"
- " -V/version\n"
- " 顯示版本資訊並查看可使用的額外功能列表\n\n"
- " -n/-dryrun\n"
- " 只列出設定值而不要執行(包含 -v)\n\n"
- " -f/-force\n"
- " 忽略警告訊息並強制執行\n\n"
- " -t/-time <時間>\n"
- " 受測程式時間限制為<時間>毫秒\n\n"
- " -e/-exec <檔案>\n"
- " 指定受測程式可執行檔名稱(務必靜態連結!)\n\n"
- " -i/-input <檔案>\n"
- " 指定要導向至受測程式標準輸入的檔案,若未指定則為 "
- WITH_NULL"\n\n"
- " -o/-output <檔案>\n"
- " 指定要導向至受測程式標準輸出的檔案,若未指定則為 "
- WITH_NULL"\n\n"
- " -r/-root <目錄>\n"
- " 受測程式將以<目錄>為根目錄執行,若無此選項則關"
- "閉 chroot 功能\n\n"
- " -m/-misc 選項1[=值],選項2[=值],選項3[=值]...\n"
- " 設定額外參數:\n\n"
- " mem=<大小>\n"
- " 設定受測程式記憶體上限為<大小>位元組\n\n"
- " outlimit=<大小>\n"
- " 受測程式最多只能輸出<大小>位元組,若未指定則不"
- "限制\n"
- " (無限制時請確定有足夠的磁碟空間!)\n\n"
- " stderr\n"
- " 將受測程式的標準錯誤也導向至輸出檔。若未指定,"
- "則只有標準輸\n"
- " 出會寫入輸出檔案,標準錯誤則被導向至 "
- WITH_NULL"\n\n"
- " nocopy\n"
- " 如果啟用了 chroot 功能,預設情況下本程式會自動"
- "將檔案複製\n"
- " 到新的根目錄中,並在結束時自動刪除檔案。\n"
- " 使用此選項則取消自動複製的功能,但請注意:此時"
- " file 指定的\n"
- " 檔案名稱是相對於新的根目錄而不是現在的根目錄!"
- "\n\n"
- " uid=<UID>\n"
- " 受測程式將以 UID 為 <UID> 的使用者身份執行\n\n"
- " gid=<GID>\n"
- " 受測程式將以 GID 為 <GID> 的群組身份執行\n"
- " 此選項會同時設定 real/effective/supplementary "
- "GID(s)\n"
- " 原有的 supplementry GIDs 會被清空,只剩下這裡指"
- "定的數值\n\n"
- , argv[0]);
- return SCTEXIT_SUCCESS;
- }else if(!strcmp(&argv[i][1], "V") ||
- !strcmp(&argv[i][1], "version") ||
- !strcmp(&argv[i][1], "-version")){
- fputs(SCTJUDGE_ABOUT_STRING, stdout);
- exit(SCTEXIT_SUCCESS);
- }else if(!strcmp(&argv[i][1], "v") ||
- !strcmp(&argv[i][1], "verbose")){
- verbose++;
- mcopt.flags |= SCTMC_VERBOSE;
- if(verbose >= 2){
- mcopt.flags |= SCTMC_DEBUG;
- }
- }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], "f") ||
- !strcmp(&argv[i][1], "force")){
- force = 1;
- mcopt.flags |= (SCTMC_FORCE);
- }else if(!strcmp(&argv[i][1], "t") ||
- !strcmp(&argv[i][1], "time")){
- SCTMAIN_CHECKARGEXIST;
- if(sscanf(argv[i], "%u", &mcopt.exectime) <= 0 ||
- mcopt.exectime <= 0){
- fprintf(stderr, "%s: 「%s」不是正確的時間設定值\n",
- argv[0], argv[i]);
- exit(SCTEXIT_SYNTAX);
- }
- }else if(!strcmp(&argv[i][1], "e") ||
- !strcmp(&argv[i][1], "exec")){
- SCTMAIN_CHECKARGEXIST;
- mcopt.executable = argv[i];
- }else if(!strcmp(&argv[i][1], "i") ||
- !strcmp(&argv[i][1], "in") ||
- !strcmp(&argv[i][1], "input")){
- SCTMAIN_CHECKARGEXIST;
- mcopt.inputfile = argv[i];
- }else if(!strcmp(&argv[i][1], "o") ||
- !strcmp(&argv[i][1], "out") ||
- !strcmp(&argv[i][1], "output")){
- SCTMAIN_CHECKARGEXIST;
- mcopt.outputfile = argv[i];
- }else if(!strcmp(&argv[i][1], "r") ||
- !strcmp(&argv[i][1], "root") ||
- !strcmp(&argv[i][1], "chroot")){
- SCTMAIN_CHECKARGEXIST;
- mcopt.chrootdir = argv[i];
- }else if(!strcmp(&argv[i][1], "m") ||
- !strcmp(&argv[i][1], "misc") ||
- !strcmp(&argv[i][1], "opt") ||
- !strcmp(&argv[i][1], "option")){
- SCTMAIN_CHECKARGEXIST;
- qarglist = l4qarg_parse(argv[i]);
- if(qarglist == NULL){
- fprintf(stderr, "%s: 記憶體配置發生錯誤\n", argv[0]);
- exit(SCTEXIT_MALLOC);
- }
- for(j=0; !l4qarg_end(qarglist[j]); j++){
- if(!strcmp(qarglist[j].arg_name, "mem") ||
- !strcmp(qarglist[j].arg_name, "memory")){
- SCTMAIN_CHECKQARGHAVEVALUE;
- if(sscanf(qarglist[j].arg_value, "%u",
- &mcopt.memlimit) <= 0 ||
- mcopt.memlimit <= 0){
- fprintf(stderr, "%s: 「%s」不是正確的記憶體限制"
- "設定值\n", argv[0],
- qarglist[j].arg_value);
- exit(SCTEXIT_SYNTAX);
- }
- }else if(!strcmp(qarglist[j].arg_name, "outlimit") ||
- !strcmp(qarglist[j].arg_name, "outlim")){
- SCTMAIN_CHECKQARGHAVEVALUE;
- if(sscanf(qarglist[j].arg_value, "%u",
- &mcopt.outlimit) <= 0 ||
- mcopt.outlimit <= 0){
- fprintf(stderr, "%s: 「%s」不是正確的輸出限制"
- "設定\n", argv[0], qarglist[j].arg_value);
- exit(SCTEXIT_SYNTAX);
- }
- }else if(!strcmp(qarglist[j].arg_name, "stderr")){
- SCTMAIN_CHECKQARGNOVALUE;
- mcopt.flags |= SCTMC_REDIR_STDERR;
- }else if(!strcmp(qarglist[j].arg_name, "nocopy")){
- SCTMAIN_CHECKQARGNOVALUE;
- mcopt.flags |= SCTMC_NOCOPY;
- }else if(!strcmp(qarglist[j].arg_name, "uid") ||
- !strcmp(qarglist[j].arg_name, "user")){
- SCTMAIN_CHECKQARGHAVEVALUE;
- mcopt.flags |= SCTMC_SETUID;
- if(sscanf(qarglist[j].arg_value, "%u", &mcopt.uid)
- <= 0){
-#ifdef HAVE_CONF_UGIDNAME
- pwinfo = getpwnam(qarglist[j].arg_value);
- if(pwinfo == NULL){
- fprintf(stderr, "%s: 「%s」不是正確的 UID 或"
- "使用者名稱\n",
- argv[0], qarglist[j].arg_value);
- exit(SCTEXIT_SYNTAX);
- }
- mcopt.uid = pwinfo->pw_uid;
-#else
- fprintf(stderr, "%s: 「%s」並不是整數\n",
- argv[0], qarglist[j].arg_value);
- exit(SCTEXIT_SYNTAX);
-#endif
- }
- }else if(!strcmp(qarglist[j].arg_name, "gid") ||
- !strcmp(qarglist[j].arg_name, "group")){
- SCTMAIN_CHECKQARGHAVEVALUE;
- mcopt.flags |= SCTMC_SETGID;
- if(sscanf(qarglist[j].arg_value, "%u", &mcopt.gid)
- <= 0){
-#ifdef HAVE_CONF_UGIDNAME
- grinfo = getgrnam(qarglist[j].arg_value);
- if(grinfo == NULL){
- fprintf(stderr, "%s: 「%s」不是正確的 GID 或"
- "群組名稱\n",
- argv[0], qarglist[j].arg_value);
- exit(SCTEXIT_SYNTAX);
- }
- mcopt.gid = grinfo->gr_gid;
-#else
- fprintf(stderr, "%s: 「%s」並不是整數\n",
- argv[0], qarglist[j].arg_value);
- exit(SCTEXIT_SYNTAX);
-#endif
- }
- }else{
- fprintf(stderr, "%s: 「%s」是不明的選項\n", argv[0],
- qarglist[j].arg_name);
- exit(SCTEXIT_SYNTAX);
- }
- }
- l4qarg_free(qarglist);
- }else{
- fprintf(stderr, "%s: 不明的選項「%s」\n", argv[0], argv[i]);
- return SCTEXIT_SYNTAX;
- }
- }else{
- fprintf(stderr, "%s: 參數 %d「%s」是多餘的\n"
- "請嘗試執行 %s -help 來取得說明\n"
- , argv[0], i, argv[i], argv[0]);
- return SCTEXIT_SYNTAX;
- }
- }
- /* 至此可以進入主程式了 */
- if(verbose){
- puts(SCTJUDGE_TITLEBAR"\n");
- }
-
- /* 檢查與修正設定值 */
- if(verbose){
- puts("正在檢查設定值是否正確......");
- }
- if(mcopt.executable == NULL){
- fputs("受測程式可執行檔名稱為必要參數,不可為空白\n", stderr);
- exit(SCTEXIT_TOOFEW);
- }
- if(mcopt.inputfile == NULL){
- mcopt.inputfile = WITH_NULL;
- }
- if(mcopt.outputfile == NULL){
- fputs("輸出檔案名稱必要參數,不可為空白\n", stderr);
- exit(SCTEXIT_TOOFEW);
- }
- if(mcopt.exectime <= 0){
- fputs("執行時間限制為必要參數,不可為空白!\n", stderr);
- exit(SCTEXIT_TOOFEW);
- }
- if((mcopt.flags & SCTMC_SETUID)){
- if(mcopt.uid == 0){
- if(!force){
- fputs("將 UID 設為 0 並不安全!(加上 -f 來強制執行)\n",
- stderr);
- exit(SCTEXIT_BADID);
- }else{
- if(procrealuid != 0){
- fputs("只有 root 可以將 UID 設定 0\n", stderr);
- exit(SCTEXIT_BADID);
- }
- }
- }
- }else{
- if(procrealuid == 0 && !force){
- fputs("不允許以 root 身份執行本程式(加上 -f 來強制執行)\n",
- stderr);
- exit(SCTEXIT_BADID);
- }
- }
-
- /* 現在開始建立 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(&tlethr, 0, 0);
- sem_init(&dispthr, 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_displaytime,
- (void*)&displayinterval);
- }
- pthread_attr_destroy(&detstate);
- pthread_attr_destroy(&joistate);
- sem_post(&mcthr);
-
- pthread_join(tmain, (void**)&mtreturn);
- /* XXX 底下這訊息很有可能讓使用者覺得很奇怪 */
- pthread_mutex_destroy(&pidmutex);
- pthread_mutex_destroy(&tkill_mx);
- pthread_mutex_destroy(&tdisplay_mx);
-
- sem_destroy(&mcthr);
- sem_destroy(&tlethr);
- sem_destroy(&dispthr);
-
- if(dryrun){
- return SCTEXIT_SUCCESS;
- }
-
- /* 這裡就要顯示結果了 */
- if(mtreturn == NULL || mtreturn->mc_exitcode != 0){
- fprintf(stderr, "%s: 因錯誤發生而結束程式\n", argv[0]);
- free((void*)mtreturn);
- sem_destroy(&mcthr);
- exit(SCTEXIT_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 SCTEXIT_SUCCESS;
-}
diff --git a/src/mkchild.c b/src/mkchild.c
deleted file mode 100644
index 805105f..0000000
--- a/src/mkchild.c
+++ /dev/null
@@ -1,711 +0,0 @@
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include "config2.h"
-#include "common.h"
-#include "sctcore.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, tlethr, dispthr;
-
-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);
- sem_post(&tlethr);
- }else{
- pthread_mutex_unlock(&tkill_mx);
- }
-
- pthread_mutex_lock(&tdisplay_mx);
- if(tdisplay_yes){
- tdisplay_yes = 0;
- pthread_mutex_unlock(&tdisplay_mx);
- sem_post(&dispthr);
- }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 位元組\n", mcopt->memlimit);
- }else{
- puts("無限制");
- }
- fputs("\t輸出限制:", stdout);
- if(mcopt->outlimit > 0){
- printf("%d 位元組\n", mcopt->outlimit);
- }else{
- puts("無限制");
- }
- if(mcopt->flags & SCTMC_SETUID){
- printf("\t設定受測程式執行時的 UID 為 %u\n", mcopt->uid);
- }else{
- puts("\t執行受測程式時維持原有的 real UID 和 effective UID");
- }
- if(mcopt->flags & SCTMC_SETGID){
- printf("\t設定受測程式執行時的 GID 為 %u\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);
-
- /* 宣告變數囉! */
- 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);
-#ifdef HAVE_WORKING_FORK
- pidchild = fork();
-#else
-#error "This program requires a working fork function."
-#endif
- 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 */
- sem_post(&tlethr);
- sem_post(&dispthr);
-
- /* 開始要 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, procrealuid, -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(WITH_NULL, 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(procrealuid);
- }
-#endif
-
- /* 開始設定資源限制 */
- if(mcopt.memlimit > 0){
- childressize = mcopt.memlimit;
- 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;
- 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/sctcore.h b/src/sctcore.h
deleted file mode 100644
index 13c8783..0000000
--- a/src/sctcore.h
+++ /dev/null
@@ -1,90 +0,0 @@
-#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>
-
-/* 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, tlethr, dispthr;
-
-/* 判斷有無 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;
- unsigned exectime;
- unsigned memlimit;
- unsigned outlimit;
- unsigned 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_DRYRUN 0x00000010
-#define SCTMC_FORCE 0x00000020
-#define SCTMC_VERBOSE 0x00000040
-#define SCTMC_DEBUG 0x00000080
-
-/* 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_displaytime(void*);
-
-#endif