1 /*
   2  * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 import java.nio.file.*;
  33 import java.nio.file.attribute.*;
  34 import static java.nio.file.attribute.PosixFilePermission.*;
  35 import static java.nio.file.FileVisitResult.*;
  36 import java.io.IOException;
  37 import java.util.*;
  38 
  39 /**
  40  * Sample code that changes the permissions of files in a similar manner to the
  41  * chmod(1) program.
  42  */
  43 
  44 public class Chmod {
  45 
  46     /**
  47      * Compiles a list of one or more <em>symbolic mode expressions</em> that
  48      * may be used to change a set of file permissions. This method is
  49      * intended for use where file permissions are required to be changed in
  50      * a manner similar to the UNIX <i>chmod</i> program.
  51      *
  52      * <p> The {@code exprs} parameter is a comma separated list of expressions
  53      * where each takes the form:
  54      * <blockquote>
  55      * <i>who operator</i> [<i>permissions</i>]
  56      * </blockquote>
  57      * where <i>who</i> is one or more of the characters {@code 'u'}, {@code 'g'},
  58      * {@code 'o'}, or {@code 'a'} meaning the owner (user), group, others, or
  59      * all (owner, group, and others) respectively.
  60      *
  61      * <p> <i>operator</i> is the character {@code '+'}, {@code '-'}, or {@code
  62      * '='} signifying how permissions are to be changed. {@code '+'} means the
  63      * permissions are added, {@code '-'} means the permissions are removed, and
  64      * {@code '='} means the permissions are assigned absolutely.
  65      *
  66      * <p> <i>permissions</i> is a sequence of zero or more of the following:
  67      * {@code 'r'} for read permission, {@code 'w'} for write permission, and
  68      * {@code 'x'} for execute permission. If <i>permissions</i> is omitted
  69      * when assigned absolutely, then the permissions are cleared for
  70      * the owner, group, or others as identified by <i>who</i>. When omitted
  71      * when adding or removing then the expression is ignored.
  72      *
  73      * <p> The following examples demonstrate possible values for the {@code
  74      * exprs} parameter:
  75      *
  76      * <table border="0">
  77      * <tr>
  78      *   <td> {@code u=rw} </td>
  79      *   <td> Sets the owner permissions to be read and write. </td>
  80      * </tr>
  81      * <tr>
  82      *   <td> {@code ug+w} </td>
  83      *   <td> Sets the owner write and group write permissions. </td>
  84      * </tr>
  85      * <tr>
  86      *   <td> {@code u+w,o-rwx} </td>
  87      *   <td> Sets the owner write, and removes the others read, others write
  88      *     and others execute permissions. </td>
  89      * </tr>
  90      * <tr>
  91      *   <td> {@code o=} </td>
  92      *   <td> Sets the others permission to none (others read, others write and
  93      *     others execute permissions are removed if set) </td>
  94      * </tr>
  95      * </table>
  96      *
  97      * @param   exprs
  98      *          List of one or more <em>symbolic mode expressions</em>
  99      *
 100      * @return  A {@code Changer} that may be used to changer a set of
 101      *          file permissions
 102      *
 103      * @throws  IllegalArgumentException
 104      *          If the value of the {@code exprs} parameter is invalid
 105      */
 106     public static Changer compile(String exprs) {
 107         // minimum is who and operator (u= for example)
 108         if (exprs.length() < 2)
 109             throw new IllegalArgumentException("Invalid mode");
 110 
 111         // permissions that the changer will add or remove
 112         final Set<PosixFilePermission> toAdd = new HashSet<PosixFilePermission>();
 113         final Set<PosixFilePermission> toRemove = new HashSet<PosixFilePermission>();
 114 
 115         // iterate over each of expression modes
 116         for (String expr: exprs.split(",")) {
 117             // minimum of who and operator
 118             if (expr.length() < 2)
 119                 throw new IllegalArgumentException("Invalid mode");
 120 
 121             int pos = 0;
 122 
 123             // who
 124             boolean u = false;
 125             boolean g = false;
 126             boolean o = false;
 127             boolean done = false;
 128             for (;;) {
 129                 switch (expr.charAt(pos)) {
 130                     case 'u' : u = true; break;
 131                     case 'g' : g = true; break;
 132                     case 'o' : o = true; break;
 133                     case 'a' : u = true; g = true; o = true; break;
 134                     default : done = true;
 135                 }
 136                 if (done)
 137                     break;
 138                 pos++;
 139             }
 140             if (!u && !g && !o)
 141                 throw new IllegalArgumentException("Invalid mode");
 142 
 143             // get operator and permissions
 144             char op = expr.charAt(pos++);
 145             String mask = (expr.length() == pos) ? "" : expr.substring(pos);
 146 
 147             // operator
 148             boolean add = (op == '+');
 149             boolean remove = (op == '-');
 150             boolean assign = (op == '=');
 151             if (!add && !remove && !assign)
 152                 throw new IllegalArgumentException("Invalid mode");
 153 
 154             // who= means remove all
 155             if (assign && mask.length() == 0) {
 156                 assign = false;
 157                 remove = true;
 158                 mask = "rwx";
 159             }
 160 
 161             // permissions
 162             boolean r = false;
 163             boolean w = false;
 164             boolean x = false;
 165             for (int i=0; i<mask.length(); i++) {
 166                 switch (mask.charAt(i)) {
 167                     case 'r' : r = true; break;
 168                     case 'w' : w = true; break;
 169                     case 'x' : x = true; break;
 170                     default:
 171                         throw new IllegalArgumentException("Invalid mode");
 172                 }
 173             }
 174 
 175             // update permissions set
 176             if (add) {
 177                 if (u) {
 178                     if (r) toAdd.add(OWNER_READ);
 179                     if (w) toAdd.add(OWNER_WRITE);
 180                     if (x) toAdd.add(OWNER_EXECUTE);
 181                 }
 182                 if (g) {
 183                     if (r) toAdd.add(GROUP_READ);
 184                     if (w) toAdd.add(GROUP_WRITE);
 185                     if (x) toAdd.add(GROUP_EXECUTE);
 186                 }
 187                 if (o) {
 188                     if (r) toAdd.add(OTHERS_READ);
 189                     if (w) toAdd.add(OTHERS_WRITE);
 190                     if (x) toAdd.add(OTHERS_EXECUTE);
 191                 }
 192             }
 193             if (remove) {
 194                 if (u) {
 195                     if (r) toRemove.add(OWNER_READ);
 196                     if (w) toRemove.add(OWNER_WRITE);
 197                     if (x) toRemove.add(OWNER_EXECUTE);
 198                 }
 199                 if (g) {
 200                     if (r) toRemove.add(GROUP_READ);
 201                     if (w) toRemove.add(GROUP_WRITE);
 202                     if (x) toRemove.add(GROUP_EXECUTE);
 203                 }
 204                 if (o) {
 205                     if (r) toRemove.add(OTHERS_READ);
 206                     if (w) toRemove.add(OTHERS_WRITE);
 207                     if (x) toRemove.add(OTHERS_EXECUTE);
 208                 }
 209             }
 210             if (assign) {
 211                 if (u) {
 212                     if (r) toAdd.add(OWNER_READ);
 213                       else toRemove.add(OWNER_READ);
 214                     if (w) toAdd.add(OWNER_WRITE);
 215                       else toRemove.add(OWNER_WRITE);
 216                     if (x) toAdd.add(OWNER_EXECUTE);
 217                       else toRemove.add(OWNER_EXECUTE);
 218                 }
 219                 if (g) {
 220                     if (r) toAdd.add(GROUP_READ);
 221                       else toRemove.add(GROUP_READ);
 222                     if (w) toAdd.add(GROUP_WRITE);
 223                       else toRemove.add(GROUP_WRITE);
 224                     if (x) toAdd.add(GROUP_EXECUTE);
 225                       else toRemove.add(GROUP_EXECUTE);
 226                 }
 227                 if (o) {
 228                     if (r) toAdd.add(OTHERS_READ);
 229                       else toRemove.add(OTHERS_READ);
 230                     if (w) toAdd.add(OTHERS_WRITE);
 231                       else toRemove.add(OTHERS_WRITE);
 232                     if (x) toAdd.add(OTHERS_EXECUTE);
 233                       else toRemove.add(OTHERS_EXECUTE);
 234                 }
 235             }
 236         }
 237 
 238         // return changer
 239         return new Changer() {
 240             @Override
 241             public Set<PosixFilePermission> change(Set<PosixFilePermission> perms) {
 242                 perms.addAll(toAdd);
 243                 perms.removeAll(toRemove);
 244                 return perms;
 245             }
 246         };
 247     }
 248 
 249     /**
 250      * A task that <i>changes</i> a set of {@link PosixFilePermission} elements.
 251      */
 252     public interface Changer {
 253         /**
 254          * Applies the changes to the given set of permissions.
 255          *
 256          * @param   perms
 257          *          The set of permissions to change
 258          *
 259          * @return  The {@code perms} parameter
 260          */
 261         Set<PosixFilePermission> change(Set<PosixFilePermission> perms);
 262     }
 263 
 264     /**
 265      * Changes the permissions of the file using the given Changer.
 266      */
 267     static void chmod(Path file, Changer changer) {
 268         try {
 269             Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
 270             Files.setPosixFilePermissions(file, changer.change(perms));
 271         } catch (IOException x) {
 272             System.err.println(x);
 273         }
 274     }
 275 
 276     /**
 277      * Changes the permission of each file and directory visited
 278      */
 279     static class TreeVisitor implements FileVisitor<Path> {
 280         private final Changer changer;
 281 
 282         TreeVisitor(Changer changer) {
 283             this.changer = changer;
 284         }
 285 
 286         @Override
 287         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
 288             chmod(dir, changer);
 289             return CONTINUE;
 290         }
 291 
 292         @Override
 293         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
 294             chmod(file, changer);
 295             return CONTINUE;
 296         }
 297 
 298         @Override
 299         public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
 300             if (exc != null)
 301                 System.err.println("WARNING: " + exc);
 302             return CONTINUE;
 303         }
 304 
 305         @Override
 306         public FileVisitResult visitFileFailed(Path file, IOException exc) {
 307             System.err.println("WARNING: " + exc);
 308             return CONTINUE;
 309         }
 310     }
 311 
 312     static void usage() {
 313         System.err.println("java Chmod [-R] symbolic-mode-list file...");
 314         System.exit(-1);
 315     }
 316 
 317     public static void main(String[] args) throws IOException {
 318         if (args.length < 2)
 319             usage();
 320         int argi = 0;
 321         int maxDepth = 0;
 322         if (args[argi].equals("-R")) {
 323             if (args.length < 3)
 324                 usage();
 325             argi++;
 326             maxDepth = Integer.MAX_VALUE;
 327         }
 328 
 329         // compile the symbolic mode expressions
 330         Changer changer = compile(args[argi++]);
 331         TreeVisitor visitor = new TreeVisitor(changer);
 332 
 333         Set<FileVisitOption> opts = Collections.emptySet();
 334         while (argi < args.length) {
 335             Path file = Paths.get(args[argi]);
 336             Files.walkFileTree(file, opts, maxDepth, visitor);
 337             argi++;
 338         }
 339     }
 340 }