//! Query the host about BPF
//!
//! For example, to list the name of every bpf program running on the system:
//! ```
//! use libbpf_rs::query::ProgInfoIter;
//!
//! let mut iter = ProgInfoIter::default();
//! for prog in iter {
//!     println!("{}", prog.name.to_string_lossy());
//! }
//! ```

use std::ffi::c_void;
use std::ffi::CString;
use std::io;
use std::mem::size_of_val;
use std::os::fd::AsFd;
use std::os::fd::AsRawFd;
use std::os::fd::BorrowedFd;
use std::os::fd::FromRawFd;
use std::os::fd::OwnedFd;
use std::os::raw::c_char;
use std::ptr;
use std::time::Duration;

use crate::util;
use crate::MapType;
use crate::ProgramAttachType;
use crate::ProgramType;
use crate::Result;

macro_rules! gen_info_impl {
    // This magic here allows us to embed doc comments into macro expansions
    ($(#[$attr:meta])*
     $name:ident, $info_ty:ty, $uapi_info_ty:ty, $next_id:expr, $fd_by_id:expr) => {
        $(#[$attr])*
        #[derive(Default, Debug)]
        pub struct $name {
            cur_id: u32,
        }

        impl $name {
            // Returns Some(next_valid_fd), None on none left
            fn next_valid_fd(&mut self) -> Option<OwnedFd> {
                loop {
                    if unsafe { $next_id(self.cur_id, &mut self.cur_id) } != 0 {
                        return None;
                    }

                    let fd = unsafe { $fd_by_id(self.cur_id) };
                    if fd < 0 {
                        let err = io::Error::last_os_error();
                        if err.kind() == io::ErrorKind::NotFound {
                            continue;
                        }

                        return None;
                    }

                    return Some(unsafe { OwnedFd::from_raw_fd(fd)});
                }
            }
        }

        impl Iterator for $name {
            type Item = $info_ty;

            fn next(&mut self) -> Option<Self::Item> {
                let fd = self.next_valid_fd()?;

                // We need to use std::mem::zeroed() instead of just using
                // ::default() because padding bytes need to be zero as well.
                // Old kernels which know about fewer fields than we do will
                // check to make sure every byte past what they know is zero
                // and will return E2BIG otherwise.
                let mut item: $uapi_info_ty = unsafe { std::mem::zeroed() };
                let item_ptr: *mut $uapi_info_ty = &mut item;
                let mut len = size_of_val(&item) as u32;

                let ret = unsafe { libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len) };
                let parsed_uapi = if ret != 0 {
                    None
                } else {
                    <$info_ty>::from_uapi(fd.as_fd(), item)
                };

                parsed_uapi
            }
        }
    };
}

/// BTF Line information
#[derive(Clone, Debug)]
pub struct LineInfo {
    /// Offset of instruction in vector
    pub insn_off: u32,
    /// File name offset
    pub file_name_off: u32,
    /// Line offset in debug info
    pub line_off: u32,
    /// Line number
    pub line_num: u32,
    /// Line column number
    pub line_col: u32,
}

impl From<&libbpf_sys::bpf_line_info> for LineInfo {
    fn from(item: &libbpf_sys::bpf_line_info) -> Self {
        LineInfo {
            insn_off: item.insn_off,
            file_name_off: item.file_name_off,
            line_off: item.line_off,
            line_num: item.line_col >> 10,
            line_col: item.line_col & 0x3ff,
        }
    }
}

/// Bpf identifier tag
#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct Tag(pub [u8; 8]);

/// Information about a BPF program
#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct ProgramInfo {
    pub name: CString,
    pub ty: ProgramType,
    pub tag: Tag,
    pub id: u32,
    pub jited_prog_insns: Vec<u8>,
    pub xlated_prog_insns: Vec<u8>,
    /// Duration since system boot
    pub load_time: Duration,
    pub created_by_uid: u32,
    pub map_ids: Vec<u32>,
    pub ifindex: u32,
    pub gpl_compatible: bool,
    pub netns_dev: u64,
    pub netns_ino: u64,
    pub jited_ksyms: Vec<*const c_void>,
    pub jited_func_lens: Vec<u32>,
    pub btf_id: u32,
    pub func_info_rec_size: u32,
    pub func_info: Vec<libbpf_sys::bpf_func_info>,
    pub line_info: Vec<LineInfo>,
    pub jited_line_info: Vec<*const c_void>,
    pub line_info_rec_size: u32,
    pub jited_line_info_rec_size: u32,
    pub prog_tags: Vec<Tag>,
    pub run_time_ns: u64,
    pub run_cnt: u64,
    /// Skipped BPF executions due to recursion or concurrent execution prevention.
    pub recursion_misses: u64,
}

