truncate: always set mode when opening file to avoid fortify errors
[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 source tree.
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 //usage:#define chmod_trivial_usage
18 //usage:       "[-R"IF_DESKTOP("cvf")"] MODE[,MODE]... FILE..."
19 //usage:#define chmod_full_usage "\n\n"
20 //usage:       "Each MODE is one or more of the letters ugoa, one of the\n"
21 //usage:       "symbols +-= and one or more of the letters rwxst\n"
22 //usage:     "\n        -R      Recurse"
23 //usage:        IF_DESKTOP(
24 //usage:     "\n        -c      List changed files"
25 //usage:     "\n        -v      List all files"
26 //usage:     "\n        -f      Hide errors"
27 //usage:        )
28 //usage:
29 //usage:#define chmod_example_usage
30 //usage:       "$ ls -l /tmp/foo\n"
31 //usage:       "-rw-rw-r--    1 root     root            0 Apr 12 18:25 /tmp/foo\n"
32 //usage:       "$ chmod u+x /tmp/foo\n"
33 //usage:       "$ ls -l /tmp/foo\n"
34 //usage:       "-rwxrw-r--    1 root     root            0 Apr 12 18:25 /tmp/foo*\n"
35 //usage:       "$ chmod 444 /tmp/foo\n"
36 //usage:       "$ ls -l /tmp/foo\n"
37 //usage:       "-r--r--r--    1 root     root            0 Apr 12 18:25 /tmp/foo\n"
38
39 #include "libbb.h"
40
41 /* This is a NOEXEC applet. Be very careful! */
42
43
44 #define OPT_RECURSE (option_mask32 & 1)
45 #define OPT_VERBOSE (IF_DESKTOP(option_mask32 & 2) IF_NOT_DESKTOP(0))
46 #define OPT_CHANGED (IF_DESKTOP(option_mask32 & 4) IF_NOT_DESKTOP(0))
47 #define OPT_QUIET   (IF_DESKTOP(option_mask32 & 8) IF_NOT_DESKTOP(0))
48 #define OPT_STR     "R" IF_DESKTOP("vcf")
49
50 /* coreutils:
51  * chmod never changes the permissions of symbolic links; the chmod
52  * system call cannot change their permissions. This is not a problem
53  * since the permissions of symbolic links are never used.
54  * However, for each symbolic link listed on the command line, chmod changes
55  * the permissions of the pointed-to file. In contrast, chmod ignores
56  * symbolic links encountered during recursive directory traversals.
57  */
58
59 static int FAST_FUNC fileAction(const char *fileName, struct stat *statbuf, void* param, int depth)
60 {
61         mode_t newmode;
62
63         /* match coreutils behavior */
64         if (depth == 0) {
65                 /* statbuf holds lstat result, but we need stat (follow link) */
66                 if (stat(fileName, statbuf))
67                         goto err;
68         } else { /* depth > 0: skip links */
69                 if (S_ISLNK(statbuf->st_mode))
70                         return TRUE;
71         }
72
73         newmode = bb_parse_mode((char *)param, statbuf->st_mode);
74         if (newmode == (mode_t)-1)
75                 bb_error_msg_and_die("invalid mode '%s'", (char *)param);
76
77         if (chmod(fileName, newmode) == 0) {
78                 if (OPT_VERBOSE
79                  || (OPT_CHANGED && statbuf->st_mode != newmode)
80                 ) {
81                         printf("mode of '%s' changed to %04o (%s)\n", fileName,
82                                 newmode & 07777, bb_mode_string(newmode)+1);
83                 }
84                 return TRUE;
85         }
86  err:
87         if (!OPT_QUIET)
88                 bb_simple_perror_msg(fileName);
89         return FALSE;
90 }
91
92 int chmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
93 int chmod_main(int argc UNUSED_PARAM, char **argv)
94 {
95         int retval = EXIT_SUCCESS;
96         char *arg, **argp;
97         char *smode;
98
99         /* Convert first encountered -r into ar, -w into aw etc
100          * so that getopt would not eat it */
101         argp = argv;
102         while ((arg = *++argp)) {
103                 /* Mode spec must be the first arg (sans -R etc) */
104                 /* (protect against mishandling e.g. "chmod 644 -r") */
105                 if (arg[0] != '-') {
106                         arg = NULL;
107                         break;
108                 }
109                 /* An option. Not a -- or valid option? */
110                 if (arg[1] && !strchr("-"OPT_STR, arg[1])) {
111                         arg[0] = 'a';
112                         break;
113                 }
114         }
115
116         /* Parse options */
117         opt_complementary = "-2";
118         getopt32(argv, ("-"OPT_STR) + 1); /* Reuse string */
119         argv += optind;
120
121         /* Restore option-like mode if needed */
122         if (arg) arg[0] = '-';
123
124         /* Ok, ready to do the deed now */
125         smode = *argv++;
126         do {
127                 if (!recursive_action(*argv,
128                         OPT_RECURSE,    // recurse
129                         fileAction,     // file action
130                         fileAction,     // dir action
131                         smode,          // user data
132                         0)              // depth
133                 ) {
134                         retval = EXIT_FAILURE;
135                 }
136         } while (*++argv);
137
138         return retval;
139 }
140
141 /*
142 Security: chmod is too important and too subtle.
143 This is a test script (busybox chmod versus coreutils).
144 Run it in empty directory.
145
146 #!/bin/sh
147 t1="/tmp/busybox chmod"
148 t2="/usr/bin/chmod"
149 create() {
150     rm -rf $1; mkdir $1
151     (
152     cd $1 || exit 1
153     mkdir dir
154     >up
155     >file
156     >dir/file
157     ln -s dir linkdir
158     ln -s file linkfile
159     ln -s ../up dir/up
160     )
161 }
162 tst() {
163     (cd test1; $t1 $1)
164     (cd test2; $t2 $1)
165     (cd test1; ls -lR) >out1
166     (cd test2; ls -lR) >out2
167     echo "chmod $1" >out.diff
168     if ! diff -u out1 out2 >>out.diff; then exit 1; fi
169     rm out.diff
170 }
171 echo "If script produced 'out.diff' file, then at least one testcase failed"
172 create test1; create test2
173 tst "a+w file"
174 tst "a-w dir"
175 tst "a+w linkfile"
176 tst "a-w linkdir"
177 tst "-R a+w file"
178 tst "-R a-w dir"
179 tst "-R a+w linkfile"
180 tst "-R a-w linkdir"
181 tst "a-r,a+x linkfile"
182 */