- Updated the address for the FSF in the opening comment.
[rsync.git] / chmod.c
1 /*
2  * Implement the core of the --chmod option.
3  *
4  * Copyright (C) 2002 Scott Howard
5  * Copyright (C) 2005, 2006 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 2 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
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "rsync.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
40 #define STATE_ERROR 0
41 #define STATE_1ST_HALF 1
42 #define STATE_2ND_HALF 2
43
44 /* Parse a chmod-style argument, and break it down into one or more AND/OR
45  * pairs in a linked list.  We return a pointer to new items on succcess
46  * (appending the items to the specified list), or NULL on error. */
47 struct chmod_mode_struct *parse_chmod(const char *modestr,
48                                       struct chmod_mode_struct **root_mode_ptr)
49 {
50         int state = STATE_1ST_HALF;
51         int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0;
52         struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL,
53                                  *prev_mode = NULL;
54
55         while (state != STATE_ERROR) {
56                 if (!*modestr || *modestr == ',') {
57                         int bits;
58
59                         if (!op) {
60                                 state = STATE_ERROR;
61                                 break;
62                         }
63                         prev_mode = curr_mode;
64                         curr_mode = new_array(struct chmod_mode_struct, 1);
65                         if (prev_mode)
66                                 prev_mode->next = curr_mode;
67                         else
68                                 first_mode = curr_mode;
69                         curr_mode->next = NULL;
70
71                         if (where)
72                                 bits = where * what;
73                         else {
74                                 where = 0111;
75                                 bits = (where * what) & ~orig_umask;
76                         }
77
78                         switch (op) {
79                         case CHMOD_ADD:
80                                 curr_mode->ModeAND = CHMOD_BITS;
81                                 curr_mode->ModeOR  = bits + topoct;
82                                 break;
83                         case CHMOD_SUB:
84                                 curr_mode->ModeAND = CHMOD_BITS - bits - topoct;
85                                 curr_mode->ModeOR  = 0;
86                                 break;
87                         case CHMOD_EQ:
88                                 curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0);
89                                 curr_mode->ModeOR  = bits + topoct;
90                                 break;
91                         }
92
93                         curr_mode->flags = flags;
94
95                         if (!*modestr)
96                                 break;
97                         modestr++;
98
99                         state = STATE_1ST_HALF;
100                         where = what = op = topoct = topbits = flags = 0;
101                 }
102
103                 if (state != STATE_2ND_HALF) {
104                         switch (*modestr) {
105                         case 'D':
106                                 if (flags & FLAG_FILES_ONLY)
107                                         state = STATE_ERROR;
108                                 flags |= FLAG_DIRS_ONLY;
109                                 break;
110                         case 'F':
111                                 if (flags & FLAG_DIRS_ONLY)
112                                         state = STATE_ERROR;
113                                 flags |= FLAG_FILES_ONLY;
114                                 break;
115                         case 'u':
116                                 where |= 0100;
117                                 topbits |= 04000;
118                                 break;
119                         case 'g':
120                                 where |= 0010;
121                                 topbits |= 02000;
122                                 break;
123                         case 'o':
124                                 where |= 0001;
125                                 break;
126                         case 'a':
127                                 where |= 0111;
128                                 break;
129                         case '+':
130                                 op = CHMOD_ADD;
131                                 state = STATE_2ND_HALF;
132                                 break;
133                         case '-':
134                                 op = CHMOD_SUB;
135                                 state = STATE_2ND_HALF;
136                                 break;
137                         case '=':
138                                 op = CHMOD_EQ;
139                                 state = STATE_2ND_HALF;
140                                 break;
141                         default:
142                                 state = STATE_ERROR;
143                                 break;
144                         }
145                 } else {
146                         switch (*modestr) {
147                         case 'r':
148                                 what |= 4;
149                                 break;
150                         case 'w':
151                                 what |= 2;
152                                 break;
153                         case 'X':
154                                 flags |= FLAG_X_KEEP;
155                                 /* FALL THROUGH */
156                         case 'x':
157                                 what |= 1;
158                                 break;
159                         case 's':
160                                 if (topbits)
161                                         topoct |= topbits;
162                                 else
163                                         topoct = 04000;
164                                 break;
165                         case 't':
166                                 topoct |= 01000;
167                                 break;
168                         default:
169                                 state = STATE_ERROR;
170                                 break;
171                         }
172                 }
173                 modestr++;
174         }
175
176         if (state == STATE_ERROR) {
177                 free_chmod_mode(first_mode);
178                 return NULL;
179         }
180
181         if (!(curr_mode = *root_mode_ptr))
182                 *root_mode_ptr = first_mode;
183         else {
184                 while (curr_mode->next)
185                         curr_mode = curr_mode->next;
186                 curr_mode->next = first_mode;
187         }
188
189         return first_mode;
190 }
191
192
193 /* Takes an existing file permission and a list of AND/OR changes, and
194  * create a new permissions. */
195 int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes)
196 {
197         int IsX = mode & 0111;
198         int NonPerm = mode & ~CHMOD_BITS;
199
200         for ( ; chmod_modes; chmod_modes = chmod_modes->next) {
201                 if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm))
202                         continue;
203                 if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm))
204                         continue;
205                 mode &= chmod_modes->ModeAND;
206                 if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm))
207                         mode |= chmod_modes->ModeOR & ~0111;
208                 else
209                         mode |= chmod_modes->ModeOR;
210         }
211
212         return mode | NonPerm;
213 }
214
215 /* Free the linked list created by parse_chmod. */
216 int free_chmod_mode(struct chmod_mode_struct *chmod_modes)
217 {
218         struct chmod_mode_struct *next;
219
220         while (chmod_modes) {
221                 next = chmod_modes->next;
222                 free(chmod_modes);
223                 chmod_modes = next;
224         }
225         return 0;
226 }