/// An iterator for the information of loaded bpf programs
#[derive(Default, Debug)]
pub struct ProgInfoIter {
    cur_id: u32,
    opts: ProgInfoQueryOptions,
}

/// Options to query the program info currently loaded
#[derive(Clone, Default, Debug)]
pub struct ProgInfoQueryOptions {
    /// Include the vector of bpf instructions in the result
    include_xlated_prog_insns: bool,
    /// Include the vector of jited instructions in the result
    include_jited_prog_insns: bool,
    /// Include the ids of maps associated with the program
    include_map_ids: bool,
    /// Include source line information corresponding to xlated code
    include_line_info: bool,
    /// Include function type information corresponding to xlated code
    include_func_info: bool,
    /// Include source line information corresponding to jited code
    include_jited_line_info: bool,
    /// Include function type information corresponding to jited code
    include_jited_func_lens: bool,
    /// Include program tags
    include_prog_tags: bool,
    /// Include the jited kernel symbols
    include_jited_ksyms: bool,
}

impl ProgInfoIter {
    /// Generate an iter from more specific query options
    pub fn with_query_opts(opts: ProgInfoQueryOptions) -> Self {
        Self {
            opts,
            ..Self::default()
        }
    }
}

impl ProgInfoQueryOptions {
    /// Include the vector of jited bpf instructions in the result
    pub fn include_xlated_prog_insns(mut self, v: bool) -> Self {
        self.include_xlated_prog_insns = v;
        self
    }

    /// Include the vector of jited instructions in the result
    pub fn include_jited_prog_insns(mut self, v: bool) -> Self {
        self.include_jited_prog_insns = v;
        self
    }

    /// Include the ids of maps associated with the program
    pub fn include_map_ids(mut self, v: bool) -> Self {
        self.include_map_ids = v;
        self
    }

    /// Include source line information corresponding to xlated code
    pub fn include_line_info(mut self, v: bool) -> Self {
        self.include_line_info = v;
        self
    }

    /// Include function type information corresponding to xlated code
    pub fn include_func_info(mut self, v: bool) -> Self {
        self.include_func_info = v;
        self
    }

    /// Include source line information corresponding to jited code
    pub fn include_jited_line_info(mut self, v: bool) -> Self {
        self.include_jited_line_info = v;
        self
    }

    /// Include function type information corresponding to jited code
    pub fn include_jited_func_lens(mut self, v: bool) -> Self {
        self.include_jited_func_lens = v;
        self
    }

    /// Include program tags
    pub fn include_prog_tags(mut self, v: bool) -> Self {
        self.include_prog_tags = v;
        self
    }

    /// Include the jited kernel symbols
    pub fn include_jited_ksyms(mut self, v: bool) -> Self {
        self.include_jited_ksyms = v;
        self
    }

    /// Include everything there is in the query results
    pub fn include_all(self) -> Self {
        Self {
            include_xlated_prog_insns: true,
            include_jited_prog_insns: true,
            include_map_ids: true,
            include_line_info: true,
            include_func_info: true,
            include_jited_line_info: true,
            include_jited_func_lens: true,
            include_prog_tags: true,
            include_jited_ksyms: true,
        }
    }
}

