diff --git a/.github/workflows/watcher_linux.yml b/.github/workflows/watcher_linux.yml new file mode 100644 index 0000000..2de19a2 --- /dev/null +++ b/.github/workflows/watcher_linux.yml @@ -0,0 +1,40 @@ +name: Watcher for Linux Test + +on: + push: + branches: [ "master" ] + paths: [ "unix/**" ] + pull_request: + branches: [ "master" ] + paths: [ "unix/**" ] + workflow_dispatch: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - 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 + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} unix/test + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --verbose -C ${{env.BUILD_TYPE}} + diff --git a/.github/workflows/watcher_macos.yml b/.github/workflows/watcher_macos.yml new file mode 100644 index 0000000..22d25dd --- /dev/null +++ b/.github/workflows/watcher_macos.yml @@ -0,0 +1,40 @@ +name: Watcher for MacOS Test + +on: + push: + branches: [ "master" ] + paths: [ "unix/**" ] + pull_request: + branches: [ "master" ] + paths: [ "unix/**" ] + workflow_dispatch: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: macos-13 + + steps: + - uses: actions/checkout@v3 + + - 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 + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} unix/test + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --verbose -C ${{env.BUILD_TYPE}} + diff --git a/CMakeLists.txt b/CMakeLists.txt index 0226bf2..ecb25bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,9 +242,9 @@ if(WIN32) add_executable(lemon ${GUI_TYPE} ${LEMON_FULL_SOURCES}) else() if(APPLE) - add_executable(watcher_unix unix/watcher_macos.mm) + add_executable(watcher_unix unix/watcher_unix.cpp unix/watcher_macos.mm) else() - add_executable(watcher_unix unix/watcher_linux.cpp) + add_executable(watcher_unix unix/watcher_unix.cpp unix/watcher_linux.cpp) endif() configure_file(unix/watcher.qrc ${CMAKE_BINARY_DIR} COPYONLY) list(APPEND LEMON_FULL_SOURCES ${CMAKE_BINARY_DIR}/watcher.qrc) diff --git a/unix/test/CMakeLists.txt b/unix/test/CMakeLists.txt new file mode 100644 index 0000000..844f871 --- /dev/null +++ b/unix/test/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.10.0) + +project(lemon_watcher_unix_test) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +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() + +add_executable(hello hello.c) +add_executable(mle_static mle_static.c) +file(COPY hello.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +add_executable(tle tle.c) +add_executable(add add.c) +add_executable(re re.c) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/scripts DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +enable_testing() +add_test(NAME watcher_run_c_test COMMAND python3 scripts/run.py) +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_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) +add_test(NAME watcher_redirect_IO_test COMMAND python3 scripts/redirect.py) +add_test(NAME watcher_RE_test COMMAND python3 scripts/runtimeerr.py) diff --git a/unix/test/add.c b/unix/test/add.c new file mode 100644 index 0000000..e4fd70d --- /dev/null +++ b/unix/test/add.c @@ -0,0 +1,10 @@ +#include + +int main() { + int a, b; + + scanf("%d%d", &a, &b); + printf("%d\n", a + b); + + return 0; +} \ No newline at end of file diff --git a/unix/test/hello.c b/unix/test/hello.c new file mode 100644 index 0000000..d5e784a --- /dev/null +++ b/unix/test/hello.c @@ -0,0 +1,7 @@ +#include + +int main() { + printf("Hello World!\n"); + + return 0; +} \ No newline at end of file diff --git a/unix/test/hello.sh b/unix/test/hello.sh new file mode 100644 index 0000000..5543a6b --- /dev/null +++ b/unix/test/hello.sh @@ -0,0 +1 @@ +echo "Hello World!" \ No newline at end of file diff --git a/unix/test/mle_static.c b/unix/test/mle_static.c new file mode 100644 index 0000000..66da9fd --- /dev/null +++ b/unix/test/mle_static.c @@ -0,0 +1,9 @@ +#include + +int a[100000000]; +// 400 MB + +int main() { + puts("Hello World!"); + return 0; +} \ No newline at end of file diff --git a/unix/test/re.c b/unix/test/re.c new file mode 100644 index 0000000..85c8c4d --- /dev/null +++ b/unix/test/re.c @@ -0,0 +1,4 @@ +int main() { + int *x = (void *)0; + return *x; +} \ No newline at end of file diff --git a/unix/test/scripts/mle_static.py b/unix/test/scripts/mle_static.py new file mode 100644 index 0000000..564cb5c --- /dev/null +++ b/unix/test/scripts/mle_static.py @@ -0,0 +1,9 @@ +import subprocess + +cmd = "\"%s\" %s" % ("./mle_static", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "380"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +assert(stdout[0] == 48) \ No newline at end of file diff --git a/unix/test/scripts/redirect.py b/unix/test/scripts/redirect.py new file mode 100644 index 0000000..999fda5 --- /dev/null +++ b/unix/test/scripts/redirect.py @@ -0,0 +1,18 @@ +import subprocess +import os +import time + +with open('_tmpin', 'w') as f: + f.writelines(['1 1']) + +cmd = "\"%s\" %s" % ("./add", "") +p = subprocess.Popen(["./watcher_unix", cmd, "_tmpin", "_tmpout", "_tmperr", "1000", "100"], shell=False) + +time.sleep(2) +p.kill() + +assert(p.returncode == 0) + +assert(os.path.exists('_tmpout')) +with open('_tmpout', 'r') as f: + assert(f.read() == "2\n") diff --git a/unix/test/scripts/run.py b/unix/test/scripts/run.py new file mode 100644 index 0000000..a98d1f1 --- /dev/null +++ b/unix/test/scripts/run.py @@ -0,0 +1,10 @@ +import subprocess + +cmd = "\"%s\" %s" % ("./hello", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +out_str = stdout.decode() +assert(out_str.split('\n')[0] == 'Hello World!') \ No newline at end of file diff --git a/unix/test/scripts/run_sh.py b/unix/test/scripts/run_sh.py new file mode 100644 index 0000000..759b93e --- /dev/null +++ b/unix/test/scripts/run_sh.py @@ -0,0 +1,10 @@ +import subprocess + +cmd = "\"%s\" %s" % ("/bin/bash", "hello.sh") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +out_str = stdout.decode() +assert(out_str.split('\n')[0] == 'Hello World!') \ No newline at end of file diff --git a/unix/test/scripts/runtimeerr.py b/unix/test/scripts/runtimeerr.py new file mode 100644 index 0000000..07d67d8 --- /dev/null +++ b/unix/test/scripts/runtimeerr.py @@ -0,0 +1,7 @@ +import subprocess +import time + +cmd = "\"%s\" %s" % ("./re", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False) + +assert(p.wait() == 2) diff --git a/unix/test/scripts/space.py b/unix/test/scripts/space.py new file mode 100644 index 0000000..8316d47 --- /dev/null +++ b/unix/test/scripts/space.py @@ -0,0 +1,13 @@ +import subprocess +import shutil + +shutil.copy("./hello", "./he llo") + +cmd = "\"%s\" %s" % ("./he llo", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +out_str = stdout.decode() +assert(out_str.split('\n')[0] == 'Hello World!') \ No newline at end of file diff --git a/unix/test/scripts/symlink_abs.py b/unix/test/scripts/symlink_abs.py new file mode 100644 index 0000000..7e2d126 --- /dev/null +++ b/unix/test/scripts/symlink_abs.py @@ -0,0 +1,16 @@ +import subprocess +import os + +if os.path.exists("hello_s_abs"): + os.remove("hello_s_abs") + +os.symlink(os.path.join(os.getcwd(), "hello"), "hello_s_abs") + +cmd = "\"%s\" %s" % ("./hello_s_abs", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +out_str = stdout.decode() +assert(out_str.split('\n')[0] == 'Hello World!') \ No newline at end of file diff --git a/unix/test/scripts/symlink_rel.py b/unix/test/scripts/symlink_rel.py new file mode 100644 index 0000000..fd947c5 --- /dev/null +++ b/unix/test/scripts/symlink_rel.py @@ -0,0 +1,16 @@ +import subprocess +import os + +if os.path.exists("hello_s_rel"): + os.remove("hello_s_rel") + +os.symlink("hello", "hello_s_rel") + +cmd = "\"%s\" %s" % ("./hello_s_rel", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +out_str = stdout.decode() +assert(out_str.split('\n')[0] == 'Hello World!') \ No newline at end of file diff --git a/unix/test/scripts/tle.py b/unix/test/scripts/tle.py new file mode 100644 index 0000000..d6f6c5c --- /dev/null +++ b/unix/test/scripts/tle.py @@ -0,0 +1,10 @@ +import subprocess +import time + +cmd = "\"%s\" %s" % ("./tle", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "100"], shell=False) + +time.sleep(5) +p.kill() + +assert(p.returncode == 3) diff --git a/unix/test/scripts/unlimit.py b/unix/test/scripts/unlimit.py new file mode 100644 index 0000000..95e9407 --- /dev/null +++ b/unix/test/scripts/unlimit.py @@ -0,0 +1,10 @@ +import subprocess + +cmd = "\"%s\" %s" % ("./mle_static", "") +p = subprocess.Popen(["./watcher_unix", cmd, "", "", "_tmperr", "1000", "-1"], shell=False, stdout=subprocess.PIPE) + +stdout, _ = p.communicate() + +assert(p.wait() == 0) +out_str = stdout.decode() +assert(out_str.split('\n')[0] == 'Hello World!') \ No newline at end of file diff --git a/unix/test/tle.c b/unix/test/tle.c new file mode 100644 index 0000000..36ac966 --- /dev/null +++ b/unix/test/tle.c @@ -0,0 +1,5 @@ +int main() { + while (1) + ; + return 0; +} \ No newline at end of file diff --git a/unix/watcher_linux.cpp b/unix/watcher_linux.cpp index e34fe1c..cd113c8 100644 --- a/unix/watcher_linux.cpp +++ b/unix/watcher_linux.cpp @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2011-2019 Project Lemon, Zhipeng Jia - * SPDX-FileCopyrightText: 2019-2022 Project LemonLime + * SPDX-FileCopyrightText: 2019-2023 Project LemonLime * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,8 +20,6 @@ #include #include -int pid; - static auto read_elf_ident(int fd, char *e_ident) -> bool { if (read(fd, e_ident, EI_NIDENT) != EI_NIDENT) { return false; @@ -69,28 +68,18 @@ template static auto calculateStaticMemoryUsage(i return res; } -void cleanUp(int /*dummy*/) { - kill(pid, SIGKILL); - exit(0); -} - -auto main(int /*argc*/, char *argv[]) -> int { - int timeLimit = 0, memoryLimit = 0; - sscanf(argv[5], "%d", &timeLimit); - timeLimit = (timeLimit - 1) / 1000 + 1; - sscanf(argv[6], "%d", &memoryLimit); - memoryLimit *= 1024 * 1024; +void initWatcher() { return; } - /* check static memory usage */ +ssize_t calculateStaticMemoryUsage(const std::string &fileName) { char e_ident[EI_NIDENT]; ssize_t staticMemoryUsage = 0; - int fd = open(argv[1], O_RDONLY); + int fd = open(fileName.c_str(), O_RDONLY); if (fd < 0) { - return 1; + return -1; } if (read_elf_ident(fd, e_ident) == false) { - return 1; + return -1; } if (e_ident[EI_CLASS] == ELFCLASS32) { staticMemoryUsage = calculateStaticMemoryUsage(fd); @@ -101,82 +90,9 @@ auto main(int /*argc*/, char *argv[]) -> int { } close(fd); - if (staticMemoryUsage == -1) { - return 1; - } - if (staticMemoryUsage > memoryLimit) { - printf("0\n%zd\n", staticMemoryUsage); - return 0; - } - - pid = fork(); - - if (pid > 0) { - signal(SIGINT, cleanUp); - signal(SIGABRT, cleanUp); - signal(SIGTERM, cleanUp); - struct rusage usage {}; - int status = 0; - - if (wait4(pid, &status, 0, &usage) == -1) - return 1; - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) == 1) - return 1; - - printf("%d\n", static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000)); - printf("%d\n", static_cast(usage.ru_maxrss) * 1024); - - if (WEXITSTATUS(status) != 0) - return 2; - - return 0; - } - - if (WIFSIGNALED(status)) { - printf("%d\n", static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000)); - printf("%d\n", static_cast(usage.ru_maxrss) * 1024); - - if (WTERMSIG(status) == SIGXCPU) - return 3; - - if (WTERMSIG(status) == SIGKILL) - return 4; - - if (WTERMSIG(status) == SIGABRT) - return 4; - - return 2; - } - } else { - if (strlen(argv[2]) > 0) - freopen(argv[2], "r", stdin); - - if (strlen(argv[3]) > 0) - freopen(argv[3], "w", stdout); - - if (strlen(argv[4]) > 0) - freopen(argv[4], "w", stderr); - - rlimit memlim{}, stalim{}, timlim{}; - - if (memoryLimit > 0) { - memlim = (rlimit){(rlim_t)memoryLimit, (rlim_t)memoryLimit}; - stalim = (rlimit){(rlim_t)memoryLimit, (rlim_t)memoryLimit}; - } else { - memlim = (rlimit){RLIM_INFINITY, RLIM_INFINITY}; - stalim = (rlimit){(rlim_t)2147483647LL, (rlim_t)2147483647LL}; - } - - timlim = (rlimit){(rlim_t)timeLimit, (rlim_t)(timeLimit + 1)}; - setrlimit(RLIMIT_AS, &memlim); - setrlimit(RLIMIT_STACK, &stalim); - setrlimit(RLIMIT_CPU, &timlim); + return staticMemoryUsage; +} - if (execlp("bash", "bash", "-c", argv[1], NULL) == -1) - return 1; - } +ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 * 1024; } - return 0; -} +size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss * 1024; } diff --git a/unix/watcher_macos.mm b/unix/watcher_macos.mm index 78581c9..10f480c 100644 --- a/unix/watcher_macos.mm +++ b/unix/watcher_macos.mm @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2011-2019 Project Lemon, Zhipeng Jia - * SPDX-FileCopyrightText: 2019-2022 Project LemonLime + * SPDX-FileCopyrightText: 2019-2023 Project LemonLime * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -25,8 +25,6 @@ #include #include -int pid; - template static ssize_t calculateStaticMemoryUsage(int fd, int start_offset = 0) { ssize_t res = 0; @@ -94,53 +92,43 @@ return res; } -void cleanUp(int dummy) { - kill(pid, SIGKILL); - exit(0); -} - -std::string getCpuBrandString() { +static std::string getCpuBrandString() { char buf[1024]; size_t buflen = 1024; sysctlbyname("machdep.cpu.brand_string", &buf, &buflen, NULL, 0); return std::string(buf, buflen); } -int main(int argc, char *argv[]) { - int isAppleSilicon = getCpuBrandString().find("Apple") != std::string::npos; +int isAppleSilicon; - int timeLimit, memoryLimit; - sscanf(argv[5], "%d", &timeLimit); - timeLimit = (timeLimit - 1) / 1000 + 1; - sscanf(argv[6], "%d", &memoryLimit); - memoryLimit *= 1024 * (isAppleSilicon ? 4 : 1); +void initWatcher() { isAppleSilicon = getCpuBrandString().find("Apple") != std::string::npos; } - /* check static memory usage */ +ssize_t calculateStaticMemoryUsage(const std::string &fileName) { uint32_t magic; ssize_t staticMemoryUsage = 0; - int fd = open(argv[1], O_RDONLY); + int fd = open(fileName.c_str(), O_RDONLY); if (fd < 0) { - return 1; + return -1; } int rc = macho_best_slice_in_fd(fd, [&](const mach_header *slice, uint64_t sliceFileOffset, size_t sliceSize) { - if (slice->magic == MH_MAGIC) { - staticMemoryUsage = calculateStaticMemoryUsage(fd, sliceFileOffset); - } else if (slice->magic == MH_MAGIC_64) { - staticMemoryUsage = calculateStaticMemoryUsage(fd, sliceFileOffset); - } else { - staticMemoryUsage = -1; - } + if (slice->magic == MH_MAGIC) { + staticMemoryUsage = calculateStaticMemoryUsage(fd, sliceFileOffset); + } else if (slice->magic == MH_MAGIC_64) { + staticMemoryUsage = calculateStaticMemoryUsage(fd, sliceFileOffset); + } else { + staticMemoryUsage = -1; + } }); if (rc != 0) { // not a fat file, consider a mach-o file if (lseek(fd, 0, SEEK_SET) < 0) { - return 1; + return -1; } if (read(fd, &magic, sizeof(magic)) != sizeof(magic)) { - return 1; + return -1; } if (magic == MH_MAGIC) { staticMemoryUsage = calculateStaticMemoryUsage(fd); @@ -152,82 +140,9 @@ int main(int argc, char *argv[]) { } close(fd); - if (staticMemoryUsage == -1) { - return 1; - } - if (staticMemoryUsage > memoryLimit) { - printf("0\n%zd\n", staticMemoryUsage); - return 0; - } - - pid = fork(); - - if (pid > 0) { - signal(SIGINT, cleanUp); - signal(SIGABRT, cleanUp); - signal(SIGTERM, cleanUp); - struct rusage usage; - int status; - - if (wait4(pid, &status, 0, &usage) == -1) - return 1; - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) == 1) - return 1; - - printf("%d\n", (int)(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000)); - printf("%d\n", (int)(usage.ru_maxrss) / (isAppleSilicon ? 4 : 1)); - - if (WEXITSTATUS(status) != 0) - return 2; - - return 0; - } - - if (WIFSIGNALED(status)) { - printf("%d\n", (int)(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000)); - printf("%d\n", (int)(usage.ru_maxrss) / (isAppleSilicon ? 4 : 1)); - - if (WTERMSIG(status) == SIGXCPU) - return 3; - - if (WTERMSIG(status) == SIGKILL) - return 4; - - if (WTERMSIG(status) == SIGABRT) - return 4; - - return 2; - } - } else { - if (strlen(argv[2]) > 0) - freopen(argv[2], "r", stdin); - - if (strlen(argv[3]) > 0) - freopen(argv[3], "w", stdout); - - if (strlen(argv[4]) > 0) - freopen(argv[4], "w", stderr); - - rlimit memlim, stalim, timlim; - - if (memoryLimit > 0) { - memlim = (rlimit){(rlim_t)memoryLimit, (rlim_t)memoryLimit}; - stalim = (rlimit){(rlim_t)memoryLimit, (rlim_t)memoryLimit}; - } else { - memlim = (rlimit){RLIM_INFINITY, RLIM_INFINITY}; - stalim = (rlimit){(rlim_t)2147483647ll, (rlim_t)2147483647ll}; - } - - timlim = (rlimit){(rlim_t)timeLimit, (rlim_t)(timeLimit + 1)}; - setrlimit(RLIMIT_AS, &memlim); - setrlimit(RLIMIT_STACK, &stalim); - setrlimit(RLIMIT_CPU, &timlim); + return staticMemoryUsage; +} - if (execlp("bash", "bash", "-c", argv[1], NULL) == -1) - return 1; - } +ssize_t getMemoryRLimit(ssize_t memoryLimitInMB) { return memoryLimitInMB * 1024 * (isAppleSilicon ? 4 : 1); } - return 0; -} +size_t getMaxRSSInByte(long ru_maxrss) { return ru_maxrss / (isAppleSilicon ? 4 : 1); } diff --git a/unix/watcher_unix.cpp b/unix/watcher_unix.cpp new file mode 100644 index 0000000..42e9f0d --- /dev/null +++ b/unix/watcher_unix.cpp @@ -0,0 +1,139 @@ +/* + * SPDX-FileCopyrightText: 2011-2019 Project Lemon, Zhipeng Jia + * SPDX-FileCopyrightText: 2019-2023 Project LemonLime + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int pid; + +void cleanUp(int /*dummy*/) { + kill(pid, SIGKILL); + exit(0); +} + +extern void initWatcher(); +extern ssize_t calculateStaticMemoryUsage(const std::string &); +extern ssize_t getMemoryRLimit(ssize_t memoryLimitInMB); +extern size_t getMaxRSSInByte(long ru_maxrss); + +/** + * argv[1]: QString("\"%1\" %2").arg(executableFile, arguments) in `judgingthread.cpp` 执行命令 + * argv[2]: 重定向输入文件(如果有) + * argv[3]: 重定向输出文件(如果有) + * argv[4]: 重定向错误流文件(如果有) + * argv[5]: 时间限制(毫秒) + * argv[6]: 空间限制(MB),若为负数表示无限制 + */ +auto main(int /*argc*/, char *argv[]) -> int { + initWatcher(); + + int timeLimit = 0; + ssize_t memoryLimit = 0; + sscanf(argv[5], "%d", &timeLimit); + timeLimit = (timeLimit - 1) / 1000 + 1; + sscanf(argv[6], "%zd", &memoryLimit); + + // 匹配 "" 来解析出文件名 + std::string fileName(argv[1]); + fileName = fileName.substr(1); + fileName = fileName.substr(0, fileName.find("\"")); + + if (memoryLimit > 0) { + ssize_t staticMemoryUsage = calculateStaticMemoryUsage(fileName); + if (staticMemoryUsage == -1) { + return 1; + } + if (staticMemoryUsage > memoryLimit * 1024 * 1024) { + printf("0\n%zd\n", staticMemoryUsage); + return 0; + } + } + + memoryLimit = getMemoryRLimit(memoryLimit); + + pid = fork(); + + if (pid > 0) { + signal(SIGINT, cleanUp); + signal(SIGABRT, cleanUp); + signal(SIGTERM, cleanUp); + struct rusage usage {}; + int status = 0; + + if (wait4(pid, &status, 0, &usage) == -1) + return 1; + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 1) + return 1; + + printf("%d\n", static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000)); + printf("%zu\n", getMaxRSSInByte(usage.ru_maxrss)); + + if (WEXITSTATUS(status) != 0) + return 2; + + return 0; + } + + if (WIFSIGNALED(status)) { + printf("%d\n", static_cast(usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000)); + printf("%zu\n", getMaxRSSInByte(usage.ru_maxrss)); + + if (WTERMSIG(status) == SIGXCPU) + return 3; + + if (WTERMSIG(status) == SIGKILL) + return 4; + + if (WTERMSIG(status) == SIGABRT) + return 4; + + return 2; + } + } else { + if (strlen(argv[2]) > 0) + freopen(argv[2], "r", stdin); + + if (strlen(argv[3]) > 0) + freopen(argv[3], "w", stdout); + + if (strlen(argv[4]) > 0) + freopen(argv[4], "w", stderr); + + rlimit memlim{}, stalim{}, timlim{}; + + if (memoryLimit > 0) { + memlim = (rlimit){(rlim_t)memoryLimit, (rlim_t)memoryLimit}; + stalim = (rlimit){(rlim_t)memoryLimit, (rlim_t)memoryLimit}; + } else { + memlim = (rlimit){RLIM_INFINITY, RLIM_INFINITY}; + stalim = (rlimit){(rlim_t)2147483647LL, (rlim_t)2147483647LL}; + } + + timlim = (rlimit){(rlim_t)timeLimit, (rlim_t)(timeLimit + 1)}; + setrlimit(RLIMIT_AS, &memlim); + setrlimit(RLIMIT_STACK, &stalim); + setrlimit(RLIMIT_CPU, &timlim); + + if (execlp("bash", "bash", "-c", argv[1], NULL) == -1) + return 1; + } + + return 0; +}