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