update Copyright years for 1996
[platform/upstream/coreutils.git] / src / chmod.c
1 /* chmod -- change permission modes of files
2    Copyright (C) 89, 90, 91, 95, 1996 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Options:
19    -R   Recursively change modes of directory contents.
20    -c   Verbosely describe only files whose modes actually change.
21    -f   Do not print error messages about files.
22    -v   Verbosely describe changed modes.
23
24    David MacKenzie <djm@gnu.ai.mit.edu> */
25
26 #include <config.h>
27 #include <stdio.h>
28 #include <getopt.h>
29 #include <sys/types.h>
30
31 #include "modechange.h"
32 #include "system.h"
33 #include "error.h"
34
35 void mode_string ();
36 char *savedir ();
37 void strip_trailing_slashes ();
38 char *xmalloc ();
39 char *xrealloc ();
40
41 static int change_dir_mode __P ((char *dir, struct mode_change *changes,
42                                  struct stat *statp));
43
44 /* The name the program was run with. */
45 char *program_name;
46
47 /* If nonzero, change the modes of directories recursively. */
48 static int recurse;
49
50 /* If nonzero, force silence (no error messages). */
51 static int force_silent;
52
53 /* If nonzero, describe the modes we set. */
54 static int verbose;
55
56 /* If nonzero, describe only modes that change. */
57 static int changes_only;
58
59 /* If nonzero, display usage information and exit.  */
60 static int show_help;
61
62 /* If nonzero, print the version on standard output and exit.  */
63 static int show_version;
64
65 static struct option const long_options[] =
66 {
67   {"recursive", no_argument, 0, 'R'},
68   {"changes", no_argument, 0, 'c'},
69   {"silent", no_argument, 0, 'f'},
70   {"quiet", no_argument, 0, 'f'},
71   {"verbose", no_argument, 0, 'v'},
72   {"help", no_argument, &show_help, 1},
73   {"version", no_argument, &show_version, 1},
74   {0, 0, 0, 0}
75 };
76
77 /* Tell the user the mode MODE that file FILE has been set to;
78    if CHANGED is zero, FILE had that mode already. */
79
80 static void
81 describe_change (char *file, short unsigned int mode, int changed)
82 {
83   char perms[11];               /* "-rwxrwxrwx" ls-style modes. */
84
85   mode_string (mode, perms);
86   perms[10] = '\0';             /* `mode_string' does not null terminate. */
87   if (changed)
88     printf (_("mode of %s changed to %04o (%s)\n"),
89             file, mode & 07777, &perms[1]);
90   else
91     printf (_("mode of %s retained as %04o (%s)\n"),
92             file, mode & 07777, &perms[1]);
93 }
94
95 /* Change the mode of FILE according to the list of operations CHANGES.
96    If DEREF_SYMLINK is nonzero and FILE is a symbolic link, change the
97    mode of the referenced file.  If DEREF_SYMLINK is zero, ignore symbolic
98    links.  Return 0 if successful, 1 if errors occurred. */
99
100 static int
101 change_file_mode (char *file, struct mode_change *changes, int deref_symlink)
102 {
103   struct stat file_stats;
104   unsigned short newmode;
105   int errors = 0;
106
107   if (lstat (file, &file_stats))
108     {
109       if (force_silent == 0)
110         error (0, errno, "%s", file);
111       return 1;
112     }
113 #ifdef S_ISLNK
114   if (S_ISLNK (file_stats.st_mode))
115     {
116       if (! deref_symlink)
117         return 0;
118       else
119         if (stat (file, &file_stats))
120           {
121             if (force_silent == 0)
122               error (0, errno, "%s", file);
123             return 1;
124           }
125     }
126 #endif
127
128   newmode = mode_adjust (file_stats.st_mode, changes);
129
130   if (newmode != (file_stats.st_mode & 07777))
131     {
132       if (verbose)
133         describe_change (file, newmode, 1);
134       if (chmod (file, (int) newmode))
135         {
136           if (force_silent == 0)
137             error (0, errno, "%s", file);
138           errors = 1;
139         }
140     }
141   else if (verbose && changes_only == 0)
142     describe_change (file, newmode, 0);
143
144   if (recurse && S_ISDIR (file_stats.st_mode))
145     errors |= change_dir_mode (file, changes, &file_stats);
146   return errors;
147 }
148
149 /* Recursively change the modes of the files in directory DIR
150    according to the list of operations CHANGES.
151    STATP points to the results of lstat on DIR.
152    Return 0 if successful, 1 if errors occurred. */
153
154 static int
155 change_dir_mode (char *dir, struct mode_change *changes, struct stat *statp)
156 {
157   char *name_space, *namep;
158   char *path;                   /* Full path of each entry to process. */
159   unsigned dirlength;           /* Length of DIR and '\0'. */
160   unsigned filelength;          /* Length of each pathname to process. */
161   unsigned pathlength;          /* Bytes allocated for `path'. */
162   int errors = 0;
163
164   errno = 0;
165   name_space = savedir (dir, statp->st_size);
166   if (name_space == NULL)
167     {
168       if (errno)
169         {
170           if (force_silent == 0)
171             error (0, errno, "%s", dir);
172           return 1;
173         }
174       else
175         error (1, 0, _("virtual memory exhausted"));
176     }
177
178   dirlength = strlen (dir) + 1; /* + 1 is for the trailing '/'. */
179   pathlength = dirlength + 1;
180   /* Give `path' a dummy value; it will be reallocated before first use. */
181   path = xmalloc (pathlength);
182   strcpy (path, dir);
183   path[dirlength - 1] = '/';
184
185   for (namep = name_space; *namep; namep += filelength - dirlength)
186     {
187       filelength = dirlength + strlen (namep) + 1;
188       if (filelength > pathlength)
189         {
190           pathlength = filelength * 2;
191           path = xrealloc (path, pathlength);
192         }
193       strcpy (path + dirlength, namep);
194       errors |= change_file_mode (path, changes, 0);
195     }
196   free (path);
197   free (name_space);
198   return errors;
199 }
200
201 static void
202 usage (int status)
203 {
204   if (status != 0)
205     fprintf (stderr, _("Try `%s --help' for more information.\n"),
206              program_name);
207   else
208     {
209       printf (_("\
210 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
211   or:  %s [OPTION]... OCTAL_MODE FILE...\n\
212 "),
213               program_name, program_name);
214       printf (_("\
215 \n\
216   -c, --changes           like verbose but report only when a change is made\n\
217   -f, --silent, --quiet   suppress most error messages\n\
218   -v, --verbose           output a diagnostic for every file processed\n\
219   -R, --recursive         change files and directories recursively\n\
220       --help              display this help and exit\n\
221       --version           output version information and exit\n\
222 \n\
223 Each MODE is one or more of the letters ugoa, one of the symbols +-= and\n\
224 one or more of the letters rwxXstugo.\n"));
225     }
226   exit (status);
227 }
228
229 /* Parse the ASCII mode given on the command line into a linked list
230    of `struct mode_change' and apply that to each file argument. */
231
232 int
233 main (int argc, char **argv)
234 {
235   struct mode_change *changes;
236   int errors = 0;
237   int modeind = 0;              /* Index of the mode argument in `argv'. */
238   int thisind;
239   int c;
240
241   program_name = argv[0];
242   setlocale (LC_ALL, "");
243   bindtextdomain (PACKAGE, LOCALEDIR);
244   textdomain (PACKAGE);
245
246   recurse = force_silent = verbose = changes_only = 0;
247
248   while (1)
249     {
250       thisind = optind ? optind : 1;
251
252       c = getopt_long (argc, argv, "RcfvrwxXstugoa,+-=", long_options,
253                        (int *) 0);
254       if (c == EOF)
255         break;
256
257       switch (c)
258         {
259         case 0:
260           break;
261         case 'r':
262         case 'w':
263         case 'x':
264         case 'X':
265         case 's':
266         case 't':
267         case 'u':
268         case 'g':
269         case 'o':
270         case 'a':
271         case ',':
272         case '+':
273         case '-':
274         case '=':
275           if (modeind != 0 && modeind != thisind)
276             error (1, 0, _("invalid mode"));
277           modeind = thisind;
278           break;
279         case 'R':
280           recurse = 1;
281           break;
282         case 'c':
283           verbose = 1;
284           changes_only = 1;
285           break;
286         case 'f':
287           force_silent = 1;
288           break;
289         case 'v':
290           verbose = 1;
291           break;
292         default:
293           usage (1);
294         }
295     }
296
297   if (show_version)
298     {
299       printf ("chmod - %s\n", PACKAGE_VERSION);
300       exit (0);
301     }
302
303   if (show_help)
304     usage (0);
305
306   if (modeind == 0)
307     modeind = optind++;
308
309   if (optind >= argc)
310     {
311       error (0, 0, _("too few arguments"));
312       usage (1);
313     }
314
315   changes = mode_compile (argv[modeind],
316                           MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS);
317   if (changes == MODE_INVALID)
318     error (1, 0, _("invalid mode"));
319   else if (changes == MODE_MEMORY_EXHAUSTED)
320     error (1, 0, _("virtual memory exhausted"));
321
322   for (; optind < argc; ++optind)
323     {
324       strip_trailing_slashes (argv[optind]);
325       errors |= change_file_mode (argv[optind], changes, 1);
326     }
327
328   exit (errors);
329 }