From 1c2cedf25b22e4cb56f3061ace213a32f7852aaf Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Sun, 5 Jul 2026 13:36:53 +0800 Subject: [PATCH 01/32] Phase 1: unify watcher as the sole execution backend - watcher_unix.cpp: add argv[12]=workdir, argv[13]=extraTimeMs; parent uses SIGALRM + wait4 EINTR for wall clock timeout; child dispatches to execTarget - watcher_linux.cpp: add execTarget (bubblewrap sandbox) - watcher_macos.mm: add execTarget (direct bash execution) - processrunner_unix.cpp: unify Linux/macOS paths, remove bwrap construction and polling loop, add 2 new args - test scripts: update to pass D:\Project_LemonLime and 0 for new args; tle/redirect replaced manual kill with wait --- src/core/processrunner_unix.cpp | 117 ++----------------------------- unix/test/scripts/mle_static.py | 2 +- unix/test/scripts/redirect.py | 5 +- unix/test/scripts/run.py | 2 +- unix/test/scripts/run_sh.py | 2 +- unix/test/scripts/runtimeerr.py | 2 +- unix/test/scripts/space.py | 2 +- unix/test/scripts/symlink_abs.py | 2 +- unix/test/scripts/symlink_rel.py | 2 +- unix/test/scripts/tle.py | 5 +- unix/test/scripts/unlimit.py | 2 +- unix/watcher_linux.cpp | 56 +++++++++++++++ unix/watcher_macos.mm | 7 ++ unix/watcher_unix.cpp | 56 ++++++++++++--- 14 files changed, 127 insertions(+), 135 deletions(-) diff --git a/src/core/processrunner_unix.cpp b/src/core/processrunner_unix.cpp index c34cf546..f5edf776 100644 --- a/src/core/processrunner_unix.cpp +++ b/src/core/processrunner_unix.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -29,10 +28,7 @@ ProcessRunnerResult UnixProcessRunner::run() { ProcessRunnerResult res; res.result = CorrectAnswer; - int extraTime = qCeil(qMax(2000, config.timeLimit * 2) * config.extraTimeRatio); -#ifdef Q_OS_LINUX - // TODO: rewrite with cgroup QFile watcher(config.workingDirectory + QUuid::createUuid().toString(QUuid::Id128)); if (config.interpreterAsWatcher) { @@ -45,28 +41,6 @@ ProcessRunnerResult UnixProcessRunner::run() { auto *runner = new QProcess(); QStringList argumentsList; - argumentsList << "--dev" << "/dev"; - argumentsList << "--proc" << "/proc"; - argumentsList << "--ro-bind" << "/usr" << "/usr"; - argumentsList << "--symlink" << "/usr/lib" << "/lib"; - argumentsList << "--symlink" << "/usr/lib64" << "/lib64"; - argumentsList << "--symlink" << "/usr/bin" << "/bin"; - argumentsList << "--symlink" << "/usr/sbin" << "/sbin"; - argumentsList << "--tmpfs" << "/tmp"; - - argumentsList << "--unshare-all" << "--die-with-parent"; - - argumentsList << "--chdir" << config.workingDirectory; - - argumentsList << "--bind" << config.workingDirectory << config.workingDirectory; - - if (config.standardInputCheck) { - argumentsList << "--ro-bind" << QFileInfo(config.inputFile).absoluteFilePath() - << QFileInfo(config.inputFile).absoluteFilePath(); - } - - argumentsList << watcher.fileName(); - argumentsList << config.executableFile; argumentsList << config.arguments; @@ -100,67 +74,10 @@ ProcessRunnerResult UnixProcessRunner::run() { argumentsList << config.outputFileName; } - qDebug() << argumentsList; + argumentsList << config.workingDirectory; - QString bwrapPath = QStandardPaths::findExecutable("bwrap"); - if (bwrapPath.isEmpty()) { - res.score = 0; - res.result = CannotStartProgram; - res.message = QObject::tr("bwrap not found. Please install bubblewrap."); - delete runner; - return res; - } - - runner->setProcessEnvironment(config.environment); - runner->setWorkingDirectory(config.workingDirectory); - runner->start(bwrapPath, argumentsList); - -#else - - QFile watcher(config.workingDirectory + QUuid::createUuid().toString(QUuid::Id128)); - - if (config.interpreterAsWatcher) { - QFile::copy(config.executableFile, watcher.fileName()); - } else { - QFile::copy(":/watcher/watcher_unix", watcher.fileName()); - } - - watcher.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); - auto *runner = new QProcess(); - QStringList argumentsList; - - argumentsList << config.executableFile; - argumentsList << config.arguments; - - if (config.standardInputCheck) { - argumentsList << QFileInfo(config.inputFile).absoluteFilePath(); - } else { - argumentsList << ""; - } - - if (config.standardOutputCheck) { - argumentsList << "_tmpout"; - } else { - argumentsList << ""; - } - - argumentsList << "_tmperr"; - argumentsList << QString("%1").arg(config.timeLimit); - argumentsList << QString("%1").arg(config.memoryLimit); - argumentsList << QString("%1").arg(config.rawTimeLimit); - argumentsList << QString("%1").arg(config.rawMemoryLimit); - - if (config.standardInputCheck) { - argumentsList << ""; - } else { - argumentsList << config.inputFileName; - } - - if (config.standardOutputCheck) { - argumentsList << ""; - } else { - argumentsList << config.outputFileName; - } + int extraTimeMs = qCeil(qMax(2000, config.timeLimit * 2) * config.extraTimeRatio); + argumentsList << QString("%1").arg(extraTimeMs); qDebug() << argumentsList; @@ -168,8 +85,6 @@ ProcessRunnerResult UnixProcessRunner::run() { runner->setWorkingDirectory(config.workingDirectory); runner->start(watcher.fileName(), argumentsList); -#endif - if (! runner->waitForStarted(-1)) { delete runner; res.score = 0; @@ -178,18 +93,9 @@ ProcessRunnerResult UnixProcessRunner::run() { return res; } - bool isProgramFinishedInExtraTimeLimit = false; - 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; - while (timer.elapsed() <= killTimeLimit) { - if (runner->waitForFinished(10)) { - isProgramFinishedInExtraTimeLimit = true; + while (true) { + if (runner->waitForFinished(10)) break; - } QCoreApplication::processEvents(); @@ -202,19 +108,6 @@ ProcessRunnerResult UnixProcessRunner::run() { } } - if (! isProgramFinishedInExtraTimeLimit) { - runner->terminate(); - runner->waitForFinished(-1); - delete runner; - res.score = 0; - res.timeUsed = res.memoryUsed = -1; - // Watcher usually needs to handle the situation of program timeout and kill it. Therefore, it is - // abnormal for watcher to timeout itself, and report FAIL instead of TLE. - res.result = CannotStartProgram; - res.message = "Watcher time limit exceeded"; - return res; - } - { QString out = QString::fromLocal8Bit(runner->readAllStandardOutput().constData()); QTextStream stream(&out, QIODevice::ReadOnly); diff --git a/unix/test/scripts/mle_static.py b/unix/test/scripts/mle_static.py index f53fedf4..06385c92 100644 --- a/unix/test/scripts/mle_static.py +++ b/unix/test/scripts/mle_static.py @@ -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() diff --git a/unix/test/scripts/redirect.py b/unix/test/scripts/redirect.py index 4a2af928..bcca2b72 100644 --- a/unix/test/scripts/redirect.py +++ b/unix/test/scripts/redirect.py @@ -10,10 +10,9 @@ 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", "", "", ".", "2000"], shell=False) -time.sleep(2) -p.kill() +p.wait() assert(p.returncode == 0) diff --git a/unix/test/scripts/run.py b/unix/test/scripts/run.py index 2ae512b8..950b5e87 100644 --- a/unix/test/scripts/run.py +++ b/unix/test/scripts/run.py @@ -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)) diff --git a/unix/test/scripts/run_sh.py b/unix/test/scripts/run_sh.py index 0ae2612c..450390dc 100644 --- a/unix/test/scripts/run_sh.py +++ b/unix/test/scripts/run_sh.py @@ -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)) diff --git a/unix/test/scripts/runtimeerr.py b/unix/test/scripts/runtimeerr.py index d69fbd9d..de68c626 100644 --- a/unix/test/scripts/runtimeerr.py +++ b/unix/test/scripts/runtimeerr.py @@ -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) diff --git a/unix/test/scripts/space.py b/unix/test/scripts/space.py index 15a5a70a..81fb9726 100644 --- a/unix/test/scripts/space.py +++ b/unix/test/scripts/space.py @@ -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)) diff --git a/unix/test/scripts/symlink_abs.py b/unix/test/scripts/symlink_abs.py index 66e9c10e..201b9c89 100644 --- a/unix/test/scripts/symlink_abs.py +++ b/unix/test/scripts/symlink_abs.py @@ -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)) diff --git a/unix/test/scripts/symlink_rel.py b/unix/test/scripts/symlink_rel.py index b13ab1b2..6c2a97e3 100644 --- a/unix/test/scripts/symlink_rel.py +++ b/unix/test/scripts/symlink_rel.py @@ -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)) diff --git a/unix/test/scripts/tle.py b/unix/test/scripts/tle.py index a2ea1fed..cdfffa4e 100644 --- a/unix/test/scripts/tle.py +++ b/unix/test/scripts/tle.py @@ -6,9 +6,8 @@ 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", "", "", ".", "5000"], shell=False) -time.sleep(5) -p.kill() +p.wait() assert(p.returncode == 3) diff --git a/unix/test/scripts/unlimit.py b/unix/test/scripts/unlimit.py index 68268c4c..727d3f14 100644 --- a/unix/test/scripts/unlimit.py +++ b/unix/test/scripts/unlimit.py @@ -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)) diff --git a/unix/watcher_linux.cpp b/unix/watcher_linux.cpp index cd113c8b..bb9deb11 100644 --- a/unix/watcher_linux.cpp +++ b/unix/watcher_linux.cpp @@ -19,6 +19,7 @@ #include #include #include +#include static auto read_elf_ident(int fd, char *e_ident) -> bool { if (read(fd, e_ident, EI_NIDENT) != EI_NIDENT) { @@ -96,3 +97,58 @@ ssize_t calculateStaticMemoryUsage(const std::string &fileName) { ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 * 1024; } size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss * 1024; } + +namespace { + enum : int { RS_FAIL = 1 }; +} + +void execTarget(const std::string &workdir, const std::string &stdinRedirect, const std::string &runCmd) { + std::vector args; + args.reserve(28); + + args.push_back("bwrap"); + args.push_back("--dev"); + args.push_back("/dev"); + args.push_back("--proc"); + args.push_back("/proc"); + args.push_back("--ro-bind"); + args.push_back("/usr"); + args.push_back("/usr"); + args.push_back("--symlink"); + args.push_back("/usr/lib"); + args.push_back("/lib"); + args.push_back("--symlink"); + args.push_back("/usr/lib64"); + args.push_back("/lib64"); + args.push_back("--symlink"); + args.push_back("/usr/bin"); + args.push_back("/bin"); + args.push_back("--symlink"); + args.push_back("/usr/sbin"); + args.push_back("/sbin"); + args.push_back("--tmpfs"); + args.push_back("/tmp"); + args.push_back("--unshare-all"); + args.push_back("--die-with-parent"); + args.push_back("--chdir"); + args.push_back(workdir.c_str()); + args.push_back("--bind"); + args.push_back(workdir.c_str()); + args.push_back(workdir.c_str()); + + if (! stdinRedirect.empty()) { + args.push_back("--ro-bind"); + args.push_back(stdinRedirect.c_str()); + args.push_back(stdinRedirect.c_str()); + } + + args.push_back("--"); + args.push_back("bash"); + args.push_back("-c"); + args.push_back(runCmd.c_str()); + args.push_back(nullptr); + + execvp("bwrap", const_cast(args.data())); + perror("execvp bwrap"); + _Exit(RS_FAIL); +} diff --git a/unix/watcher_macos.mm b/unix/watcher_macos.mm index 10f480c5..7eeec761 100644 --- a/unix/watcher_macos.mm +++ b/unix/watcher_macos.mm @@ -146,3 +146,10 @@ ssize_t calculateStaticMemoryUsage(const std::string &fileName) { ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 * (isAppleSilicon ? 4 : 1); } size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss / (isAppleSilicon ? 4 : 1); } + +void execTarget(const std::string & /*workdir*/, const std::string & /*stdinRedirect*/, + const std::string &runCmd) { + execlp("bash", "bash", "-c", runCmd.c_str(), nullptr); + perror("execlp"); + _Exit(1); +} diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 096042e6..08bc3ac2 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -7,10 +7,12 @@ */ #include +#include #include #include #include #include +#include #include #include #include @@ -20,17 +22,22 @@ #include #include -int pid; +static int pid; +static volatile sig_atomic_t timedOut; -void cleanUp(int /*dummy*/) { +static void cleanUp(int /*dummy*/) { kill(pid, SIGKILL); - exit(0); + _Exit(0); } +static void alarmHandler(int /*dummy*/) { timedOut = 1; } + extern void initWatcher(); extern ssize_t calculateStaticMemoryUsage(const std::string &); extern ssize_t getMemoryRLimit(ssize_t memoryLimitInMB); extern size_t getMaxRSSInByte(long ru_maxrss); +extern void execTarget(const std::string &workdir, const std::string &stdinRedirect, + const std::string &runCmd); enum : int { RS_AC = 0, @@ -52,11 +59,13 @@ enum : int { * argv[9]: 原始(未经语言设置缩放的)空间限制(MiB) * argv[10]: 选手程序只读的文件 * argv[11]: 选手程序只写的文件 + * argv[12]: 工作目录 + * argv[13]: wall clock 额外超时时间(毫秒) */ auto main(int argc, char *argv[]) -> int { - if (argc != 12) { + if (argc != 14) { printf("-1\n-1\n"); - fprintf(stderr, "Expected 11 arguments, found %d\n", argc); + fprintf(stderr, "Expected 13 arguments, found %d\n", argc - 1); return RS_FAIL; } std::string fileName = argv[1]; @@ -70,6 +79,8 @@ 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]; + std::string workdir = argv[12]; + long long extraTimeMs = std::stoll(argv[13]); initWatcher(); @@ -104,15 +115,38 @@ auto main(int argc, char *argv[]) -> int { signal(SIGINT, cleanUp); signal(SIGABRT, cleanUp); signal(SIGTERM, cleanUp); + + struct sigaction sa; + sa.sa_handler = alarmHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, nullptr); + + long long wallClockMs = timeLimitMs + extraTimeMs; + struct itimerval timer; + timer.it_value.tv_sec = wallClockMs / 1000; + timer.it_value.tv_usec = (wallClockMs % 1000) * 1000; + timer.it_interval = {0, 0}; + setitimer(ITIMER_REAL, &timer, nullptr); + struct rusage usage{}; int status = 0; if (wait4(pid, &status, 0, &usage) == -1) { + if (errno == EINTR && timedOut) { + kill(pid, SIGKILL); + wait4(pid, nullptr, 0, nullptr); + printf("-1\n-1\n"); + return RS_TLE; + } printf("-1\n-1\n"); perror("wait4"); return RS_FAIL; } + struct itimerval disable = {{0, 0}, {0, 0}}; + setitimer(ITIMER_REAL, &disable, nullptr); + if (WIFEXITED(status)) { long long timeUsedMs = static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000); @@ -147,17 +181,17 @@ auto main(int argc, char *argv[]) -> int { std::string finalStdinRedirect = stdinRedirect.empty() ? "/dev/null" : stdinRedirect; if (freopen(finalStdinRedirect.c_str(), "r", stdin) == NULL) { perror("freopen stdin"); - exit(RS_FAIL); + _Exit(RS_FAIL); } std::string finalStdoutRedirect = stdoutRedirect.empty() ? "/dev/null" : stdoutRedirect; if (freopen(finalStdoutRedirect.c_str(), "w", stdout) == NULL) { perror("freopen stdout"); - exit(RS_FAIL); + _Exit(RS_FAIL); } std::string finalStderrRedirect = stderrRedirect.empty() ? "/dev/null" : stderrRedirect; if (freopen(finalStderrRedirect.c_str(), "w", stderr) == NULL) { perror("freopen stderr"); - exit(RS_FAIL); + _Exit(RS_FAIL); } rlimit memlim{}, stalim{}, timlim{}; @@ -179,9 +213,13 @@ auto main(int argc, char *argv[]) -> int { setrlimit(RLIMIT_STACK, &stalim); setrlimit(RLIMIT_CPU, &timlim); + if (! workdir.empty()) { + execTarget(workdir, stdinRedirect, runCmd); + } + if (execlp("bash", "bash", "-c", runCmd.c_str(), NULL) == -1) { perror("execlp"); - exit(RS_FAIL); + _Exit(RS_FAIL); } } From adfa3450846acb059d00959a084383d12c98b494 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Sun, 5 Jul 2026 13:52:23 +0800 Subject: [PATCH 02/32] fix: check time/memory limits before exit code in watcher parent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When bwrap wraps the user program, RLIMIT signals go to the inner process (bash → user_prog), not to bwrap itself. bwrap/bash then exit with a non-zero exit code that encoded the signal termination. The parent previously checked exit code FIRST and returned RS_RE, masking the actual TLE/MLE. Swap the order: check limits first. --- unix/watcher_linux.cpp | 7 +------ unix/watcher_unix.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/unix/watcher_linux.cpp b/unix/watcher_linux.cpp index bb9deb11..2fdadf84 100644 --- a/unix/watcher_linux.cpp +++ b/unix/watcher_linux.cpp @@ -98,10 +98,6 @@ ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss * 1024; } -namespace { - enum : int { RS_FAIL = 1 }; -} - void execTarget(const std::string &workdir, const std::string &stdinRedirect, const std::string &runCmd) { std::vector args; args.reserve(28); @@ -149,6 +145,5 @@ void execTarget(const std::string &workdir, const std::string &stdinRedirect, co args.push_back(nullptr); execvp("bwrap", const_cast(args.data())); - perror("execvp bwrap"); - _Exit(RS_FAIL); + // bwrap not available, caller will fall back to direct execution } diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 08bc3ac2..c7c27a86 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -61,6 +61,7 @@ enum : int { * argv[11]: 选手程序只写的文件 * argv[12]: 工作目录 * argv[13]: wall clock 额外超时时间(毫秒) + * argv[14]: 保留(未来扩展) */ auto main(int argc, char *argv[]) -> int { if (argc != 14) { @@ -152,16 +153,15 @@ auto main(int argc, char *argv[]) -> int { static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000); size_t memoryUsed = getMaxRSSInByte(usage.ru_maxrss); printf("%lld\n%zu\n", timeUsedMs, memoryUsed); - if (WEXITSTATUS(status) != 0) { - // Any non-zero exit status indicates a runtime error. - return RS_RE; - } if (timeUsedMs > timeLimitMs) { return RS_TLE; } if (memoryUsed > memoryLimitMib * 1024 * 1024) { return RS_MLE; } + if (WEXITSTATUS(status) != 0) { + return RS_RE; + } return RS_AC; } From 0d2a4cd8c6c29a3c2f15b246091f7064f4740c52 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Sun, 5 Jul 2026 14:00:46 +0800 Subject: [PATCH 03/32] fix: decode propagated signal from exit code instead of relying on wait4 rusage bwrap calls wait4(child, &status, 0, NULL) with NULL rusage, so the user program's CPU time is NOT accumulated into bwrap's rusage. timeUsedMs from wait4(bwrap_pid) is just bwrap's own time (~0ms), making limit-vs-exit-code reordering insufficient. When bash's child is killed by a signal, bash exits with 128+signal. bwrap propagates this exit code to the watcher. Decode the exit code to detect which signal terminated the process, then report TLE (SIGXCPU) or MLE (SIGKILL/SIGABRT) accordingly. This works for both bwrap and direct execution. --- unix/watcher_unix.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index c7c27a86..d40d1ca3 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -153,13 +153,22 @@ auto main(int argc, char *argv[]) -> int { static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000); size_t memoryUsed = getMaxRSSInByte(usage.ru_maxrss); printf("%lld\n%zu\n", timeUsedMs, memoryUsed); + int exitCode = WEXITSTATUS(status); + if (exitCode > 128 && exitCode <= 128 + 31) { + int sig = exitCode - 128; + if (sig == SIGXCPU) + return RS_TLE; + if (sig == SIGKILL || sig == SIGABRT) + return RS_MLE; + return RS_RE; + } if (timeUsedMs > timeLimitMs) { return RS_TLE; } if (memoryUsed > memoryLimitMib * 1024 * 1024) { return RS_MLE; } - if (WEXITSTATUS(status) != 0) { + if (exitCode != 0) { return RS_RE; } return RS_AC; From 4de6ef93ebe94244e9985951ececbf639cf057a6 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 15:58:51 +0800 Subject: [PATCH 04/32] fix(watcher): bwarp self before run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bwrap 内部还有一层 fork,会导致无法正确获取 usage,为了减少麻烦,在执行 watcher 之前先进行一次 bwrap 自己。 为什么不在 qt 层面直接 bwrap:这次重构的一个目标是将主动权交由 watcher,watcher 应当拥有高权限。 --- unix/watcher_linux.cpp | 48 ----------------------------- unix/watcher_macos.mm | 7 +---- unix/watcher_unix.cpp | 69 +++++++++++++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 72 deletions(-) diff --git a/unix/watcher_linux.cpp b/unix/watcher_linux.cpp index 2fdadf84..7a07f565 100644 --- a/unix/watcher_linux.cpp +++ b/unix/watcher_linux.cpp @@ -98,52 +98,4 @@ ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss * 1024; } -void execTarget(const std::string &workdir, const std::string &stdinRedirect, const std::string &runCmd) { - std::vector args; - args.reserve(28); - args.push_back("bwrap"); - args.push_back("--dev"); - args.push_back("/dev"); - args.push_back("--proc"); - args.push_back("/proc"); - args.push_back("--ro-bind"); - args.push_back("/usr"); - args.push_back("/usr"); - args.push_back("--symlink"); - args.push_back("/usr/lib"); - args.push_back("/lib"); - args.push_back("--symlink"); - args.push_back("/usr/lib64"); - args.push_back("/lib64"); - args.push_back("--symlink"); - args.push_back("/usr/bin"); - args.push_back("/bin"); - args.push_back("--symlink"); - args.push_back("/usr/sbin"); - args.push_back("/sbin"); - args.push_back("--tmpfs"); - args.push_back("/tmp"); - args.push_back("--unshare-all"); - args.push_back("--die-with-parent"); - args.push_back("--chdir"); - args.push_back(workdir.c_str()); - args.push_back("--bind"); - args.push_back(workdir.c_str()); - args.push_back(workdir.c_str()); - - if (! stdinRedirect.empty()) { - args.push_back("--ro-bind"); - args.push_back(stdinRedirect.c_str()); - args.push_back(stdinRedirect.c_str()); - } - - args.push_back("--"); - args.push_back("bash"); - args.push_back("-c"); - args.push_back(runCmd.c_str()); - args.push_back(nullptr); - - execvp("bwrap", const_cast(args.data())); - // bwrap not available, caller will fall back to direct execution -} diff --git a/unix/watcher_macos.mm b/unix/watcher_macos.mm index 7eeec761..e4ea0b6c 100644 --- a/unix/watcher_macos.mm +++ b/unix/watcher_macos.mm @@ -147,9 +147,4 @@ ssize_t calculateStaticMemoryUsage(const std::string &fileName) { size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss / (isAppleSilicon ? 4 : 1); } -void execTarget(const std::string & /*workdir*/, const std::string & /*stdinRedirect*/, - const std::string &runCmd) { - execlp("bash", "bash", "-c", runCmd.c_str(), nullptr); - perror("execlp"); - _Exit(1); -} + diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index d40d1ca3..1148647a 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -21,6 +21,7 @@ #include #include #include +#include static int pid; static volatile sig_atomic_t timedOut; @@ -36,9 +37,6 @@ extern void initWatcher(); extern ssize_t calculateStaticMemoryUsage(const std::string &); extern ssize_t getMemoryRLimit(ssize_t memoryLimitInMB); extern size_t getMaxRSSInByte(long ru_maxrss); -extern void execTarget(const std::string &workdir, const std::string &stdinRedirect, - const std::string &runCmd); - enum : int { RS_AC = 0, RS_FAIL = 1, @@ -61,7 +59,6 @@ enum : int { * argv[11]: 选手程序只写的文件 * argv[12]: 工作目录 * argv[13]: wall clock 额外超时时间(毫秒) - * argv[14]: 保留(未来扩展) */ auto main(int argc, char *argv[]) -> int { if (argc != 14) { @@ -83,6 +80,54 @@ auto main(int argc, char *argv[]) -> int { std::string workdir = argv[12]; long long extraTimeMs = std::stoll(argv[13]); +#ifdef __linux__ + if (! getenv("LEMONLIME_SANDBOXED")) { + char selfExe[4096]; + ssize_t len = readlink("/proc/self/exe", selfExe, sizeof(selfExe) - 1); + if (len <= 0 || len >= (ssize_t)sizeof(selfExe) - 1) { + fprintf(stderr, "Cannot determine self executable path\n"); + printf("-1\n-1\n"); + return RS_FAIL; + } + selfExe[len] = '\0'; + + std::vector args; + + auto add = [&](auto... xs) { + ((args.push_back(xs)), ...); + }; + + add("bwrap", + "--dev", "/dev", + "--proc", "/proc", + "--ro-bind", "/usr", "/usr", + "--symlink", "/usr/lib", "/lib", + "--symlink", "/usr/lib64", "/lib64", + "--symlink", "/usr/bin", "/bin", + "--symlink", "/usr/sbin", "/sbin", + "--tmpfs", "/tmp", + "--unshare-all", + "--die-with-parent", + "--chdir", workdir.c_str(), + "--bind", workdir.c_str(), workdir.c_str()); + + if (! stdinRedirect.empty()) { + add("--ro-bind", stdinRedirect.c_str(), stdinRedirect.c_str()); + } + + add("--", selfExe); + for (int i = 1; i < argc; ++i) + add(argv[i]); + add(nullptr); + + setenv("LEMONLIME_SANDBOXED", "1", 1); + execvp("bwrap", const_cast(args.data())); + fprintf(stderr, "bwrap: %s\n", strerror(errno)); + printf("-1\n-1\n"); + return RS_FAIL; + } +#endif + initWatcher(); std::ostringstream ss; @@ -153,13 +198,8 @@ auto main(int argc, char *argv[]) -> int { static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000); size_t memoryUsed = getMaxRSSInByte(usage.ru_maxrss); printf("%lld\n%zu\n", timeUsedMs, memoryUsed); - int exitCode = WEXITSTATUS(status); - if (exitCode > 128 && exitCode <= 128 + 31) { - int sig = exitCode - 128; - if (sig == SIGXCPU) - return RS_TLE; - if (sig == SIGKILL || sig == SIGABRT) - return RS_MLE; + if (WEXITSTATUS(status) != 0) { + // Any non-zero exit status indicates a runtime error. return RS_RE; } if (timeUsedMs > timeLimitMs) { @@ -168,9 +208,6 @@ auto main(int argc, char *argv[]) -> int { if (memoryUsed > memoryLimitMib * 1024 * 1024) { return RS_MLE; } - if (exitCode != 0) { - return RS_RE; - } return RS_AC; } @@ -222,10 +259,6 @@ auto main(int argc, char *argv[]) -> int { setrlimit(RLIMIT_STACK, &stalim); setrlimit(RLIMIT_CPU, &timlim); - if (! workdir.empty()) { - execTarget(workdir, stdinRedirect, runCmd); - } - if (execlp("bash", "bash", "-c", runCmd.c_str(), NULL) == -1) { perror("execlp"); _Exit(RS_FAIL); From 8ed4c65a3250d19f09bc93c0d904d44e4808d731 Mon Sep 17 00:00:00 2001 From: "GitHub Action (clang-format)" Date: Mon, 13 Jul 2026 07:59:19 +0000 Subject: [PATCH 05/32] style: format codes --- unix/watcher_linux.cpp | 2 -- unix/watcher_unix.cpp | 23 ++++++----------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/unix/watcher_linux.cpp b/unix/watcher_linux.cpp index 7a07f565..79fd6e41 100644 --- a/unix/watcher_linux.cpp +++ b/unix/watcher_linux.cpp @@ -97,5 +97,3 @@ ssize_t calculateStaticMemoryUsage(const std::string &fileName) { ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 * 1024; } size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss * 1024; } - - diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 1148647a..df0f4ae1 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -93,23 +93,12 @@ auto main(int argc, char *argv[]) -> int { std::vector args; - auto add = [&](auto... xs) { - ((args.push_back(xs)), ...); - }; - - add("bwrap", - "--dev", "/dev", - "--proc", "/proc", - "--ro-bind", "/usr", "/usr", - "--symlink", "/usr/lib", "/lib", - "--symlink", "/usr/lib64", "/lib64", - "--symlink", "/usr/bin", "/bin", - "--symlink", "/usr/sbin", "/sbin", - "--tmpfs", "/tmp", - "--unshare-all", - "--die-with-parent", - "--chdir", workdir.c_str(), - "--bind", workdir.c_str(), workdir.c_str()); + auto add = [&](auto... xs) { ((args.push_back(xs)), ...); }; + + add("bwrap", "--dev", "/dev", "--proc", "/proc", "--ro-bind", "/usr", "/usr", "--symlink", "/usr/lib", + "/lib", "--symlink", "/usr/lib64", "/lib64", "--symlink", "/usr/bin", "/bin", "--symlink", + "/usr/sbin", "/sbin", "--tmpfs", "/tmp", "--unshare-all", "--die-with-parent", "--chdir", + workdir.c_str(), "--bind", workdir.c_str(), workdir.c_str()); if (! stdinRedirect.empty()) { add("--ro-bind", stdinRedirect.c_str(), stdinRedirect.c_str()); From 6c6f8305b6805b7781fa766f5952aac059cd08ac Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 16:38:49 +0800 Subject: [PATCH 06/32] fix typo --- unix/watcher_unix.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index df0f4ae1..2730b83d 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -95,10 +95,19 @@ auto main(int argc, char *argv[]) -> int { auto add = [&](auto... xs) { ((args.push_back(xs)), ...); }; - add("bwrap", "--dev", "/dev", "--proc", "/proc", "--ro-bind", "/usr", "/usr", "--symlink", "/usr/lib", - "/lib", "--symlink", "/usr/lib64", "/lib64", "--symlink", "/usr/bin", "/bin", "--symlink", - "/usr/sbin", "/sbin", "--tmpfs", "/tmp", "--unshare-all", "--die-with-parent", "--chdir", - workdir.c_str(), "--bind", workdir.c_str(), workdir.c_str()); + add("bwrap"); + add("--dev", "/dev"); + add("--proc", "/proc"); + add("--ro-bind", "/usr", "/usr"); + add("--symlink", "/usr/lib", "/lib"); + add("--symlink", "/usr/lib64", "/lib64"); + add("--symlink", "/usr/bin", "/bin"); + add("--symlink", "/usr/sbin", "/sbin"); + add("--tmpfs", "/tmp"); + add("--unshare-all"); + add("--die-with-parent"); + add("--chdir", workdir.c_str()); + add("--bind", workdir.c_str(), workdir.c_str()); if (! stdinRedirect.empty()) { add("--ro-bind", stdinRedirect.c_str(), stdinRedirect.c_str()); From fa0228be11cf3b0e264703d42a381bdd7dcddd93 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 17:04:22 +0800 Subject: [PATCH 07/32] feat: add process limit --- unix/watcher_unix.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 2730b83d..77379d0b 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -238,7 +238,7 @@ auto main(int argc, char *argv[]) -> int { _Exit(RS_FAIL); } - rlimit memlim{}, stalim{}, timlim{}; + rlimit memlim{}, stalim{}, timlim{}, nproclim{}; if (memoryLimitMib > 0) { memlim = (rlimit){(rlim_t)actualMemoryRLimit, (rlim_t)actualMemoryRLimit}; @@ -256,6 +256,8 @@ auto main(int argc, char *argv[]) -> int { setrlimit(RLIMIT_AS, &memlim); setrlimit(RLIMIT_STACK, &stalim); setrlimit(RLIMIT_CPU, &timlim); + nproclim = (rlimit){(rlim_t)16, (rlim_t)16}; + setrlimit(RLIMIT_NPROC, &nproclim); if (execlp("bash", "bash", "-c", runCmd.c_str(), NULL) == -1) { perror("execlp"); From 541eb5f0a32b7c58636f3d9f562387152bc8a392 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 17:44:41 +0800 Subject: [PATCH 08/32] upd --- unix/test/scripts/redirect.py | 3 ++- unix/test/scripts/tle.py | 3 ++- unix/watcher_linux.cpp | 1 - unix/watcher_macos.mm | 2 -- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/unix/test/scripts/redirect.py b/unix/test/scripts/redirect.py index bcca2b72..e27cc298 100644 --- a/unix/test/scripts/redirect.py +++ b/unix/test/scripts/redirect.py @@ -12,7 +12,8 @@ p = subprocess.Popen(["./watcher_unix", "./add", "", tmpin, tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "2000"], shell=False) -p.wait() +time.sleep(2) +p.kill() assert(p.returncode == 0) diff --git a/unix/test/scripts/tle.py b/unix/test/scripts/tle.py index cdfffa4e..81ecb508 100644 --- a/unix/test/scripts/tle.py +++ b/unix/test/scripts/tle.py @@ -8,6 +8,7 @@ p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", ".", "5000"], shell=False) -p.wait() +time.sleep(5) +p.kill() assert(p.returncode == 3) diff --git a/unix/watcher_linux.cpp b/unix/watcher_linux.cpp index 79fd6e41..cd113c8b 100644 --- a/unix/watcher_linux.cpp +++ b/unix/watcher_linux.cpp @@ -19,7 +19,6 @@ #include #include #include -#include static auto read_elf_ident(int fd, char *e_ident) -> bool { if (read(fd, e_ident, EI_NIDENT) != EI_NIDENT) { diff --git a/unix/watcher_macos.mm b/unix/watcher_macos.mm index e4ea0b6c..10f480c5 100644 --- a/unix/watcher_macos.mm +++ b/unix/watcher_macos.mm @@ -146,5 +146,3 @@ ssize_t calculateStaticMemoryUsage(const std::string &fileName) { ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 * (isAppleSilicon ? 4 : 1); } size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss / (isAppleSilicon ? 4 : 1); } - - From 1f2a6a46aacbd989daa26c95ee43d6833cc2b67a Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 17:45:23 +0800 Subject: [PATCH 09/32] upd --- unix/test/scripts/redirect.py | 2 +- unix/test/scripts/tle.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/test/scripts/redirect.py b/unix/test/scripts/redirect.py index e27cc298..262da41c 100644 --- a/unix/test/scripts/redirect.py +++ b/unix/test/scripts/redirect.py @@ -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", "", "", ".", "2000"], shell=False) +p = subprocess.Popen(["./watcher_unix", "./add", "", tmpin, tmpout, tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False) time.sleep(2) p.kill() diff --git a/unix/test/scripts/tle.py b/unix/test/scripts/tle.py index 81ecb508..12a51539 100644 --- a/unix/test/scripts/tle.py +++ b/unix/test/scripts/tle.py @@ -6,7 +6,7 @@ tmpout = f"_tmpout_{pid}" tmperr = f"_tmperr_{pid}" -p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", ".", "5000"], shell=False) +p = subprocess.Popen(["./watcher_unix", "./tle", "", "", "", tmperr, "1000", "100", "1000", "100", "", "", ".", "0"], shell=False) time.sleep(5) p.kill() From 84d139bdc6b32286fb35ab867a7a670a6d5b0a92 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 19:27:09 +0800 Subject: [PATCH 10/32] use exit --- unix/watcher_unix.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 77379d0b..6dd29549 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -28,7 +28,7 @@ static volatile sig_atomic_t timedOut; static void cleanUp(int /*dummy*/) { kill(pid, SIGKILL); - _Exit(0); + exit(0); } static void alarmHandler(int /*dummy*/) { timedOut = 1; } @@ -225,17 +225,17 @@ auto main(int argc, char *argv[]) -> int { std::string finalStdinRedirect = stdinRedirect.empty() ? "/dev/null" : stdinRedirect; if (freopen(finalStdinRedirect.c_str(), "r", stdin) == NULL) { perror("freopen stdin"); - _Exit(RS_FAIL); + exit(RS_FAIL); } std::string finalStdoutRedirect = stdoutRedirect.empty() ? "/dev/null" : stdoutRedirect; if (freopen(finalStdoutRedirect.c_str(), "w", stdout) == NULL) { perror("freopen stdout"); - _Exit(RS_FAIL); + exit(RS_FAIL); } std::string finalStderrRedirect = stderrRedirect.empty() ? "/dev/null" : stderrRedirect; if (freopen(finalStderrRedirect.c_str(), "w", stderr) == NULL) { perror("freopen stderr"); - _Exit(RS_FAIL); + exit(RS_FAIL); } rlimit memlim{}, stalim{}, timlim{}, nproclim{}; @@ -261,7 +261,7 @@ auto main(int argc, char *argv[]) -> int { if (execlp("bash", "bash", "-c", runCmd.c_str(), NULL) == -1) { perror("execlp"); - _Exit(RS_FAIL); + exit(RS_FAIL); } } From c56bd3be97aeadc7fe7a3e6d687e128fd1828d09 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 21:13:20 +0800 Subject: [PATCH 11/32] feat: use thread --- unix/watcher_unix.cpp | 62 ++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 6dd29549..6abc081d 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -6,8 +6,10 @@ * */ +#include #include #include +#include #include #include #include @@ -20,19 +22,20 @@ #include #include #include +#ifdef __linux__ +#include +#endif +#include #include #include static int pid; -static volatile sig_atomic_t timedOut; static void cleanUp(int /*dummy*/) { kill(pid, SIGKILL); exit(0); } -static void alarmHandler(int /*dummy*/) { timedOut = 1; } - extern void initWatcher(); extern ssize_t calculateStaticMemoryUsage(const std::string &); extern ssize_t getMemoryRLimit(ssize_t memoryLimitInMB); @@ -155,41 +158,50 @@ auto main(int argc, char *argv[]) -> int { pid = fork(); if (pid > 0) { - // Parent process signal(SIGINT, cleanUp); signal(SIGABRT, cleanUp); signal(SIGTERM, cleanUp); - struct sigaction sa; - sa.sa_handler = alarmHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(SIGALRM, &sa, nullptr); - long long wallClockMs = timeLimitMs + extraTimeMs; - struct itimerval timer; - timer.it_value.tv_sec = wallClockMs / 1000; - timer.it_value.tv_usec = (wallClockMs % 1000) * 1000; - timer.it_interval = {0, 0}; - setitimer(ITIMER_REAL, &timer, nullptr); - struct rusage usage{}; - int status = 0; + int childPfd = -1; +#ifdef __linux__ + childPfd = syscall(SYS_pidfd_open, pid, 0); +#endif - if (wait4(pid, &status, 0, &usage) == -1) { - if (errno == EINTR && timedOut) { - kill(pid, SIGKILL); - wait4(pid, nullptr, 0, nullptr); - printf("-1\n-1\n"); - return RS_TLE; + auto timedOut = std::make_shared>(false); + auto done = std::make_shared>(false); + + std::thread([=]() { + std::this_thread::sleep_for(std::chrono::milliseconds(wallClockMs)); + if (!*done) { + *timedOut = true; +#ifdef __linux__ + if (childPfd >= 0) + syscall(SYS_pidfd_send_signal, childPfd, SIGKILL, NULL, 0); + else +#endif + kill(pid, SIGKILL); } + }).detach(); + + struct rusage usage{}; + int status; + + while (wait4(pid, &status, 0, &usage) == -1) { + if (errno == EINTR) + continue; printf("-1\n-1\n"); perror("wait4"); return RS_FAIL; } - struct itimerval disable = {{0, 0}, {0, 0}}; - setitimer(ITIMER_REAL, &disable, nullptr); + *done = true; + + if (*timedOut) { + printf("-1\n-1\n"); + return RS_TLE; + } if (WIFEXITED(status)) { long long timeUsedMs = From 7da06b27b8e73a89240a98fa25e2ddf6505e7744 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 21:13:27 +0800 Subject: [PATCH 12/32] install bwrap --- .github/workflows/watcher_linux.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/watcher_linux.yml b/.github/workflows/watcher_linux.yml index 0967e6ca..05c0e907 100644 --- a/.github/workflows/watcher_linux.yml +++ b/.github/workflows/watcher_linux.yml @@ -23,6 +23,9 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Install bubblewrap + run: sudo apt-get update && sudo apt-get install -y bubblewrap + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From d9227bd5b330e2ae3e5d618104c58c7454e3d38b Mon Sep 17 00:00:00 2001 From: "GitHub Action (clang-format)" Date: Mon, 13 Jul 2026 13:13:43 +0000 Subject: [PATCH 13/32] style: format codes --- unix/watcher_unix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 6abc081d..b3102636 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -174,7 +174,7 @@ auto main(int argc, char *argv[]) -> int { std::thread([=]() { std::this_thread::sleep_for(std::chrono::milliseconds(wallClockMs)); - if (!*done) { + if (! *done) { *timedOut = true; #ifdef __linux__ if (childPfd >= 0) From e7fad03d41a329c69cf5b4be0dbeb96b7aea006f Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 21:17:13 +0800 Subject: [PATCH 14/32] fix --- unix/watcher_unix.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 6abc081d..68b7f78c 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -158,6 +159,7 @@ auto main(int argc, char *argv[]) -> int { pid = fork(); if (pid > 0) { + // Parent process signal(SIGINT, cleanUp); signal(SIGABRT, cleanUp); signal(SIGTERM, cleanUp); @@ -186,7 +188,7 @@ auto main(int argc, char *argv[]) -> int { }).detach(); struct rusage usage{}; - int status; + int status = 0; while (wait4(pid, &status, 0, &usage) == -1) { if (errno == EINTR) From 19a089f538465a95ae660f3e8e174dc581191d2b Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 21:20:23 +0800 Subject: [PATCH 15/32] upd --- unix/watcher_unix.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index fc26ddfe..a625616e 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -30,9 +30,9 @@ #include #include -static int pid; +int pid; -static void cleanUp(int /*dummy*/) { +void cleanUp(int /*dummy*/) { kill(pid, SIGKILL); exit(0); } @@ -41,6 +41,7 @@ extern void initWatcher(); extern ssize_t calculateStaticMemoryUsage(const std::string &); extern ssize_t getMemoryRLimit(ssize_t memoryLimitInMB); extern size_t getMaxRSSInByte(long ru_maxrss); + enum : int { RS_AC = 0, RS_FAIL = 1, @@ -190,9 +191,7 @@ auto main(int argc, char *argv[]) -> int { struct rusage usage{}; int status = 0; - while (wait4(pid, &status, 0, &usage) == -1) { - if (errno == EINTR) - continue; + if (wait4(pid, &status, 0, &usage) == -1) { printf("-1\n-1\n"); perror("wait4"); return RS_FAIL; From cfbc54f7998e6ec9acdc8b0a6cf1bad4351dbbe1 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 21:27:04 +0800 Subject: [PATCH 16/32] fix cmakelist --- unix/test/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unix/test/CMakeLists.txt b/unix/test/CMakeLists.txt index 844f8713..a6ec29eb 100644 --- a/unix/test/CMakeLists.txt +++ b/unix/test/CMakeLists.txt @@ -6,12 +6,16 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +find_package(Threads REQUIRED) + if(APPLE) add_executable(watcher_unix ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_unix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_macos.mm) else() add_executable(watcher_unix ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_unix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_linux.cpp) endif() +target_link_libraries(watcher_unix PRIVATE Threads::Threads) + add_executable(hello hello.c) add_executable(mle_static mle_static.c) file(COPY hello.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) From e6246356f0cfd1223e28f7bf7d185a2693354ba2 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Mon, 13 Jul 2026 21:33:16 +0800 Subject: [PATCH 17/32] update cmakelist --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0dba817..4c064e1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,8 @@ if(APPLE) set(GUI_TYPE MACOSX_BUNDLE) endif() +find_package(Threads REQUIRED) + if(WIN32) add_executable(lemon ${GUI_TYPE} ${LEMON_FULL_SOURCES}) else() @@ -279,6 +281,7 @@ else() else() add_executable(watcher_unix unix/watcher_unix.cpp unix/watcher_linux.cpp) endif() + target_link_libraries(watcher_unix PRIVATE Threads::Threads) configure_file(unix/watcher.qrc ${CMAKE_BINARY_DIR} COPYONLY) list(APPEND LEMON_FULL_SOURCES ${CMAKE_BINARY_DIR}/watcher.qrc) add_executable(lemon ${GUI_TYPE} ${LEMON_FULL_SOURCES}) From ee1e78cb29e04ae6c1a48281453be0bfe64625da Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Tue, 14 Jul 2026 14:51:21 +0800 Subject: [PATCH 18/32] use pidfd and timerfd --- CMakeLists.txt | 3 -- unix/test/CMakeLists.txt | 4 -- unix/watcher_unix.cpp | 87 +++++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c064e1c..f0dba817 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,8 +271,6 @@ if(APPLE) set(GUI_TYPE MACOSX_BUNDLE) endif() -find_package(Threads REQUIRED) - if(WIN32) add_executable(lemon ${GUI_TYPE} ${LEMON_FULL_SOURCES}) else() @@ -281,7 +279,6 @@ else() else() add_executable(watcher_unix unix/watcher_unix.cpp unix/watcher_linux.cpp) endif() - target_link_libraries(watcher_unix PRIVATE Threads::Threads) configure_file(unix/watcher.qrc ${CMAKE_BINARY_DIR} COPYONLY) list(APPEND LEMON_FULL_SOURCES ${CMAKE_BINARY_DIR}/watcher.qrc) add_executable(lemon ${GUI_TYPE} ${LEMON_FULL_SOURCES}) diff --git a/unix/test/CMakeLists.txt b/unix/test/CMakeLists.txt index a6ec29eb..844f8713 100644 --- a/unix/test/CMakeLists.txt +++ b/unix/test/CMakeLists.txt @@ -6,16 +6,12 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -find_package(Threads REQUIRED) - if(APPLE) add_executable(watcher_unix ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_unix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_macos.mm) else() add_executable(watcher_unix ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_unix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../watcher_linux.cpp) endif() -target_link_libraries(watcher_unix PRIVATE Threads::Threads) - add_executable(hello hello.c) add_executable(mle_static mle_static.c) file(COPY hello.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index a625616e..3044d22c 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -6,15 +6,12 @@ * */ -#include #include #include -#include #include #include #include #include -#include #include #include #include @@ -24,9 +21,11 @@ #include #include #ifdef __linux__ +#include +#include #include +#include #endif -#include #include #include @@ -157,52 +156,76 @@ auto main(int argc, char *argv[]) -> int { ssize_t actualMemoryRLimit = getMemoryRLimit(memoryLimitMib); +#ifdef __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 signal(SIGINT, cleanUp); signal(SIGABRT, cleanUp); signal(SIGTERM, cleanUp); - long long wallClockMs = timeLimitMs + extraTimeMs; - - int childPfd = -1; -#ifdef __linux__ - childPfd = syscall(SYS_pidfd_open, pid, 0); -#endif - - auto timedOut = std::make_shared>(false); - auto done = std::make_shared>(false); + struct rusage usage{}; + int status; - std::thread([=]() { - std::this_thread::sleep_for(std::chrono::milliseconds(wallClockMs)); - if (! *done) { - *timedOut = true; #ifdef __linux__ - if (childPfd >= 0) - syscall(SYS_pidfd_send_signal, childPfd, SIGKILL, NULL, 0); - else -#endif - kill(pid, SIGKILL); - } - }).detach(); - - struct rusage usage{}; - int status = 0; + long long wallClockMs = timeLimitMs + extraTimeMs; - if (wait4(pid, &status, 0, &usage) == -1) { + int timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (timerFd < 0) { + perror("timerfd_create"); printf("-1\n-1\n"); - perror("wait4"); return RS_FAIL; } - *done = true; + struct itimerspec ts{}; + ts.it_value.tv_sec = wallClockMs / 1000; + ts.it_value.tv_nsec = (wallClockMs % 1000) * 1000000; + timerfd_settime(timerFd, 0, &ts, nullptr); + + struct pollfd pfds[2]{}; + pfds[0].fd = childPfd; + pfds[0].events = POLLIN; + pfds[1].fd = timerFd; + pfds[1].events = POLLIN; + + poll(pfds, 2, -1); + + bool childExited = pfds[0].revents & POLLIN; + bool timedOut = pfds[1].revents & POLLIN; + + if (! childExited) + kill(pid, SIGKILL); + + wait4(pid, &status, 0, &usage); - if (*timedOut) { + close(childPfd); + close(timerFd); + + 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 = From 91fe7368e7b501cba2ada9e6bfcd8a71106e0e88 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Tue, 14 Jul 2026 14:51:30 +0800 Subject: [PATCH 19/32] fix ci --- .github/workflows/watcher_linux.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/watcher_linux.yml b/.github/workflows/watcher_linux.yml index 05c0e907..72f98ea1 100644 --- a/.github/workflows/watcher_linux.yml +++ b/.github/workflows/watcher_linux.yml @@ -12,6 +12,7 @@ on: env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release + LEMONLIME_SANDBOXED: 1 jobs: build: @@ -23,9 +24,6 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install bubblewrap - run: sudo apt-get update && sudo apt-get install -y bubblewrap - - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 0b32245a774c724e2ffbe1ef708f2035f9847f6a Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Tue, 14 Jul 2026 20:42:06 +0800 Subject: [PATCH 20/32] make diff beautiful --- unix/watcher_unix.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 3044d22c..2fe54ad7 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -174,12 +174,12 @@ auto main(int argc, char *argv[]) -> int { } if (pid > 0) { + // Parent process signal(SIGINT, cleanUp); signal(SIGABRT, cleanUp); signal(SIGTERM, cleanUp); - struct rusage usage{}; - int status; + int status = 0; #ifdef __linux__ long long wallClockMs = timeLimitMs + extraTimeMs; From 7aa8999b84b8812a9de4b9fb42d2de9626086857 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Wed, 15 Jul 2026 16:42:34 +0800 Subject: [PATCH 21/32] use kqueue for macos --- unix/watcher_unix.cpp | 44 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 2fe54ad7..b74084ad 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -20,11 +20,13 @@ #include #include #include -#ifdef __linux__ +#if defined(__linux__) #include #include #include #include +#elif defined(__APPLE__) +#include #endif #include #include @@ -156,7 +158,7 @@ auto main(int argc, char *argv[]) -> int { ssize_t actualMemoryRLimit = getMemoryRLimit(memoryLimitMib); -#ifdef __linux__ +#if defined(__linux__) int childPfd = -1; struct clone_args args{}; args.flags = CLONE_PIDFD; @@ -181,9 +183,8 @@ auto main(int argc, char *argv[]) -> int { struct rusage usage{}; int status = 0; -#ifdef __linux__ long long wallClockMs = timeLimitMs + extraTimeMs; - +#if defined(__linux__) int timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (timerFd < 0) { perror("timerfd_create"); @@ -215,6 +216,41 @@ auto main(int argc, char *argv[]) -> int { close(childPfd); close(timerFd); + if (timedOut) { + printf("-1\n-1\n"); + return RS_TLE; + } +#elif defined(__APPLE__) + 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, NOTE_MSECONDS, wallClockMs, NULL); + + struct kevent events[2]; + int n = kevent(kq, changes, 2, events, 2, NULL); + + 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; From d40b04005956910d784dfd2502c9cc8737fdc431 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Wed, 15 Jul 2026 22:29:03 +0800 Subject: [PATCH 22/32] add timer in qt side --- src/core/processrunner_unix.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/core/processrunner_unix.cpp b/src/core/processrunner_unix.cpp index f5edf776..4a7f4f62 100644 --- a/src/core/processrunner_unix.cpp +++ b/src/core/processrunner_unix.cpp @@ -93,9 +93,18 @@ ProcessRunnerResult UnixProcessRunner::run() { return res; } - while (true) { - if (runner->waitForFinished(10)) + bool isProgramFinishedInExtraTimeLimit = false; + 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 + 1000; + while (timer.elapsed() <= killTimeLimit) { + if (runner->waitForFinished(10)) { + isProgramFinishedInExtraTimeLimit = true; break; + } QCoreApplication::processEvents(); @@ -108,6 +117,19 @@ ProcessRunnerResult UnixProcessRunner::run() { } } + if (! isProgramFinishedInExtraTimeLimit) { + runner->terminate(); + runner->waitForFinished(-1); + delete runner; + res.score = 0; + res.timeUsed = res.memoryUsed = -1; + // Watcher usually needs to handle the situation of program timeout and kill it. Therefore, it is + // abnormal for watcher to timeout itself, and report FAIL instead of TLE. + res.result = CannotStartProgram; + res.message = "Watcher time limit exceeded"; + return res; + } + { QString out = QString::fromLocal8Bit(runner->readAllStandardOutput().constData()); QTextStream stream(&out, QIODevice::ReadOnly); From 57e8334236b67b2d33e8e1deaefe10d1b9454f18 Mon Sep 17 00:00:00 2001 From: "GitHub Action (clang-format)" Date: Wed, 15 Jul 2026 14:29:20 +0000 Subject: [PATCH 23/32] style: format codes --- unix/watcher_unix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index b74084ad..fba69b6b 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -244,7 +244,7 @@ auto main(int argc, char *argv[]) -> int { timedOut = true; } - if (timedOut && !childExited) + if (timedOut && ! childExited) kill(pid, SIGKILL); wait4(pid, &status, 0, &usage); From b75771a6ecd72aa235e58b6f871215cf1b1b66fd Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Wed, 15 Jul 2026 22:36:32 +0800 Subject: [PATCH 24/32] fix --- src/core/processrunner_unix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/processrunner_unix.cpp b/src/core/processrunner_unix.cpp index 4a7f4f62..810d4eb8 100644 --- a/src/core/processrunner_unix.cpp +++ b/src/core/processrunner_unix.cpp @@ -99,7 +99,7 @@ ProcessRunnerResult UnixProcessRunner::run() { // 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 + 1000; + long long killTimeLimit = (config.timeLimit + 999) / 1000 * 1000 + extraTimeMs + 1000; while (timer.elapsed() <= killTimeLimit) { if (runner->waitForFinished(10)) { isProgramFinishedInExtraTimeLimit = true; From ca22673cb1b9b3904d3490bae45fcbf88b4e1910 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Thu, 16 Jul 2026 07:58:09 +0800 Subject: [PATCH 25/32] fix macos --- unix/watcher_unix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index fba69b6b..e5dd8962 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -230,7 +230,7 @@ auto main(int argc, char *argv[]) -> int { 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, NOTE_MSECONDS, wallClockMs, NULL); + EV_SET(&changes[1], 0, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, wallClockMs, NULL); struct kevent events[2]; int n = kevent(kq, changes, 2, events, 2, NULL); From 92af944f6f68f45b13bf7b50702448cc583c851e Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Thu, 16 Jul 2026 08:44:09 +0800 Subject: [PATCH 26/32] add sleep tle test --- unix/test/CMakeLists.txt | 2 ++ unix/test/scripts/sleep_tle.py | 15 +++++++++++++++ unix/test/sleep_tle.c | 6 ++++++ 3 files changed, 23 insertions(+) create mode 100644 unix/test/scripts/sleep_tle.py create mode 100644 unix/test/sleep_tle.c diff --git a/unix/test/CMakeLists.txt b/unix/test/CMakeLists.txt index 844f8713..78991477 100644 --- a/unix/test/CMakeLists.txt +++ b/unix/test/CMakeLists.txt @@ -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}) @@ -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) diff --git a/unix/test/scripts/sleep_tle.py b/unix/test/scripts/sleep_tle.py new file mode 100644 index 00000000..cca950b5 --- /dev/null +++ b/unix/test/scripts/sleep_tle.py @@ -0,0 +1,15 @@ +import subprocess +import time +import os + +pid = os.getpid() +tmperr = f"_tmperr_{pid}" + +p = subprocess.Popen(["./watcher_unix", "./sleep_tle", "", "", "", tmperr, + "1000", "100", "1000", "100", "", "", ".", "0"], + shell=False) + +time.sleep(3) +p.kill() + +assert(p.returncode == 3) diff --git a/unix/test/sleep_tle.c b/unix/test/sleep_tle.c new file mode 100644 index 00000000..e13b08b0 --- /dev/null +++ b/unix/test/sleep_tle.c @@ -0,0 +1,6 @@ +#include + +int main() { + sleep(10); + return 0; +} From 48790e27ff4ff4da9dc9db3d43237524b5db3482 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Thu, 16 Jul 2026 12:08:33 +0800 Subject: [PATCH 27/32] use {} to init --- unix/watcher_unix.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index e5dd8962..c0922249 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -88,14 +88,13 @@ auto main(int argc, char *argv[]) -> int { #ifdef __linux__ if (! getenv("LEMONLIME_SANDBOXED")) { - char selfExe[4096]; + char selfExe[4096] = {}; ssize_t len = readlink("/proc/self/exe", selfExe, sizeof(selfExe) - 1); - if (len <= 0 || len >= (ssize_t)sizeof(selfExe) - 1) { + if (len <= 0) { fprintf(stderr, "Cannot determine self executable path\n"); printf("-1\n-1\n"); return RS_FAIL; } - selfExe[len] = '\0'; std::vector args; From a31fb8095899ee71345fb5a0cbbad57214993430 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Thu, 23 Jul 2026 08:52:39 +0800 Subject: [PATCH 28/32] add 1s to cpu time limit --- unix/watcher_unix.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index c0922249..86920f09 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -320,8 +320,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; + // Calculate time limit in seconds, rounding up and add 1s (because it limit cpu time, not user time) + rlim_t soft_time_limit_sec = (timeLimitMs + 999) / 1000 + 1; timlim = (rlimit){soft_time_limit_sec, soft_time_limit_sec + 1}; // Soft limit + 1 for hard limit setrlimit(RLIMIT_AS, &memlim); From a3b61ae13f6528027892fabbd6d85cfbbbf995a6 Mon Sep 17 00:00:00 2001 From: ZnPdCo Date: Thu, 23 Jul 2026 08:53:10 +0800 Subject: [PATCH 29/32] remove hard limit --- unix/watcher_unix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 86920f09..30d53829 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -322,7 +322,7 @@ auto main(int argc, char *argv[]) -> int { // Calculate time limit in seconds, rounding up and add 1s (because it limit cpu time, not user time) rlim_t soft_time_limit_sec = (timeLimitMs + 999) / 1000 + 1; - timlim = (rlimit){soft_time_limit_sec, soft_time_limit_sec + 1}; // Soft limit + 1 for hard limit + timlim = (rlimit){soft_time_limit_sec, soft_time_limit_sec}; setrlimit(RLIMIT_AS, &memlim); setrlimit(RLIMIT_STACK, &stalim); From 37daeeade89f2abd50d9921ed54241a0099eabda Mon Sep 17 00:00:00 2001 From: ZnPdCo <66149312+ZnPdCo@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:31:54 +0800 Subject: [PATCH 30/32] Remove CPU time limit --- unix/watcher_unix.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 30d53829..8da65eee 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -309,7 +309,7 @@ auto main(int argc, char *argv[]) -> int { exit(RS_FAIL); } - rlimit memlim{}, stalim{}, timlim{}, nproclim{}; + rlimit memlim{}, stalim{}, nproclim{}; if (memoryLimitMib > 0) { memlim = (rlimit){(rlim_t)actualMemoryRLimit, (rlim_t)actualMemoryRLimit}; @@ -320,13 +320,8 @@ auto main(int argc, char *argv[]) -> int { stalim = (rlimit){(rlim_t)2147483647LL, (rlim_t)2147483647LL}; } - // Calculate time limit in seconds, rounding up and add 1s (because it limit cpu time, not user time) - rlim_t soft_time_limit_sec = (timeLimitMs + 999) / 1000 + 1; - timlim = (rlimit){soft_time_limit_sec, soft_time_limit_sec}; - setrlimit(RLIMIT_AS, &memlim); setrlimit(RLIMIT_STACK, &stalim); - setrlimit(RLIMIT_CPU, &timlim); nproclim = (rlimit){(rlim_t)16, (rlim_t)16}; setrlimit(RLIMIT_NPROC, &nproclim); From a78b46776c4c7b870a838ae8ffb04dcfb8e6906f Mon Sep 17 00:00:00 2001 From: ZnPdCo <66149312+ZnPdCo@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:15:27 +0800 Subject: [PATCH 31/32] remove obsolete OS --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index e0a35eb5..eb1cc93b 100644 --- a/README.md +++ b/README.md @@ -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 | From 08a4a1812ddd5620ff063dc722562f188ff939f6 Mon Sep 17 00:00:00 2001 From: ZnPdCo <66149312+ZnPdCo@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:16:55 +0800 Subject: [PATCH 32/32] remove nproc --- unix/watcher_unix.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp index 8da65eee..971fd0d8 100644 --- a/unix/watcher_unix.cpp +++ b/unix/watcher_unix.cpp @@ -309,7 +309,7 @@ auto main(int argc, char *argv[]) -> int { exit(RS_FAIL); } - rlimit memlim{}, stalim{}, nproclim{}; + rlimit memlim{}, stalim{}; if (memoryLimitMib > 0) { memlim = (rlimit){(rlim_t)actualMemoryRLimit, (rlim_t)actualMemoryRLimit}; @@ -322,8 +322,6 @@ auto main(int argc, char *argv[]) -> int { setrlimit(RLIMIT_AS, &memlim); setrlimit(RLIMIT_STACK, &stalim); - nproclim = (rlimit){(rlim_t)16, (rlim_t)16}; - setrlimit(RLIMIT_NPROC, &nproclim); if (execlp("bash", "bash", "-c", runCmd.c_str(), NULL) == -1) { perror("execlp");