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;
018
019import java.io.File;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.Serializable;
024import java.nio.ByteBuffer;
025import java.nio.channels.FileChannel;
026import java.nio.channels.FileLock;
027import java.nio.file.FileSystems;
028import java.nio.file.Files;
029import java.nio.file.Path;
030import java.nio.file.Paths;
031import java.nio.file.attribute.FileOwnerAttributeView;
032import java.nio.file.attribute.FileTime;
033import java.nio.file.attribute.PosixFileAttributeView;
034import java.nio.file.attribute.PosixFilePermission;
035import java.nio.file.attribute.PosixFilePermissions;
036import java.util.Date;
037import java.util.HashMap;
038import java.util.Map;
039import java.util.Set;
040
041import org.apache.logging.log4j.core.Layout;
042import org.apache.logging.log4j.core.LoggerContext;
043import org.apache.logging.log4j.core.config.Configuration;
044import org.apache.logging.log4j.core.util.Constants;
045import org.apache.logging.log4j.core.util.FileUtils;
046
047
048/**
049 * Manages actual File I/O for File Appenders.
050 */
051public class FileManager extends OutputStreamManager {
052
053    private static final FileManagerFactory FACTORY = new FileManagerFactory();
054
055    private final boolean isAppend;
056    private final boolean createOnDemand;
057    private final boolean isLocking;
058    private final String advertiseURI;
059    private final int bufferSize;
060    private final Set<PosixFilePermission> filePermissions;
061    private final String fileOwner;
062    private final String fileGroup;
063    private final boolean attributeViewEnabled;
064
065    /**
066     * @deprecated
067     */
068    @Deprecated
069    protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
070            final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
071            final boolean writeHeader) {
072        this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
073    }
074
075    /**
076     * @deprecated
077     * @since 2.6
078     */
079    @Deprecated
080    protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
081            final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader,
082            final ByteBuffer buffer) {
083        super(os, fileName, layout, writeHeader, buffer);
084        this.isAppend = append;
085        this.createOnDemand = false;
086        this.isLocking = locking;
087        this.advertiseURI = advertiseURI;
088        this.bufferSize = buffer.capacity();
089        this.filePermissions = null;
090        this.fileOwner = null;
091        this.fileGroup = null;
092        this.attributeViewEnabled = false;
093    }
094
095    /**
096     * @deprecated
097     * @since 2.7
098     */
099    @Deprecated
100    protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
101            final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
102            final boolean writeHeader, final ByteBuffer buffer) {
103        super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
104        this.isAppend = append;
105        this.createOnDemand = createOnDemand;
106        this.isLocking = locking;
107        this.advertiseURI = advertiseURI;
108        this.bufferSize = buffer.capacity();
109        this.filePermissions = null;
110        this.fileOwner = null;
111        this.fileGroup = null;
112        this.attributeViewEnabled = false;
113    }
114
115    /**
116     * @since 2.9
117     */
118    protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
119            final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
120            final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader,
121            final ByteBuffer buffer) {
122        super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
123        this.isAppend = append;
124        this.createOnDemand = createOnDemand;
125        this.isLocking = locking;
126        this.advertiseURI = advertiseURI;
127        this.bufferSize = buffer.capacity();
128
129        final Set<String> views = FileSystems.getDefault().supportedFileAttributeViews();
130        if (views.contains("posix")) {
131            this.filePermissions = filePermissions != null ? PosixFilePermissions.fromString(filePermissions) : null;
132            this.fileGroup = fileGroup;
133        } else {
134            this.filePermissions = null;
135            this.fileGroup = null;
136            if (filePermissions != null) {
137                LOGGER.warn("Posix file attribute permissions defined but it is not supported by this files system.");
138            }
139            if (fileGroup != null) {
140                LOGGER.warn("Posix file attribute group defined but it is not supported by this files system.");
141            }
142        }
143
144        if (views.contains("owner")) {
145            this.fileOwner = fileOwner;
146        } else {
147            this.fileOwner = null;
148            if (fileOwner != null) {
149                LOGGER.warn("Owner file attribute defined but it is not supported by this files system.");
150            }
151        }
152
153        // Supported and defined
154        this.attributeViewEnabled = this.filePermissions != null || this.fileOwner != null || this.fileGroup != null;
155    }
156
157    /**
158     * Returns the FileManager.
159     * @param fileName The name of the file to manage.
160     * @param append true if the file should be appended to, false if it should be overwritten.
161     * @param locking true if the file should be locked while writing, false otherwise.
162     * @param bufferedIo true if the contents should be buffered as they are written.
163     * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.)
164     * @param advertiseUri the URI to use when advertising the file
165     * @param layout The layout
166     * @param bufferSize buffer size for buffered IO
167     * @param filePermissions File permissions
168     * @param fileOwner File owner
169     * @param fileGroup File group
170     * @param configuration The configuration.
171     * @return A FileManager for the File.
172     */
173    public static FileManager getFileManager(final String fileName, final boolean append, boolean locking,
174            final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri,
175            final Layout<? extends Serializable> layout,
176            final int bufferSize, final String filePermissions, final String fileOwner, final String fileGroup,
177            final Configuration configuration) {
178
179        if (locking && bufferedIo) {
180            locking = false;
181        }
182        return narrow(FileManager.class, getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize,
183                createOnDemand, advertiseUri, layout, filePermissions, fileOwner, fileGroup, configuration), FACTORY));
184    }
185
186    @Override
187    protected OutputStream createOutputStream() throws IOException {
188        final String filename = getFileName();
189        LOGGER.debug("Now writing to {} at {}", filename, new Date());
190        final File file = new File(filename);
191        final FileOutputStream fos = new FileOutputStream(file, isAppend);
192        if (file.exists() && file.length() == 0) {
193            try {
194                FileTime now = FileTime.fromMillis(System.currentTimeMillis());
195                Files.setAttribute(file.toPath(), "creationTime", now);
196            } catch (Exception ex) {
197                LOGGER.warn("Unable to set current file tiem for {}", filename);
198            }
199        }
200        defineAttributeView(Paths.get(filename));
201        return fos;
202    }
203
204    protected void defineAttributeView(final Path path) {
205        if (attributeViewEnabled) {
206            try {
207                // FileOutputStream may not create new file on all jvm
208                path.toFile().createNewFile();
209
210                FileUtils.defineFilePosixAttributeView(path, filePermissions, fileOwner, fileGroup);
211            } catch (final Exception e) {
212                LOGGER.error("Could not define attribute view on path \"{}\" got {}", path, e.getMessage(), e);
213            }
214        }
215    }
216
217    @Override
218    protected synchronized void write(final byte[] bytes, final int offset, final int length,
219            final boolean immediateFlush) {
220        if (isLocking) {
221            try {
222                @SuppressWarnings("resource")
223                final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
224                /*
225                 * Lock the whole file. This could be optimized to only lock from the current file position. Note that
226                 * locking may be advisory on some systems and mandatory on others, so locking just from the current
227                 * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
228                 * exception if the region of the file is already locked by another FileChannel in the same JVM.
229                 * Hopefully, that will be avoided since every file should have a single file manager - unless two
230                 * different files strings are configured that somehow map to the same file.
231                 */
232                try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
233                    super.write(bytes, offset, length, immediateFlush);
234                }
235            } catch (final IOException ex) {
236                throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
237            }
238        } else {
239            super.write(bytes, offset, length, immediateFlush);
240        }
241    }
242
243    /**
244     * Overrides {@link OutputStreamManager#writeToDestination(byte[], int, int)} to add support for file locking.
245     *
246     * @param bytes the array containing data
247     * @param offset from where to write
248     * @param length how many bytes to write
249     * @since 2.8
250     */
251    @Override
252    protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
253        if (isLocking) {
254            try {
255                @SuppressWarnings("resource")
256                final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel();
257                /*
258                 * Lock the whole file. This could be optimized to only lock from the current file position. Note that
259                 * locking may be advisory on some systems and mandatory on others, so locking just from the current
260                 * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an
261                 * exception if the region of the file is already locked by another FileChannel in the same JVM.
262                 * Hopefully, that will be avoided since every file should have a single file manager - unless two
263                 * different files strings are configured that somehow map to the same file.
264                 */
265                try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) {
266                    super.writeToDestination(bytes, offset, length);
267                }
268            } catch (final IOException ex) {
269                throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex);
270            }
271        } else {
272            super.writeToDestination(bytes, offset, length);
273        }
274    }
275
276    /**
277     * Returns the name of the File being managed.
278     * @return The name of the File being managed.
279     */
280    public String getFileName() {
281        return getName();
282    }
283    /**
284     * Returns the append status.
285     * @return true if the file will be appended to, false if it is overwritten.
286     */
287    public boolean isAppend() {
288        return isAppend;
289    }
290
291    /**
292     * Returns the lazy-create.
293     * @return true if the file will be lazy-created.
294     */
295    public boolean isCreateOnDemand() {
296        return createOnDemand;
297    }
298
299    /**
300     * Returns the lock status.
301     * @return true if the file will be locked when writing, false otherwise.
302     */
303    public boolean isLocking() {
304        return isLocking;
305    }
306
307    /**
308     * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative
309     * number.
310     * @return the buffer size, or a negative number if the output stream is not buffered
311     */
312    public int getBufferSize() {
313        return bufferSize;
314    }
315
316    /**
317     * Returns posix file permissions if defined and the OS supports posix file attribute,
318     * null otherwise.
319     * @return File posix permissions
320     * @see PosixFileAttributeView
321     */
322    public Set<PosixFilePermission> getFilePermissions() {
323        return filePermissions;
324    }
325
326    /**
327     * Returns file owner if defined and the OS supports owner file attribute view,
328     * null otherwise.
329     * @return File owner
330     * @see FileOwnerAttributeView
331     */
332    public String getFileOwner() {
333        return fileOwner;
334    }
335
336    /**
337     * Returns file group if defined and the OS supports posix/group file attribute view,
338     * null otherwise.
339     * @return File group
340     * @see PosixFileAttributeView
341     */
342    public String getFileGroup() {
343        return fileGroup;
344    }
345
346    /**
347     * Returns true if file attribute view enabled for this file manager.
348     *
349     * @return True if posix or owner supported and defined false otherwise.
350     */
351    public boolean isAttributeViewEnabled() {
352        return attributeViewEnabled;
353    }
354
355    /**
356     * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>.
357     *
358     * @return Map of content format keys supporting FileManager
359     */
360    @Override
361    public Map<String, String> getContentFormat() {
362        final Map<String, String> result = new HashMap<>(super.getContentFormat());
363        result.put("fileURI", advertiseURI);
364        return result;
365    }
366
367    /**
368     * Factory Data.
369     */
370    private static class FactoryData extends ConfigurationFactoryData {
371        private final boolean append;
372        private final boolean locking;
373        private final boolean bufferedIo;
374        private final int bufferSize;
375        private final boolean createOnDemand;
376        private final String advertiseURI;
377        private final Layout<? extends Serializable> layout;
378        private final String filePermissions;
379        private final String fileOwner;
380        private final String fileGroup;
381
382        /**
383         * Constructor.
384         * @param append Append status.
385         * @param locking Locking status.
386         * @param bufferedIo Buffering flag.
387         * @param bufferSize Buffer size.
388         * @param createOnDemand if you want to lazy-create the file (a.k.a. on-demand.)
389         * @param advertiseURI the URI to use when advertising the file
390         * @param layout The layout
391         * @param filePermissions File permissions
392         * @param fileOwner File owner
393         * @param fileGroup File group
394         * @param configuration the configuration
395         */
396        public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize,
397                final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
398                final String filePermissions, final String fileOwner, final String fileGroup,
399                final Configuration configuration) {
400            super(configuration);
401            this.append = append;
402            this.locking = locking;
403            this.bufferedIo = bufferedIo;
404            this.bufferSize = bufferSize;
405            this.createOnDemand = createOnDemand;
406            this.advertiseURI = advertiseURI;
407            this.layout = layout;
408            this.filePermissions = filePermissions;
409            this.fileOwner = fileOwner;
410            this.fileGroup = fileGroup;
411        }
412    }
413
414    /**
415     * Factory to create a FileManager.
416     */
417    private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
418
419        /**
420         * Creates a FileManager.
421         * @param name The name of the File.
422         * @param data The FactoryData
423         * @return The FileManager for the File.
424         */
425        @Override
426        public FileManager createManager(final String name, final FactoryData data) {
427            final File file = new File(name);
428            try {
429                FileUtils.makeParentDirs(file);
430                final boolean writeHeader = !data.append || !file.exists();
431                final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
432                final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]);
433                final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append);
434                final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking,
435                        data.createOnDemand, data.advertiseURI, data.layout,
436                        data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer);
437                if (fos != null && fm.attributeViewEnabled) {
438                    fm.defineAttributeView(file.toPath());
439                }
440                return fm;
441            } catch (final IOException ex) {
442                LOGGER.error("FileManager (" + name + ") " + ex, ex);
443            }
444            return null;
445        }
446    }
447
448}