impl ProgramInfo {
    fn load_from_fd(fd: BorrowedFd<'_>, opts: &ProgInfoQueryOptions) -> Result<Self> {
        let mut item = libbpf_sys::bpf_prog_info::default();

        let mut xlated_prog_insns: Vec<u8> = Vec::new();
        let mut jited_prog_insns: Vec<u8> = Vec::new();
        let mut map_ids: Vec<u32> = Vec::new();
        let mut jited_line_info: Vec<*const c_void> = Vec::new();
        let mut line_info: Vec<libbpf_sys::bpf_line_info> = Vec::new();
        let mut func_info: Vec<libbpf_sys::bpf_func_info> = Vec::new();
        let mut jited_func_lens: Vec<u32> = Vec::new();
        let mut prog_tags: Vec<Tag> = Vec::new();
        let mut jited_ksyms: Vec<*const c_void> = Vec::new();

        let item_ptr: *mut libbpf_sys::bpf_prog_info = &mut item;
        let mut len = size_of_val(&item) as u32;

        let ret = unsafe {
            libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
        };
        util::parse_ret(ret)?;

        // SANITY: `libbpf` should guarantee NUL termination.
        let name = util::c_char_slice_to_cstr(&item.name).unwrap();
        let ty = ProgramType::from(item.type_);

        if opts.include_xlated_prog_insns {
            xlated_prog_insns.resize(item.xlated_prog_len as usize, 0u8);
            item.xlated_prog_insns = xlated_prog_insns.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.xlated_prog_len = 0;
        }

        if opts.include_jited_prog_insns {
            jited_prog_insns.resize(item.jited_prog_len as usize, 0u8);
            item.jited_prog_insns = jited_prog_insns.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.jited_prog_len = 0;
        }

        if opts.include_map_ids {
            map_ids.resize(item.nr_map_ids as usize, 0u32);
            item.map_ids = map_ids.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_map_ids = 0;
        }

        if opts.include_line_info {
            line_info.resize(
                item.nr_line_info as usize,
                libbpf_sys::bpf_line_info::default(),
            );
            item.line_info = line_info.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_line_info = 0;
        }

        if opts.include_func_info {
            func_info.resize(
                item.nr_func_info as usize,
                libbpf_sys::bpf_func_info::default(),
            );
            item.func_info = func_info.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_func_info = 0;
        }

        if opts.include_jited_line_info {
            jited_line_info.resize(item.nr_jited_line_info as usize, ptr::null());
            item.jited_line_info = jited_line_info.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_jited_line_info = 0;
        }

        if opts.include_jited_func_lens {
            jited_func_lens.resize(item.nr_jited_func_lens as usize, 0);
            item.jited_func_lens = jited_func_lens.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_jited_func_lens = 0;
        }

        if opts.include_prog_tags {
            prog_tags.resize(item.nr_prog_tags as usize, Tag::default());
            item.prog_tags = prog_tags.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_prog_tags = 0;
        }

        if opts.include_jited_ksyms {
            jited_ksyms.resize(item.nr_jited_ksyms as usize, ptr::null());
            item.jited_ksyms = jited_ksyms.as_mut_ptr() as *mut c_void as u64;
        } else {
            item.nr_jited_ksyms = 0;
        }

        let ret = unsafe {
            libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
        };
        util::parse_ret(ret)?;

        Ok(ProgramInfo {
            name: name.to_owned(),
            ty,
            tag: Tag(item.tag),
            id: item.id,
            jited_prog_insns,
            xlated_prog_insns,
            load_time: Duration::from_nanos(item.load_time),
            created_by_uid: item.created_by_uid,
            map_ids,
            ifindex: item.ifindex,
            gpl_compatible: item._bitfield_1.get_bit(0),
            netns_dev: item.netns_dev,
            netns_ino: item.netns_ino,
            jited_ksyms,
            jited_func_lens,
            btf_id: item.btf_id,
            func_info_rec_size: item.func_info_rec_size,
            func_info,
            line_info: line_info.iter().map(|li| li.into()).collect(),
            jited_line_info,
            line_info_rec_size: item.line_info_rec_size,
            jited_line_info_rec_size: item.jited_line_info_rec_size,
            prog_tags,
            run_time_ns: item.run_time_ns,
            run_cnt: item.run_cnt,
            recursion_misses: item.recursion_misses,
        })
    }
}

impl ProgInfoIter {
    fn next_valid_fd(&mut self) -> Option<OwnedFd> {
        loop {
            if unsafe { libbpf_sys::bpf_prog_get_next_id(self.cur_id, &mut self.cur_id) } != 0 {
                return None;
            }

            let fd = unsafe { libbpf_sys::bpf_prog_get_fd_by_id(self.cur_id) };
            if fd < 0 {
                let err = io::Error::last_os_error();
                if err.kind() == io::ErrorKind::NotFound {
                    continue;
                }
                return None;
            }

            return Some(unsafe { OwnedFd::from_raw_fd(fd) });
        }
    }
}

impl Iterator for ProgInfoIter {
    type Item = ProgramInfo;

