Tizen 2.0 Release
[external/tizen-coreutils.git] / lib / modechange.c
1 /* modechange.c -- file mode manipulation
2
3    Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005,
4    2006 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
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
17    along with this program; if not, write to the Free Software Foundation,
18    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19
20 /* Written by David MacKenzie <djm@ai.mit.edu> */
21
22 /* The ASCII mode string is compiled into an array of `struct
23    modechange', which can then be applied to each file to be changed.
24    We do this instead of re-parsing the ASCII string for each file
25    because the compiled form requires less computation to use; when
26    changing the mode of many files, this probably results in a
27    performance gain.  */
28
29 #include <config.h>
30
31 #include "modechange.h"
32 #include <sys/stat.h>
33 #include "stat-macros.h"
34 #include "xalloc.h"
35 #include <stdlib.h>
36
37 /* The traditional octal values corresponding to each mode bit.  */
38 #define SUID 04000
39 #define SGID 02000
40 #define SVTX 01000
41 #define RUSR 00400
42 #define WUSR 00200
43 #define XUSR 00100
44 #define RGRP 00040
45 #define WGRP 00020
46 #define XGRP 00010
47 #define ROTH 00004
48 #define WOTH 00002
49 #define XOTH 00001
50 #define ALLM 07777 /* all octal mode bits */
51
52 /* Convert OCTAL, which uses one of the traditional octal values, to
53    an internal mode_t value.  */
54 static mode_t
55 octal_to_mode (unsigned int octal)
56 {
57   /* Help the compiler optimize the usual case where mode_t uses
58      the traditional octal representation.  */
59   return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
60            && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
61            && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
62            && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
63           ? octal
64           : (mode_t) ((octal & SUID ? S_ISUID : 0)
65                       | (octal & SGID ? S_ISGID : 0)
66                       | (octal & SVTX ? S_ISVTX : 0)
67                       | (octal & RUSR ? S_IRUSR : 0)
68                       | (octal & WUSR ? S_IWUSR : 0)
69                       | (octal & XUSR ? S_IXUSR : 0)
70                       | (octal & RGRP ? S_IRGRP : 0)
71                       | (octal & WGRP ? S_IWGRP : 0)
72                       | (octal & XGRP ? S_IXGRP : 0)
73                       | (octal & ROTH ? S_IROTH : 0)
74                       | (octal & WOTH ? S_IWOTH : 0)
75                       | (octal & XOTH ? S_IXOTH : 0)));
76 }
77
78 /* Special operations flags.  */
79 enum
80   {
81     /* For the sentinel at the end of the mode changes array.  */
82     MODE_DONE,
83
84     /* The typical case.  */
85     MODE_ORDINARY_CHANGE,
86
87     /* In addition to the typical case, affect the execute bits if at
88        least one execute bit is set already, or if the file is a
89        directory.  */
90     MODE_X_IF_ANY_X,
91
92     /* Instead of the typical case, copy some existing permissions for
93        u, g, or o onto the other two.  Which of u, g, or o is copied
94        is determined by which bits are set in the `value' field.  */
95     MODE_COPY_EXISTING
96   };
97
98 /* Description of a mode change.  */
99 struct mode_change
100 {
101   char op;                      /* One of "=+-".  */
102   char flag;                    /* Special operations flag.  */
103   mode_t affected;              /* Set for u, g, o, or a.  */
104   mode_t value;                 /* Bits to add/remove.  */
105   mode_t mentioned;             /* Bits explicitly mentioned.  */
106 };
107
108 /* Return a mode_change array with the specified `=ddd'-style
109    mode change operation, where NEW_MODE is `ddd' and MENTIONED
110    contains the bits explicitly mentioned in the mode are MENTIONED.  */
111
112 static struct mode_change *
113 make_node_op_equals (mode_t new_mode, mode_t mentioned)
114 {
115   struct mode_change *p = xmalloc (2 * sizeof *p);
116   p->op = '=';
117   p->flag = MODE_ORDINARY_CHANGE;
118   p->affected = CHMOD_MODE_BITS;
119   p->value = new_mode;
120   p->mentioned = mentioned;
121   p[1].flag = MODE_DONE;
122   return p;
123 }
124
125 /* Return a pointer to an array of file mode change operations created from
126    MODE_STRING, an ASCII string that contains either an octal number
127    specifying an absolute mode, or symbolic mode change operations with
128    the form:
129    [ugoa...][[+-=][rwxXstugo...]...][,...]
130
131    Return NULL if `mode_string' does not contain a valid
132    representation of file mode change operations.  */
133
134 struct mode_change *
135 mode_compile (char const *mode_string)
136 {
137   /* The array of mode-change directives to be returned.  */
138   struct mode_change *mc;
139   size_t used = 0;
140
141   if ('0' <= *mode_string && *mode_string < '8')
142     {
143       unsigned int octal_mode = 0;
144       mode_t mode;
145       mode_t mentioned;
146
147       do
148         {
149           octal_mode = 8 * octal_mode + *mode_string++ - '0';
150           if (ALLM < octal_mode)
151             return NULL;
152         }
153       while ('0' <= *mode_string && *mode_string < '8');
154
155       if (*mode_string)
156         return NULL;
157
158       mode = octal_to_mode (octal_mode);
159       mentioned = (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO;
160       return make_node_op_equals (mode, mentioned);
161     }
162
163   /* Allocate enough space to hold the result.  */
164   {
165     size_t needed = 1;
166     char const *p;
167     for (p = mode_string; *p; p++)
168       needed += (*p == '=' || *p == '+' || *p == '-');
169     mc = xnmalloc (needed, sizeof *mc);
170   }
171
172   /* One loop iteration for each `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.  */
173   for (;; mode_string++)
174     {
175       /* Which bits in the mode are operated on.  */
176       mode_t affected = 0;
177
178       /* Turn on all the bits in `affected' for each group given.  */
179       for (;; mode_string++)
180         switch (*mode_string)
181           {
182           default:
183             goto invalid;
184           case 'u':
185             affected |= S_ISUID | S_IRWXU;
186             break;
187           case 'g':
188             affected |= S_ISGID | S_IRWXG;
189             break;
190           case 'o':
191             affected |= S_ISVTX | S_IRWXO;
192             break;
193           case 'a':
194             affected |= CHMOD_MODE_BITS;
195             break;
196           case '=': case '+': case '-':
197             goto no_more_affected;
198           }
199     no_more_affected:;
200
201       do
202         {
203           char op = *mode_string++;
204           mode_t value;
205           char flag = MODE_COPY_EXISTING;
206           struct mode_change *change;
207
208           switch (*mode_string++)
209             {
210             case 'u':
211               /* Set the affected bits to the value of the `u' bits
212                  on the same file.  */
213               value = S_IRWXU;
214               break;
215             case 'g':
216               /* Set the affected bits to the value of the `g' bits
217                  on the same file.  */
218               value = S_IRWXG;
219               break;
220             case 'o':
221               /* Set the affected bits to the value of the `o' bits
222                  on the same file.  */
223               value = S_IRWXO;
224               break;
225
226             default:
227               value = 0;
228               flag = MODE_ORDINARY_CHANGE;
229
230               for (mode_string--;; mode_string++)
231                 switch (*mode_string)
232                   {
233                   case 'r':
234                     value |= S_IRUSR | S_IRGRP | S_IROTH;
235                     break;
236                   case 'w':
237                     value |= S_IWUSR | S_IWGRP | S_IWOTH;
238                     break;
239                   case 'x':
240                     value |= S_IXUSR | S_IXGRP | S_IXOTH;
241                     break;
242                   case 'X':
243                     flag = MODE_X_IF_ANY_X;
244                     break;
245                   case 's':
246                     /* Set the setuid/gid bits if `u' or `g' is selected.  */
247                     value |= S_ISUID | S_ISGID;
248                     break;
249                   case 't':
250                     /* Set the "save text image" bit if `o' is selected.  */
251                     value |= S_ISVTX;
252                     break;
253                   default:
254                     goto no_more_values;
255                   }
256             no_more_values:;
257             }
258
259           change = &mc[used++];
260           change->op = op;
261           change->flag = flag;
262           change->affected = affected;
263           change->value = value;
264           change->mentioned = (affected ? affected & value : value);
265         }
266       while (*mode_string == '=' || *mode_string == '+'
267              || *mode_string == '-');
268
269       if (*mode_string != ',')
270         break;
271     }
272
273   if (*mode_string == 0)
274     {
275       mc[used].flag = MODE_DONE;
276       return mc;
277     }
278
279 invalid:
280   free (mc);
281   return NULL;
282 }
283
284 /* Return a file mode change operation that sets permissions to match those
285    of REF_FILE.  Return NULL (setting errno) if REF_FILE can't be accessed.  */
286
287 struct mode_change *
288 mode_create_from_ref (const char *ref_file)
289 {
290   struct stat ref_stats;
291
292   if (stat (ref_file, &ref_stats) != 0)
293     return NULL;
294   return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
295 }
296
297 /* Return the file mode bits of OLDMODE (which is the mode of a
298    directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
299    indicated by the list of change operations CHANGES.  If DIR, the
300    type 'X' change affects the returned value even if no execute bits
301    were set in OLDMODE, and set user and group ID bits are preserved
302    unless CHANGES mentioned them.  If PMODE_BITS is not null, store into
303    *PMODE_BITS a mask denoting file mode bits that are affected by
304    CHANGES.
305
306    The returned value and *PMODE_BITS contain only file mode bits.
307    For example, they have the S_IFMT bits cleared on a standard
308    Unix-like host.  */
309
310 mode_t
311 mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
312              struct mode_change const *changes, mode_t *pmode_bits)
313 {
314   /* The adjusted mode.  */
315   mode_t newmode = oldmode & CHMOD_MODE_BITS;
316
317   /* File mode bits that CHANGES cares about.  */
318   mode_t mode_bits = 0;
319
320   for (; changes->flag != MODE_DONE; changes++)
321     {
322       mode_t affected = changes->affected;
323       mode_t omit_change =
324         (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
325       mode_t value = changes->value;
326
327       switch (changes->flag)
328         {
329         case MODE_ORDINARY_CHANGE:
330           break;
331
332         case MODE_COPY_EXISTING:
333           /* Isolate in `value' the bits in `newmode' to copy.  */
334           value &= newmode;
335
336           /* Copy the isolated bits to the other two parts.  */
337           value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH)
338                      ? S_IRUSR | S_IRGRP | S_IROTH : 0)
339                     | (value & (S_IWUSR | S_IWGRP | S_IWOTH)
340                        ? S_IWUSR | S_IWGRP | S_IWOTH : 0)
341                     | (value & (S_IXUSR | S_IXGRP | S_IXOTH)
342                        ? S_IXUSR | S_IXGRP | S_IXOTH : 0));
343           break;
344
345         case MODE_X_IF_ANY_X:
346           /* Affect the execute bits if execute bits are already set
347              or if the file is a directory.  */
348           if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
349             value |= S_IXUSR | S_IXGRP | S_IXOTH;
350           break;
351         }
352
353       /* If WHO was specified, limit the change to the affected bits.
354          Otherwise, apply the umask.  Either way, omit changes as
355          requested.  */
356       value &= (affected ? affected : ~umask_value) & ~ omit_change;
357
358       switch (changes->op)
359         {
360         case '=':
361           /* If WHO was specified, preserve the previous values of
362              bits that are not affected by this change operation.
363              Otherwise, clear all the bits.  */
364           {
365             mode_t preserved = (affected ? ~affected : 0) | omit_change;
366             mode_bits |= CHMOD_MODE_BITS & ~preserved;
367             newmode = (newmode & preserved) | value;
368             break;
369           }
370
371         case '+':
372           mode_bits |= value;
373           newmode |= value;
374           break;
375
376         case '-':
377           mode_bits |= value;
378           newmode &= ~value;
379           break;
380         }
381     }
382
383   if (pmode_bits)
384     *pmode_bits = mode_bits;
385   return newmode;
386 }