//
// Syd: rock-solid unikernel
// src/syd-open.rs: Given a number, print the list of open flags.
//                  Given a flag name, print the value of the open flag.
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{process::ExitCode, str::FromStr};

use nix::{errno::Errno, fcntl::OFlag};
use syd::err::SydResult;

// Set global allocator to mimalloc.
#[global_allocator]
static GLOBAL: mimalloc2_rust::GlobalMiMalloc = mimalloc2_rust::GlobalMiMalloc;

struct OpenFlag(OFlag);

impl std::fmt::Display for OpenFlag {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", flags_to_str(self.0).join("|"))
    }
}

impl FromStr for OpenFlag {
    type Err = Errno;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.to_ascii_uppercase();
        let s = if s.starts_with("O_") {
            s
        } else {
            format!("O_{s}")
        };
        match s.as_str() {
            "O_RDONLY" => Ok(OpenFlag(OFlag::O_RDONLY)),
            "O_WRONLY" => Ok(OpenFlag(OFlag::O_WRONLY)),
            "O_RDWR" => Ok(OpenFlag(OFlag::O_RDWR)),

            "O_APPEND" => Ok(OpenFlag(OFlag::O_APPEND)),
            "O_CREAT" => Ok(OpenFlag(OFlag::O_CREAT)),
            "O_NOFOLLOW" => Ok(OpenFlag(OFlag::O_NOFOLLOW)),
            "O_NOATIME" => Ok(OpenFlag(OFlag::O_NOATIME)),

            "O_SYNC" => Ok(OpenFlag(OFlag::O_SYNC)),
            "O_ASYNC" => Ok(OpenFlag(OFlag::O_ASYNC)),
            "O_DSYNC" => Ok(OpenFlag(OFlag::O_DSYNC)),
            "O_DIRECT" => Ok(OpenFlag(OFlag::O_DIRECT)),

            "O_EXCL" => Ok(OpenFlag(OFlag::O_EXCL)),
            "O_CLOEXEC" => Ok(OpenFlag(OFlag::O_CLOEXEC)),
            "O_TRUNC" => Ok(OpenFlag(OFlag::O_TRUNC)),
            "O_DIRECTORY" => Ok(OpenFlag(OFlag::O_DIRECTORY)),
            "O_PATH" => Ok(OpenFlag(OFlag::O_PATH)),
            "O_TMPFILE" => Ok(OpenFlag(OFlag::O_TMPFILE)),

            "O_NOCTTY" => Ok(OpenFlag(OFlag::O_NOCTTY)),
            "O_NONBLOCK" => Ok(OpenFlag(OFlag::O_NONBLOCK)),
            "O_LARGEFILE" => Ok(OpenFlag(OFlag::O_LARGEFILE)),
            _ => Err(Errno::EINVAL),
        }
    }
}

fn main() -> SydResult<ExitCode> {
    syd::set_sigpipe_dfl()?;

    // Configure mimalloc.
    syd::configure_mimalloc();

    Ok(match std::env::args().nth(1) {
        None => {
            println!("Usage: syd-open flags");
            println!("Given a number, print the list of open flags.");
            println!("Given a flag name, print the value of the open flag.");
            ExitCode::SUCCESS
        }
        Some(flags) => match flags.parse::<i32>() {
            Ok(flags) => {
                for flag in flags_to_str(OFlag::from_bits_truncate(flags)) {
                    println!("{flag}");
                }
                ExitCode::SUCCESS
            }
            Err(_) => {
                if let Ok(flag) = OpenFlag::from_str(&flags) {
                    println!("{flag}");
                    ExitCode::SUCCESS
                } else {
                    ExitCode::FAILURE
                }
            }
        },
    })
}

fn flags_to_str(flag: OFlag) -> Vec<String> {
    let mut flags = Vec::new();

    // Check access mode separately because it's a bitmask of O_RDONLY, O_WRONLY, and O_RDWR
    match flag & OFlag::O_ACCMODE {
        accmode if accmode == OFlag::O_RDONLY => flags.push("O_RDONLY".to_string()),
        accmode if accmode == OFlag::O_WRONLY => flags.push("O_WRONLY".to_string()),
        accmode if accmode == OFlag::O_RDWR => flags.push("O_RDWR".to_string()),
        _ => (),
    }

    // Check for other flags
    if flag.contains(OFlag::O_APPEND) {
        flags.push("O_APPEND".to_string());
    }
    if flag.contains(OFlag::O_ASYNC) {
        flags.push("O_ASYNC".to_string());
    }
    if flag.contains(OFlag::O_CLOEXEC) {
        flags.push("O_CLOEXEC".to_string());
    }
    if flag.contains(OFlag::O_CREAT) {
        flags.push("O_CREAT".to_string());
    }
    if flag.contains(OFlag::O_DIRECT) {
        flags.push("O_DIRECT".to_string());
    }
    if flag.contains(OFlag::O_DIRECTORY) {
        flags.push("O_DIRECTORY".to_string());
    }
    if flag.contains(OFlag::O_DSYNC) {
        flags.push("O_DSYNC".to_string());
    }
    if flag.contains(OFlag::O_EXCL) {
        flags.push("O_EXCL".to_string());
    }
    if flag.contains(OFlag::O_LARGEFILE) {
        flags.push("O_LARGEFILE".to_string());
    }
    if flag.contains(OFlag::O_NOATIME) {
        flags.push("O_NOATIME".to_string());
    }
    if flag.contains(OFlag::O_NOCTTY) {
        flags.push("O_NOCTTY".to_string());
    }
    if flag.contains(OFlag::O_NOFOLLOW) {
        flags.push("O_NOFOLLOW".to_string());
    }
    if flag.contains(OFlag::O_NONBLOCK) {
        flags.push("O_NONBLOCK".to_string());
    }
    if flag.contains(OFlag::O_PATH) {
        flags.push("O_PATH".to_string());
    }
    if flag.contains(OFlag::O_SYNC) {
        flags.push("O_SYNC".to_string());
    }
    if flag.contains(OFlag::O_TMPFILE) {
        flags.push("O_TMPFILE".to_string());
    }
    if flag.contains(OFlag::O_TRUNC) {
        flags.push("O_TRUNC".to_string());
    }

    flags
}
