diff --git a/closed/src/java.base/share/native/libjli/criuhelpers.c b/closed/src/java.base/share/native/libjli/criuhelpers.c new file mode 100644 index 00000000000..d62cd73f577 --- /dev/null +++ b/closed/src/java.base/share/native/libjli/criuhelpers.c @@ -0,0 +1,303 @@ +/* + * =========================================================================== + * (c) Copyright IBM Corp. 2024, 2024 All Rights Reserved + * =========================================================================== + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * IBM designates this particular file as subject to the "Classpath" exception + * as provided by IBM in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, see . + * + * =========================================================================== + */ + +#include "criuhelpers.h" + +#if defined(J9VM_OPT_CRAC_SUPPORT) + +#include "java.h" +#include +#include + +/** + * Get the value of a command line option in the array of command line arguments. + * @param[in] optionName The name of the command line option to search for + * @param[in] argc The number of command line arguments + * @param[in] argv The array of command line arguments + * @param[out] error A pointer to an integer for the error code + * @return A pointer to the value of the command line option if found, NULL otherwise + */ +static const char * +getCommandLineOptionValue(const char *optionName, int argc, char **argv, int *error) +{ + const char *value = NULL; + int i = 0; + size_t optionNameLength = strlen(optionName); + *error = -1; + for (i = argc - 1; i >= 0; i--) { + const char *arg = argv[i]; + if (0 == strncmp(arg, optionName, optionNameLength)) { + const char *equals = arg + optionNameLength; + if (('=' == *equals) || ('\0' == *equals)) { + *error = 0; + value = equals; + if ('=' == *value) { + value += 1; + } + if ('\0' == *value) { + value = NULL; + } + break; + } + } + } + return value; +} + +/** + * Get the checkpoint directory from command line options. + * @param[in] argc The number of command line arguments + * @param[in] argv The array of command line arguments + * @param[in/out] error A pointer to an integer for the error code + * @return A pointer to the checkpoint directory if found, NULL otherwise + */ +static const char * +getCheckpointDirectory(int argc, char **argv, int *error) +{ + const char *checkpointDirectory = NULL; + const char *checkpointDirectoryPropertyValue = getCommandLineOptionValue("-XX:CRaCRestoreFrom", argc, argv, error); + if (0 == *error) { + if (NULL != checkpointDirectoryPropertyValue) { + checkpointDirectory = checkpointDirectoryPropertyValue; + } else { + JLI_ReportErrorMessage("The value of the command line option -XX:CRaCRestoreFrom was not found."); + *error = -2; + } + } + return checkpointDirectory; +} + +/** + * Get the log level specified in the command line arguments. + * Valid log levels are from 0 to 4, inclusive; the default is 2. + * @param[in] argc The number of command line arguments + * @param[in] argv The array of command line arguments + * @param[in/out] error A pointer to an integer for the error code + * @return The log level integer if successful, default of 2 otherwise + */ +static int +getLogLevel(int argc, char **argv, int *error) +{ + int logLevelValue = 2; /* default */ + const char *logLevelPropertyValue = getCommandLineOptionValue("-Dopenj9.internal.criu.logLevel", argc, argv, error); + if (0 == *error) { + const char *c = logLevelPropertyValue; + if (NULL == c) { + goto done; + } + for (; '\0' != *c; c++) { + if (!isdigit(*c)) { + goto setLogLevelOptionValueNotValidError; + } + } + logLevelValue = atoi(logLevelPropertyValue); + if ((0 <= logLevelValue) && (logLevelValue <= 4)) { + goto done; + } else { + goto setLogLevelOptionValueNotValidError; + } + } else if (-1 == *error) { + goto done; + } +setLogLevelOptionValueNotValidError: + JLI_ReportErrorMessage( + "The option '-Dopenj9.internal.criu.logLevel=%s' is not valid.", + logLevelPropertyValue); + *error = -2; +done: + return logLevelValue; +} + +/** + * Check if the unprivileged mode is specified in the command line arguments. + * @param[in] argc The number of command line arguments + * @param[in] argv The array of command line arguments + * @param[in/out] error A pointer to an integer for the error code + * @return true if the unprivileged mode option is specified in the command line arguments, false otherwise + */ +static jboolean +isUnprivilegedModeOn(int argc, char **argv, int *error) +{ + jboolean isUnprivilegedModeOn = JNI_FALSE; + const char *unprivilegedModePropertyValue = getCommandLineOptionValue("-Dopenj9.internal.criu.unprivilegedMode", argc, argv, error); + if (0 == *error) { + if (NULL == unprivilegedModePropertyValue) { + isUnprivilegedModeOn = JNI_TRUE; + } else { + JLI_ReportErrorMessage( + "The option '-Dopenj9.internal.criu.unprivilegedMode=%s' does not accept a value.", + unprivilegedModePropertyValue); + *error = -2; + } + } + return isUnprivilegedModeOn; +} + +/** + * Get the log file specified in the command line arguments. + * @param[in] argc The number of command line arguments + * @param[in] argv The array of command line arguments + * @param[in/out] error A pointer to an integer for the error code + * @return A pointer to the log file string if successful, NULL otherwise + */ +static const char * +getLogFile(int argc, char **argv, int *error) +{ + const char *logFile = NULL; + const char *logFilePropertyValue = getCommandLineOptionValue("-Dopenj9.internal.criu.logFile", argc, argv, error); + if (0 == *error) { + if (NULL != logFilePropertyValue) { + logFile = logFilePropertyValue; + } else { + JLI_ReportErrorMessage("The option -Dopenj9.internal.criu.logFile requires a value."); + *error = -2; + } + } + return logFile; +} + +/** + * Restore the system state from a checkpoint using the CRIU tool. + * @param[in] checkpointDirectory The directory containing the checkpoint data + * @param[in] logLevel The log level for CRIU logging + * @param[in] unprivilegedModeOn Indicates whether the unprivileged mode option is on + * @param[in] logFile The log file option for CRIU + * @return 0 if the execution of the 'criu restore' command succeeds, -1 otherwise + */ +static int +restoreFromCheckpoint(const char *checkpointDirectory, int logLevel, jboolean unprivilegedModeOn, const char *logFile) +{ + int length = -1; + char logLevelOption[4] = { 0 }; + char *logFileOption = NULL; + int argc = 0; + const char *argv[9] = { NULL }; + argv[argc++] = "criu"; + argv[argc++] = "restore"; + argv[argc++] = "-D"; + argv[argc++] = checkpointDirectory; + /* The log level is a single digit. */ + snprintf(logLevelOption, sizeof(logLevelOption), "-v%d", logLevel); + argv[argc++] = logLevelOption; + argv[argc++] = "--shell-job"; + if (unprivilegedModeOn) { + argv[argc++] = "--unprivileged"; + } + if (NULL != logFile) { + length = strlen(logFile) + sizeof("--log-file=%s") - 1; + logFileOption = (char *)JLI_MemAlloc(length + 1); + if (NULL == logFileOption) { + JLI_ReportErrorMessage("Failed to allocate memory for option '--log-file=%s'.", logFile); + goto fail; + } + if (snprintf(logFileOption, length + 1, "--log-file=%s", logFile) < 0) { + JLI_ReportErrorMessage("Failed to format option '--log-file=%s'.", logFile); + goto fail; + } + argv[argc++] = logFileOption; + } + argv[argc] = NULL; + execvp(argv[0], (char * const *)argv); + /* If execvp returns, there was an error. */ +fail: + if (NULL != logFileOption) { + JLI_MemFree((void *)logFileOption); + } + return -1; +} + +/** + * Handle the restoration of the system state from a checkpoint. + * @param[in] argc The number of command line arguments + * @param[in] argv The array of command line arguments + */ +void +handleCRaCRestore(int argc, char **argv) +{ + const char *checkpointDirectory = NULL; + int error = 0; + int parentProcessExitStatus = EXIT_SUCCESS; + pid_t childProcessPid = 0; + int logLevel = 0; + int childProcessExitStatus = EXIT_SUCCESS; + jboolean unprivilegedModeOn = JNI_FALSE; + const char *logFile = NULL; + int childProcessPidStatus = 0; + int childProcessPidExitStatus = 0; + checkpointDirectory = getCheckpointDirectory(argc, argv, &error); + if (-1 == error) { + /* Option -XX:CRaCRestoreFrom not specified. */ + return; + } + if (-2 == error) { + JLI_ReportErrorMessage("Failed to get the CRIU checkpoint directory."); + parentProcessExitStatus = EXIT_FAILURE; + goto doneParentProcess; + } + /* + * The if block will be invoked by the child process, + * and the else block will be invoked by the parent process. + */ + childProcessPid = fork(); + if (0 == childProcessPid) { + logLevel = getLogLevel(argc, argv, &error); + if (-2 == error) { + JLI_ReportErrorMessage("Failed to get the CRIU log level."); + childProcessExitStatus = EXIT_FAILURE; + goto doneChildProcess; + } + unprivilegedModeOn = isUnprivilegedModeOn(argc, argv, &error); + if (-2 == error) { + JLI_ReportErrorMessage("Failed to get the CRIU unprivileged mode."); + childProcessExitStatus = EXIT_FAILURE; + goto doneChildProcess; + } + logFile = getLogFile(argc, argv, &error); + if (-2 == error) { + JLI_ReportErrorMessage("Failed to get the CRIU log file."); + childProcessExitStatus = EXIT_FAILURE; + goto doneChildProcess; + } + childProcessExitStatus = restoreFromCheckpoint(checkpointDirectory, logLevel, unprivilegedModeOn, logFile); +doneChildProcess: + exit(childProcessExitStatus); + } else { + waitpid(childProcessPid, &childProcessPidStatus, 0); + if (WIFEXITED(childProcessPidStatus)) { + childProcessPidExitStatus = WEXITSTATUS(childProcessPidStatus); + if (EXIT_SUCCESS == childProcessPidExitStatus) { + JLI_ReportMessage("Completed restore with -XX:CRaCRestoreFrom=PATH."); + } else { + JLI_ReportErrorMessage("Failed to restore from checkpoint, error=%d.", childProcessPidExitStatus); + parentProcessExitStatus = EXIT_FAILURE; + } + } else { + JLI_ReportErrorMessage("The CRIU restore child process failed."); + parentProcessExitStatus = EXIT_FAILURE; + } + } +doneParentProcess: + exit(parentProcessExitStatus); +} +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ diff --git a/closed/src/java.base/share/native/libjli/criuhelpers.h b/closed/src/java.base/share/native/libjli/criuhelpers.h new file mode 100644 index 00000000000..b2dab6bca74 --- /dev/null +++ b/closed/src/java.base/share/native/libjli/criuhelpers.h @@ -0,0 +1,34 @@ +/* + * =========================================================================== + * (c) Copyright IBM Corp. 2024, 2024 All Rights Reserved + * =========================================================================== + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * IBM designates this particular file as subject to the "Classpath" exception + * as provided by IBM in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, see . + * + * =========================================================================== + */ + +#ifndef CRIUHELPERS_H +#define CRIUHELPERS_H + +#include "j9cfg.h" + +#if defined(J9VM_OPT_CRAC_SUPPORT) +void handleCRaCRestore(int argc, char **argv); +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ + +#endif /* CRIUHELPERS_H */ diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 9a4e0360980..808f394f306 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -25,7 +25,7 @@ /* * =========================================================================== - * (c) Copyright IBM Corp. 2022, 2023 All Rights Reserved + * (c) Copyright IBM Corp. 2022, 2024 All Rights Reserved * =========================================================================== */ @@ -59,6 +59,7 @@ #include +#include "criuhelpers.h" #include "java.h" #include "jni.h" @@ -474,6 +475,10 @@ JavaMain(void* _args) int ret = 0; jlong start = 0, end = 0; +#if defined(J9VM_OPT_CRAC_SUPPORT) + handleCRaCRestore(argc, argv); +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ + RegisterThread(); /* Initialize the virtual machine */