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