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