// This file is part of the uutils coreutils package. // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. #![no_main] use libfuzzer_sys::fuzz_target; use uu_dirname::uumain; use rand::RngExt; use rand::prelude::IndexedRandom; use std::ffi::OsString; use uufuzz::CommandResult; use uufuzz::{compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd}; static CMD_PATH: &str = "dirname"; fn generate_dirname_args() -> Vec { let mut rng = rand::rng(); let mut args = Vec::new(); // 20% chance to include -z/--zero flag if rng.random_bool(0.2) { if rng.random_bool(0.5) { args.push("-z".to_string()); } else { args.push("--zero".to_string()); } } // 30% chance to use one of the specific issue #8924 cases if rng.random_bool(0.3) { let issue_cases = [ "foo//.", "foo/./", "foo/bar/./", "bar//.", "test/./", "a/b/./", "x//.", "dir/subdir/./", ]; args.push(issue_cases.choose(&mut rng).unwrap().to_string()); } else { // Generate 1-3 path arguments normally let num_paths = rng.random_range(1..=3); for _ in 0..num_paths { args.push(generate_path()); } } args } fn generate_path() -> String { let mut rng = rand::rng(); // Different types of paths to test let path_type = rng.random_range(0..15); match path_type { // Simple paths 0 => generate_random_string(rng.random_range(1..=20)), // Paths with slashes 1 => { let mut path = String::new(); let components = rng.random_range(1..=5); for i in 0..components { if i > 0 { path.push('/'); } path.push_str(&generate_random_string(rng.random_range(1..=10))); } path } // Root path 2 => "/".to_string(), // Absolute paths 3 => { let mut path = "/".to_string(); let components = rng.random_range(1..=4); for _ in 0..components { path.push_str(&generate_random_string(rng.random_range(1..=8))); path.push('/'); } // Remove trailing slash sometimes if rng.random_bool(0.5) && path.len() > 1 { path.pop(); } path } // Paths ending with "/." (specific case from issue #8924) 4 => { let base = if rng.random_bool(0.3) { "/".to_string() } else { format!("/{}", generate_random_string(rng.random_range(1..=10))) }; format!("{}.", base) } // Paths with multiple slashes 5 => { let base = generate_random_string(rng.random_range(1..=10)); format!( "///{}//{}", base, generate_random_string(rng.random_range(1..=8)) ) } // Paths with dots 6 => { let components = [".", "..", "...", "...."]; let chosen = components.choose(&mut rng).unwrap(); if rng.random_bool(0.5) { format!("/{}", chosen) } else { chosen.to_string() } } // Single character paths 7 => { let chars = ['a', 'x', '1', '-', '_', '.']; chars.choose(&mut rng).unwrap().to_string() } // Empty string (edge case) 8 => "".to_string(), // Issue #8924 specific cases: paths like "foo//." 9 => { let base = generate_random_string(rng.random_range(1..=10)); format!("{}//.", base) } // Issue #8924 specific cases: paths like "foo/./" 10 => { let base = generate_random_string(rng.random_range(1..=10)); format!("{}/./", base) } // Issue #8924 specific cases: paths like "foo/bar/./" 11 => { let base1 = generate_random_string(rng.random_range(1..=8)); let base2 = generate_random_string(rng.random_range(1..=8)); format!("{}/{}/./", base1, base2) } // More complex patterns with ./ and multiple slashes 12 => { let base = generate_random_string(rng.random_range(1..=10)); let patterns = ["/./", "//./", "//.//", "/.//"]; let pattern = patterns.choose(&mut rng).unwrap(); format!("{}{}", base, pattern) } // Patterns with .. and multiple slashes 13 => { let base = generate_random_string(rng.random_range(1..=10)); let patterns = ["/..", "//..", "/../", "//..//"]; let pattern = patterns.choose(&mut rng).unwrap(); format!("{}{}", base, pattern) } // Complex paths with special cases _ => { let special_endings = [".", "..", "/.", "/..", "//", "/", "/./.", "//.", "./"]; let base = generate_random_string(rng.random_range(1..=15)); let ending = special_endings.choose(&mut rng).unwrap(); format!("{}{}", base, ending) } } } fuzz_target!(|_data: &[u8]| { let dirname_args = generate_dirname_args(); let mut args = vec![OsString::from("dirname")]; args.extend(dirname_args.iter().map(OsString::from)); let rust_result = generate_and_run_uumain(&args, uumain, None); let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); eprintln!("Stderr: {}", error_result.stderr); eprintln!("Exit Code: {}", error_result.exit_code); CommandResult { stdout: String::new(), stderr: error_result.stderr, exit_code: error_result.exit_code, } } }; compare_result( "dirname", &format!("{:?}", &args[1..]), None, &rust_result, &gnu_result, false, ); });