Fix CodeQL note-severity alerts: ignored error status of file and stream calls (#814)
24 files modified
2 files added
| | |
| | | * |
| | | * Copyright 2006-2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package com.forgerock.opendj.util; |
| | | |
| | |
| | | n = readLimit - bytesRead; |
| | | } |
| | | |
| | | bytesRead += n; |
| | | return parentStream.skip(n); |
| | | // The parent stream is allowed to skip fewer bytes than requested, so only account for the |
| | | // bytes which were actually skipped. |
| | | final long skipped = parentStream.skip(n); |
| | | if (skipped > 0) { |
| | | bytesRead += (int) skipped; |
| | | } |
| | | return skipped; |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions copyright 2012-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.forgerock.opendj.io; |
| | | |
| | |
| | | |
| | | // Ignore all unused trailing components. |
| | | final SizeLimitInputStream subSq = (SizeLimitInputStream) in; |
| | | if (subSq.getSizeLimit() - subSq.getBytesRead() > 0) { |
| | | logger.trace("Ignoring %d unused trailing bytes in ASN.1 SEQUENCE", |
| | | subSq.getSizeLimit() - subSq.getBytesRead()); |
| | | subSq.skip(subSq.getSizeLimit() - subSq.getBytesRead()); |
| | | final long unusedBytes = subSq.getSizeLimit() - subSq.getBytesRead(); |
| | | if (unusedBytes > 0) { |
| | | logger.trace("Ignoring %d unused trailing bytes in ASN.1 SEQUENCE", unusedBytes); |
| | | if (skipFully(subSq, unusedBytes) != unusedBytes) { |
| | | // The remaining components could not be skipped, so the reader would be left |
| | | // positioned in the middle of the sequence. |
| | | throw DecodeException.fatalError(ERR_ASN1_SKIP_TRUNCATED_VALUE.get(unusedBytes)); |
| | | } |
| | | } |
| | | |
| | | logger.trace("READ ASN.1 END SEQUENCE"); |
| | |
| | | // Read the header if haven't done so already |
| | | peekLength(); |
| | | |
| | | final long bytesSkipped = in.skip(peekLength); |
| | | final long bytesSkipped = skipFully(in, peekLength); |
| | | if (bytesSkipped != peekLength) { |
| | | final LocalizableMessage message = ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength); |
| | | throw DecodeException.fatalError(message); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Skips the requested number of bytes, retrying as needed since {@link InputStream#skip} is |
| | | * allowed to skip fewer bytes than requested even when the end of the stream has not been |
| | | * reached. |
| | | * |
| | | * @param stream |
| | | * The stream to skip bytes from. |
| | | * @param length |
| | | * The number of bytes to skip. |
| | | * @return The number of bytes actually skipped, which is smaller than {@code length} only if the |
| | | * end of the stream was reached. |
| | | * @throws IOException |
| | | * If an error occurs while skipping bytes. |
| | | */ |
| | | private static long skipFully(final InputStream stream, final long length) throws IOException { |
| | | long remaining = length; |
| | | while (remaining > 0) { |
| | | final long skipped = stream.skip(remaining); |
| | | if (skipped > 0) { |
| | | remaining -= skipped; |
| | | } else if (stream.read() < 0) { |
| | | // End of stream reached: no more bytes can be skipped. |
| | | break; |
| | | } else { |
| | | remaining--; |
| | | } |
| | | } |
| | | return length - remaining; |
| | | } |
| | | |
| | | /** |
| | | * Internal helper method reading the additional ASN.1 length bytes and |
| | | * transition to the next state if successful. |
| | | * |
| New file |
| | |
| | | /* |
| | | * The contents of this file are subject to the terms of the Common Development and |
| | | * Distribution License (the License). You may not use this file except in compliance with the |
| | | * License. |
| | | * |
| | | * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | * specific language governing permission and limitations under the License. |
| | | * |
| | | * When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package com.forgerock.opendj.util; |
| | | |
| | | import static org.fest.assertions.Assertions.*; |
| | | |
| | | import java.io.BufferedInputStream; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.InputStream; |
| | | |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** Test {@code SizeLimitInputStream}. */ |
| | | @SuppressWarnings("javadoc") |
| | | public final class SizeLimitInputStreamTestCase extends UtilTestCase { |
| | | |
| | | @Test |
| | | public void testSkipAccountsForTheBytesActuallySkipped() throws Exception { |
| | | // A BufferedInputStream only skips what its buffer holds, without reaching the end of the |
| | | // parent stream, which is exactly the case getBytesRead() used to get wrong. |
| | | final InputStream parent = new BufferedInputStream(new ByteArrayInputStream(new byte[32]), 4); |
| | | // Fill the buffer with 4 bytes and consume one of them, leaving 3 skippable bytes in it. |
| | | parent.read(); |
| | | |
| | | final SizeLimitInputStream stream = new SizeLimitInputStream(parent, 32); |
| | | assertThat(stream.skip(16)).isEqualTo(3); |
| | | assertThat(stream.getBytesRead()).isEqualTo(3); |
| | | } |
| | | |
| | | @Test |
| | | public void testSkipIsCappedToTheSizeLimit() throws Exception { |
| | | final SizeLimitInputStream stream = |
| | | new SizeLimitInputStream(new ByteArrayInputStream(new byte[32]), 8); |
| | | |
| | | assertThat(stream.skip(20)).isEqualTo(8); |
| | | assertThat(stream.getBytesRead()).isEqualTo(8); |
| | | assertThat(stream.read()).isEqualTo(-1); |
| | | } |
| | | |
| | | @Test |
| | | public void testSkipStopsAtTheEndOfTheParentStream() throws Exception { |
| | | final SizeLimitInputStream stream = |
| | | new SizeLimitInputStream(new ByteArrayInputStream(new byte[2]), 8); |
| | | |
| | | assertThat(stream.skip(8)).isEqualTo(2); |
| | | assertThat(stream.getBytesRead()).isEqualTo(2); |
| | | } |
| | | |
| | | @Test |
| | | public void testReadAccountsForTheBytesActuallyRead() throws Exception { |
| | | final SizeLimitInputStream stream = |
| | | new SizeLimitInputStream(new ByteArrayInputStream(new byte[32]), 8); |
| | | |
| | | assertThat(stream.read()).isEqualTo(0); |
| | | assertThat(stream.read(new byte[16])).isEqualTo(7); |
| | | assertThat(stream.getBytesRead()).isEqualTo(8); |
| | | assertThat(stream.read()).isEqualTo(-1); |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2013 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.forgerock.opendj.io; |
| | | |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.io.BufferedInputStream; |
| | | import java.io.ByteArrayInputStream; |
| | | |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Test class for ASN1InputStreamReader. |
| | | */ |
| | |
| | | final ByteArrayInputStream inStream = new ByteArrayInputStream(b); |
| | | return new ASN1InputStreamReader(inStream, maxElementSize); |
| | | } |
| | | |
| | | /** |
| | | * Returns a reader whose underlying stream skips fewer bytes than requested without having |
| | | * reached its end, which is what {@code BufferedInputStream} does once its buffer is partially |
| | | * consumed. |
| | | */ |
| | | private ASN1Reader getBufferedReader(final byte[] b) { |
| | | return new ASN1InputStreamReader(new BufferedInputStream(new ByteArrayInputStream(b), 4), 0); |
| | | } |
| | | |
| | | /** |
| | | * Tests that the trailing components of a sequence are fully skipped even when the underlying |
| | | * stream skips fewer bytes than requested. |
| | | * |
| | | * @throws Exception |
| | | * If an unexpected problem occurs. |
| | | */ |
| | | @Test |
| | | public void testDecodeSequenceIncompleteReadOverBufferedStream() throws Exception { |
| | | // A sequence holding ten booleans, of which only the first one is read, followed by an |
| | | // integer which must still be decoded correctly once the sequence has been skipped. |
| | | final byte[] b = |
| | | new byte[] { 0x30, 0x0C, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, |
| | | 0x01, 0x01, 0x02, 0x01, 0x7F }; |
| | | final ASN1Reader reader = getBufferedReader(b); |
| | | |
| | | reader.readStartSequence(); |
| | | assertFalse(reader.readBoolean()); |
| | | reader.readEndSequence(); |
| | | |
| | | assertEquals(reader.readInteger(), 127); |
| | | } |
| | | |
| | | /** |
| | | * Tests that {@code skipElement} does not report a truncated value when the underlying stream |
| | | * skips fewer bytes than requested. |
| | | * |
| | | * @throws Exception |
| | | * If an unexpected problem occurs. |
| | | */ |
| | | @Test |
| | | public void testSkipElementOverBufferedStream() throws Exception { |
| | | final byte[] b = |
| | | new byte[] { 0x30, 0x0C, 0x02, 0x01, 0x05, 0x04, 0x04, 0x61, 0x62, 0x63, 0x64, 0x02, |
| | | 0x01, 0x7F }; |
| | | final ASN1Reader reader = getBufferedReader(b); |
| | | |
| | | reader.readStartSequence(); |
| | | assertEquals(reader.readInteger(), 5); |
| | | reader.skipElement(); |
| | | assertEquals(reader.readInteger(), 127); |
| | | reader.readEndSequence(); |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2013-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.quicksetup; |
| | | |
| | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.nio.file.Files; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.opends.quicksetup.util.Utils; |
| | | import org.opends.server.util.ServerConstants; |
| | | |
| | |
| | | */ |
| | | public class LicenseFile |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | private static final String INSTALL_ROOT_SYSTEM_PROPERTY = "INSTALL_ROOT"; |
| | | /** The license file name in Legal directory. */ |
| | | private static final String LICENSE_FILE_NAME = "Forgerock_License.txt"; |
| | |
| | | String instanceLegalDirName = Utils.getInstancePathFromInstallPath(getInstallDirectory()) |
| | | + File.separator + LEGAL_FOLDER_NAME; |
| | | File instanceLegalDir = new File(instanceLegalDirName); |
| | | if (!instanceLegalDir.exists()) |
| | | if (!instanceLegalDir.isDirectory() && !instanceLegalDir.mkdirs()) |
| | | { |
| | | instanceLegalDir.mkdir(); |
| | | logger.warn(LocalizableMessage.raw("Unable to create the legal directory %s", instanceLegalDirName)); |
| | | } |
| | | return instanceLegalDirName; |
| | | } |
| | |
| | | String instanceLegalDirName = instanceDirname + File.separator + LEGAL_FOLDER_NAME; |
| | | File instanceLegalDir = new File(instanceLegalDirName); |
| | | |
| | | File approvalFile = new File(instanceLegalDir, ACCEPTED_LICENSE_FILE_NAME); |
| | | try |
| | | { |
| | | if (!instanceLegalDir.exists()) |
| | | Files.createDirectories(instanceLegalDir.toPath()); |
| | | if (!approvalFile.createNewFile() && !approvalFile.isFile()) |
| | | { |
| | | instanceLegalDir.mkdir(); |
| | | logger.warn(LocalizableMessage.raw("Unable to create the license approval file %s", approvalFile)); |
| | | } |
| | | new File(instanceLegalDir, ACCEPTED_LICENSE_FILE_NAME).createNewFile(); |
| | | } |
| | | catch (IOException e) |
| | | { |
| | | // do nothing |
| | | logger.warn(LocalizableMessage.raw( |
| | | "Unable to create the license approval file %s: %s", approvalFile, e.getLocalizedMessage())); |
| | | } |
| | | } |
| | | } |
| | |
| | | writer.write(getJavaArgPropertyForScript(scriptName) + "=" + argument); |
| | | } |
| | | } |
| | | |
| | | String libDir = Utils.getPath( |
| | | Utils.getInstancePathFromInstallPath(installPath), LIBRARIES_PATH_RELATIVE); |
| | | // Create directory if it doesn't exist yet |
| | | File fLib = new File(libDir); |
| | | if (!fLib.exists()) |
| | | { |
| | | fLib.mkdir(); |
| | | } |
| | | // final String destinationFile = Utils.getPath(libDir, isWindows() ? SET_JAVA_PROPERTIES_FILE_WINDOWS |
| | | // : SET_JAVA_PROPERTIES_FILE_UNIX); |
| | | } |
| | | |
| | | /** |
| | |
| | | generationDir = Files.createTempDirectory(CONFIG_GUIDE_DIR).toString(); |
| | | } else { |
| | | // Create new dir if necessary |
| | | new File(generationDir).mkdir(); |
| | | Files.createDirectories(new File(generationDir).toPath()); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | |
| | | throw new DirectoryException(DirectoryServer.getCoreConfigManager().getServerErrorResultCode(), m); |
| | | } |
| | | |
| | | if (tempFile.exists()) |
| | | { |
| | | // Rename the existing "live" file out of the way and move the new file |
| | | // into place. |
| | | try |
| | | { |
| | | oldFile.delete(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | } |
| | | |
| | | // Rename the existing "live" file out of the way and move the new file into place. |
| | | // renameFile() deletes an existing ".old" file and reports a failure to do so. |
| | | try |
| | | { |
| | | if (ldifFile.exists()) |
| | | { |
| | | ldifFile.renameTo(oldFile); |
| | | renameFile(ldifFile, oldFile); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | |
| | | |
| | | try |
| | | { |
| | | tempFile.renameTo(ldifFile); |
| | | renameFile(tempFile, ldifFile); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | writer.close(); |
| | | |
| | | |
| | | // See if there is a ".save" file. If so, then delete it. |
| | | File saveFile = getFileForPath(backingFilePath + ".save"); |
| | | try |
| | | { |
| | | if (saveFile.exists()) |
| | | { |
| | | saveFile.delete(); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | |
| | | |
| | | // If there is an existing backing file, then rename it to ".save". |
| | | // renameFile() deletes an existing ".save" file and reports a failure to do so. |
| | | File saveFile = getFileForPath(backingFilePath + ".save"); |
| | | File backingFile = getFileForPath(backingFilePath); |
| | | try |
| | | { |
| | | if (backingFile.exists()) |
| | | { |
| | | backingFile.renameTo(saveFile); |
| | | renameFile(backingFile, saveFile); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | |
| | | File tmpFile = getFileForPath(tmpFilePath); |
| | | try |
| | | { |
| | | tmpFile.renameTo(backingFile); |
| | | renameFile(tmpFile, backingFile); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | } |
| | | |
| | | // If a ".startok" file already exists, then move it to an ".old" file. |
| | | // renameFile() deletes an existing ".old" file and reports a failure to do so. |
| | | File oldFile = new File(oldFilePath); |
| | | try |
| | | { |
| | | if (oldFile.exists()) |
| | | { |
| | | oldFile.delete(); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | } |
| | | |
| | | File startOKFile = new File(startOKFilePath); |
| | | try |
| | | { |
| | | if (startOKFile.exists()) |
| | | { |
| | | startOKFile.renameTo(oldFile); |
| | | renameFile(startOKFile, oldFile); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | |
| | | // Rename the temp file to the ".startok" file. |
| | | try |
| | | { |
| | | tempFile.renameTo(startOKFile); |
| | | renameFile(tempFile, startOKFile); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | |
| | | // Move the current config file out of the way and replace it with the updated version. |
| | | File oldSource = new File(sourceFile.getAbsolutePath() + ".prechanges"); |
| | | if (oldSource.exists()) |
| | | { |
| | | oldSource.delete(); |
| | | } |
| | | sourceFile.renameTo(oldSource); |
| | | new File(tempFilePath).renameTo(sourceFile); |
| | | renameFile(sourceFile, oldSource); |
| | | renameFile(new File(tempFilePath), sourceFile); |
| | | |
| | | // Move the changes file out of the way so it doesn't get applied again. |
| | | File newChanges = new File(changesFile.getAbsolutePath() + ".applied"); |
| | | if (newChanges.exists()) |
| | | { |
| | | newChanges.delete(); |
| | | } |
| | | changesFile.renameTo(newChanges); |
| | | renameFile(changesFile, newChanges); |
| | | } |
| | | |
| | | private void applyConfigChangesIfNeeded(File configFileToUse) throws InitializationException |
| | |
| | | * |
| | | * Copyright 2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2013-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.core; |
| | | |
| | |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.util.Collection; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | |
| | | |
| | | if (liveFile.exists()) |
| | | { |
| | | // Keeping a copy of the previous version is best effort only: save() is called from the |
| | | // entry encoding path, so failing here would turn a backup problem into a failed update. |
| | | final File saveFile = new File(liveFile.getAbsolutePath() + ".save"); |
| | | if (saveFile.exists()) |
| | | try |
| | | { |
| | | saveFile.delete(); |
| | | renameFile(liveFile, saveFile); |
| | | } |
| | | liveFile.renameTo(saveFile); |
| | | catch (final IOException e) |
| | | { |
| | | logger.traceException(e); |
| | | logger.warn(WARN_COMPRESSEDSCHEMA_CANNOT_SAVE_PREVIOUS_DATA, liveFile, saveFile, |
| | | stackTraceToSingleLineString(e)); |
| | | } |
| | | } |
| | | tempFile.renameTo(liveFile); |
| | | renameFile(tempFile, liveFile); |
| | | } |
| | | catch (final Exception e) |
| | | { |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2013-2015 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.core; |
| | | |
| | |
| | | File f = getFileForPath(lockFile); |
| | | try |
| | | { |
| | | if (! f.exists()) |
| | | if (! f.createNewFile()) |
| | | { |
| | | f.createNewFile(); |
| | | logger.trace("Lock file %s already exists", lockFile); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | |
| | | File f = getFileForPath(lockFile); |
| | | try |
| | | { |
| | | if (! f.exists()) |
| | | if (! f.createNewFile()) |
| | | { |
| | | f.createNewFile(); |
| | | logger.trace("Lock file %s already exists", lockFile); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | |
| | | private long totalFilesRotated; |
| | | private long totalFilesCleaned; |
| | | |
| | | /** |
| | | * Set when the current file could not be renamed by {@link #rotate()} and cleared as soon as a |
| | | * rotation succeeds again. While it is set, size based rotations are not triggered from |
| | | * {@link #writeRecord(String)} and the failure is not logged again: the rotation is only retried |
| | | * from the {@code RotaterThread}, once per interval. |
| | | */ |
| | | private boolean rotationFailed; |
| | | /** Same latch as {@link #rotationFailed}, for the log files a retention policy cannot delete. */ |
| | | private boolean cleanupFailed; |
| | | |
| | | /** The underlying output stream. */ |
| | | private MeteredStream outputStream; |
| | | /** The underlying buffered writer using the output stream. */ |
| | |
| | | int bufferSize) |
| | | throws IOException, DirectoryException |
| | | { |
| | | // Create new file if it doesn't exist |
| | | if(!file.exists()) |
| | | { |
| | | file.createNewFile(); |
| | | } |
| | | |
| | | // The file is created by the output stream below if it does not exist yet. |
| | | FileOutputStream stream = new FileOutputStream(file, append); |
| | | outputStream = new MeteredStream(stream, file.length()); |
| | | |
| | |
| | | File[] files = |
| | | retentionPolicy.deleteFiles(writer.getNamingPolicy()); |
| | | |
| | | int cleanedCount = 0; |
| | | for(File file : files) |
| | | { |
| | | file.delete(); |
| | | totalFilesCleaned++; |
| | | logger.trace("%s cleaned up log file %s", retentionPolicy, file); |
| | | if (file.delete()) |
| | | { |
| | | cleanedCount++; |
| | | totalFilesCleaned++; |
| | | cleanupFailed = false; |
| | | logger.trace("%s cleaned up log file %s", retentionPolicy, file); |
| | | } |
| | | else if (!cleanupFailed) |
| | | { |
| | | // Only report the first failure: the same files are returned on every interval. |
| | | cleanupFailed = true; |
| | | logger.warn(WARN_LOGGER_ERROR_DELETING_FILE, file, retentionPolicy); |
| | | } |
| | | } |
| | | |
| | | if(files.length > 0) |
| | | if(cleanedCount > 0) |
| | | { |
| | | lastCleanTime = TimeThread.getCalendar(); |
| | | lastCleanCount = files.length; |
| | | lastCleanCount = cleanedCount; |
| | | } |
| | | } |
| | | catch(DirectoryException de) |
| | |
| | | |
| | | synchronized(this) |
| | | { |
| | | if(sizeLimit > 0 && outputStream.written + size + 1 >= sizeLimit) |
| | | // Once a rotation has failed the file stays over the size limit, so rotating it again for |
| | | // every single record would only repeat the failure. Leave the retry to the RotaterThread. |
| | | if(sizeLimit > 0 && !rotationFailed && outputStream.written + size + 1 >= sizeLimit) |
| | | { |
| | | rotate(); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * Tries to rotate the log files. If the new log file already exists, it |
| | | * tries to rename the file. On failure, all subsequent log write requests |
| | | * will throw exceptions. |
| | | * Tries to rotate the log files by renaming the current file to the name provided by the naming |
| | | * policy. When the rename fails, the current file is kept and appended to rather than truncated, |
| | | * the failure is reported once and the rotation is retried on the next interval. |
| | | * <p> |
| | | * Note that {@code File.renameTo} silently replaces the target on most platforms, so a rotation |
| | | * happening within the same second as a previous one overwrites the file it just rotated. |
| | | */ |
| | | private synchronized void rotate() |
| | | { |
| | |
| | | |
| | | File currentFile = namingPolicy.getInitialName(); |
| | | File newFile = namingPolicy.getNextName(); |
| | | currentFile.renameTo(newFile); |
| | | final boolean renamed = currentFile.renameTo(newFile); |
| | | // The latch must be set before the writer is re-opened: constructWriter() logs warnings of its |
| | | // own, and when this writer backs the error log they come straight back into writeRecord() on |
| | | // this thread, where they must not trigger another rotation attempt. |
| | | final boolean report = !renamed && !rotationFailed; |
| | | rotationFailed = !renamed; |
| | | |
| | | try |
| | | { |
| | | constructWriter(currentFile, filePermissions, encoding, append, |
| | | // If the file could not be rotated then keep appending to it rather than truncating it. |
| | | constructWriter(currentFile, filePermissions, encoding, append || !renamed, |
| | | bufferSize); |
| | | } |
| | | catch (Exception e) |
| | |
| | | errorHandler.handleOpenError(currentFile, e); |
| | | } |
| | | |
| | | logger.trace("Log file %s rotated and renamed to %s", currentFile, newFile); |
| | | totalFilesRotated++; |
| | | lastRotationTime = TimeThread.getCalendar(); |
| | | if (renamed) |
| | | { |
| | | logger.trace("Log file %s rotated and renamed to %s", currentFile, newFile); |
| | | totalFilesRotated++; |
| | | lastRotationTime = TimeThread.getCalendar(); |
| | | } |
| | | else if (report) |
| | | { |
| | | logger.error(ERR_LOGGER_ERROR_ROTATING_FILE, currentFile, newFile); |
| | | } |
| | | // lastRotationTime is left untouched on failure so that a time based policy keeps asking for a |
| | | // rotation on every interval instead of waiting for a whole new period. |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | try |
| | | { |
| | | if(!logFile.exists()) |
| | | if(!logFile.createNewFile()) |
| | | { |
| | | logFile.createNewFile(); |
| | | logger.trace("Referential integrity update log file %s already exists", logFileName); |
| | | } |
| | | } |
| | | catch (IOException io) |
| | |
| | | } |
| | | } |
| | | } |
| | | logFile.delete(); |
| | | logFile.createNewFile(); |
| | | if (!logFile.delete() || !logFile.createNewFile()) { |
| | | logger.error(ERR_PLUGIN_REFERENT_CANNOT_REPLACE_LOGFILE, logFileName); |
| | | } |
| | | } catch (IOException io) { |
| | | logger.error(ERR_PLUGIN_REFERENT_REPLACE_LOGFILE, io.getMessage()); |
| | | } |
| | |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | import java.io.File; |
| | | import java.nio.file.Files; |
| | | import java.util.Collections; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | |
| | | final File dbDirectory = getFileForPath(dbDirName); |
| | | try |
| | | { |
| | | if (!dbDirectory.exists()) |
| | | { |
| | | dbDirectory.mkdir(); |
| | | } |
| | | Files.createDirectories(dbDirectory.toPath()); |
| | | return dbDirectory; |
| | | } |
| | | catch (Exception e) |
| | |
| | | { |
| | | try |
| | | { |
| | | if (!logfile.exists()) |
| | | if (!logfile.createNewFile()) |
| | | { |
| | | logfile.createNewFile(); |
| | | logger.trace("Log file %s already exists", logfile.getPath()); |
| | | } |
| | | } |
| | | catch (IOException e) |
| | |
| | | final File newRotationFile = getLastRotationTimePath(lastRotationTime); |
| | | try |
| | | { |
| | | newRotationFile.createNewFile(); |
| | | if (!newRotationFile.createNewFile() && !newRotationFile.isFile()) |
| | | { |
| | | throw new ChangelogException(ERR_CHANGELOG_UNABLE_TO_CREATE_LAST_LOG_ROTATION_TIME_FILE.get( |
| | | newRotationFile.getPath(), lastRotationTime)); |
| | | } |
| | | } |
| | | catch (IOException e) |
| | | { |
| | |
| | | matchingRuleUses, ldapSyntaxes); |
| | | |
| | | File upgradeDirectory = getUpgradeDirectory(); |
| | | upgradeDirectory.mkdir(); |
| | | Files.createDirectories(upgradeDirectory.toPath()); |
| | | File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME); |
| | | concatFilePath = concatFile.getAbsolutePath(); |
| | | |
| | |
| | | writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes); |
| | | } |
| | | |
| | | if (concatFile.exists()) |
| | | { |
| | | concatFile.delete(); |
| | | } |
| | | tempFile.renameTo(concatFile); |
| | | renameFile(tempFile, concatFile); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2013-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | |
| | | package org.opends.server.tools.upgrade; |
| | |
| | | import java.io.FileReader; |
| | | import java.io.IOException; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.opends.server.util.StaticUtils; |
| | | |
| | | /** |
| | |
| | | */ |
| | | class LicenseFile |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | private static final String INSTALL_ROOT_SYSTEM_PROPERTY = "INSTALL_ROOT"; |
| | | |
| | | /** |
| | |
| | | final String instanceDirname = UpgradeUtils.getInstancePathFromInstallPath(getInstallRootPathFromSystem(".")); |
| | | final String instanceLegalDirName = instanceDirname + File.separator + LEGAL_FOLDER_NAME; |
| | | final File instanceLegalDir = new File(instanceLegalDirName); |
| | | if (!instanceLegalDir.exists()) |
| | | if (!instanceLegalDir.isDirectory() && !instanceLegalDir.mkdirs()) |
| | | { |
| | | instanceLegalDir.mkdir(); |
| | | logger.warn(LocalizableMessage.raw("Unable to create the legal directory %s", instanceLegalDirName)); |
| | | } |
| | | return instanceLegalDirName; |
| | | } |
| | |
| | | { |
| | | if (getApproval()) |
| | | { |
| | | final File approvalFile = new File(getInstanceLegalDirectory(), ACCEPTED_LICENSE_FILE_NAME); |
| | | try |
| | | { |
| | | new File(getInstanceLegalDirectory(), ACCEPTED_LICENSE_FILE_NAME).createNewFile(); |
| | | if (!approvalFile.createNewFile() && !approvalFile.isFile()) |
| | | { |
| | | logger.warn(LocalizableMessage.raw("Unable to create the license approval file %s", approvalFile)); |
| | | } |
| | | } |
| | | catch (IOException e) |
| | | { // do nothing |
| | | { |
| | | logger.warn(LocalizableMessage.raw( |
| | | "Unable to create the license approval file %s: %s", approvalFile, e.getLocalizedMessage())); |
| | | } |
| | | } |
| | | } |
| | |
| | | import java.io.FileReader; |
| | | import java.io.FilenameFilter; |
| | | import java.io.IOException; |
| | | import java.nio.file.Files; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.Map; |
| | |
| | | { |
| | | logger.debug(LocalizableMessage.raw("Parent file of %s doesn't exist", destination.getPath())); |
| | | |
| | | parentDirectory.mkdirs(); |
| | | Files.createDirectories(parentDirectory.toPath()); |
| | | |
| | | logger.debug(LocalizableMessage.raw("Parent directory %s created.", parentDirectory.getPath())); |
| | | } |
| | | if (!destination.exists()) |
| | | { |
| | | destination.createNewFile(); |
| | | } |
| | | |
| | | logger.debug(LocalizableMessage.raw("Writing entries in %s.", destination.getAbsolutePath())); |
| | | |
| | | // The destination file is created by the output stream below if it does not exist yet. |
| | | try (LDIFEntryWriter writer = new LDIFEntryWriter(new FileOutputStream(destination))) |
| | | { |
| | | writer.writeEntry(theNewSchemaEntry); |
| | |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Portions Copyright 2014-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.types; |
| | | |
| | |
| | | import java.io.FileReader; |
| | | import java.io.FileWriter; |
| | | import java.io.IOException; |
| | | import java.nio.file.Files; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | |
| | | // Rename the new descriptor file to match the previous one. |
| | | try |
| | | { |
| | | newDescriptorFile.renameTo(descriptorFile); |
| | | renameFile(newDescriptorFile, descriptorFile); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | { |
| | | try |
| | | { |
| | | dir.mkdirs(); |
| | | Files.createDirectories(dir.toPath()); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | { |
| | | String savedDescriptorFilePath = descriptorFilePath + ".save"; |
| | | File savedDescriptorFile = new File(savedDescriptorFilePath); |
| | | if (savedDescriptorFile.exists()) |
| | | { |
| | | try |
| | | { |
| | | savedDescriptorFile.delete(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | LocalizableMessage message = ERR_BACKUPDIRECTORY_CANNOT_DELETE_SAVED_DESCRIPTOR.get( |
| | | savedDescriptorFilePath, getExceptionMessage(e), descriptorFilePath, descriptorFilePath); |
| | | throw new IOException(message.toString()); |
| | | } |
| | | } |
| | | |
| | | try |
| | | { |
| | | descriptorFile.renameTo(savedDescriptorFile); |
| | | // renameFile() deletes an existing target and reports a failure to do so. |
| | | renameFile(descriptorFile, savedDescriptorFile); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | * |
| | | * Copyright 2006-2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2013-2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.types; |
| | | |
| | |
| | | case APPEND: |
| | | // Create new file if it doesn't exist ensuring that we can |
| | | // set its permissions. |
| | | if (!f.exists()) |
| | | { |
| | | f.createNewFile(); |
| | | mustSetPermissions = true; |
| | | } |
| | | mustSetPermissions = f.createNewFile(); |
| | | ldifOutputStream = new FileOutputStream(ldifFile, true); |
| | | break; |
| | | case OVERWRITE: |
| | | // Create new file if it doesn't exist ensuring that we can |
| | | // set its permissions. |
| | | if (!f.exists()) |
| | | { |
| | | f.createNewFile(); |
| | | mustSetPermissions = true; |
| | | } |
| | | mustSetPermissions = f.createNewFile(); |
| | | ldifOutputStream = new FileOutputStream(ldifFile, false); |
| | | break; |
| | | case FAIL: |
| | | if (f.exists()) |
| | | // Create new file ensuring that we can set its permissions. The creation is |
| | | // atomic, hence it also fails if the file was created by someone else in |
| | | // the mean time. |
| | | if (!f.createNewFile()) |
| | | { |
| | | LocalizableMessage message = ERR_LDIF_FILE_EXISTS.get(ldifFile); |
| | | throw new IOException(message.toString()); |
| | | } |
| | | // Create new file ensuring that we can set its permissions. |
| | | f.createNewFile(); |
| | | mustSetPermissions = true; |
| | | ldifOutputStream = new FileOutputStream(ldifFile); |
| | | break; |
| | |
| | | # |
| | | # Copyright 2006-2010 Sun Microsystems, Inc. |
| | | # Portions Copyright 2011-2016 ForgeRock AS. |
| | | # Portions Copyright 2026 3A Systems, LLC. |
| | | |
| | | |
| | | # |
| | |
| | | provided object class set because it used an undefined token %s |
| | | ERR_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA_622=Unable to write the \ |
| | | updated compressed schema token data: %s |
| | | WARN_COMPRESSEDSCHEMA_CANNOT_SAVE_PREVIOUS_DATA_756=Unable to keep a copy of \ |
| | | the previous compressed schema token data by renaming %s to %s: %s. The \ |
| | | updated token data has still been written |
| | | ERR_ENTRYENCODECFG_INVALID_LENGTH_623=Unable to decode the provided \ |
| | | entry encode configuration element because it has an invalid length |
| | | INFO_RESULT_NO_OPERATION_624=No Operation |
| | |
| | | # |
| | | # Copyright 2006-2008 Sun Microsystems, Inc. |
| | | # Portions Copyright 2015-2016 ForgeRock AS. |
| | | # Portions Copyright 2026 3A Systems, LLC. |
| | | |
| | | # |
| | | # Global directives |
| | |
| | | common audit log publisher %s, the keystore file %s could not be read: %s |
| | | ERR_COMMON_AUDIT_KEYSTORE_FILE_IS_EMPTY_33=Error while processing \ |
| | | common audit log publisher %s, the keystore file %s is empty |
| | | ERR_LOGGER_ERROR_ROTATING_FILE_34=Error occurred while rotating log file %s to \ |
| | | %s. The current log file will be kept and appended to, and the rotation will be \ |
| | | retried on the next interval. Any further rotation errors will be ignored until \ |
| | | a rotation succeeds |
| | | WARN_LOGGER_ERROR_DELETING_FILE_35=Error occurred while deleting log file %s \ |
| | | while enforcing retention policy %s. Any further deletion errors will be ignored \ |
| | | until a log file is deleted successfully |
| | |
| | | # |
| | | # Copyright 2006-2010 Sun Microsystems, Inc. |
| | | # Portions Copyright 2014-2016 ForgeRock AS. |
| | | # Portions Copyright 2026 3A Systems, LLC. |
| | | |
| | | |
| | | |
| | |
| | | Referential Integrity plugin update log file: %s |
| | | ERR_PLUGIN_REFERENT_REPLACE_LOGFILE_84=An error occurred replacing the \ |
| | | Referential Integrity plugin update log file: %s |
| | | ERR_PLUGIN_REFERENT_CANNOT_REPLACE_LOGFILE_130=The Referential Integrity \ |
| | | plugin update log file %s could not be deleted and created again after its \ |
| | | records were processed. Those records will be processed again the next time \ |
| | | the log file is read |
| | | INFO_PLUGIN_REFERENT_LOGFILE_CHANGE_REQUIRES_RESTART_85=The file name that \ |
| | | the Referential Integrity plugin logs changes to during background \ |
| | | processing has been changed from %s to %s, but this change will not take \ |
| New file |
| | |
| | | /* |
| | | * The contents of this file are subject to the terms of the Common Development and |
| | | * Distribution License (the License). You may not use this file except in compliance with the |
| | | * License. |
| | | * |
| | | * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | * specific language governing permission and limitations under the License. |
| | | * |
| | | * When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.loggers; |
| | | |
| | | import static org.mockito.Mockito.*; |
| | | import static org.testng.Assert.*; |
| | | |
| | | import java.io.File; |
| | | import java.io.FilenameFilter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.util.List; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.server.config.server.SizeLimitLogRotationPolicyCfg; |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.types.FilePermission; |
| | | import org.testng.annotations.AfterMethod; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.BeforeMethod; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** Tests the rotation of {@link MultifileTextWriter}, in particular when the rename fails. */ |
| | | @SuppressWarnings("javadoc") |
| | | public class MultifileTextWriterTestCase extends DirectoryServerTestCase |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | private static final DN PUBLISHER_DN = DN.valueOf("cn=Test Logger,cn=Loggers,cn=config"); |
| | | /** Small enough for two records to overflow it, large enough for one not to. */ |
| | | private static final long SIZE_LIMIT = 64; |
| | | private static final String RECORD_A = "a".repeat(32); |
| | | private static final String RECORD_B = "b".repeat(32); |
| | | /** Long enough to leave the re-opened stream over the size limit together with the first record. */ |
| | | private static final String STAND_IN_WARNING = |
| | | "stand-in for the permission warnings logged while the writer is re-opened"; |
| | | |
| | | /** Naming policy with fixed names, so that the test does not depend on the current second. */ |
| | | private static final class FixedNamingPolicy implements FileNamingPolicy |
| | | { |
| | | private final File initialFile; |
| | | private final File nextFile; |
| | | |
| | | private FixedNamingPolicy(File initialFile, File nextFile) |
| | | { |
| | | this.initialFile = initialFile; |
| | | this.nextFile = nextFile; |
| | | } |
| | | |
| | | @Override |
| | | public File getInitialName() |
| | | { |
| | | return initialFile; |
| | | } |
| | | |
| | | @Override |
| | | public File getNextName() |
| | | { |
| | | return nextFile; |
| | | } |
| | | |
| | | @Override |
| | | public FilenameFilter getFilenameFilter() |
| | | { |
| | | return new FilenameFilter() |
| | | { |
| | | @Override |
| | | public boolean accept(File dir, String name) |
| | | { |
| | | return name.equals(nextFile.getName()); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | @Override |
| | | public File[] listFiles() |
| | | { |
| | | return new File[0]; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * A file which can run a hook from within constructWriter(): after a failed rotation the writer |
| | | * is re-opened, and {@code FilePermission.setPermissions} checks {@code exists()} at the exact |
| | | * point where the permission warnings are logged. |
| | | */ |
| | | private static final class HookedFile extends File |
| | | { |
| | | private static final long serialVersionUID = 1L; |
| | | private transient Runnable onExists; |
| | | |
| | | private HookedFile(File parent, String child) |
| | | { |
| | | super(parent, child); |
| | | } |
| | | |
| | | @Override |
| | | public boolean exists() |
| | | { |
| | | if (onExists != null) |
| | | { |
| | | onExists.run(); |
| | | } |
| | | return super.exists(); |
| | | } |
| | | } |
| | | |
| | | private File tempDir; |
| | | private File logFile; |
| | | private File rotatedFile; |
| | | private MultifileTextWriter writer; |
| | | |
| | | @BeforeClass |
| | | public void startServer() throws Exception |
| | | { |
| | | // The writer registers itself as a shutdown listener, which requires a bootstrapped server. |
| | | TestCaseUtils.startServer(); |
| | | } |
| | | |
| | | @BeforeMethod |
| | | public void setUp() throws Exception |
| | | { |
| | | tempDir = Files.createTempDirectory("MultifileTextWriterTestCase").toFile(); |
| | | logFile = new File(tempDir, "test.log"); |
| | | rotatedFile = new File(tempDir, "test.log.rotated"); |
| | | } |
| | | |
| | | @AfterMethod |
| | | public void tearDown() throws Exception |
| | | { |
| | | if (writer != null) |
| | | { |
| | | writer.shutdown(); |
| | | writer = null; |
| | | } |
| | | deleteRecursively(tempDir); |
| | | } |
| | | |
| | | /** |
| | | * Makes the rename attempted by the rotation fail on every platform: renaming a file onto a |
| | | * non-empty directory is refused both on POSIX systems and on Windows. |
| | | */ |
| | | private void breakRotation() throws Exception |
| | | { |
| | | assertTrue(rotatedFile.mkdir()); |
| | | assertTrue(new File(rotatedFile, "blocker").createNewFile()); |
| | | } |
| | | |
| | | private MultifileTextWriter newWriter() throws Exception |
| | | { |
| | | MultifileTextWriter newWriter = new MultifileTextWriter("Multifile Text Writer for " + PUBLISHER_DN, |
| | | Long.MAX_VALUE, new FixedNamingPolicy(logFile, rotatedFile), FilePermission.decodeUNIXMode("600"), |
| | | new LogPublisherErrorHandler(PUBLISHER_DN), "UTF-8", true, true, 0); |
| | | |
| | | SizeLimitLogRotationPolicyCfg config = mock(SizeLimitLogRotationPolicyCfg.class); |
| | | when(config.getFileSizeLimit()).thenReturn(SIZE_LIMIT); |
| | | SizeBasedRotationPolicy policy = new SizeBasedRotationPolicy(); |
| | | policy.initializeLogRotationPolicy(config); |
| | | newWriter.addRotationPolicy(policy); |
| | | |
| | | // The rotater thread is deliberately not started: the rotations under test are the ones |
| | | // triggered inline by writeRecord(). |
| | | return newWriter; |
| | | } |
| | | |
| | | private List<String> linesOf(File file) throws Exception |
| | | { |
| | | return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); |
| | | } |
| | | |
| | | private static void deleteRecursively(File file) |
| | | { |
| | | if (file.isDirectory()) |
| | | { |
| | | for (File child : file.listFiles()) |
| | | { |
| | | deleteRecursively(child); |
| | | } |
| | | } |
| | | file.delete(); |
| | | } |
| | | |
| | | @Test |
| | | public void testSuccessfulRotationRenamesTheFile() throws Exception |
| | | { |
| | | writer = newWriter(); |
| | | |
| | | writer.writeRecord(RECORD_A); |
| | | writer.writeRecord(RECORD_B); |
| | | writer.flush(); |
| | | |
| | | assertEquals(writer.getTotalFilesRotated(), 1); |
| | | assertEquals(linesOf(rotatedFile), List.of(RECORD_A)); |
| | | assertEquals(linesOf(logFile), List.of(RECORD_B)); |
| | | } |
| | | |
| | | @Test |
| | | public void testFailedRotationAppendsInsteadOfTruncating() throws Exception |
| | | { |
| | | breakRotation(); |
| | | writer = newWriter(); |
| | | |
| | | writer.writeRecord(RECORD_A); |
| | | assertEquals(writer.getTotalFilesRotated(), 0); |
| | | |
| | | // Overflows the size limit, so a rotation is attempted and fails. |
| | | writer.writeRecord(RECORD_B); |
| | | writer.flush(); |
| | | |
| | | assertEquals(writer.getTotalFilesRotated(), 0, "a failed rotation must not be counted"); |
| | | assertTrue(rotatedFile.isDirectory(), "the rotation target must have been left alone"); |
| | | assertEquals(linesOf(logFile), List.of(RECORD_A, RECORD_B), |
| | | "the log file must have been appended to rather than truncated"); |
| | | } |
| | | |
| | | /** |
| | | * A failed rotation of the file backing the error log used to come back into writeRecord() on the |
| | | * same thread, from a writer which had just been closed and was still over the size limit, and to |
| | | * recurse until the stack blew up. |
| | | */ |
| | | @Test |
| | | @SuppressWarnings({ "rawtypes", "unchecked" }) |
| | | public void testFailedRotationOfTheErrorLogDoesNotRecurse() throws Exception |
| | | { |
| | | breakRotation(); |
| | | writer = newWriter(); |
| | | |
| | | MultifileTextWriter publishing = writer; |
| | | ErrorLogPublisher publisher = TextErrorLogPublisher.getServerStartupTextErrorPublisher(publishing); |
| | | ErrorLogger.getInstance().addLogPublisher(publisher); |
| | | try |
| | | { |
| | | publishing.writeRecord(RECORD_A); |
| | | // Overflows the size limit: the rotation fails and reports the failure through the very |
| | | // logger this writer is backing. |
| | | publishing.writeRecord(RECORD_B); |
| | | publishing.flush(); |
| | | } |
| | | finally |
| | | { |
| | | // Removing the publisher closes it, which already shuts this writer down. |
| | | ErrorLogger.getInstance().removeLogPublisher(publisher); |
| | | writer = null; |
| | | } |
| | | |
| | | assertEquals(publishing.getTotalFilesRotated(), 0); |
| | | |
| | | List<String> lines = linesOf(logFile); |
| | | assertEquals(lines.get(0), RECORD_A, "the log file must have been appended to"); |
| | | assertTrue(lines.contains(RECORD_B), "the record which triggered the rotation must not be lost"); |
| | | assertTrue(lines.stream().anyMatch(line -> line.contains("rotating log file")), |
| | | "the rotation failure must be reported in the log file, but it contained: " + lines); |
| | | } |
| | | |
| | | /** |
| | | * constructWriter() logs permission warnings of its own, between re-seeding the stream with the |
| | | * over-limit file length and the point where the failed rotation used to set its latch. When the |
| | | * writer backs the error log, such a warning used to re-enter writeRecord() and to recurse until |
| | | * the stack blew up. The hook stands in for the permission warning, firing at the same point of |
| | | * the re-open. |
| | | */ |
| | | @Test |
| | | @SuppressWarnings({ "rawtypes", "unchecked" }) |
| | | public void testWarningDuringReopenAfterFailedRotationDoesNotRecurse() throws Exception |
| | | { |
| | | breakRotation(); |
| | | HookedFile hookedLogFile = new HookedFile(tempDir, logFile.getName()); |
| | | logFile = hookedLogFile; |
| | | writer = newWriter(); |
| | | |
| | | MultifileTextWriter publishing = writer; |
| | | ErrorLogPublisher publisher = TextErrorLogPublisher.getServerStartupTextErrorPublisher(publishing); |
| | | ErrorLogger.getInstance().addLogPublisher(publisher); |
| | | try |
| | | { |
| | | publishing.writeRecord(RECORD_A); |
| | | hookedLogFile.onExists = () -> logger.warn(LocalizableMessage.raw(STAND_IN_WARNING)); |
| | | // Overflows the size limit: the rotation fails, and while the writer is re-opened the hook |
| | | // logs through the very logger this writer is backing, like the permission warnings do. |
| | | publishing.writeRecord(RECORD_B); |
| | | publishing.flush(); |
| | | } |
| | | finally |
| | | { |
| | | // Removing the publisher closes it, which already shuts this writer down. |
| | | ErrorLogger.getInstance().removeLogPublisher(publisher); |
| | | writer = null; |
| | | } |
| | | |
| | | assertEquals(publishing.getTotalFilesRotated(), 0); |
| | | |
| | | List<String> lines = linesOf(logFile); |
| | | assertEquals(lines.get(0), RECORD_A, "the log file must have been appended to"); |
| | | assertTrue(lines.stream().anyMatch(line -> line.contains(STAND_IN_WARNING)), |
| | | "the warning logged during the re-open must not be lost, but the log file contained: " + lines); |
| | | assertTrue(lines.contains(RECORD_B), "the record which triggered the rotation must not be lost"); |
| | | assertEquals(lines.stream().filter(line -> line.contains("rotating log file")).count(), 1L, |
| | | "the rotation failure must be reported exactly once, but the log file contained: " + lines); |
| | | } |
| | | } |