blob: a540f9d5379be47e0374124169539a68d9e28d8e [file] [log] [blame]
Victor Hsiehae67d3b2021-10-19 12:59:42 -07001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This is a test helper program that opens files and/or directories, then passes the file
18//! descriptors to the specified command. When passing the file descriptors, they are mapped to the
19//! specified numbers in the child process.
20
21use anyhow::{bail, Context, Result};
22use clap::{App, Arg, Values};
23use command_fds::{CommandFdExt, FdMapping};
24use log::{debug, error};
Victor Hsieh0e8976f2021-11-05 16:54:24 +000025use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
Victor Hsiehae67d3b2021-10-19 12:59:42 -070026use std::fs::{File, OpenOptions};
Victor Hsieh0e8976f2021-11-05 16:54:24 +000027use std::os::unix::io::{AsRawFd, RawFd};
Victor Hsiehae67d3b2021-10-19 12:59:42 -070028use std::process::Command;
29
30// `PseudoRawFd` is just an integer and not necessarily backed by a real FD. It is used to denote
31// the expecting FD number, when trying to set up FD mapping in the child process. The intention
32// with this alias is to improve readability by distinguishing from actual RawFd.
33type PseudoRawFd = RawFd;
34
Victor Hsieh0e8976f2021-11-05 16:54:24 +000035struct FileMapping<T: AsRawFd> {
36 file: T,
Victor Hsiehae67d3b2021-10-19 12:59:42 -070037 target_fd: PseudoRawFd,
38}
39
Victor Hsieh0e8976f2021-11-05 16:54:24 +000040impl<T: AsRawFd> FileMapping<T> {
Victor Hsiehae67d3b2021-10-19 12:59:42 -070041 fn as_fd_mapping(&self) -> FdMapping {
42 FdMapping { parent_fd: self.file.as_raw_fd(), child_fd: self.target_fd }
43 }
44}
45
46struct Args {
Victor Hsieh0e8976f2021-11-05 16:54:24 +000047 ro_files: Vec<FileMapping<File>>,
48 rw_files: Vec<FileMapping<File>>,
49 dir_files: Vec<FileMapping<Dir>>,
Victor Hsiehae67d3b2021-10-19 12:59:42 -070050 cmdline_args: Vec<String>,
51}
52
Victor Hsieh0e8976f2021-11-05 16:54:24 +000053fn parse_and_create_file_mapping<F, T>(
Victor Hsiehae67d3b2021-10-19 12:59:42 -070054 values: Option<Values<'_>>,
55 opener: F,
Victor Hsieh0e8976f2021-11-05 16:54:24 +000056) -> Result<Vec<FileMapping<T>>>
Victor Hsiehae67d3b2021-10-19 12:59:42 -070057where
Victor Hsieh0e8976f2021-11-05 16:54:24 +000058 F: Fn(&str) -> Result<T>,
59 T: AsRawFd,
Victor Hsiehae67d3b2021-10-19 12:59:42 -070060{
61 if let Some(options) = values {
62 options
63 .map(|option| {
64 // Example option: 10:/some/path
65 let strs: Vec<&str> = option.split(':').collect();
66 if strs.len() != 2 {
67 bail!("Invalid option: {}", option);
68 }
69 let fd = strs[0].parse::<PseudoRawFd>().context("Invalid FD format")?;
70 let path = strs[1];
71 Ok(FileMapping { target_fd: fd, file: opener(path)? })
72 })
73 .collect::<Result<_>>()
74 } else {
75 Ok(Vec::new())
76 }
77}
78
79fn parse_args() -> Result<Args> {
80 #[rustfmt::skip]
81 let matches = App::new("open_then_run")
82 .arg(Arg::with_name("open-ro")
83 .long("open-ro")
84 .value_name("FD:PATH")
85 .help("Open <PATH> read-only to pass as fd <FD>")
86 .multiple(true)
87 .number_of_values(1))
88 .arg(Arg::with_name("open-rw")
89 .long("open-rw")
90 .value_name("FD:PATH")
91 .help("Open/create <PATH> read-write to pass as fd <FD>")
92 .multiple(true)
93 .number_of_values(1))
94 .arg(Arg::with_name("open-dir")
95 .long("open-dir")
96 .value_name("FD:DIR")
97 .help("Open <DIR> to pass as fd <FD>")
98 .multiple(true)
99 .number_of_values(1))
100 .arg(Arg::with_name("args")
101 .help("Command line to execute with pre-opened FD inherited")
102 .last(true)
103 .required(true)
104 .multiple(true))
105 .get_matches();
106
107 let ro_files = parse_and_create_file_mapping(matches.values_of("open-ro"), |path| {
108 OpenOptions::new().read(true).open(path).with_context(|| format!("Open {} read-only", path))
109 })?;
110
111 let rw_files = parse_and_create_file_mapping(matches.values_of("open-rw"), |path| {
112 OpenOptions::new()
113 .read(true)
114 .write(true)
115 .create(true)
116 .open(path)
117 .with_context(|| format!("Open {} read-write", path))
118 })?;
119
120 let dir_files = parse_and_create_file_mapping(matches.values_of("open-dir"), |path| {
Victor Hsieh0e8976f2021-11-05 16:54:24 +0000121 Dir::open(path, OFlag::O_DIRECTORY | OFlag::O_RDONLY, Mode::S_IRWXU)
122 .with_context(|| format!("Open {} directory", path))
Victor Hsiehae67d3b2021-10-19 12:59:42 -0700123 })?;
124
125 let cmdline_args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
126
127 Ok(Args { ro_files, rw_files, dir_files, cmdline_args })
128}
129
130fn try_main() -> Result<()> {
131 let args = parse_args()?;
132
133 let mut command = Command::new(&args.cmdline_args[0]);
134 command.args(&args.cmdline_args[1..]);
135
136 // Set up FD mappings in the child process.
137 let mut fd_mappings = Vec::new();
138 fd_mappings.extend(args.ro_files.iter().map(FileMapping::as_fd_mapping));
139 fd_mappings.extend(args.rw_files.iter().map(FileMapping::as_fd_mapping));
140 fd_mappings.extend(args.dir_files.iter().map(FileMapping::as_fd_mapping));
141 command.fd_mappings(fd_mappings)?;
142
143 debug!("Spawning {:?}", command);
144 command.spawn()?;
145 Ok(())
146}
147
148fn main() {
149 android_logger::init_once(
150 android_logger::Config::default()
151 .with_tag("open_then_run")
152 .with_min_level(log::Level::Debug),
153 );
154
155 if let Err(e) = try_main() {
156 error!("Failed with {:?}", e);
157 std::process::exit(1);
158 }
159}