    fn next(&mut self) -> Option<Self::Item> {
        let fd = self.next_valid_fd()?;

        let prog = ProgramInfo::load_from_fd(fd.as_fd(), &self.opts);

        match prog {
            Ok(p) => Some(p),
            // TODO: We should consider bubbling up errors properly.
            Err(_err) => None,
        }
    }
}

/// Information about a BPF map
#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct MapInfo {
    pub name: CString,
    pub ty: MapType,
    pub id: u32,
    pub key_size: u32,
    pub value_size: u32,
    pub max_entries: u32,
    pub map_flags: u32,
    pub ifindex: u32,
    pub btf_vmlinux_value_type_id: u32,
    pub netns_dev: u64,
    pub netns_ino: u64,
    pub btf_id: u32,
    pub btf_key_type_id: u32,
    pub btf_value_type_id: u32,
}

impl MapInfo {
    fn from_uapi(_fd: BorrowedFd<'_>, s: libbpf_sys::bpf_map_info) -> Option<Self> {
        // SANITY: `libbpf` should guarantee NUL termination.
        let name = util::c_char_slice_to_cstr(&s.name).unwrap();
        let ty = MapType::from(s.type_);

        Some(Self {
            name: name.to_owned(),
            ty,
            id: s.id,
            key_size: s.key_size,
            value_size: s.value_size,
            max_entries: s.max_entries,
            map_flags: s.map_flags,
            ifindex: s.ifindex,
            btf_vmlinux_value_type_id: s.btf_vmlinux_value_type_id,
            netns_dev: s.netns_dev,
            netns_ino: s.netns_ino,
            btf_id: s.btf_id,
            btf_key_type_id: s.btf_key_type_id,
            btf_value_type_id: s.btf_value_type_id,
        })
    }
}

gen_info_impl!(
    /// Iterator that returns [`MapInfo`]s.
    MapInfoIter,
    MapInfo,
    libbpf_sys::bpf_map_info,
    libbpf_sys::bpf_map_get_next_id,
    libbpf_sys::bpf_map_get_fd_by_id
);

/// Information about BPF type format
#[derive(Debug, Clone)]
pub struct BtfInfo {
    /// The name associated with this btf information in the kernel
    pub name: CString,
    /// The raw btf bytes from the kernel
    pub btf: Vec<u8>,
    /// The btf id associated with this btf information in the kernel
    pub id: u32,
}

impl BtfInfo {
    fn load_from_fd(fd: BorrowedFd<'_>) -> Result<Self> {
        let mut item = libbpf_sys::bpf_btf_info::default();
        let mut btf: Vec<u8> = Vec::new();
        let mut name: Vec<u8> = Vec::new();

        let item_ptr: *mut libbpf_sys::bpf_btf_info = &mut item;
        let mut len = size_of_val(&item) as u32;

        let ret = unsafe {
            libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
        };
        util::parse_ret(ret)?;

        // The API gives you the ascii string length while expecting
        // you to give it back space for a nul-terminator
        item.name_len += 1;
        name.resize(item.name_len as usize, 0u8);
        item.name = name.as_mut_ptr() as *mut c_void as u64;

        btf.resize(item.btf_size as usize, 0u8);
        item.btf = btf.as_mut_ptr() as *mut c_void as u64;

        let ret = unsafe {
            libbpf_sys::bpf_obj_get_info_by_fd(fd.as_raw_fd(), item_ptr as *mut c_void, &mut len)
        };
        util::parse_ret(ret)?;

        Ok(BtfInfo {
            // SANITY: Our buffer contained space for a NUL byte and we set its
            //         contents to 0. Barring a `libbpf` bug a NUL byte will be
            //         present.
            name: CString::from_vec_with_nul(name).unwrap(),
            btf,
            id: item.id,
        })
    }
}

#[derive(Debug, Default)]
/// An iterator for the btf type information of modules and programs
/// in the kernel
pub struct BtfInfoIter {
    cur_id: u32,
}

impl BtfInfoIter {
    // Returns Some(next_valid_fd), None on none left
    fn next_valid_fd(&mut self) -> Option<OwnedFd> {
        loop {
            if unsafe { libbpf_sys::bpf_btf_get_next_id(self.cur_id, &mut self.cur_id) } != 0 {
                return None;
            }

            let fd = unsafe { libbpf_sys::bpf_btf_get_fd_by_id(self.cur_id) };
            if fd < 0 {
                let err = io::Error::last_os_error();
                if err.kind() == io::ErrorKind::NotFound {
                    continue;
                }
                return None;
            }

            return Some(unsafe { OwnedFd::from_raw_fd(fd) });
        }
    }
}

