1 /*
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 
21 use anyhow::{bail, Context, Result};
22 use clap::{parser::ValuesRef, Arg, ArgAction};
23 use command_fds::{CommandFdExt, FdMapping};
24 use log::{debug, error};
25 use std::fs::OpenOptions;
26 use std::os::unix::fs::OpenOptionsExt;
27 use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
28 use 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.
33 type PseudoRawFd = RawFd;
34 
35 struct OwnedFdMapping {
36     owned_fd: OwnedFd,
37     target_fd: PseudoRawFd,
38 }
39 
40 impl OwnedFdMapping {
as_fd_mapping(&self) -> FdMapping41     fn as_fd_mapping(&self) -> FdMapping {
42         FdMapping { parent_fd: self.owned_fd.as_raw_fd(), child_fd: self.target_fd }
43     }
44 }
45 
46 struct Args {
47     ro_file_fds: Vec<OwnedFdMapping>,
48     rw_file_fds: Vec<OwnedFdMapping>,
49     dir_fds: Vec<OwnedFdMapping>,
50     cmdline_args: Vec<String>,
51 }
52 
parse_and_create_file_mapping<F>( values: Option<ValuesRef<'_, String>>, opener: F, ) -> Result<Vec<OwnedFdMapping>> where F: Fn(&str) -> Result<OwnedFd>,53 fn parse_and_create_file_mapping<F>(
54     values: Option<ValuesRef<'_, String>>,
55     opener: F,
56 ) -> Result<Vec<OwnedFdMapping>>
57 where
58     F: Fn(&str) -> Result<OwnedFd>,
59 {
60     if let Some(options) = values {
61         options
62             .map(|option| {
63                 // Example option: 10:/some/path
64                 let strs: Vec<&str> = option.split(':').collect();
65                 if strs.len() != 2 {
66                     bail!("Invalid option: {}", option);
67                 }
68                 let fd = strs[0].parse::<PseudoRawFd>().context("Invalid FD format")?;
69                 let path = strs[1];
70                 Ok(OwnedFdMapping { target_fd: fd, owned_fd: opener(path)? })
71             })
72             .collect::<Result<_>>()
73     } else {
74         Ok(Vec::new())
75     }
76 }
77 
78 #[rustfmt::skip]
args_command() -> clap::Command79 fn args_command() -> clap::Command {
80     clap::Command::new("open_then_run")
81         .arg(Arg::new("open-ro")
82              .long("open-ro")
83              .value_name("FD:PATH")
84              .help("Open <PATH> read-only to pass as fd <FD>")
85              .action(ArgAction::Append))
86         .arg(Arg::new("open-rw")
87              .long("open-rw")
88              .value_name("FD:PATH")
89              .help("Open/create <PATH> read-write to pass as fd <FD>")
90              .action(ArgAction::Append))
91         .arg(Arg::new("open-dir")
92              .long("open-dir")
93              .value_name("FD:DIR")
94              .help("Open <DIR> to pass as fd <FD>")
95              .action(ArgAction::Append))
96         .arg(Arg::new("args")
97              .help("Command line to execute with pre-opened FD inherited")
98              .last(true)
99              .required(true)
100              .num_args(0..))
101 }
102 
parse_args() -> Result<Args>103 fn parse_args() -> Result<Args> {
104     let matches = args_command().get_matches();
105 
106     let ro_file_fds = parse_and_create_file_mapping(matches.get_many("open-ro"), |path| {
107         Ok(OwnedFd::from(
108             OpenOptions::new()
109                 .read(true)
110                 .open(path)
111                 .with_context(|| format!("Open {} read-only", path))?,
112         ))
113     })?;
114 
115     let rw_file_fds = parse_and_create_file_mapping(matches.get_many("open-rw"), |path| {
116         Ok(OwnedFd::from(
117             OpenOptions::new()
118                 .read(true)
119                 .write(true)
120                 .create(true)
121                 .truncate(true)
122                 .open(path)
123                 .with_context(|| format!("Open {} read-write", path))?,
124         ))
125     })?;
126 
127     let dir_fds = parse_and_create_file_mapping(matches.get_many("open-dir"), |path| {
128         Ok(OwnedFd::from(
129             OpenOptions::new()
130                 .custom_flags(libc::O_DIRECTORY)
131                 .read(true) // O_DIRECTORY can only be opened with read
132                 .open(path)
133                 .with_context(|| format!("Open {} directory", path))?,
134         ))
135     })?;
136 
137     let cmdline_args: Vec<_> =
138         matches.get_many::<String>("args").unwrap().map(|s| s.to_string()).collect();
139 
140     Ok(Args { ro_file_fds, rw_file_fds, dir_fds, cmdline_args })
141 }
142 
try_main() -> Result<()>143 fn try_main() -> Result<()> {
144     let args = parse_args()?;
145 
146     let mut command = Command::new(&args.cmdline_args[0]);
147     command.args(&args.cmdline_args[1..]);
148 
149     // Set up FD mappings in the child process.
150     let mut fd_mappings = Vec::new();
151     fd_mappings.extend(args.ro_file_fds.iter().map(OwnedFdMapping::as_fd_mapping));
152     fd_mappings.extend(args.rw_file_fds.iter().map(OwnedFdMapping::as_fd_mapping));
153     fd_mappings.extend(args.dir_fds.iter().map(OwnedFdMapping::as_fd_mapping));
154     command.fd_mappings(fd_mappings)?;
155 
156     debug!("Spawning {:?}", command);
157     command.spawn()?;
158     Ok(())
159 }
160 
main()161 fn main() {
162     android_logger::init_once(
163         android_logger::Config::default()
164             .with_tag("open_then_run")
165             .with_max_level(log::LevelFilter::Debug),
166     );
167 
168     if let Err(e) = try_main() {
169         error!("Failed with {:?}", e);
170         std::process::exit(1);
171     }
172 }
173 
174 #[cfg(test)]
175 mod tests {
176     use super::*;
177 
178     #[test]
verify_command()179     fn verify_command() {
180         // Check that the command parsing has been configured in a valid way.
181         args_command().debug_assert();
182     }
183 }
184