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