1 /* chown -- change user and group ownership of files
2    Copyright (C) 1989-2023 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 3 of the License, or
7    (at your option) 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, see <https://www.gnu.org/licenses/>.  */
16 
17 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
18 
19 #include <config.h>
20 #include <stdio.h>
21 #include <sys/types.h>
22 #include <getopt.h>
23 
24 #include "system.h"
25 #include "chown-core.h"
26 #include "fts_.h"
27 #include "quote.h"
28 #include "root-dev-ino.h"
29 #include "userspec.h"
30 
31 /* The official name of this program (e.g., no 'g' prefix).  */
32 #define PROGRAM_NAME "chown"
33 
34 #define AUTHORS \
35   proper_name ("David MacKenzie"), \
36   proper_name ("Jim Meyering")
37 
38 /* The argument to the --reference option.  Use the owner and group IDs
39    of this file.  This file must exist.  */
40 static char *reference_file;
41 
42 /* For long options that have no equivalent short option, use a
43    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
44 enum
45 {
46   DEREFERENCE_OPTION = CHAR_MAX + 1,
47   FROM_OPTION,
48   NO_PRESERVE_ROOT,
49   PRESERVE_ROOT,
50   REFERENCE_FILE_OPTION
51 };
52 
53 static struct option const long_options[] =
54 {
55   {"recursive", no_argument, nullptr, 'R'},
56   {"changes", no_argument, nullptr, 'c'},
57   {"dereference", no_argument, nullptr, DEREFERENCE_OPTION},
58   {"from", required_argument, nullptr, FROM_OPTION},
59   {"no-dereference", no_argument, nullptr, 'h'},
60   {"no-preserve-root", no_argument, nullptr, NO_PRESERVE_ROOT},
61   {"preserve-root", no_argument, nullptr, PRESERVE_ROOT},
62   {"quiet", no_argument, nullptr, 'f'},
63   {"silent", no_argument, nullptr, 'f'},
64   {"reference", required_argument, nullptr, REFERENCE_FILE_OPTION},
65   {"verbose", no_argument, nullptr, 'v'},
66   {GETOPT_HELP_OPTION_DECL},
67   {GETOPT_VERSION_OPTION_DECL},
68   {nullptr, 0, nullptr, 0}
69 };
70 
71 void
usage(int status)72 usage (int status)
73 {
74   if (status != EXIT_SUCCESS)
75     emit_try_help ();
76   else
77     {
78       printf (_("\
79 Usage: %s [OPTION]... [OWNER][:[GROUP]] FILE...\n\
80   or:  %s [OPTION]... --reference=RFILE FILE...\n\
81 "),
82               program_name, program_name);
83       fputs (_("\
84 Change the owner and/or group of each FILE to OWNER and/or GROUP.\n\
85 With --reference, change the owner and group of each FILE to those of RFILE.\n\
86 \n\
87 "), stdout);
88       fputs (_("\
89   -c, --changes          like verbose but report only when a change is made\n\
90   -f, --silent, --quiet  suppress most error messages\n\
91   -v, --verbose          output a diagnostic for every file processed\n\
92 "), stdout);
93       fputs (_("\
94       --dereference      affect the referent of each symbolic link (this is\n\
95                          the default), rather than the symbolic link itself\n\
96   -h, --no-dereference   affect symbolic links instead of any referenced file\n\
97 "), stdout);
98       fputs (_("\
99                          (useful only on systems that can change the\n\
100                          ownership of a symlink)\n\
101 "), stdout);
102       fputs (_("\
103       --from=CURRENT_OWNER:CURRENT_GROUP\n\
104                          change the owner and/or group of each file only if\n\
105                          its current owner and/or group match those specified\n\
106                          here.  Either may be omitted, in which case a match\n\
107                          is not required for the omitted attribute\n\
108 "), stdout);
109       fputs (_("\
110       --no-preserve-root  do not treat '/' specially (the default)\n\
111       --preserve-root    fail to operate recursively on '/'\n\
112 "), stdout);
113       fputs (_("\
114       --reference=RFILE  use RFILE's owner and group rather than specifying\n\
115                          OWNER:GROUP values.  RFILE is always dereferenced.\n\
116 "), stdout);
117       fputs (_("\
118   -R, --recursive        operate on files and directories recursively\n\
119 "), stdout);
120       fputs (_("\
121 \n\
122 The following options modify how a hierarchy is traversed when the -R\n\
123 option is also specified.  If more than one is specified, only the final\n\
124 one takes effect.\n\
125 \n\
126   -H                     if a command line argument is a symbolic link\n\
127                          to a directory, traverse it\n\
128   -L                     traverse every symbolic link to a directory\n\
129                          encountered\n\
130   -P                     do not traverse any symbolic links (default)\n\
131 \n\
132 "), stdout);
133       fputs (HELP_OPTION_DESCRIPTION, stdout);
134       fputs (VERSION_OPTION_DESCRIPTION, stdout);
135       fputs (_("\
136 \n\
137 Owner is unchanged if missing.  Group is unchanged if missing, but changed\n\
138 to login group if implied by a ':' following a symbolic OWNER.\n\
139 OWNER and GROUP may be numeric as well as symbolic.\n\
140 "), stdout);
141       printf (_("\
142 \n\
143 Examples:\n\
144   %s root /u        Change the owner of /u to \"root\".\n\
145   %s root:staff /u  Likewise, but also change its group to \"staff\".\n\
146   %s -hR root /u    Change the owner of /u and subfiles to \"root\".\n\
147 "),
148               program_name, program_name, program_name);
149       emit_ancillary_info (PROGRAM_NAME);
150     }
151   exit (status);
152 }
153 
154 int
main(int argc,char ** argv)155 main (int argc, char **argv)
156 {
157   bool preserve_root = false;
158 
159   uid_t uid = -1;	/* Specified uid; -1 if not to be changed. */
160   gid_t gid = -1;	/* Specified gid; -1 if not to be changed. */
161 
162   /* Change the owner (group) of a file only if it has this uid (gid).
163      -1 means there's no restriction.  */
164   uid_t required_uid = -1;
165   gid_t required_gid = -1;
166 
167   /* Bit flags that control how fts works.  */
168   int bit_flags = FTS_PHYSICAL;
169 
170   /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been
171      specified.  */
172   int dereference = -1;
173 
174   struct Chown_option chopt;
175   bool ok;
176   int optc;
177 
178   initialize_main (&argc, &argv);
179   set_program_name (argv[0]);
180   setlocale (LC_ALL, "");
181   bindtextdomain (PACKAGE, LOCALEDIR);
182   textdomain (PACKAGE);
183 
184   atexit (close_stdout);
185 
186   chopt_init (&chopt);
187 
188   while ((optc = getopt_long (argc, argv, "HLPRcfhv", long_options, nullptr))
189          != -1)
190     {
191       switch (optc)
192         {
193         case 'H': /* Traverse command-line symlinks-to-directories.  */
194           bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
195           break;
196 
197         case 'L': /* Traverse all symlinks-to-directories.  */
198           bit_flags = FTS_LOGICAL;
199           break;
200 
201         case 'P': /* Traverse no symlinks-to-directories.  */
202           bit_flags = FTS_PHYSICAL;
203           break;
204 
205         case 'h': /* --no-dereference: affect symlinks */
206           dereference = 0;
207           break;
208 
209         case DEREFERENCE_OPTION: /* --dereference: affect the referent
210                                     of each symlink */
211           dereference = 1;
212           break;
213 
214         case NO_PRESERVE_ROOT:
215           preserve_root = false;
216           break;
217 
218         case PRESERVE_ROOT:
219           preserve_root = true;
220           break;
221 
222         case REFERENCE_FILE_OPTION:
223           reference_file = optarg;
224           break;
225 
226         case FROM_OPTION:
227           {
228             bool warn;
229             char const *e = parse_user_spec_warn (optarg,
230                                                   &required_uid, &required_gid,
231                                                   nullptr, nullptr, &warn);
232             if (e)
233               error (warn ? 0 : EXIT_FAILURE, 0, "%s: %s", e, quote (optarg));
234             break;
235           }
236 
237         case 'R':
238           chopt.recurse = true;
239           break;
240 
241         case 'c':
242           chopt.verbosity = V_changes_only;
243           break;
244 
245         case 'f':
246           chopt.force_silent = true;
247           break;
248 
249         case 'v':
250           chopt.verbosity = V_high;
251           break;
252 
253         case_GETOPT_HELP_CHAR;
254         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
255         default:
256           usage (EXIT_FAILURE);
257         }
258     }
259 
260   if (chopt.recurse)
261     {
262       if (bit_flags == FTS_PHYSICAL)
263         {
264           if (dereference == 1)
265             error (EXIT_FAILURE, 0,
266                    _("-R --dereference requires either -H or -L"));
267           dereference = 0;
268         }
269     }
270   else
271     {
272       bit_flags = FTS_PHYSICAL;
273     }
274   chopt.affect_symlink_referent = (dereference != 0);
275 
276   if (argc - optind < (reference_file ? 1 : 2))
277     {
278       if (argc <= optind)
279         error (0, 0, _("missing operand"));
280       else
281         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
282       usage (EXIT_FAILURE);
283     }
284 
285   if (reference_file)
286     {
287       struct stat ref_stats;
288       if (stat (reference_file, &ref_stats))
289         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
290                quoteaf (reference_file));
291 
292       uid = ref_stats.st_uid;
293       gid = ref_stats.st_gid;
294       chopt.user_name = uid_to_name (ref_stats.st_uid);
295       chopt.group_name = gid_to_name (ref_stats.st_gid);
296     }
297   else
298     {
299       bool warn;
300       char const *e = parse_user_spec_warn (argv[optind], &uid, &gid,
301                                             &chopt.user_name,
302                                             &chopt.group_name, &warn);
303       if (e)
304         error (warn ? 0 : EXIT_FAILURE, 0, "%s: %s", e, quote (argv[optind]));
305 
306       /* If a group is specified but no user, set the user name to the
307          empty string so that diagnostics say "ownership :GROUP"
308          rather than "group GROUP".  */
309       if (!chopt.user_name && chopt.group_name)
310         chopt.user_name = xstrdup ("");
311 
312       optind++;
313     }
314 
315   if (chopt.recurse && preserve_root)
316     {
317       static struct dev_ino dev_ino_buf;
318       chopt.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
319       if (chopt.root_dev_ino == nullptr)
320         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
321                quoteaf ("/"));
322     }
323 
324   bit_flags |= FTS_DEFER_STAT;
325   ok = chown_files (argv + optind, bit_flags,
326                     uid, gid,
327                     required_uid, required_gid, &chopt);
328 
329   main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
330 }
331