Skip to content

Commit 2187934

Browse files
tevoineachkeita
authored andcommitted
Template creation command (microsoft#3531)
* Tasks are selectable * Almost there * It works * fmt * remove dead code * Remove unnecessary comments * Improve instructions * fix bug * Add some dummy values for paths
1 parent 86dc822 commit 2187934

14 files changed

+470
-20
lines changed

src/agent/onefuzz-task/src/local/cmd.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use super::{create_template, template};
45
#[cfg(any(target_os = "linux", target_os = "windows"))]
56
use crate::local::coverage;
67
use crate::local::{common::add_common_config, libfuzzer_fuzz, tui::TerminalUi};
78
use anyhow::{Context, Result};
9+
810
use clap::{Arg, ArgAction, Command};
911
use std::time::Duration;
1012
use std::{path::PathBuf, str::FromStr};
1113
use strum::IntoEnumIterator;
1214
use strum_macros::{EnumIter, EnumString, IntoStaticStr};
1315
use tokio::{select, time::timeout};
14-
15-
use super::template;
16-
1716
#[derive(Debug, PartialEq, Eq, EnumString, IntoStaticStr, EnumIter)]
1817
#[strum(serialize_all = "kebab-case")]
1918
enum Commands {
2019
#[cfg(any(target_os = "linux", target_os = "windows"))]
2120
Coverage,
2221
LibfuzzerFuzz,
2322
Template,
23+
CreateTemplate,
2424
}
2525

2626
const TIMEOUT: &str = "timeout";
@@ -43,7 +43,7 @@ pub async fn run(args: clap::ArgMatches) -> Result<()> {
4343

4444
let sub_args = sub_args.clone();
4545

46-
let terminal = if start_ui {
46+
let terminal = if start_ui && command != Commands::CreateTemplate {
4747
Some(TerminalUi::init()?)
4848
} else {
4949
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
@@ -62,6 +62,7 @@ pub async fn run(args: clap::ArgMatches) -> Result<()> {
6262

6363
template::launch(config, event_sender).await
6464
}
65+
Commands::CreateTemplate => create_template::run(),
6566
}
6667
});
6768

@@ -116,6 +117,7 @@ pub fn args(name: &'static str) -> Command {
116117
.args(vec![Arg::new("config")
117118
.value_parser(value_parser!(std::path::PathBuf))
118119
.required(true)]),
120+
Commands::CreateTemplate => create_template::args(subcommand.into()),
119121
};
120122

121123
cmd = if add_common {

src/agent/onefuzz-task/src/local/coverage.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,20 @@ pub struct Coverage {
148148
}
149149

150150
#[async_trait]
151-
impl Template for Coverage {
151+
impl Template<Coverage> for Coverage {
152+
fn example_values() -> Coverage {
153+
Coverage {
154+
target_exe: PathBuf::from("path_to_your_exe"),
155+
target_env: HashMap::new(),
156+
target_options: vec![],
157+
target_timeout: None,
158+
module_allowlist: None,
159+
source_allowlist: None,
160+
input_queue: Some(PathBuf::from("path_to_your_inputs")),
161+
readonly_inputs: vec![PathBuf::from("path_to_readonly_inputs")],
162+
coverage: PathBuf::from("path_to_where_you_want_coverage_to_be_output"),
163+
}
164+
}
152165
async fn run(&self, context: &RunContext) -> Result<()> {
153166
let ri: Result<Vec<SyncedDir>> = self
154167
.readonly_inputs
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
use crate::local::template::CommonProperties;
2+
3+
use super::template::{TaskConfig, TaskConfigDiscriminants, TaskGroup};
4+
use anyhow::Result;
5+
use clap::Command;
6+
use std::str::FromStr;
7+
use std::{
8+
io,
9+
path::{Path, PathBuf},
10+
};
11+
12+
use strum::VariantNames;
13+
14+
use crate::local::{
15+
coverage::Coverage, generic_analysis::Analysis, generic_crash_report::CrashReport,
16+
generic_generator::Generator, libfuzzer::LibFuzzer,
17+
libfuzzer_crash_report::LibfuzzerCrashReport, libfuzzer_merge::LibfuzzerMerge,
18+
libfuzzer_regression::LibfuzzerRegression, libfuzzer_test_input::LibfuzzerTestInput,
19+
template::Template, test_input::TestInput,
20+
};
21+
22+
use crossterm::{
23+
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
24+
execute,
25+
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
26+
};
27+
use tui::{prelude::*, widgets::*};
28+
29+
pub fn args(name: &'static str) -> Command {
30+
Command::new(name).about("interactively create a template")
31+
}
32+
33+
pub fn run() -> Result<()> {
34+
// setup terminal
35+
enable_raw_mode()?;
36+
let mut stdout = io::stdout();
37+
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
38+
let backend = CrosstermBackend::new(stdout);
39+
let mut terminal = Terminal::new(backend)?;
40+
41+
// create app and run it
42+
let app = App::new();
43+
let res = run_app(&mut terminal, app);
44+
45+
// restore terminal
46+
disable_raw_mode()?;
47+
execute!(
48+
terminal.backend_mut(),
49+
LeaveAlternateScreen,
50+
DisableMouseCapture
51+
)?;
52+
terminal.show_cursor()?;
53+
54+
match res {
55+
Ok(None) => { /* user quit, do nothing */ }
56+
Ok(Some(path)) => match path.canonicalize() {
57+
Ok(canonical_path) => println!("Wrote the template to: {:?}", canonical_path),
58+
_ => println!("Wrote the template to: {:?}", path),
59+
},
60+
Err(e) => println!("Failed to write template due to {}", e),
61+
}
62+
63+
Ok(())
64+
}
65+
66+
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> Result<Option<PathBuf>> {
67+
loop {
68+
terminal.draw(|f| ui(f, &mut app))?;
69+
if let Event::Key(key) = event::read()? {
70+
if key.kind == KeyEventKind::Press {
71+
match key.code {
72+
KeyCode::Char('q') => return Ok(None),
73+
KeyCode::Char(' ') => app.items.toggle(),
74+
KeyCode::Down => app.items.next(),
75+
KeyCode::Up => app.items.previous(),
76+
KeyCode::Enter => {
77+
return match generate_template(app.items.items) {
78+
Ok(p) => Ok(Some(p)),
79+
Err(e) => Err(e),
80+
}
81+
}
82+
_ => {}
83+
}
84+
}
85+
}
86+
}
87+
}
88+
89+
fn generate_template(items: Vec<ListElement>) -> Result<PathBuf> {
90+
let tasks: Vec<TaskConfig> = items
91+
.iter()
92+
.filter(|item| item.is_included)
93+
.filter_map(|list_element| {
94+
match TaskConfigDiscriminants::from_str(list_element.task_type) {
95+
Err(e) => {
96+
error!(
97+
"Failed to match task config {:?} - {}",
98+
list_element.task_type, e
99+
);
100+
None
101+
}
102+
Ok(t) => match t {
103+
TaskConfigDiscriminants::LibFuzzer => {
104+
Some(TaskConfig::LibFuzzer(LibFuzzer::example_values()))
105+
}
106+
TaskConfigDiscriminants::Analysis => {
107+
Some(TaskConfig::Analysis(Analysis::example_values()))
108+
}
109+
TaskConfigDiscriminants::Coverage => {
110+
Some(TaskConfig::Coverage(Coverage::example_values()))
111+
}
112+
TaskConfigDiscriminants::CrashReport => {
113+
Some(TaskConfig::CrashReport(CrashReport::example_values()))
114+
}
115+
TaskConfigDiscriminants::Generator => {
116+
Some(TaskConfig::Generator(Generator::example_values()))
117+
}
118+
TaskConfigDiscriminants::LibfuzzerCrashReport => Some(
119+
TaskConfig::LibfuzzerCrashReport(LibfuzzerCrashReport::example_values()),
120+
),
121+
TaskConfigDiscriminants::LibfuzzerMerge => {
122+
Some(TaskConfig::LibfuzzerMerge(LibfuzzerMerge::example_values()))
123+
}
124+
TaskConfigDiscriminants::LibfuzzerRegression => Some(
125+
TaskConfig::LibfuzzerRegression(LibfuzzerRegression::example_values()),
126+
),
127+
TaskConfigDiscriminants::LibfuzzerTestInput => Some(
128+
TaskConfig::LibfuzzerTestInput(LibfuzzerTestInput::example_values()),
129+
),
130+
TaskConfigDiscriminants::TestInput => {
131+
Some(TaskConfig::TestInput(TestInput::example_values()))
132+
}
133+
TaskConfigDiscriminants::Radamsa => Some(TaskConfig::Radamsa),
134+
},
135+
}
136+
})
137+
.collect();
138+
139+
let definition = TaskGroup {
140+
common: CommonProperties {
141+
setup_dir: None,
142+
extra_setup_dir: None,
143+
extra_dir: None,
144+
create_job_dir: false,
145+
},
146+
tasks,
147+
};
148+
149+
let filename = "template";
150+
let mut filepath = format!("./{}.yaml", filename);
151+
let mut output_file = Path::new(&filepath);
152+
let mut counter = 0;
153+
while output_file.exists() {
154+
filepath = format!("./{}-{}.yaml", filename, counter);
155+
output_file = Path::new(&filepath);
156+
counter += 1;
157+
}
158+
159+
std::fs::write(output_file, serde_yaml::to_string(&definition)?)?;
160+
161+
Ok(output_file.into())
162+
}
163+
164+
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
165+
let areas = Layout::default()
166+
.direction(Direction::Vertical)
167+
.constraints([Constraint::Percentage(100)])
168+
.split(f.size());
169+
// Iterate through all elements in the `items` app and append some debug text to it.
170+
let items: Vec<ListItem> = app
171+
.items
172+
.items
173+
.iter()
174+
.map(|list_element| {
175+
let title = if list_element.is_included {
176+
format!("✅ {}", list_element.task_type)
177+
} else {
178+
list_element.task_type.to_string()
179+
};
180+
ListItem::new(title).style(Style::default().fg(Color::Black).bg(Color::White))
181+
})
182+
.collect();
183+
184+
// Create a List from all list items and highlight the currently selected one
185+
let items = List::new(items)
186+
.block(
187+
Block::default()
188+
.borders(Borders::ALL)
189+
.title("Select which tasks you want to include in the template. Use ⬆/⬇ to navigate and <space> to select. Press <enter> when you're done."),
190+
)
191+
.highlight_style(
192+
Style::default()
193+
.bg(Color::LightGreen)
194+
.add_modifier(Modifier::BOLD),
195+
)
196+
.highlight_symbol(">> ");
197+
198+
// We can now render the item list
199+
f.render_stateful_widget(items, areas[0], &mut app.items.state);
200+
}
201+
202+
struct ListElement<'a> {
203+
pub task_type: &'a str,
204+
pub is_included: bool,
205+
}
206+
207+
pub trait Toggle {
208+
fn toggle(&mut self) {}
209+
}
210+
211+
impl<'a> Toggle for ListElement<'a> {
212+
fn toggle(&mut self) {
213+
self.is_included = !self.is_included
214+
}
215+
}
216+
217+
struct App<'a> {
218+
items: StatefulList<ListElement<'a>>,
219+
}
220+
221+
impl<'a> App<'a> {
222+
fn new() -> App<'a> {
223+
App {
224+
items: StatefulList::with_items(
225+
TaskConfig::VARIANTS
226+
.iter()
227+
.map(|name| ListElement {
228+
task_type: name,
229+
is_included: false,
230+
})
231+
.collect(),
232+
),
233+
}
234+
}
235+
}
236+
237+
struct StatefulList<ListElement> {
238+
state: ListState,
239+
items: Vec<ListElement>,
240+
}
241+
242+
impl<T: Toggle> StatefulList<T> {
243+
fn with_items(items: Vec<T>) -> StatefulList<T> {
244+
StatefulList {
245+
state: ListState::default(),
246+
items,
247+
}
248+
}
249+
250+
fn next(&mut self) {
251+
let i = match self.state.selected() {
252+
Some(i) => {
253+
if self.items.first().is_some() {
254+
(i + 1) % self.items.len()
255+
} else {
256+
0
257+
}
258+
}
259+
None => 0,
260+
};
261+
self.state.select(Some(i));
262+
}
263+
264+
fn previous(&mut self) {
265+
let i = match self.state.selected() {
266+
Some(i) => {
267+
if i == 0 {
268+
self.items.len() - 1
269+
} else {
270+
i - 1
271+
}
272+
}
273+
None => 0,
274+
};
275+
self.state.select(Some(i));
276+
}
277+
278+
fn toggle(&mut self) {
279+
if let Some(index) = self.state.selected() {
280+
if let Some(element) = self.items.get_mut(index) {
281+
element.toggle()
282+
}
283+
}
284+
}
285+
}

src/agent/onefuzz-task/src/local/generic_analysis.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,23 @@ pub struct Analysis {
2727
}
2828

2929
#[async_trait]
30-
impl Template for Analysis {
30+
impl Template<Analysis> for Analysis {
31+
fn example_values() -> Analysis {
32+
Analysis {
33+
analyzer_exe: String::new(),
34+
analyzer_options: vec![],
35+
analyzer_env: HashMap::new(),
36+
target_exe: PathBuf::from("path_to_your_exe"),
37+
target_options: vec![],
38+
input_queue: Some(PathBuf::from("path_to_your_inputs")),
39+
crashes: Some(PathBuf::from("path_where_crashes_written")),
40+
analysis: PathBuf::new(),
41+
tools: None,
42+
reports: Some(PathBuf::from("path_where_reports_written")),
43+
unique_reports: Some(PathBuf::from("path_where_reports_written")),
44+
no_repro: Some(PathBuf::from("path_where_no_repro_reports_written")),
45+
}
46+
}
3147
async fn run(&self, context: &RunContext) -> Result<()> {
3248
let input_q = if let Some(w) = &self.input_queue {
3349
Some(context.monitor_dir(w).await?)

0 commit comments

Comments
 (0)