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 }