blob: 3e6ae71a0817986c2efa2ee7c79f10f2fe09b244 [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 Hsiehae67d3b2021-10-19 12:59:42 -070025use std::fs::{File, OpenOptions};
Victor Hsieh9646ebf2021-10-28 12:55:37 -070026use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd, io::RawFd};
Victor Hsiehae67d3b2021-10-19 12:59:42 -070027use std::process::Command;
28
29// `PseudoRawFd` is just an integer and not necessarily backed by a real FD. It is used to denote
30// the expecting FD number, when trying to set up FD mapping in the child process. The intention
31// with this alias is to improve readability by distinguishing from actual RawFd.
32type PseudoRawFd = RawFd;
33
Victor Hsieh9646ebf2021-10-28 12:55:37 -070034struct FileMapping {
35 file: File,
Victor Hsiehae67d3b2021-10-19 12:59:42 -070036 target_fd: PseudoRawFd,
37}
38
Victor Hsieh9646ebf2021-10-28 12:55:37 -070039impl FileMapping {
Victor Hsiehae67d3b2021-10-19 12:59:42 -070040 fn as_fd_mapping(&self) -> FdMapping {
41 FdMapping { parent_fd: self.file.as_raw_fd(), child_fd: self.target_fd }
42 }
43}
44
45struct Args {
Victor Hsieh9646ebf2021-10-28 12:55:37 -070046 ro_files: Vec<FileMapping>,
47 rw_files: Vec<FileMapping>,
48 dir_files: Vec<FileMapping>,
Victor Hsiehae67d3b2021-10-19 12:59:42 -070049 cmdline_args: Vec<String>,
50}
51
Victor Hsieh9646ebf2021-10-28 12:55:37 -070052fn parse_and_create_file_mapping<F>(
Victor Hsiehae67d3b2021-10-19 12:59:42 -070053 values: Option<Values<'_>>,
54 opener: F,
Victor Hsieh9646ebf2021-10-28 12:55:37 -070055) -> Result<Vec<FileMapping>>
Victor Hsiehae67d3b2021-10-19 12:59:42 -070056where
Victor Hsieh9646ebf2021-10-28 12:55:37 -070057 F: Fn(&str) -> Result<File>,
Victor Hsiehae67d3b2021-10-19 12:59:42 -070058{
59 if let Some(options) = values {
60 options
61 .map(|option| {
62 // Example option: 10:/some/path
63 let strs: Vec<&str> = option.split(':').collect();
64 if strs.len() != 2 {
65 bail!("Invalid option: {}", option);
66 }
67 let fd = strs[0].parse::<PseudoRawFd>().context("Invalid FD format")?;
68 let path = strs[1];
69 Ok(FileMapping { target_fd: fd, file: opener(path)? })
70 })
71 .collect::<Result<_>>()
72 } else {
73 Ok(Vec::new())
74 }
75}
76
77fn parse_args() -> Result<Args> {
78 #[rustfmt::skip]
79 let matches = App::new("open_then_run")
80 .arg(Arg::with_name("open-ro")
81 .long("open-ro")
82 .value_name("FD:PATH")
83 .help("Open <PATH> read-only to pass as fd <FD>")
84 .multiple(true)
85 .number_of_values(1))
86 .arg(Arg::with_name("open-rw")
87 .long("open-rw")
88 .value_name("FD:PATH")
89 .help("Open/create <PATH> read-write to pass as fd <FD>")
90 .multiple(true)
91 .number_of_values(1))
92 .arg(Arg::with_name("open-dir")
93 .long("open-dir")
94 .value_name("FD:DIR")
95 .help("Open <DIR> to pass as fd <FD>")
96 .multiple(true)
97 .number_of_values(1))
98 .arg(Arg::with_name("args")
99 .help("Command line to execute with pre-opened FD inherited")
100 .last(true)
101 .required(true)
102 .multiple(true))
103 .get_matches();
104
105 let ro_files = parse_and_create_file_mapping(matches.values_of("open-ro"), |path| {
106 OpenOptions::new().read(true).open(path).with_context(|| format!("Open {} read-only", path))
107 })?;
108
109 let rw_files = parse_and_create_file_mapping(matches.values_of("open-rw"), |path| {
110 OpenOptions::new()
111 .read(true)
112 .write(true)
113 .create(true)
114 .open(path)
115 .with_context(|| format!("Open {} read-write", path))
116 })?;
117
118 let dir_files = parse_and_create_file_mapping(matches.values_of("open-dir"), |path| {
Victor Hsieh9646ebf2021-10-28 12:55:37 -0700119 // The returned FD represents a path (that's supposed to be a directory), and is not really
120 // a file. It's better to use std::os::unix::io::OwnedFd but it's currently experimental.
121 // Ideally, all FDs opened by this program should be `OwnedFd` since we are only opening
122 // them for the provided program, and are not supposed to do anything else.
123 OpenOptions::new()
124 .custom_flags(libc::O_PATH | libc::O_DIRECTORY)
125 .open(path)
Victor Hsiehae67d3b2021-10-19 12:59:42 -0700126 .with_context(|| format!("Open {} directory", path))
127 })?;
128
129 let cmdline_args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
130
131 Ok(Args { ro_files, rw_files, dir_files, cmdline_args })
132}
133
134fn try_main() -> Result<()> {
135 let args = parse_args()?;
136
137 let mut command = Command::new(&args.cmdline_args[0]);
138 command.args(&args.cmdline_args[1..]);
139
140 // Set up FD mappings in the child process.
141 let mut fd_mappings = Vec::new();
142 fd_mappings.extend(args.ro_files.iter().map(FileMapping::as_fd_mapping));
143 fd_mappings.extend(args.rw_files.iter().map(FileMapping::as_fd_mapping));
144 fd_mappings.extend(args.dir_files.iter().map(FileMapping::as_fd_mapping));
145 command.fd_mappings(fd_mappings)?;
146
147 debug!("Spawning {:?}", command);
148 command.spawn()?;
149 Ok(())
150}
151
152fn main() {
153 android_logger::init_once(
154 android_logger::Config::default()
155 .with_tag("open_then_run")
156 .with_min_level(log::Level::Debug),
157 );
158
159 if let Err(e) = try_main() {
160 error!("Failed with {:?}", e);
161 std::process::exit(1);
162 }
163}