3f9c8b437a0b3862367a8adeda39dab9882d29c1
[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 struct chmod_mode_struct *parse_chmod(const char *modestr,
27                                       struct chmod_mode_struct *append_to)
28 {
29         int state = STATE_1ST_HALF;
30         int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0;
31         struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL,
32                                  *prev_mode = NULL;
33
34         while (state != STATE_ERROR) {
35                 if (!*modestr || *modestr == ',') {
36                         int bits;
37
38                         if (!op) {
39                                 state = STATE_ERROR;
40                                 break;
41                         }
42                         prev_mode = curr_mode;
43                         curr_mode = new_array(struct chmod_mode_struct, 1);
44                         if (prev_mode)
45                                 prev_mode->next = curr_mode;
46                         else
47                                 first_mode = curr_mode;
48                         curr_mode->next = NULL;
49
50                         if (where)
51                                 bits = where * what;
52                         else {
53                                 where = 0111;
54                                 bits = (where * what) & ~orig_umask;
55                         }
56
57                         switch (op) {
58                         case CHMOD_ADD:
59                                 curr_mode->ModeAND = 07777;
60                                 curr_mode->ModeOR  = bits + topoct;
61                                 break;
62                         case CHMOD_SUB:
63                                 curr_mode->ModeAND = 07777 - bits - topoct;
64                                 curr_mode->ModeOR  = 0;
65                                 break;
66                         case CHMOD_EQ:
67                                 curr_mode->ModeAND = 07777 - (where * 7) - (topoct ? topbits : 0);
68                                 curr_mode->ModeOR  = bits + topoct;
69                                 break;
70                         }
71
72                         curr_mode->flags = flags;
73
74                         if (!*modestr)
75                                 break;
76                         modestr++;
77
78                         state = STATE_1ST_HALF;
79                         where = what = op = topoct = topbits = flags = 0;
80                 }
81
82                 if (state != STATE_2ND_HALF) {
83                         switch (*modestr) {
84                         case 'D':
85                                 if (flags & FLAG_FILES_ONLY)
86                                         state = STATE_ERROR;
87                                 flags |= FLAG_DIRS_ONLY;
88                                 break;
89                         case 'F':
90                                 if (flags & FLAG_DIRS_ONLY)
91                                         state = STATE_ERROR;
92                                 flags |= FLAG_FILES_ONLY;
93                                 break;
94                         case 'u':
95                                 where |= 0100;
96                                 topbits |= 04000;
97                                 break;
98                         case 'g':
99                                 where |= 0010;
100                                 topbits |= 02000;
101                                 break;
102                         case 'o':
103                                 where |= 0001;
104                                 break;
105                         case 'a':
106                                 where |= 0111;
107                                 break;
108                         case '+':
109                                 op = CHMOD_ADD;
110                                 state = STATE_2ND_HALF;
111                                 break;
112                         case '-':
113                                 op = CHMOD_SUB;
114                                 state = STATE_2ND_HALF;
115                                 break;
116                         case '=':
117                                 op = CHMOD_EQ;
118                                 state = STATE_2ND_HALF;
119                                 break;
120                         default:
121                                 state = STATE_ERROR;
122                                 break;
123                         }
124                 } else {
125                         switch (*modestr) {
126                         case 'r':
127                                 what |= 4;
128                                 break;
129                         case 'w':
130                                 what |= 2;
131                                 break;
132                         case 'X':
133                                 flags |= FLAG_X_KEEP;
134                                 /* FALL THROUGH */
135                         case 'x':
136                                 what |= 1;
137                                 break;
138                         case 's':
139                                 if (topbits)
140                                         topoct |= topbits;
141                                 else
142                                         topoct = 04000;
143                                 break;
144                         case 't':
145                                 topoct |= 01000;
146                                 break;
147                         default:
148                                 state = STATE_ERROR;
149                                 break;
150                         }
151                 }
152                 modestr++;
153         }
154
155         if (state == STATE_ERROR) {
156                 free_chmod_mode(first_mode);
157                 return NULL;
158         }
159
160         if (append_to) {
161                 for (prev_mode = append_to; prev_mode->next; )
162                         prev_mode = prev_mode->next;
163                 prev_mode->next = first_mode;
164                 return append_to;
165         }
166
167         return first_mode;
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 }