diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index b3c18e318a85..d2daa16f9dbc 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -370,6 +370,7 @@ void DynamicArchive::prepare_for_dynamic_dumping() { void DynamicArchive::dump(const char* archive_name, TRAPS) { assert(UseSharedSpaces && RecordDynamicDumpInfo, "already checked in arguments.cpp?"); assert(ArchiveClassesAtExit == nullptr, "already checked in arguments.cpp?"); + assert(!AutoCreateSharedArchive, "Should not call this function, instead call dump(TRAPS)"); ArchiveClassesAtExit = archive_name; if (Arguments::init_shared_archive_paths()) { prepare_for_dynamic_dumping(); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 1651aef99b08..58087c4a0940 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -173,9 +173,17 @@ FileMapInfo::FileMapInfo(bool is_static) { if (_is_static) { assert(_current_info == NULL, "must be singleton"); // not thread safe _current_info = this; + _full_path = Arguments::GetSharedArchivePath(); } else { assert(_dynamic_archive_info == NULL, "must be singleton"); // not thread safe _dynamic_archive_info = this; + _full_path = Arguments::GetSharedDynamicArchivePath(); + if (AutoCreateSharedArchive) { + if (!validate_archive()) { + // regenerate shared archive at exit + DynamicDumpSharedSpaces = true; + } + } } _file_offset = 0; _file_open = false; @@ -189,6 +197,22 @@ FileMapInfo::~FileMapInfo() { assert(_dynamic_archive_info == this, "must be singleton"); // not thread safe _dynamic_archive_info = NULL; } + if (_file_open) { + os::close(_fd); + } +} + +// Do preliminary validation on archive. More checks are in initialization. +bool FileMapInfo::validate_archive() { + if (!os::file_exists(_full_path)) { + return false; + } + // validate header info + if (!check_archive(_full_path, _is_static)) { + return false; + } + + return true; } void FileMapInfo::populate_header(size_t core_region_alignment) { @@ -204,10 +228,16 @@ void FileMapInfo::populate_header(size_t core_region_alignment) { // dynamic header including base archive name for non-default base archive c_header_size = sizeof(DynamicArchiveHeader); header_size = c_header_size; - if (!FLAG_IS_DEFAULT(SharedArchiveFile)) { - base_archive_name_size = strlen(Arguments::GetSharedArchivePath()) + 1; - header_size += base_archive_name_size; - base_archive_path_offset = c_header_size; + if (SharedArchiveFile != nullptr) { + // -XX:SharedArchiveFile= or : + char* def_base_archive = Arguments::get_default_shared_archive_path(); + if (strcmp(def_base_archive, Arguments::GetSharedArchivePath()) != 0) { + base_archive_name_size = strlen(Arguments::GetSharedArchivePath()) + 1; + header_size += base_archive_name_size; + base_archive_path_offset = c_header_size; + } + // Arguments::get_default_shared_archive_path creates buffer, needs release + FREE_C_HEAP_ARRAY(char, def_base_archive); } } _header = (FileMapHeader*)os::malloc(header_size, mtInternal); @@ -1083,27 +1113,35 @@ class FileHeaderHelper { return &_header; } - bool read_base_archive_name(char** target) { + // This function is call from FileMapInfo::get_base_archive_name, and could happen in very early + // vm stage and the log is not available yet. Do not use log in his function. + char* read_base_archive_name() { assert(_fd != -1, "Archive should be open"); size_t name_size = (size_t)_header._base_archive_name_size; assert(name_size != 0, "For non-default base archive, name size should be non-zero!"); - *target = NEW_C_HEAP_ARRAY(char, name_size, mtInternal); + char* base_name = NEW_C_HEAP_ARRAY(char, name_size, mtInternal); lseek(_fd, _header._base_archive_path_offset, SEEK_SET); // position to correct offset. - size_t n = os::read(_fd, *target, (unsigned int)name_size); + size_t n = os::read(_fd, base_name, (unsigned int)name_size); if (n != name_size) { - log_info(cds)("Unable to read base archive name from archive"); - FREE_C_HEAP_ARRAY(char, *target); - return false; + warning("Unable to read base archive name from archive"); + FREE_C_HEAP_ARRAY(char, base_name); + return nullptr; } - if (!os::file_exists(*target)) { - log_info(cds)("Base archive %s does not exist", *target); - FREE_C_HEAP_ARRAY(char, *target); - return false; + if (*(base_name + name_size - 1) != '\0' || strlen(base_name) != name_size - 1) { + warning("Base archive name is damaged"); + FREE_C_HEAP_ARRAY(char, base_name); + return nullptr; } - return true; + if (!os::file_exists(base_name)) { + warning("Base archive %s does not exist", base_name); + FREE_C_HEAP_ARRAY(char, base_name); + return nullptr; + } + return base_name; } }; +// See comment for get_base_archive_name. bool FileMapInfo::check_archive(const char* archive_name, bool is_static) { FileHeaderHelper file_helper; if (!file_helper.initialize(archive_name)) { @@ -1120,34 +1158,45 @@ bool FileMapInfo::check_archive(const char* archive_name, bool is_static) { return false; } if (header->_base_archive_path_offset != 0) { - log_info(cds)("_base_archive_path_offset should be 0"); - log_info(cds)("_base_archive_path_offset = " UINT32_FORMAT, header->_base_archive_path_offset); + warning("_base_archive_path_offset should be 0"); + warning("_base_archive_path_offset = " UINT32_FORMAT, header->_base_archive_path_offset); return false; } } else { if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) { - vm_exit_during_initialization("Not a top shared archive", archive_name); + if (!AutoCreateSharedArchive) { + vm_exit_during_initialization("Not a top shared archive", archive_name); + } return false; } unsigned int name_size = header->_base_archive_name_size; unsigned int path_offset = header->_base_archive_path_offset; unsigned int header_size = header->_header_size; - if (path_offset + name_size != header_size) { - log_info(cds)("_header_size should be equal to _base_archive_path_offset plus _base_archive_name_size"); - log_info(cds)(" _base_archive_name_size = " UINT32_FORMAT, name_size); - log_info(cds)(" _base_archive_path_offset = " UINT32_FORMAT, path_offset); - log_info(cds)(" _header_size = " UINT32_FORMAT, header_size); - return false; - } - char* base_name = NULL; - if (!file_helper.read_base_archive_name(&base_name)) { - return false; + if (name_size != 0 && path_offset != 0) { + if (path_offset + name_size != header_size) { + warning("_header_size should be equal to _base_archive_path_offset plus _base_archive_name_size"); + warning(" _base_archive_name_size = " UINT32_FORMAT, name_size); + warning(" _base_archive_path_offset = " UINT32_FORMAT, path_offset); + warning(" _header_size = " UINT32_FORMAT, header_size); + return false; + } + char* base_name = file_helper.read_base_archive_name(); + if (base_name == nullptr) { + return false; + } + FREE_C_HEAP_ARRAY(char, base_name); + } else { + if (name_size != 0 || path_offset != 0) { + warning("_base_archive_name_size and _base_archive_path_offset must be 0 at same time"); + return false; + } } - FREE_C_HEAP_ARRAY(char, base_name); } return true; } +// Since fail_continue calls log function and this function can be called in very early vm stage +// when log is not available yet so do not call fail_continue or do logging in this function. bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name, char** base_archive_name) { FileHeaderHelper file_helper; @@ -1157,12 +1206,13 @@ bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name, GenericCDSFileMapHeader* header = file_helper.get_generic_file_header(); if (header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) { // Not a dynamic header, no need to proceed further. + warning("Not a dynmaic archive"); return false; } if ((header->_base_archive_name_size == 0 && header->_base_archive_path_offset != 0) || (header->_base_archive_name_size != 0 && header->_base_archive_path_offset == 0)) { - fail_continue("Default base archive not set correct"); + warning("Default base archive not set correct"); return false; } if (header->_base_archive_name_size == 0 && @@ -1170,8 +1220,8 @@ bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name, *base_archive_name = Arguments::get_default_shared_archive_path(); } else { // read the base archive name - if (!file_helper.read_base_archive_name(base_archive_name)) { - *base_archive_name = NULL; + *base_archive_name = file_helper.read_base_archive_name(); + if (*base_archive_name == nullptr) { return false; } } @@ -2297,13 +2347,16 @@ bool FileMapInfo::initialize() { return false; } - if (!open_for_read()) { - return false; - } - if (!init_from_file(_fd)) { - return false; - } - if (!validate_header()) { + // AutoCreateSharedArchive + if (!open_for_read() || !init_from_file(_fd) || !validate_header()) { + if (_is_static) { + FileMapInfo::fail_continue("Initialize static archive failed."); + } else { + FileMapInfo::fail_continue("Initialize dynamic archive failed."); + if (AutoCreateSharedArchive) { + DynamicDumpSharedSpaces = true; + } + } return false; } return true; diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 7708bbe7fa58..f958da319267 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -444,6 +444,7 @@ class FileMapInfo : public CHeapObj { static void assert_mark(bool check); // File manipulation. + bool validate_archive() NOT_CDS_RETURN_(false); bool initialize() NOT_CDS_RETURN_(false); bool open_for_read(); void open_for_write(const char* path = NULL); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 4996f5457755..1f9246c5890c 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -3130,6 +3130,17 @@ jint Arguments::finalize_vm_init_args(bool patch_mod_javabase) { return JNI_ERR; } + if (AutoCreateSharedArchive) { + if (SharedArchiveFile == NULL) { + log_info(cds)("-XX:+AutoCreateSharedArchive must work with a valid SharedArchiveFile"); + return JNI_ERR; + } + if (ArchiveClassesAtExit != NULL) { + log_info(cds)("-XX:+AutoCreateSharedArchive does not work with ArchiveClassesAtExit"); + return JNI_ERR; + } + } + if (ArchiveClassesAtExit == NULL && !RecordDynamicDumpInfo) { FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, false); } else { @@ -3507,6 +3518,7 @@ bool Arguments::init_shared_archive_paths() { SharedDynamicArchivePath = nullptr; } } + if (SharedArchiveFile == NULL) { SharedArchivePath = get_default_shared_archive_path(); } else { @@ -3524,6 +3536,7 @@ bool Arguments::init_shared_archive_paths() { } } } + if (!is_dumping_archive()){ if (archives > 2) { vm_exit_during_initialization( @@ -3538,6 +3551,11 @@ bool Arguments::init_shared_archive_paths() { } else { SharedDynamicArchivePath = temp_archive_path; } + // +AutoCreateSharedArchive, regenerate the dynamic archive base on default archive. + if (AutoCreateSharedArchive && !os::file_exists(SharedArchivePath)) { + SharedDynamicArchivePath = temp_archive_path; + SharedArchivePath = get_default_shared_archive_path(); + } } else { extract_shared_archive_paths((const char*)SharedArchiveFile, &SharedArchivePath, &SharedDynamicArchivePath); diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 299613611c01..3f0857d0541f 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1825,6 +1825,9 @@ const intx ObjectAlignmentInBytes = 8; product(bool, RecordDynamicDumpInfo, false, \ "Record class info for jcmd VM.cds dynamic_dump") \ \ + product(bool, AutoCreateSharedArchive, false, \ + "Create shared archive at exit if cds mapping failed") \ + \ product(bool, PrintSharedArchiveAndExit, false, \ "Print shared archive file contents") \ \ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java index fbecc57d604a..dfe0fe22a40c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/ArchiveConsistency.java @@ -139,7 +139,8 @@ private static void doTest(String baseArchiveName, String topArchiveName) throws runTwo(baseArchiveName, wrongBasePathOffset, appJar, mainClass, 1, new String[] {"An error has occurred while processing the shared archive file.", - "Header checksum verification failed", + "Base archive name is damaged", + "Error occurred during initialization of VM", "Unable to use shared archive"}); // 5. Make base archive name not terminated with '\0' System.out.println("\n5. Make base archive name not terminated with '\0'"); @@ -152,8 +153,8 @@ private static void doTest(String baseArchiveName, String topArchiveName) throws runTwo(baseArchiveName, wrongBaseName, appJar, mainClass, 1, - new String[] {"Base archive " + baseArchiveName, - " does not exist", - "Header checksum verification failed"}); + new String[] {"Base archive name is damaged", + "Error occurred during initialization of VM", + "Unable to use shared archive"}); } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveTestBase.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveTestBase.java index f6e25cfdeb8d..7a7d67bf5e93 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveTestBase.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveTestBase.java @@ -94,6 +94,19 @@ public static String getNewArchiveName(String stem) { return TestCommon.getNewArchiveName(stem); } + /** + * Excute a JVM to dump a base archive by + * -Xshare:dump -XX:SharedArchiveFile=baseArchiveName + */ + public static Result dumpBaseArchive(String baseArchiveName, String... cmdLineSuffix) + throws Exception + { + OutputAnalyzer output = TestCommon.dumpBaseArchive(baseArchiveName, cmdLineSuffix); + CDSOptions opts = new CDSOptions(); + opts.setXShareMode("dump"); + return new Result(opts, output); + } + /** * Execute a JVM using the base archive (given by baseArchiveName) with the command line * (given by cmdLineSuffix). At JVM exit, dump all eligible classes into the top archive diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java new file mode 100644 index 000000000000..92afc08a3d29 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/TestAutoCreateSharedArchive.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * 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. + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @bug 8261455 + * @summary test -XX:+AutoCreateSharedArchive feature + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build Hello + * @build sun.hotspot.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:./WhiteBox.jar TestAutoCreateSharedArchive + */ + +/* + * -XX:SharedArchiveFile can be specified in two styles: + * + * (A) Test with default base archive -XX:+SharedArchiveFile= + * (B) Test with the base archive is specified: -XX:SharedArchiveFile=: + * all the following if not explained explicitly, run with flag -XX:+AutoCreateSharedArchive + * + * 10 Case (A) + * + * 10.1 run with non-existing archive should automatically create dynamic archive + * If the JDK's default CDS archive cannot be loaded, print out warning, run continue without shared archive + * and no shared archive created at exit. + * 10.2 run with the created dynamic archive should pass. + * 10.3 run with the created dynamic archive and -XX:+AutoCreateSharedArchive should pass and no shared archive created at exit. + * + * 11 run with damaged magic should not regenerate dynamic archive. + * Bad magic of the shared archive leads the archive open as static that will not find base archive. With base archive not shared, + * at exit, no shared archive (top) will be generated. + * 12 run with a bad versioned archive should create dynamic archive. + * A bad version of the archive still makes the archive open as dynamic so a new archive but failed to share, but base archive + * is shared, so the top archive will be generated at exit. + * 13 run with a bad jvm_ident archive should create dynamic archive + * The reason as 12. + * 14 Read base archive from top archive failed + * If stored base archive name is not correct, it will lead the archive opened as static and no shared in runtime, + * also no shared archive created at exit. + * 15 Create an archive with only dynamic magic value(size of 4) + * This created archive file contains only dynamic magic value, size of the file is 4 bytes. + * Run with this archive with default base archive failed due to fail to read file header. + * + * 20 (case B) + * + * 20.1 dump base archive which will be used for dumping top archive. + * 20.2 dump top archive based on base archive obtained in 20.1. + * 20.3 run -XX:SharedArchiveFile=: to verify the archives. + * + * 21 if version of top archive is not correct (not the current version), the archive cannot be shared and will be + * regenerated at exit. + * 22 if base archive is not with correct version, both base and top archives will not be shared. + * At exit, there is no shared archive created automatically. + * 23 create an archive with dynamic magic value only like in 15 + * Run with the base archive created in step 20.1 will exit abnormal due to fail to read file header. + * 24 Run -Xshare:auto -XX:SharedArchiveFile=base (created in 20.1) -XX:+AutoCreateSharedArchive + * Warning for not a dynamic archive, run with static archive. Not dynamic archive is created at exit. + */ + +import java.io.IOException; +import java.io.File; + +import java.nio.file.attribute.FileTime; +import java.nio.file.Files; +import java.nio.file.Paths; + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.cds.CDSArchiveUtils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.helpers.ClassFileInstaller; + +public class TestAutoCreateSharedArchive extends DynamicArchiveTestBase { + private static final String BASE_NAME = CDSTestUtils.getOutputFileName("base.jsa"); + private static final String TOP_NAME = CDSTestUtils.getOutputFileName("top.jsa"); + private static final String mainAppClass = "Hello"; + private static final String HELLO_SOURCE = "Hello source: shared objects file (top)"; + + public static void main(String[] args) throws Exception { + runTest(TestAutoCreateSharedArchive::testAutoCreateSharedArchive); + } + + public static void checkFileExists(String fileName) throws Exception { + File file = new File(fileName); + if (!file.exists()) { + throw new IOException("Archive " + fileName + " is not automatically created"); + } + } + + public static String startNewArchive(String testName) { + String newArchiveName = TestCommon.getNewArchiveName(testName); + TestCommon.setCurrentArchiveName(newArchiveName); + return newArchiveName; + } + + public static void print(String message) { + System.out.println(message); + } + + private static void testAutoCreateSharedArchive() throws Exception { + String appJar = ClassFileInstaller.getJarPath("hello.jar"); + + File archiveFile = new File(TOP_NAME); + if (archiveFile.exists()) { + archiveFile.delete(); + } + + // The list numbers try to match JDK-8272331 (CSR for JDK-8261455) test items but not exactly matched. + + // 10 non-existing archive should automatically create dynamic archive based on default shared archive + // if base archive loaded. + print("10 Test with default base shared archive"); + print("10.1 run with non-existing archive should automatically create dynamic archive"); + run(TOP_NAME, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0) + .shouldContain("Dumping shared data to file:") + .shouldContain(TOP_NAME); + }); + checkFileExists(TOP_NAME); + + // 10.2 run with the created dynamic archive should pass + print("10.2 run with the created dynamic archive should pass"); + run(TOP_NAME, + "-Xlog:cds", + "-Xlog:class+load", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0) + .shouldContain(HELLO_SOURCE); + }); + // remember the FileTime + FileTime ft1 = Files.getLastModifiedTime(Paths.get(TOP_NAME)); + + // 10.3 run with the created dynamic archive with -XX:+AutoCreateSharedArchive should pass + print("10.3 run with the created dynamic archive with -XX:+AutoCreateSharedArchive should pass"); + run(TOP_NAME, + "-Xlog:cds", + "-Xlog:class+load", + "-Xlog:cds+dynamic=info", + "-XX:+AutoCreateSharedArchive", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0) + .shouldNotContain("Dumping shared data to file") + .shouldContain(HELLO_SOURCE); + }); + FileTime ft2 = Files.getLastModifiedTime(Paths.get(TOP_NAME)); + if (!ft2.equals(ft1)) { + throw new RuntimeException("Archive file " + TOP_NAME + " should not be updated"); + } + + // 11 run with damaged magic should not regenerate dynamic archive + // The bad magic will make the archive be opened as static archive + // and failed, no shared for base archive either. + print("11 run with damaged magic should not regenerate dynamic archive"); + String modMagic = startNewArchive("modify-magic"); + File copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modMagic); + CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetMagic(), 0x1234); + ft1 = Files.getLastModifiedTime(Paths.get(modMagic)); + + run(modMagic, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0); + output.shouldNotContain("Dumping shared data to file"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(modMagic)); + if (!ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + modMagic + " should not automatically be generated"); + } + + // 12 run with a bad versioned archive should create dynamic archive + print("12 run with a bad versioned archive should create dynamic archive"); + String modVersion = startNewArchive("modify-version"); + copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modVersion); + CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), 0x00000000); + ft1 = Files.getLastModifiedTime(Paths.get(modVersion)); + + run(modVersion, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0); + }); + ft2 = Files.getLastModifiedTime(Paths.get(modVersion)); + if (ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + modVersion + " should automatically be generated"); + } + + // 13 run with a bad jvm_indent archive should create dynamic archive + print("13 run with a bad jvm_ident archive should create dynamic archive"); + String modJvmIdent = startNewArchive("modify-jvmident"); + copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modJvmIdent); + CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetJvmIdent(), 0x00000000); + ft1 = Files.getLastModifiedTime(Paths.get(modJvmIdent)); + + run(modJvmIdent, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0); + }); + ft2 = Files.getLastModifiedTime(Paths.get(modJvmIdent)); + if (ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + modJvmIdent + " should automatically be generated"); + } + + // 14 read base archive from top archive failed + // the failure will cause the archive be opened as static + // so no shared both for base and top + print("14 read base archive from top archive failed"); + String modBaseName = startNewArchive("modify-basename"); + copiedJsa = CDSArchiveUtils.copyArchiveFile(archiveFile, modBaseName); + int nameSize = CDSArchiveUtils.baseArchiveNameSize(copiedJsa); + int offset = CDSArchiveUtils.baseArchivePathOffset(copiedJsa); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < nameSize - 4; i++) { + sb.append('Z'); + } + sb.append(".jsa"); + sb.append('\0'); + String newName = sb.toString(); + CDSArchiveUtils.writeData(copiedJsa, offset, newName.getBytes()); + + ft1 = Files.getLastModifiedTime(Paths.get(modBaseName)); + run(modBaseName, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0); + output.shouldNotContain("Dumping shared data to file"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(modBaseName)); + if (!ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + modBaseName + " should not automatically be generated"); + } + + // 15 Create an archive with only dynamic magic (size of 4) + print("15 Create an archive with only dynamic magic (size of 4)"); + String magicOnly = startNewArchive("magic-only"); + copiedJsa = CDSArchiveUtils.createMagicOnlyFile(magicOnly, false/*dynamic*/); + ft1 = Files.getLastModifiedTime(Paths.get(magicOnly)); + run(magicOnly, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertAbnormalExit(output -> { + output.shouldHaveExitValue(1); + output.shouldContain("Unable to read generic CDS file map header from shared archive"); + output.shouldNotContain("Dumping shared data to file:"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(magicOnly)); + if (!ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + modBaseName + " should not automatically be generated"); + } + + // delete top archive + if (archiveFile.exists()) { + archiveFile.delete(); + } + // delete base archive + File baseFile = new File(BASE_NAME); + if (baseFile.exists()) { + baseFile.delete(); + } + // 20 Testing with -XX:SharedArchiveFile=base:top + print("20 Testing with -XX:SharedArchiveFile=base:top"); + // 20.1 dump base archive and top archive + print("20.1 dump base archive " + BASE_NAME); + dumpBaseArchive(BASE_NAME, "-Xlog:cds") + .assertNormalExit(output -> { + output.shouldHaveExitValue(0); + }); + checkFileExists(BASE_NAME); + + // 20.2 dump top based on base + print("20.2 dump top based on base"); + dump2(BASE_NAME, TOP_NAME, + "-Xlog:cds", + "-cp", appJar, mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0) + .shouldContain("Dumping shared data to file:") + .shouldContain(TOP_NAME); + }); + checkFileExists(TOP_NAME); + + // 20.3 run with base and top + print("20.3 run with base and top"); + run2(BASE_NAME, TOP_NAME, + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-Xlog:class+load", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0) + .shouldContain(HELLO_SOURCE); + }); + + // 21 top version is not correct, regenerate top + print("21 top version is not correct, regenerate top"); + String modHeader = startNewArchive("modify-header"); + File topFile = new File(TOP_NAME); + copiedJsa = CDSArchiveUtils.copyArchiveFile(topFile, modHeader); + CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), 0xff); + ft1 = Files.getLastModifiedTime(Paths.get(modHeader)); + + run2(BASE_NAME, modHeader, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0) + .shouldContain("Dumping shared data to file:") + .shouldContain(modHeader) + .shouldContain("Regenerate MethodHandle Holder classes"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(modHeader)); + if (ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + modBaseName + " should automatically be generated"); + } + + // 22 screw up base archive, will not generate top + print("22 screw up base archive, will not generate top"); + baseFile = new File(BASE_NAME); + String modBase = startNewArchive("modify-base"); + copiedJsa = CDSArchiveUtils.copyArchiveFile(baseFile, modBase); + CDSArchiveUtils.modifyHeaderIntField(copiedJsa, CDSArchiveUtils.offsetVersion(), 0xff); + ft1 = Files.getLastModifiedTime(Paths.get(TOP_NAME)); + + run2(modBase, TOP_NAME, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldContain("The shared archive file has the wrong version") + .shouldContain("Initialize static archive failed") + .shouldContain("Unable to map shared spaces") + .shouldContain("Hello World") + .shouldNotContain("Dumping shared data to file:"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(TOP_NAME)); + if (!ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + TOP_NAME + " should not be created at exit"); + } + + // 23 create an archive like in 15 + print("23 create an archive with dynamic magic number only"); + copiedJsa = CDSArchiveUtils.createMagicOnlyFile(magicOnly, false /*dynamic*/); + ft1 = Files.getLastModifiedTime(Paths.get(magicOnly)); + run2(BASE_NAME, magicOnly, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertAbnormalExit(output -> { + output.shouldContain("Unable to read generic CDS file map header from shared archive") + .shouldNotContain("Dumping shared data to file:"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(magicOnly)); + if (!ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + magicOnly + " should not be created at exit"); + } + + // 24 Run -Xshare:auto -XX:SharedArchiveFile=base (created in 20.1) -XX:+AutoCreateSharedArchive + // Warning for not a dynamic archive, run with static archive. No dynamic archive is created at exit. + print("24 Run -Xshare:auto -XX:SharedArchiveFile=base (created in 20.1) -XX:+AutoCreateSharedArchive"); + ft1 = Files.getLastModifiedTime(Paths.get(BASE_NAME)); + run(BASE_NAME, + "-Xshare:auto", + "-XX:+AutoCreateSharedArchive", + "-Xlog:cds", + "-Xlog:cds+dynamic=info", + "-cp", appJar, + mainAppClass) + .assertNormalExit(output -> { + output.shouldHaveExitValue(0); + output.shouldContain("VM warning: Not a dynmaic archive"); + output.shouldNotContain("Dumping shared data to file"); + }); + ft2 = Files.getLastModifiedTime(Paths.get(BASE_NAME)); + if (!ft1.equals(ft2)) { + throw new RuntimeException("Shared archive " + BASE_NAME + " should not be created at exit"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jcmd/JCmdTestFileSafety.java b/test/hotspot/jtreg/runtime/cds/appcds/jcmd/JCmdTestFileSafety.java index e6312521d157..8ecf21c0f61c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jcmd/JCmdTestFileSafety.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jcmd/JCmdTestFileSafety.java @@ -38,6 +38,13 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; + import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.Platform; @@ -61,6 +68,12 @@ static void checkContainAbsoluteLogPath(OutputAnalyzer output) throws Exception } } + static FileTime getLastModifiedTimeFor(String fileName) throws IOException { + Path p = Paths.get(fileName); + BasicFileAttributes bfa = Files.getFileAttributeView(p, BasicFileAttributeView.class).readAttributes(); + return bfa.lastModifiedTime(); + } + static void test() throws Exception { buildJars(); @@ -103,16 +116,20 @@ static void test() throws Exception { pid = app.getPid(); localFileName = subDir + File.separator + "MyDynamicDump.jsa"; test(localFileName, pid, noBoot, EXPECT_PASS); - app.stopApp(); - // cannot dynamically dump twice, restart - app = createLingeredApp("-cp", allJars, "-XX:+RecordDynamicDumpInfo"); - pid = app.getPid(); + FileTime time0 = getLastModifiedTimeFor(localFileName); + // do dynamic dump again, but set dir not writable, the process will exit. outputDirFile.setWritable(false); test(localFileName, pid, noBoot, EXPECT_FAIL); outputDirFile.setWritable(true); - app.stopApp(); - // MyDynamicDump.jsa should exist + // MyDynamicDump.jsa should exist but not modified. checkFileExistence(localFileName, true); + FileTime time1 = getLastModifiedTimeFor(localFileName); + if (time1.compareTo(time0) != 0) { + throw new RuntimeException("A new archive file created, it should not be created"); + } + if (app.getProcess().isAlive()) { + app.stopApp(); + } File rmFile = new File(localFileName); rmFile.delete(); outputDirFile.delete(); diff --git a/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java b/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java index 21f374a1c460..1fe463933e84 100644 --- a/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java +++ b/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java @@ -331,6 +331,18 @@ public static File copyArchiveFile(File orgJsaFile, String newName) throws Excep return newJsaFile; } + public static File createMagicOnlyFile(String fileName, boolean createStatic) throws Exception { + File file = new File(fileName); + if (file.exists()) { + file.delete(); + } + try (FileOutputStream out = new FileOutputStream(file)) { + ByteBuffer buffer = ByteBuffer.allocate(4).putInt(createStatic ? staticMagic: dynamicMagic); + out.write(buffer.array(), 0, 4); + } + return file; + } + private static FileChannel getFileChannel(File file, boolean write) throws Exception { List arry = new ArrayList(); arry.add(READ);