From 6284cfd69ae66fcd62b2e9ba21c7a7805ef9f35e Mon Sep 17 00:00:00 2001 From: Jiawei_Tang Date: Fri, 18 Aug 2023 15:47:58 +0800 Subject: [PATCH] [EagerAppCDS] enable classpath change by replacing given env var Summary: If user give environment vars and use `-Dcom.alibaba.cds.cp.reloc.envs`, cds will change the classpath by using given environment vars to transform some paths in classpath. Test Plan: ci jtreg jdk/test/com/alibaba/quickstart/TestEnvVariableReplay.java jdk/test/com/alibaba/quickstart/TestEnvVariableDump.java Reviewed-by: lingjun-cg, yuleil Issue: https://github.com/dragonwell-project/dragonwell8/issues/595 --- .../share/vm/classfile/sharedClassUtil.cpp | 104 ++++--- .../share/vm/classfile/sharedClassUtil.hpp | 2 + hotspot/src/share/vm/memory/filemap.cpp | 13 +- hotspot/src/share/vm/runtime/globals_ext.hpp | 2 + hotspot/src/share/vm/runtime/quickStart.cpp | 262 ++++++++++++++---- hotspot/src/share/vm/runtime/quickStart.hpp | 12 + .../com/alibaba/quickstart/TestEnvClass.java | 10 + .../quickstart/TestEnvVariableDump.java | 61 ++++ .../quickstart/TestEnvVariableReplay.java | 97 +++++++ 9 files changed, 461 insertions(+), 102 deletions(-) create mode 100644 jdk/test/com/alibaba/quickstart/TestEnvClass.java create mode 100644 jdk/test/com/alibaba/quickstart/TestEnvVariableDump.java create mode 100644 jdk/test/com/alibaba/quickstart/TestEnvVariableReplay.java diff --git a/hotspot/src/share/vm/classfile/sharedClassUtil.cpp b/hotspot/src/share/vm/classfile/sharedClassUtil.cpp index 4bf61fcd046..300ccd945a7 100644 --- a/hotspot/src/share/vm/classfile/sharedClassUtil.cpp +++ b/hotspot/src/share/vm/classfile/sharedClassUtil.cpp @@ -38,6 +38,7 @@ #include "runtime/arguments.hpp" #include "runtime/java.hpp" #include "runtime/os.hpp" +#include "runtime/quickStart.hpp" class ManifestStream: public ResourceObj { @@ -156,56 +157,69 @@ ClassPathSegment* SharedPathsMiscInfoExt::to_sorted_segments(const char* path, i return cps; } +bool SharedPathsMiscInfoExt::checkAPP(const char* path) { + size_t len = strlen(path); + const char *appcp = Arguments::get_appclasspath(); + assert(appcp != NULL, "NULL app classpath"); + size_t appcp_len = strlen(appcp); + if (appcp_len < len) { + return fail("Run time APP classpath is shorter than the one at dump time: ", appcp); + } + // Prefix is OK: E.g., dump with -cp foo.jar, but run with -cp foo.jar:bar.jar + if (AppCDSVerifyClassPathOrder) { + if (strncmp(path, appcp, len) != 0) { + return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); + } + if (appcp[len] != '\0' && appcp[len] != os::path_separator()[0]) { + return fail("Dump time APP classpath is not a proper prefix of run time APP classpath: ", appcp); + } + } else{ + ResourceMark rm; + int app_seg_num = 0; + int path_seg_num = 0; + ClassPathSegment* app_cps = to_sorted_segments(appcp, app_seg_num); + ClassPathSegment* path_cps = to_sorted_segments(path, path_seg_num); + if (app_seg_num < path_seg_num) { + return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); + } else { + int i = 0, j = 0; + while(i < path_seg_num && j < app_seg_num){ + if (app_cps[j]._len != path_cps[i]._len){ + j++; + } else { + int c = strncmp(app_cps[j]._start, path_cps[i]._start, app_cps[j]._len); + if (c == 0) { + i++; + j++; + } else if (c < 0) { + j++; + } else { + break; + } + } + } + if (i != path_seg_num) { + return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); + } + } + } + return true; +} bool SharedPathsMiscInfoExt::check(jint type, const char* path) { - switch (type) { case APP: { - size_t len = strlen(path); - const char *appcp = Arguments::get_appclasspath(); - assert(appcp != NULL, "NULL app classpath"); - size_t appcp_len = strlen(appcp); - if (appcp_len < len) { - return fail("Run time APP classpath is shorter than the one at dump time: ", appcp); - } - // Prefix is OK: E.g., dump with -cp foo.jar, but run with -cp foo.jar:bar.jar - if (AppCDSVerifyClassPathOrder) { - if (strncmp(path, appcp, len) != 0) { - return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); - } - if (appcp[len] != '\0' && appcp[len] != os::path_separator()[0]) { - return fail("Dump time APP classpath is not a proper prefix of run time APP classpath: ", appcp); - } - } else{ - ResourceMark rm; - int app_seg_num = 0; - int path_seg_num = 0; - ClassPathSegment* app_cps = to_sorted_segments(appcp, app_seg_num); - ClassPathSegment* path_cps = to_sorted_segments(path, path_seg_num); - if (app_seg_num < path_seg_num) { - return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); - } else { - int i = 0, j = 0; - while(i < path_seg_num && j < app_seg_num){ - if (app_cps[j]._len != path_cps[i]._len){ - j++; - } else { - int c = strncmp(app_cps[j]._start, path_cps[i]._start, app_cps[j]._len); - if (c == 0) { - i++; - j++; - } else if (c < 0) { - j++; - } else { - break; - } - } - } - if (i != path_seg_num) { - return fail("[APP classpath mismatch, actual: -Djava.class.path=", appcp); - } - } + if (QuickStart::need_convert_path_by_env()) { + int new_path_len = QuickStart::get_max_replaced_path_len(path) + 1; + char* new_path = NEW_C_HEAP_ARRAY(char, new_path_len, mtInternal); + QuickStart::convert_path_by_env(path, new_path); + ClassLoader::trace_class_path(tty, "new_path: ", new_path); + bool result = checkAPP(new_path); + FREE_C_HEAP_ARRAY(char, new_path, mtInternal); + return result; + } else { + return checkAPP(path); } } break; diff --git a/hotspot/src/share/vm/classfile/sharedClassUtil.hpp b/hotspot/src/share/vm/classfile/sharedClassUtil.hpp index 3ba1d24fd44..a234f0b3302 100644 --- a/hotspot/src/share/vm/classfile/sharedClassUtil.hpp +++ b/hotspot/src/share/vm/classfile/sharedClassUtil.hpp @@ -82,6 +82,8 @@ class SharedPathsMiscInfoExt : public SharedPathsMiscInfo { virtual bool check(jint type, const char* path); + virtual bool checkAPP(const char* path); + void add_app_classpath(const char* path) { add_path(path, APP); } diff --git a/hotspot/src/share/vm/memory/filemap.cpp b/hotspot/src/share/vm/memory/filemap.cpp index c94579c8085..7f069e0c2c0 100644 --- a/hotspot/src/share/vm/memory/filemap.cpp +++ b/hotspot/src/share/vm/memory/filemap.cpp @@ -34,6 +34,7 @@ #include "oops/objArrayOop.hpp" #include "runtime/arguments.hpp" #include "runtime/java.hpp" +#include "runtime/quickStart.hpp" #include "runtime/os.hpp" #include "services/memTracker.hpp" #include "utilities/defaultStream.hpp" @@ -264,6 +265,13 @@ bool FileMapInfo::validate_classpath_entry_table() { SharedClassPathEntry* ent = shared_classpath(i); struct stat st; const char* name = ent->_name; + char* new_ent_name = QuickStart::replace_if_contains(name); + if (new_ent_name != NULL) { + if (TraceClassPaths || (TraceClassLoading && Verbose)) { + tty->print_cr("replace %s with %s", name, new_ent_name); + } + name = new_ent_name; + } bool ok = true; if (TraceClassPaths || (TraceClassLoading && Verbose)) { tty->print_cr("[Checking shared classpath entry: %s]", name); @@ -277,7 +285,7 @@ bool FileMapInfo::validate_classpath_entry_table() { ok = false; } } else { - if (ent->_timestamp != st.st_mtime || + if ((ent->_timestamp != st.st_mtime && !CDSIgnoreFileTimeCheck) || ent->_filesize != st.st_size) { ok = false; if (PrintSharedArchiveAndExit) { @@ -290,6 +298,9 @@ bool FileMapInfo::validate_classpath_entry_table() { } } } + if (new_ent_name != NULL) { + FREE_C_HEAP_ARRAY(char, new_ent_name, mtInternal); + } if (ok) { if (TraceClassPaths || (TraceClassLoading && Verbose)) { tty->print_cr("[ok]"); diff --git a/hotspot/src/share/vm/runtime/globals_ext.hpp b/hotspot/src/share/vm/runtime/globals_ext.hpp index a385c614e14..afa1e3a05b2 100644 --- a/hotspot/src/share/vm/runtime/globals_ext.hpp +++ b/hotspot/src/share/vm/runtime/globals_ext.hpp @@ -174,6 +174,8 @@ "Ignore non-empty dir check in AppCDS") \ product(bool, CDSIgnoreBootClasspathDiff, false, \ "keep AppCDS on after appending boot classpath") \ + product(bool, CDSIgnoreFileTimeCheck, false, \ + "do not check timestamp of jar file when using CDS") \ \ product(bool, SuppressAppCDSErrors, false, \ "Suppress AppCDS errors during initialization; use warnings instead") \ diff --git a/hotspot/src/share/vm/runtime/quickStart.cpp b/hotspot/src/share/vm/runtime/quickStart.cpp index 73cfcf5e899..8822026af22 100644 --- a/hotspot/src/share/vm/runtime/quickStart.cpp +++ b/hotspot/src/share/vm/runtime/quickStart.cpp @@ -23,6 +23,15 @@ SPARC_ONLY("sparc") #endif +#ifdef log_error +#undef log_error +#endif +#ifdef log_info +#undef log_info +#endif +#define log_error(...) (!verbose()) ? (void)0 : tty->print_cr +#define log_info(...) (!verbose()) ? (void)0 : tty->print_cr + bool QuickStart::_is_starting = true; bool QuickStart::_is_enabled = false; bool QuickStart::_verbose = false; @@ -50,6 +59,10 @@ int QuickStart::_jvm_option_count = 0; const char** QuickStart::_jvm_options = NULL; const char* QuickStart::_cp_in_metadata_file = NULL; +GrowableArray* QuickStart::old_envs = NULL; +GrowableArray* QuickStart::new_envs = NULL; +int QuickStart::_max_env_diff_len = 0; + const char* QuickStart::_opt_name[] = { #define OPT_TAG(name) #name, OPT_TAG_LIST @@ -105,6 +118,8 @@ int QuickStart::_lock_file_fd = 0; #define JVM_CONF_FILE "jvm_conf" #define CDS_DIFF_CLASSES "cds_diff_classes.lst" +#define ENV_MARK "ENV." + QuickStart::~QuickStart() { if (_cache_path) { os::free(const_cast(_cache_path)); @@ -167,32 +182,32 @@ bool QuickStart::parse_command_line_arguments(const char* options) { } else if (match_option(cur, "printStat", &tail)) { if (tail[0] != '\0') { success = false; - tty->print_cr("Invalid -Xquickstart option '%s'", cur); + log_error(quickstart)("Invalid -Xquickstart option '%s'", cur); } _print_stat_enabled = true; } else if (match_option(cur, "destroy", &tail)) { if (tail[0] != '\0') { success = false; - tty->print_cr("Invalid -Xquickstart option '%s'", cur); + log_error(quickstart)("Invalid -Xquickstart option '%s'", cur); } _need_destroy = true; } else if (match_option(cur, "profile", &tail)) { if (tail[0] != '\0') { success = false; - tty->print_cr("Invalid -Xquickstart option '%s'", cur); + log_error(quickstart)("Invalid -Xquickstart option '%s'", cur); } _profile_only = true; } else if (match_option(cur, "dump", &tail)) { if (tail[0] != '\0') { success = false; - tty->print_cr("Invalid -Xquickstart option '%s'", cur); + log_error(quickstart)("Invalid -Xquickstart option '%s'", cur); } _dump_only = true; } } else { for (int i = 0; i < first_level_options_size; i++) { if (match_option(cur, first_level_options[i], &tail)) { - tty->print_cr("-Xquickstart option '%s' should be put in front of other options", cur); + log_error(quickstart)("-Xquickstart option '%s' should be put in front of other options", cur); fileStream stream(defaultStream::output_stream()); print_command_line_help(&stream); success = false; @@ -212,7 +227,7 @@ bool QuickStart::parse_command_line_arguments(const char* options) { } else if (match_option(cur, "verbose", &tail)) { if (tail[0] != '\0') { success = false; - tty->print_cr("Invalid -Xquickstart option '%s'", cur); + log_error(quickstart)("Invalid -Xquickstart option '%s'", cur); } _verbose = true; } else if (match_option(cur, "path=", &tail)) { @@ -235,7 +250,7 @@ bool QuickStart::parse_command_line_arguments(const char* options) { } if (!ignore) { success = false; - tty->print_cr("Invalid -Xquickstart option '%s'", cur); + log_error(quickstart)("Invalid -Xquickstart option '%s'", cur); } } } @@ -253,7 +268,7 @@ bool QuickStart::set_optimization(const char* option, bool enabled) { } } - tty->print_cr("Invalid -Xquickstart optimization option '%s'", option); + log_error(quickstart)("Invalid -Xquickstart optimization option '%s'", option); return false; } @@ -393,7 +408,7 @@ bool QuickStart::check_integrity(const JavaVMInitArgs* options_args, const char* _metadata_file = os::fopen(meta_file, "r"); if (!_metadata_file) { // if one process removes metadata here, will NULL. - tty->print_cr("metadata file may be destroyed by another process."); + log_error(quickstart)("metadata file may be destroyed by another process."); return false; } bool result = load_and_validate(options_args); @@ -450,14 +465,13 @@ bool QuickStart::load_and_validate(const JavaVMInitArgs* options_args) { _vm_version = VM_Version::internal_vm_info_string(); while (fgets(line, sizeof(line), _metadata_file) != NULL) { - if (!feature_checked && match_option(line, _identifier_name[Features], &tail)) { check_features(tail); feature_checked = true; } else if (!version_checked && match_option(line, _identifier_name[VMVersion], &tail)) { // read jvm info if (options_args != NULL && strncmp(tail, _vm_version, strlen(_vm_version)) != 0) { - tty->print_cr("VM Version isn't the same."); + log_error(quickstart)("VM Version isn't the same."); return false; } version_checked = true; @@ -472,18 +486,18 @@ bool QuickStart::load_and_validate(const JavaVMInitArgs* options_args) { const char *image_ident = QuickStart::image_id(); size_t ident_size = image_ident != NULL ? strlen(image_ident) : 0; if (size != ident_size) { - tty->print_cr("Container image isn't the same."); + log_error(quickstart)("Container image isn't the same."); return false; } if (strncmp(tail, QuickStart::image_id(), size) != 0) { - tty->print_cr("Container image isn't the same."); + log_error(quickstart)("Container image isn't the same."); return false; } } else if (!option_checked && match_option(line, _identifier_name[JVMOptionCount], &tail)) { // read previous jvm options count if (sscanf(tail, "%d", &_jvm_option_count) != 1) { - tty->print_cr("Unable to read the option number."); + log_error(quickstart)("Unable to read the option number."); return false; } option_checked = true; @@ -492,7 +506,7 @@ bool QuickStart::load_and_validate(const JavaVMInitArgs* options_args) { // Note: at this time of argument parsing, we cannot use Thread local and ResourceMark for (int index = 0; index < _jvm_option_count; index++) { if (fgets(line, sizeof(line), _metadata_file) == NULL) { - tty->print_cr("Unable to read JVM option."); + log_error(quickstart)("Unable to read JVM option."); return false; } else if (_dump_only) { //when run with dump stage.JVM option and features only can get from metadata @@ -507,11 +521,11 @@ bool QuickStart::load_and_validate(const JavaVMInitArgs* options_args) { } else if (!cp_len_checked && match_option(line, _identifier_name[ClassPathLength], &tail)) { int cp_len = 0; if (sscanf(tail, "%d", &cp_len) != 1) { - tty->print_cr("Unable read class path length."); + log_error(quickstart)("Unable read class path length."); return false; } if (cp_len <= 0 ) { - tty->print_cr("Invalid %s 's value %d.It should > 0." ,_identifier_name[ClassPathLength], cp_len); + log_error(quickstart)("Invalid %s 's value %d.It should > 0." ,_identifier_name[ClassPathLength], cp_len); return false; } @@ -520,18 +534,65 @@ bool QuickStart::load_and_validate(const JavaVMInitArgs* options_args) { cp_len += 2; char *cp_buff = NEW_C_HEAP_ARRAY(char, cp_len, mtInternal); if (fgets(cp_buff, cp_len, _metadata_file) == NULL) { - tty->print_cr("Unable to read classpath option."); + log_error(quickstart)("Unable to read classpath option."); FREE_C_HEAP_ARRAY(char, cp_buff, mtInternal); return false; } trim_tail_newline(cp_buff); _cp_in_metadata_file = os::strdup(cp_buff); FREE_C_HEAP_ARRAY(char, cp_buff, mtInternal); + } else if (match_option(line, ENV_MARK, &tail)) { + if (old_envs == NULL) { + old_envs = new (ResourceObj::C_HEAP, mtInternal) GrowableArray(5, true); + new_envs = new (ResourceObj::C_HEAP, mtInternal) GrowableArray(5, true); + } + // remove line_separator in line + char* pos = strstr(line, os::line_separator()); + if (pos != NULL) { + *pos = '\0'; + } + if (tail[0] != '\0') { + read_env_from_file_and_environment(line); + } } } return true; } +void QuickStart::read_env_from_file_and_environment(char* line) { + + // 1. read old env from file + int p1 = strlen(ENV_MARK); + int p2 = strchr(line, '=') - line; + // e.g. from ENV.PWD=Path get Path + int old_env_len = strlen(line) - p2 -1; + char* old_env = NEW_C_HEAP_ARRAY(char, old_env_len + 1, mtInternal); // char + \0 + strncpy(old_env, &line[p2+1], old_env_len); + old_env[old_env_len] = '\0'; + + // 2. read new env from environment + // e.g. from ENV.PWD=PATH get PWD + char* env_name = NEW_C_HEAP_ARRAY(char, p2 - p1 + 1, mtInternal); + strncpy(env_name, &line[p1], p2-p1); + env_name[p2-p1] = '\0'; + // e.g. ::getenv(PWD) + char* new_env_val = ::getenv(env_name); + FREE_C_HEAP_ARRAY(char, env_name, mtInternal); + + // 3. append val into old_envs and new_envs + if (new_env_val != NULL) { + old_envs->append(old_env); + new_envs->append(new_env_val); + log_info(quickstart)("old_env: %s", old_env); + log_info(quickstart)("new_env: %s", new_env_val); + // record _max_env_diff_len + int new_env_len = strlen(new_env_val); + if (new_env_len - old_env_len > _max_env_diff_len) { + _max_env_diff_len = new_env_len - old_env_len; + } + } +} + void QuickStart::trim_tail_newline(char *str) { int len = (int)strlen(str); int i; @@ -554,24 +615,20 @@ void QuickStart::trim_tail_newline(char *str) { } void QuickStart::calculate_cache_path() { if (_cache_path != NULL) { - if (_verbose) { - tty->print_cr("cache path is set from -Xquickstart:path=%s", _cache_path); - } + log_info(quickstart)("cache path is set from -Xquickstart:path=%s", _cache_path); return; } const char *buffer = ::getenv("QUICKSTART_CACHE_PATH"); if (buffer != NULL && (_cache_path = os::strdup(buffer)) != NULL) { - if (_verbose) { - tty->print_cr("set from env with %s", _cache_path); - } + log_info(quickstart)("set from env with %s", _cache_path); return; } if (_profile_only) { const char* temp = os::get_temp_directory(); if (temp == NULL) { - tty->print_cr("Cannot get temp directory"); + log_error(quickstart)("Cannot get temp directory"); vm_exit(1); } char buf[JVM_MAXPATHLEN]; @@ -583,7 +640,7 @@ void QuickStart::calculate_cache_path() { if (home == NULL) { home = os::get_current_directory(buf_pwd, sizeof(buf_pwd)); if (home == NULL) { - tty->print_cr("neither HOME env nor current_dir is available"); + log_error(quickstart)("neither HOME env nor current_dir is available"); vm_exit(1); } } @@ -591,15 +648,15 @@ void QuickStart::calculate_cache_path() { jio_snprintf(buf, JVM_MAXPATHLEN, "%s%s%s", home, os::file_separator(), DEFAULT_SHARED_DIRECTORY); _cache_path = os::strdup(buf); } - tty->print_cr("cache path is set as default with %s", _cache_path); + log_info(quickstart)("cache path is set as default with %s", _cache_path); } void QuickStart::destroy_cache_folder() { if (_need_destroy && _cache_path != NULL) { if (remove_dir(_cache_path) < 0) { - tty->print_cr("failed to destroy the cache folder: %s", _cache_path); + log_error(quickstart)("failed to destroy the cache folder: %s", _cache_path); } else { - tty->print_cr("destroy the cache folder: %s", _cache_path); + log_info(quickstart)("destroy the cache folder: %s", _cache_path); } vm_exit(0); } @@ -608,9 +665,9 @@ void QuickStart::destroy_cache_folder() { if (_profile_only && _cache_path != NULL) { if (!os::dir_is_empty(_cache_path)) { if (remove_dir(_cache_path) < 0) { - tty->print_cr("failed to destroy the cache folder: %s", _cache_path); + log_error(quickstart)("failed to destroy the cache folder: %s", _cache_path); } else { - tty->print_cr("destroy the cache folder: %s", _cache_path); + log_info(quickstart)("destroy the cache folder: %s", _cache_path); } } } @@ -756,11 +813,11 @@ bool QuickStart::determine_role(const JavaVMInitArgs* options_args) { } ret = ::mkdir(_cache_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if (ret != 0) { - tty->print_cr("Could not mkdir [%s] because [%s]", _cache_path, strerror(errno)); + log_error(quickstart)("Could not mkdir [%s] because [%s]", _cache_path, strerror(errno)); return false; } } else if (!S_ISDIR(st.st_mode)) { - tty->print_cr("Cache path [%s] is not a directory, " + log_error(quickstart)("Cache path [%s] is not a directory, " "please use -Xquickstart:path= or environment variable " "QUICKSTART_CACHE_PATH to specify.\n", _cache_path); @@ -781,7 +838,7 @@ bool QuickStart::determine_role(const JavaVMInitArgs* options_args) { // if the lock exists, it returns -1. _lock_file_fd = os::create_binary_file(_lock_path, false); if (_lock_file_fd == -1) { - tty->print_cr("Fail to create LOCK file"); + log_error(quickstart)("Fail to create LOCK file"); return false; } jio_snprintf(buf, JVM_MAXPATHLEN, "%s%s%s", _cache_path, os::file_separator(), TEMP_METADATA_FILE); @@ -789,29 +846,29 @@ bool QuickStart::determine_role(const JavaVMInitArgs* options_args) { ret = os::stat(buf, &st); if (ret == 0) { // error: A file exists, determine failed. Maybe this is a user's file. - tty->print_cr("The %s file exists\n", TEMP_METADATA_FILE); + log_error(quickstart)("The %s file exists\n", TEMP_METADATA_FILE); return false; } _temp_metadata_file = new(ResourceObj::C_HEAP, mtInternal) fileStream(_temp_metadata_file_path, "w"); if (!_temp_metadata_file) { - tty->print_cr("Failed to create %s file\n", TEMP_METADATA_FILE); + log_error(quickstart)("Failed to create %s file\n", TEMP_METADATA_FILE); return false; } if (!dump_cached_info(options_args)) { - tty->print_cr("Failed to dump cached information\n"); + log_error(quickstart)("Failed to dump cached information\n"); return false; } if (_profile_only) { _role = Profiler; - tty->print_cr("Running as profiler"); + log_info(quickstart)("Running as profiler"); } else { _role = Tracer; - tty->print_cr("Running as tracer"); + log_info(quickstart)("Running as tracer"); } return true; } else if (ret == 0 && check_integrity(options_args, _metadata_file_path)) { _role = Replayer; - tty->print_cr("Running as replayer"); + log_info(quickstart)("Running as replayer"); return true; } return false; @@ -831,7 +888,7 @@ bool QuickStart::prepare_dump(const JavaVMInitArgs *options_args) { _temp_metadata_file_path = os::strdup(buf); int ret = os::stat(_temp_metadata_file_path, &st); if (ret < 0 && errno == ENOENT) { - tty->print_cr("The %s file not exists\n", _temp_metadata_file_path); + log_error(quickstart)("The %s file not exists\n", _temp_metadata_file_path); return false; } else if (ret == 0 && check_integrity(options_args, _temp_metadata_file_path)) { // Create a LOCK file @@ -840,16 +897,16 @@ bool QuickStart::prepare_dump(const JavaVMInitArgs *options_args) { // if the lock exists, it returns -1. _lock_file_fd = os::create_binary_file(_lock_path, false); if (_lock_file_fd == -1) { - tty->print_cr("Fail to create LOCK file"); + log_error(quickstart)("Fail to create LOCK file"); return false; } jio_snprintf(buf, JVM_MAXPATHLEN, "%s%s%s", _cache_path, os::file_separator(), METADATA_FILE); _metadata_file_path = os::strdup(buf); _role = Dumper; - tty->print_cr("Running as dumper"); + log_info(quickstart)("Running as dumper"); return true; } - tty->print_cr("Cannot dump,maybe the %s is invalid!ret: %d", TEMP_METADATA_FILE, ret); + log_error(quickstart)("Cannot dump,maybe the %s is invalid!ret: %d", TEMP_METADATA_FILE, ret); return false; #endif } @@ -865,9 +922,8 @@ void QuickStart::settle_opt_pass_table() { void QuickStart::set_opt_passed(opt feature) { // set a feature passed _opt_passed[feature] = true; - if (_verbose) { - tty->print_cr("feature %s is enabled and passed", _opt_name[feature]); - } + + log_info(quickstart)("feature %s is enabled and passed", _opt_name[feature]); // If all features are passed, we set the environment for roles of this process bool opt_all_passed = true; @@ -875,9 +931,7 @@ void QuickStart::set_opt_passed(opt feature) { opt_all_passed &= _opt_passed[i]; } if (opt_all_passed) { - if (_verbose) { - tty->print_cr("all enabled features are passed"); - } + log_info(quickstart)("all enabled features are passed"); setenv_for_roles(); } } @@ -889,7 +943,7 @@ void QuickStart::generate_metadata_file(bool rename_metafile) { if (rename_metafile) { ret = ::rename(_temp_metadata_file_path, _metadata_file_path); if (ret != 0) { - tty->print_cr("Could not mv [%s] to [%s] because [%s]\n", + log_error(quickstart)("Could not mv [%s] to [%s] because [%s]\n", TEMP_METADATA_FILE, METADATA_FILE, strerror(errno)); } } @@ -897,13 +951,13 @@ void QuickStart::generate_metadata_file(bool rename_metafile) { // remove lock file ret = ::close(_lock_file_fd); if (ret != 0) { - tty->print_cr("Could not close [%s] because [%s]\n", + log_error(quickstart)("Could not close [%s] because [%s]\n", LOCK_FILE, strerror(errno)); } ret = ::remove(_lock_path); if (ret != 0) { - tty->print_cr("Could not delete [%s] because [%s]\n", + log_error(quickstart)("Could not delete [%s] because [%s]\n", LOCK_FILE, strerror(errno)); } } @@ -922,7 +976,7 @@ int QuickStart::remove_dir(const char* dir) { int ret = os::stat(dir, &dir_stat); if (ret < 0) { - tty->print_cr("Fail to get the stat for directory %s\n", dir); + log_error(quickstart)("Fail to get the stat for directory %s\n", dir); return ret; } @@ -947,7 +1001,7 @@ int QuickStart::remove_dir(const char* dir) { } ret = ::rmdir(dir); } else { - tty->print_cr("unknow file type\n"); + log_error(quickstart)("unknow file type\n"); } return ret; #endif @@ -957,7 +1011,7 @@ void QuickStart::notify_dump() { if (_role == Tracer || _role == Profiler || _role == Dumper) { generate_metadata_file(_role != Profiler); } - tty->print_cr("notifying dump done."); + log_info(quickstart)("notifying dump done."); } bool QuickStart::dump_cached_info(const JavaVMInitArgs* options_args) { @@ -1006,7 +1060,103 @@ bool QuickStart::dump_cached_info(const JavaVMInitArgs* options_args) { _temp_metadata_file->print_cr("%s", option->optionString); } + const char* envs = Arguments::get_property("com.alibaba.cds.cp.reloc.envs"); + if (envs != NULL) { + char env_i[JVM_MAXPATHLEN]; + int p1 = 0, p2 = 0; + bool envs_process_end = false; + while(!envs_process_end) { + if (envs[p2] == '\0') { + envs_process_end = true; + } + if((envs[p2] == ',' || envs[p2] == '\0')) { + strncpy(env_i, &envs[p1], p2-p1); + env_i[p2-p1] = '\0'; + p1 = p2 + 1; + const char* env_i_value = ::getenv(env_i); + if(env_i_value != NULL) { + _temp_metadata_file->print_cr("%s%s=%s", ENV_MARK, env_i, env_i_value); + } + } + p2++; + } + } + _temp_metadata_file->flush(); return true; } +bool QuickStart::need_convert_path_by_env() { + return old_envs != NULL; +} + +void QuickStart::convert_path_by_env(const char* origin_path, char* new_path) { + if (old_envs == NULL) { + log_error(quickstart)("Shouldn't convert path according to environment variables when envs is NULL."); + return; + } + + int p1 = 0, p2 = p1; + bool process_end = false; + while (!process_end) { + if (origin_path[p2] == '\0') { + process_end = true; + } + if((origin_path[p2] == *os::path_separator() || origin_path[p2] == '\0')) { + char split[JVM_MAXPATHLEN]; + strncpy(split, &origin_path[p1], p2-p1); + split[p2-p1] = '\0'; + char* path = replace_if_contains(split); + if (path != NULL) { + strcat(new_path, path); + FREE_C_HEAP_ARRAY(char, path, mtInternal); + } else { + strcat(new_path, split); + } + if (origin_path[p2] == *os::path_separator()) { + strcat(new_path, os::path_separator()); + } else { + strcat(new_path, "\0"); + } + p1 = p2 + 1; + } + p2++; + } +} + +char* QuickStart::replace_if_contains(const char* path) { + if(old_envs != NULL) { + int array_cnt = old_envs->length(); + for(int i = 0; i < array_cnt; i++) { + const char* target = old_envs->at(i); + if(strstr(path, target) != NULL) { + int target_len = strlen(target); + const char* new_target = new_envs->at(i); + int new_target_len = strlen(new_target); + char* new_path = NEW_C_HEAP_ARRAY(char, new_target_len + strlen(path) - target_len + 1, mtInternal); + new_path[0] = '\0'; // clear the buffer + strcat(new_path, new_target); + strcat(new_path, &path[target_len]); + strcat(new_path, "\0"); + return new_path; + } + } + } + + return NULL; +} + +int QuickStart::get_max_replaced_path_len(const char* origin_path) { + int origin_path_len = (int)strlen(origin_path); + if (_max_env_diff_len <= 0) return origin_path_len; + int count = 1; + int p = 0; + while(origin_path[p] != '\0') { + if (origin_path[p] == *os::path_separator()) { + count++; + } + p++; + } + + return origin_path_len + (count * _max_env_diff_len); +} diff --git a/hotspot/src/share/vm/runtime/quickStart.hpp b/hotspot/src/share/vm/runtime/quickStart.hpp index 0c0ef235ba1..aee4d799b92 100644 --- a/hotspot/src/share/vm/runtime/quickStart.hpp +++ b/hotspot/src/share/vm/runtime/quickStart.hpp @@ -87,6 +87,11 @@ class QuickStart : AllStatic { static const char** _jvm_options; static const char* _cp_in_metadata_file; + // old env, new env + static GrowableArray* old_envs; + static GrowableArray* new_envs; + static int _max_env_diff_len; // len(new_env) - len(old_env) + static bool set_optimization(const char* option, bool enabled); static bool determine_role(const JavaVMInitArgs* options_args); static bool prepare_dump(const JavaVMInitArgs* options_args); @@ -111,6 +116,13 @@ class QuickStart : AllStatic { static void set_opt_passed(opt feature); static void notify_dump(); + static bool need_convert_path_by_env(); + static void convert_path_by_env(const char* origin_path, char* new_path); + static char* replace_if_contains(const char* path); + static int get_max_replaced_path_len(const char* origin_path); +private: + static void read_env_from_file_and_environment(char* line); + // cds stuff private: static const char* _origin_class_list; diff --git a/jdk/test/com/alibaba/quickstart/TestEnvClass.java b/jdk/test/com/alibaba/quickstart/TestEnvClass.java new file mode 100644 index 00000000000..3a5b40726b1 --- /dev/null +++ b/jdk/test/com/alibaba/quickstart/TestEnvClass.java @@ -0,0 +1,10 @@ +public class TestEnvClass { + public static void main(String[] args) { + System.out.println("==TestEnvClass2=="); + TestEnvClass testEnvClass = new TestEnvClass(); + testEnvClass.printClassName2(); + } + public void printClassName2() { + System.out.println("==TestEnvClass2=="); + } +} diff --git a/jdk/test/com/alibaba/quickstart/TestEnvVariableDump.java b/jdk/test/com/alibaba/quickstart/TestEnvVariableDump.java new file mode 100644 index 00000000000..7c1852d92ac --- /dev/null +++ b/jdk/test/com/alibaba/quickstart/TestEnvVariableDump.java @@ -0,0 +1,61 @@ +/* + * @test + * @summary Test Environment Variable Dump + * @library /lib /lib/testlibrary + * @requires os.arch=="amd64" | os.arch=="aarch64" + * @run main/othervm/timeout=600 TestEnvVariableDump + */ +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import java.io.*; +import java.util.Map; +import static jdk.testlibrary.Asserts.*; + +public class TestEnvVariableDump { + private static String cachepath = System.getProperty("user.dir"); + private static final String metadata_file = "metadata"; + private static final String hadoop_home = "/hadoop_home"; + private static final String hadoop_common_home = "/hadoop_common_home"; + + public static void main(String[] args) throws Exception { + TestEnvVariableDump test = new TestEnvVariableDump(); + cachepath = cachepath + "/testEnvVariableDump"; + test.runAsTracer(); + test.checkMetadataFile(cachepath + "/" + metadata_file); + } + + private void runAsTracer() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose", "-Dcom.alibaba.cds.cp.reloc.envs=PWD,HADOOP_HOME,HADOOP_COMMON_HOME", "-XX:+IgnoreAppCDSDirCheck", "-version"); + Map env = pb.environment(); + env.put("PWD", cachepath); + env.put("HADOOP_HOME", cachepath + hadoop_home); + env.put("HADOOP_COMMON_HOME", cachepath + hadoop_common_home); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain("Running as tracer"); + output.shouldHaveExitValue(0); + } + + private void checkMetadataFile(String filepath) { + boolean pwd_checked = false; + boolean hadoop_home_checked = false; + boolean hadoop_common_home_checked = false; + try { + BufferedReader in = new BufferedReader(new FileReader(filepath)); + String str; + while ((str = in.readLine()) != null) { + if(str.contains("ENV.PWD")) { + pwd_checked = str.contains(cachepath); + } + if(str.contains("ENV.HADOOP_HOME")) { + hadoop_home_checked = str.contains(cachepath + hadoop_home); + } + if(str.contains("ENV.HADOOP_COMMON_HOME")) { + hadoop_common_home_checked = str.contains(cachepath + hadoop_common_home); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + assertTrue(pwd_checked && hadoop_home_checked && hadoop_common_home_checked); + } +} \ No newline at end of file diff --git a/jdk/test/com/alibaba/quickstart/TestEnvVariableReplay.java b/jdk/test/com/alibaba/quickstart/TestEnvVariableReplay.java new file mode 100644 index 00000000000..fc80b092f75 --- /dev/null +++ b/jdk/test/com/alibaba/quickstart/TestEnvVariableReplay.java @@ -0,0 +1,97 @@ +/* + * @test + * @summary Test Environment Variable Dump + * @library /lib /lib/testlibrary + * @requires os.arch=="amd64" | os.arch=="aarch64" + * @build TestEnvClass + * @run driver ClassFileInstaller -jar test.jar TestEnvClass + * @run main/othervm/timeout=600 TestEnvVariableReplay + */ +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import java.io.*; +import java.util.Map; +import static jdk.testlibrary.Asserts.*; + +public class TestEnvVariableReplay { + + private static String TESTJAR = "/test.jar"; + private static final String TESTNAME = "TestEnvClass"; + private static final String TESTCLASS = TESTNAME + ".class"; + + private static String cachepath = System.getProperty("user.dir"); + private static String newDir = System.getProperty("user.dir"); + private static final String metadata_file = "metadata"; + private static final String origin_home1 = "/origin_home1"; + private static final String origin_home2 = "/origin_home2"; + private static final String new_home1 = "/new_home1"; + private static final String new_home2 = "/new_home2_long"; + private static String classpath = ""; + + public static void main(String[] args) throws Exception { + TestEnvVariableReplay test = new TestEnvVariableReplay(); + cachepath = cachepath + "/testEnvVariableDump"; + newDir = newDir + "/newDir"; + test.makeAllDir(); + test.moveJar(System.getProperty("user.dir") + TESTJAR , newDir + origin_home1 + TESTJAR); + test.buildOriginClassPath(); + test.runAsTracer(); + test.moveJar(newDir + origin_home1 + TESTJAR, newDir + new_home1 + TESTJAR); + test.buildNewClassPath(); + test.runAsReplayer(); + } + + private void makeAllDir() { + mkdir(newDir); + mkdir(newDir + origin_home1); + mkdir(newDir + origin_home2); + mkdir(newDir + new_home1); + mkdir(newDir + new_home2); + } + + private void buildOriginClassPath() { + classpath = cachepath + ":" + newDir + origin_home1 + TESTJAR + ":" + newDir + origin_home2; + } + + private void buildNewClassPath() { + classpath = cachepath + ":" + newDir + new_home1 + TESTJAR + ":" + newDir + new_home2; + } + + private void moveJar(String from, String to) { + File file = new File(from); + assertTrue (file.renameTo(new File(to))); + file.delete(); + } + + private void mkdir(String dir_path){ + try { + File dir = new File(dir_path); + dir.mkdir(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void runAsTracer() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose", "-Dcom.alibaba.cds.cp.reloc.envs=HOME1,HOME2", "-XX:+IgnoreAppCDSDirCheck", "-cp", classpath, TESTNAME); + Map env = pb.environment(); + env.put("HOME1", newDir + origin_home1); + env.put("HOME2", newDir + origin_home2); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + System.out.println(output.getOutput()); + output.shouldContain("Running as tracer"); + output.shouldHaveExitValue(0); + } + + private void runAsReplayer() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xquickstart:path=" + cachepath, "-Xquickstart:verbose", "-Dcom.alibaba.cds.cp.reloc.envs=HOME1,HOME2", "-XX:+IgnoreAppCDSDirCheck", "-XX:+TraceClassPaths", "-cp", classpath, TESTNAME); + Map env = pb.environment(); + env.put("HOME1", newDir + new_home1); + env.put("HOME2", newDir + new_home2); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + System.out.println(output.getOutput()); + output.shouldContain("Running as replayer"); + output.shouldHaveExitValue(0); + } + +} \ No newline at end of file