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