Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,9 @@ A tiny judging environment for OI contest based on Lemon + LemonPlus
| Arch | 2021-07-11 | amd64 | KDE-Xorg |
| Arch | 2020-10-31 | amd64 | KDE-Wayland |
| Ubuntu | 20.04 | amd64 | GNOME 3 |
| Ubuntu | 18.04.4 | amd64 | GNOME 3 |
| NOI Linux 2(Ubuntu) | 20.04 | amd64 | GNOME 3 |
| Linux Mint | 19.3 | amd64 | Cinnamon |
| Deepin | 15.11 | amd64 | DDE |
| Deepin | 20 (1000) | amd64 | DDE |
| Debian | 10.3.0 | amd64 | LXQt; KDE-Xorg |
| Fedora | 31-1.9 | amd64 | XFCE |
| openSUSE | Leap 15.1 | amd64 | iceWM |
| openSUSE | Tumbleweed | amd64 | KDE-Xorg |
| macOS | 15.2 Beta | arm64 | Aqua |

Expand Down
10 changes: 7 additions & 3 deletions src/core/processrunner_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ ProcessRunnerResult UnixProcessRunner::run() {
argumentsList << config.outputFileName;
}

argumentsList << QString("%1").arg(extraTime);

qDebug() << argumentsList;

QString bwrapPath = QStandardPaths::findExecutable("bwrap");
Expand Down Expand Up @@ -162,6 +164,8 @@ ProcessRunnerResult UnixProcessRunner::run() {
argumentsList << config.outputFileName;
}

argumentsList << QString("%1").arg(extraTime);

qDebug() << argumentsList;

