001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender.rolling.action; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.PrintWriter; 022import java.nio.file.AtomicMoveNotSupportedException; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.nio.file.Paths; 026import java.nio.file.StandardCopyOption; 027 028/** 029 * File rename action. 030 */ 031public class FileRenameAction extends AbstractAction { 032 033 /** 034 * Source. 035 */ 036 private final File source; 037 038 /** 039 * Destination. 040 */ 041 private final File destination; 042 043 /** 044 * If true, rename empty files, otherwise delete empty files. 045 */ 046 private final boolean renameEmptyFiles; 047 048 /** 049 * Creates an FileRenameAction. 050 * 051 * @param src current file name. 052 * @param dst new file name. 053 * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files. 054 */ 055 public FileRenameAction(final File src, final File dst, final boolean renameEmptyFiles) { 056 source = src; 057 destination = dst; 058 this.renameEmptyFiles = renameEmptyFiles; 059 } 060 061 /** 062 * Rename file. 063 * 064 * @return true if successfully renamed. 065 */ 066 @Override 067 public boolean execute() { 068 return execute(source, destination, renameEmptyFiles); 069 } 070 071 /** 072 * Gets the destination. 073 * 074 * @return the destination. 075 */ 076 public File getDestination() { 077 return this.destination; 078 } 079 080 /** 081 * Gets the source. 082 * 083 * @return the source. 084 */ 085 public File getSource() { 086 return this.source; 087 } 088 089 /** 090 * Whether to rename empty files. If true, rename empty files, otherwise delete empty files. 091 * 092 * @return Whether to rename empty files. 093 */ 094 public boolean isRenameEmptyFiles() { 095 return renameEmptyFiles; 096 } 097 098 /** 099 * Rename file. 100 * 101 * @param source current file name. 102 * @param destination new file name. 103 * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files. 104 * @return true if successfully renamed. 105 */ 106 public static boolean execute(final File source, final File destination, final boolean renameEmptyFiles) { 107 if (renameEmptyFiles || (source.length() > 0)) { 108 final File parent = destination.getParentFile(); 109 if ((parent != null) && !parent.exists()) { 110 // LOG4J2-679: ignore mkdirs() result: in multithreaded scenarios, 111 // if one thread succeeds the other thread returns false 112 // even though directories have been created. Check if dir exists instead. 113 parent.mkdirs(); 114 if (!parent.exists()) { 115 LOGGER.error("Unable to create directory {}", parent.getAbsolutePath()); 116 return false; 117 } 118 } 119 try { 120 try { 121 return moveFile(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath())); 122 } catch (final IOException exMove) { 123 LOGGER.debug("Unable to move file {} to {}: {} {} - will try to copy and delete", 124 source.getAbsolutePath(), destination.getAbsolutePath(), exMove.getClass().getName(), 125 exMove.getMessage()); 126 boolean result = source.renameTo(destination); 127 if (!result) { 128 try { 129 Files.copy(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()), 130 StandardCopyOption.REPLACE_EXISTING); 131 try { 132 Files.delete(Paths.get(source.getAbsolutePath())); 133 result = true; 134 LOGGER.trace("Renamed file {} to {} using copy and delete", 135 source.getAbsolutePath(), destination.getAbsolutePath()); 136 } catch (final IOException exDelete) { 137 LOGGER.error("Unable to delete file {}: {} {}", source.getAbsolutePath(), 138 exDelete.getClass().getName(), exDelete.getMessage()); 139 try { 140 result = true; 141 new PrintWriter(source.getAbsolutePath()).close(); 142 LOGGER.trace("Renamed file {} to {} with copy and truncation", 143 source.getAbsolutePath(), destination.getAbsolutePath()); 144 } catch (final IOException exOwerwrite) { 145 LOGGER.error("Unable to overwrite file {}: {} {}", 146 source.getAbsolutePath(), exOwerwrite.getClass().getName(), 147 exOwerwrite.getMessage()); 148 } 149 } 150 } catch (final IOException exCopy) { 151 LOGGER.error("Unable to copy file {} to {}: {} {}", source.getAbsolutePath(), 152 destination.getAbsolutePath(), exCopy.getClass().getName(), exCopy.getMessage()); 153 } 154 } else { 155 LOGGER.trace("Renamed file {} to {} with source.renameTo", 156 source.getAbsolutePath(), destination.getAbsolutePath()); 157 } 158 return result; 159 } 160 } catch (final RuntimeException ex) { 161 LOGGER.error("Unable to rename file {} to {}: {} {}", source.getAbsolutePath(), 162 destination.getAbsolutePath(), ex.getClass().getName(), ex.getMessage()); 163 } 164 } else { 165 try { 166 source.delete(); 167 } catch (final Exception exDelete) { 168 LOGGER.error("Unable to delete empty file {}: {} {}", source.getAbsolutePath(), 169 exDelete.getClass().getName(), exDelete.getMessage()); 170 } 171 } 172 173 return false; 174 } 175 176 private static boolean moveFile(Path source, Path target) throws IOException { 177 try { 178 Files.move(source, target, 179 StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); 180 LOGGER.trace("Renamed file {} to {} with Files.move", source.toFile().getAbsolutePath(), 181 target.toFile().getAbsolutePath()); 182 return true; 183 } catch (final AtomicMoveNotSupportedException ex) { 184 Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); 185 LOGGER.trace("Renamed file {} to {} with Files.move", source.toFile().getAbsolutePath(), 186 target.toFile().getAbsolutePath()); 187 return true; 188 } 189 } 190 191 @Override 192 public String toString() { 193 return FileRenameAction.class.getSimpleName() + '[' + source + " to " + destination 194 + ", renameEmptyFiles=" + renameEmptyFiles + ']'; 195 } 196 197}