impl Iterator for BtfInfoIter {
    type Item = BtfInfo;

    fn next(&mut self) -> Option<Self::Item> {
        let fd = self.next_valid_fd()?;

        let info = BtfInfo::load_from_fd(fd.as_fd());

        match info {
            Ok(i) => Some(i),
            // TODO: We should consider bubbling up errors properly.
            Err(_err) => None,
        }
    }
}

#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct RawTracepointLinkInfo {
    pub name: String,
}

#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct TracingLinkInfo {
    pub attach_type: ProgramAttachType,
}

#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct CgroupLinkInfo {
    pub cgroup_id: u64,
    pub attach_type: ProgramAttachType,
}

#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct NetNsLinkInfo {
    pub ino: u32,
    pub attach_type: ProgramAttachType,
}

#[derive(Debug, Clone)]
// TODO: Document variants.
#[allow(missing_docs)]
pub enum LinkTypeInfo {
    RawTracepoint(RawTracepointLinkInfo),
    Tracing(TracingLinkInfo),
    Cgroup(CgroupLinkInfo),
    Iter,
    NetNs(NetNsLinkInfo),
    Unknown,
}

/// Information about a BPF link
#[derive(Debug, Clone)]
// TODO: Document members.
#[allow(missing_docs)]
pub struct LinkInfo {
    pub info: LinkTypeInfo,
    pub id: u32,
    pub prog_id: u32,
}

impl LinkInfo {
    fn from_uapi(fd: BorrowedFd<'_>, mut s: libbpf_sys::bpf_link_info) -> Option<Self> {
        let type_info = match s.type_ {
            libbpf_sys::BPF_LINK_TYPE_RAW_TRACEPOINT => {
                let mut buf = [0; 256];
                s.__bindgen_anon_1.raw_tracepoint.tp_name = buf.as_mut_ptr() as u64;
                s.__bindgen_anon_1.raw_tracepoint.tp_name_len = buf.len() as u32;
                let item_ptr: *mut libbpf_sys::bpf_link_info = &mut s;
                let mut len = size_of_val(&s) as u32;

                let ret = unsafe {
                    libbpf_sys::bpf_obj_get_info_by_fd(
                        fd.as_raw_fd(),
                        item_ptr as *mut c_void,
                        &mut len,
                    )
                };
                if ret != 0 {
                    return None;
                }

                LinkTypeInfo::RawTracepoint(RawTracepointLinkInfo {
                    name: util::c_ptr_to_string(
                        unsafe { s.__bindgen_anon_1.raw_tracepoint.tp_name } as *const c_char,
                    )
                    .unwrap_or_else(|_| "?".to_string()),
                })
            }
            libbpf_sys::BPF_LINK_TYPE_TRACING => LinkTypeInfo::Tracing(TracingLinkInfo {
                attach_type: ProgramAttachType::from(unsafe {
                    s.__bindgen_anon_1.tracing.attach_type
                }),
            }),
            libbpf_sys::BPF_LINK_TYPE_CGROUP => LinkTypeInfo::Cgroup(CgroupLinkInfo {
                cgroup_id: unsafe { s.__bindgen_anon_1.cgroup.cgroup_id },
                attach_type: ProgramAttachType::from(unsafe {
                    s.__bindgen_anon_1.cgroup.attach_type
                }),
            }),
            libbpf_sys::BPF_LINK_TYPE_ITER => LinkTypeInfo::Iter,
            libbpf_sys::BPF_LINK_TYPE_NETNS => LinkTypeInfo::NetNs(NetNsLinkInfo {
                ino: unsafe { s.__bindgen_anon_1.netns.netns_ino },
                attach_type: ProgramAttachType::from(unsafe {
                    s.__bindgen_anon_1.netns.attach_type
                }),
            }),
            _ => LinkTypeInfo::Unknown,
        };

        Some(Self {
            info: type_info,
            id: s.id,
            prog_id: s.prog_id,
        })
    }
}

gen_info_impl!(
    /// Iterator that returns [`LinkInfo`]s.
    LinkInfoIter,
    LinkInfo,
    libbpf_sys::bpf_link_info,
    libbpf_sys::bpf_link_get_next_id,
    libbpf_sys::bpf_link_get_fd_by_id
);
