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