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