// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/main/tools/linux-sandbox-options.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "src/main/tools/logging.h" #include "src/main/tools/process-tools.h" using std::ifstream; using std::unique_ptr; using std::vector; struct Options opt; // Print out a usage error. argc and argv are the argument counter and vector, // fmt is a format, string for the error message to print. static void Usage(char *program_name, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\nUsage: %s -- command arg1 @args\n", program_name); fprintf(stderr, "\nPossible arguments:\n" " -W working directory (uses current directory if " "not specified)\n" " -T timeout after which the child process will be " "terminated with SIGTERM\n" " -t in case timeout occurs, how long to wait before " "killing the child with SIGKILL\n" " -l redirect stdout to a file\n" " -L redirect stderr to a file\n" " -w make a file or directory writable for the sandboxed " "process\n" " -e mount an empty tmpfs on a directory\n" " -M/-m directory to mount inside the sandbox\n" " Multiple directories can be specified and each of them will be " "mounted readonly.\n" " The -M option specifies which directory to mount, the -m option " "specifies where to\n" " -S if set, write stats in protobuf format to a file\n" " -H if set, make hostname in the sandbox equal to 'localhost'\n" " -N if set, a new network namespace will be created\n" " -R if set, make the uid/gid be root\n" " -U if set, make the uid/gid be nobody\n" " -D if set, debug info will be printed\n" " @FILE read newline-separated arguments from FILE\n" " -- command to run inside sandbox, followed by arguments\n"); exit(EXIT_FAILURE); } static void ValidateIsAbsolutePath(char *path, char *program_name, char flag) { if (path[0] != '/') { Usage(program_name, "The -%c option must be used with absolute paths only.", flag); } } // Parses command line flags from an argv array and puts the results into an // Options structure passed in as an argument. static void ParseCommandLine(unique_ptr> args) { extern char *optarg; extern int optind, optopt; int c; bool source_specified = false; while ((c = getopt(args->size(), args->data(), ":W:T:t:l:L:w:e:M:m:S:HNRUD")) != -1) { if (c != 'M' && c != 'm') source_specified = false; switch (c) { case 'W': if (opt.working_dir.empty()) { ValidateIsAbsolutePath(optarg, args->front(), static_cast(c)); opt.working_dir.assign(optarg); } else { Usage(args->front(), "Multiple working directories (-W) specified, expected one."); } break; case 'T': if (sscanf(optarg, "%d", &opt.timeout_secs) != 1 || opt.timeout_secs < 0) { Usage(args->front(), "Invalid timeout (-T) value: %s", optarg); } break; case 't': if (sscanf(optarg, "%d", &opt.kill_delay_secs) != 1 || opt.kill_delay_secs < 0) { Usage(args->front(), "Invalid kill delay (-t) value: %s", optarg); } break; case 'l': if (opt.stdout_path.empty()) { opt.stdout_path.assign(optarg); } else { Usage(args->front(), "Cannot redirect stdout to more than one destination."); } break; case 'L': if (opt.stderr_path.empty()) { opt.stderr_path.assign(optarg); } else { Usage(args->front(), "Cannot redirect stderr to more than one destination."); } break; case 'w': ValidateIsAbsolutePath(optarg, args->front(), static_cast(c)); opt.writable_files.emplace_back(optarg); break; case 'e': ValidateIsAbsolutePath(optarg, args->front(), static_cast(c)); opt.tmpfs_dirs.emplace_back(optarg); break; case 'M': ValidateIsAbsolutePath(optarg, args->front(), static_cast(c)); // Add the current source path to both source and target lists opt.bind_mount_sources.emplace_back(optarg); opt.bind_mount_targets.emplace_back(optarg); source_specified = true; break; case 'm': ValidateIsAbsolutePath(optarg, args->front(), static_cast(c)); if (!source_specified) { Usage(args->front(), "The -m option must be strictly preceded by an -M option."); } opt.bind_mount_targets.pop_back(); opt.bind_mount_targets.emplace_back(optarg); source_specified = false; break; case 'S': if (opt.stats_path.empty()) { opt.stats_path.assign(optarg); } else { Usage(args->front(), "Cannot write stats to more than one destination."); } break; case 'H': opt.fake_hostname = true; break; case 'N': opt.create_netns = true; break; case 'R': if (opt.fake_username) { Usage(args->front(), "The -R option cannot be used at the same time us the -U " "option."); } opt.fake_root = true; break; case 'U': if (opt.fake_root) { Usage(args->front(), "The -U option cannot be used at the same time us the -R " "option."); } opt.fake_username = true; break; case 'D': opt.debug = true; break; case '?': Usage(args->front(), "Unrecognized argument: -%c (%d)", optopt, optind); break; case ':': Usage(args->front(), "Flag -%c requires an argument", optopt); break; } } if (optind < static_cast(args->size())) { if (opt.args.empty()) { opt.args.assign(args->begin() + optind, args->end()); } else { Usage(args->front(), "Merging commands not supported."); } } } // Expands a single argument, expanding options @filename to read in the content // of the file and add it to the list of processed arguments. static unique_ptr> ExpandArgument( unique_ptr> expanded, char *arg) { if (arg[0] == '@') { const char *filename = arg + 1; // strip off the '@'. ifstream f(filename); if (!f.is_open()) { DIE("opening argument file %s failed", filename); } for (std::string line; std::getline(f, line);) { if (line.length() > 0) { expanded = ExpandArgument(std::move(expanded), strdup(line.c_str())); } } if (f.bad()) { DIE("error while reading from argument file %s", filename); } } else { expanded->push_back(arg); } return expanded; } // Pre-processes an argument list, expanding options @filename to read in the // content of the file and add it to the list of arguments. Stops expanding // arguments once it encounters "--". static unique_ptr> ExpandArguments(const vector &args) { unique_ptr> expanded(new vector()); expanded->reserve(args.size()); for (auto arg = args.begin(); arg != args.end(); ++arg) { if (strcmp(*arg, "--") != 0) { expanded = ExpandArgument(std::move(expanded), *arg); } else { expanded->insert(expanded->end(), arg, args.end()); break; } } return expanded; } void ParseOptions(int argc, char *argv[]) { vector args(argv, argv + argc); ParseCommandLine(ExpandArguments(args)); if (opt.args.empty()) { Usage(args.front(), "No command specified."); } if (opt.working_dir.empty()) { opt.working_dir = getcwd(nullptr, 0); } }