From 0c7481022fff9ba8b7f9753ee2253fd348e9eee9 Mon Sep 17 00:00:00 2001 From: Amarpreet Singh Date: Fri, 19 Apr 2024 12:16:52 -0400 Subject: [PATCH] Support restore with -XX:CRaCRestoreFrom=PATH Support restore with -XX:CRaCRestoreFrom=PATH that specifies the path to the checkpoint image. Issue: https://github.com/eclipse-openj9/openj9/issues/19261 Signed-off-by: Amarpreet Singh --- src/java.base/share/native/libjli/java.c | 361 ++++++++++++++++++++++- 1 file changed, 360 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 75e697e1471..a3377e39447 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 * =========================================================================== */ @@ -61,6 +61,11 @@ #include "java.h" #include "jni.h" +#include "j9cfg.h" +#if defined(J9VM_OPT_CRAC_SUPPORT) +#include +#include +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ /* * A NOTE TO DEVELOPERS: For performance reasons it is important that @@ -72,6 +77,46 @@ #define USE_STDERR JNI_TRUE /* we usually print to stderr */ #define USE_STDOUT JNI_FALSE +#if defined(J9VM_OPT_CRAC_SUPPORT) +#define EQUALS_SIGN '=' +#define NULL_TERMINATOR '\0' +#define OPTION_VALUE_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the value of the command line option %s." +#define OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE "The value of the command line option %s was not found." +#define LOG_LEVEL_LENGTH_CALCULATATION_ERROR_MESSAGE "Failed to calculate the length of the CRIU log level string." +#define LOG_LEVEL_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the CRIU log level." +#define OPTION_VALUE_NOT_VALID_ERROR_MESSAGE "The value of the command line option is not valid, option=%s, value=%s." +#define OPTION_VALUE_NOT_EXPECTED_ERROR_MESSAGE "The value of the command line option is not expected, option=%s, value=%s." +#define LOG_FILE_LENGTH_CALCULATION_ERROR_MESSAGE "Failed to calculate the length of the CRIU log file string." +#define LOG_FILE_MEMORY_ALLOCATION_ERROR_MESSAGE "Failed to allocate memory for the CRIU log file option." +#define CHECKPOINT_DIRECTORY_ERROR_MESSAGE "Failed to get the CRIU checkpoint directory, error=%d." +#define LOG_LEVEL_ERROR_MESSAGE "Failed to get the CRIU log level, error=%d." +#define UNPRIVILEGED_MODE_ERROR_MESSAGE "Failed to get the CRIU unprivileged mode, error=%d." +#define LOG_FILE_ERROR_MESSAGE "Failed to get the CRIU log file, error=%d." +#define RESTORE_FROM_CHECKPOINT_ERROR_MESSAGE "Failed to restore from checkpoint." +#define RESTORE_CHILD_PROCESS_FAILED_ERROR_MESSAGE "The CRIU restore child process failed." +#define MEMORY_ALLOCATION_ERROR 1 +#define OPTION_NAME_NOT_FOUND_ERROR 2 +#define OPTION_VALUE_NOT_FOUND_ERROR 3 +#define OPTION_VALUE_NOT_VALID_ERROR 4 +#define OPTION_VALUE_NOT_EXPECTED_ERROR 5 +#define CRAC_RESTORE_FROM_OPTION_NAME "-XX:CRaCRestoreFrom" +#define LOG_LEVEL_OPTION_NAME "-Dopenj9.internal.criu.logLevel" +#define LOG_LEVEL_OPTION_FORMAT "-v%d" +#define UNPRIVILEGED_MODE_OPTION_NAME "-Dopenj9.internal.criu.unprivilegedMode" +#define LOG_FILE_OPTION_NAME "-Dopenj9.internal.criu.logFile" +#define LOG_FILE_OPTION_FORMAT "--log-file=%s" +#define CRIU_RESTORE_MIN_ARGS 6 +#define CRIU_RESTORE_MAX_ARGS 9 +#define CRIU_COMMAND "criu" +#define CRIU_RESTORE_OPTION "restore" +#define CRIU_CHECKPOINT_DIRECTORY_OPTION "-D" +#define CRIU_DEFAULT_LOG_LEVEL_OPTION "-v2" +#define CRIU_UNPRIVILEGED_MODE_OPTION "--unprivileged" +#define CRIU_SHELL_JOB_OPTION "--shell-job" +#define EXECVP_ERROR -1 +#define EXECVP_SUCCESS 0 +#define WAIT_INDEFINITELY 0 +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ static jboolean printVersion = JNI_FALSE; /* print and exit */ static jboolean showVersion = JNI_FALSE; /* print but continue */ @@ -534,6 +579,316 @@ invokeInstanceMainWithoutArgs(JNIEnv *env, jclass mainClass) { return 1; } +#if defined(J9VM_OPT_CRAC_SUPPORT) +/** + * 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 char *getCommandLineOptionValue(const char *optionName, int argc, char **argv, int *error) { + char *arg = NULL; + unsigned int optionNameLength = strlen(optionName); + char *equals = NULL; + jboolean optionNameFound = JNI_FALSE; + char *value = NULL; + *error = 0; + for (int i = 0; i < argc; i++) { + arg = argv[i]; + if (0 == strncmp(arg, optionName, optionNameLength)) { + equals = arg + optionNameLength; + if ((EQUALS_SIGN == *equals) || (NULL_TERMINATOR == *equals)) { + optionNameFound = JNI_TRUE; + if (EQUALS_SIGN == *equals) { + equals++; + } + if (NULL_TERMINATOR != *equals) { + value = strdup(equals); + if (NULL == value) { + JLI_ReportErrorMessage(OPTION_VALUE_MEMORY_ALLOCATION_ERROR_MESSAGE, optionName); + *error = MEMORY_ALLOCATION_ERROR; + } + } + break; + } + } + } + if (!optionNameFound) { + *error = OPTION_NAME_NOT_FOUND_ERROR; + } + return value; +} + +/** + * Free the memory allocated for a command line option value. + * @param[in/out] value A pointer to a pointer to the command line option value + */ +static void freeCommandLineOptionValue(char **value) { + if ((NULL != value) && (NULL != *value)) { + JLI_MemFree(*value); + *value = NULL; + } +} + +/** + * Check if an error occurred while retrieving a command line option value. + * @param[in] error The error code of the result of obtaining the command line option value + * @return true if an error occurred, false otherwise + */ +static jboolean hasGetCommandLineOptionValueFoundError(int error) { + return 0 != error; +} + +/** + * 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 char *getCheckpointDirectory(int argc, char **argv, int *error) { + char *checkpointDirectory = NULL; + char *checkpointDirectoryPropertyValue = getCommandLineOptionValue(CRAC_RESTORE_FROM_OPTION_NAME, argc, argv, error); + if (!hasGetCommandLineOptionValueFoundError(*error)) { + if (NULL != checkpointDirectoryPropertyValue) { + checkpointDirectory = checkpointDirectoryPropertyValue; + } else { + JLI_ReportErrorMessage(OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE, CRAC_RESTORE_FROM_OPTION_NAME); + *error = OPTION_VALUE_NOT_FOUND_ERROR; + } + } + return checkpointDirectory; +} + +/** + * Check if a command line option is found with an error condition. + * @param[in] error The error code of the result of obtaining the command line option + * @return true if the command line option is found and an error occurred, false otherwise + */ +static jboolean isCommandLineOptionFoundWithError(int error) { + return (OPTION_NAME_NOT_FOUND_ERROR != error) && hasGetCommandLineOptionValueFoundError(error); +} + +/** + * Get the log level 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 level string if successful, NULL otherwise + */ +static char *getLogLevel(int argc, char **argv, int *error) { + char *logLevel = NULL; + char *logLevelPropertyValue = NULL; + int logLevelValue = 0; + int logLevelLength = 0; + logLevelPropertyValue = getCommandLineOptionValue(LOG_LEVEL_OPTION_NAME, argc, argv, error); + if (!isCommandLineOptionFoundWithError(*error)) { + if (NULL != logLevelPropertyValue) { + for (const char *c = logLevelPropertyValue; NULL_TERMINATOR != *c; c++) { + if (!isdigit(*c)) { + goto setLogLevelOptionValueNotValidError; + } + } + logLevelValue = atoi(logLevelPropertyValue); + if ((logLevelValue >= 0) && (logLevelValue <= 4)) { + logLevelLength = snprintf(NULL, 0, LOG_LEVEL_OPTION_FORMAT, logLevelValue); + if (logLevelLength < 0) { + JLI_ReportErrorMessage(LOG_LEVEL_LENGTH_CALCULATATION_ERROR_MESSAGE); + *error = MEMORY_ALLOCATION_ERROR; + goto freeLogLevelPropertyValue; + } + logLevel = (char *)JLI_MemAlloc(logLevelLength + 1); + if (NULL == logLevel) { + JLI_ReportErrorMessage(LOG_LEVEL_MEMORY_ALLOCATION_ERROR_MESSAGE); + *error = MEMORY_ALLOCATION_ERROR; + goto freeLogLevelPropertyValue; + } + snprintf(logLevel, logLevelLength + 1, LOG_LEVEL_OPTION_FORMAT, logLevelValue); + goto freeLogLevelPropertyValue; + } else { + goto setLogLevelOptionValueNotValidError; + } + } else { + goto returnGetLogLevel; + } + } +setLogLevelOptionValueNotValidError: + JLI_ReportErrorMessage(OPTION_VALUE_NOT_VALID_ERROR_MESSAGE, LOG_LEVEL_OPTION_NAME, logLevelPropertyValue); + *error = OPTION_VALUE_NOT_VALID_ERROR; +freeLogLevelPropertyValue: + freeCommandLineOptionValue(&logLevelPropertyValue); +returnGetLogLevel: + return logLevel; +} + +/** + * Check if the unprivileged mode option 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; + char *unprivilegedModePropertyValue = NULL; + unprivilegedModePropertyValue = getCommandLineOptionValue(UNPRIVILEGED_MODE_OPTION_NAME, argc, argv, error); + if (!hasGetCommandLineOptionValueFoundError(*error)) { + if (NULL == unprivilegedModePropertyValue) { + isUnprivilegedModeOn = JNI_TRUE; + } else { + JLI_ReportErrorMessage(OPTION_VALUE_NOT_EXPECTED_ERROR_MESSAGE, UNPRIVILEGED_MODE_OPTION_NAME, unprivilegedModePropertyValue); + *error = OPTION_VALUE_NOT_EXPECTED_ERROR; + freeCommandLineOptionValue(&unprivilegedModePropertyValue); + } + } + return isUnprivilegedModeOn; +} + +/** + * Get the log file option 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 char *getLogFile(int argc, char **argv, int *error) { + char *logFile = NULL; + char *logFilePropertyValue = NULL; + int logFileLength = 0; + logFilePropertyValue = getCommandLineOptionValue(LOG_FILE_OPTION_NAME, argc, argv, error); + if (!hasGetCommandLineOptionValueFoundError(*error)) { + if (NULL != logFilePropertyValue) { + logFileLength = snprintf(NULL, 0, LOG_FILE_OPTION_FORMAT, logFilePropertyValue) + 1; + if (logFileLength < 0) { + JLI_ReportErrorMessage(LOG_FILE_LENGTH_CALCULATION_ERROR_MESSAGE); + *error = MEMORY_ALLOCATION_ERROR; + goto freeLogFilePropertyValue; + } + logFile = (char *)JLI_MemAlloc(logFileLength); + if (NULL == logFile) { + JLI_ReportErrorMessage(LOG_FILE_MEMORY_ALLOCATION_ERROR_MESSAGE); + *error = MEMORY_ALLOCATION_ERROR; + } else { + snprintf(logFile, logFileLength, LOG_FILE_OPTION_FORMAT, logFilePropertyValue); + } + goto freeLogFilePropertyValue; + } else { + JLI_ReportErrorMessage(OPTION_VALUE_NOT_FOUND_ERROR_MESSAGE, LOG_FILE_OPTION_NAME); + *error = OPTION_VALUE_NOT_FOUND_ERROR; + goto returnGetLogFile; + } + } +freeLogFilePropertyValue: + freeCommandLineOptionValue(&logFilePropertyValue); +returnGetLogFile: + 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 EXECVP_SUCCESS if the execution of the 'criu restore' command succeeds, EXECVP_ERROR otherwise + */ +static int restoreFromCheckpoint(char *checkpointDirectory, char *logLevel, jboolean unprivilegedModeOn, char *logFile) { + int argc = CRIU_RESTORE_MIN_ARGS; + char *argv[CRIU_RESTORE_MAX_ARGS] = { NULL }; + argv[0] = CRIU_COMMAND; + argv[1] = CRIU_RESTORE_OPTION; + argv[2] = CRIU_CHECKPOINT_DIRECTORY_OPTION; + argv[3] = checkpointDirectory; + argv[4] = (NULL == logLevel) ? CRIU_DEFAULT_LOG_LEVEL_OPTION : logLevel; + argv[5] = CRIU_SHELL_JOB_OPTION; + if (unprivilegedModeOn) { + argv[argc++] = CRIU_UNPRIVILEGED_MODE_OPTION; + } + if (NULL != logFile) { + argv[argc++] = logFile; + } + argv[argc] = NULL; + if (EXECVP_ERROR == execvp(argv[0], argv)) { + return EXECVP_ERROR; + } + return EXECVP_SUCCESS; +} + +/** + * 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 + */ +static void handleCRaCRestore(int argc, char **argv) { + char *checkpointDirectory = NULL; + int error = 0; + int parentProcessExitStatus = EXIT_SUCCESS; + pid_t childProcessPid = 0; + char *logLevel = NULL; + int childProcessExitStatus = EXIT_SUCCESS; + jboolean unprivilegedModeOn = JNI_FALSE; + char *logFile = NULL; + int childProcessPidStatus = 0; + checkpointDirectory = getCheckpointDirectory(argc, argv, &error); + if (OPTION_NAME_NOT_FOUND_ERROR == error) { + return; + } + if (hasGetCommandLineOptionValueFoundError(error)) { + JLI_ReportErrorMessage(CHECKPOINT_DIRECTORY_ERROR_MESSAGE, error); + parentProcessExitStatus = EXIT_FAILURE; + goto exitParentProcessHandleCRaCRestore; + } + /* + * 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 (isCommandLineOptionFoundWithError(error)) { + JLI_ReportErrorMessage(LOG_LEVEL_ERROR_MESSAGE, error); + childProcessExitStatus = EXIT_FAILURE; + goto exitChildProcessHandleCRaCRestore; + } + unprivilegedModeOn = isUnprivilegedModeOn(argc, argv, &error); + if (isCommandLineOptionFoundWithError(error)) { + JLI_ReportErrorMessage(UNPRIVILEGED_MODE_ERROR_MESSAGE, error); + childProcessExitStatus = EXIT_FAILURE; + goto freeLogLevel; + } + logFile = getLogFile(argc, argv, &error); + if (isCommandLineOptionFoundWithError(error)) { + JLI_ReportErrorMessage(LOG_FILE_ERROR_MESSAGE, error); + childProcessExitStatus = EXIT_FAILURE; + goto freeLogLevel; + } + childProcessExitStatus = restoreFromCheckpoint(checkpointDirectory, logLevel, unprivilegedModeOn, logFile); +freeLogFile: + freeCommandLineOptionValue(&logFile); +freeLogLevel: + freeCommandLineOptionValue(&logLevel); +exitChildProcessHandleCRaCRestore: + exit(childProcessExitStatus); + } else { + waitpid(childProcessPid, &childProcessPidStatus, WAIT_INDEFINITELY); + if (WIFEXITED(childProcessPidStatus)) { + if (EXIT_SUCCESS != WEXITSTATUS(childProcessPidStatus)) { + JLI_ReportErrorMessage(RESTORE_FROM_CHECKPOINT_ERROR_MESSAGE); + parentProcessExitStatus = EXIT_FAILURE; + } + } else { + JLI_ReportErrorMessage(RESTORE_CHILD_PROCESS_FAILED_ERROR_MESSAGE); + parentProcessExitStatus = EXIT_FAILURE; + } + freeCommandLineOptionValue(&checkpointDirectory); + } +exitParentProcessHandleCRaCRestore: + exit(parentProcessExitStatus); +} +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ + int JavaMain(void* _args) { @@ -554,6 +909,10 @@ JavaMain(void* _args) RegisterThread(); +#if defined(J9VM_OPT_CRAC_SUPPORT) + handleCRaCRestore(argc, argv); +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ + /* Initialize the virtual machine */ start = CurrentTimeMicros(); if (!InitializeJVM(&vm, &env, &ifn)) {