    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     */
    017    package org.apache.logging.log4j.core.appender.rolling.action;
    018
    019    import java.io.File;
    020    import java.io.FileInputStream;
    021    import java.io.FileOutputStream;
    022    import java.io.IOException;
    023    import java.nio.channels.FileChannel;
    024
    025    /**
    026     * File rename action.
    027     */
    028    public class FileRenameAction extends AbstractAction {
    029
    030        /**
    031         * Source.
    032         */
    033        private final File source;
    034
    035        /**
    036         * Destination.
    037         */
    038        private final File destination;
    039
    040        /**
    041         * If true, rename empty files, otherwise delete empty files.
    042         */
    043        private final boolean renameEmptyFiles;
    044
    045        /**
    046         * Creates an FileRenameAction.
    047         *
    048         * @param src              current file name.
    049         * @param dst              new file name.
    050         * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files.
    051         */
    052        public FileRenameAction(final File src, final File dst, final boolean renameEmptyFiles) {
    053            source = src;
    054            destination = dst;
    055            this.renameEmptyFiles = renameEmptyFiles;
    056        }
    057
    058        /**
    059         * Rename file.
    060         *
    061         * @return true if successfully renamed.
    062         */
    063        @Override
    064        public boolean execute() {
    065            return execute(source, destination, renameEmptyFiles);
    066        }
    067
    068        /**
    069         * Rename file.
    070         *
    071         * @param source           current file name.
    072         * @param destination      new file name.
    073         * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files.
    074         * @return true if successfully renamed.
    075         */
    076        public static boolean execute(final File source, final File destination, final boolean renameEmptyFiles) {
    077            if (renameEmptyFiles || source.length() > 0) {
    078                final File parent = destination.getParentFile();
    079                if (parent != null && !parent.exists()) {
    080                    // LOG4J2-679: ignore mkdirs() result: in multithreaded scenarios,
    081                    // if one thread succeeds the other thread returns false
    082                    // even though directories have been created. Check if dir exists instead.
    083                    parent.mkdirs();
    084                    if (!parent.exists()) {
    085                        LOGGER.error("Unable to create directory {}", parent.getAbsolutePath());
    086                        return false;
    087                    }
    088                }
    089                try {
    090                    if (!source.renameTo(destination)) {
    091                        try {
    092                            copyFile(source, destination);
    093                            return source.delete();
    094                        } catch (final IOException iex) {
    095                            LOGGER.error("Unable to rename file {} to {} - {}", source.getAbsolutePath(),
    096                                destination.getAbsolutePath(), iex.getMessage());
    097                        }
    098                    }
    099                    return true;
    100                } catch (final Exception ex) {
    101                    try {
    102                        copyFile(source, destination);
    103                        return source.delete();
    104                    } catch (final IOException iex) {
    105                        LOGGER.error("Unable to rename file {} to {} - {}", source.getAbsolutePath(),
    106                            destination.getAbsolutePath(), iex.getMessage());
    107                    }
    108                }
    109            } else {
    110                try {
    111                    source.delete();
    112                } catch (final Exception ex) {
    113                    LOGGER.error("Unable to delete empty file " + source.getAbsolutePath());
    114                }
    115            }
    116
    117            return false;
    118        }
    119
    120        private static void copyFile(final File source, final File destination) throws IOException {
    121            if (!destination.exists()) {
    122                destination.createNewFile();
    123            }
    124
    125            FileChannel srcChannel = null;
    126            FileChannel destChannel = null;
    127            FileInputStream srcStream = null;
    128            FileOutputStream destStream = null;
    129            try {
    130                srcStream = new FileInputStream(source);
    131                destStream = new FileOutputStream(destination);
    132                srcChannel = srcStream.getChannel();
    133                destChannel = destStream.getChannel();
    134                destChannel.transferFrom(srcChannel, 0, srcChannel.size());
    135            } finally {
    136                if (srcChannel != null) {
    137                    srcChannel.close();
    138                }
    139                if (srcStream != null) {
    140                    srcStream.close();
    141                }
    142                if (destChannel != null) {
    143                    destChannel.close();
    144                }
    145                if (destStream != null) {
    146                    destStream.close();
    147                }
    148            }
    149        }
    150
    151        @Override
    152        public String toString() {
    153            return FileRenameAction.class.getSimpleName() + '[' + source + " to " + destination //
    154                    + ", renameEmptyFiles=" + renameEmptyFiles + ']';
    155        }
    156    }