diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 75e697e1471..ca05b500842 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,12 @@ #define USE_STDERR JNI_TRUE /* we usually print to stderr */ #define USE_STDOUT JNI_FALSE +#if defined(J9VM_OPT_CRAC_SUPPORT) +#define MEMORY_ALLOCATION_ERROR 1 +#define OPTION_NOT_FOUND 2 +#define INVALID_OPTION_VALUE 3 +#define OPTION_VALUE_NOT_FOUND 4 +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ static jboolean printVersion = JNI_FALSE; /* print and exit */ static jboolean showVersion = JNI_FALSE; /* print but continue */ @@ -534,6 +545,260 @@ invokeInstanceMainWithoutArgs(JNIEnv *env, jclass mainClass) { return 1; } +#if defined(J9VM_OPT_CRAC_SUPPORT) +static char *getCommandLineOptionValue(const char *optionName, int argc, char **argv, int *error) { + *error = 0; + for (int i = 0; i < argc; i++) { + char *arg = argv[i]; + if (0 == strncmp(arg, optionName, strlen(optionName))) { + char *equals = strchr(arg, '='); + if (NULL != equals) { + if ('\0' == *(equals + 1)) { + return NULL; + } + int length = strlen(equals + 1); + char *value = (char *)JLI_MemAlloc(length + 1); + if (NULL == value) { + JLI_ReportErrorMessage( + "Failed to allocate memory for the value of the command line option %s.", + optionName); + *error = MEMORY_ALLOCATION_ERROR; + return NULL; + } + snprintf(value, length + 1, "%s", equals + 1); + value[length] = '\0'; + return value; + } + return NULL; + } + } + *error = OPTION_NOT_FOUND; + return NULL; +} + +static void freeCommandLineOptionValue(char *value) { + if (value) { + JLI_MemFree(value); + value = NULL; + } +} + +static jboolean isCommandLineOptionFoundWithError(int error) { + return OPTION_NOT_FOUND != error && error; +} + +static char *getCheckpointDir(int argc, char **argv, int *error) { + const char *checkpointDirPropertyName = "-XX:CRaCRestoreFrom"; + char *checkpointDirPropertyValue = getCommandLineOptionValue(checkpointDirPropertyName, argc, argv, error); + if (!*error) { + if (checkpointDirPropertyValue) { + return checkpointDirPropertyValue; + } else { + JLI_ReportErrorMessage("The value of the command line option %s was not found.", checkpointDirPropertyName); + *error = OPTION_VALUE_NOT_FOUND; + return NULL; + } + } + return NULL; +} + +static char *getDefaultLogLevel(int *error) { + *error = 0; + char *defaultLogLevel = strdup("-v2"); + if (NULL == defaultLogLevel) { + JLI_ReportErrorMessage("Failed to allocate memory for the CRIU default log level."); + *error = MEMORY_ALLOCATION_ERROR; + } + return defaultLogLevel; +} + +static char *getLogLevel(int argc, char **argv, int *error) { + const char *logLevelPropertyName = "-Dopenj9.internal.criu.logLevel"; + char *logLevelPropertyValue = getCommandLineOptionValue(logLevelPropertyName, argc, argv, error); + if (!isCommandLineOptionFoundWithError(*error)) { + if (logLevelPropertyValue) { + for (const char *c = logLevelPropertyValue; *c; c++) { + if (!isdigit(*c)) { + JLI_ReportErrorMessage( + "The value %s of the command line option %s is not valid.", + logLevelPropertyValue, + logLevelPropertyName); + freeCommandLineOptionValue(logLevelPropertyValue); + *error = INVALID_OPTION_VALUE; + return NULL; + } + } + int logLevelValue = atoi(logLevelPropertyValue); + if (0 <= logLevelValue && 4 >= logLevelValue) { + int logLevelLength = snprintf(NULL, 0, "-v%d", logLevelValue); + if (0 > logLevelLength) { + JLI_ReportErrorMessage("Failed to calculate the length of the CRIU log level string."); + freeCommandLineOptionValue(logLevelPropertyValue); + *error = MEMORY_ALLOCATION_ERROR; + return NULL; + } + char *logLevel = (char *)JLI_MemAlloc(logLevelLength + 1); + if (NULL == logLevel) { + JLI_ReportErrorMessage("Failed to allocate memory for the CRIU log level."); + freeCommandLineOptionValue(logLevelPropertyValue); + *error = MEMORY_ALLOCATION_ERROR; + return NULL; + } + snprintf(logLevel, logLevelLength + 1, "-v%d", logLevelValue); + freeCommandLineOptionValue(logLevelPropertyValue); + return logLevel; + } else { + JLI_ReportErrorMessage( + "The value %s of the command line option %s is not valid.", + logLevelPropertyValue, + logLevelPropertyName); + freeCommandLineOptionValue(logLevelPropertyValue); + *error = INVALID_OPTION_VALUE; + return NULL; + } + } else { + return getDefaultLogLevel(error); + } + } + return NULL; +} + +static char *getUnprivilegedMode(int argc, char **argv, int *error) { + const char *unprivilegedModePropertyName = "-Dopenj9.internal.criu.unprivilegedMode"; + char *unprivilegedModePropertyValue = getCommandLineOptionValue(unprivilegedModePropertyName, argc, argv, error); + if (!*error) { + if (!unprivilegedModePropertyValue) { + char *unprivilegedMode = strdup("--unprivileged"); + if (NULL == unprivilegedMode) { + JLI_ReportErrorMessage("Failed to allocate memory for the CRIU unprivileged mode."); + *error = MEMORY_ALLOCATION_ERROR; + return NULL; + } + return unprivilegedMode; + } else { + JLI_ReportErrorMessage( + "The value %s of the command line option %s is not expected.", + unprivilegedModePropertyValue, + unprivilegedModePropertyName); + freeCommandLineOptionValue(unprivilegedModePropertyValue); + *error = INVALID_OPTION_VALUE; + return NULL; + } + } + return NULL; +} + +static char *getLogFile(int argc, char **argv, int *error) { + const char *logFilePropertyName = "-Dopenj9.internal.criu.logFile"; + char *logFilePropertyValue = getCommandLineOptionValue(logFilePropertyName, argc, argv, error); + if (!*error) { + if (logFilePropertyValue) { + int logFileLength = snprintf(NULL, 0, "--log-file=%s", logFilePropertyValue) + 1; + if (0 > logFileLength) { + JLI_ReportErrorMessage("Failed to calculate the length of the CRIU log file string."); + freeCommandLineOptionValue(logFilePropertyValue); + *error = MEMORY_ALLOCATION_ERROR; + return NULL; + } + char *logFile = (char *)JLI_MemAlloc(logFileLength); + if (NULL == logFile) { + JLI_ReportErrorMessage("Failed to allocate memory for the CRIU log file option."); + *error = MEMORY_ALLOCATION_ERROR; + return NULL; + } + snprintf(logFile, logFileLength, "--log-file=%s", logFilePropertyValue); + freeCommandLineOptionValue(logFilePropertyValue); + return logFile; + } else { + JLI_ReportErrorMessage("The value of the command line option %s was not found.", logFilePropertyName); + *error = OPTION_VALUE_NOT_FOUND; + return NULL; + } + } + return NULL; +} + +static int restoreFromCheckpoint(char *checkpointDir, char *logLevel, char *unprivilegedMode, char *logFile) { + int argc = 6; + char *argv[9] = { NULL }; + argv[0] = "criu"; + argv[1] = "restore"; + argv[2] = "-D"; + argv[3] = checkpointDir; + argv[4] = logLevel; + argv[5] = "--shell-job"; + if (unprivilegedMode) { + argv[argc++] = unprivilegedMode; + } + if (logFile) { + argv[argc++] = logFile; + } + argv[argc] = NULL; + if (-1 == execvp(argv[0], argv)) { + return -1; + } + return 0; +} + +static void handleCRaCRestore(int argc, char **argv) { + int error = 0; + char *checkpointDir = getCheckpointDir(argc, argv, &error); + if (OPTION_NOT_FOUND == error) { + freeCommandLineOptionValue(checkpointDir); + return; + } + if (error) { + JLI_ReportErrorMessage("Failed to get the CRIU checkpoint directory, error=%d.", error); + exit(EXIT_FAILURE); + } + /* + * The if block will be invoked by the child process, + * and the else block will be invoked by the parent process. + */ + pid_t criuRestorePid = fork(); + if (0 == criuRestorePid) { + char *logLevel = getLogLevel(argc, argv, &error); + if (isCommandLineOptionFoundWithError(error)) { + JLI_ReportErrorMessage("Failed to get the CRIU log level, error=%d.", error); + exit(EXIT_FAILURE); + } + char *unprivilegedMode = getUnprivilegedMode(argc, argv, &error); + if (isCommandLineOptionFoundWithError(error)) { + JLI_ReportErrorMessage("Failed to get the CRIU unprivileged mode, error=%d.", error); + freeCommandLineOptionValue(logLevel); + exit(EXIT_FAILURE); + } + char *logFile = getLogFile(argc, argv, &error); + if (isCommandLineOptionFoundWithError(error)) { + JLI_ReportErrorMessage("Failed to get the CRIU log file, error=%d.", error); + freeCommandLineOptionValue(logLevel); + freeCommandLineOptionValue(unprivilegedMode); + exit(EXIT_FAILURE); + } + int restoreResult = restoreFromCheckpoint(checkpointDir, logLevel, unprivilegedMode, logFile); + freeCommandLineOptionValue(logLevel); + freeCommandLineOptionValue(unprivilegedMode); + freeCommandLineOptionValue(logFile); + exit(restoreResult); + } else { + int exitStatus = EXIT_SUCCESS; + int criuRestorePidStatus = 0; + waitpid(criuRestorePid, &criuRestorePidStatus, 0); + if (WIFEXITED(criuRestorePidStatus)) { + if (0 != WEXITSTATUS(criuRestorePidStatus)) { + JLI_ReportErrorMessage("Failed to restore from checkpoint."); + exitStatus = EXIT_FAILURE; + } + } else { + JLI_ReportErrorMessage("The CRIU restore child process failed."); + exitStatus = EXIT_FAILURE; + } + freeCommandLineOptionValue(checkpointDir); + exit(exitStatus); + } +} +#endif /* defined(J9VM_OPT_CRAC_SUPPORT) */ + int JavaMain(void* _args) { @@ -554,6 +819,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)) {