diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index 53dac216..a61f084f 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -3,13 +3,14 @@ use std::{io, path::Path}; use thiserror::Error; use crate::{ - generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE, + generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_KPROBE}, programs::{ define_link_wrapper, load_program, perf_attach::{PerfLinkIdInner, PerfLinkInner}, probe::{attach, ProbeKind}, - ProgramData, ProgramError, + FdLink, LinkError, ProgramData, ProgramError, }, + sys::bpf_link_get_info_by_fd, VerifierLogLevel, }; @@ -119,3 +120,32 @@ pub enum KProbeError { io_error: io::Error, }, } + +impl TryFrom for FdLink { + type Error = LinkError; + + fn try_from(value: KProbeLink) -> Result { + if let PerfLinkInner::FdLink(fd) = value.into_inner() { + Ok(fd) + } else { + Err(LinkError::InvalidLink) + } + } +} + +impl TryFrom for KProbeLink { + type Error = LinkError; + + fn try_from(fd_link: FdLink) -> Result { + let info = + bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError { + call: "BPF_OBJ_GET_INFO_BY_FD", + code: 0, + io_error, + })?; + if info.type_ == (bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI as u32) { + return Ok(KProbeLink::new(PerfLinkInner::FdLink(fd_link))); + } + Err(LinkError::InvalidLink) + } +} diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs index 5f5c188f..7b3055f4 100644 --- a/aya/src/programs/links.rs +++ b/aya/src/programs/links.rs @@ -17,6 +17,10 @@ use crate::{ sys::{bpf_get_object, bpf_pin_object, bpf_prog_detach}, }; +// for docs link +#[allow(unused)] +use crate::programs::cgroup_skb::CgroupSkb; + /// A Link. pub trait Link: std::fmt::Debug + 'static { /// Unique Id @@ -82,6 +86,30 @@ impl Drop for LinkMap { pub struct FdLinkId(pub(crate) RawFd); /// A file descriptor link. +/// +/// Fd links are returned directly when attaching some program types (for +/// instance [`CgroupSkb`]), or can be obtained by converting other link +/// types (see the `TryFrom` implementations). +/// +/// An important property of fd links is that they can be pinned. Pinning +/// can be used keep a link attached "in background" even after the program +/// that has created the link terminates. +/// +/// # Example +/// +///```no_run +/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; +/// use aya::{Bpf, programs::{links::FdLink, KProbe}}; +/// +/// let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?; +/// program.load()?; +/// let link_id = program.attach("try_to_wake_up", 0)?; +/// let link = program.take_link(link_id).unwrap(); +/// let fd_link: FdLink = link.try_into().unwrap(); +/// fd_link.pin("/sys/fs/bpf/intercept_wakeups_link").unwrap(); +/// +/// # Ok::<(), aya::BpfError>(()) +/// ``` #[derive(Debug)] pub struct FdLink { pub(crate) fd: RawFd, diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs index 3c245573..5de7eeef 100644 --- a/aya/src/programs/perf_event.rs +++ b/aya/src/programs/perf_event.rs @@ -6,6 +6,7 @@ pub use crate::generated::{ use crate::{ generated::{ + bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT, perf_type_id::{ PERF_TYPE_BREAKPOINT, PERF_TYPE_HARDWARE, PERF_TYPE_HW_CACHE, PERF_TYPE_RAW, @@ -16,9 +17,9 @@ use crate::{ links::define_link_wrapper, load_program, perf_attach, perf_attach::{PerfLinkIdInner, PerfLinkInner}, - ProgramData, ProgramError, + FdLink, LinkError, ProgramData, ProgramError, }, - sys::perf_event_open, + sys::{bpf_link_get_info_by_fd, perf_event_open}, }; /// The type of perf event @@ -189,6 +190,35 @@ impl PerfEvent { } } +impl TryFrom for FdLink { + type Error = LinkError; + + fn try_from(value: PerfEventLink) -> Result { + if let PerfLinkInner::FdLink(fd) = value.into_inner() { + Ok(fd) + } else { + Err(LinkError::InvalidLink) + } + } +} + +impl TryFrom for PerfEventLink { + type Error = LinkError; + + fn try_from(fd_link: FdLink) -> Result { + let info = + bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError { + call: "BPF_OBJ_GET_INFO_BY_FD", + code: 0, + io_error, + })?; + if info.type_ == (bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as u32) { + return Ok(PerfEventLink::new(PerfLinkInner::FdLink(fd_link))); + } + Err(LinkError::InvalidLink) + } +} + define_link_wrapper!( /// The link used by [PerfEvent] programs. PerfEventLink, diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs index d67c6a41..b23594b7 100644 --- a/aya/src/programs/trace_point.rs +++ b/aya/src/programs/trace_point.rs @@ -3,14 +3,14 @@ use std::{fs, io, path::Path}; use thiserror::Error; use crate::{ - generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT, + generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT}, programs::{ define_link_wrapper, load_program, perf_attach::{perf_attach, PerfLinkIdInner, PerfLinkInner}, utils::find_tracefs_path, - ProgramData, ProgramError, + FdLink, LinkError, ProgramData, ProgramError, }, - sys::perf_event_open_trace_point, + sys::{bpf_link_get_info_by_fd, perf_event_open_trace_point}, }; /// The type returned when attaching a [`TracePoint`] fails. @@ -116,6 +116,35 @@ define_link_wrapper!( PerfLinkIdInner ); +impl TryFrom for FdLink { + type Error = LinkError; + + fn try_from(value: TracePointLink) -> Result { + if let PerfLinkInner::FdLink(fd) = value.into_inner() { + Ok(fd) + } else { + Err(LinkError::InvalidLink) + } + } +} + +impl TryFrom for TracePointLink { + type Error = LinkError; + + fn try_from(fd_link: FdLink) -> Result { + let info = + bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError { + call: "BPF_OBJ_GET_INFO_BY_FD", + code: 0, + io_error, + })?; + if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) { + return Ok(TracePointLink::new(PerfLinkInner::FdLink(fd_link))); + } + Err(LinkError::InvalidLink) + } +} + pub(crate) fn read_sys_fs_trace_point_id( tracefs: &Path, category: &str, diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index b632407e..bab82d4b 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -14,13 +14,14 @@ use std::{ use thiserror::Error; use crate::{ - generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE, + generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_KPROBE}, programs::{ define_link_wrapper, load_program, perf_attach::{PerfLinkIdInner, PerfLinkInner}, probe::{attach, ProbeKind}, - ProgramData, ProgramError, + FdLink, LinkError, ProgramData, ProgramError, }, + sys::bpf_link_get_info_by_fd, VerifierLogLevel, }; @@ -160,6 +161,35 @@ define_link_wrapper!( PerfLinkIdInner ); +impl TryFrom for FdLink { + type Error = LinkError; + + fn try_from(value: UProbeLink) -> Result { + if let PerfLinkInner::FdLink(fd) = value.into_inner() { + Ok(fd) + } else { + Err(LinkError::InvalidLink) + } + } +} + +impl TryFrom for UProbeLink { + type Error = LinkError; + + fn try_from(fd_link: FdLink) -> Result { + let info = + bpf_link_get_info_by_fd(fd_link.fd).map_err(|io_error| LinkError::SyscallError { + call: "BPF_OBJ_GET_INFO_BY_FD", + code: 0, + io_error, + })?; + if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) { + return Ok(UProbeLink::new(PerfLinkInner::FdLink(fd_link))); + } + Err(LinkError::InvalidLink) + } +} + /// The type returned when attaching an [`UProbe`] fails. #[derive(Debug, Error)] pub enum UProbeError { diff --git a/test/integration-ebpf/src/test.rs b/test/integration-ebpf/src/test.rs index 2c4efc91..187a3926 100644 --- a/test/integration-ebpf/src/test.rs +++ b/test/integration-ebpf/src/test.rs @@ -3,11 +3,11 @@ use aya_bpf::{ bindings::xdp_action, - macros::{kprobe, xdp}, - programs::{ProbeContext, XdpContext}, + macros::{kprobe, tracepoint, uprobe, xdp}, + programs::{ProbeContext, TracePointContext, XdpContext}, }; -#[xdp(name = "test_unload_xdp")] +#[xdp(name = "test_xdp")] pub fn pass(ctx: XdpContext) -> u32 { match unsafe { try_pass(ctx) } { Ok(ret) => ret, @@ -20,8 +20,17 @@ unsafe fn try_pass(_ctx: XdpContext) -> Result { } #[kprobe] -// truncated name to match bpftool output -pub fn test_unload_kpr(_ctx: ProbeContext) -> u32 { +pub fn test_kprobe(_ctx: ProbeContext) -> u32 { + 0 +} + +#[tracepoint] +pub fn test_tracepoint(_ctx: TracePointContext) -> u32 { + 0 +} + +#[uprobe] +pub fn test_uprobe(_ctx: ProbeContext) -> u32 { 0 } diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index ede471e2..8958ae59 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -1,10 +1,10 @@ -use std::{convert::TryInto as _, thread, time}; +use std::{convert::TryInto as _, process::Command, thread, time}; use aya::{ maps::Array, programs::{ links::{FdLink, PinnedLink}, - loaded_programs, KProbe, TracePoint, Xdp, XdpFlags, + loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags, }, util::KernelVersion, Bpf, @@ -50,10 +50,52 @@ fn multiple_btf_maps() { assert_eq!(val_2, 42); } +fn is_linked(prog_id: &u32) -> bool { + let output = Command::new("bpftool").args(["link"]).output(); + let output = output.expect("Failed to run 'bpftool link'"); + let stdout = String::from_utf8(output.stdout).unwrap(); + stdout.contains(&prog_id.to_string()) +} + +macro_rules! assert_loaded_and_linked { + ($name:literal, $loaded:expr) => { + for i in 0..(MAX_RETRIES + 1) { + // Ignore race failures which can happen when the tests delete a + // program in the middle of a `loaded_programs()` call. + let id = loaded_programs() + .filter_map(|prog| prog.ok()) + .find(|prog| prog.name() == $name.as_bytes()) + .map(|prog| Some(prog.id())); + let mut linked = false; + if let Some(prog_id) = id { + linked = is_linked(&prog_id.unwrap()); + if linked == $loaded { + break; + } + } + + if i == MAX_RETRIES { + panic!( + "Expected (loaded/linked: {}) but found (id: {}, linked: {}", + $loaded, + id.is_some(), + linked + ); + } + thread::sleep(time::Duration::from_millis(RETRY_DURATION_MS)); + } + }; +} + macro_rules! assert_loaded { ($name:literal, $loaded:expr) => { for i in 0..(MAX_RETRIES + 1) { - let state = loaded_programs().any(|prog| prog.unwrap().name() == $name.as_bytes()); + // Ignore race failures which can happen when the tests delete a + // program in the middle of a `loaded_programs()` call. + let state = loaded_programs() + .filter_map(|prog| prog.ok()) + .any(|prog| prog.name() == $name.as_bytes()); + if state == $loaded { break; } @@ -68,59 +110,109 @@ macro_rules! assert_loaded { #[test] fn unload_xdp() { let mut bpf = Bpf::load(crate::TEST).unwrap(); - let prog: &mut Xdp = bpf - .program_mut("test_unload_xdp") - .unwrap() - .try_into() - .unwrap(); + let prog: &mut Xdp = bpf.program_mut("test_xdp").unwrap().try_into().unwrap(); prog.load().unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded!("test_xdp", true); let link = prog.attach("lo", XdpFlags::default()).unwrap(); { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded_and_linked!("test_xdp", true); }; - assert_loaded!("test_unload_xdp", false); + assert_loaded!("test_xdp", false); prog.load().unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded!("test_xdp", true); prog.attach("lo", XdpFlags::default()).unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded!("test_xdp", true); prog.unload().unwrap(); - assert_loaded!("test_unload_xdp", false); + assert_loaded!("test_xdp", false); } #[test] fn unload_kprobe() { let mut bpf = Bpf::load(crate::TEST).unwrap(); - let prog: &mut KProbe = bpf - .program_mut("test_unload_kpr") + let prog: &mut KProbe = bpf.program_mut("test_kprobe").unwrap().try_into().unwrap(); + prog.load().unwrap(); + assert_loaded!("test_kprobe", true); + let link = prog.attach("try_to_wake_up", 0).unwrap(); + { + let _link_owned = prog.take_link(link).unwrap(); + prog.unload().unwrap(); + assert_loaded_and_linked!("test_kprobe", true); + }; + + assert_loaded!("test_kprobe", false); + prog.load().unwrap(); + + assert_loaded!("test_kprobe", true); + prog.attach("try_to_wake_up", 0).unwrap(); + + assert_loaded!("test_kprobe", true); + prog.unload().unwrap(); + + assert_loaded!("test_kprobe", false); +} + +#[test] +fn basic_tracepoint() { + let mut bpf = Bpf::load(crate::TEST).unwrap(); + let prog: &mut TracePoint = bpf + .program_mut("test_tracepoint") .unwrap() .try_into() .unwrap(); + prog.load().unwrap(); - assert_loaded!("test_unload_kpr", true); - let link = prog.attach("try_to_wake_up", 0).unwrap(); + assert_loaded!("test_tracepoint", true); + let link = prog.attach("syscalls", "sys_enter_kill").unwrap(); + { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); - assert_loaded!("test_unload_kpr", true); + assert_loaded_and_linked!("test_tracepoint", true); }; - assert_loaded!("test_unload_kpr", false); + assert_loaded!("test_tracepoint", false); prog.load().unwrap(); - assert_loaded!("test_unload_kpr", true); - prog.attach("try_to_wake_up", 0).unwrap(); + assert_loaded!("test_tracepoint", true); + prog.attach("syscalls", "sys_enter_kill").unwrap(); - assert_loaded!("test_unload_kpr", true); + assert_loaded!("test_tracepoint", true); prog.unload().unwrap(); - assert_loaded!("test_unload_kpr", false); + assert_loaded!("test_tracepoint", false); +} + +#[test] +fn basic_uprobe() { + let mut bpf = Bpf::load(crate::TEST).unwrap(); + let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap(); + + prog.load().unwrap(); + assert_loaded!("test_uprobe", true); + let link = prog.attach(Some("sleep"), 0, "libc", None).unwrap(); + + { + let _link_owned = prog.take_link(link).unwrap(); + prog.unload().unwrap(); + assert_loaded_and_linked!("test_uprobe", true); + }; + + assert_loaded!("test_uprobe", false); + prog.load().unwrap(); + + assert_loaded!("test_uprobe", true); + prog.attach(Some("sleep"), 0, "libc", None).unwrap(); + + assert_loaded!("test_uprobe", true); + prog.unload().unwrap(); + + assert_loaded!("test_uprobe", false); } #[test] @@ -132,30 +224,26 @@ fn pin_link() { } let mut bpf = Bpf::load(crate::TEST).unwrap(); - let prog: &mut Xdp = bpf - .program_mut("test_unload_xdp") - .unwrap() - .try_into() - .unwrap(); + let prog: &mut Xdp = bpf.program_mut("test_xdp").unwrap().try_into().unwrap(); prog.load().unwrap(); let link_id = prog.attach("lo", XdpFlags::default()).unwrap(); let link = prog.take_link(link_id).unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded!("test_xdp", true); let fd_link: FdLink = link.try_into().unwrap(); let pinned = fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap(); // because of the pin, the program is still attached prog.unload().unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded!("test_xdp", true); // delete the pin, but the program is still attached let new_link = pinned.unpin().unwrap(); - assert_loaded!("test_unload_xdp", true); + assert_loaded!("test_xdp", true); // finally when new_link is dropped we're detached drop(new_link); - assert_loaded!("test_unload_xdp", false); + assert_loaded!("test_xdp", false); } #[test] @@ -198,7 +286,7 @@ fn pin_lifecycle() { } // should still be loaded since link was pinned - assert_loaded!("pass", true); + assert_loaded_and_linked!("pass", true); // 4. Load a new version of the program, unpin link, and atomically replace old program { @@ -217,3 +305,173 @@ fn pin_lifecycle() { // program should be unloaded assert_loaded!("pass", false); } + +#[test] +fn pin_lifecycle_tracepoint() { + // 1. Load Program and Pin + { + let mut bpf = Bpf::load(crate::TEST).unwrap(); + let prog: &mut TracePoint = bpf + .program_mut("test_tracepoint") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.pin("/sys/fs/bpf/aya-tracepoint-test-prog").unwrap(); + } + + // should still be loaded since prog was pinned + assert_loaded!("test_tracepoint", true); + + // 2. Load program from bpffs but don't attach it + { + let _ = TracePoint::from_pin("/sys/fs/bpf/aya-tracepoint-test-prog").unwrap(); + } + + // should still be loaded since prog was pinned + assert_loaded!("test_tracepoint", true); + + // 3. Load program from bpffs and attach + { + let mut prog = TracePoint::from_pin("/sys/fs/bpf/aya-tracepoint-test-prog").unwrap(); + let link_id = prog.attach("syscalls", "sys_enter_kill").unwrap(); + let link = prog.take_link(link_id).unwrap(); + let fd_link: FdLink = link.try_into().unwrap(); + fd_link + .pin("/sys/fs/bpf/aya-tracepoint-test-sys-enter-kill") + .unwrap(); + + // Unpin the program. It will stay attached since its links were pinned. + prog.unpin().unwrap(); + } + + // should still be loaded since link was pinned + assert_loaded_and_linked!("test_tracepoint", true); + + // 4. unpin link, and make sure everything is unloaded + { + PinnedLink::from_pin("/sys/fs/bpf/aya-tracepoint-test-sys-enter-kill") + .unwrap() + .unpin() + .unwrap(); + } + + // program should be unloaded + assert_loaded!("test_tracepoint", false); +} + +#[test] +fn pin_lifecycle_kprobe() { + // 1. Load Program and Pin + { + let mut bpf = Bpf::load(crate::TEST).unwrap(); + let prog: &mut KProbe = bpf.program_mut("test_kprobe").unwrap().try_into().unwrap(); + prog.load().unwrap(); + prog.pin("/sys/fs/bpf/aya-kprobe-test-prog").unwrap(); + } + + // should still be loaded since prog was pinned + assert_loaded!("test_kprobe", true); + + // 2. Load program from bpffs but don't attach it + { + let _ = KProbe::from_pin( + "/sys/fs/bpf/aya-kprobe-test-prog", + aya::programs::ProbeKind::KProbe, + ) + .unwrap(); + } + + // should still be loaded since prog was pinned + assert_loaded!("test_kprobe", true); + + // 3. Load program from bpffs and attach + { + let mut prog = KProbe::from_pin( + "/sys/fs/bpf/aya-kprobe-test-prog", + aya::programs::ProbeKind::KProbe, + ) + .unwrap(); + let link_id = prog.attach("try_to_wake_up", 0).unwrap(); + let link = prog.take_link(link_id).unwrap(); + let fd_link: FdLink = link.try_into().unwrap(); + fd_link + .pin("/sys/fs/bpf/aya-kprobe-test-try-to-wake-up") + .unwrap(); + + // Unpin the program. It will stay attached since its links were pinned. + prog.unpin().unwrap(); + } + + // should still be loaded since link was pinned + assert_loaded_and_linked!("test_kprobe", true); + + // 4. unpin link, and make sure everything is unloaded + { + PinnedLink::from_pin("/sys/fs/bpf/aya-kprobe-test-try-to-wake-up") + .unwrap() + .unpin() + .unwrap(); + } + + // program should be unloaded + assert_loaded!("test_kprobe", false); +} + +#[test] +fn pin_lifecycle_uprobe() { + // 1. Load Program and Pin + { + let mut bpf = Bpf::load(crate::TEST).unwrap(); + let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap(); + prog.load().unwrap(); + prog.pin("/sys/fs/bpf/aya-uprobe-test-prog").unwrap(); + } + + // should still be loaded since prog was pinned + assert_loaded!("test_uprobe", true); + + // 2. Load program from bpffs but don't attach it + { + let _ = UProbe::from_pin( + "/sys/fs/bpf/aya-uprobe-test-prog", + aya::programs::ProbeKind::UProbe, + ) + .unwrap(); + } + + // should still be loaded since prog was pinned + assert_loaded!("test_uprobe", true); + + // 3. Load program from bpffs and attach + { + let mut prog = UProbe::from_pin( + "/sys/fs/bpf/aya-uprobe-test-prog", + aya::programs::ProbeKind::UProbe, + ) + .unwrap(); + let link_id = prog.attach(Some("sleep"), 0, "libc", None).unwrap(); + let link = prog.take_link(link_id).unwrap(); + let fd_link: FdLink = link.try_into().unwrap(); + fd_link + .pin("/sys/fs/bpf/aya-uprobe-test-bash-sleep") + .unwrap(); + + // Unpin the program. It will stay attached since its links were pinned. + prog.unpin().unwrap(); + } + + // should still be loaded since link was pinned + assert_loaded_and_linked!("test_uprobe", true); + + // 4. unpin link, and make sure everything is unloaded + { + PinnedLink::from_pin("/sys/fs/bpf/aya-uprobe-test-bash-sleep") + .unwrap() + .unpin() + .unwrap(); + } + + // program should be unloaded + assert_loaded!("test_uprobe", false); +} diff --git a/test/run.sh b/test/run.sh index 23c0dd1a..9def9a22 100755 --- a/test/run.sh +++ b/test/run.sh @@ -192,7 +192,7 @@ EOF exec_vm sudo dnf config-manager --set-enabled updates-testing exec_vm sudo dnf config-manager --set-enabled updates-testing-modular echo "Installing dependencies" - exec_vm sudo dnf install -qy llvm llvm-devel clang clang-devel zlib-devel + exec_vm sudo dnf install -qy bpftool llvm llvm-devel clang clang-devel zlib-devel exec_vm 'curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \ -y --profile minimal --default-toolchain nightly --component rust-src --component clippy' exec_vm 'echo source ~/.cargo/env >> ~/.bashrc'