// Symlink tests are separate since they don't in general work on a Windows host.
//@ignore-host: windows # creating symlinks requires admin permissions on Windows
//@ignore-target: windows # File handling is not implemented yet
//@compile-flags: -Zmiri-disable-isolation

use std::ffi::CString;
use std::io::ErrorKind;
use std::os::unix::ffi::OsStrExt;

#[path = "../../utils/libc.rs"]
mod libc_utils;
#[path = "../../utils/mod.rs"]
mod utils;
use libc_utils::errno_result;

fn main() {
    test_readlink();
    test_nofollow_symlink();
}

fn test_readlink() {
    let bytes = b"Hello, World!\n";
    let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes);
    let expected_path = path.as_os_str().as_bytes();

    let symlink_path = utils::prepare("miri_test_fs_symlink.txt");
    std::os::unix::fs::symlink(&path, &symlink_path).unwrap();

    // Test that the expected string gets written to a buffer of proper
    // length, and that a trailing null byte is not written.
    let symlink_c_str = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
    let symlink_c_ptr = symlink_c_str.as_ptr();

    // Make the buf one byte larger than it needs to be,
    // and check that the last byte is not overwritten.
    let mut large_buf = vec![0xFF; expected_path.len() + 1];
    let res = errno_result(unsafe {
        libc::readlink(symlink_c_ptr, large_buf.as_mut_ptr().cast(), large_buf.len())
    })
    .unwrap();
    // Check that the resolved path was properly written into the buf.
    assert_eq!(&large_buf[..(large_buf.len() - 1)], expected_path);
    assert_eq!(large_buf.last(), Some(&0xFF));
    assert_eq!(res, (large_buf.len() - 1) as isize);

    // Test that the resolved path is truncated if the provided buffer
    // is too small.
    let mut small_buf = [0u8; 2];
    let res = errno_result(unsafe {
        libc::readlink(symlink_c_ptr, small_buf.as_mut_ptr().cast(), small_buf.len())
    })
    .unwrap();
    assert_eq!(small_buf, &expected_path[..small_buf.len()]);
    assert_eq!(res, small_buf.len() as isize);

    // Test that we report a proper error for a missing path.
    let err = errno_result(unsafe {
        libc::readlink(
            c"MIRI_MISSING_FILE_NAME".as_ptr(),
            small_buf.as_mut_ptr().cast(),
            small_buf.len(),
        )
    })
    .unwrap_err();
    assert_eq!(err.kind(), ErrorKind::NotFound);
}

fn test_nofollow_symlink() {
    let path = utils::prepare_with_content("test_nofollow_symlink_target.txt", b"Hello, World!\n");

    let symlink_path = utils::prepare("test_nofollow_symlink.txt");
    std::os::unix::fs::symlink(&path, &symlink_path).unwrap();

    let symlink_cpath = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();

    let err = errno_result(unsafe {
        libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC)
    })
    .unwrap_err();
    assert_eq!(err.raw_os_error(), Some(libc::ELOOP));
}
