# HG changeset patch # User mlarsson # Date 1456741193 -3600 # Mon Feb 29 11:19:53 2016 +0100 # Node ID 136491ea9b4a8f59f0c31692b5602ef309a2863d # Parent fc2c277bce1477d744ac4ea5008663c7124996f0 [mq]: 8146879.archiving diff --git a/src/os/posix/vm/os_posix.cpp b/src/os/posix/vm/os_posix.cpp --- a/src/os/posix/vm/os_posix.cpp +++ b/src/os/posix/vm/os_posix.cpp @@ -181,6 +181,10 @@ return vsnprintf(buf, len, fmt, args); } +int os::fileno(FILE* fp) { + return ::fileno(fp); +} + void os::Posix::print_load_average(outputStream* st) { st->print("load average:"); double loadavg[3]; diff --git a/src/os/windows/vm/os_windows.cpp b/src/os/windows/vm/os_windows.cpp --- a/src/os/windows/vm/os_windows.cpp +++ b/src/os/windows/vm/os_windows.cpp @@ -4575,6 +4575,9 @@ return 0; } +int os::fileno(FILE* fp) { + return _fileno(fp); +} // This code is a copy of JDK's sysSync // from src/windows/hpi/src/sys_api_md.c diff --git a/src/share/vm/logging/logFileOutput.cpp b/src/share/vm/logging/logFileOutput.cpp --- a/src/share/vm/logging/logFileOutput.cpp +++ b/src/share/vm/logging/logFileOutput.cpp @@ -36,13 +36,15 @@ const char* LogFileOutput::TimestampFormat = "%Y-%m-%d_%H-%M-%S"; const char* LogFileOutput::FileSizeOptionKey = "filesize"; const char* LogFileOutput::FileCountOptionKey = "filecount"; +const char* LogFileOutput::ExistingFileModeKey = "mode"; char LogFileOutput::_pid_str[PidBufferSize]; char LogFileOutput::_vm_start_time_str[StartTimeBufferSize]; LogFileOutput::LogFileOutput(const char* name) : LogFileStreamOutput(NULL), _name(os::strdup_check_oom(name, mtLogging)), _file_name(NULL), _archive_name(NULL), _archive_name_len(0), _current_size(0), - _rotate_size(0), _current_file(1), _file_count(0), _rotation_semaphore(1) { + _rotate_size(0), _current_file(1), _file_count(0), _rotation_semaphore(1), + _existing_file_mode(Archive) { _file_name = make_file_name(name, _pid_str, _vm_start_time_str); } @@ -72,7 +74,7 @@ os::free(const_cast(_name)); } -size_t LogFileOutput::parse_value(const char* value_str) { +size_t parse_value(const char* value_str) { char* end; unsigned long long value = strtoull(value_str, &end, 10); if (!isdigit(*value_str) || end != value_str + strlen(value_str) || value >= SIZE_MAX) { @@ -81,7 +83,7 @@ return value; } -bool LogFileOutput::configure_rotation(const char* options) { +bool LogFileOutput::configure(const char* options) { if (options == NULL || strlen(options) == 0) { return true; } @@ -122,6 +124,17 @@ break; } _rotate_size = value * K; + } else if (strcmp(ExistingFileModeKey, key) == 0) { + if (strcmp("archive", value_str) == 0) { + _existing_file_mode = Archive; + } else if (strcmp("append", value_str) == 0) { + _existing_file_mode = Append; + } else if (strcmp("truncate", value_str) == 0) { + _existing_file_mode = Truncate; + } else { + success = false; + break; + } } else { success = false; break; @@ -133,15 +146,67 @@ return success; } +static bool file_exists(const char* filename) { + struct stat dummy_stat; + return os::stat(filename, &dummy_stat) == 0; +} + +// Renames the given file (if it exists) from for example "jvm.log" to "jvm.log.X" +// where X is the lowest number such that the resulting filename doesn't already exist. +// This is done to prevent overwriting old log files by mistake. +static void archive_file(const char* filename) { + // Nothing to archive if the given file doesn't exist + if (!file_exists(filename)) { + return; + } + + // Size should be filename-length + dot + max digits in SIZE_MAX + null char + const size_t size = strlen(filename) + ceil(log10(static_cast(SIZE_MAX))) + 2; + char* archive_name = NEW_C_HEAP_ARRAY(char, size, mtLogging); + + bool archived = false; + for (size_t i = 0; i < SIZE_MAX; i++) { + jio_snprintf(archive_name, size, "%s." SIZE_FORMAT, filename, i); + if (file_exists(archive_name)) { + continue; + } + // Unique archive name found! + int ret = rename(filename, archive_name); + if (ret == 0) { + archived = true; + } + break; + } + + FREE_C_HEAP_ARRAY(char, archive_name); + if (archived) { + return; + } + + jio_fprintf(defaultStream::error_stream(), + "Unable to archive pre-existing log file: %s. " + "File will be appended instead.\n", filename); +} + bool LogFileOutput::initialize(const char* options) { - if (!configure_rotation(options)) { + if (!configure(options)) { return false; } + + if (_existing_file_mode == Archive) { + archive_file(_file_name); + } + _stream = fopen(_file_name, FileOpenMode); if (_stream == NULL) { - log_error(logging)("Could not open log file '%s' (%s).\n", _file_name, strerror(errno)); + log_error(logging)("Could not open log file '%s' (%s).", _file_name, strerror(errno)); return false; } + + if (_existing_file_mode == Truncate) { + os::ftruncate(fileno(_stream), 0); + } + return true; } diff --git a/src/share/vm/logging/logFileOutput.hpp b/src/share/vm/logging/logFileOutput.hpp --- a/src/share/vm/logging/logFileOutput.hpp +++ b/src/share/vm/logging/logFileOutput.hpp @@ -34,6 +34,7 @@ class LogFileOutput : public LogFileStreamOutput { private: static const char* FileOpenMode; + static const char* ExistingFileModeKey; static const char* FileCountOptionKey; static const char* FileSizeOptionKey; static const char* PidFilenamePlaceholder; @@ -59,11 +60,16 @@ // Semaphore used for synchronizing file rotations and writes Semaphore _rotation_semaphore; + enum ExistingFileMode { + Archive, + Append, + Truncate + } _existing_file_mode; + void archive(); void rotate(); - bool configure_rotation(const char* options); + bool configure(const char* options); char *make_file_name(const char* file_name, const char* pid_string, const char* timestamp_string); - static size_t parse_value(const char* value_str); bool should_rotate() { return _file_count > 0 && _rotate_size > 0 && _current_size >= _rotate_size; diff --git a/src/share/vm/runtime/os.hpp b/src/share/vm/runtime/os.hpp --- a/src/share/vm/runtime/os.hpp +++ b/src/share/vm/runtime/os.hpp @@ -516,6 +516,7 @@ static int ftruncate(int fd, jlong length); static int fsync(int fd); static int available(int fd, jlong *bytes); + static int fileno(FILE* fp); //File i/o operations diff --git a/test/serviceability/logging/TestExistingLogFile.java b/test/serviceability/logging/TestExistingLogFile.java new file mode 100644 --- /dev/null +++ b/test/serviceability/logging/TestExistingLogFile.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016, 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 TestExistingLogFile + * @summary Ensure log files are archived/appended/truncated properly. + * @library /testlibrary + * @run driver TestExistingLogFile + */ + +import java.nio.file.Files; +import java.nio.file.Paths; + +import jdk.test.lib.ProcessTools; +import jdk.test.lib.OutputAnalyzer; + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertTrue; +import static jdk.test.lib.Asserts.assertFalse; + +public class TestExistingLogFile { + private static final String ARCHIVED_LOG_SUFFIX = ".0"; + private static final String BASE_XLOG_CMD = "-Xlog:logging=trace:"; + private static final String EXPECTED_LINE = "Log configuration fully initialized."; + + public static void main(String[] args) throws Exception { + testArchiveMode(); + testAppendMode(); + testTruncateMode(); + } + + private static void testArchiveMode() throws Exception { + final String logFile = "archived.log"; + final String archivedLogFile = logFile + ARCHIVED_LOG_SUFFIX; + + System.out.println("Testing archive mode"); + runVMTwice(BASE_XLOG_CMD + logFile); + + assertTrue(Files.exists(Paths.get(archivedLogFile)), "Archived log file not found."); + assertEquals(1L, occurencesInFile(EXPECTED_LINE, logFile), + "Log file should contain exactly one occurence of the expected line."); + assertEquals(1L, occurencesInFile(EXPECTED_LINE, archivedLogFile), + "Archived log file should contain exactly one occurence of the expected line."); + + // Run it twice again make sure two new archive files are created + runVMTwice(BASE_XLOG_CMD + logFile); + assertTrue(Files.exists(Paths.get(logFile + ".1")), "Second archived log file not found."); + assertTrue(Files.exists(Paths.get(logFile + ".2")), "Third archived log file not found."); + } + + private static void testAppendMode() throws Exception { + final String logFile = "appended.log"; + final String archivedLogFile = logFile + ARCHIVED_LOG_SUFFIX; + + System.out.println("Testing append mode"); + runVMTwice(BASE_XLOG_CMD + logFile + "::mode=append"); + + assertFalse(Files.exists(Paths.get(archivedLogFile)), + "Log file was archived when it should have been appended."); + assertEquals(2L, occurencesInFile(EXPECTED_LINE, logFile), + "Appended log file should contain exactly two occurences of the expected line."); + } + + private static void testTruncateMode() throws Exception { + final String logFile = "truncated.log"; + final String archivedLogFile = logFile + ARCHIVED_LOG_SUFFIX; + + System.out.println("Testing truncate mode"); + runVMTwice(BASE_XLOG_CMD + logFile + "::mode=truncate"); + + assertFalse(Files.exists(Paths.get(archivedLogFile)), + "Log file was archived when it should have been truncated."); + assertEquals(1L, occurencesInFile(EXPECTED_LINE, logFile), + "Truncated log file should contain exactly one occurence of the expected line."); + } + + private static long occurencesInFile(final String string, final String file) throws Exception { + return Files.lines(Paths.get(file)) + .filter(s -> s.contains(string)).count(); + } + + private static void runVMTwice(final String options) throws Exception { + for (int i = 0; i < 2; i++) { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(options, "-version"); + Process p = pb.start(); + OutputAnalyzer output = new OutputAnalyzer(p); + output.shouldHaveExitValue(0); + } + } +} +