| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs; |
| |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.attribute.DosFileAttributeView; |
| import java.nio.file.attribute.PosixFilePermission; |
| import java.util.Arrays; |
| import java.util.EnumSet; |
| import java.util.Set; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.types.Resource; |
| import org.apache.tools.ant.types.ResourceCollection; |
| import org.apache.tools.ant.types.resources.Resources; |
| import org.apache.tools.ant.util.PermissionUtils; |
| import org.apache.tools.ant.util.StringUtils; |
| |
| /** |
| * Sets {@link PosixFilePermission}s for resources. |
| * |
| * <p>This task provides a subset of {@link Chmod}'s and {@link |
| * org.apache.tools.ant.taskdefs.optional.windows.Attrib}'s abilities |
| * in less platform dependent way.</p> |
| * |
| * <p>It requires a file system that supports PosixFilePermissions for |
| * its full potential. It can optionally fall back to |
| * DosFilePermissions (only changing the readonly state) on file |
| * systems that don't support POSIX permissions. See {@link |
| * SetPermissions.NonPosixMode}</p> |
| * |
| * @since Ant 1.10.0 |
| */ |
| public class SetPermissions extends Task { |
| private final Set<PosixFilePermission> permissions = |
| EnumSet.noneOf(PosixFilePermission.class); |
| private Resources resources = null; |
| private boolean failonerror = true; |
| private NonPosixMode nonPosixMode = NonPosixMode.fail; |
| |
| /** |
| * Options for dealing with file systems that don't support POSIX |
| * permissions. |
| */ |
| public enum NonPosixMode { |
| /** Fail the build. */ |
| fail, |
| /** Log an error and go on. */ |
| pass, |
| /** |
| * Try DosFilePermissions - setting the read-only flag - and |
| * fail the build if that fails as well. |
| */ |
| tryDosOrFail, |
| /** |
| * Try DosFilePermissions - setting the read-only flag - and |
| * log an error and go on if that fails as well. |
| */ |
| tryDosOrPass; |
| } |
| |
| /** |
| * Adds permissions as a comma separated list. |
| * @param perms comma separated list of names of {@link PosixFilePermission}s. |
| */ |
| public void setPermissions(String perms) { |
| if (perms != null) { |
| Arrays.stream(perms.split(",")) |
| .map(String::trim) |
| .filter(s -> !s.isEmpty()) |
| .map(s -> Enum.valueOf(PosixFilePermission.class, s)) |
| .forEach(permissions::add); |
| } |
| } |
| |
| /** |
| * A 3 digit octal string, specify the user, group and |
| * other modes in the standard Unix fashion; |
| * @param octalString a <code>String</code> value |
| */ |
| public void setMode(String octalString) { |
| int mode = Integer.parseInt(octalString, 8); |
| permissions.addAll(PermissionUtils.permissionsFromMode(mode)); |
| } |
| |
| /** |
| * Set whether to fail when errors are encountered. If false, note errors |
| * to the output but keep going. Default is true. |
| * <p>Only applies to IO and SecurityExceptions, see {@link |
| * #setNonPosixMode} for ways to deal with file-systems that don't |
| * support PosixPermissions.</p> |
| * @param failonerror true or false. |
| */ |
| public void setFailOnError(final boolean failonerror) { |
| this.failonerror = failonerror; |
| } |
| |
| /** |
| * Set what to do if changing the permissions of a file is not |
| * possible because the file-system doesn't support POSIX file |
| * permissions. |
| * <p>The default is {@link NonPosixMode#fail}.</p> |
| * @param m what to do if changing the permissions of a file is not possible |
| */ |
| public void setNonPosixMode(NonPosixMode m) { |
| this.nonPosixMode = m; |
| } |
| |
| /** |
| * Adds a collection of resources to set permissions on. |
| * @param rc a resource collection |
| */ |
| public void add(ResourceCollection rc) { |
| if (resources == null) { |
| resources = new Resources(); |
| } |
| resources.add(rc); |
| } |
| |
| public void execute() { |
| if (resources == null) { |
| throw new BuildException("At least one resource-collection is required"); |
| } |
| Resource currentResource = null; |
| try { |
| for (Resource r : resources) { |
| currentResource = r; |
| try { |
| PermissionUtils.setPermissions(r, permissions, this::posixPermissionsNotSupported); |
| } catch (IOException ioe) { |
| maybeThrowException(ioe, "Failed to set permissions on '%s' due to %s", r, ioe.getMessage()); |
| } |
| } |
| } catch (ClassCastException cce) { |
| maybeThrowException(null, |
| "some specified permissions are not of type PosixFilePermission: %s", |
| StringUtils.join(permissions, ", ")); |
| } catch (SecurityException se) { |
| maybeThrowException(null, |
| "the SecurityManager denies role accessUserInformation or write access for SecurityManager.checkWrite for resource '%s'", |
| currentResource); |
| } catch (BuildException be) { |
| // maybe thrown by callback method this::posixPermissionsNotSupported. |
| maybeThrowException(be, be.getMessage()); |
| } |
| } |
| |
| private void maybeThrowException(Exception exc, String msgFormat, Object... msgArgs) { |
| String msg = String.format(msgFormat, msgArgs); |
| if (failonerror) { |
| if (exc instanceof BuildException) { |
| throw (BuildException)exc; |
| } else { |
| throw new BuildException(msg, exc); |
| } |
| } else { |
| log("Warning: " + msg, Project.MSG_ERR); |
| } |
| } |
| |
| private void posixPermissionsNotSupported(Path p) { |
| String msg = String.format("the associated path '%s' does" |
| + " not support the PosixFileAttributeView", p); |
| switch (nonPosixMode) { |
| case fail: |
| throw new BuildException(msg); |
| case pass: |
| log("Warning: " + msg, Project.MSG_ERR); |
| break; |
| case tryDosOrFail: |
| tryDos(p, true); |
| break; |
| case tryDosOrPass: |
| tryDos(p, false); |
| break; |
| } |
| } |
| |
| private void tryDos(Path p, boolean failIfDosIsNotSupported) { |
| log("Falling back to DosFileAttributeView"); |
| boolean readOnly = !isWritable(); |
| DosFileAttributeView view = Files.getFileAttributeView(p, DosFileAttributeView.class); |
| if (view != null) { |
| try { |
| view.setReadOnly(readOnly); |
| } catch (IOException ioe) { |
| maybeThrowException(ioe, "Failed to set permissions on '%s' due to %s", |
| p, ioe.getMessage()); |
| } catch (SecurityException uoe) { |
| maybeThrowException(null, "the SecurityManager denies role " |
| + "accessUserInformation or write access for " |
| + "SecurityManager.checkWrite for resource '%s'", |
| p); |
| } |
| } else { |
| String msg = String.format("the associated path '%s' does" |
| + " not support the DosFileAttributeView", p); |
| if (failIfDosIsNotSupported) { |
| throw new BuildException(msg); |
| } else { |
| log("Warning: " + msg, Project.MSG_ERR); |
| } |
| } |
| } |
| |
| private boolean isWritable() { |
| return permissions.contains(PosixFilePermission.OWNER_WRITE) |
| || permissions.contains(PosixFilePermission.GROUP_WRITE) |
| || permissions.contains(PosixFilePermission.OTHERS_WRITE); |
| } |
| } |