├── src ├── database │ ├── models │ │ ├── mod.rs │ │ └── user.rs │ ├── service_handler.rs │ ├── adapters │ │ ├── mod.rs │ │ ├── mmap.rs │ │ ├── fread_fwrite_struct.rs │ │ ├── dbm.rs │ │ └── mysql.rs │ ├── mod.rs │ └── database_config.rs ├── file_system │ ├── parser │ │ ├── etc │ │ │ ├── mod.rs │ │ │ └── fstab.rs │ │ ├── proc │ │ │ ├── mod.rs │ │ │ └── net │ │ │ │ ├── mod.rs │ │ │ │ ├── ipv6_route.rs │ │ │ │ └── route.rs │ │ └── mod.rs │ ├── syscall.rs │ └── mod.rs ├── dylibs_binding │ ├── readline.rs │ ├── mod.rs │ ├── sqlite3.rs │ ├── curses.rs │ ├── crypto.rs │ ├── gdbm_compat.rs │ ├── event.rs │ └── mysqlclient.rs ├── bin │ ├── dig.rs │ ├── nslookup.rs │ ├── route.rs │ ├── host.rs │ ├── arp.rs │ ├── unlink.rs │ ├── rmdir.rs │ ├── dirname.rs │ ├── basename.rs │ ├── touch.rs │ ├── uname.rs │ ├── hostname.rs │ ├── chmod.rs │ ├── pwd.rs │ ├── ls.rs │ ├── cat.rs │ ├── tee.rs │ ├── id.rs │ ├── tree.rs │ ├── ping.rs │ └── stat.rs ├── misc_syscall.rs ├── lib.rs ├── macros.rs ├── errno.rs ├── network.rs └── time.rs ├── .gitignore ├── database_config.toml.example ├── Cargo.toml ├── .cargo └── config.toml.example ├── examples ├── getrusage.rs ├── sigabrt_free_dylib_data.rs ├── get_disk_uuid.rs ├── sigret_mmap_offset_u8_to_empty_file.rs ├── sigsegv_opendir_open_null.rs ├── sigbus_mmap_offset_struct_empty_file.rs ├── get_default_route_ip_and_mac.rs └── sigabrt_closedir_wrong.rs ├── docs └── system_call_notes.md └── README.md /src/database/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod user; 2 | -------------------------------------------------------------------------------- /src/file_system/parser/etc/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod fstab; 2 | -------------------------------------------------------------------------------- /src/file_system/parser/proc/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod net; 2 | -------------------------------------------------------------------------------- /src/file_system/parser/proc/net/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod route; 2 | -------------------------------------------------------------------------------- /src/file_system/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod etc; 2 | pub mod proc; 3 | -------------------------------------------------------------------------------- /src/database/service_handler.rs: -------------------------------------------------------------------------------- 1 | //! 处理业务相关的service,可能需要跨表(多个Model)进行读写操作 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .cargo/config.toml 4 | database_config.toml 5 | .idea 6 | -------------------------------------------------------------------------------- /database_config.toml.example: -------------------------------------------------------------------------------- 1 | [mysql] 2 | username="root" 3 | password="" 4 | db_name="test" 5 | -------------------------------------------------------------------------------- /src/database/adapters/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dbm; 2 | mod fread_fwrite_struct; 3 | mod mmap; 4 | pub mod mysql; 5 | -------------------------------------------------------------------------------- /src/dylibs_binding/readline.rs: -------------------------------------------------------------------------------- 1 | #[link(name = "readline")] 2 | extern "C" { 3 | pub static rl_readline_version: libc::c_int; 4 | } 5 | -------------------------------------------------------------------------------- /src/bin/dig.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | panic!( 3 | "dig, nslookup and host command are same for DNS resolve. Please use host command instead" 4 | ); 5 | } 6 | -------------------------------------------------------------------------------- /src/bin/nslookup.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | panic!( 3 | "dig, nslookup and host command are same for DNS resolve. Please use host command instead" 4 | ); 5 | } 6 | -------------------------------------------------------------------------------- /src/dylibs_binding/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod crypto; 2 | pub mod curses; 3 | pub mod event; 4 | pub mod gdbm_compat; 5 | pub mod mysqlclient; 6 | pub mod readline; 7 | pub mod sqlite3; 8 | -------------------------------------------------------------------------------- /src/bin/route.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | `route` or `ip route` command alternative impl 3 | TODO do A/B testing with ip route command output? 4 | */ 5 | 6 | fn main() { 7 | dbg!(std::fs::read_to_string("/proc/net/route").unwrap()); 8 | } 9 | -------------------------------------------------------------------------------- /src/file_system/syscall.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_char; 2 | 3 | #[link(name = "c")] 4 | extern "C" { 5 | /// Both dirname() and basename() may modify the contents of path 6 | pub fn basename(path: *mut c_char) -> *mut c_char; 7 | pub fn dirname(path: *mut c_char) -> *mut c_char; 8 | } 9 | -------------------------------------------------------------------------------- /src/misc_syscall.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_gnu_get_libc_version() { 3 | extern "C" { 4 | fn gnu_get_libc_version() -> *const libc::c_char; 5 | } 6 | let version_cstr = unsafe { std::ffi::CStr::from_ptr(gnu_get_libc_version()) }; 7 | dbg!(version_cstr.to_str().unwrap()); 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linux_programming" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | libc = "0.2" 8 | 9 | [dev-dependencies] 10 | # toml and serde is only used to read database config file 11 | toml = "0.5" 12 | serde = { version = "1.0", features = ["derive"] } 13 | # serde_json to parse lsblk output 14 | serde_json = "1.0" 15 | -------------------------------------------------------------------------------- /src/bin/host.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::file_system::print_executable_usage; 2 | use linux_programming::network::dns_resolve; 3 | 4 | fn main() { 5 | let args = std::env::args().collect::>(); 6 | if args.len() != 2 { 7 | eprintln!("host: missing operand"); 8 | print_executable_usage(args[0].as_str(), "$hostname"); 9 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 10 | } 11 | let _ = dns_resolve(args[1].as_str()); 12 | } 13 | -------------------------------------------------------------------------------- /.cargo/config.toml.example: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags=["-Zinstrument-mcount"] 3 | 4 | [alias] 5 | check_x86_32 = "check --tests --target=i686-unknown-linux-gnu" 6 | test_clippy = ["clippy", "--tests", "--", "-Wclippy::pedantic", "-Aclippy::doc_markdown", "-Aclippy::cast-possible-truncation", "-Aclippy::cast-possible-wrap"] 7 | example_clippy = ["clippy", "--examples", "--", "-Wclippy::pedantic", "-Aclippy::doc_markdown", "-Aclippy::cast-possible-truncation", "-Aclippy::cast-possible-wrap"] 8 | -------------------------------------------------------------------------------- /src/bin/arp.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let f = unsafe { libc::fopen("/proc/net/arp\0".as_ptr().cast(), "r\0".as_ptr().cast()) }; 3 | let mut line_buf = [0_u8; libc::BUFSIZ as usize]; 4 | loop { 5 | let line = unsafe { libc::fgets(line_buf.as_mut_ptr().cast(), line_buf.len() as i32, f) }; 6 | if line.is_null() { 7 | break; 8 | } 9 | unsafe { 10 | libc::printf("%s\0".as_ptr().cast(), line); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/dylibs_binding/sqlite3.rs: -------------------------------------------------------------------------------- 1 | //! check sqlite3 whether install: ldconfig -p | grep libsqlite3 2 | #[link(name = "sqlite3")] 3 | extern "C" { 4 | pub fn sqlite3_libversion() -> *const libc::c_char; 5 | } 6 | 7 | #[test] 8 | fn test_sqlite3_libversion() { 9 | // copy version_str from libsqlite3.so 10 | let sqlite3_version = unsafe { 11 | std::ffi::CStr::from_ptr(sqlite3_libversion()) 12 | .to_str() 13 | .unwrap() 14 | }; 15 | dbg!(sqlite3_version); 16 | } 17 | -------------------------------------------------------------------------------- /src/bin/unlink.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let args = std::env::args().collect::>(); 3 | if args.len() != 2 { 4 | eprintln!("unlink: missing operand"); 5 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 6 | } 7 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 8 | if unsafe { libc::unlink(filename.as_ptr()) } == -1 { 9 | unsafe { 10 | libc::perror(filename.as_ptr()); 11 | libc::exit(libc::EXIT_FAILURE); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/bin/rmdir.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let args = std::env::args().collect::>(); 3 | if args.len() != 2 { 4 | eprintln!("rmdir: missing operand"); 5 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 6 | } 7 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 8 | if unsafe { libc::rmdir(filename.as_ptr()) } == -1 { 9 | unsafe { 10 | libc::perror(filename.as_ptr().cast()); 11 | libc::exit(libc::EXIT_FAILURE); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/database/mod.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 最近我在看一些系统编程相关的书,里面提到4种不同的数据库,但几乎都是处理同一个数据模型(例如订单数据) 3 | 每当我学一种新的数据库时都要重复写大量模型绑定或测试代码,因此我想到了以下抽象提高数据库驱动开发和学习的效率,大伙帮忙看看有什么能改进的地方吗? 4 | 源码在: https://github.com/pymongo/linux_commands_rewritten_in_rust/tree/main/src/database 5 | 6 | 我用trait建立了数据库+模型两个泛型参数的组合的抽象 7 | 这样每当书介绍新的数据库我都能通过trait快速开发新数据库驱动并自动适应各种数据模型 8 | 目前支持mmap和dbm数据库,将来会支持mysql,sqlite,redis,代码文件结构如图2 9 | 数据库适配器层: 负责析构函数 10 | Model层: 数据模型的定义和序列化 11 | DAO: 数据模型的读写需求 12 | service层: 处理业务 13 | */ 14 | pub mod adapters; 15 | pub mod database_config; 16 | mod models; 17 | -------------------------------------------------------------------------------- /src/bin/dirname.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::file_system::syscall::dirname; 2 | 3 | fn main() { 4 | let args = std::env::args().collect::>(); 5 | if args.len() != 2 { 6 | eprintln!("dirname: missing operand"); 7 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 8 | } 9 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 10 | unsafe { 11 | libc::printf( 12 | "%s\n\0".as_ptr().cast(), 13 | dirname(filename.as_ptr() as *mut libc::c_char), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/bin/basename.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::file_system::syscall::basename; 2 | 3 | fn main() { 4 | let args = std::env::args().collect::>(); 5 | if args.len() != 2 { 6 | eprintln!("basename: missing operand"); 7 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 8 | } 9 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 10 | unsafe { 11 | libc::printf( 12 | "%s\n\0".as_ptr().cast(), 13 | basename(filename.as_ptr() as *mut libc::c_char), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/getrusage.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | unsafe { 3 | main_(); 4 | } 5 | } 6 | 7 | unsafe fn main_() { 8 | // cpu usage = ((curr_usage.ru_utime+curr_usage.ru_stime) - (last_usage.ru_utime+last_usage.ru_stime)) / sample_interval 9 | // let sample_interval_ms = 5000; 10 | let ptr = libc::malloc(1 * 1024 * 1024 * 1024); 11 | assert!(!ptr.is_null()); 12 | dbg!(std::process::id()); 13 | 14 | let mut usage = std::mem::zeroed(); 15 | libc::getrusage(libc::RUSAGE_SELF, &mut usage); 16 | dbg!(usage.ru_maxrss); 17 | libc::sleep(999); 18 | } -------------------------------------------------------------------------------- /examples/sigabrt_free_dylib_data.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::dylibs_binding::sqlite3::sqlite3_libversion; 2 | 3 | fn main() { 4 | unsafe { 5 | let ptr = sqlite3_libversion() as *mut i8; 6 | let len = libc::strlen(ptr); 7 | let version = String::from_raw_parts(ptr.cast(), len, len); 8 | println!("found sqlite3 version={}", version); 9 | // Bug is here: String::drop try to free sqlite3 dylib string data cause signal SIGABRT 10 | // How to fix: mem::forget(version) or slice/ptr/strdup copy sqlite dylib data to Rust process 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/file_system/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parser; 2 | pub mod syscall; 3 | use syscall::basename; 4 | 5 | /// print example: 6 | /// usage: host $hostname 7 | pub fn print_executable_usage(argv_0: &str, usage: &str) { 8 | let exe_name = std::ffi::CString::new(argv_0).unwrap(); 9 | let executable_name = unsafe { basename(exe_name.as_ptr() as *mut std::os::raw::c_char) }; 10 | let len = unsafe { libc::strlen(executable_name) }; 11 | let executable_name = unsafe { String::from_raw_parts(executable_name.cast(), len, len) }; 12 | eprintln!("usage: {} {}", executable_name, usage); 13 | std::mem::forget(executable_name); 14 | } 15 | -------------------------------------------------------------------------------- /src/database/database_config.rs: -------------------------------------------------------------------------------- 1 | //! database_config(eg. username, password) use in test 2 | 3 | #[derive(serde::Deserialize, Debug)] 4 | pub struct Config { 5 | pub mysql: MysqlConfig, 6 | } 7 | 8 | #[derive(serde::Deserialize, Debug)] 9 | pub struct MysqlConfig { 10 | pub username: String, 11 | pub password: String, 12 | pub db_name: String, 13 | } 14 | 15 | impl Config { 16 | #[must_use] 17 | pub fn load_production_config() -> Self { 18 | let config_filename = format!("{}/database_config.toml", env!("CARGO_MANIFEST_DIR")); 19 | let toml_str = std::fs::read_to_string(config_filename).unwrap(); 20 | toml::de::from_str(&toml_str).unwrap() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/bin/touch.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let args = std::env::args().collect::>(); 3 | if args.len() != 2 { 4 | eprintln!("touch: missing operand"); 5 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 6 | } 7 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 8 | let fd = unsafe { 9 | libc::open( 10 | filename.as_ptr(), 11 | libc::O_CREAT, 12 | libc::S_IRUSR | libc::S_IWUSR | libc::S_IRGRP | libc::S_IROTH, 13 | ) 14 | }; 15 | if fd == -1 { 16 | unsafe { 17 | libc::perror(filename.as_ptr().cast()); 18 | libc::exit(libc::EXIT_FAILURE); 19 | } 20 | } 21 | unsafe { libc::close(fd) }; 22 | } 23 | -------------------------------------------------------------------------------- /src/bin/uname.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::syscall; 2 | 3 | fn main() { 4 | let mut uname = unsafe { std::mem::zeroed() }; 5 | syscall!(uname(&mut uname)); 6 | syscall!(printf("sysname=%s\n\0".as_ptr().cast(), uname.sysname)); 7 | syscall!(printf( 8 | "nodename(hostname)=%s\n\0".as_ptr().cast(), 9 | uname.nodename 10 | )); 11 | syscall!(printf("release=%s\n\0".as_ptr().cast(), uname.release)); 12 | // The version contains the date that kernel is compile 13 | syscall!(printf("version=%s\n\0".as_ptr().cast(), uname.version)); 14 | syscall!(printf("machine=%s\n\0".as_ptr().cast(), uname.machine)); 15 | syscall!(printf( 16 | "domainname=%s\n\0".as_ptr().cast(), 17 | uname.domainname 18 | )); 19 | } 20 | -------------------------------------------------------------------------------- /src/bin/hostname.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::syscall; 2 | 3 | /** 4 | ## multi ways to get hostname in Linux 5 | - libc::gethostname() 6 | - libc::uname() 7 | - uname --nodename 8 | - hostname 9 | - cat /etc/hostname 10 | - cat /proc/sys/kernel/hostname 11 | */ 12 | fn main() { 13 | let mut buf = [0_u8; 64]; 14 | syscall!(gethostname(buf.as_mut_ptr().cast(), 128)); 15 | syscall!(printf( 16 | "%s\n\0".as_ptr().cast(), 17 | buf.as_ptr().cast::() 18 | )); 19 | } 20 | 21 | #[cfg(FALSE)] 22 | unsafe fn get_hostname_by_gethostname_syscall() -> String { 23 | let mut buf = [0_u8; 256]; 24 | libc::gethostname(buf.as_mut_ptr().cast(), buf.len()); 25 | let len = libc::strlen(buf.as_ptr().cast()); 26 | String::from_utf8_unchecked(buf[..len].to_vec()) 27 | } 28 | -------------------------------------------------------------------------------- /src/bin/chmod.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::syscall; 2 | 3 | fn main() { 4 | let args = std::env::args().collect::>(); 5 | if args.len() != 3 { 6 | eprintln!("usage_example: chmod 777 main.rs"); 7 | return; 8 | } 9 | 10 | let permission_bits = args[1].as_bytes(); 11 | let user_permission = u32::from(permission_bits[0] - b'0'); 12 | assert!(user_permission <= 7); 13 | let group_permission = u32::from(permission_bits[1] - b'0'); 14 | assert!(group_permission <= 7); 15 | let other_permission = u32::from(permission_bits[2] - b'0'); 16 | assert!(other_permission <= 7); 17 | 18 | let permission = (user_permission << 6) | (group_permission << 3) | other_permission; 19 | let filename = std::ffi::CString::new(args[2].as_bytes()).unwrap(); 20 | syscall!(chmod(filename.as_ptr(), permission)); 21 | } 22 | -------------------------------------------------------------------------------- /src/bin/pwd.rs: -------------------------------------------------------------------------------- 1 | /** 2 | getcwd aka get current working directory 3 | getcwd is same as std::env::current_dir() or std::env::var("PWD")(in some shell) 4 | 5 | ## How to get first nul_byte(b'\0') in bytes if system_call doesn't tell you modified len 6 | example system call: getcwd, strerror_r 7 | 8 | One solution is `unsafe { std::ffi::CStr::from_ptr(buf.as_ptr().cast()) }` 9 | 10 | other better alternatives: 11 | 1. let pwd_str_len = buf.iter().position(|&x| x == b'\0').unwrap(); 12 | 2. let pwd_str_len = unsafe { libc::strlen(buf.as_ptr().cast()) }; 13 | */ 14 | use linux_programming::{syscall, NAME_MAX}; 15 | 16 | fn main() { 17 | let mut buf = [0_u8; NAME_MAX]; 18 | unsafe { libc::getcwd(buf.as_mut_ptr().cast(), buf.len()) }; 19 | syscall!(printf( 20 | "%s\n\0".as_ptr().cast(), 21 | buf.as_ptr().cast::() 22 | )); 23 | } 24 | -------------------------------------------------------------------------------- /src/bin/ls.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let args = std::env::args().collect::>(); 3 | let input_filename = if let Some(filename) = args.get(1) { 4 | format!("{}\0", filename) // or std::ffi::CString::new 5 | } else { 6 | ".\0".to_string() 7 | }; 8 | 9 | let dirp = unsafe { libc::opendir(input_filename.as_ptr().cast()) }; 10 | if dirp.is_null() { 11 | unsafe { 12 | libc::perror(input_filename.as_ptr().cast()); 13 | } 14 | return; 15 | } 16 | loop { 17 | let dir_entry = unsafe { libc::readdir(dirp) }; 18 | if dir_entry.is_null() { 19 | // directory_entries iterator end 20 | break; 21 | } 22 | unsafe { 23 | let dir_entry = *dir_entry; 24 | libc::printf("%s\n\0".as_ptr().cast(), dir_entry.d_name); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/dylibs_binding/curses.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use std::os::raw::c_int; 3 | 4 | // curses/ncurses: a terminal ui facilities 5 | #[cfg(test)] 6 | #[link(name = "curses")] 7 | extern "C" { 8 | type window; 9 | fn initscr() -> *mut window; 10 | fn endwin() -> c_int; 11 | fn refresh() -> c_int; 12 | #[link_name = "move"] 13 | fn move_(x: c_int, y: c_int) -> c_int; 14 | fn printw(format: *const libc::c_char, ...) -> c_int; 15 | } 16 | 17 | #[cfg(test)] 18 | unsafe fn curses_hello_world() { 19 | initscr(); 20 | move_(10, 15); 21 | printw("Hello World\0".as_ptr().cast()); 22 | refresh(); 23 | libc::sleep(1); 24 | endwin(); 25 | libc::exit(libc::EXIT_SUCCESS); 26 | } 27 | 28 | #[test] 29 | #[ignore = "would mess up terminal display"] 30 | fn run_curses_hello_world() { 31 | unsafe { 32 | curses_hello_world(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(extern_types)] 2 | #![warn(clippy::nursery, clippy::pedantic)] 3 | #![allow( 4 | clippy::module_name_repetitions, 5 | clippy::doc_markdown, 6 | clippy::missing_safety_doc, 7 | clippy::missing_errors_doc, 8 | clippy::missing_panics_doc, 9 | clippy::cast_sign_loss, 10 | clippy::cast_possible_wrap, 11 | clippy::cast_possible_truncation 12 | )] 13 | #![doc=include_str!("../README.md")] 14 | #![doc=include_str!("../docs/system_call_notes.md")] 15 | 16 | #[cfg(test)] 17 | pub mod database; 18 | pub mod dylibs_binding; 19 | pub mod errno; 20 | pub mod file_system; 21 | mod macros; 22 | pub mod network; 23 | pub mod time; 24 | #[cfg(test)] 25 | mod misc_syscall; 26 | 27 | // #include 28 | pub const NAME_MAX: usize = 256; 29 | pub const SOCKADDR_IN_LEN: libc::socklen_t = 30 | std::mem::size_of::() as libc::socklen_t; 31 | -------------------------------------------------------------------------------- /src/bin/cat.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let args = std::env::args().collect::>(); 3 | if args.len() != 2 { 4 | eprintln!("cat: missing operand"); 5 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 6 | } 7 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 8 | let f = unsafe { libc::fopen(filename.as_ptr().cast(), "r\0".as_ptr().cast()) }; 9 | if f.is_null() { 10 | unsafe { 11 | libc::perror(std::ptr::null()); 12 | libc::exit(libc::EXIT_FAILURE); 13 | } 14 | } 15 | let mut line_buf = [0_u8; libc::BUFSIZ as usize]; 16 | loop { 17 | let line = unsafe { libc::fgets(line_buf.as_mut_ptr().cast(), line_buf.len() as i32, f) }; 18 | if line.is_null() { 19 | break; 20 | } 21 | unsafe { 22 | libc::printf("%s\0".as_ptr().cast(), line); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // 错误处理的脑洞: 2 | // 为 -1 和 Null 实现 trait SyscallErr 3 | // 然后用 rustc_hir 检查确保每一个 SyscallErr 的使用都用在 unsafe fn 或者 libc:: 上 4 | #[macro_export] 5 | macro_rules! syscall { 6 | ($fun:ident ( $($arg:expr),* $(,)* )) => { 7 | { 8 | #[allow(unused_unsafe)] 9 | let res = unsafe { libc::$fun($($arg),*) }; 10 | if res == -1 { 11 | // Err(std::io::Error::last_os_error()) 12 | panic!("{}", std::io::Error::last_os_error()) 13 | } else { 14 | // Ok(res) 15 | res 16 | } 17 | } 18 | }; 19 | } 20 | 21 | #[macro_export] 22 | macro_rules! syscall_expr { 23 | ($expr:expr) => {{ 24 | let res = unsafe { $expr }; 25 | if res == -1 { 26 | panic!("{}", std::io::Error::last_os_error()) 27 | } else { 28 | res 29 | } 30 | }}; 31 | } 32 | -------------------------------------------------------------------------------- /examples/get_disk_uuid.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::file_system::parser::etc; 2 | 3 | fn main() { 4 | dbg!(etc::fstab::parse_etc_fstab()); 5 | } 6 | 7 | #[cfg(test)] 8 | mod lsblk { 9 | #[derive(serde::Deserialize)] 10 | struct LsblkJsonOutput { 11 | blockdevices: Vec, 12 | } 13 | 14 | #[derive(serde::Deserialize)] 15 | struct LsblkItem { 16 | // path: String, 17 | // fstype: Option, 18 | uuid: Option, 19 | } 20 | 21 | #[test] 22 | fn test() { 23 | let output = std::process::Command::new("lsblk") 24 | .arg("--json") 25 | .arg("--output") 26 | .arg("uuid") 27 | .output() 28 | .unwrap(); 29 | let lsblk_output_str = unsafe { String::from_utf8_unchecked(output.stdout) }; 30 | let lsblk_output = serde_json::from_str::(&lsblk_output_str).unwrap(); 31 | let uuid = lsblk_output 32 | .blockdevices 33 | .into_iter() 34 | .find_map(|x| x.uuid) 35 | .unwrap_or_default(); 36 | dbg!(uuid); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/dylibs_binding/crypto.rs: -------------------------------------------------------------------------------- 1 | #[link(name = "crypto", kind = "dylib")] 2 | extern "C" { 3 | /// unsigned char *MD5(const unsigned char *d, unsigned long n, unsigned char *md); 4 | fn MD5(input: *const u8, input_len: usize, output: &mut [u8; 16]) -> *mut u8; 5 | } 6 | 7 | #[must_use] 8 | pub fn openssl_md5(input: &[u8]) -> String { 9 | let mut output = [0_u8; 16]; 10 | unsafe { 11 | MD5(input.as_ptr().cast(), input.len(), &mut output); 12 | } 13 | let output = u128::from_be_bytes(output); // transmute 用的是 native_endian,最好还是显式的调用 from_be_bytes 14 | format!("{:x}", output) 15 | } 16 | 17 | #[test] 18 | fn test_openssl_md5() { 19 | const MD5_TEST_CASES: [(&[u8], &str); 3] = [ 20 | ( 21 | b"The quick brown fox jumps over the lazy dog", 22 | "9e107d9d372bb6826bd81d3542a419d6", 23 | ), 24 | ( 25 | b"The quick brown fox jumps over the lazy dog.", 26 | "e4d909c290d0fb1ca068ffaddf22cbd0", 27 | ), 28 | (b"", "d41d8cd98f00b204e9800998ecf8427e"), 29 | ]; 30 | for (input, output) in MD5_TEST_CASES { 31 | assert_eq!(openssl_md5(input), output); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/sigret_mmap_offset_u8_to_empty_file.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | ## Runtime Error 3 | 4 | ## SIGENT 的可能原因: 5 | - mmap空文件后,读取偏移为10的数据 6 | - unimplemented instructions 7 | */ 8 | use linux_programming::syscall; 9 | fn main() { 10 | const LEN: usize = 10; 11 | let fd = syscall!(open( 12 | "/tmp/my_mmap_data\0".as_ptr().cast(), 13 | libc::O_RDWR | libc::O_CREAT, 14 | libc::S_IRUSR | libc::S_IWUSR, 15 | )); 16 | // How to Fix: libc::write(fd, [0_u8; 10].as_ptr().cast(), 10); or set MAP_ANONYMOUS flag 17 | let mapped_addr = unsafe { 18 | libc::mmap( 19 | std::ptr::null_mut::(), 20 | LEN, 21 | libc::PROT_READ | libc::PROT_WRITE, 22 | // The segment changes are made in the file 23 | libc::MAP_SHARED, 24 | fd, 25 | 0, 26 | ) 27 | }; 28 | if mapped_addr == libc::MAP_FAILED { 29 | panic!("{}", std::io::Error::last_os_error()); 30 | } 31 | unsafe { 32 | libc::close(fd); 33 | } 34 | // Bug is here: read offset 10 to a empty file 35 | let _data = unsafe { *mapped_addr.cast::<[u8; LEN]>() }; 36 | syscall!(munmap(mapped_addr, LEN)); 37 | } 38 | -------------------------------------------------------------------------------- /examples/sigsegv_opendir_open_null.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Linux would terminal process when process dereference of a invalid address(SIGSEGV) 3 | 4 | ## segment fault 5 | process try to access memory it doesn't own 6 | 7 | ## SIGSEGV 的可能原因: 8 | - dereference NULL or invalid_address, eg. readdir(NULL) 9 | - dereference to System V shared memory not attach or after detach 10 | - stack overflow 11 | - use-after-free(danling pointers): access de-allocated memory 12 | - using uninitialized pointer 13 | - access memory process doesn't own, eg. index out of range 14 | */ 15 | fn main() { 16 | let input_filename = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml\0"); 17 | // Bug is here: should check dirp.is_null(). if input_filename not a dir, dirp would be NULL 18 | let dirp = unsafe { libc::opendir(input_filename.as_ptr().cast()) }; 19 | // How to fix: if dirp.is_null() { panic!() } 20 | loop { 21 | // `Segmentation fault (core dumped)` exit code 139 (interrupted by signal 11: SIGSEGV) 22 | let dir_entry = unsafe { libc::readdir(dirp) }; 23 | if dir_entry.is_null() { 24 | break; 25 | } 26 | unsafe { 27 | let dir_entry = *dir_entry; 28 | libc::printf("%s\n\0".as_ptr().cast(), dir_entry.d_name); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/file_system/parser/etc/fstab.rs: -------------------------------------------------------------------------------- 1 | pub fn parse_etc_fstab() { 2 | for line in std::fs::read_to_string("/etc/fstab").unwrap().lines() { 3 | if line.starts_with('#') { 4 | continue; 5 | } 6 | let row = line.split_whitespace().collect::>(); 7 | let mount_point = row[1]; 8 | if mount_point == "/" { 9 | let uuid = row[0].split_once('=').unwrap().1; 10 | dbg!(uuid); 11 | } 12 | } 13 | } 14 | 15 | #[test] 16 | fn test_parse_etc_fstab() { 17 | parse_etc_fstab(); 18 | } 19 | 20 | #[cfg(test)] 21 | mod parse_with_error_handling { 22 | fn parse_etc_fstab_with_error_handling() -> Option { 23 | for line in std::fs::read_to_string("/etc/fstab").ok()?.lines() { 24 | if line.starts_with('#') { 25 | continue; 26 | } 27 | let row = line.split_whitespace().collect::>(); 28 | let mount_point = row[1]; 29 | if mount_point == "/" { 30 | let uuid = row[0].split_once('=')?.1; 31 | return Some(uuid.to_string()); 32 | } 33 | } 34 | None 35 | } 36 | 37 | #[test] 38 | fn test_parse_etc_fstab_with_error_handling() { 39 | parse_etc_fstab_with_error_handling(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/sigbus_mmap_offset_struct_empty_file.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | ## bus error 3 | access memory beyond the physically address 4 | 5 | ## SIGBUS 的可能原因: 6 | - mmap空文件后,读取偏移为10的数据显然超出"物理内存范围",因为mmap是基于磁盘文件的数据,如果磁盘文件的大小为0,那么映射成的物理内存长度只能是0 7 | */ 8 | #[derive(Clone, Copy)] 9 | #[repr(C)] 10 | struct Byte(u8); 11 | use linux_programming::syscall; 12 | 13 | fn main() { 14 | const LEN: usize = 10; 15 | const SIZE: usize = std::mem::size_of::(); 16 | let fd = syscall!(open( 17 | "/tmp/my_mmap_data\0".as_ptr().cast(), 18 | libc::O_RDWR | libc::O_CREAT, 19 | libc::S_IRUSR | libc::S_IWUSR, 20 | )); 21 | let mmap_len = LEN * SIZE; 22 | // How to Fix: libc::write(fd, [0_u8; 10].as_ptr().cast(), 10); or set MAP_ANONYMOUS flag 23 | let mapped_addr = unsafe { 24 | libc::mmap( 25 | std::ptr::null_mut::(), 26 | mmap_len, 27 | libc::PROT_READ | libc::PROT_WRITE, 28 | // The segment changes are made in the file 29 | libc::MAP_SHARED, 30 | fd, 31 | 0, 32 | ) 33 | }; 34 | if mapped_addr == libc::MAP_FAILED { 35 | panic!("{}", std::io::Error::last_os_error()); 36 | } 37 | unsafe { 38 | libc::close(fd); 39 | } 40 | // Bug is here: read offset 10 to a empty file 41 | let _data = unsafe { *mapped_addr.cast::<[Byte; LEN]>() }; 42 | syscall!(munmap(mapped_addr, mmap_len)); 43 | } 44 | -------------------------------------------------------------------------------- /src/bin/tee.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::syscall; 2 | 3 | fn main() { 4 | let args = std::env::args().collect::>(); 5 | if args.len() != 2 { 6 | eprintln!("tee: missing operand"); 7 | eprintln!("usage example: cat Cargo.toml | tee Cargo.toml.bak"); 8 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 9 | } 10 | 11 | let mut pipes_1 = [-1; 2]; 12 | let mut pipes_2 = [-1; 2]; 13 | syscall!(pipe(pipes_1.as_mut_ptr())); 14 | syscall!(pipe(pipes_2.as_mut_ptr())); 15 | 16 | // STDIN -> pipes_1[write] -> pipes_1[read] 17 | syscall!(splice( 18 | libc::STDIN_FILENO, 19 | std::ptr::null_mut(), 20 | pipes_1[1], 21 | std::ptr::null_mut(), 22 | libc::PIPE_BUF, 23 | 0 24 | )); 25 | // pipes_1[read].copy() -> pipes_2[write] -> pipes_2[read] 26 | syscall!(tee(pipes_1[0], pipes_2[1], libc::PIPE_BUF, 0)); 27 | 28 | // pipes_1[read] -> STDOUT 29 | syscall!(splice( 30 | pipes_1[0], 31 | std::ptr::null_mut(), 32 | libc::STDOUT_FILENO, 33 | std::ptr::null_mut(), 34 | libc::PIPE_BUF, 35 | 0 36 | )); 37 | 38 | let filename = std::ffi::CString::new(args[1].as_str()).unwrap(); 39 | let fd = syscall!(open( 40 | filename.as_ptr(), 41 | libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC, 42 | 0o644 43 | )); 44 | // pipes_2[read] -> fd 45 | syscall!(splice( 46 | pipes_2[0], 47 | std::ptr::null_mut(), 48 | fd, 49 | std::ptr::null_mut(), 50 | libc::PIPE_BUF, 51 | 0 52 | )); 53 | } 54 | -------------------------------------------------------------------------------- /docs/system_call_notes.md: -------------------------------------------------------------------------------- 1 | # Linux system programming 2 | 3 | This repo is cover these topics: 4 | - linux commands rewritten in Rust 5 | 6 | ## 系统调用通用知识 7 | 8 | ### 相同名字的命令(可执行文件)和系统调用函数成对出现 9 | 10 | 大部分系统调用函数都会有一个同名的命令,例如chmod命令对应chmod系统调用函数 11 | 12 | `man 1 chmod`能打开chmod命令的文档,`man 2 chmod`能打开chmod系统调用函数的文档 13 | 14 | --- 15 | 16 | ### 所有系统调用函数指针类型入参都有`restrict`修饰 17 | 18 | 这是为了编译器优化,可以参考: 19 | 20 | 通过程序员保证传入的指针独享其指向的内存,不会有其它指针也指向该内存,让编译器进行更多优化 21 | 22 | 但对 Rust 来说,由于所有权机制就像一个RwLock,同时只能有一个指针对某块内存有mutable的权限,所以不需要C语言的restrict关键词去修饰指针 23 | 24 | 如果用了 restrict 去修饰两个指针,而它们在作用域内又指向同一地址,那么是 UB 25 | 26 | --- 27 | 28 | ### 获取C语言函数返回值的几种情况 29 | 30 | 1. 返回值是`T`,直接用let接收返回值即可,例如: rand, time 31 | 2. (尽量不用)返回值是`*const T`,解引用返回值原始指针,例如: localtime 32 | 3. 返回值是`*const T`但有入参是`*mut T`,允许Rust传递可变指针,C函数内部将返回值写入到可变指针中,例如: localtime_r 33 | 34 | 一般系统调用函数例如localtime既提供返回引用的localtime也提供调用方传入可变指针作为返回值的localtime_r 35 | 36 | 因为FFI调用更倾向于调用方进行内存分配/回收管理,所以Rust一定就要用传入可变指针让系统调用函数把返回值写进去的方式 37 | 38 | 所以同样是localtime的系统调用Rust会用localtime_r,gethostbyname_r而不用localtime 39 | 40 | --- 41 | 42 | ### 系统调用函数错误处理 43 | 44 | 系统调用失败时会返回 -1(例如stat) 或 NULL(例如localtime_r) 45 | 46 | 此时会更新 thread_local 的 errno 变量,可以通过 errno 相关的几个系统调用知道错误码或错误原因 47 | 48 | --- 49 | 50 | ### 系统调用常见缩写 51 | - dirp -> directory stream pointer 52 | - ent -> entry: dirent.h, ENOENT(error no entry) 53 | - nam -> name: getpwnam, tmpnam 54 | - ppid -> parent PID 55 | - xxxctl -> control, eg. shmctl, 例外: fcntl 中把 control 缩写成 cntl 56 | - xxx_r -> re-entrant(可重入), 例如 localtime 和 localtime_r,_r 的版本多传入一个可变指针接收返回值实现可重入,而非 _r 版本返回值指向动态库的静态内存(有状态) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux commands rewritten in Rust 2 | 3 | ## project structure 4 | 5 | - src/bin: Linux commands rewritten in Rust 6 | - src/database: like sqlx project, include database adapters eg. MySQL 7 | - src/dylibs_binding: Rust binding for eg. libmysqlclient.so, libsqlite3.so 8 | - src/file_system: some file_system relative bindings e.g. `basename()` 9 | - src/file_system/parser: parse to some files e.g. `/proc/net/route` to get Linux system information from files 10 | - src/network: network API which libc doesn't include, eg. inet_aton, gethostbyname 11 | - src/time: time API which libc doesn't include, eg. strftime, strptime 12 | - examples: C/C++/Rust SIGABRT/SIGSEGV bad examples and how to fix tips 13 | - docs: documents or notes called by eg. `#![doc = include_str!("README.md”)]` 14 | 15 | ## cargo test must run in **single thread** 16 | 17 | To run database test you need to copy config file and edit it(eg. your mysql password): 18 | 19 | > cp database_config.toml.example database_config.toml && vim database_config.toml 20 | 21 | this config is only for mysql testing, run commands in src/bin doesn't need this 22 | 23 | because multi database adapters test is using a **same file** to store data 24 | 25 | > RUST_TEST_THREADS=1 cargo test 26 | 27 | or 28 | 29 | > cargo test -- --test-threads=1 30 | 31 | ## known bugs on target armv7-unknown-linux-gnueabihf 32 | 33 | - database::adapters::dbm may double-free or malloc corrupted 34 | 35 | ## reference: 36 | - [gnu core utils rewritten in Rust](https://github.com/uutils/coreutils) 37 | - 38 | - 39 | -------------------------------------------------------------------------------- /src/dylibs_binding/gdbm_compat.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_int}; 2 | 3 | /* 4 | ## dbm/gdbm: a key-value single file database facilities 5 | My gdbm_compat binding on crates.io: https://crates.io/crates/gdbm_compat 6 | 7 | ## 不要链接新版gdbm库 8 | gdbm的新版跟旧版gdbm_compat完全不兼容 9 | 旧版gdbm_compat的open叫dbm_open,新版 10 | *Beginning Linux Programming* 的示例只需要 `gcc -lgdbm_compat` 就能编译了 11 | */ 12 | #[link(name = "gdbm_compat")] 13 | extern "C" { 14 | pub type dbm_ptr; 15 | /// 注意filename参数文件名不要带后缀名,dbm会自动创建基于输入文件名.dir和.pag后缀的两个文件 16 | pub fn dbm_open(filename: *const c_char, flags: c_int, mode: libc::mode_t) -> *mut dbm_ptr; 17 | pub fn dbm_close(dbm_ptr: *mut dbm_ptr); 18 | pub fn dbm_store( 19 | dbm_ptr: *mut dbm_ptr, 20 | key_datum: datum, 21 | value_datum: datum, 22 | store_mode: c_int, 23 | ) -> c_int; 24 | pub fn dbm_fetch(dbm_ptr: *mut dbm_ptr, key_datum: datum) -> datum; 25 | /// reset database's cursor to first entry 26 | pub fn dbm_firstkey(dbm_ptr: *mut dbm_ptr) -> datum; 27 | pub fn dbm_nextkey(dbm_ptr: *mut dbm_ptr) -> datum; 28 | pub fn dbm_delete(dbm_ptr: *mut dbm_ptr, key_datum: datum) -> c_int; 29 | /// unused 30 | pub fn dbm_error(dbm_ptr: *mut dbm_ptr) -> c_int; 31 | /// unused 32 | pub fn dbm_clearerr(dbm_ptr: *mut dbm_ptr) -> c_int; 33 | } 34 | 35 | /// store_mode arg of dbm_store 36 | pub struct StoreMode; 37 | 38 | impl StoreMode { 39 | pub const DBM_INSERT: c_int = 0; 40 | pub const DBM_REPLACE: c_int = 1; 41 | } 42 | 43 | #[derive(Clone, Copy)] 44 | #[repr(C)] 45 | pub struct datum { 46 | /// this is a raw pointer of bytes 47 | pub dptr: *mut c_char, 48 | pub dsize: c_int, 49 | } 50 | -------------------------------------------------------------------------------- /src/bin/id.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | ## How to get calling process uid/gid? 3 | `libc::getuid()` and `libc::getgid()` 4 | 5 | ## how to find uid by username? 6 | `libc::getpwnam()`, `libc::getpwnam_r()` need to input user's password 7 | reference: 8 | 9 | ## how to list/traverse all username 10 | ```text 11 | loop { 12 | let entries = libc::getpwent(); 13 | if entries.is_null() { 14 | break; 15 | } 16 | libc::printf("%s\n\0".as_ptr().cast(), (*entries).pw_name); 17 | } 18 | ``` 19 | */ 20 | use linux_programming::errno::{last_errno, last_errno_message}; 21 | 22 | /// current output: 23 | /// expected output: uid=1000(w) gid=1001(w) groups=1001(w),998(wheel),991(lp),3(sys),90(network),98(power),1000(autologin),966(sambashare) 24 | fn main() { 25 | let args = std::env::args().collect::>(); 26 | if args.len() != 2 { 27 | eprintln!("usage_example: id root"); 28 | return; 29 | } 30 | let username = &args[1]; 31 | let username_with_nul = std::ffi::CString::new(username.as_bytes()).unwrap(); 32 | 33 | // passwd.pw_passwd always 'x' (passwd is hidden) 34 | let passwd = unsafe { libc::getpwnam(username_with_nul.as_ptr()) }; 35 | if passwd.is_null() { 36 | if last_errno() == 0 { 37 | eprintln!("id: `{}`: no such user", username); 38 | } else { 39 | eprintln!("{}", last_errno_message()); 40 | } 41 | return; 42 | } 43 | let passwd = unsafe { *passwd }; 44 | println!( 45 | "uid={uid}({username}) gid={gid}({username})", 46 | uid = passwd.pw_uid, 47 | gid = passwd.pw_gid, 48 | username = username 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/file_system/parser/proc/net/ipv6_route.rs: -------------------------------------------------------------------------------- 1 | /// An entry in the ipv4 route table 2 | #[derive(Debug, Clone)] 3 | pub struct RouteEntry { 4 | /// Interface to which packets for this route will be sent 5 | pub iface: String, 6 | /// The destination network or destination host 7 | pub destination: Ipv4Addr, 8 | pub gateway: Ipv4Addr, 9 | pub flags: u16, 10 | /// Number of references to this route 11 | pub refcnt: u16, 12 | /// Count of lookups for the route 13 | pub r#use: u16, 14 | /// The 'distance' to the target (usually counted in hops) 15 | pub metrics: u32, 16 | pub mask: Ipv4Addr, 17 | /// Default maximum transmission unit for TCP connections over this route 18 | pub mtu: u32, 19 | /// Default window size for TCP connections over this route 20 | pub window: u32, 21 | /// Initial RTT (Round Trip Time) 22 | pub irtt: u32, 23 | } 24 | 25 | /// Reads the ipv4 route table 26 | /// 27 | /// This data is from the `/proc/net/route` file 28 | pub fn route() -> ProcResult> { 29 | let file = FileWrapper::open("/proc/net/route")?; 30 | let reader = BufReader::new(file); 31 | 32 | let mut vec = Vec::new(); 33 | 34 | // First line is a header we need to skip 35 | for line in reader.lines().skip(1) { 36 | // Check if there might have been an IO error. 37 | let line = line?; 38 | let mut line = line.split_whitespace(); 39 | // network interface name, e.g. eth0 40 | let iface = expect!(line.next()); 41 | let destination = expect!(Ipv4Addr::from_str(expect!(line.next()))); 42 | let gateway = expect!(Ipv4Addr::from_str(expect!(line.next()))); 43 | let flags = from_str!(u16, expect!(line.next()), 16); 44 | let refcnt = from_str!(u16, expect!(line.next()), 10); 45 | let r#use = from_str!(u16, expect!(line.next()), 10); 46 | let metrics = from_str!(u32, expect!(line.next()), 10); 47 | let mask = expect!(Ipv4Addr::from_str(expect!(line.next()))); 48 | let mtu = from_str!(u32, expect!(line.next()), 10); 49 | let window = from_str!(u32, expect!(line.next()), 10); 50 | let irtt = from_str!(u32, expect!(line.next()), 10); 51 | vec.push(RouteEntry { 52 | iface: iface.to_string(), 53 | destination, 54 | gateway, 55 | flags, 56 | refcnt, 57 | r#use, 58 | metrics, 59 | mask, 60 | mtu, 61 | window, 62 | irtt, 63 | }); 64 | } 65 | 66 | Ok(vec) 67 | } 68 | -------------------------------------------------------------------------------- /src/bin/tree.rs: -------------------------------------------------------------------------------- 1 | /** 2 | run 100 tims calc mean run time: perf stat -r 100 ./target/debug/tree 3 | */ 4 | fn main() { 5 | let args = std::env::args().collect::>(); 6 | let input_filename = if let Some(filename) = args.get(1) { 7 | format!("{}\0", filename) 8 | } else { 9 | ".\0".to_string() 10 | }; 11 | 12 | let input_filename_cstr = input_filename.as_ptr().cast(); 13 | let dirp = unsafe { libc::opendir(input_filename_cstr) }; 14 | if dirp.is_null() { 15 | unsafe { 16 | libc::perror(input_filename_cstr); 17 | } 18 | return; 19 | } 20 | unsafe { 21 | libc::chdir(input_filename_cstr); 22 | } 23 | unsafe { 24 | traverse_dir_dfs(dirp, 0); 25 | } 26 | } 27 | 28 | unsafe fn traverse_dir_dfs(dirp: *mut libc::DIR, indent: usize) { 29 | loop { 30 | let dir_entry = libc::readdir(dirp); 31 | if dir_entry.is_null() { 32 | return; 33 | } 34 | let dir_entry = *dir_entry; 35 | let filename_cstr = dir_entry.d_name.as_ptr(); 36 | 37 | // skip current directory and parent directory 38 | if libc::strcmp(filename_cstr, ".\0".as_ptr().cast()) == 0 39 | || libc::strcmp(filename_cstr, "..\0".as_ptr().cast()) == 0 40 | { 41 | continue; 42 | } 43 | 44 | // check file whether a directory 45 | let mut stat_buf = std::mem::zeroed(); 46 | // lstat doesn't follow link 47 | linux_programming::syscall!(lstat(filename_cstr, &mut stat_buf)); 48 | let is_dir = (stat_buf.st_mode & libc::S_IFMT) == libc::S_IFDIR; 49 | 50 | // convert filename from [c_char; NAME_MAX] to String 51 | let filename_string = String::from_raw_parts( 52 | (filename_cstr as *mut i8).cast(), 53 | libc::strlen(filename_cstr), 54 | linux_programming::NAME_MAX, 55 | ); 56 | println!( 57 | "{}{}{}", 58 | " ".repeat(indent), 59 | filename_string, 60 | if is_dir { "/" } else { "" } 61 | ); 62 | std::mem::forget(filename_string); 63 | 64 | if is_dir { 65 | // backtracking: opendir<->closedir, chdir(filename_cstr)<->chdir("..\0") 66 | let dirp_inner_dir = libc::opendir(filename_cstr); 67 | libc::chdir(filename_cstr); 68 | traverse_dir_dfs(dirp_inner_dir, indent + 4); 69 | libc::chdir("..\0".as_ptr().cast()); 70 | libc::closedir(dirp_inner_dir); 71 | // set ptr to null after tree to prevent double free 72 | // dirp_inner_dir = std::ptr::null_mut(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/dylibs_binding/event.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_int, c_short, c_void, timeval}; 2 | 3 | #[link(name = "event")] 4 | extern "C" { 5 | pub type event; 6 | /// event_type is similar to pollfd.events 7 | pub fn event_new( 8 | base: *mut event_base, 9 | fd: c_int, 10 | event_type: c_short, 11 | cb: extern "C" fn(fd: c_int, event_type: c_short, cb_arg: *mut c_void), 12 | cb_arg: *mut c_void, 13 | ) -> *mut event; 14 | pub fn event_add(ev: *mut event, timeout: *const timeval) -> c_int; 15 | pub fn event_free(ev: *mut event); 16 | /// use in signal event arg 17 | pub fn event_self_cbarg() -> *mut c_void; 18 | /// Reactor 19 | pub type event_base; 20 | pub fn event_init() -> *mut event_base; 21 | pub fn event_base_loop(base: *mut event_base, flags: c_int) -> c_int; 22 | pub fn event_base_dispatch(base: *mut event_base) -> c_int; 23 | pub fn event_base_free(base: *mut event_base); 24 | pub fn event_base_loopexit(base: *mut event_base, delay: *const timeval) -> c_int; 25 | } 26 | 27 | /// event_type 28 | pub const EV_WRITE: c_short = 0x40; 29 | pub const EV_SIGNAL: c_short = 0x80; 30 | /// run forever 31 | pub const EV_PERSIST: c_short = 0x10; 32 | pub const EV_ET: c_short = 0x20; 33 | 34 | #[cfg(test)] 35 | extern "C" fn sigint_cb(_fd: i32, _event_type: c_short, _cb_arg: *mut c_void) { 36 | println!("get SIGINT Ctrl+C"); 37 | // let base = cb_arg as *mut event_base; 38 | // let delay = timeval { 39 | // tv_sec: 1, 40 | // tv_usec: 0, 41 | // }; 42 | // crate::syscall_expr!(event_base_loopexit(base, &delay)); 43 | } 44 | 45 | #[cfg(test)] 46 | extern "C" fn timeout_cb(_fd: i32, _event_type: c_short, _cb_arg: *mut c_void) { 47 | println!("run interval in 1s..."); 48 | } 49 | 50 | #[test] 51 | fn test_libevent() { 52 | unsafe { 53 | let base = event_init(); 54 | assert!(!base.is_null()); 55 | 56 | // FIXME SIGINT callback not working 57 | let sigint_event = event_new( 58 | base, 59 | libc::SIGINT, 60 | EV_SIGNAL | EV_PERSIST, 61 | sigint_cb, 62 | event_self_cbarg(), 63 | ); 64 | assert!(!sigint_event.is_null()); 65 | assert_ne!(event_add(sigint_event, std::ptr::null()), -1); 66 | 67 | let timeout_event = event_new(base, -1, EV_PERSIST, timeout_cb, std::ptr::null_mut()); 68 | assert!(!timeout_event.is_null()); 69 | let interval = timeval { 70 | tv_sec: 1, 71 | tv_usec: 0, 72 | }; 73 | assert_ne!(event_add(timeout_event, &interval), -1); 74 | 75 | assert_ne!(event_base_dispatch(base), -1); 76 | // or event_base_loop(base, 0); 77 | 78 | event_free(sigint_event); 79 | event_base_free(base); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/file_system/parser/proc/net/route.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | #[derive(Debug)] 4 | pub struct ProcNetRoute { 5 | pub iface: String, 6 | pub destination: Ipv4Addr, 7 | /// TODO support ipv6? 8 | pub gateway: Ipv4Addr, 9 | } 10 | 11 | /// if machine not connected to internet/router, all gateway is 0.0.0.0 12 | #[must_use] 13 | fn parse_proc_net_route() -> Vec { 14 | let mut routes = vec![]; 15 | for line in std::fs::read_to_string("/proc/net/route") 16 | .unwrap() 17 | .lines() 18 | // skip header row 19 | .skip(1) 20 | { 21 | let row = line.split('\t').collect::>(); 22 | routes.push(ProcNetRoute { 23 | iface: row[0].to_string(), 24 | // parse naive endian bytes like `0112A8C0` -> `192.168.18.1` 25 | destination: u32::from_str_radix(row[1], 16) 26 | .unwrap() 27 | .to_ne_bytes() 28 | .try_into() 29 | .unwrap(), 30 | gateway: u32::from_str_radix(row[2], 16) 31 | .unwrap() 32 | .to_ne_bytes() 33 | .try_into() 34 | .unwrap(), 35 | }); 36 | } 37 | routes 38 | } 39 | 40 | #[must_use] 41 | pub fn default_route_network_interface() -> String { 42 | parse_proc_net_route() 43 | .into_iter() 44 | .find(|network_interface| { 45 | network_interface.gateway != Ipv4Addr::UNSPECIFIED 46 | && network_interface.destination == Ipv4Addr::UNSPECIFIED 47 | }) 48 | .unwrap() 49 | .iface 50 | } 51 | 52 | #[test] 53 | fn test_parse_proc_net_route() { 54 | dbg!(parse_proc_net_route()); 55 | } 56 | 57 | #[cfg(test)] 58 | mod parse_with_error_handling { 59 | fn default_route_network_interface() -> Option { 60 | use std::net::Ipv4Addr; 61 | for line in std::fs::read_to_string("/proc/net/route") 62 | .ok()? 63 | .lines() 64 | .skip(1) 65 | { 66 | // let row = line.split('\t').collect::>(); 67 | let row = line.split_whitespace().collect::>(); 68 | let gateway: Ipv4Addr = u32::from_str_radix(row[2], 16) 69 | .ok()? 70 | .to_ne_bytes() 71 | .try_into() 72 | .ok()?; 73 | if gateway != Ipv4Addr::UNSPECIFIED { 74 | return Some(row[0].to_string()); 75 | } 76 | } 77 | None 78 | } 79 | 80 | fn mac_address() -> Option { 81 | Some( 82 | std::fs::read_to_string(format!( 83 | "/sys/class/net/{}/address", 84 | default_route_network_interface()? 85 | )) 86 | .ok()? 87 | .trim_end() 88 | .to_string(), 89 | ) 90 | } 91 | 92 | #[test] 93 | fn test_default_route_network_interface() { 94 | dbg!(default_route_network_interface()); 95 | dbg!(mac_address()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/bin/ping.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::network::{dns_resolve, icmphdr, icmq_checksum, ICMP_ECHO}; 2 | use linux_programming::{syscall, SOCKADDR_IN_LEN}; 3 | 4 | const PACKET_LEN: usize = 64; 5 | 6 | #[repr(C)] 7 | struct Packet { 8 | hdr: icmphdr, 9 | msg: [u8; PACKET_LEN - std::mem::size_of::()], 10 | } 11 | 12 | #[allow(clippy::cast_possible_truncation)] 13 | fn main() { 14 | let args = std::env::args().collect::>(); 15 | if args.len() != 2 { 16 | eprintln!("ping: missing operand"); 17 | unsafe { libc::exit(libc::EXIT_FAILURE) }; 18 | } 19 | 20 | let hostname = args[1].as_str(); 21 | println!("{}", hostname); 22 | let addr = dns_resolve(hostname); 23 | let remote_addr = libc::sockaddr_in { 24 | sin_family: libc::AF_INET as libc::sa_family_t, 25 | sin_port: 0, 26 | sin_addr: addr, 27 | sin_zero: unsafe { std::mem::zeroed() }, 28 | }; 29 | 30 | /* 31 | use **sysctl** check ping permission 32 | ``` 33 | [w@ww ~]$ sysctl net.ipv4.ping_group_range 34 | net.ipv4.ping_group_range = 0 2147483647 35 | ``` 36 | ubuntu/android allow any group_id(0-2147483647) to send ping in UDP protocol 37 | ping 命令用的 ICMP 一般是 SOCK_RAW 或 UDP 38 | 如果 linux 系统 sysctl 配置的 net.ipv4.ping_group_range 不包含当前用户的所在组 39 | 那只能通过 root 权限的 SOCK_RAW 发 ping 数据包,例如 `sudo -E cargo run` 40 | */ 41 | let socket_fd = syscall!(socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_ICMP)); 42 | syscall!(fcntl(socket_fd, libc::F_SETFL, libc::O_NONBLOCK)); 43 | syscall!(setsockopt( 44 | socket_fd, 45 | libc::SOL_IP, 46 | libc::IP_TTL, 47 | (&64 as *const i32).cast(), 48 | std::mem::size_of::() as u32 49 | )); 50 | 51 | for _ in 0..10 { 52 | let mut packet: Packet = unsafe { std::mem::zeroed() }; 53 | let mut addr = remote_addr; 54 | let mut addrlen = SOCKADDR_IN_LEN; 55 | let recvfrom_ret = unsafe { 56 | libc::recvfrom( 57 | socket_fd, 58 | (&mut packet as *mut Packet).cast(), 59 | PACKET_LEN, 60 | 0, 61 | (&mut addr as *mut libc::sockaddr_in).cast(), 62 | &mut addrlen, 63 | ) 64 | }; 65 | if recvfrom_ret > 0 { 66 | println!("ping success"); 67 | std::process::exit(libc::EXIT_SUCCESS); 68 | } 69 | 70 | packet = unsafe { std::mem::zeroed() }; 71 | packet.hdr.type_ = ICMP_ECHO; 72 | for i in 0..packet.msg.len() - 1 { 73 | packet.msg[i] = i as u8 + b'0'; 74 | } 75 | packet.hdr.checksum = icmq_checksum(&packet.msg); 76 | 77 | syscall!(sendto( 78 | socket_fd, 79 | (&packet as *const Packet).cast(), 80 | PACKET_LEN, 81 | 0, 82 | (&remote_addr as *const libc::sockaddr_in).cast::(), 83 | SOCKADDR_IN_LEN, 84 | )); 85 | 86 | syscall!(usleep(300 * 1000)); 87 | } 88 | eprintln!("ping failed!"); 89 | } 90 | -------------------------------------------------------------------------------- /src/dylibs_binding/mysqlclient.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_int, c_uint}; 2 | 3 | /* 4 | > gcc -lmysqlclient mysql_conn_example.c 5 | 6 | ```text 7 | // mysql_conn_example.c 8 | #include 9 | #include 10 | 11 | int main(int argc, char *argv[]) { 12 | MYSQL *conn_ptr = mysql_init(NULL); 13 | conn_ptr = mysql_real_connect(conn_ptr, "localhost", 14 | "w", 15 | "w", 16 | "test", 17 | 0, NULL, 0); 18 | if (conn_ptr) { 19 | printf("Connection success\n"); 20 | } else { 21 | printf("Connection failed\n"); 22 | } 23 | mysql_close(conn_ptr); 24 | return 0; 25 | } 26 | ``` 27 | */ 28 | #[link(name = "mysqlclient")] 29 | extern "C" { 30 | /// this is a struct, not a opaque type 31 | pub type mysql; 32 | /// this is a struct, not a opaque type 33 | pub type mysql_res; 34 | /// mysql function return 0 or not_null means no error 35 | pub fn mysql_errno(connection: *mut mysql) -> c_uint; 36 | pub fn mysql_error(connection: *mut mysql) -> *const c_char; 37 | 38 | pub fn mysql_init(connection: *mut mysql) -> *mut mysql; 39 | pub fn mysql_real_connect( 40 | connection: *mut mysql, 41 | server_host: *const c_char, 42 | sql_user_name: *const c_char, 43 | sql_password: *const c_char, 44 | db_name: *const c_char, 45 | port_number: c_uint, 46 | unix_socket_name: *const c_char, 47 | flags: c_uint, 48 | ) -> *mut mysql; 49 | pub fn mysql_close(connection: *mut mysql); 50 | /// return 0 if ping success 51 | pub fn mysql_ping(connection: *mut mysql) -> c_int; 52 | /// query arg with no terminating semicolon, query SQL statement's line break is `\` 53 | pub fn mysql_query(connection: *mut mysql, query: *const c_char) -> c_int; 54 | /// returns the number of rows affected by the UPDATE, INSERT, or DELETE query 55 | pub fn mysql_affected_rows(connection: *mut mysql) -> my_ulonglong; 56 | /// fetch all rows at a time, used in your query data total size is small(network may not incomplete data) 57 | pub fn mysql_store_result(connection: *mut mysql) -> *mut mysql_res; 58 | /// get row one by one at a time, used in return data very large(large than memory limits) 59 | /// use_result include the header row while store result not 60 | pub fn mysql_use_result(connection: *mut mysql) -> *mut mysql_res; 61 | pub fn mysql_free_result(res_ptr: *mut mysql_res); 62 | 63 | pub fn mysql_num_rows(res_ptr: *mut mysql_res) -> my_ulonglong; 64 | /// you must mysql_fetch_row repeatedly until all the data has been retrieved 65 | /// If not, subsequent operations in process to retrieve data may corrupt 66 | pub fn mysql_fetch_row(res_ptr: *mut mysql_res) -> MysqlRow; 67 | /// unsigned int STDCALL mysql_field_count(MYSQL *mysql); 68 | pub fn mysql_field_count(connection: *mut mysql) -> libc::c_ulong; 69 | // pub fn mysql_real_escape_string_quote() 70 | } 71 | 72 | #[allow(non_camel_case_types)] 73 | pub type my_ulonglong = libc::c_ulonglong; 74 | pub type MysqlRow = *mut *mut libc::c_char; 75 | 76 | pub const MYSQL_DEFAULT_PORT: c_uint = 0; 77 | -------------------------------------------------------------------------------- /src/database/adapters/mmap.rs: -------------------------------------------------------------------------------- 1 | use crate::database::models::user::{CrudUserDao, User, Username}; 2 | use crate::syscall; 3 | 4 | struct MmapDb { 5 | mapped_addr: *mut libc::c_void, 6 | } 7 | 8 | impl MmapDb { 9 | const MAPPED_BYTES: usize = 10 | ::Model::LEN * ::Model::SIZE; 11 | #[cfg(test)] 12 | fn new() -> Self { 13 | // let fd = syscall!(open(Self::DB_FILENAME, libc::O_RDWR | libc::O_CREAT, libc::S_IRUSR | libc::S_IWUSR,)); 14 | // insert bytes to file to fit the mapped_len required 15 | // syscall!(write(fd, [0_u8; Self::MAPPED_BYTES].as_ptr().cast(), Self::MAPPED_BYTES)); 16 | 17 | let mapped_addr = unsafe { 18 | libc::mmap( 19 | std::ptr::null_mut(), 20 | Self::MAPPED_BYTES, 21 | libc::PROT_READ | libc::PROT_WRITE, 22 | // MAP_SHARED: The segment changes are made in the file 23 | // MAP_ANONYMOUS: not map from file, would init region to zero and ignore fd and offset arg 24 | libc::MAP_SHARED | libc::MAP_ANONYMOUS, 25 | -1, 26 | 0, 27 | ) 28 | }; 29 | if mapped_addr == libc::MAP_FAILED { 30 | // mmap return 0 is ok, !0 is libc::MAP_FAILED 31 | panic!("{}", std::io::Error::last_os_error()); 32 | } 33 | // mmap成功后就可以关闭fd,关闭fd不会影响mmap 34 | // syscall!(close(fd)); 35 | Self { mapped_addr } 36 | } 37 | } 38 | 39 | impl Drop for MmapDb { 40 | fn drop(&mut self) { 41 | syscall!(munmap(self.mapped_addr, Self::MAPPED_BYTES)); 42 | } 43 | } 44 | 45 | impl CrudUserDao for MmapDb { 46 | type Model = User; 47 | unsafe fn insert_sample_data(&self) { 48 | // 注意不能解引用,否则解引用之后会是Copy语义,不能修改到mmap对应的文件数据 49 | let users = self.mapped_addr.cast::<[Self::Model; Self::Model::LEN]>(); 50 | for user_id in 0..Self::Model::LEN { 51 | let user = Self::Model::new(user_id as u8); 52 | (*users)[user_id] = user; 53 | } 54 | // optional if single process: sync mmap change to files 55 | libc::msync(self.mapped_addr, Self::MAPPED_BYTES, libc::MS_SYNC); 56 | } 57 | 58 | unsafe fn select_all(&self) -> Vec { 59 | let users = *self.mapped_addr.cast::<[Self::Model; Self::Model::LEN]>(); 60 | users.to_vec() 61 | } 62 | 63 | unsafe fn find_user_by_id(&self, user_id: u8) -> Self::Model { 64 | assert!(User::user_id_is_valid(user_id)); 65 | let users = *self.mapped_addr.cast::<[Self::Model; Self::Model::LEN]>(); 66 | users[usize::from(user_id)] 67 | } 68 | 69 | unsafe fn update_username_by_id(&self, user_id: u8, username: Username) { 70 | assert!(User::user_id_is_valid(user_id)); 71 | let users = self.mapped_addr.cast::<[Self::Model; Self::Model::LEN]>(); 72 | (*users)[usize::from(user_id)].username = username; 73 | } 74 | } 75 | 76 | #[test] 77 | fn test_mmap_database() { 78 | let db_adapter = MmapDb::new(); 79 | crate::database::models::user::test_user_crud(&db_adapter); 80 | } 81 | -------------------------------------------------------------------------------- /src/database/models/user.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | pub type Username = [u8; 7]; 4 | 5 | /// 只要结构体的各个字段都是栈上内存,没有指针,就无需序列化(保证内存对齐跟C一样)也能读写进文件中 6 | #[derive(Clone, Copy)] 7 | #[repr(C)] 8 | pub struct User { 9 | /// user_id from 0 to 9 10 | pub user_id: u8, 11 | /// string bytes without nul terminator, username is lowercase letter, can't contains comma 12 | pub username: Username, 13 | } 14 | 15 | impl std::fmt::Debug for User { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | f.debug_struct("User") 18 | .field("user_id", &self.user_id) 19 | .field("username", &unsafe { 20 | String::from_utf8_unchecked(self.username.to_vec()) 21 | }) 22 | .finish() 23 | } 24 | } 25 | 26 | impl std::str::FromStr for User { 27 | type Err = (); 28 | 29 | fn from_str(s: &str) -> Result { 30 | let (user_id_str, username_str) = s.split_once(",").unwrap(); 31 | Ok(Self { 32 | user_id: user_id_str.parse().unwrap(), 33 | username: username_str.as_bytes().try_into().unwrap(), 34 | }) 35 | } 36 | } 37 | 38 | impl User { 39 | pub const SIZE: usize = std::mem::size_of::(); 40 | pub const LEN: usize = 10; 41 | pub const fn new(user_id: u8) -> Self { 42 | assert!(Self::user_id_is_valid(user_id)); 43 | let mut username = *b"user_00"; 44 | username[5] = b'0' + (user_id / 10) % 10; 45 | username[6] = b'0' + user_id % 10; 46 | Self { user_id, username } 47 | } 48 | 49 | #[inline] 50 | #[allow(clippy::trivially_copy_pass_by_ref)] // must pass reference 51 | pub const fn as_ptr(&self) -> *const Self { 52 | self as *const Self 53 | } 54 | 55 | /** 56 | // these can compile, Rust think *mut is superset of *const? 57 | fn as_mut_ptr(&mut self) -> *const Self { 58 | self as *mut Self 59 | } 60 | */ 61 | #[inline] 62 | pub fn as_mut_ptr(&mut self) -> *mut Self { 63 | self as *mut Self 64 | } 65 | 66 | /// only stdio's fread/fwrite and mmap need calc user_id by offset, so it want user_id is in range [0..=9] 67 | pub const fn user_id_is_valid(user_id: u8) -> bool { 68 | user_id < Self::LEN as u8 69 | } 70 | } 71 | 72 | pub trait CrudUserDao { 73 | type Model: Sized + Clone + std::fmt::Debug; 74 | const DB_FILENAME: *const libc::c_char = "/tmp/my_db\0".as_ptr().cast(); 75 | unsafe fn insert_sample_data(&self); 76 | unsafe fn select_all(&self) -> Vec; 77 | unsafe fn find_user_by_id(&self, user_id: u8) -> Self::Model; 78 | unsafe fn update_username_by_id(&self, user_id: u8, username: Username); 79 | } 80 | 81 | #[cfg(test)] 82 | pub fn test_user_crud>(db_adapter: &DB) { 83 | unsafe { 84 | db_adapter.insert_sample_data(); 85 | dbg!(db_adapter.select_all()); 86 | 87 | assert_eq!(db_adapter.find_user_by_id(3).username, *b"user_03"); 88 | db_adapter.update_username_by_id(3, *b"tuesday"); 89 | assert_eq!(db_adapter.find_user_by_id(3).username, *b"tuesday"); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/get_default_route_ip_and_mac.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::{file_system::parser::proc, syscall}; 2 | use std::net::Ipv4Addr; 3 | 4 | /// get default router network interface's mac/physics address 5 | /// use `ip route` or `route` command to get(route command require net-tools package) 6 | /// ## Alternative 7 | /// find first Iface which Gateway != 0 in `/proc/net/route` 8 | fn main() { 9 | let route_default_network_interface = proc::net::route::default_route_network_interface(); 10 | assert_eq!( 11 | route_default_network_interface, 12 | get_default_route_network_interface_by_ip_route() 13 | ); 14 | dbg!(get_mac_addr_by_network_interface( 15 | route_default_network_interface 16 | )); 17 | } 18 | 19 | fn get_default_route_network_interface_by_ip_route() -> String { 20 | let output = std::process::Command::new("ip") 21 | .arg("route") 22 | .arg("show") 23 | .arg("default") 24 | .output() 25 | .unwrap(); 26 | // output e.g. "default via 192.168.18.1 dev wlp4s0 proto dhcp metric 600 \n" 27 | let output = unsafe { String::from_utf8_unchecked(output.stdout) }; 28 | 29 | // `ip route show default` parser 30 | // `split()` is similar to `libc::strtok()` 31 | output.split_whitespace().nth(4).unwrap().to_string() 32 | } 33 | 34 | /// get mac/physics address by network interface 35 | fn get_mac_addr_by_network_interface(network_interface: String) -> String { 36 | std::fs::read_to_string(format!("/sys/class/net/{}/address", network_interface)) 37 | .unwrap_or_default() 38 | .trim_end() 39 | .to_string() 40 | } 41 | 42 | unsafe fn get_default_network_interface_ip() -> std::net::Ipv4Addr { 43 | let mut machine_ip = std::mem::zeroed(); 44 | // borrowed value not live long 45 | let default_route = proc::net::route::default_route_network_interface(); 46 | 47 | let mut addrs = std::mem::zeroed(); 48 | // return first address of Vec 49 | syscall!(getifaddrs(&mut addrs)); 50 | let mut cur = addrs; 51 | while !cur.is_null() { 52 | let cur_deref = *cur; 53 | if cur_deref.ifa_addr.is_null() { 54 | cur = cur_deref.ifa_next; 55 | continue; 56 | } 57 | // sa_family is oneof AF_PACKET, AF_INET, AF_INET6 58 | if (*cur_deref.ifa_addr).sa_family != libc::AF_INET as u16 { 59 | cur = cur_deref.ifa_next; 60 | continue; 61 | } 62 | 63 | if libc::strcmp(cur_deref.ifa_name, default_route.as_ptr().cast()) == 0 { 64 | let addr = *cur_deref.ifa_addr.cast::(); 65 | machine_ip = std::net::Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()); 66 | break; 67 | } 68 | cur = cur_deref.ifa_next; 69 | } 70 | libc::freeifaddrs(addrs); 71 | machine_ip 72 | } 73 | 74 | #[test] 75 | fn a2() { 76 | let default_route = proc::net::route::default_route_network_interface(); 77 | 78 | unsafe { 79 | libc::printf( 80 | "%s\n\0".as_ptr().cast(), 81 | default_route.as_ptr().cast::(), 82 | ); 83 | dbg!(libc::strcmp( 84 | "wlp4s0\0".as_ptr().cast(), 85 | "wlp4s0\0".as_ptr().cast() 86 | )); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/database/adapters/fread_fwrite_struct.rs: -------------------------------------------------------------------------------- 1 | use crate::database::models::user::{CrudUserDao, User, Username}; 2 | 3 | struct FreadFwriteDb { 4 | /// database FILE stream pointer 5 | db_fp: *mut libc::FILE, 6 | } 7 | 8 | impl FreadFwriteDb { 9 | #[cfg(test)] 10 | fn new() -> Self { 11 | let fp = unsafe { libc::fopen(Self::DB_FILENAME, "w+\0".as_ptr().cast()) }; 12 | if fp.is_null() { 13 | panic!("{}", std::io::Error::last_os_error()); 14 | } 15 | Self { db_fp: fp } 16 | } 17 | } 18 | 19 | impl Drop for FreadFwriteDb { 20 | fn drop(&mut self) { 21 | crate::syscall!(fclose(self.db_fp)); 22 | unsafe { 23 | libc::unlink(Self::DB_FILENAME); 24 | } 25 | } 26 | } 27 | 28 | impl CrudUserDao for FreadFwriteDb { 29 | type Model = User; 30 | /** 31 | ```text 32 | $ od -c target/users_db 33 | 0000000 \0 u s e r _ 0 0 001 u s e r _ 0 1 34 | 0000020 002 u s e r _ 0 2 003 a c c o u n t 35 | 0000040 004 u s e r _ 0 4 005 u s e r _ 0 5 36 | 0000060 006 u s e r _ 0 6 \a u s e r _ 0 7 37 | 0000100 \b u s e r _ 0 8 \t u s e r _ 0 9 38 | 0000120 39 | ``` 40 | note that user_id=006 is escape to b'\a' in od 41 | */ 42 | unsafe fn insert_sample_data(&self) { 43 | for user_id in 0..Self::Model::LEN { 44 | let user = Self::Model::new(user_id as u8); 45 | // read/write 系统调用其实也可以读写(序列化)一个结构体 46 | libc::fwrite(user.as_ptr().cast(), Self::Model::SIZE, 1, self.db_fp); 47 | } 48 | } 49 | 50 | unsafe fn select_all(&self) -> Vec { 51 | let mut users = [std::mem::zeroed::(); Self::Model::LEN]; 52 | libc::fseek(self.db_fp, 0, libc::SEEK_SET); 53 | let read_count = libc::fread( 54 | users.as_mut_ptr().cast(), 55 | Self::Model::LEN, 56 | Self::Model::LEN, 57 | self.db_fp, 58 | ); 59 | assert_ne!(read_count, 0); 60 | users.to_vec() 61 | } 62 | 63 | unsafe fn find_user_by_id(&self, user_id: u8) -> Self::Model { 64 | assert!(User::user_id_is_valid(user_id)); 65 | let mut user = std::mem::zeroed::(); 66 | libc::fseek( 67 | self.db_fp, 68 | libc::c_long::from(user_id) * Self::Model::SIZE as libc::c_long, 69 | libc::SEEK_SET, 70 | ); 71 | libc::fread(user.as_mut_ptr().cast(), Self::Model::SIZE, 1, self.db_fp); 72 | user 73 | } 74 | 75 | unsafe fn update_username_by_id(&self, user_id: u8, username: Username) { 76 | assert!(User::user_id_is_valid(user_id)); 77 | let offset = libc::c_long::from(user_id) * Self::Model::SIZE as libc::c_long; 78 | let mut user = std::mem::zeroed::(); 79 | libc::fseek(self.db_fp, offset, libc::SEEK_SET); 80 | libc::fread(user.as_mut_ptr().cast(), Self::Model::SIZE, 1, self.db_fp); 81 | user.username = username; 82 | libc::fseek(self.db_fp, offset, libc::SEEK_SET); // reset cursor after fread 83 | libc::fwrite(user.as_ptr().cast(), Self::Model::SIZE, 1, self.db_fp); 84 | } 85 | } 86 | 87 | #[test] 88 | fn test_stdio_database() { 89 | let db_adapter = FreadFwriteDb::new(); 90 | crate::database::models::user::test_user_crud(&db_adapter); 91 | } 92 | -------------------------------------------------------------------------------- /examples/sigabrt_closedir_wrong.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | SIGABRT 的可能原因: 3 | - double free, example: `closedir(dirp);closedir(dirp);` 4 | - 尝试free非进程内存分配器管理的内存: 例如Rust进程free mysql动态链接库的static变量内存(mysql_fetch_row的返回值) 5 | 6 | ## 避免 double free 的编码习惯 7 | 8 | 单线程应用,在 free(ptr) 之后将 ptr 设置为 NULL,多线程应用则用引用计数 9 | 10 | ```no_run 11 | // bad 12 | let dirp = libc::opendir("/home\0".as_ptr().cast()); 13 | libc::closedir(dirp); 14 | libc::closedir(dirp); 15 | ``` 16 | 17 | ```no_run 18 | // good example 19 | let mut dirp = libc::opendir("/home\0".as_ptr().cast()); 20 | libc::closedir(dirp); 21 | dirp = std::ptr::null_mut(); 22 | libc::closedir(dirp); 23 | dirp = std::ptr::null_mut(); 24 | ``` 25 | 26 | ## double free 不一定及时报错 27 | 28 | *Beginning Linux Programming 4th edition* Page 260 有详细介绍(PDF 293页): 29 | 30 | > allocated memory is writing beyond the end of an allocated block(one example is double-free) 31 | 32 | 例如尝试free一块已经回收的内存(allocated memory beyond the block) 33 | 34 | > one reason malloc failed is the memory structures have been corrupted, When this happens, the program may not terminate immediately 35 | 36 | 例如错误递归或循环间隐式的free同一个资源,程序会被valgrind检查出`closedir InvalidFree` 37 | 38 | 但是进程却能正常退出,如果加上 `current_dir` 的函数调用程序则 SIGABRT 39 | 40 | 原因是 操作系统/进程 并不会立即回收内存,更像是异步的回收内存,在回收/申请堆内存时才会检查并报错 SIGABRT 41 | 42 | 而 Rust 的 `current_dir` 就像 `Future::poll` 去申请内存(因为文件绝对路径可能很长,需要扩容) 43 | 44 | Rust 申请内存时发现当前进程居然有几块 double free 的内存,为了避免错误进一步扩散,就报错 SIGABRT 45 | */ 46 | 47 | fn main() { 48 | let dir_name = "/\0"; 49 | let dir_name_cstr = dir_name.as_ptr().cast(); 50 | let dirp = unsafe { libc::opendir(dir_name_cstr) }; 51 | if dirp.is_null() { 52 | unsafe { 53 | libc::perror(dir_name_cstr); 54 | } 55 | return; 56 | } 57 | unsafe { 58 | libc::chdir(dir_name_cstr); 59 | } 60 | unsafe { 61 | traverse_dir_dfs(dirp, 0); 62 | } 63 | } 64 | 65 | unsafe fn traverse_dir_dfs(dirp: *mut libc::DIR, indent: usize) { 66 | loop { 67 | let dir_entry = libc::readdir(dirp); 68 | if dir_entry.is_null() { 69 | // malloc(): unsorted double linked list corrupted\n `Aborted (core dumped)` exit code 134 (interrupted by signal 6: SIGABRT) 70 | let _sigabrt_line = std::env::current_dir().unwrap(); 71 | return; 72 | } 73 | let dir_entry = *dir_entry; 74 | let filename_cstr = dir_entry.d_name.as_ptr(); 75 | 76 | // skip current directory and parent directory 77 | if libc::strcmp(filename_cstr, ".\0".as_ptr().cast()) == 0 78 | || libc::strcmp(filename_cstr, "..\0".as_ptr().cast()) == 0 79 | { 80 | continue; 81 | } 82 | 83 | // check file whether a directory 84 | let mut stat_buf = std::mem::zeroed(); 85 | // lstat doesn't follow link 86 | linux_programming::syscall!(lstat(filename_cstr, &mut stat_buf)); 87 | let is_dir = stat_buf.st_mode & libc::S_IFMT == libc::S_IFDIR; 88 | 89 | // convert filename from [c_char; NAME_MAX] to String 90 | let filename_len = libc::strlen(filename_cstr); 91 | let filename_bytes = 92 | &*(&dir_entry.d_name[..filename_len] as *const [libc::c_char] as *const [u8]); 93 | let filename_string = String::from_utf8_unchecked(filename_bytes.to_owned()); 94 | println!( 95 | "{}{}{}", 96 | " ".repeat(indent), 97 | filename_string, 98 | if is_dir { "/" } else { "" } 99 | ); 100 | 101 | if is_dir { 102 | let dirp_inner_dir = libc::opendir(filename_cstr); 103 | libc::chdir(filename_cstr); 104 | traverse_dir_dfs(dirp_inner_dir, indent + 4); 105 | libc::chdir("..\0".as_ptr().cast()); 106 | // Bug is here: this should be `closedir(dirp_inner_dir)` 107 | // https://github.com/rust-lang/rust/issues/86899 108 | // if a directory has two subdirectory, this would cause **`double free`** (closedir to a same dir twice) 109 | libc::closedir(dirp); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/errno.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | ## 说下我是怎么学习 errno 的方法论和过程 3 | 1. 发现mkfifo打开文件路径字符串末尾没带'\0'导致打开失败问题(一开始还不知道是缺少nul byte的原因) 4 | 2. 查看nix库mkfifo的示例及其源码 5 | 3. 发现nix库会认为系统调用返回-1就是系统调用失败 6 | 4. 再发现nix库遇到系统调用失败时会用libc::__errno_location()查找错误原因 7 | 5. __errno_location()会跳转到errno词条的man7.org文档,详细学习了errno原理 8 | 6. 标准库源码搜索errno_location(): last_os_error()调用了errno(),errno()调用那个location 9 | 7. 再看标准库impl fmt::Debug for Repr源码,发现libc::strerror_r能把errno数字变err_msg字符串 10 | 8. 后来发现很多linux底层库都用last_os_error()处理系统调用失败的情况 11 | 12 | ## errno文档解读 13 | 14 | 15 | > system calls and some library functions in the event of an error to indicate what went wrong 16 | > 17 | > -1 from most system calls; -1 or NULL from most library functions 18 | 19 | 大意是一些库或系统调用返回-1或NULL调试调用出错,系统调用通常返回-1表示调用失败,这时候可以找errno查看错误码确定错误原因 20 | 21 | > error numbers using the errno(1) command(part of the moreutils package) 22 | 23 | 补充说明,出错时可以调用`__errno_location()`函数获取最近一次系统调用的错误码 24 | 25 | 可以用errno命令解读错误码数字的详细含义,也可以用strerror_r将errno转换为错误信息的字符串 26 | 27 | > errno is thread-local 28 | 29 | ## errno should copy immediately 30 | 31 | 除了C标准库函数,还有很多库都会修改extern int errno,包括Rust的print(毕竟调用了libc::write) 32 | 33 | ## errno错误码示例 34 | 35 | ### ENOENT 2 No such file or directory 36 | 可能的错误原因: 37 | - 路径不存在 38 | - 路径字符串不合法: **C语言的字符串没加\0作为终止符** 39 | 40 | ### ENOMEM 12 Cannot allocate memory 41 | 注意标准库的Error没有解析错误码12,所以标准库没有像C语言那样能处理内存分配失败的情况(失败就panic,C一般通过malloc的返回值是否为null处理内存申请失败) 42 | 43 | 可能的错误原因: 44 | - io_uring not enough lockable memory, please increase memlock config in /etc/security/limits.conf 45 | 46 | ### EADDRINUSE 98 Address already in use 47 | 48 | ### 其它错误 49 | - disk is full 50 | - too many open files(ulimits max open fd) 51 | */ 52 | #[must_use] 53 | pub fn last_errno() -> i32 { 54 | // std::io::Error::last_os_error().raw_os_error().unwrap() 55 | // 一定要调用 errno_location 获取 errno, 直接获取 `static errno: libc::c_int` 的值会 segfault 56 | unsafe { *libc::__errno_location() } 57 | } 58 | 59 | #[must_use] 60 | #[inline] 61 | pub fn last_errno_message() -> String { 62 | let errno = last_errno(); 63 | errno_err_msg(errno).unwrap() 64 | } 65 | 66 | pub fn errno_err_msg(errno: i32) -> Result { 67 | const BUF_LEN: usize = 128; 68 | let mut buf = [0_u8; BUF_LEN]; 69 | let ret = unsafe { libc::strerror_r(errno, buf.as_mut_ptr().cast(), BUF_LEN) }; 70 | if ret == libc::EINVAL { 71 | // EINVAL 22 Invalid argument 72 | return Err(std::io::ErrorKind::InvalidInput); 73 | } 74 | assert_eq!(ret, 0); 75 | 76 | let err_msg_buf_len = unsafe { libc::strlen(buf.as_ptr().cast()) }; 77 | let err_msg = unsafe { String::from_utf8_unchecked(buf[..err_msg_buf_len].to_vec()) }; 78 | Ok(err_msg) 79 | } 80 | 81 | /** 82 | TODO 83 | parse file like this 84 | ```text 85 | #define _ASM_GENERIC_ERRNO_H 86 | 87 | #include 88 | 89 | #define EDEADLK 35 /* Resource deadlock would occur */ 90 | #define ENAMETOOLONG 36 /* File name too long */ 91 | ``` 92 | 93 | > grep -r "#define[[:blank:]]ENOENT" . 94 | */ 95 | #[cfg(test)] 96 | unsafe fn read_errno() { 97 | let f = libc::fopen( 98 | "/usr/include/asm-generic/errno-base.h\0".as_ptr().cast(), 99 | "r\0".as_ptr().cast(), 100 | ); 101 | let mut line_buf = [0_u8; 256]; 102 | loop { 103 | let line = libc::fgets(line_buf.as_mut_ptr().cast(), line_buf.len() as i32, f); 104 | if line.is_null() { 105 | break; 106 | } 107 | // #define ENOENT 2 /* No such file or directory */ 108 | let mut error_name = [0_u8; 12]; 109 | let mut error_number = 0_u32; 110 | let mut error_message = [0_u8; 128]; 111 | // rust alternative is text_io::scan 112 | let modified_count = libc::sscanf( 113 | line, 114 | "#define %s %u /* %[^,*]\0".as_ptr().cast(), 115 | &mut error_name, 116 | &mut error_number, 117 | &mut error_message, 118 | ); 119 | if modified_count == 3 { 120 | libc::printf( 121 | "error_name=%s, error_number=%u, error_message=%s\n\0" 122 | .as_ptr() 123 | .cast(), 124 | &error_name, 125 | error_number, 126 | &error_message, 127 | ); 128 | } 129 | } 130 | //libc::fscanf(stream, format) 131 | } 132 | 133 | #[test] 134 | fn test_read_errno() { 135 | unsafe { 136 | read_errno(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/bin/stat.rs: -------------------------------------------------------------------------------- 1 | use linux_programming::syscall; 2 | use linux_programming::time::format_timestamp_with_nanosecond; 3 | 4 | fn main() { 5 | let args = std::env::args().collect::>(); 6 | if args.len() != 2 { 7 | eprintln!("stat: missing operand"); 8 | eprintln!("Try 'stat --help' for more information."); 9 | return; 10 | } 11 | match args[1].as_str() { 12 | "--version" => { 13 | println!("stat (GNU coreutils) rewritten in Rust"); 14 | println!("source code: https://github.com/pymongo/linux_commands_rewritten_in_rust"); 15 | } 16 | "--help" => { 17 | println!("help doc is working in progress"); 18 | } 19 | filename => { 20 | my_stat(filename); 21 | } 22 | } 23 | } 24 | 25 | fn my_stat(filename: &str) { 26 | let filename_with_nul = format!("{}\0", filename); 27 | let mut file_stat = unsafe { std::mem::zeroed() }; 28 | syscall!(stat(filename_with_nul.as_ptr().cast(), &mut file_stat)); 29 | println!(" File: {}", filename); 30 | println!( 31 | " Size: {:<15} Blocks: {:<10} IO Block: {:<6} {}", 32 | file_stat.st_size, 33 | file_stat.st_blocks, 34 | file_stat.st_blksize, 35 | get_filetype(file_stat.st_mode) 36 | ); 37 | println!("Device: "); 38 | println!("Access: "); 39 | 40 | let access_time = format_timestamp_with_nanosecond(file_stat.st_atime, file_stat.st_atime_nsec); 41 | let modify_time = format_timestamp_with_nanosecond(file_stat.st_mtime, file_stat.st_mtime_nsec); 42 | println!("Access: {}", access_time); 43 | println!("Modify: {}", modify_time); 44 | println!("Change: {}", modify_time); 45 | // FIXME `/dev/console` create_time should be null 46 | println!(" Birth: {}", access_time); 47 | } 48 | 49 | /** 50 | ## 「重要」Unix文件类型 51 | 52 | markdown table generate from csv by: 53 | 54 | | stat.h | file_type | find -type/ls-l(first char) | bash test | example | $LS_COLORS | 55 | |----------|------------------|-----------------------------|-----------|-----------------------------|------------| 56 | | S_IFIFO | FIFO(pipe) | p | -p | /run/systemd/sessions/1.ref | amber | 57 | | S_IFCHR | character device | c | -c | /dev/console | yellow | 58 | | S_IFDIR | directory | d | -d | /usr/bin/ | purple | 59 | | S_IFBLK | block device | b | -b | /dev/nvme0n1p2 | yellow | 60 | | S_IFREG | regular file | f/- | -f | /usr/include/stdio.h | white | 61 | | S_IFLNK | symbolic link | l | -L/-h | /usr/lib/libcurl.so | aqua | 62 | | S_IFSOCK | socket | s | -S | /tmp/mongodb-27017.sock | magenta | 63 | 64 | ```csv 65 | stat.h,file_type,find -type/ls-l(first char),bash test,example,$LS_COLORS 66 | S_IFIFO,FIFO(pipe),p,-p,/run/systemd/sessions/1.ref,amber 67 | S_IFCHR,character device,c,-c,/dev/console,yellow 68 | S_IFDIR,directory,d,-d,/usr/bin/,purple 69 | S_IFBLK,block device,b,-b,/dev/nvme0n1p2,yellow 70 | S_IFREG,regular file,f/-,-f,/usr/include/stdio.h,white 71 | S_IFLNK,symbolic link,l,-L/-h,/usr/lib/libcurl.so,aqua 72 | S_IFSOCK,socket,s,-S,/tmp/mongodb-27017.sock ,magenta 73 | ``` 74 | 75 | - 串行读写设备示例: 磁带,键盘 76 | - 块状读写设备示例: DVD/CD, HDD 都是一次读写一个扇区 77 | */ 78 | #[allow(clippy::doc_markdown)] 79 | fn get_filetype<'a>(st_mode: u32) -> &'a str { 80 | let filetype_mask = st_mode & libc::S_IFMT; 81 | match filetype_mask { 82 | libc::S_IFIFO => "FIFO", 83 | libc::S_IFCHR => "character device", 84 | libc::S_IFDIR => "directory", 85 | libc::S_IFBLK => "block device", 86 | libc::S_IFREG => "regular file", 87 | libc::S_IFLNK => "symbolic link", 88 | libc::S_IFSOCK => "socket", 89 | _ => unreachable!(), 90 | } 91 | } 92 | 93 | #[test] 94 | fn test_stat_st_mode_bit_mask() { 95 | // 1000 000 110100100 96 | // kind permission 97 | println!("{:016b}", libc::S_IFMT); 98 | 99 | println!("{:016b}", libc::S_ISUID); 100 | println!("{:016b}", libc::S_ISGID); 101 | println!("{:016b}", libc::S_ISVTX); 102 | 103 | println!("{:016b}", libc::S_IRWXU); 104 | println!("{:016b}", libc::S_IRWXG); 105 | println!("{:016b}", libc::S_IRWXO); 106 | } 107 | -------------------------------------------------------------------------------- /src/network.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_char, c_int, in_addr}; 2 | 3 | /* 4 | ```text 5 | MariaDB [test]> select inet_aton("192.168.1.1"); 6 | +--------------------------+ 7 | | inet_aton("192.168.1.1") | 8 | +--------------------------+ 9 | | 3232235777 | 10 | +--------------------------+ 11 | ``` 12 | */ 13 | #[link(name = "c")] 14 | extern "C" { 15 | /// aton: means string to network_ip 16 | /// ntoa: means network_ip to string 17 | pub fn inet_aton(cp: *const c_char, inp: *mut in_addr) -> c_int; 18 | pub fn inet_ntoa(in_: in_addr) -> *mut c_char; 19 | // inet_addr 是 inet_aton 完全不考虑字符串解析错误的版本 20 | // fn inet_addr(cp: *const c_char) -> in_addr_t 21 | pub fn gethostbyname(name: *const c_char) -> *mut libc::hostent; 22 | /// htons: H(host byte order) TO N(network byte order) S(short) 23 | /// network byte order == MSB == bigger-endian 24 | pub fn htonl(hostlong: u32) -> u32; 25 | pub fn htons(hostshort: u16) -> u16; 26 | /// License managers use this to ensure that 27 | /// software programs can run only on machines that hold valid licenses 28 | pub fn gethostid() -> i64; 29 | } 30 | 31 | /// The getaddrinfo() function combines the functionality provided by the gethostbyname(3) and getservbyname(3) functions into a single interface 32 | #[cfg(test)] 33 | fn dns_lookup_getaddrinfo(hostname: &str) { 34 | let host = std::ffi::CString::new(hostname).unwrap(); 35 | unsafe { 36 | // ret: Vec 37 | let mut ret = std::ptr::null_mut(); 38 | crate::syscall!(getaddrinfo( 39 | host.as_ptr(), 40 | std::ptr::null(), 41 | std::ptr::null(), 42 | &mut ret 43 | )); 44 | let sockaddr = *((*ret).ai_addr); 45 | let mut ipv4 = [0_u8; 4]; 46 | #[allow(clippy::transmute_ptr_to_ptr)] 47 | ipv4.copy_from_slice(std::mem::transmute(&sockaddr.sa_data[2..6])); 48 | println!("{} = {:?}", hostname, ipv4); 49 | libc::freeaddrinfo(ret); 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_dns_lookup_getaddrinfo() { 55 | dns_lookup_getaddrinfo("baidu.com"); 56 | } 57 | 58 | #[must_use] 59 | pub fn dns_resolve(hostname: &str) -> libc::in_addr { 60 | let hostname_cstring = std::ffi::CString::new(hostname).unwrap(); 61 | let hostent = unsafe { gethostbyname(hostname_cstring.as_ptr().cast()) }; 62 | if hostent.is_null() { 63 | panic!("Invalid hostname"); 64 | } 65 | let h_name = unsafe { (*hostent).h_name }; 66 | let h_name_len = unsafe { libc::strlen(h_name) }; 67 | let hostname_alias = unsafe { String::from_raw_parts(h_name.cast(), h_name_len, h_name_len) }; 68 | println!("{} is an alias for {}", hostname_alias, hostname); 69 | let mut h_addr_list = unsafe { *hostent }.h_addr_list; 70 | let mut ipv4_addr_list = vec![]; 71 | while !h_addr_list.is_null() { 72 | // let _in_addr_ptr = unsafe { (*h_addr_list).cast::() }; 73 | let ipv4_addr_ptr = unsafe { (*h_addr_list).cast::<[u8; 4]>() }; 74 | if ipv4_addr_ptr.is_null() { 75 | break; 76 | } 77 | let ipv4_addr = unsafe { *ipv4_addr_ptr }; 78 | ipv4_addr_list.push(ipv4_addr); 79 | println!( 80 | "{} has address {}.{}.{}.{}", 81 | hostname_alias, ipv4_addr[0], ipv4_addr[1], ipv4_addr[2], ipv4_addr[3] 82 | ); 83 | h_addr_list = unsafe { h_addr_list.add(1) }; 84 | } 85 | std::mem::forget(hostname_alias); 86 | libc::in_addr { 87 | s_addr: unsafe { htonl(u32::from_be_bytes(ipv4_addr_list[0])) }, 88 | } 89 | } 90 | 91 | #[test] 92 | fn test_dns_resolve() { 93 | let _ = dns_resolve("www.rust-lang.org"); 94 | } 95 | 96 | /// icmphdr.type usually use ICMP_ECHO 97 | pub const ICMP_ECHO: u8 = 8; 98 | 99 | #[repr(C)] 100 | pub struct icmphdr { 101 | pub type_: u8, 102 | pub code: u8, 103 | pub checksum: u16, 104 | pub un: un, 105 | } 106 | 107 | #[derive(Clone, Copy)] 108 | #[repr(C)] 109 | pub union un { 110 | echo: echo, 111 | gateway: u32, 112 | frag: frag, 113 | } 114 | 115 | #[derive(Clone, Copy)] 116 | #[repr(C)] 117 | pub struct echo { 118 | pub id: u16, 119 | pub sequence: u16, 120 | } 121 | 122 | #[derive(Clone, Copy)] 123 | #[repr(C)] 124 | pub struct frag { 125 | __glibc_reserved: u16, 126 | mtu: u16, 127 | } 128 | 129 | /// rfc792 130 | #[must_use] 131 | pub fn icmq_checksum(bytes: &[u8]) -> u16 { 132 | let mut sum = 0_u32; 133 | // skip type(u8) and code(u8) filed, because checksum initial value is 0, doesn't need to skip checksum field 134 | bytes.chunks_exact(2).skip(1).for_each(|u16_bytes| { 135 | sum += u32::from(u16::from_be_bytes( 136 | std::convert::TryInto::try_into(u16_bytes).unwrap(), 137 | )); 138 | }); 139 | 140 | // sum = sum的高16位 + sum的低16位 141 | // 如果溢出(sum的高16位不为0)则继续,我这里偷懒了 142 | sum = (sum >> 16) + (sum & 0xffff); 143 | 144 | !sum as u16 145 | } 146 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | ## 「重要」`&T`和`*const T`,`&mut T`和`*mut T`是同一个类型 3 | 4 | 观察MIR代码可知,以下两种写法展开成的MIR源码是一样的: 5 | > localtime_r(×tamp, &mut tm_struct); 6 | > 7 | > localtime_r(×tamp as *const i64, &mut tm_struct as *mut _); 8 | 9 | 参考 chrono 源码: 10 | 11 | ```text 12 | if libc::localtime_r(&sec, &mut out).is_null() { 13 | panic!("localtime_r failed: {}", io::Error::last_os_error()); 14 | } 15 | ``` 16 | */ 17 | 18 | // [Why libc remove strftime?](https://github.com/rust-lang/libc/commit/377ee7307ae7d4b6a5fb46802958518f724ba873) 19 | extern "C" { 20 | fn strftime( 21 | buf: *mut libc::c_char, 22 | buf_len: libc::size_t, 23 | time_format: *const libc::c_char, 24 | tm_struct: *const libc::tm, 25 | ) -> libc::size_t; 26 | /// strptime not found in windows 27 | /// return the last one char consumed in the conversion 28 | #[cfg(test)] 29 | fn strptime( 30 | s: *const libc::c_char, 31 | format: *const libc::c_char, 32 | tm_struct: *mut libc::tm, 33 | ) -> *const libc::c_char; 34 | } 35 | 36 | /// https://stackoverflow.com/questions/522251/whats-the-difference-between-iso-8601-and-rfc-3339-date-formats 37 | /// chrono/time 的 RFC3339 的 +0000 要写成 +00:00 38 | const RFC_3339_EXAMPLE: &str = "1970-01-01T00:00:00+0000\0"; 39 | const RFC_3339_LEN: usize = RFC_3339_EXAMPLE.len(); 40 | const RFC_3339_FORMAT: *const libc::c_char = "%Y-%m-%dT%H:%M:%S%z\0".as_ptr().cast(); 41 | 42 | /// s must end with nul 43 | #[cfg(test)] 44 | unsafe fn parse_rfc_3339_to_tm(s: &str) -> libc::tm { 45 | let mut tm = std::mem::zeroed(); 46 | let parse_len = strptime(s.as_ptr().cast(), RFC_3339_FORMAT, &mut tm); 47 | assert_eq!( 48 | parse_len.offset_from(s.as_ptr().cast()), 49 | (s.len() - 1) as isize 50 | ); 51 | tm 52 | } 53 | 54 | #[must_use] 55 | pub fn tm_to_rfc_3339(tm: &libc::tm) -> String { 56 | let mut buffer = [0_u8; RFC_3339_LEN]; 57 | let str_len = unsafe { 58 | strftime( 59 | buffer.as_mut_ptr().cast(), 60 | RFC_3339_LEN, 61 | RFC_3339_FORMAT, 62 | tm, 63 | ) 64 | }; 65 | assert_eq!(str_len, RFC_3339_LEN - 1); 66 | unsafe { String::from_utf8_unchecked(buffer[..RFC_3339_LEN - 1].to_vec()) } 67 | } 68 | 69 | #[test] 70 | fn test_parse_rfc_3339() { 71 | unsafe { 72 | let now_i64 = libc::time(std::ptr::null_mut()); 73 | let mut now_tm = std::mem::zeroed(); 74 | libc::gmtime_r(&now_i64, &mut now_tm); 75 | let s = format!("{}\0", tm_to_rfc_3339(&now_tm)); 76 | dbg!(&s); 77 | let mut tm2 = parse_rfc_3339_to_tm(&s); 78 | assert_eq!(now_tm.tm_sec, tm2.tm_sec); 79 | // mktime assumes that the date value is in the local time zone 80 | let timestamp = libc::mktime(&mut tm2); 81 | libc::localtime_r(&now_i64, &mut now_tm); 82 | assert_eq!(now_i64, timestamp + now_tm.tm_gmtoff); 83 | } 84 | } 85 | 86 | /** 87 | output example: 2021-07-03 09:54:39.444706875 +0800 88 | 89 | ## daynight saving time example 90 | when calc the timezone we need to know daynight saving time flag(`tm.is_dst`) 91 | 92 | a example on mountain timezone 93 | ```text 94 | timezone getup_time 95 | UTC 17:00 96 | MST(UTC-07) 10:00 (without dst, 11/01-03/14) 97 | MDT(UTC-06) 11:00 (summer with dst, 03/14-11/01) 98 | ``` 99 | */ 100 | #[must_use] 101 | pub fn format_timestamp_with_nanosecond( 102 | timestamp: libc::time_t, 103 | nanosecond: libc::time_t, 104 | ) -> String { 105 | let mut tm = unsafe { std::mem::zeroed() }; 106 | unsafe { 107 | libc::localtime_r(×tamp, &mut tm); 108 | } 109 | let mut ymd_hms = tm_to_rfc_3339(&tm).into_bytes(); 110 | ymd_hms.truncate(ymd_hms.len() - "+0800".len()); 111 | let ymd_hms = unsafe { String::from_utf8_unchecked(ymd_hms) }; 112 | let timezone = tm.tm_gmtoff / 3600 + if tm.tm_isdst > 0 { 1 } else { 0 }; 113 | format!("{}.{} {:+03}00", ymd_hms, nanosecond, timezone) 114 | } 115 | 116 | #[test] 117 | fn test_format_timestamp_with_nanosecond() { 118 | const TEST_CASES: [(libc::time_t, libc::time_t, &str); 1] = [( 119 | 1_625_277_279, 120 | 444_706_875, 121 | "2021-07-03T09:54:39.444706875 +0800", 122 | )]; 123 | for (timestamp, nanosecond, output) in TEST_CASES { 124 | assert_eq!( 125 | format_timestamp_with_nanosecond(timestamp, nanosecond), 126 | output 127 | ); 128 | } 129 | } 130 | 131 | /* 132 | ## relative system call 133 | - asctime(struct tm*): return example: `Sun Jun 9 12:34:56 2007\n\0` 134 | - ctime: same as `asctime(localtime(timeval))` 135 | */ 136 | #[test] 137 | fn test_asctime_and_ctime() { 138 | #[link(name = "c")] 139 | extern "C" { 140 | /// asctime include `\n` 141 | fn asctime(tm: *const libc::tm) -> *const libc::c_char; 142 | /// same as `asctime(localtime(time_t))` 143 | fn ctime(timestamp: *const libc::time_t) -> *const libc::c_char; 144 | } 145 | unsafe { 146 | libc::printf( 147 | "%s\0".as_ptr().cast(), 148 | asctime(libc::localtime(&libc::time(std::ptr::null_mut()))), 149 | ); 150 | libc::printf( 151 | "%s\0".as_ptr().cast(), 152 | ctime(&libc::time(std::ptr::null_mut())), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/database/adapters/dbm.rs: -------------------------------------------------------------------------------- 1 | use crate::database::models::user::{CrudUserDao, User, Username}; 2 | use crate::dylibs_binding::gdbm_compat::{ 3 | datum, dbm_close, dbm_fetch, dbm_firstkey, dbm_nextkey, dbm_open, dbm_ptr, dbm_store, StoreMode, 4 | }; 5 | 6 | pub struct DbmDb { 7 | dbm_ptr: *mut dbm_ptr, 8 | } 9 | 10 | impl Default for DbmDb { 11 | fn default() -> Self { 12 | let dbm_ptr = unsafe { 13 | dbm_open( 14 | Self::DB_FILENAME, 15 | libc::O_RDWR | libc::O_CREAT, 16 | libc::S_IRUSR | libc::S_IWUSR, 17 | ) 18 | }; 19 | if dbm_ptr.is_null() { 20 | panic!("{}", std::io::Error::last_os_error()); 21 | } 22 | Self { dbm_ptr } 23 | } 24 | } 25 | 26 | impl Drop for DbmDb { 27 | #[allow(clippy::shadow_unrelated)] 28 | fn drop(&mut self) { 29 | unsafe { 30 | dbm_close(self.dbm_ptr); 31 | // python: os.path.basename, Rust: file_stem 32 | let file_stem = libc::strdup(Self::DB_FILENAME); 33 | let file_dir = libc::strcat(file_stem, ".dir\0".as_ptr().cast()); 34 | let file_stem = libc::strdup(Self::DB_FILENAME); 35 | let file_pag = libc::strcat(file_stem, ".pag\0".as_ptr().cast()); 36 | libc::unlink(file_dir); 37 | libc::unlink(file_pag); 38 | } 39 | } 40 | } 41 | 42 | impl CrudUserDao for DbmDb { 43 | type Model = User; 44 | unsafe fn insert_sample_data(&self) { 45 | for user_id in 0..Self::Model::LEN { 46 | let mut user_id = user_id as u8; 47 | let mut user = Self::Model::new(user_id); 48 | let key_datum = datum { 49 | dptr: (&mut user_id as *mut u8).cast(), 50 | dsize: std::mem::size_of_val(&user_id) as i32, 51 | }; 52 | let value_datum = datum { 53 | dptr: user.as_mut_ptr().cast(), 54 | dsize: User::SIZE as i32, 55 | }; 56 | assert_eq!( 57 | dbm_store(self.dbm_ptr, key_datum, value_datum, StoreMode::DBM_INSERT), 58 | 0 59 | ); 60 | } 61 | } 62 | 63 | unsafe fn select_all(&self) -> Vec { 64 | let mut users = vec![]; 65 | let mut key_datum = dbm_firstkey(self.dbm_ptr); 66 | loop { 67 | if key_datum.dptr.is_null() { 68 | break; 69 | } 70 | 71 | let value_datum = dbm_fetch(self.dbm_ptr, key_datum); 72 | if !value_datum.dptr.is_null() { 73 | let mut user = std::mem::zeroed::(); 74 | std::ptr::copy( 75 | value_datum.dptr.cast(), 76 | user.as_mut_ptr(), 77 | value_datum.dsize as usize, 78 | ); 79 | users.push(user); 80 | } 81 | 82 | key_datum = dbm_nextkey(self.dbm_ptr); 83 | } 84 | // 就像HashMap一样,此时的users是无序的 85 | users 86 | } 87 | 88 | unsafe fn find_user_by_id(&self, mut user_id: u8) -> Self::Model { 89 | let key_datum = datum { 90 | dptr: (&mut user_id as *mut u8).cast(), 91 | dsize: std::mem::size_of_val(&user_id) as i32, 92 | }; 93 | let value_datum = dbm_fetch(self.dbm_ptr, key_datum); 94 | if value_datum.dptr.is_null() { 95 | panic!("user_id={} not found!", user_id); 96 | } 97 | let mut user = std::mem::zeroed::(); 98 | std::ptr::copy( 99 | value_datum.dptr.cast(), 100 | user.as_mut_ptr(), 101 | value_datum.dsize as usize, 102 | ); 103 | user 104 | } 105 | 106 | unsafe fn update_username_by_id(&self, mut user_id: u8, username: Username) { 107 | let key_datum = datum { 108 | dptr: (&mut user_id as *mut u8).cast(), 109 | dsize: std::mem::size_of_val(&user_id) as i32, 110 | }; 111 | let mut value_datum = dbm_fetch(self.dbm_ptr, key_datum); 112 | if value_datum.dptr.is_null() { 113 | panic!("user_id={} not found!", user_id); 114 | } 115 | let mut user = std::mem::zeroed::(); 116 | std::ptr::copy( 117 | value_datum.dptr.cast(), 118 | user.as_mut_ptr(), 119 | value_datum.dsize as usize, 120 | ); 121 | user.username = username; 122 | value_datum.dptr = user.as_mut_ptr().cast(); 123 | assert_eq!( 124 | dbm_store(self.dbm_ptr, key_datum, value_datum, StoreMode::DBM_REPLACE), 125 | 0 126 | ); 127 | } 128 | } 129 | 130 | #[test] 131 | fn test_dbm_database() { 132 | let db_adapter = DbmDb::default(); 133 | crate::database::models::user::test_user_crud(&db_adapter); 134 | } 135 | 136 | #[cfg(test)] 137 | unsafe fn dbm_create_read_update_delete() { 138 | use crate::dylibs_binding::gdbm_compat::dbm_delete; 139 | let handle = DbmDb::default(); 140 | 141 | let mut key = 1; 142 | let mut user = User::new(key); 143 | let key_datum = datum { 144 | dptr: (&mut key as *mut u8).cast(), 145 | dsize: std::mem::size_of_val(&key) as i32, 146 | }; 147 | 148 | // § create 149 | let value_datum = datum { 150 | dptr: user.as_mut_ptr().cast(), 151 | dsize: User::SIZE as i32, 152 | }; 153 | assert_eq!( 154 | dbm_store( 155 | handle.dbm_ptr, 156 | key_datum, 157 | value_datum, 158 | StoreMode::DBM_INSERT 159 | ), 160 | 0 161 | ); 162 | 163 | // § read 164 | let mut value_datum = dbm_fetch(handle.dbm_ptr, key_datum); 165 | assert!(!value_datum.dptr.is_null()); 166 | // copy user data from database 167 | let mut user = std::mem::zeroed::(); 168 | //std::ptr::copy(value_datum.dptr.cast(), user.as_mut_ptr(), value_datum.dsize as usize); 169 | libc::memcpy( 170 | user.as_mut_ptr().cast(), 171 | value_datum.dptr.cast(), 172 | value_datum.dsize as usize, 173 | ); 174 | dbg!(user); 175 | 176 | // § update 177 | user.username = *b"tuesday"; 178 | value_datum.dptr = user.as_mut_ptr().cast(); 179 | assert_eq!( 180 | dbm_store( 181 | handle.dbm_ptr, 182 | key_datum, 183 | value_datum, 184 | StoreMode::DBM_REPLACE 185 | ), 186 | 0 187 | ); 188 | 189 | // § delete 190 | let delete_ret = dbm_delete(handle.dbm_ptr, key_datum); 191 | if delete_ret == 0 { 192 | println!("user_id={} delete success", user.user_id); 193 | } else { 194 | println!("user_id={} not exist!", user.user_id); 195 | } 196 | } 197 | 198 | #[test] 199 | fn test_dbm_create_read_update_delete() { 200 | unsafe { 201 | dbm_create_read_update_delete(); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/database/adapters/mysql.rs: -------------------------------------------------------------------------------- 1 | use crate::database::database_config::MysqlConfig; 2 | use crate::database::models::user::{CrudUserDao, User, Username}; 3 | use crate::dylibs_binding::mysqlclient::{ 4 | my_ulonglong, mysql, mysql_affected_rows, mysql_close, mysql_errno, mysql_error, 5 | mysql_fetch_row, mysql_field_count, mysql_free_result, mysql_init, mysql_num_rows, mysql_ping, 6 | mysql_query, mysql_real_connect, mysql_store_result, MYSQL_DEFAULT_PORT, 7 | }; 8 | use std::ffi::CString; 9 | 10 | pub struct MysqlConnection { 11 | connection: *mut mysql, 12 | } 13 | 14 | impl MysqlConnection { 15 | #[must_use] 16 | pub fn new(config: MysqlConfig) -> Self { 17 | let connection = unsafe { 18 | #[allow(clippy::zero_ptr)] 19 | mysql_init(0 as *mut mysql) 20 | }; 21 | let mysql_conn = Self { connection }; 22 | /* 23 | you can call mysql_options() between mysql_init and mysql_real_connect 24 | - MYSQL_INIT_COMMAND, like sqlx's after_connect() API 25 | - MYSQL_OPT_RECONNECT 26 | - MYSQL_OPT_CONNECT_TIMEOUT 27 | - MYSQL_OPT_COMPRESS(compress on transfer) 28 | ... 29 | */ 30 | let username = CString::new(config.username).unwrap(); 31 | let password = CString::new(config.password).unwrap(); 32 | let db_name = CString::new(config.db_name).unwrap(); 33 | let connect_result = unsafe { 34 | mysql_real_connect( 35 | mysql_conn.connection, 36 | "localhost\0".as_ptr().cast(), 37 | username.as_ptr().cast(), 38 | password.as_ptr().cast(), 39 | db_name.as_ptr().cast(), 40 | MYSQL_DEFAULT_PORT, 41 | std::ptr::null(), 42 | 0, 43 | ) 44 | }; 45 | if connect_result.is_null() { 46 | mysql_conn.print_last_mysql_error_and_exit(); 47 | } 48 | mysql_conn 49 | } 50 | 51 | /// return true if ping success 52 | #[must_use] 53 | pub fn ping(&self) -> bool { 54 | unsafe { mysql_ping(self.connection) == 0 } 55 | } 56 | 57 | pub fn print_last_mysql_error_and_exit(&self) { 58 | unsafe { 59 | libc::printf( 60 | "mysql errno=%d, err_msg=%s\n\0".as_ptr().cast(), 61 | mysql_errno(self.connection), 62 | mysql_error(self.connection), 63 | ); 64 | libc::exit(libc::EXIT_FAILURE); 65 | } 66 | } 67 | 68 | /// sql string without nul byte 69 | #[allow(clippy::must_use_candidate)] 70 | pub fn query(&self, sql: &str) -> Option> { 71 | let is_select_statement = sql.contains("select") || sql.contains("SELECT"); 72 | let sql = CString::new(sql).unwrap(); 73 | let ret = unsafe { mysql_query(self.connection, sql.as_ptr().cast()) }; 74 | if ret != 0 { 75 | self.print_last_mysql_error_and_exit(); 76 | } 77 | // only select statement has return value 78 | if !is_select_statement { 79 | return None; 80 | } 81 | 82 | let res_ptr = unsafe { mysql_store_result(self.connection) }; 83 | if res_ptr.is_null() { 84 | self.print_last_mysql_error_and_exit(); 85 | } 86 | 87 | let num_rows = unsafe { mysql_num_rows(res_ptr) } as usize; 88 | let mut records = vec![]; 89 | // loop times is mysql_num_rows() 90 | loop { 91 | // type of sql_row is Vec> 92 | let sql_row = unsafe { mysql_fetch_row(res_ptr) }; 93 | if sql_row.is_null() { 94 | break; 95 | } 96 | 97 | // csv format in string 98 | let mut row_bytes = Vec::::new(); 99 | 100 | let fields = unsafe { mysql_field_count(self.connection) }; 101 | for index in 0..fields { 102 | let field_str = unsafe { 103 | /* 104 | Reference: BLP aka *beginning linux programming 4th edition* 105 | BLP page 284(pdf_317): 106 | > (note for dbm_fetch) 107 | > 108 | > The actual data may still be held in local storage space inside the dbm library 109 | > 110 | > and must be copied into program variables before any further dbm functions are called 111 | 112 | BLP page 431(pdf_374): 113 | > mysql_error, which provides a meaningful text message instead. 114 | > 115 | > The message text is written to some internal static memory space, 116 | > 117 | > so you need tocopy it elsewhere if you want to save the error text 118 | */ 119 | // copy strdup bytes from mysql dylib 120 | // or `Vec::from_raw_parts` and mem::forget 121 | // when from_raw_parts drop, would dealloc bytes in mysql dylib cause memory error, must manual forget to drop 122 | // let bytes = *sql_row.add(index as usize); 123 | let bytes = libc::strdup(*sql_row.add(index as usize)); // or strcpy, or std::ptr::copy 124 | let bytes_len = libc::strlen(bytes); 125 | String::from_raw_parts(bytes.cast(), bytes_len, bytes_len) 126 | }; 127 | row_bytes.extend(field_str.into_bytes()); 128 | row_bytes.push(b','); 129 | } 130 | 131 | if !row_bytes.is_empty() { 132 | // pop last comma 133 | row_bytes.pop().unwrap(); 134 | // BLP 书上例子是用 sscanf 解析每一个字段的值 135 | let row_str = unsafe { String::from_utf8_unchecked(row_bytes) }; 136 | #[allow(clippy::match_wild_err_arm)] 137 | let record = match row_str.parse::() { 138 | Ok(record) => record, 139 | Err(_) => panic!("parse error"), 140 | }; 141 | records.push(record); 142 | } 143 | } 144 | unsafe { 145 | mysql_free_result(res_ptr); 146 | } 147 | assert_eq!(records.len(), num_rows); 148 | Some(records) 149 | } 150 | 151 | /// if delete the whole tables, the affected rows would be zero 152 | #[must_use] 153 | pub fn affected_rows(&self) -> my_ulonglong { 154 | unsafe { mysql_affected_rows(self.connection) } 155 | } 156 | 157 | /// last_insert_id in current connection(current thread?) 158 | #[must_use] 159 | pub fn last_insert_id(&self) -> libc::c_ulong { 160 | self.query::("select last_insert_id()") 161 | .unwrap()[0] 162 | } 163 | } 164 | 165 | impl Drop for MysqlConnection { 166 | fn drop(&mut self) { 167 | unsafe { 168 | mysql_close(self.connection); 169 | } 170 | } 171 | } 172 | 173 | /** 174 | mysql's general_log output: 175 | ```text 176 | /usr/bin/mariadbd, Version: 10.5.10-MariaDB (Arch Linux). started with: 177 | Tcp port: 3306 Unix socket: /run/mysqld/mysqld.sock 178 | Time Id Command Argument 179 | 210711 21:19:32 39 Connect w@localhost on test using Socket 180 | 39 Quit 181 | ``` 182 | */ 183 | #[test] 184 | fn test_mysql_connect_and_ping() { 185 | let config = crate::database::database_config::Config::load_production_config(); 186 | let mysql_conn = MysqlConnection::new(config.mysql); 187 | assert!(mysql_conn.ping()); 188 | } 189 | 190 | /// err_msg=MySQL server has gone away 191 | #[test] 192 | fn test_error_mysql_server_has_gone() { 193 | let connection = unsafe { 194 | #[allow(clippy::zero_ptr)] 195 | mysql_init(0 as *mut mysql) 196 | }; 197 | let ret = unsafe { mysql_ping(connection) }; 198 | assert_ne!(ret, 0); 199 | unsafe { 200 | libc::printf( 201 | "mysql errno=%d, err_msg=%s\n\0".as_ptr().cast(), 202 | mysql_errno(connection), 203 | mysql_error(connection), 204 | ); 205 | } 206 | } 207 | 208 | impl CrudUserDao for MysqlConnection { 209 | type Model = User; 210 | 211 | unsafe fn insert_sample_data(&self) { 212 | self.query::("drop table if exists users"); 213 | self.query::("create table if not exists users(user_id tinyint unsigned not null primary key, username varchar(7) not null)"); 214 | for user_id in 0..Self::Model::LEN { 215 | let user_id = user_id as u8; 216 | let user = Self::Model::new(user_id); 217 | let username = String::from_utf8_unchecked(user.username.to_vec()); 218 | // use prepare statement to insert is better 219 | let insert_sql = format!( 220 | "insert into users(user_id, username) values({}, '{}')", 221 | user.user_id, username 222 | ); 223 | self.query::(&insert_sql); 224 | assert_eq!(self.affected_rows(), 1); 225 | // because our user_id is set manual, and not AUTO_INCREMENT 226 | // so last_insert_id() would be zero after insert(because last insert id not using AUTO_INCREMENT) 227 | } 228 | } 229 | 230 | unsafe fn select_all(&self) -> Vec { 231 | self.query("select user_id, username from users").unwrap() 232 | } 233 | 234 | unsafe fn find_user_by_id(&self, user_id: u8) -> Self::Model { 235 | let sql = format!( 236 | "select user_id, username from users where user_id={}", 237 | user_id 238 | ); 239 | self.query(&sql).unwrap()[0] 240 | } 241 | 242 | unsafe fn update_username_by_id(&self, user_id: u8, username: Username) { 243 | let username = String::from_utf8_unchecked(username.to_vec()); 244 | // use prepare statement to update is better 245 | let insert_sql = format!( 246 | "update users set username='{}' where user_id={}", 247 | username, user_id 248 | ); 249 | self.query::(&insert_sql); 250 | if self.affected_rows() == 0 { 251 | panic!("user_id={} not found", user_id); 252 | } 253 | } 254 | } 255 | 256 | #[test] 257 | fn test_insert_sample_data() { 258 | let config = crate::database::database_config::Config::load_production_config(); 259 | let mysql_conn = MysqlConnection::new(config.mysql); 260 | unsafe { 261 | mysql_conn.insert_sample_data(); 262 | } 263 | } 264 | 265 | #[test] 266 | fn test_update_username_by_id() { 267 | let config = crate::database::database_config::Config::load_production_config(); 268 | let mysql_conn = MysqlConnection::new(config.mysql); 269 | unsafe { 270 | mysql_conn.insert_sample_data(); 271 | mysql_conn.update_username_by_id(3, *b"tuesday"); 272 | } 273 | } 274 | 275 | #[test] 276 | fn test_query_with_generic() { 277 | let config = crate::database::database_config::Config::load_production_config(); 278 | let mysql_conn = MysqlConnection::new(config.mysql); 279 | unsafe { 280 | mysql_conn.insert_sample_data(); 281 | } 282 | let user_id = mysql_conn 283 | .query::( 284 | "\ 285 | SELECT user_id \ 286 | FROM users \ 287 | WHERE username='user_01'", 288 | ) 289 | .unwrap()[0]; 290 | assert_eq!(user_id, 1); 291 | let user = mysql_conn 292 | .query::( 293 | "\ 294 | SELECT user_id, username \ 295 | FROM users \ 296 | WHERE username='user_01'", 297 | ) 298 | .unwrap()[0]; 299 | assert_eq!(user.user_id, 1); 300 | } 301 | 302 | #[test] 303 | fn test_mysql_database() { 304 | let config = crate::database::database_config::Config::load_production_config(); 305 | let db_adapter = MysqlConnection::new(config.mysql); 306 | crate::database::models::user::test_user_crud(&db_adapter); 307 | } 308 | --------------------------------------------------------------------------------