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 
41 import java.nio.file.*;
42 import java.nio.file.attribute.*;
43 import static java.nio.file.attribute.PosixFilePermission.*;
44 import static java.nio.file.FileVisitResult.*;
45 import java.io.IOException;
46 import 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 
53 public 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      */
compile(String exprs)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