tests: unpack xz-compressed tarballs when possible, not always *.gz
[coreutils.git] / src / chroot.c
blobe4765babbf28dafc8090f798a81119b936d0a769
1 /* chroot -- run command or shell with special root directory
2 Copyright (C) 95, 96, 1997, 1999-2004, 2007-2009
3 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 /* Written by Roland McGrath. */
20 #include <config.h>
21 #include <getopt.h>
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <grp.h>
26 #include "system.h"
27 #include "error.h"
28 #include "long-options.h"
29 #include "quote.h"
30 #include "userspec.h"
31 #include "xstrtol.h"
33 /* The official name of this program (e.g., no `g' prefix). */
34 #define PROGRAM_NAME "chroot"
36 #define AUTHORS proper_name ("Roland McGrath")
38 #ifndef MAXGID
39 # define MAXGID GID_T_MAX
40 #endif
42 enum
44 GROUPS = UCHAR_MAX + 1,
45 USERSPEC
48 static struct option const long_opts[] =
50 {"groups", required_argument, NULL, GROUPS},
51 {"userspec", required_argument, NULL, USERSPEC},
52 {GETOPT_HELP_OPTION_DECL},
53 {GETOPT_VERSION_OPTION_DECL},
54 {NULL, 0, NULL, 0}
57 /* Call setgroups to set the supplementary groups to those listed in GROUPS.
58 GROUPS is a comma separated list of supplementary groups (names or numbers).
59 Parse that list, converting any names to numbers, and call setgroups on the
60 resulting numbers. Upon any failure give a diagnostic and return nonzero.
61 Otherwise return zero. */
62 static int
63 set_additional_groups (char const *groups)
65 GETGROUPS_T *gids = NULL;
66 size_t n_gids_allocated = 0;
67 size_t n_gids = 0;
68 char *buffer = xstrdup (groups);
69 char const *tmp;
70 int ret = 0;
72 for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
74 struct group *g;
75 unsigned long int value;
77 if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
78 g = getgrgid (value);
79 else
81 g = getgrnam (tmp);
82 if (g != NULL)
83 value = g->gr_gid;
86 if (g == NULL)
88 error (0, errno, _("invalid group %s"), quote (tmp));
89 ret = -1;
90 continue;
93 if (n_gids == n_gids_allocated)
94 gids = X2NREALLOC (gids, &n_gids_allocated);
95 gids[n_gids++] = value;
98 if (ret == 0 && n_gids == 0)
100 error (0, 0, _("invalid group list %s"), quote (groups));
101 ret = -1;
104 if (ret == 0)
106 ret = setgroups (n_gids, gids);
107 if (ret)
108 error (0, errno, _("failed to set additional groups"));
111 free (buffer);
112 free (gids);
113 return ret;
116 void
117 usage (int status)
119 if (status != EXIT_SUCCESS)
120 fprintf (stderr, _("Try `%s --help' for more information.\n"),
121 program_name);
122 else
124 printf (_("\
125 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
126 or: %s OPTION\n\
127 "), program_name, program_name);
129 fputs (_("\
130 Run COMMAND with root directory set to NEWROOT.\n\
132 "), stdout);
134 fputs (_("\
135 --userspec=USER:GROUP specify user and group (ID or name) to use\n\
136 --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
137 "), stdout);
139 fputs (HELP_OPTION_DESCRIPTION, stdout);
140 fputs (VERSION_OPTION_DESCRIPTION, stdout);
141 fputs (_("\
143 If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\
144 "), stdout);
145 emit_ancillary_info ();
147 exit (status);
151 main (int argc, char **argv)
153 int c;
154 char const *userspec = NULL;
155 char const *groups = NULL;
157 initialize_main (&argc, &argv);
158 set_program_name (argv[0]);
159 setlocale (LC_ALL, "");
160 bindtextdomain (PACKAGE, LOCALEDIR);
161 textdomain (PACKAGE);
163 initialize_exit_failure (EXIT_CANCELED);
164 atexit (close_stdout);
166 parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
167 usage, AUTHORS, (char const *) NULL);
169 while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
171 switch (c)
173 case USERSPEC:
174 userspec = optarg;
175 break;
176 case GROUPS:
177 groups = optarg;
178 break;
179 default:
180 usage (EXIT_CANCELED);
184 if (argc <= optind)
186 error (0, 0, _("missing operand"));
187 usage (EXIT_CANCELED);
190 if (chroot (argv[optind]) != 0)
191 error (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
192 argv[optind]);
194 if (chdir ("/"))
195 error (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
197 if (argc == optind + 1)
199 /* No command. Run an interactive shell. */
200 char *shell = getenv ("SHELL");
201 if (shell == NULL)
202 shell = bad_cast ("/bin/sh");
203 argv[0] = shell;
204 argv[1] = bad_cast ("-i");
205 argv[2] = NULL;
207 else
209 /* The following arguments give the command. */
210 argv += optind + 1;
213 bool fail = false;
215 /* Attempt to set all three: supplementary groups, group ID, user ID.
216 Diagnose any failures. If any have failed, exit before execvp. */
217 if (userspec)
219 uid_t uid = -1;
220 gid_t gid = -1;
221 char *user;
222 char *group;
223 char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group);
225 if (err)
226 error (EXIT_CANCELED, errno, "%s", err);
228 free (user);
229 free (group);
231 if (groups && set_additional_groups (groups))
232 fail = true;
234 if (gid != (gid_t) -1 && setgid (gid))
236 error (0, errno, _("failed to set group-ID"));
237 fail = true;
240 if (uid != (uid_t) -1 && setuid (uid))
242 error (0, errno, _("failed to set user-ID"));
243 fail = true;
246 else
248 /* Yes, this call is identical to the one above.
249 However, when --userspec and --groups groups are used together,
250 we don't want to call this function until after parsing USER:GROUP,
251 and it must be called before setuid. */
252 if (groups && set_additional_groups (groups))
253 fail = true;
256 if (fail)
257 exit (EXIT_CANCELED);
259 /* Execute the given command. */
260 execvp (argv[0], argv);
263 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
264 error (0, errno, _("failed to run command %s"), quote (argv[0]));
265 exit (exit_status);