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