Martin gave his approval to use GPLv3 with this code.
[rsync/rsync.git] / chmod.c
1 /*
2  * Implement the core of the --chmod option.
3  *
4  * Copyright (C) 2002 Scott Howard
5  * Copyright (C) 2005-2007 Wayne Davison
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 3 as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, visit the http://fsf.org website.
18  */
19
20 #include "rsync.h"
21
22 extern mode_t orig_umask;
23
24 #define FLAG_X_KEEP (1<<0)
25 #define FLAG_DIRS_ONLY (1<<1)
26 #define FLAG_FILES_ONLY (1<<2)
27
28 struct chmod_mode_struct {
29         struct chmod_mode_struct *next;
30         int ModeAND, ModeOR;
31         char flags;
32 };
33
34 #define CHMOD_ADD 1
35 #define CHMOD_SUB 2
36 #define CHMOD_EQ  3
37
38 #define STATE_ERROR 0
39 #define STATE_1ST_HALF 1
40 #define STATE_2ND_HALF 2
41
42 /* Parse a chmod-style argument, and break it down into one or more AND/OR
43  * pairs in a linked list.  We return a pointer to new items on succcess
44  * (appending the items to the specified list), or NULL on error. */
45 struct chmod_mode_struct *parse_chmod(const char *modestr,
46                                       struct chmod_mode_struct **root_mode_ptr)
47 {
48         int state = STATE_1ST_HALF;
49         int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0;
50         struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL,
51                                  *prev_mode = NULL;
52
53         while (state != STATE_ERROR) {
54                 if (!*modestr || *modestr == ',') {
55                         int bits;
56
57                         if (!op) {
58                                 state = STATE_ERROR;
59                                 break;
60                         }
61                         prev_mode = curr_mode;
62                         curr_mode = new_array(struct chmod_mode_struct, 1);
63                         if (prev_mode)
64                                 prev_mode->next = curr_mode;
65                         else
66                                 first_mode = curr_mode;
67                         curr_mode->next = NULL;
68
69                         if (where)
70                                 bits = where * what;
71                         else {
72                                 where = 0111;
73                                 bits = (where * what) & ~orig_umask;
74                         }
75
76                         switch (op) {
77                         case CHMOD_ADD:
78                                 curr_mode->ModeAND = CHMOD_BITS;
79                                 curr_mode->ModeOR  = bits + topoct;
80                                 break;
81                         case CHMOD_SUB:
82                                 curr_mode->ModeAND = CHMOD_BITS - bits - topoct;
83                                 curr_mode->ModeOR  = 0;
84                                 break;
85                         case CHMOD_EQ:
86                                 curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0);
87                                 curr_mode->ModeOR  = bits + topoct;
88                                 break;
89                         }
90
91                         curr_mode->flags = flags;
92
93                         if (!*modestr)
94                                 break;
95                         modestr++;
96
97                         state = STATE_1ST_HALF;
98                         where = what = op = topoct = topbits = flags = 0;
99                 }
100
101                 if (state != STATE_2ND_HALF) {
102                         switch (*modestr) {
103                         case 'D':
104                                 if (flags & FLAG_FILES_ONLY)
105                                         state = STATE_ERROR;
106                                 flags |= FLAG_DIRS_ONLY;
107                                 break;
108                         case 'F':
109                                 if (flags & FLAG_DIRS_ONLY)
110                                         state = STATE_ERROR;
111                                 flags |= FLAG_FILES_ONLY;
112                                 break;
113                         case 'u':
114                                 where |= 0100;
115                                 topbits |= 04000;
116                                 break;
117                         case 'g':
118                                 where |= 0010;
119                                 topbits |= 02000;
120                                 break;
121                         case 'o':
122                                 where |= 0001;
123                                 break;
124                         case 'a':
125                                 where |= 0111;
126                                 break;
127                         case '+':
128                                 op = CHMOD_ADD;
129                                 state = STATE_2ND_HALF;
130                                 break;
131                         case '-':
132                                 op = CHMOD_SUB;
133                                 state = STATE_2ND_HALF;
134                                 break;
135                         case '=':
136                                 op = CHMOD_EQ;
137                                 state = STATE_2ND_HALF;
138                                 break;
139                         default:
140                                 state = STATE_ERROR;
141                                 break;
142                         }
143                 } else {
144                         switch (*modestr) {
145                         case 'r':
146                                 what |= 4;
147                                 break;
148                         case 'w':
149                                 what |= 2;
150                                 break;
151                         case 'X':
152                                 flags |= FLAG_X_KEEP;
153                                 /* FALL THROUGH */
154                         case 'x':
155                                 what |= 1;
156                                 break;
157                         case 's':
158                                 if (topbits)
159                                         topoct |= topbits;
160                                 else
161                                         topoct = 04000;
162                                 break;
163                         case 't':
164                                 topoct |= 01000;
165                                 break;
166                         default:
167                                 state = STATE_ERROR;
168                                 break;
169                         }
170                 }
171                 modestr++;
172         }
173
174         if (state == STATE_ERROR) {
175                 free_chmod_mode(first_mode);
176                 return NULL;
177         }
178
179         if (!(curr_mode = *root_mode_ptr))
180                 *root_mode_ptr = first_mode;
181         else {
182                 while (curr_mode->next)
183                         curr_mode = curr_mode->next;
184                 curr_mode->next = first_mode;
185         }
186
187         return first_mode;
188 }
189
190
191 /* Takes an existing file permission and a list of AND/OR changes, and
192  * create a new permissions. */
193 int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes)
194 {
195         int IsX = mode & 0111;
196         int NonPerm = mode & ~CHMOD_BITS;
197
198         for ( ; chmod_modes; chmod_modes = chmod_modes->next) {
199                 if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm))
200                         continue;
201                 if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm))
202                         continue;
203                 mode &= chmod_modes->ModeAND;
204                 if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm))
205                         mode |= chmod_modes->ModeOR & ~0111;
206                 else
207                         mode |= chmod_modes->ModeOR;
208         }
209
210         return mode | NonPerm;
211 }
212
213 /* Free the linked list created by parse_chmod. */
214 int free_chmod_mode(struct chmod_mode_struct *chmod_modes)
215 {
216         struct chmod_mode_struct *next;
217
218         while (chmod_modes) {
219                 next = chmod_modes->next;
220                 free(chmod_modes);
221                 chmod_modes = next;
222         }
223         return 0;
224 }