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/* 33 * This source code is provided to illustrate the usage of a given feature 34 * or technique and has been deliberately simplified. Additional steps 35 * required for a production-quality application, such as security checks, 36 * input validation and proper error handling, might not be present in 37 * this sample code. 38 */ 39 40 41import java.nio.file.*; 42import java.nio.file.attribute.*; 43import static java.nio.file.attribute.PosixFilePermission.*; 44import static java.nio.file.FileVisitResult.*; 45import java.io.IOException; 46import java.util.*; 47 48/** 49 * Sample code that changes the permissions of files in a similar manner to the 50 * chmod(1) program. 51 */ 52 53public class Chmod { 54 55 /** 56 * Compiles a list of one or more <em>symbolic mode expressions</em> that 57 * may be used to change a set of file permissions. This method is 58 * intended for use where file permissions are required to be changed in 59 * a manner similar to the UNIX <i>chmod</i> program. 60 * 61 * <p> The {@code exprs} parameter is a comma separated list of expressions 62 * where each takes the form: 63 * <blockquote> 64 * <i>who operator</i> [<i>permissions</i>] 65 * </blockquote> 66 * where <i>who</i> is one or more of the characters {@code 'u'}, {@code 'g'}, 67 * {@code 'o'}, or {@code 'a'} meaning the owner (user), group, others, or 68 * all (owner, group, and others) respectively. 69 * 70 * <p> <i>operator</i> is the character {@code '+'}, {@code '-'}, or {@code 71 * '='} signifying how permissions are to be changed. {@code '+'} means the 72 * permissions are added, {@code '-'} means the permissions are removed, and 73 * {@code '='} means the permissions are assigned absolutely. 74 * 75 * <p> <i>permissions</i> is a sequence of zero or more of the following: 76 * {@code 'r'} for read permission, {@code 'w'} for write permission, and 77 * {@code 'x'} for execute permission. If <i>permissions</i> is omitted 78 * when assigned absolutely, then the permissions are cleared for 79 * the owner, group, or others as identified by <i>who</i>. When omitted 80 * when adding or removing then the expression is ignored. 81 * 82 * <p> The following examples demonstrate possible values for the {@code 83 * exprs} parameter: 84 * 85 * <table border="0"> 86 * <tr> 87 * <td> {@code u=rw} </td> 88 * <td> Sets the owner permissions to be read and write. </td> 89 * </tr> 90 * <tr> 91 * <td> {@code ug+w} </td> 92 * <td> Sets the owner write and group write permissions. </td> 93 * </tr> 94 * <tr> 95 * <td> {@code u+w,o-rwx} </td> 96 * <td> Sets the owner write, and removes the others read, others write 97 * and others execute permissions. </td> 98 * </tr> 99 * <tr> 100 * <td> {@code o=} </td> 101 * <td> Sets the others permission to none (others read, others write and 102 * others execute permissions are removed if set) </td> 103 * </tr> 104 * </table> 105 * 106 * @param exprs 107 * List of one or more <em>symbolic mode expressions</em> 108 * 109 * @return A {@code Changer} that may be used to changer a set of 110 * file permissions 111 * 112 * @throws IllegalArgumentException 113 * If the value of the {@code exprs} parameter is invalid 114 */ 115 public static Changer compile(String exprs) { 116 // minimum is who and operator (u= for example) 117 if (exprs.length() < 2) 118 throw new IllegalArgumentException("Invalid mode"); 119 120 // permissions that the changer will add or remove 121 final Set<PosixFilePermission> toAdd = new HashSet<PosixFilePermission>(); 122 final Set<PosixFilePermission> toRemove = new HashSet<PosixFilePermission>(); 123 124 // iterate over each of expression modes 125 for (String expr: exprs.split(",")) { 126 // minimum of who and operator 127 if (expr.length() < 2) 128 throw new IllegalArgumentException("Invalid mode"); 129 130 int pos = 0; 131 132 // who 133 boolean u = false; 134 boolean g = false; 135 boolean o = false; 136 boolean done = false; 137 for (;;) { 138 switch (expr.charAt(pos)) { 139 case 'u' : u = true; break; 140 case 'g' : g = true; break; 141 case 'o' : o = true; break; 142 case 'a' : u = true; g = true; o = true; break; 143 default : done = true; 144 } 145 if (done) 146 break; 147 pos++; 148 } 149 if (!u && !g && !o) 150 throw new IllegalArgumentException("Invalid mode"); 151 152 // get operator and permissions 153 char op = expr.charAt(pos++); 154 String mask = (expr.length() == pos) ? "" : expr.substring(pos); 155 156 // operator 157 boolean add = (op == '+'); 158 boolean remove = (op == '-'); 159 boolean assign = (op == '='); 160 if (!add && !remove && !assign) 161 throw new IllegalArgumentException("Invalid mode"); 162 163 // who= means remove all 164 if (assign && mask.length() == 0) { 165 assign = false; 166 remove = true; 167 mask = "rwx"; 168 } 169 170 // permissions 171 boolean r = false; 172 boolean w = false; 173 boolean x = false; 174 for (int i=0; i<mask.length(); i++) { 175 switch (mask.charAt(i)) { 176 case 'r' : r = true; break; 177 case 'w' : w = true; break; 178 case 'x' : x = true; break; 179 default: 180 throw new IllegalArgumentException("Invalid mode"); 181 } 182 } 183 184 // update permissions set 185 if (add) { 186 if (u) { 187 if (r) toAdd.add(OWNER_READ); 188 if (w) toAdd.add(OWNER_WRITE); 189 if (x) toAdd.add(OWNER_EXECUTE); 190 } 191 if (g) { 192 if (r) toAdd.add(GROUP_READ); 193 if (w) toAdd.add(GROUP_WRITE); 194 if (x) toAdd.add(GROUP_EXECUTE); 195 } 196 if (o) { 197 if (r) toAdd.add(OTHERS_READ); 198 if (w) toAdd.add(OTHERS_WRITE); 199 if (x) toAdd.add(OTHERS_EXECUTE); 200 } 201 } 202 if (remove) { 203 if (u) { 204 if (r) toRemove.add(OWNER_READ); 205 if (w) toRemove.add(OWNER_WRITE); 206 if (x) toRemove.add(OWNER_EXECUTE); 207 } 208 if (g) { 209 if (r) toRemove.add(GROUP_READ); 210 if (w) toRemove.add(GROUP_WRITE); 211 if (x) toRemove.add(GROUP_EXECUTE); 212 } 213 if (o) { 214 if (r) toRemove.add(OTHERS_READ); 215 if (w) toRemove.add(OTHERS_WRITE); 216 if (x) toRemove.add(OTHERS_EXECUTE); 217 } 218 } 219 if (assign) { 220 if (u) { 221 if (r) toAdd.add(OWNER_READ); 222 else toRemove.add(OWNER_READ); 223 if (w) toAdd.add(OWNER_WRITE); 224 else toRemove.add(OWNER_WRITE); 225 if (x) toAdd.add(OWNER_EXECUTE); 226 else toRemove.add(OWNER_EXECUTE); 227 } 228 if (g) { 229 if (r) toAdd.add(GROUP_READ); 230 else toRemove.add(GROUP_READ); 231 if (w) toAdd.add(GROUP_WRITE); 232 else toRemove.add(GROUP_WRITE); 233 if (x) toAdd.add(GROUP_EXECUTE); 234 else toRemove.add(GROUP_EXECUTE); 235 } 236 if (o) { 237 if (r) toAdd.add(OTHERS_READ); 238 else toRemove.add(OTHERS_READ); 239 if (w) toAdd.add(OTHERS_WRITE); 240 else toRemove.add(OTHERS_WRITE); 241 if (x) toAdd.add(OTHERS_EXECUTE); 242 else toRemove.add(OTHERS_EXECUTE); 243 } 244 } 245 } 246 247 // return changer 248 return new Changer() { 249 @Override 250 public Set<PosixFilePermission> change(Set<PosixFilePermission> perms) { 251 perms.addAll(toAdd); 252 perms.removeAll(toRemove); 253 return perms; 254 } 255 }; 256 } 257 258 /** 259 * A task that <i>changes</i> a set of {@link PosixFilePermission} elements. 260 */ 261 public interface Changer { 262 /** 263 * Applies the changes to the given set of permissions. 264 * 265 * @param perms 266 * The set of permissions to change 267 * 268 * @return The {@code perms} parameter 269 */ 270 Set<PosixFilePermission> change(Set<PosixFilePermission> perms); 271 } 272 273 /** 274 * Changes the permissions of the file using the given Changer. 275 */ 276 static void chmod(Path file, Changer changer) { 277 try { 278 Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file); 279 Files.setPosixFilePermissions(file, changer.change(perms)); 280 } catch (IOException x) { 281 System.err.println(x); 282 } 283 } 284 285 /** 286 * Changes the permission of each file and directory visited 287 */ 288 static class TreeVisitor implements FileVisitor<Path> { 289 private final Changer changer; 290 291 TreeVisitor(Changer changer) { 292 this.changer = changer; 293 } 294 295 @Override 296 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { 297 chmod(dir, changer); 298 return CONTINUE; 299 } 300 301 @Override 302 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 303 chmod(file, changer); 304 return CONTINUE; 305 } 306 307 @Override 308 public FileVisitResult postVisitDirectory(Path dir, IOException exc) { 309 if (exc != null) 310 System.err.println("WARNING: " + exc); 311 return CONTINUE; 312 } 313 314 @Override 315 public FileVisitResult visitFileFailed(Path file, IOException exc) { 316 System.err.println("WARNING: " + exc); 317 return CONTINUE; 318 } 319 } 320 321 static void usage() { 322 System.err.println("java Chmod [-R] symbolic-mode-list file..."); 323 System.exit(-1); 324 } 325 326 public static void main(String[] args) throws IOException { 327 if (args.length < 2) 328 usage(); 329 int argi = 0; 330 int maxDepth = 0; 331 if (args[argi].equals("-R")) { 332 if (args.length < 3) 333 usage(); 334 argi++; 335 maxDepth = Integer.MAX_VALUE; 336 } 337 338 // compile the symbolic mode expressions 339 Changer changer = compile(args[argi++]); 340 TreeVisitor visitor = new TreeVisitor(changer); 341 342 Set<FileVisitOption> opts = Collections.emptySet(); 343 while (argi < args.length) { 344 Path file = Paths.get(args[argi]); 345 Files.walkFileTree(file, opts, maxDepth, visitor); 346 argi++; 347 } 348 } 349} 350