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.IOException;
020import java.nio.file.FileVisitResult;
021import java.nio.file.FileVisitor;
022import java.nio.file.Path;
023import java.nio.file.SimpleFileVisitor;
024import java.nio.file.attribute.BasicFileAttributes;
025import java.nio.file.attribute.FileOwnerAttributeView;
026import java.nio.file.attribute.PosixFileAttributeView;
027import java.nio.file.attribute.PosixFilePermission;
028import java.nio.file.attribute.PosixFilePermissions;
029import java.util.List;
030import java.util.Set;
031
032import org.apache.logging.log4j.core.Core;
033import org.apache.logging.log4j.core.config.Configuration;
034import org.apache.logging.log4j.core.config.plugins.Plugin;
035import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
036import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
037import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
038import org.apache.logging.log4j.core.config.plugins.PluginElement;
039import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
040import org.apache.logging.log4j.core.lookup.StrSubstitutor;
041import org.apache.logging.log4j.core.util.FileUtils;
042import org.apache.logging.log4j.util.Strings;
043
044/**
045 * File posix attribute view action.
046 * 
047 * Allow to define file permissions, user and group for log files on posix supported OS.
048 */
049@Plugin(name = "PosixViewAttribute", category = Core.CATEGORY_NAME, printObject = true)
050public class PosixViewAttributeAction extends AbstractPathAction {
051    
052    /**
053     * File permissions.
054     */
055    private final Set<PosixFilePermission> filePermissions;
056
057    /**
058     * File owner.
059     */
060    private final String fileOwner;
061
062    /**
063     * File group.
064     */
065    private final String fileGroup;
066
067    private PosixViewAttributeAction(final String basePath, final boolean followSymbolicLinks,
068            final int maxDepth, final PathCondition[] pathConditions, final StrSubstitutor subst,
069            final Set<PosixFilePermission> filePermissions,
070            final String fileOwner, final String fileGroup) {
071        super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst);
072        this.filePermissions = filePermissions;
073        this.fileOwner = fileOwner;
074        this.fileGroup = fileGroup;
075    }
076
077    @PluginBuilderFactory
078    public static Builder newBuilder() {
079        return new Builder();
080    }
081
082    /**
083     * Builder for the posix view attribute action.
084     */
085    public static class Builder implements org.apache.logging.log4j.core.util.Builder<PosixViewAttributeAction> {
086
087        @PluginConfiguration
088        private Configuration configuration;
089        
090        private StrSubstitutor subst;
091
092        @PluginBuilderAttribute
093        @Required(message = "No base path provided")
094        private String basePath;
095
096        @PluginBuilderAttribute
097        private boolean followLinks = false;
098
099        @PluginBuilderAttribute
100        private int maxDepth = 1;
101
102        @PluginElement("PathConditions")
103        private PathCondition[] pathConditions;
104
105        @PluginBuilderAttribute(value = "filePermissions")
106        private String filePermissionsString;
107
108        private Set<PosixFilePermission> filePermissions;
109
110        @PluginBuilderAttribute
111        private String fileOwner;
112
113        @PluginBuilderAttribute
114        private String fileGroup;
115
116        @Override
117        public PosixViewAttributeAction build() {
118            if (Strings.isEmpty(basePath)) {
119                LOGGER.error("Posix file attribute view action not valid because base path is empty.");
120                return null;
121            }
122
123            if (filePermissions == null && Strings.isEmpty(filePermissionsString)
124                        && Strings.isEmpty(fileOwner) && Strings.isEmpty(fileGroup)) {
125                LOGGER.error("Posix file attribute view not valid because nor permissions, user or group defined.");
126                return null;
127            }
128
129            if (!FileUtils.isFilePosixAttributeViewSupported()) {
130                LOGGER.warn("Posix file attribute view defined but it is not supported by this files system.");
131                return null;
132            }
133
134            return new PosixViewAttributeAction(basePath, followLinks, maxDepth, pathConditions,
135                    subst != null ? subst : configuration.getStrSubstitutor(),
136                    filePermissions != null ? filePermissions :
137                                filePermissionsString != null ? PosixFilePermissions.fromString(filePermissionsString) : null,
138                    fileOwner,
139                    fileGroup);
140        }
141
142        /**
143         * Define required configuration, used to retrieve string substituter.
144         *
145         * @param configuration {@link AbstractPathAction#getStrSubstitutor()}
146         * @return This builder
147         */
148        public Builder withConfiguration(final Configuration configuration) {
149            this.configuration = configuration;
150            return this;
151        }
152
153        /**
154         * Define string substituter.
155         *
156         * @param subst {@link AbstractPathAction#getStrSubstitutor()}
157         * @return This builder
158         */
159        public Builder withSubst(final StrSubstitutor subst) {
160            this.subst = subst;
161            return this;
162        }
163
164        /**
165         * Define base path to apply condition before execute posix file attribute action.
166         * @param basePath {@link AbstractPathAction#getBasePath()}
167         * @return This builder
168         */
169        public Builder withBasePath(final String basePath) {
170            this.basePath = basePath;
171            return this;
172        }
173
174        /**
175         * True to allow synonyms links during search of eligible files.
176         * @param followLinks Follow synonyms links
177         * @return This builder
178         */
179        public Builder withFollowLinks(final boolean followLinks) {
180            this.followLinks = followLinks;
181            return this;
182        }
183
184        /**
185         * Define max folder depth to search for eligible files to apply posix attribute view.
186         * @param maxDepth Max search depth 
187         * @return This builder
188         */
189        public Builder withMaxDepth(final int maxDepth) {
190            this.maxDepth = maxDepth;
191            return this;
192        }
193
194        /**
195         * Define path conditions to filter files in {@link PosixViewAttributeAction#getBasePath()}.
196         *
197         * @param pathConditions {@link AbstractPathAction#getPathConditions()}
198         * @return This builder
199         */
200        public Builder withPathConditions(final PathCondition[] pathConditions) {
201            this.pathConditions = pathConditions;
202            return this;
203        }
204
205        /**
206         * Define file permissions in posix format to apply during action execution eligible files.
207         *
208         * Example:
209         * <p>rw-rw-rw
210         * <p>r--r--r--
211         * @param filePermissionsString Permissions to apply
212         * @return This builder
213         */
214        public Builder withFilePermissionsString(final String filePermissionsString) {
215            this.filePermissionsString = filePermissionsString;
216            return this;
217        }
218
219        /**
220         * Define file permissions to apply during action execution eligible files.
221         * @param filePermissions Permissions to apply
222         * @return This builder
223         */
224        public Builder withFilePermissions(final Set<PosixFilePermission> filePermissions) {
225            this.filePermissions = filePermissions;
226            return this;
227        }
228
229        /**
230         * Define file owner to apply during action execution eligible files.
231         * @param fileOwner File owner
232         * @return This builder
233         */
234        public Builder withFileOwner(final String fileOwner) {
235            this.fileOwner = fileOwner;
236            return this;
237        }
238
239        /**
240         * Define file group to apply during action execution eligible files.
241         * @param fileGroup File group
242         * @return This builder
243         */
244        public Builder withFileGroup(final String fileGroup) {
245            this.fileGroup = fileGroup;
246            return this;
247        }
248    }
249
250    @Override
251    protected FileVisitor<Path> createFileVisitor(final Path basePath,
252            final List<PathCondition> conditions) {
253        return new SimpleFileVisitor<Path>() {
254            @Override
255            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
256                for (final PathCondition pathFilter : conditions) {
257                    final Path relative = basePath.relativize(file);
258                    if (!pathFilter.accept(basePath, relative, attrs)) {
259                        LOGGER.trace("Not defining posix attribute base={}, relative={}", basePath, relative);
260                        return FileVisitResult.CONTINUE;
261                    }
262                }
263                FileUtils.defineFilePosixAttributeView(file, filePermissions, fileOwner, fileGroup);
264                return FileVisitResult.CONTINUE;
265            }
266        };
267    }
268
269    /**
270     * Returns posix file permissions if defined and the OS supports posix file attribute,
271     * null otherwise.
272     * @return File posix permissions
273     * @see PosixFileAttributeView
274     */
275    public Set<PosixFilePermission> getFilePermissions() {
276        return filePermissions;
277    }
278    
279    /**
280     * Returns file owner if defined and the OS supports owner file attribute view,
281     * null otherwise. 
282     * @return File owner
283     * @see FileOwnerAttributeView
284     */
285    public String getFileOwner() {
286        return fileOwner;
287    }
288
289    /**
290     * Returns file group if defined and the OS supports posix/group file attribute view,
291     * null otherwise. 
292     * @return File group
293     * @see PosixFileAttributeView
294     */
295    public String getFileGroup() {
296        return fileGroup;
297    }
298
299    @Override
300    public String toString() {
301        return "PosixViewAttributeAction [filePermissions=" + filePermissions + ", fileOwner="
302                + fileOwner + ", fileGroup=" + fileGroup + ", getBasePath()=" + getBasePath()
303                + ", getMaxDepth()=" + getMaxDepth() + ", getPathConditions()="
304                + getPathConditions() + "]";
305    }
306
307}