- fold recurse, depthFirst and dereference params into one param flags.
[oweals/busybox.git] / coreutils / chmod.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini chmod implementation for busybox
4  *
5  * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
6  *
7  * Reworked by (C) 2002 Vladimir Oleynik <dzo@simtreas.ru>
8  *  to correctly parse '-rwxgoa'
9  *
10  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
11  */
12
13 /* BB_AUDIT SUSv3 compliant */
14 /* BB_AUDIT GNU defects - unsupported long options. */
15 /* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */
16
17 #include "busybox.h"
18
19 #define OPT_RECURSE (option_mask32 & 1)
20 #define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 2) SKIP_DESKTOP(0))
21 #define OPT_CHANGED (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0))
22 #define OPT_QUIET   (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0))
23 #define OPT_STR     "R" USE_DESKTOP("vcf")
24
25 /* coreutils:
26  * chmod never changes the permissions of symbolic links; the chmod
27  * system call cannot change their permissions. This is not a problem
28  * since the permissions of symbolic links are never used.
29  * However, for each symbolic link listed on the command line, chmod changes
30  * the permissions of the pointed-to file. In contrast, chmod ignores
31  * symbolic links encountered during recursive directory traversals.
32  */
33
34 static int fileAction(const char *fileName, struct stat *statbuf, void* param, int depth)
35 {
36         mode_t newmode;
37
38         /* match coreutils behavior */
39         if (depth == 0) {
40                 /* statbuf holds lstat result, but we need stat (follow link) */
41                 if (stat(fileName, statbuf))
42                         goto err;
43         } else { /* depth > 0: skip links */
44                 if (S_ISLNK(statbuf->st_mode))
45                         return TRUE;
46         }
47         newmode = statbuf->st_mode;
48
49         if (!bb_parse_mode((char *)param, &newmode))
50                 bb_error_msg_and_die("invalid mode: %s", (char *)param);
51
52         if (chmod(fileName, newmode) == 0) {
53                 if (OPT_VERBOSE
54                  || (OPT_CHANGED && statbuf->st_mode != newmode)
55                 ) {
56                         printf("mode of '%s' changed to %04o (%s)\n", fileName,
57                                 newmode & 07777, bb_mode_string(newmode)+1);
58                 }
59                 return TRUE;
60         }
61  err:
62         if (!OPT_QUIET)
63                 bb_perror_msg("%s", fileName);
64         return FALSE;
65 }
66
67 int chmod_main(int argc, char **argv);
68 int chmod_main(int argc, char **argv)
69 {
70         int retval = EXIT_SUCCESS;
71         char *arg, **argp;
72         char *smode;
73
74         /* Convert first encountered -r into ar, -w into aw etc
75          * so that getopt would not eat it */
76         argp = argv;
77         while ((arg = *++argp)) {
78                 /* Mode spec must be the first arg (sans -R etc) */
79                 /* (protect against mishandling e.g. "chmod 644 -r") */
80                 if (arg[0] != '-') {
81                         arg = NULL;
82                         break;
83                 }
84                 /* An option. Not a -- or valid option? */
85                 if (arg[1] && !strchr("-"OPT_STR, arg[1])) {
86                         arg[0] = 'a';
87                         break;
88                 }
89         }
90
91         /* Parse options */
92         opt_complementary = "-2";
93         getopt32(argc, argv, ("-"OPT_STR) + 1); /* Reuse string */
94         argv += optind;
95
96         /* Restore option-like mode if needed */
97         if (arg) arg[0] = '-';
98
99         /* Ok, ready to do the deed now */
100         smode = *argv++;
101         do {
102                 if (!recursive_action(*argv,
103                         OPT_RECURSE,    // recurse
104                         fileAction,     // file action
105                         fileAction,     // dir action
106                         smode,          // user data
107                         0)              // depth
108                 ) {
109                         retval = EXIT_FAILURE;
110                 }
111         } while (*++argv);
112
113         return retval;
114 }
115
116 /*
117 Security: chmod is too important and too subtle.
118 This is a test script (busybox chmod versus coreutils).
119 Run it in empty directory.
120
121 #!/bin/sh
122 t1="/tmp/busybox chmod"
123 t2="/usr/bin/chmod"
124 create() {
125     rm -rf $1; mkdir $1
126     (
127     cd $1 || exit 1
128     mkdir dir
129     >up
130     >file
131     >dir/file
132     ln -s dir linkdir
133     ln -s file linkfile
134     ln -s ../up dir/up
135     )
136 }
137 tst() {
138     (cd test1; $t1 $1)
139     (cd test2; $t2 $1)
140     (cd test1; ls -lR) >out1
141     (cd test2; ls -lR) >out2
142     echo "chmod $1" >out.diff
143     if ! diff -u out1 out2 >>out.diff; then exit 1; fi
144     rm out.diff
145 }
146 echo "If script produced 'out.diff' file, then at least one testcase failed"
147 create test1; create test2
148 tst "a+w file"
149 tst "a-w dir"
150 tst "a+w linkfile"
151 tst "a-w linkdir"
152 tst "-R a+w file"
153 tst "-R a-w dir"
154 tst "-R a+w linkfile"
155 tst "-R a-w linkdir"
156 tst "a-r,a+x linkfile"
157 */