doc: improve ls --help grammar
[coreutils/ericb.git] / src / chroot.c
blob95c227b360d6615cf8cc8251d1c4944516729da8
1 /* chroot -- run command or shell with special root directory
2 Copyright (C) 1995-1997, 1999-2004, 2007-2011 Free Software Foundation, Inc.
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.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 /* Written by Roland McGrath. */
19 #include <config.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <grp.h>
25 #include "system.h"
26 #include "error.h"
27 #include "quote.h"
28 #include "userspec.h"
29 #include "xstrtol.h"
31 /* The official name of this program (e.g., no `g' prefix). */
32 #define PROGRAM_NAME "chroot"
34 #define AUTHORS proper_name ("Roland McGrath")
36 #ifndef MAXGID
37 # define MAXGID GID_T_MAX
38 #endif
40 enum
42 GROUPS = UCHAR_MAX + 1,
43 USERSPEC
46 static struct option const long_opts[] =
48 {"groups", required_argument, NULL, GROUPS},
49 {"userspec", required_argument, NULL, USERSPEC},
50 {GETOPT_HELP_OPTION_DECL},
51 {GETOPT_VERSION_OPTION_DECL},
52 {NULL, 0, NULL, 0}
55 /* Call setgroups to set the supplementary groups to those listed in GROUPS.
56 GROUPS is a comma separated list of supplementary groups (names or numbers).
57 Parse that list, converting any names to numbers, and call setgroups on the
58 resulting numbers. Upon any failure give a diagnostic and return nonzero.
59 Otherwise return zero. */
60 static int
61 set_additional_groups (char const *groups)
63 GETGROUPS_T *gids = NULL;
64 size_t n_gids_allocated = 0;
65 size_t n_gids = 0;
66 char *buffer = xstrdup (groups);
67 char const *tmp;
68 int ret = 0;
70 for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
72 struct group *g;
73 unsigned long int value;
75 if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
76 g = getgrgid (value);
77 else
79 g = getgrnam (tmp);
80 if (g != NULL)
81 value = g->gr_gid;
84 if (g == NULL)
86 error (0, errno, _("invalid group %s"), quote (tmp));
87 ret = -1;
88 continue;
91 if (n_gids == n_gids_allocated)
92 gids = X2NREALLOC (gids, &n_gids_allocated);
93 gids[n_gids++] = value;
96 if (ret == 0 && n_gids == 0)
98 error (0, 0, _("invalid group list %s"), quote (groups));
99 ret = -1;
102 if (ret == 0)
104 ret = setgroups (n_gids, gids);
105 if (ret)
106 error (0, errno, _("failed to set additional groups"));
109 free (buffer);
110 free (gids);
111 return ret;
114 void
115 usage (int status)
117 if (status != EXIT_SUCCESS)
118 fprintf (stderr, _("Try `%s --help' for more information.\n"),
119 program_name);
120 else
122 printf (_("\
123 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
124 or: %s OPTION\n\
125 "), program_name, program_name);
127 fputs (_("\
128 Run COMMAND with root directory set to NEWROOT.\n\
130 "), stdout);
132 fputs (_("\
133 --userspec=USER:GROUP specify user and group (ID or name) to use\n\
134 --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
135 "), stdout);
137 fputs (HELP_OPTION_DESCRIPTION, stdout);
138 fputs (VERSION_OPTION_DESCRIPTION, stdout);
139 fputs (_("\
141 If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\
142 "), stdout);
143 emit_ancillary_info ();
145 exit (status);
149 main (int argc, char **argv)
151 int c;
152 char const *userspec = NULL;
153 char const *groups = NULL;
155 initialize_main (&argc, &argv);
156 set_program_name (argv[0]);
157 setlocale (LC_ALL, "");
158 bindtextdomain (PACKAGE, LOCALEDIR);
159 textdomain (PACKAGE);
161 initialize_exit_failure (EXIT_CANCELED);
162 atexit (close_stdout);
164 while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
166 switch (c)
168 case USERSPEC:
169 userspec = optarg;
170 break;
172 case GROUPS:
173 groups = optarg;
174 break;
176 case_GETOPT_HELP_CHAR;
178 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
180 default:
181 usage (EXIT_CANCELED);
185 if (argc <= optind)
187 error (0, 0, _("missing operand"));
188 usage (EXIT_CANCELED);
191 if (chroot (argv[optind]) != 0)
192 error (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
193 argv[optind]);
195 if (chdir ("/"))
196 error (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
198 if (argc == optind + 1)
200 /* No command. Run an interactive shell. */
201 char *shell = getenv ("SHELL");
202 if (shell == NULL)
203 shell = bad_cast ("/bin/sh");
204 argv[0] = shell;
205 argv[1] = bad_cast ("-i");
206 argv[2] = NULL;
208 else
210 /* The following arguments give the command. */
211 argv += optind + 1;
214 bool fail = false;
216 /* Attempt to set all three: supplementary groups, group ID, user ID.
217 Diagnose any failures. If any have failed, exit before execvp. */
218 if (userspec)
220 uid_t uid = -1;
221 gid_t gid = -1;
222 char *user;
223 char *group;
224 char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group);
226 if (err)
227 error (EXIT_CANCELED, errno, "%s", err);
229 free (user);
230 free (group);
232 if (groups && set_additional_groups (groups))
233 fail = true;
235 if (gid != (gid_t) -1 && setgid (gid))
237 error (0, errno, _("failed to set group-ID"));
238 fail = true;
241 if (uid != (uid_t) -1 && setuid (uid))
243 error (0, errno, _("failed to set user-ID"));
244 fail = true;
247 else
249 /* Yes, this call is identical to the one above.
250 However, when --userspec and --groups groups are used together,
251 we don't want to call this function until after parsing USER:GROUP,
252 and it must be called before setuid. */
253 if (groups && set_additional_groups (groups))
254 fail = true;
257 if (fail)
258 exit (EXIT_CANCELED);
260 /* Execute the given command. */
261 execvp (argv[0], argv);
264 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
265 error (0, errno, _("failed to run command %s"), quote (argv[0]));
266 exit (exit_status);