runner->setProcessEnvironment(config.environment);
Expand All @@ -182,9 +186,9 @@ ProcessRunnerResult UnixProcessRunner::run() {
QElapsedTimer timer;
timer.start();

// Using rlimit to limit CPU time can only be accurate to seconds,
// so here it is rounded up to an integer second.
long long killTimeLimit = (config.timeLimit + 999) / 1000 * 1000 + extraTime;
// The watcher itself enforces the wall clock (timeLimit + extraTime) and exits shortly
// after the timer fires; this loop only guards against a hung watcher, with 1s of slack.
long long killTimeLimit = 1LL * config.timeLimit + extraTime + 1000;
while (timer.elapsed() <= killTimeLimit) {
if (runner->waitForFinished(10)) {
isProgramFinishedInExtraTimeLimit = true;
Expand Down
2 changes: 2 additions & 0 deletions unix/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ file(COPY hello.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_executable(tle tle.c)
add_executable(add add.c)
add_executable(re re.c)
add_executable(sleep_tle sleep_tle.c)

file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/scripts DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

Expand All @@ -27,6 +28,7 @@ add_test(NAME watcher_run_sh_test COMMAND python3 scripts/run_sh.py)
add_test(NAME watcher_MLE_static_test COMMAND python3 scripts/mle_static.py)
add_test(NAME watcher_unlimit_memory_test COMMAND python3 scripts/unlimit.py)
add_test(NAME watcher_TLE_test COMMAND python3 scripts/tle.py)
add_test(NAME watcher_wall_clock_TLE_test COMMAND python3 scripts/sleep_tle.py)
add_test(NAME watcher_filename_with_space_test COMMAND python3 scripts/space.py)
add_test(NAME watcher_symlink_abs_test COMMAND python3 scripts/symlink_abs.py)
add_test(NAME watcher_symlink_rel_test COMMAND python3 scripts/symlink_rel.py)
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/mle_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
pid = os.getpid()
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", "", tmperr, "1000", "380", "1000", "380", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", "", tmperr, "1000", "380", "1000", "380", "", "", "0"], shell=False, stdout=subprocess.PIPE)

stdout, _ = p.communicate()

Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/redirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
with open(tmpin, 'w') as f:
f.writelines(['1 1'])

p = subprocess.Popen(["./watcher_unix", "./add", "", tmpin, tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False)
p = subprocess.Popen(["./watcher_unix", "./add", "", tmpin, tmpout, tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False)

time.sleep(2)
p.kill()
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./hello", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./hello", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/run_sh.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "/bin/sh", "hello.sh", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "/bin/sh", "hello.sh", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/runtimeerr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
pid = os.getpid()
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./re", "", "", "", tmperr, "1000", "100", "1000", "100", "", ""], shell=False)
p = subprocess.Popen(["./watcher_unix", "./re", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False)

assert(p.wait() == 2)
17 changes: 17 additions & 0 deletions unix/test/scripts/sleep_tle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import subprocess
import time
import os

pid = os.getpid()
tmperr = f"_tmperr_{pid}"

# `sleep` does not consume CPU time, so it can bypass RLIMIT_CPU;
# the watcher must time it out by wall clock.
p = subprocess.Popen(["./watcher_unix", "./sleep_tle", "", "", "", tmperr,
"1000", "100", "1000", "100", "", "", "0"],
shell=False)

time.sleep(3)
p.kill()

assert(p.returncode == 3)
2 changes: 1 addition & 1 deletion unix/test/scripts/space.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

shutil.copy("./hello", "./he llo")

p = subprocess.Popen(["./watcher_unix", "./he llo", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./he llo", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/symlink_abs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

os.symlink(os.path.join(os.getcwd(), "hello"), "hello_s_abs")

p = subprocess.Popen(["./watcher_unix", "./hello_s_abs", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./hello_s_abs", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/symlink_rel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

os.symlink("hello", "hello_s_rel")

p = subprocess.Popen(["./watcher_unix", "./hello_s_rel", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./hello_s_rel", "", "", tmpout, tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/tle.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", ""], shell=False)
p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", "0"], shell=False)

time.sleep(5)
p.kill()
Expand Down
2 changes: 1 addition & 1 deletion unix/test/scripts/unlimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
tmpout = f"_tmpout_{pid}"
tmperr = f"_tmperr_{pid}"

p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", tmpout, tmperr, "1000", "-1", "1000", "-1", "", ""], shell=False, stdout=subprocess.PIPE)
p = subprocess.Popen(["./watcher_unix", "./mle_static", "", "", tmpout, tmperr, "1000", "-1", "1000", "-1", "", "", "0"], shell=False, stdout=subprocess.PIPE)

assert(p.wait() == 0)
assert(os.path.exists(tmpout))
Expand Down
6 changes: 6 additions & 0 deletions unix/test/sleep_tle.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <unistd.h>

int main() {
sleep(10);
return 0;
}
139 changes: 131 additions & 8 deletions unix/watcher_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

#include <cassert>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
Expand All @@ -18,6 +19,15 @@
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#if defined(__linux__)
#include <linux/sched.h>
#include <poll.h>
#include <sys/syscall.h>
#include <sys/timerfd.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
defined(__DragonFly__)
#include <sys/event.h>
#endif
#include <unistd.h>

int pid;
Expand Down Expand Up @@ -52,11 +62,12 @@ enum : int {
* argv[9]: 原始(未经语言设置缩放的)空间限制(MiB)
* argv[10]: 选手程序只读的文件
* argv[11]: 选手程序只写的文件
* argv[12]: wall clock 额外超时时间(毫秒)
*/
auto main(int argc, char *argv[]) -> int {
if (argc != 12) {
if (argc != 13) {
printf("-1\n-1\n");
fprintf(stderr, "Expected 11 arguments, found %d\n", argc);
fprintf(stderr, "Expected 12 arguments, found %d\n", argc - 1);
return RS_FAIL;
}
std::string fileName = argv[1];
Expand All @@ -70,6 +81,7 @@ auto main(int argc, char *argv[]) -> int {
[[maybe_unused]] long long rawMemoryLimitMib = std::stoll(argv[9]);
[[maybe_unused]] std::string readableFile = argv[10];
[[maybe_unused]] std::string writableFile = argv[11];
long long extraTimeMs = std::stoll(argv[12]);

initWatcher();

Expand Down Expand Up @@ -97,7 +109,22 @@ auto main(int argc, char *argv[]) -> int {

ssize_t actualMemoryRLimit = getMemoryRLimit(memoryLimitMib);

#if defined(__linux__)
int childPfd = -1;
struct clone_args args{};
args.flags = CLONE_PIDFD;
args.pidfd = (unsigned long long)&childPfd;
args.exit_signal = SIGCHLD;
pid = syscall(SYS_clone3, &args, sizeof(args));
#else
pid = fork();
#endif

if (pid < 0) {
perror("fork");
printf("-1\n-1\n");
return RS_FAIL;
}

if (pid > 0) {
// Parent process
Expand All @@ -107,11 +134,111 @@ auto main(int argc, char *argv[]) -> int {
struct rusage usage{};
int status = 0;

// Time limit is enforced by a wall-clock timer instead of `setrlimit(RLIMIT_CPU)`:
// RLIMIT_CPU counts CPU time while the judge measures user time, and a program that
// merely sleeps never hits it.
long long wallClockMs = timeLimitMs + extraTimeMs;
#if defined(__linux__)
int timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (timerFd < 0) {
perror("timerfd_create");
printf("-1\n-1\n");
return RS_FAIL;
}

struct itimerspec ts{};
ts.it_value.tv_sec = wallClockMs / 1000;
ts.it_value.tv_nsec = (wallClockMs % 1000) * 1000000;
if (timerfd_settime(timerFd, 0, &ts, nullptr) < 0) {
perror("timerfd_settime");
printf("-1\n-1\n");
return RS_FAIL;
}

struct pollfd pfds[2]{};
pfds[0].fd = childPfd;
pfds[0].events = POLLIN;
pfds[1].fd = timerFd;
pfds[1].events = POLLIN;

int pollResult;
do {
pollResult = poll(pfds, 2, -1);
} while (pollResult < 0 && errno == EINTR);

if (pollResult < 0) {
perror("poll");
printf("-1\n-1\n");
return RS_FAIL;
}

bool childExited = pfds[0].revents & POLLIN;
bool timedOut = pfds[1].revents & POLLIN;

if (! childExited)
kill(pid, SIGKILL);

wait4(pid, &status, 0, &usage);

close(childPfd);
close(timerFd);

if (timedOut) {
printf("-1\n-1\n");
return RS_TLE;
}
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
defined(__DragonFly__)
int kq = kqueue();
if (kq < 0) {
perror("kqueue");
printf("-1\n-1\n");
return RS_FAIL;
}

struct kevent changes[2];
EV_SET(&changes[0], pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, NULL);
EV_SET(&changes[1], 0, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, wallClockMs, NULL);

struct kevent events[2];
int n;
do {
n = kevent(kq, changes, 2, events, 2, NULL);
} while (n < 0 && errno == EINTR);

if (n < 0) {
perror("kevent");
printf("-1\n-1\n");
return RS_FAIL;
}

bool childExited = false;
bool timedOut = false;
for (int i = 0; i < n; i++) {
if (events[i].filter == EVFILT_PROC)
childExited = true;
else if (events[i].filter == EVFILT_TIMER)
timedOut = true;
}

if (timedOut && ! childExited)
kill(pid, SIGKILL);

wait4(pid, &status, 0, &usage);

close(kq);

if (timedOut) {
printf("-1\n-1\n");
return RS_TLE;
}
#else
if (wait4(pid, &status, 0, &usage) == -1) {
printf("-1\n-1\n");
perror("wait4");
return RS_FAIL;
}
#endif

if (WIFEXITED(status)) {
long long timeUsedMs =
Expand Down Expand Up @@ -144,6 +271,7 @@ auto main(int argc, char *argv[]) -> int {
return RS_RE;
}
} else {
// Child process
std::string finalStdinRedirect = stdinRedirect.empty() ? "/dev/null" : stdinRedirect;
if (freopen(finalStdinRedirect.c_str(), "r", stdin) == NULL) {
perror("freopen stdin");
Expand All @@ -160,7 +288,7 @@ auto main(int argc, char *argv[]) -> int {
exit(RS_FAIL);
}

rlimit memlim{}, stalim{}, timlim{};
rlimit memlim{}, stalim{};

if (memoryLimitMib > 0) {
memlim = (rlimit){(rlim_t)actualMemoryRLimit, (rlim_t)actualMemoryRLimit};
Expand All @@ -171,13 +299,8 @@ auto main(int argc, char *argv[]) -> int {
stalim = (rlimit){(rlim_t)2147483647LL, (rlim_t)2147483647LL};
}

// Calculate time limit in seconds, rounding up
rlim_t soft_time_limit_sec = (timeLimitMs + 999) / 1000;
timlim = (rlimit){soft_time_limit_sec, soft_time_limit_sec + 1}; // Soft limit + 1 for hard limit

setrlimit(RLIMIT_AS, &memlim);
setrlimit(RLIMIT_STACK, &stalim);
setrlimit(RLIMIT_CPU, &timlim);

if (execlp("bash", "bash", "-c", runCmd.c_str(), NULL) == -1) {
perror("execlp");
Expand Down