Converted README to markdown
[rox-filer.git] / ROX-Filer / src / modechange.c
blob94fc5f2826eef3002698f586d6c9213043d7984f
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* modechange.c -- file mode manipulation
22 * This rest of this file was taken from GNU FileUtils, with minor
23 * modifications (eg changing the memory handling to use GLib).
26 /* Written by David MacKenzie <djm@ai.mit.edu> */
28 /* The ASCII mode string is compiled into a linked list of `struct
29 modechange', which can then be applied to each file to be changed.
30 We do this instead of re-parsing the ASCII string for each file
31 because the compiled form requires less computation to use; when
32 changing the mode of many files, this probably results in a
33 performance gain. */
35 #include "config.h"
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <string.h>
41 #include <glib.h>
43 #include "modechange.h"
45 #define isodigit(c) ((c) >= '0' && (c) <= '7')
47 /* Return a positive integer containing the value of the ASCII
48 octal number S. If S is not an octal number or is more than
49 4 digits, return -1. */
51 static int
52 oatoi (const char *s)
54 register int i;
56 if (*s == 0 || strlen (s) > 4)
57 return -1;
58 for (i = 0; isodigit (*s); ++s)
59 i = i * 8 + *s - '0';
60 if (*s)
61 return -1;
62 return i;
65 /* Return a linked list of file mode change operations created from
66 MODE_STRING, an ASCII string that contains either an octal number
67 specifying an absolute mode, or symbolic mode change operations with
68 the form:
69 [ugoa...][[+-=][rwxXstugo...]...][,...]
70 MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-)
71 should not affect bits set in the umask when no users are given.
72 Operators not selected in MASKED_OPS ignore the umask.
74 Return NULL if `mode_string' does not contain a valid
75 representation of file mode change operations. */
77 struct mode_change *
78 mode_compile (const char *mode_string, unsigned int masked_ops)
80 struct mode_change *head; /* First element of the linked list. */
81 struct mode_change *change; /* An element of the linked list. */
82 int i; /* General purpose temporary. */
83 int umask_value; /* The umask value (surprise). */
84 unsigned short affected_bits; /* Which bits in the mode are operated on. */
85 unsigned short affected_masked; /* `affected_bits' modified by umask. */
86 unsigned ops_to_mask; /* Operators to actually use umask on. */
88 i = oatoi (mode_string);
89 if (i >= 0)
91 if (i > 07777)
92 return NULL;
93 head = g_new(struct mode_change, 1);
94 head->next = NULL;
95 head->op = '=';
96 head->flags = 0;
97 head->value = i;
98 head->affected = 07777; /* Affect all permissions. */
99 return head;
102 umask_value = umask (0);
103 umask (umask_value); /* Restore the old value. */
105 head = NULL;
106 change = NULL;
107 --mode_string;
109 /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */
112 affected_bits = 0;
113 ops_to_mask = 0;
114 /* Turn on all the bits in `affected_bits' for each group given. */
115 for (++mode_string;; ++mode_string)
116 switch (*mode_string)
118 case 'u':
119 affected_bits |= 04700;
120 break;
121 case 'g':
122 affected_bits |= 02070;
123 break;
124 case 'o':
125 affected_bits |= 01007;
126 break;
127 case 'a':
128 affected_bits |= 07777;
129 break;
130 default:
131 goto no_more_affected;
134 no_more_affected:
135 /* If none specified, affect all bits, except perhaps those
136 set in the umask. */
137 if (affected_bits == 0)
139 affected_bits = 07777;
140 ops_to_mask = masked_ops;
143 while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-')
145 /* Add the element to the tail of the list, so the operations
146 are performed in the correct order. */
147 if (head == NULL)
149 head = g_new(struct mode_change, 1);
150 change = head;
152 else
154 change->next = g_new(struct mode_change, 1);
155 change = change->next;
158 change->next = NULL;
159 change->op = *mode_string; /* One of "=+-". */
160 affected_masked = affected_bits;
161 if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS
162 : *mode_string == '+' ? MODE_MASK_PLUS
163 : MODE_MASK_MINUS))
164 affected_masked &= ~umask_value;
165 change->affected = affected_masked;
166 change->value = 0;
167 change->flags = 0;
169 /* Set `value' according to the bits set in `affected_masked'. */
170 for (++mode_string;; ++mode_string)
171 switch (*mode_string)
173 case 'r':
174 change->value |= 00444 & affected_masked;
175 break;
176 case 'w':
177 change->value |= 00222 & affected_masked;
178 break;
179 case 'X':
180 change->flags |= MODE_X_IF_ANY_X;
181 /* Fall through. */
182 case 'x':
183 change->value |= 00111 & affected_masked;
184 break;
185 case 's':
186 /* Set the setuid/gid bits if `u' or `g' is selected. */
187 change->value |= 06000 & affected_masked;
188 break;
189 case 't':
190 /* Set the "save text image" bit if `o' is selected. */
191 change->value |= 01000 & affected_masked;
192 break;
193 case 'u':
194 /* Set the affected bits to the value of the `u' bits
195 on the same file. */
196 if (change->value)
197 goto invalid;
198 change->value = 00700;
199 change->flags |= MODE_COPY_EXISTING;
200 break;
201 case 'g':
202 /* Set the affected bits to the value of the `g' bits
203 on the same file. */
204 if (change->value)
205 goto invalid;
206 change->value = 00070;
207 change->flags |= MODE_COPY_EXISTING;
208 break;
209 case 'o':
210 /* Set the affected bits to the value of the `o' bits
211 on the same file. */
212 if (change->value)
213 goto invalid;
214 change->value = 00007;
215 change->flags |= MODE_COPY_EXISTING;
216 break;
217 default:
218 goto no_more_values;
220 no_more_values:;
222 } while (*mode_string == ',');
223 if (*mode_string == 0)
224 return head;
225 invalid:
226 mode_free (head);
227 return NULL;
230 /* Return file mode OLDMODE, adjusted as indicated by the list of change
231 operations CHANGES. If OLDMODE is a directory, the type `X'
232 change affects it even if no execute bits were set in OLDMODE.
233 The returned value has the S_IFMT bits cleared. */
235 unsigned short
236 mode_adjust (unsigned int oldmode, const struct mode_change *changes)
238 unsigned short newmode; /* The adjusted mode and one operand. */
239 unsigned short value; /* The other operand. */
241 newmode = oldmode & 07777;
243 for (; changes; changes = changes->next)
245 if (changes->flags & MODE_COPY_EXISTING)
247 /* Isolate in `value' the bits in `newmode' to copy, given in
248 the mask `changes->value'. */
249 value = newmode & changes->value;
251 if (changes->value & 00700)
252 /* Copy `u' permissions onto `g' and `o'. */
253 value |= (value >> 3) | (value >> 6);
254 else if (changes->value & 00070)
255 /* Copy `g' permissions onto `u' and `o'. */
256 value |= (value << 3) | (value >> 3);
257 else
258 /* Copy `o' permissions onto `u' and `g'. */
259 value |= (value << 3) | (value << 6);
261 /* In order to change only `u', `g', or `o' permissions,
262 or some combination thereof, clear unselected bits.
263 This can not be done in mode_compile because the value
264 to which the `changes->affected' mask is applied depends
265 on the old mode of each file. */
266 value &= changes->affected;
268 else
270 value = changes->value;
271 /* If `X', do not affect the execute bits if the file is not a
272 directory and no execute bits are already set. */
273 if ((changes->flags & MODE_X_IF_ANY_X)
274 && !S_ISDIR (oldmode)
275 && (newmode & 00111) == 0)
276 value &= ~00111; /* Clear the execute bits. */
279 switch (changes->op)
281 case '=':
282 /* Preserve the previous values in `newmode' of bits that are
283 not affected by this change operation. */
284 newmode = (newmode & ~changes->affected) | value;
285 break;
286 case '+':
287 newmode |= value;
288 break;
289 case '-':
290 newmode &= ~value;
291 break;
294 return newmode;
297 /* Free the memory used by the list of file mode change operations
298 CHANGES. */
300 void
301 mode_free (register struct mode_change *changes)
303 register struct mode_change *next;
305 while (changes)
307 next = changes->next;
308 g_free (changes);
309 changes = next;