// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use s2n_tls::error;
use s2n_tls_tokio::{TlsAcceptor, TlsConnector, TlsStream};
use std::{convert::TryFrom, sync::Arc};
use tokio::{
    io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
    join, time,
};

pub mod common;

async fn read_until_shutdown<S: AsyncRead + AsyncWrite + Unpin>(
    stream: &mut TlsStream<S>,
) -> Result<(), std::io::Error> {
    let mut received = [0; 1];
    // Zero bytes read indicates EOF
    while stream.read(&mut received).await? != 0 {}
    stream.shutdown().await
}

async fn write_until_shutdown<S: AsyncWrite + Unpin>(stream: &mut S) -> Result<(), std::io::Error> {
    let sent = [0; 1];
    loop {
        if let Err(err) = stream.write(&sent).await {
            let tls_err = error::Error::try_from(err).unwrap();
            assert_eq!(tls_err.kind(), error::ErrorType::ConnectionClosed);
            break;
        }
    }
    stream.shutdown().await
}

#[tokio::test]
async fn client_initiated_shutdown() -> Result<(), Box<dyn std::error::Error>> {
    let (server_stream, client_stream) = common::get_streams().await?;

    let client = TlsConnector::new(common::client_config()?.build()?);
    let server = TlsAcceptor::new(common::server_config()?.build()?);

    let (mut client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    tokio::try_join!(read_until_shutdown(&mut server), client.shutdown())?;

    Ok(())
}

#[tokio::test]
async fn server_initiated_shutdown() -> Result<(), Box<dyn std::error::Error>> {
    let (server_stream, client_stream) = common::get_streams().await?;

    let client = TlsConnector::new(common::client_config()?.build()?);
    let server = TlsAcceptor::new(common::server_config()?.build()?);

    let (mut client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    tokio::try_join!(read_until_shutdown(&mut client), server.shutdown())?;

    Ok(())
}

/// Reading and writing handles should both respond to a peer's "close notify"
/// appropriately. The read handle should immediately exit and writing should
/// fail with a "connection closed" error.
#[tokio::test]
async fn shutdown_after_split() -> Result<(), Box<dyn std::error::Error>> {
    let (server_stream, client_stream) = common::get_streams().await?;

    let client = TlsConnector::new(common::client_config_tls12()?.build()?);
    let server = TlsAcceptor::new(common::server_config_tls12()?.build()?);

    let (client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    let (mut client_reader, mut client_writer) = tokio::io::split(client);

    let mut received = [0; 1];

    // All tasks must cleanly exit. try_join will return as soon as an error
    // occurs, so if the result is any error then the test has failed.
    tokio::try_join!(
        server.shutdown(),
        client_reader.read(&mut received),
        write_until_shutdown(&mut client_writer),
    )?;
    Ok(())
}

/// Reading and writing handles should both respond to a peers "close notify"
/// appropriately. TLS1.3 connections have "half close behavior". The read
/// handle should immediately exit, but the write handle can continue to write
/// until explicitly shutdown. After both client handles have shutdown, the
/// server should cleanly exit.
#[tokio::test]
async fn shutdown_after_halfclose_split() -> Result<(), Box<dyn std::error::Error>> {
    let (server_stream, client_stream) = common::get_streams().await?;

    let client = TlsConnector::new(common::client_config()?.build()?);
    let server = TlsAcceptor::new(common::server_config()?.build()?);

    let (client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    let (mut client_reader, mut client_writer) = tokio::io::split(client);

    let close_notify_recvd = Arc::new(tokio::sync::Notify::new());
    let close_notify_recvd_clone = close_notify_recvd.clone();

    let mut received = [0; 1];

    // all tasks must complete, and must complete successfully
    // the client tasks will panic if an error is encountered, so those don't
    // need to be checked.
    let (server, _, _) = tokio::join!(
        server.shutdown(),
        async {
            let bytes_read = client_reader.read(&mut received).await.unwrap();
            // 0 bytes read indicate that we returned because of close notify
            assert_eq!(bytes_read, 0);
            // signal the writer task that close notify received
            close_notify_recvd.notify_one();
        },
        async {
            // wait for the connection to receive "close notify" from peer
            close_notify_recvd_clone.notified().await;
            // confirm that we can write even after receiving the shutdown from
            // the server
            client_writer
                .write_all("random bytes".as_bytes())
                .await
                .unwrap();
            client_writer.flush().await.unwrap();
            // shutdown
            client_writer.shutdown().await.unwrap()
        }
    );
    // make sure the server shutdown cleanly
    assert!(server.is_ok());
    Ok(())
}

#[tokio::test(start_paused = true)]
async fn shutdown_with_blinding() -> Result<(), Box<dyn std::error::Error>> {
    let clock = common::TokioTime::default();
    let mut server_config = common::server_config()?;
    server_config.set_monotonic_clock(clock)?;

    let client = TlsConnector::new(common::client_config()?.build()?);
    let server = TlsAcceptor::new(server_config.build()?);

    let (server_stream, client_stream) = common::get_streams().await?;
    let server_stream = common::TestStream::new(server_stream);
    let overrides = server_stream.overrides();
    let (mut client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    // Trigger a blinded error for the server.
    overrides.next_read(Some(Box::new(|_, _, buf| {
        // Parsing the header is one of the blinded operations
        // in s2n_recv, so provide a malformed header.
        let zeroed_header = [23, 0, 0, 0, 0];
        buf.put_slice(&zeroed_header);
        Ok(()).into()
    })));
    let mut received = [0; 1];
    let result = server.read_exact(&mut received).await;
    assert!(result.is_err());

    // Shutdown MUST NOT complete faster than minimal blinding time.
    let (timeout, _) = join!(
        time::timeout(common::MIN_BLINDING_SECS, server.shutdown()),
        time::timeout(common::MIN_BLINDING_SECS, read_until_shutdown(&mut client)),
    );
    assert!(timeout.is_err());

    // Shutdown MUST eventually complete after blinding.
    //
    // We check for completion, but not for success. At the moment, the
    // call to s2n_shutdown will fail due to issues in the underlying C library.
    let (timeout, _) = join!(
        time::timeout(common::MAX_BLINDING_SECS, server.shutdown()),
        time::timeout(common::MAX_BLINDING_SECS, read_until_shutdown(&mut client)),
    );
    assert!(timeout.is_ok());

    Ok(())
}

#[tokio::test(start_paused = true)]
async fn shutdown_with_blinding_bad_close_record() -> Result<(), Box<dyn std::error::Error>> {
    let clock = common::TokioTime::default();
    let mut server_config = common::server_config()?;
    server_config.set_monotonic_clock(clock)?;

    let client = TlsConnector::new(common::client_config()?.build()?);
    let server = TlsAcceptor::new(server_config.build()?);

    let (server_stream, client_stream) = common::get_streams().await?;
    let server_stream = common::TestStream::new(server_stream);
    let overrides = server_stream.overrides();
    let (mut client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    // Turn the closure alert to a bad message
    overrides.next_read(Some(Box::new(|_, _, buf| {
        // Parsing the header is one of the blinded operations
        // in s2n_recv, so provide a malformed header.
        let zeroed_header = [23, 0, 0, 0, 0];
        buf.put_slice(&zeroed_header);
        Ok(()).into()
    })));

    // Shutdown MUST NOT complete faster than minimal blinding time.
    let (timeout, _) = join!(
        time::timeout(common::MIN_BLINDING_SECS, server.shutdown()),
        time::timeout(common::MIN_BLINDING_SECS, read_until_shutdown(&mut client)),
    );
    assert!(timeout.is_err());

    // Shutdown MUST eventually complete after blinding.
    //
    // We check for completion, but not for success. At the moment, the
    // call to s2n_shutdown will fail due to issues in the underlying C library.
    let (timeout, _) = join!(
        time::timeout(common::MAX_BLINDING_SECS, server.shutdown()),
        time::timeout(common::MAX_BLINDING_SECS, read_until_shutdown(&mut client)),
    );
    // timeout should be OK, but shutdown should return an error because of
    // the bad record.
    assert!(matches!(timeout, Ok(Err(_))));

    Ok(())
}

#[tokio::test(start_paused = true)]
async fn shutdown_with_poll_blinding() -> Result<(), Box<dyn std::error::Error>> {
    let clock = common::TokioTime::default();
    let mut server_config = common::server_config()?;
    server_config.set_monotonic_clock(clock)?;

    let client = TlsConnector::new(common::client_config()?.build()?);
    let server = TlsAcceptor::new(server_config.build()?);

    let (server_stream, client_stream) = common::get_streams().await?;
    let server_stream = common::TestStream::new(server_stream);
    let overrides = server_stream.overrides();
    let (mut client, mut server) =
        common::run_negotiate(&client, client_stream, &server, server_stream).await?;

    // Trigger a blinded error for the server.
    overrides.next_read(Some(Box::new(|_, _, buf| {
        // Parsing the header is one of the blinded operations
        // in s2n_recv, so provide a malformed header.
        let zeroed_header = [23, 0, 0, 0, 0];
        buf.put_slice(&zeroed_header);
        Ok(()).into()
    })));
    let mut received = [0; 1];
    let result = server.read_exact(&mut received).await;
    assert!(result.is_err());

    // poll_blinding MUST NOT complete faster than minimal blinding time.
    let (timeout, _) = join!(
        time::timeout(common::MIN_BLINDING_SECS, server.apply_blinding()),
        time::timeout(common::MIN_BLINDING_SECS, read_until_shutdown(&mut client)),
    );
    assert!(timeout.is_err());

    // Shutdown MUST eventually complete after blinding.
    //
    // We check for completion, but not for success. At the moment, the
    // call to s2n_shutdown will fail due to issues in the underlying C library.
    let (timeout, _) = join!(
        time::timeout(common::MAX_BLINDING_SECS, server.apply_blinding()),
        time::timeout(common::MAX_BLINDING_SECS, read_until_shutdown(&mut client)),
    );
    assert!(timeout.is_ok());

    Ok(())
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use s2n_tls::{
    config,
    connection::Builder,
    error::Error,
    security::{DEFAULT, DEFAULT_TLS13},
};
use s2n_tls_tokio::{TlsAcceptor, TlsConnector, TlsStream};
use std::time::Duration;
use tokio::{
    io::{AsyncRead, AsyncWrite},
    net::{TcpListener, TcpStream},
};

mod stream;
pub use stream::*;
mod time;
pub use time::*;

/// NOTE: this certificate and key are used for testing purposes only!
pub static CERT_PEM: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/examples/certs/cert.pem"
));
pub static KEY_PEM: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/examples/certs/key.pem"
));
pub static RSA_CERT_PEM: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/examples/certs/cert_rsa.pem"
));
pub static RSA_KEY_PEM: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/examples/certs/key_rsa.pem"
));

pub const MIN_BLINDING_SECS: Duration = Duration::from_secs(10);
pub const MAX_BLINDING_SECS: Duration = Duration::from_secs(30);

pub async fn get_streams() -> Result<(TcpStream, TcpStream), tokio::io::Error> {
    let localhost = "127.0.0.1".to_owned();
    let listener = TcpListener::bind(format!("{}:0", localhost)).await?;
    let addr = listener.local_addr()?;
    let client_stream = TcpStream::connect(&addr).await?;
    let (server_stream, _) = listener.accept().await?;
    Ok((server_stream, client_stream))
}

pub fn client_config() -> Result<config::Builder, Error> {
    let mut builder = config::Config::builder();
    builder.set_security_policy(&DEFAULT_TLS13)?;
    builder.trust_pem(CERT_PEM)?;
    Ok(builder)
}

pub fn server_config() -> Result<config::Builder, Error> {
    let mut builder = config::Config::builder();
    builder.set_security_policy(&DEFAULT_TLS13)?;
    builder.load_pem(CERT_PEM, KEY_PEM)?;
    Ok(builder)
}

pub fn client_config_tls12() -> Result<config::Builder, Error> {
    let mut builder = config::Config::builder();
    builder.set_security_policy(&DEFAULT)?;
    builder.trust_pem(RSA_CERT_PEM)?;
    Ok(builder)
}

pub fn server_config_tls12() -> Result<config::Builder, Error> {
    let mut builder = config::Config::builder();
    builder.set_security_policy(&DEFAULT)?;
    builder.load_pem(RSA_CERT_PEM, RSA_KEY_PEM)?;
    Ok(builder)
}

pub async fn run_negotiate<A: Builder, B: Builder, C, D>(
    client: &TlsConnector<A>,
    client_stream: C,
    server: &TlsAcceptor<B>,
    server_stream: D,
) -> Result<(TlsStream<C, A::Output>, TlsStream<D, B::Output>), Error>
where
    <A as Builder>::Output: Unpin,
    <B as Builder>::Output: Unpin,
    C: AsyncRead + AsyncWrite + Unpin,
    D: AsyncRead + AsyncWrite + Unpin,
{
    let (client, server) = tokio::join!(
        client.connect("localhost", client_stream),
        server.accept(server_stream)
    );
    Ok((client?, server?))
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::{
    io,
    pin::Pin,
    sync::{Arc, Mutex},
    task::{Context, Poll},
};
use tokio::{
    io::{AsyncRead, AsyncWrite, ReadBuf},
    net::TcpStream,
};

type ReadFn = Box<dyn Fn(Pin<&mut TcpStream>, &mut Context, &mut ReadBuf) -> Poll<io::Result<()>>>;
type WriteFn = Box<dyn Fn(Pin<&mut TcpStream>, &mut Context, &[u8]) -> Poll<io::Result<usize>>>;

#[derive(Default)]
struct OverrideMethods {
    next_read: Option<ReadFn>,
    next_write: Option<WriteFn>,
}

#[derive(Default)]
pub struct Overrides(Mutex<OverrideMethods>);

impl Overrides {
    pub fn next_read(&self, input: Option<ReadFn>) {
        if let Ok(mut overrides) = self.0.lock() {
            overrides.next_read = input;
        }
    }

    pub fn next_write(&self, input: Option<WriteFn>) {
        if let Ok(mut overrides) = self.0.lock() {
            overrides.next_write = input;
        }
    }
}

pub struct TestStream {
    stream: TcpStream,
    overrides: Arc<Overrides>,
}

impl TestStream {
    pub fn new(stream: TcpStream) -> Self {
        let overrides = Arc::new(Overrides::default());
        Self { stream, overrides }
    }

    pub fn overrides(&self) -> Arc<Overrides> {
        self.overrides.clone()
    }
}

impl AsyncRead for TestStream {
    fn poll_read(
        self: Pin<&mut Self>,
        ctx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<io::Result<()>> {
        let s = self.get_mut();
        let stream = Pin::new(&mut s.stream);
        let action = match s.overrides.0.lock() {
            Ok(mut overrides) => overrides.next_read.take(),
            _ => None,
        };
        if let Some(f) = action {
            (f)(stream, ctx, buf)
        } else {
            stream.poll_read(ctx, buf)
        }
    }
}

impl AsyncWrite for TestStream {
    fn poll_write(
        self: Pin<&mut Self>,
        ctx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<io::Result<usize>> {
        let s = self.get_mut();
        let stream = Pin::new(&mut s.stream);
        let action = match s.overrides.0.lock() {
            Ok(mut overrides) => overrides.next_write.take(),
            _ => None,
        };
        if let Some(f) = action {
            (f)(stream, ctx, buf)
        } else {
            stream.poll_write(ctx, buf)
        }
    }

    fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
        Pin::new(&mut self.stream).poll_flush(ctx)
    }

    fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
        Pin::new(&mut self.stream).poll_shutdown(ctx)
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use s2n_tls::callbacks::MonotonicClock;
use std::time::Duration;
use tokio::time::Instant;

/// A monotonic clock that allows the s2n-tls C library time
/// to follow the tokio::time::pause behavior.
pub struct TokioTime(Instant);

impl Default for TokioTime {
    fn default() -> Self {
        TokioTime(Instant::now())
    }
}

impl MonotonicClock for TokioTime {
    fn get_time(&self) -> Duration {
        self.0.elapsed()
    }
}


use crate::{constants, distro, helper};
use distro::DistroKind;
use std::io::Write;
use std::{env, fs, io, process};

pub(crate) fn run_repair_script(distro: &distro::Distro, action_name: &str) -> io::Result<()> {
    helper::log_info("----- Start action -----");

    match env::set_current_dir(constants::RESCUE_ROOT) {
        Ok(_) => {}
        Err(e) => println!("Error in set current dir : {e}"),
    }

    // Set the environment correct
    let convert_bool = |state: bool| -> String {
        if state {
            "true".to_string()
        } else {
            "false".to_string()
        }
    };
    match distro.kind {
        DistroKind::Debian | DistroKind::Ubuntu => {
            env::set_var("isUbuntu", "true");
            env::set_var("isADE", convert_bool(distro.is_ade));
            env::set_var("root_part_path", distro.rescue_root.root_part_path.as_str());
            env::set_var("efi_part_path", helper::get_efi_part_path(distro).as_str());
            env::set_var("boot_part_path", distro.boot_part.boot_part_path.as_str());
            env::remove_var("isSuse");
            env::remove_var("isRedHat");
            env::remove_var("isRedHat6");
        }
        DistroKind::Suse => {
            env::set_var("isSuse", "true");
            env::set_var("root_part_path", distro.rescue_root.root_part_path.as_str());
            env::set_var("efi_part_path", helper::get_efi_part_path(distro).as_str());
            env::set_var("boot_part_path", distro.boot_part.boot_part_path.as_str());
            env::remove_var("isUbuntu");
            env::remove_var("isRedHat");
            env::remove_var("isRedHat6");
        }
        DistroKind::RedHatCentOS => {
            env::set_var("isRedHat", "true");
            env::set_var("isADE", convert_bool(distro.is_ade));
            env::set_var("root_part_path", distro.rescue_root.root_part_path.as_str());
            env::set_var("efi_part_path", helper::get_efi_part_path(distro).as_str());
            env::set_var("boot_part_path", distro.boot_part.boot_part_path.as_str());
            match distro.is_lvm {
                true => env::set_var("isLVM", "true"),
                false => env::set_var("isLVM", "false"),
            }
            env::set_var("lvm_root_part", distro.lvm_details.lvm_root_part.as_str());
            env::set_var("lvm_usr_part", distro.lvm_details.lvm_usr_part.as_str());
            env::set_var("lvm_var_part", distro.lvm_details.lvm_var_part.as_str());
            env::remove_var("isUbuntu");
            env::remove_var("isSuse");
            env::remove_var("isRedHat6");
        }
        DistroKind::RedHatCentOS6 => {
            env::set_var("isRedHat", "true");
            env::set_var("isADE", convert_bool(distro.is_ade));
            env::set_var("isRedHat6", "true");
            env::set_var("root_part_path", distro.rescue_root.root_part_path.as_str());
            env::set_var("efi_part_path", helper::get_efi_part_path(distro).as_str());
            env::set_var("boot_part_path", distro.boot_part.boot_part_path.as_str());
            match distro.is_lvm {
                true => env::set_var("isLVM", "true"),
                false => env::set_var("isLVM", "false"),
            }
            env::set_var("lvm_root_part", distro.lvm_details.lvm_root_part.as_str());
            env::set_var("lvm_usr_part", distro.lvm_details.lvm_usr_part.as_str());
            env::set_var("lvm_var_part", distro.lvm_details.lvm_var_part.as_str());
            env::remove_var("isUbuntu");
            env::remove_var("isSuse");
        }
        DistroKind::Undefined => {} // Nothing to do
    }

    if action_name == constants::CHROOT_CLI {
        // create a TMUX session which is used while one works directly in the chroot environment
        match process::Command::new("tmux")
            .arg("new-session")
            .arg("-d")
            .arg("-s")
            .arg("rescue")
            .arg("chroot")
            .arg(constants::RESCUE_ROOT)
            .arg("/bin/bash")
            .spawn()
            .expect("chroot can not be started")
            .wait()
        {
            Ok(_) => (),
            Err(err) => return Err(err),
        }

        // Need to prepare the environment to make chroot a bit more safer
        match process::Command::new("tmux")
            .arg("send-keys")
            .arg("-t")
            .arg("rescue")
            .arg(format!(". {}/safe-exit.sh", constants::ACTION_IMPL_DIR))
            .arg("Enter")
            .spawn()
            .expect("tmux script not run able")
            .wait()
        {
            Ok(_) => (),
            Err(err) => return Err(err),
        }

        match process::Command::new("tmux")
            .arg("attach")
            .arg("-t")
            .arg("rescue")
            .spawn()
            .expect("tmux attach not possible")
            .wait()
        {
            Ok(_) => (),
            Err(err) => return Err(err),
        }

    } else {
        // This is the normal arm to be used. Here we run the selected action
        // At first make the script executable
        let file_name = format!("{}/{}-impl.sh", constants::ACTION_IMPL_DIR, action_name);
        cmd_lib::run_cmd!(chmod 500 ${file_name})?;

        let output = process::Command::new("chroot")
            .arg(constants::RESCUE_ROOT)
            .arg("/bin/bash")
            .arg("-c")
            .arg(format!(
                "{}/{}-impl.sh",
                constants::ACTION_IMPL_DIR,
                action_name
            ))
            .output()?;

        io::stdout().write_all(&output.stdout).unwrap();
    }
    helper::log_info("----- Action stopped -----");

    Ok(())
}

pub(crate) fn is_action_available(action_name: &str) -> io::Result<bool> {
    if action_name == constants::CHROOT_CLI {
        return Ok(true);
    }

    let dircontent = fs::read_dir(constants::ACTION_IMPL_DIR)?;
    let mut actions: Vec<String> = Vec::new();
    for item in dircontent {
        let detail = format!("{}", item?.path().display());
        actions.push(detail);
    }
    Ok(actions
        .iter()
        .any(|a| a.ends_with(&format!("{action_name}-impl.sh"))))
}


use crate::constants;
use crate::distro;
use crate::helper;
use crate::mount;
use crate::redhat;
use crate::ubuntu;

/*

At first we need to find out whether we have to work on an encrypted OS
We can do this with lsblk in order to find out whether we have a device with the name osencrypt available

 lsblk
NAME                MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda                   8:0    0   48M  0 disk
â””â”€sda1                8:1    0   46M  0 part  /mnt/azure_bek_disk
sdb                   8:16   0   30G  0 disk
â”œâ”€sdb1                8:17   0 29.9G  0 part  /
â”œâ”€sdb14               8:30   0    4M  0 part
â””â”€sdb15               8:31   0  106M  0 part  /boot/efi
sdc                   8:32   0   64G  0 disk
â”œâ”€sdc1                8:33   0  500M  0 part  /tmp/dev/sdc1
â”œâ”€sdc2                8:34   0  500M  0 part  /investigateroot/boot
â”œâ”€sdc3                8:35   0    2M  0 part
â””â”€sdc4                8:36   0   63G  0 part
  â””â”€osencrypt       253:0    0   63G  0 crypt
    â”œâ”€rootvg-tmplv  253:1    0    2G  0 lvm   /investigateroot/tmp
    â”œâ”€rootvg-usrlv  253:2    0   10G  0 lvm   /investigateroot/usr
    â”œâ”€rootvg-optlv  253:3    0    2G  0 lvm   /investigateroot/opt
    â”œâ”€rootvg-homelv 253:4    0    1G  0 lvm   /investigateroot/home
    â”œâ”€rootvg-varlv  253:5    0    8G  0 lvm   /investigateroot/var
    â””â”€rootvg-rootlv 253:6    0    2G  0 lvm   /investigateroot
sdd                   8:48   0   50G  0 disk
â””â”€sdd1                8:49   0   50G  0 part  /mnt
sr0                  11:0    1  628K  0 rom

In the next step it is required to unmount all of LVM LVs.
This is due to the fact that we need to do a fs-check on all of the partitions. This we have to do for the boot
and EFI partition as well.

In the next step we mount them again on the usual paths for ALAR

------

On a non LVM system we need to do the similar steps
On an Ubuntu 16.x distro we have these details

 lsblk
NAME          MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda             8:0    0   50G  0 disk
â””â”€sda1          8:1    0   50G  0 part  /mnt
sdb             8:16   0   48M  0 disk
â””â”€sdb1          8:17   0   46M  0 part  /mnt/azure_bek_disk
sdc             8:32   0   30G  0 disk
â”œâ”€sdc1          8:33   0 29.9G  0 part  /
â”œâ”€sdc14         8:46   0    4M  0 part
â””â”€sdc15         8:47   0  106M  0 part  /boot/efi
sdd             8:48   0   30G  0 disk
â”œâ”€sdd1          8:49   0 29.7G  0 part
â”‚ â””â”€osencrypt 253:0    0 29.7G  0 crypt /investigateroot
â”œâ”€sdd2          8:50   0  256M  0 part  /investigateroot/boot
â”œâ”€sdd14         8:62   0    4M  0 part
â””â”€sdd15         8:63   0  106M  0 part  /tmp/dev/sdd15
sr0            11:0    1  628K  0 rom

Ubuntu 16 or 18 don't have a seperate boot partition
If ADE is used on them an extra partition is created to store the boot and luks part on an not encrypted
partition

*/

pub(crate) fn is_ade_enabled() -> bool {
    cmd_lib::run_cmd!(lsblk | grep -q osencrypt).is_ok()
}

pub(crate) fn do_ubuntu_ade(mut partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    helper::log_info("This is a recent Ubuntu 16.x/18.x with ADE enabled");

    // Get EFI partition
    partition_info.retain(|x| x.contains("EF00")); //Get the UEFI partition
    helper::set_efi_part_number_and_fs(distro, &partition_info[0]);
    helper::set_efi_part_path(distro);
    helper::fsck_partition(
        helper::get_efi_part_path(distro).as_str(),
        helper::get_efi_part_fs(distro).as_str(),
    );

    // Set the root_part_path manually
    distro.rescue_root.root_part_path = constants::OSENCRYPT_PATH.to_string();

    set_root_part_fs(distro);

    // Due to the changed partition layout on an ADE enabled OS we have to set the boot partiton details
    // We use hardcoded values in this case
    distro.boot_part.boot_part_fs = "ext2".to_string();
    distro.boot_part.boot_part_number = 2;
    distro.boot_part.boot_part_path = format!(
        "{}{}",
        helper::read_link(constants::RESCUE_DISK),
        distro.boot_part.boot_part_number
    );

    // Due to the fact that we have already mounted filesystems for ADE on a repair-vm
    // we need to unmount them first before we can do a fsck on each of the partitions
    umount_investigations(distro);
    fsck_partitions(distro);

    ubuntu::verify_ubuntu(distro);
}

pub(crate) fn do_redhat_nolvm_ade(partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    // Unfortunately we need to work with hardcoded values as there exist no label information
    distro.boot_part.boot_part_fs = "xfs".to_string();
    distro.boot_part.boot_part_number = 1;

    set_root_part_fs(distro);
    distro.rescue_root.root_part_number = 2;

    // Set the root_part_path manually for ADE
    distro.rescue_root.root_part_path = constants::OSENCRYPT_PATH.to_string();

    //For EFI partition we use normal logic in order to setup the details correct
    for partition in partition_info.iter() {
        if partition.contains("EFI") {
            helper::set_efi_part_number_and_fs(distro, partition);
        }
    }

    distro.boot_part.boot_part_path = format!(
        "{}{}",
        helper::read_link(constants::RESCUE_DISK),
        distro.boot_part.boot_part_number
    );

    helper::set_efi_part_path(distro);

    //Unmount the investigation path, otherwise the fsck isn't possible
    umount_investigations(distro);
    fsck_partitions(distro);

    redhat::verify_redhat_nolvm(distro);
}

pub(crate) fn do_redhat6_or_7_ade(partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    println!("6_7 info : {:?}", &partition_info);
    if let Some(root_info) = partition_info.iter().find(|x| x.contains("GiB")) {
        distro.rescue_root.root_part_number = helper::get_partition_number_detail(root_info);
        distro.rescue_root.root_part_path = format!(
            "{}{}",
            helper::read_link(constants::RESCUE_DISK),
            distro.rescue_root.root_part_number
        );
    }

    if let Some(boot_info) = partition_info.iter().find(|x| x.contains("MiB")) {
        distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(boot_info);
        distro.boot_part.boot_part_number = helper::get_partition_number_detail(boot_info);
        distro.boot_part.boot_part_path = format!(
            "{}{}",
            helper::read_link(constants::RESCUE_DISK),
            distro.boot_part.boot_part_number
        );
    }

    // Set the root_part_path manually for ADE
    distro.rescue_root.root_part_path = constants::OSENCRYPT_PATH.to_string();
    set_root_part_fs(distro);

    //Unmount the investigation path, otherwise the fsck isn't possible
    umount_investigations(distro);
    fsck_partitions(distro);

    redhat::verify_redhat_nolvm(distro);
}

pub(crate) fn do_redhat_lvm_ade(mut partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    /*
     Unfortunately we need to work with hardcoded values as there exist no label information

     Number  Start   End     Size    File system  Name                  Flags
    1      1049kB  525MB   524MB   fat16        EFI System Partition  boot, esp
    2      525MB   1050MB  524MB   xfs                                msftdata
    3      1050MB  1052MB  2097kB                                     bios_grub
    4      1052MB  68.7GB  67.7GB                                     lvm
    */

    // Get the right partitions
    // Get the boot partition first
    let mut partition_info_copy = partition_info.to_owned(); // we need a copy for later usage
    partition_info.retain(|x| !(x.contains("EF00") || x.contains("EF02") || x.contains("8E00"))); //remove the UEFI, the bios_boot partition

    distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(&partition_info[0]);
    distro.boot_part.boot_part_number = helper::get_partition_number_detail(&partition_info[0]);

    set_root_part_fs(distro);
    distro.rescue_root.root_part_number = 4;

    // Set the root_part_path manually for ADE
    distro.rescue_root.root_part_path = constants::OSENCRYPT_PATH.to_string();

    //For EFI partition we use normal logic in order to setup the details correct
    partition_info_copy.retain(|x| x.contains("EF00")); //Get the UEFI partition only
    helper::set_efi_part_number_and_fs(distro, &partition_info_copy[0]);

    distro.boot_part.boot_part_path = format!(
        "{}{}",
        helper::read_link(constants::RESCUE_DISK),
        distro.boot_part.boot_part_number
    );

    helper::set_efi_part_path(distro);

    // LVM mounts need to be removed. We need to remount them later
    umount_investigations_lvm();
    //Unmount the investigation path, otherwise the fsck isn't possible
    umount_investigations(distro);
    fsck_partitions(distro);

    // Set the LVM path details in order to work with ADE
    distro.lvm_details.lvm_root_part = redhat::lvm_path_helper("rootlv");
    distro.lvm_details.lvm_usr_part = redhat::lvm_path_helper("usrlv");
    distro.lvm_details.lvm_var_part = redhat::lvm_path_helper("varlv");

    redhat::verify_redhat_lvm(distro);
}

fn fsck_partitions(distro: &distro::Distro) {
    helper::fsck_partition(
        distro.rescue_root.root_part_path.as_str(),
        distro.rescue_root.root_part_fs.as_str(),
    );

    helper::fsck_partition(
        distro.boot_part.boot_part_path.as_str(),
        distro.boot_part.boot_part_fs.as_str(),
    );

    helper::fsck_partition(
        helper::get_efi_part_path(distro).as_str(),
        helper::get_efi_part_fs(distro).as_str(),
    );
}

fn umount_investigations(distro: &distro::Distro) {
    // umount EFI
    if helper::has_efi_part(distro) {
        mount::umount(constants::INVESTIGATEROOT_EFI_DIR);
    }
    // umount boot
    mount::umount(constants::INVESTIGATEROOT_BOOT_DIR);

    // umount osencrypt
    if !distro.is_lvm {
        //  If it is LVM we have already unmounted the '/investigationroot'
        mount::umount(constants::INVESTIGATEROOT_DIR);
    }
}

fn umount_investigations_lvm() {
    /*
        These are the mounts we have to remove
        â””â”€sdc4                8:36   0   63G  0 part
            â””â”€osencrypt       253:0    0   63G  0 crypt
            â”œâ”€rootvg-tmplv  253:1    0    2G  0 lvm   /investigateroot/tmp
            â”œâ”€rootvg-usrlv  253:2    0   10G  0 lvm   /investigateroot/usr
            â”œâ”€rootvg-optlv  253:3    0    2G  0 lvm   /investigateroot/opt
            â”œâ”€rootvg-homelv 253:4    0    1G  0 lvm   /investigateroot/home
            â”œâ”€rootvg-varlv  253:5    0    8G  0 lvm   /investigateroot/var
            â””â”€rootvg-rootlv 253:6    0    2G  0 lvm   /investigateroot
    */
    mount::umount("/investigateroot/tmp");
    mount::umount("/investigateroot/usr");
    mount::umount("/investigateroot/opt");
    mount::umount("/investigateroot/home");
    mount::umount("/investigateroot/var");
    mount::umount("/investigateroot");
}

fn set_root_part_fs(mut distro: &mut distro::Distro) {
    if let Ok(line) = cmd_lib::run_fun!(lsblk -fn /dev/mapper/osencrypt) {
        distro.rescue_root.root_part_fs = helper::cut(line.as_str(), " ", 1).to_string();
    }
}


/*

The following options and flags need to be available

FLAG
-----
-s --standalone : This should signal that we run in standalone mode. Any required repair scripts need to be downloaded from git

ARGUMENT
--------
Either pass over a single action or many seperated by a comma.
Each action needs then to be verified for its existens on git/filesystem
If the action does exists it gets executed

OPTIONS
--------
 -d --dir : The directory in which action-implementations are stored. Can be used for testing of scripts as well.
            The standalone flag is necessary to be set as well

*/
use clap::{App, Arg};

pub(crate) struct CliInfo {
    pub(crate) action_directory: String,
    pub(crate) actions: String,
}

impl CliInfo {
    pub(crate) fn new() -> Self {
        Self {
            action_directory: "".to_string(),
            actions: "".to_string(),
        }
    }
}

pub(crate) fn cli() -> CliInfo {
    let about = "
ALAR tries to assist with non boot able scenarios by running
one or more different actions in order to get a VM in a running state that allows
the administrator to further recover the VM after it is up, running and accessible again.
";
    let matches = App::new("Azure Linux Auto Recover")
        .version(clap::crate_version!())
        .author("Marcus Lachmanez , malachma@microsoft.com")
        .about(about)
        .arg(
            Arg::with_name("directory")
                .short('d')
                .long("directory")
                .takes_value(true)
                .help(
                    "The directory in which the actions are defined",
                ),
        )
        .arg(
            Arg::with_name("ACTION")
                .help("Sets the input file to use")
                .required(true)
                .index(1),
        )
        .get_matches();
    let mut cli_info = CliInfo::new();

    // Calling .unwrap() is safe here because "ACTION" is required
    // this is true for directory as well if flag standalone is present
    cli_info.actions = matches.value_of("ACTION").unwrap().to_string();
    if matches.is_present("directory") {
        cli_info.action_directory = matches.value_of("directory").unwrap().to_string();
    }

    cli_info
}


pub(crate) static RESCUE_DISK: &str = "/dev/disk/azure/scsi1/lun0";
pub(crate) static RESCUE_ROOT: &str = "/srv/rescue-root/";
pub(crate) static RESCUE_ROOT_RUN: &str = "/srv/rescue-root/run";
pub(crate) static RESCUE_ROOT_BOOT: &str = "/srv/rescue-root/boot";
pub(crate) static RESCUE_ROOT_BOOT_EFI: &str = "/srv/rescue-root/boot/efi";
pub(crate) static RESCUE_ROOT_USR: &str = "/srv/rescue-root/usr";
pub(crate) static RESCUE_ROOT_VAR: &str = "/srv/rescue-root/var";
pub(crate) static SUPPORT_FILESYSTEMS: &str = "dev proc sys tmp dev/pts";
pub(crate) static ASSERT_PATH: &str = "/tmp/assert";
pub(crate) static REDHAT_RELEASE: &str = "/tmp/assert/etc/redhat-release";
pub(crate) static OS_RELEASE: &str = "/tmp/assert/etc/os-release";
pub(crate) static OSENCRYPT_PATH: &str = "/dev/mapper/osencrypt";
pub(crate) static ACTION_IMPL_DIR: &str = "/tmp/action_implementation";
pub(crate) static INVESTIGATEROOT_DIR: &str = "/investigateroot";
pub(crate) static INVESTIGATEROOT_BOOT_DIR: &str = "/investigateroot/boot";
pub(crate) static INVESTIGATEROOT_EFI_DIR: &str = "/investigateroot/boot/efi";
pub(crate) static CHROOT_CLI: &str = "chroot-cli";
pub(crate) static TARBALL: &str = "https://github.com/Azure/ALAR/tarball/master";


use crate::ade;
use crate::constants;
use crate::helper;
use crate::helper::read_link;
use crate::mount;
use crate::redhat;
use crate::suse;
use crate::ubuntu;
use std::process;

#[derive(Debug)]
pub struct Distro {
    pub boot_part: BootPartDetails,
    pub rescue_root: RootPartDetails,
    pub efi_part: EfiPartT,
    pub is_lvm: bool,
    pub is_ade: bool,
    pub lvm_details: LVMDetails,
    pub kind: DistroKind,
}

#[derive(Debug, PartialEq, Default)]
pub enum DistroKind {
    Debian,
    Suse,
    RedHatCentOS,
    RedHatCentOS6,
    Ubuntu,
    #[default]
    Undefined,
}
#[derive(Debug)]
pub struct BootPartDetails {
    pub(crate) boot_part_fs: String,
    pub(crate) boot_part_number: u8,
    pub(crate) boot_part_path: String,
}

#[derive(Debug)]
pub struct RootPartDetails {
    pub(crate) root_part_fs: String,
    pub(crate) root_part_number: u8,
    pub(crate) root_part_path: String,
}

#[derive(Debug)]
pub struct LVMDetails {
    pub(crate) lvm_root_part: String,
    pub(crate) lvm_usr_part: String,
    pub(crate) lvm_var_part: String,
}

#[derive(Debug, PartialEq)]
pub struct EfiPartition {
    pub(crate) efi_part_number: u8,
    pub(crate) efi_part_fs: String,
    pub(crate) efi_part_path: String,
}

impl EfiPartition {
    fn new() -> Self {
        Self {
            efi_part_fs: "".to_string(),
            efi_part_path: "".to_string(),
            efi_part_number: 0,
        }
    }
}

#[derive(Debug, PartialEq, Default)]
pub enum EfiPartT {
    EfiPart(EfiPartition),
    #[default]
    NoEFI,
}

impl EfiPartT {
    pub fn new() -> EfiPartT {
        EfiPartT::EfiPart(EfiPartition::new())
    }
}

impl BootPartDetails {
    fn new() -> Self {
        Self {
            boot_part_fs: "".to_string(),
            boot_part_path: "".to_string(),
            boot_part_number: 0,
        }
    }
}

impl RootPartDetails {
    fn new() -> Self {
        Self {
            root_part_fs: "".to_string(),
            root_part_path: "".to_string(),
            root_part_number: 0,
        }
    }
}

impl LVMDetails {
    fn new() -> Self {
        Self {
            lvm_root_part: "".to_string(),
            lvm_usr_part: "".to_string(),
            lvm_var_part: "".to_string(),
        }
    }
}

impl Distro {
    pub fn new() -> Distro {
        let mut partitions: Vec<String> = Vec::new();
        //get_partitions is filling the variable partitions
        get_partitions(&mut partitions);
        let efi_part: EfiPartT = Default::default();
        let boot_part: BootPartDetails = BootPartDetails::new();
        let root_part: RootPartDetails = RootPartDetails::new();
        let kind: DistroKind = Default::default();
        let lvm_details = LVMDetails::new();
        let mut distro: Distro = Distro {
            boot_part,
            rescue_root: root_part,
            efi_part,
            is_lvm: false,
            is_ade: false,
            lvm_details,
            kind,
        };

        // here we start the core logic in order to determine what distro and type we have to cope with
        dispatch(partitions, &mut distro);

        distro
    }
}

fn get_partitions(partitions: &mut Vec<String>) {
    let link = read_link(constants::RESCUE_DISK);
    let sedscript = r#"s|[ ]\+| |g;s|^[ \t]*||"#;
    let out = cmd_lib::run_fun!(sgdisk ${link} -p | grep -E "^ *[1,2,3,4,5,6]" | sed $sedscript);
    // we are only interested in partitions which contain the numbers 1-6. Multiple whitespaces and trailing ones are removed

    match out {
        Ok(v) => {
            for line in v.lines() {
                // Need to check if no garbage is in it
                if line.contains("GiB") || line.contains("MiB") || line.contains("KiB") {
                    partitions.push(line.to_string());
                }
            }
            helper::log_info(
                format!("We have the following partitions determined: {partitions:?}").as_str(),
            );
        }
        Err(e) => panic!("Error {e:?}"),
    }
}

// If there is only one partition detected
fn do_old_ubuntu_or_centos(partition_info: Vec<String>, mut distro: &mut Distro) {
    helper::log_info("This could be an old Ubuntu image or even an CentOS with one partition only");

    // At first we have to determine whether this is a Ubuntu distro
    // or whether it is a single partiton CentOs distro
    if let Err(e) = mount::mkdir_assert() {
        panic!(
            "Creating assert directory is not possible : '{e}'. ALAR is not able to proceed further"
        );
    }

    distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(&partition_info[0]);
    distro.rescue_root.root_part_number = helper::get_partition_number_detail(&partition_info[0]);
    distro.rescue_root.root_part_path = format!(
        "{}{}",
        read_link(constants::RESCUE_DISK),
        distro.rescue_root.root_part_number
    );

    helper::fsck_partition(
        distro.rescue_root.root_part_path.as_str(),
        distro.rescue_root.root_part_fs.as_str(),
    );
    mount::mount_path_assert(distro.rescue_root.root_part_path.as_str());
    let pretty_name = helper::get_pretty_name("/tmp/assert/etc/os-release");
    mount::umount(constants::ASSERT_PATH);

    if pretty_name.contains("Debian") || pretty_name.contains("Ubuntu") {
        distro.kind = DistroKind::Ubuntu;
        let _ = mount::rmdir(constants::ASSERT_PATH);
    } else {
        // Single partiton CentOS
        let _ = mount::rmdir(constants::ASSERT_PATH);
        redhat::verify_redhat_nolvm(distro);
    }
}

// if we have two partition detected
//fn do_red_hat(partition_info: &Vec<String>, distro: &mut Distro) {
fn do_red_hat(partition_info: Vec<String>, distro: &mut Distro) {
    helper::log_info("This could be a RedHat/Centos 6/7 image");
    redhat::do_redhat6_or_7(partition_info, distro);
}

// if we have 3 partition detected
// Usuall this is a layout for Ubuntu. But recent RedHat RAW images do have this layout as well
fn do_recent_ubuntu_or(partition_info: Vec<String>, distro: &mut Distro) {
    helper::log_info(
        "This could be a recent Ubuntu 16.x or 18.x image. Or a RedHat with RAW partitions",
    );
    // We call this function for both Ubuntu and RedHat the logic is the same to get partitions determined
    ubuntu::do_ubuntu(partition_info, distro);

    // In the next step we figure out what it is
    // The order is important otherwise an incorrect distro gets reported
    redhat::verify_redhat_nolvm(distro);
    ubuntu::verify_ubuntu(distro);
}

// if we have 4 partition detected
fn do_suse_or_lvm_or_ubuntu(partition_info: Vec<String>, distro: &mut Distro) {
    // This function is also called if we have an recent Ubuntu distro with ADE enabled
    // With ADE a 4th partition got added to hold the boot-part-details plus luks

    // Define an enum which is used to decide which further part has to be executed

    enum Logic {
        RedHat,
        Suse,
        Ubuntu,
    }
    let mut which_logic = Logic::RedHat; // Default value is Redhat

    // Not sure whether this is a RedHat or CENTOS with LVM or it is a Suse 12/15 instead
    // Need to make a simple test
    for partition in partition_info.iter() {
        if partition.contains("lxboot") {
            which_logic = Logic::Suse;
        }
    }

    // Verify if we have an Ubuntu distro with ADE enabled
    // This needs to be verified first before we can do the RedHat part instead
    // Since with ADE on Ubuntu we got a 4th partition added
    if distro.is_ade {
        let pretty_name = helper::get_pretty_name("/investigateroot/etc/os-release"); // This path must exists, otherwise it can not be determined
        if pretty_name.is_empty() {
            helper::log_error("'/investigationroot' needs to be mounted first. ALAR does stop");
            process::exit(1);
        }
        if pretty_name.contains("Ubuntu") {
            which_logic = Logic::Ubuntu;
        }
    }

    match which_logic {
        Logic::RedHat => redhat::do_redhat_lvm_or(partition_info, distro),
        Logic::Suse => suse::do_suse(partition_info, distro),
        Logic::Ubuntu => ade::do_ubuntu_ade(partition_info, distro),
    }
}

fn dispatch(partition_info: Vec<String>, mut distro: &mut Distro) {
    // Test for an ADE repair environment
    distro.is_ade = ade::is_ade_enabled();
    helper::log_info(format!("Ade is enabled : {}", distro.is_ade).as_str());

    match partition_info.len() {
        1 => do_old_ubuntu_or_centos(partition_info, distro),
        2 => do_red_hat(partition_info, distro),
        3 => do_recent_ubuntu_or(partition_info, distro),
        4 => do_suse_or_lvm_or_ubuntu(partition_info, distro),
        _ => {
            helper::log_error("Unrecognized Linux distribution. ALAR tool is stopped\n
                 Your OS can not be determined. The OS distros supported are:\n
                 CentOS/Redhat 6.8 - 8.2\n
                 Ubuntu 16.4 LTS, Ubuntu 18.4 LTS, Ubuntu 22.04 LTS\n
                 Suse 12 and 15\n
                 Debain 9 and 10\n
                 ALAR will stop!\n
                 If your OS is in the above list please report this issue at https://github.com/azure/repair-script-library/issues"
        );

            process::exit(1);
        }
    }
}


use crate::constants;
use crate::distro::{Distro, EfiPartT, EfiPartition};
use crate::mount;
use chrono::prelude::Utc;
use cmd_lib::*;
use std::process::Stdio;
use std::{fs, process};

pub fn log_info(msg: &str) {
    println!("[Info {}] {}", Utc::now(), msg);
}

#[allow(dead_code)]
pub fn log_output(msg: &str) {
    println!("[Output {}] {}", Utc::now(), msg);
}

#[allow(dead_code)]
pub fn log_warning(msg: &str) {
    println!("[Warning {}] {}", Utc::now(), msg);
}

pub fn log_error(msg: &str) {
    println!("[Error {}] {}", Utc::now(), msg);
}

pub fn log_debug(msg: &str) {
    println!("[Debug {}] {}", Utc::now(), msg);
}

pub(crate) fn read_link(path: &str) -> String {
    match fs::canonicalize(path) {
        Ok(value) => format!("{}", value.display()),
        Err(e) => {
            log_error(&e.to_string());
            panic!("readlink did fail : {path}")
        }
    }
}

pub(crate) fn part_info_helper(sgdiskinfo: &str) -> String {
    let part_number = cut(sgdiskinfo, " ", 0).trim_end().parse::<u8>().unwrap();
    let path = format!("{}{}", read_link(constants::RESCUE_DISK), part_number);
    let run_output = run_fun!(blkid -o export $path);
    match run_output {
        Ok(value) => value,
        Err(_) => "ERROR".to_string(),
    }
}

pub(crate) fn cut<'a>(source: &'a str, delimiter: &str, field: usize) -> &'a str {
    match source.split(delimiter).nth(field) {
        Some(value) => value,
        None => {
            log_error("String not found. FATAL! ERROR NOT RECOVERABLE");
            panic!("Error in function cut");
        }
    }
}

pub(crate) fn get_partition_number_detail(sgdiskinfo: &str) -> u8 {
    cut(sgdiskinfo, " ", 0).parse::<u8>().unwrap()
}

pub(crate) fn get_partition_filesystem_detail(sgdiskinfo: &str) -> String {
    let info = part_info_helper(sgdiskinfo);
    let mut lines = vec![];
    let mut fs_return = "".to_string();
    for i in info.lines() {
        lines.push(i);
    }
    lines.retain(|x| x.starts_with("TYPE="));
    if let Some(fs) = lines[0].to_string().strip_prefix("TYPE=") {
        fs_return = fs.to_string();
    }
    fs_return
}

pub(crate) fn get_pretty_name(path: &str) -> String {
    let mut pretty_name: String = "".to_string();
    if let Ok(name) = run_fun!(grep -s PRETTY_NAME $path) {
        pretty_name = cut(&name, "=", 1).to_string();
    } else if let Ok(value) = fs::read_to_string(constants::REDHAT_RELEASE) {
        pretty_name = value;
    }
    log_info(format!("Pretty Name is : {}", &pretty_name).as_str());
    pretty_name
}

pub(crate) fn fsck_partition(partition_path: &str, partition_filesystem: &str) {
    // Need to handel the condition if no filesystem is available
    // This can happen if we have a LVM partition
    if partition_filesystem.is_empty() {
        return;
    }

    //let mut result: result::Result<String, io::Error> = Err(io::Error::new(io::ErrorKind::Other, "none")); // run_cmd returns "type CmdResult = Result<(), Error>;"
    let mut exit_code = Some(0i32);

    match partition_filesystem {
        "xfs" => {
            log_info(format!("fsck for XFS on {partition_path}").as_str());
            if let Err(e) = mount::mkdir_assert() {
                panic!("Creating assert directory is not possible : '{e}'. ALAR is not able to proceed further");
            }

            // In case the filesystem has valuable metadata changes in a log which needs to
            // be replayed.  Mount the filesystem to replay the log, and unmount it before
            // re-running xfs_repair
            mount::mount_path_assert(partition_path);
            mount::umount(constants::ASSERT_PATH);

            if let Ok(stat) = process::Command::new("xfs_repair")
                .arg(partition_path)
                .stdout(Stdio::null())
                .stderr(Stdio::null())
                .status()
            {
                exit_code = stat.code();
            }

            /*
               Because of RedHat9 a second validation needs to be performed
               as xfs_repair on Ubuntu isn't able to cope with the newer XFS v5 format which is used on RedHat9
               --> Found unsupported filesystem features
            */

            if let Ok(value) = process::Command::new("xfs_repair")
                .args([partition_path])
                .output()
            {
                // unwrap should be safe here as we get a result returned
                // xfs_repair is throwing an error, thus we need to use stderr
                let result_value = String::from_utf8(value.stderr).unwrap();
                if result_value.contains("Found unsupported filesystem features") {
                    exit_code = Some(0);
                }
            }
        }
        "fat16" => {
            log_info("fsck for fat16/vfat");
            if let Ok(stat) = process::Command::new("fsck.vfat")
                .args(["-p", partition_path])
                .status()
            {
                exit_code = stat.code();
            }
        }
        _ => {
            log_info(format!("fsck for {partition_filesystem}").as_str());
            if let Ok(stat) = process::Command::new(format!("fsck.{partition_filesystem}"))
                .args(["-p", partition_path])
                .status()
            {
                exit_code = stat.code();
            }
        }
    }

    match exit_code {
        // error 4 is returned by fsck.ext4 only
        Some(_code @ 4) => {
            log_error(
                format!(
                    "Partition {} can not be repaired in auto mode",
                    &partition_path
                )
                .as_str(),
            );
            log_error("Aborting ALAR");
            process::exit(1);
        }
        // xfs_repair -n returns 1 if the fs is corrupted.
        // Also fsck may raise this error but we ignore it as even a normal recover is raising it. FALSE-NEGATIVE
        Some(_code @ 1) if partition_filesystem == "xfs" => {
            log_error("A general error occured while trying to recover the device ${root_rescue}.");
            log_error("Aborting ALAR");
            process::exit(1);
        }
        None => {
            panic!(
                "fsck operation terminated by signal error. ALAR is not able to proceed further!"
            );
        }

        // Any other error state is not of interest for us
        _ => {}
    }

    log_info("File system check finished");
}

pub(crate) fn set_efi_part_number_and_fs(distro: &mut Distro, partition: &str) {
    let mut new_efi_part = EfiPartT::new();
    if let EfiPartT::EfiPart(EfiPartition {
        efi_part_number: ref mut ref_to_number,
        efi_part_fs: ref mut ref_to_efi_part_fs,
        efi_part_path: _,
    }) = new_efi_part
    {
        *ref_to_efi_part_fs = get_partition_filesystem_detail(partition);
        *ref_to_number = get_partition_number_detail(partition);
    }
    distro.efi_part = new_efi_part;
}

pub(crate) fn set_efi_part_path(distro: &mut Distro) {
    // set_efi_part_path has to be used only after set_efi_part_number_and_fs has been called
    let part_number = get_efi_part_number(distro);
    if let EfiPartT::EfiPart(EfiPartition {
        efi_part_number: _,
        efi_part_fs: _,
        efi_part_path: ref mut ref_to_efi_part_path,
    }) = distro.efi_part
    {
        *ref_to_efi_part_path = format!("{}{}", read_link(constants::RESCUE_DISK), part_number);
    }
}

pub(crate) fn get_efi_part_path(distro: &Distro) -> String {
    let mut path: String = String::from("");
    if let EfiPartT::EfiPart(EfiPartition {
        efi_part_number: _,
        efi_part_fs: _,
        efi_part_path: ref ref_to_efi_part_path,
    }) = distro.efi_part
    {
        path = ref_to_efi_part_path.to_string();
    }
    path
}

pub(crate) fn has_efi_part(distro: &Distro) -> bool {
    match distro.efi_part {
        EfiPartT::NoEFI => false,
        EfiPartT::EfiPart(_) => true,
    }
}

pub(crate) fn get_efi_part_fs(distro: &Distro) -> String {
    let mut fs: String = String::from("");
    if let EfiPartT::EfiPart(EfiPartition {
        efi_part_number: _,
        efi_part_fs: ref ref_to_efi_part_fs,
        efi_part_path: _,
    }) = distro.efi_part
    {
        fs = ref_to_efi_part_fs.to_string();
    }
    fs
}

fn get_efi_part_number(distro: &Distro) -> u8 {
    let mut number: u8 = 0;
    if let EfiPartT::EfiPart(EfiPartition {
        efi_part_number: internal_number,
        efi_part_fs: _,
        efi_part_path: _,
    }) = distro.efi_part
    {
        number = internal_number;
    }
    number
}


mod action;
mod ade;
mod cli;
mod constants;
mod distro;
mod helper;
mod mount;
mod prepare_action;
mod redhat;
mod standalone;
mod suse;
mod ubuntu;
use std::process;

fn main() {
    // First verify we have the right amount of information to operate
    let cli_info = cli::cli();

    // are we root?
    let euid = unsafe { nc::geteuid() };
    if euid != 0 {
        println!("Please run alar as root");
        process::exit(1);
    }

    // Verify the distro we have to work with
    // the Distro struct does contain then all of the required information
    let distro = distro::Distro::new();
    eprintln!("{distro:?}");

    // Do we have a valid distro or not?
    if distro.kind == distro::DistroKind::Undefined {
        helper::log_error("Unrecognized Linux distribution. The ALAR tool isn't able to recover it\n
                 The OS distros supported are:\n
                 CentOS/Redhat 6.8 - 9.x\n
                 Ubuntu 16.4 LTS, 18 LTS, 20.04 LTS\n
                 Suse 12 and 15\n
                 Debain 9, 10, 11\n
                 ALAR will stop!\n
                 If your OS is in the above list please report this issue at https://github.com/Azure/ALAR/issues"
        );
        process::exit(1);
    }

    // Prepare and mount the partitions. Take into account what distro we have to deal with
    match mount::mkdir_rescue_root() {
        Ok(_) => {}
        Err(e) => panic!("The rescue-root dir can't be created. This is not recoverable! : {e} "),
    }

    // Step 2 of prepare and mount. Mount the right dirs depending on the distro determined
    prepare_action::distro_mount(&distro);

    // Get the actions
    if let Err(e) = standalone::download_action_scripts(&cli::cli()) {
        prepare_action::distro_umount(&distro);
        panic!("action scripts are not able to be copied or downloadable : '{e}'");
    }

    // Verify we have an implementation available for the action to be executed
    // Define a variable for the error condition that may happen
    let mut is_action_error = false;
    for action_name in cli_info.actions.split(',') {
        match action::is_action_available(action_name) {
            // Do the action
            Ok(_is @ true) => match action::run_repair_script(&distro, action_name) {
                Ok(_) => is_action_error = false,
                Err(e) => {
                    helper::log_error(
                        format!("Action {} raised an error: '{}'", &action_name, e).as_str(),
                    );
                    is_action_error = true;
                }
            },
            Ok(_is @ false) => {
                helper::log_error(format!("Action '{action_name}' is not available").as_str());
                is_action_error = true;
            }
            Err(e) => {
                helper::log_error(
                    format!("There was an error raised while verifying the action: '{e}'").as_str(),
                );
                is_action_error = true;
            }
        }
    }

    // Umount everything again

    prepare_action::distro_umount(&distro);

    // Inform the calling process about the success
    if is_action_error {
        process::exit(1);
    } else {
        process::exit(0);
    }
}


use crate::constants;
use crate::helper;
use std::{fs, io, process};
//use sys_mount;

pub(crate) fn mkdir_assert() -> Result<(), io::Error> {
    match fs::create_dir_all(constants::ASSERT_PATH) {
        Ok(()) => Ok(()),
        Err(e) => {
            println!("Error while creating the assert directory: {e}");
            Err(e)
        }
    }
}

pub(crate) fn mkdir_rescue_root() -> Result<(), io::Error> {
    match fs::create_dir_all(constants::RESCUE_ROOT) {
        Ok(()) => Ok(()),
        Err(e) => {
            println!("Error while creating the rescue-root directory: {e}");
            Err(e)
        }
    }
}
fn mount(source: &str, destination: &str, _option: Option<&str>) {
    // There is an issue on Ubuntu that the XFS filesystem is not enabled by default
    // We need to load the driver first
    match process::Command::new("modprobe").arg("xfs").status() {
        Ok(_) => {}
        Err(_) => helper::log_error(
            "Loading of the module xfs was not possible. This may result in mount issues! : ",
        ),
    }

    let supported = match sys_mount::SupportedFilesystems::new() {
        Ok(supported) => supported,
        Err(_) => {
            helper::log_error("Failed to get supported file systems");
            panic!();
        }
    };

    match sys_mount::Mount::builder()
        .fstype(&supported)
        .flags(sys_mount::MountFlags::empty())
        .mount(source, destination)
    {
        Ok(_) => {
            helper::log_info(format!("mounted {} to {}", source, &destination).as_str());
        }
        Err(why) => {
            helper::log_error(format!("failed to mount {source} on {destination}: {why}").as_str());
            panic!();
        }
    }
}

pub(crate) fn bind_mount(source: &str, destination: &str) {
    let supported = match sys_mount::SupportedFilesystems::new() {
        Ok(supported) => supported,
        Err(_) => {
            helper::log_error("Failed to get supported file systems");
            panic!();
        }
    };

    //match sys_mount::Mount::new(source, destination, &supported, sys_mount::MountFlags::BIND, None) {
    // match sys_mount::Mount::new(source, destination) {
    match sys_mount::Mount::builder()
        .fstype(&supported)
        .flags(sys_mount::MountFlags::BIND)
        .mount(source, destination)
    {
        Ok(_) => {
            //helper::log_info(format!("mounted {} to {}", source, &destination).as_str() );
        }
        Err(why) => {
            helper::log_error(format!("failed to mount {source} on {destination}: {why}").as_str());
            panic!();
        }
    }
}

pub(crate) fn mount_path_assert(source: &str) {
    mount(source, constants::ASSERT_PATH, None);
}

pub(crate) fn mount_root_on_rescue_root(root_source: &str, option: Option<&str>) {
    mount(root_source, constants::RESCUE_ROOT, option);
}

pub(crate) fn mount_boot_on_rescue_boot(boot_source: &str, option: Option<&str>) {
    mount(boot_source, constants::RESCUE_ROOT_BOOT, option);
}

pub(crate) fn mount_efi_on_rescue_efi(efi_source: &str, option: Option<&str>) {
    mount(efi_source, constants::RESCUE_ROOT_BOOT_EFI, option);
}

// Used only for LVM
pub(crate) fn mount_usr_on_rescue_root_usr(usr_source: &str) {
    mount(usr_source, constants::RESCUE_ROOT_USR, None);
}

// Used only for LVM
pub(crate) fn mount_var_on_rescue_root_var(var_source: &str) {
    mount(var_source, constants::RESCUE_ROOT_VAR, None);
}

pub(crate) fn umount(destination: &str) {
    match sys_mount::unmount(destination, sys_mount::UnmountFlags::DETACH) {
        Ok(()) => (),
        Err(why) => {
            helper::log_error(format!("Failed to unmount {destination}: {why}").as_str());
            helper::log_error("This shouldn't cause a severe issue for ALAR.");
        }
    }
}

pub(crate) fn rmdir(path: &str) -> std::io::Result<()> {
    fs::remove_dir_all(path)?;
    Ok(())
}


use crate::constants;
use crate::distro;
use crate::distro::DistroKind;
use crate::helper;
use crate::mount;

use std::{fs, io};

pub(crate) fn ubuntu_mount(distro: &distro::Distro) {
    // We have to verify also whether we have old Ubuntus/Debian with one partition only
    // Or whether we have also an EFI partition available

    mount::mount_root_on_rescue_root(distro.rescue_root.root_part_path.as_str(), None);

    // If ADE is enabled the extra boot partition needs to be mounted
    if distro.is_ade {
        mount::mount_boot_on_rescue_boot(distro.boot_part.boot_part_path.as_str(), None);
    }

    let mount_option: Option<&str>;
    if distro.efi_part != distro::EfiPartT::NoEFI {
        if helper::get_efi_part_fs(distro) == "xfs" {
            mount_option = Some("nouuid");
        } else {
            mount_option = None;
        }
        mount::mount_efi_on_rescue_efi(helper::get_efi_part_path(distro).as_str(), mount_option);
    }

    mount_support_filesystem();
    mount::bind_mount("/run", constants::RESCUE_ROOT_RUN);
}

pub(crate) fn ubuntu_umount(distro: &distro::Distro) {
    umount_support_filesystem();
    mount::umount(constants::RESCUE_ROOT_RUN);

    if distro.efi_part != distro::EfiPartT::NoEFI {
        mount::umount(constants::RESCUE_ROOT_BOOT_EFI);
    }

    // If ADE is enabled for Ubuntu the boot partition needs to be unmounted first
    if distro.is_ade {
        mount::umount(constants::RESCUE_ROOT_BOOT);
    }

    mount::umount(constants::RESCUE_ROOT);
}

pub(crate) fn suse_mount(distro: &distro::Distro) {
    redhat_mount(distro); // We can use the same functionality
}

pub(crate) fn suse_umount(distro: &distro::Distro) {
    redhat_umount(distro); // we can use the same functionality
}

pub(crate) fn redhat_mount(distro: &distro::Distro) {
    if distro.is_lvm {
        let mut mount_option: Option<&str>;
        mount::mount_root_on_rescue_root(distro.lvm_details.lvm_root_part.as_str(), None);
        mount::mount_usr_on_rescue_root_usr(distro.lvm_details.lvm_usr_part.as_str());
        mount::mount_var_on_rescue_root_var(distro.lvm_details.lvm_var_part.as_str());

        if distro.boot_part.boot_part_fs == "xfs" {
            mount_option = Some("nouuid");
        } else {
            mount_option = None;
        }
        mount::mount_boot_on_rescue_boot(distro.boot_part.boot_part_path.as_str(), mount_option);

        if helper::get_efi_part_fs(distro) == "xfs" {
            mount_option = Some("nouuid");
        } else {
            mount_option = None;
        }
        mount::mount_efi_on_rescue_efi(helper::get_efi_part_path(distro).as_str(), mount_option);

        mount_support_filesystem();
    } else {
        // if we have an XFS filesystem we have to set the 'nouuid' option
        let mut mount_option: Option<&str>;
        if distro.rescue_root.root_part_fs == "xfs" {
            mount_option = Some("nouuid");
        } else {
            mount_option = None;
        }

        mount::mount_root_on_rescue_root(distro.rescue_root.root_part_path.as_str(), mount_option);

        if distro.boot_part.boot_part_fs == "xfs" {
            mount_option = Some("nouuid");
        } else {
            mount_option = None;
        }

        // if no boot partition is available we skip this step
        // this is required if we have the special condition that a RedHat RAW image does only have 3 partitons
        // root, EF00 and EF02
        if distro.boot_part.boot_part_number != 0 {
            mount::mount_boot_on_rescue_boot(
                distro.boot_part.boot_part_path.as_str(),
                mount_option,
            );
        }

        if distro.efi_part != distro::EfiPartT::NoEFI {
            if helper::get_efi_part_fs(distro) == "xfs" {
                mount_option = Some("nouuid");
            } else {
                mount_option = None;
            }
            mount::mount_efi_on_rescue_efi(
                helper::get_efi_part_path(distro).as_str(),
                mount_option,
            );
        }
        mount_support_filesystem();
    }
}

pub(crate) fn redhat6_mount(distro: &distro::Distro) {
    mount::mount_root_on_rescue_root(distro.rescue_root.root_part_path.as_str(), None);
    // In case we have no boot part information like on a single CentOS distro we don't need to mount boot
    if distro.boot_part.boot_part_number != 0 {
        mount::mount_boot_on_rescue_boot(distro.boot_part.boot_part_path.as_str(), None);
    }
    mount_support_filesystem();
}

pub(crate) fn redhat6_umount(distro: &distro::Distro) {
    umount_support_filesystem();
    // In case we have no boot part information like on a single CentOS distro we don't need to umount boot
    if distro.boot_part.boot_part_number != 0 {
        mount::umount(constants::RESCUE_ROOT_BOOT);
    }
    mount::umount(constants::RESCUE_ROOT);
}

pub(crate) fn redhat_umount(distro: &distro::Distro) {
    if distro.is_lvm {
        umount_support_filesystem();
        mount::umount(constants::RESCUE_ROOT_BOOT_EFI);
        mount::umount(constants::RESCUE_ROOT_BOOT);
        mount::umount(constants::RESCUE_ROOT_USR);
        mount::umount(constants::RESCUE_ROOT_VAR);
        mount::umount(constants::RESCUE_ROOT);
    } else {
        umount_support_filesystem();
        if distro.efi_part != distro::EfiPartT::NoEFI {
            mount::umount(constants::RESCUE_ROOT_BOOT_EFI);
        }

        // if no boot partition is available we skip this step
        // this is required if we have the special condition that a RedHat RAW image does only have 3 partitons
        // root, EF00 and EF02
        if distro.boot_part.boot_part_number != 0 {
            mount::umount(constants::RESCUE_ROOT_BOOT);
        }
        mount::umount(constants::RESCUE_ROOT);
    }
}

fn mount_support_filesystem() {
    match mkdir_support_filesystems() {
        Ok(()) => {}
        Err(e) => {
            panic!("Support Filesystems are not able to be created. This is not recoverable : {e}")
        }
    }
    for fs in constants::SUPPORT_FILESYSTEMS.to_string().split(' ') {
        mount::bind_mount(
            format!("/{fs}/").as_str(),
            format!("{}{fs}", constants::RESCUE_ROOT).as_str(),
        );
    }
}
fn umount_support_filesystem() {
    for fs in constants::SUPPORT_FILESYSTEMS.to_string().rsplit(' ') {
        mount::umount(format!("{}{}", constants::RESCUE_ROOT, fs).as_str());
    }
}

fn mkdir_support_filesystems() -> io::Result<()> {
    for fs in constants::SUPPORT_FILESYSTEMS.to_string().split(' ') {
        fs::create_dir_all(format!("{}{}", constants::RESCUE_ROOT, fs))?;
    }
    Ok(())
}

pub(crate) fn distro_mount(distro: &distro::Distro) {
    match distro.kind {
        DistroKind::Debian | DistroKind::Ubuntu => ubuntu_mount(distro),
        DistroKind::Suse => suse_mount(distro),
        DistroKind::RedHatCentOS => redhat_mount(distro),
        DistroKind::RedHatCentOS6 => redhat6_mount(distro),
        DistroKind::Undefined => {} // Nothing to do here we have covered this condition already
    }
}

pub(crate) fn distro_umount(distro: &distro::Distro) {
    match distro.kind {
        DistroKind::Debian | DistroKind::Ubuntu => ubuntu_umount(distro),
        DistroKind::Suse => suse_umount(distro),
        DistroKind::RedHatCentOS => redhat_umount(distro),
        DistroKind::RedHatCentOS6 => redhat6_umount(distro),
        DistroKind::Undefined => {} // Nothing to do here we have covered this condition already
    }
}


#![allow(non_snake_case)]

use crate::ade;
use crate::constants;
use crate::distro;
use crate::helper;
use crate::mount;

use cmd_lib::{run_cmd, run_fun};

pub(crate) fn do_redhat_lvm_or(partition_info: Vec<String>, distro: &mut distro::Distro) {
    let mut contains_lvm_partition: bool = false;

    for partition in partition_info.iter() {
        if partition.contains("8E00") {
            contains_lvm_partition = true;
        }
    }

    if contains_lvm_partition {
        do_redhat_lvm(partition_info, distro);
    } else if partition_info.len() == 1 {
        do_centos_single_partition(distro);
    } else {
        do_redhat_nolvm(partition_info, distro);
    }
}

pub(crate) fn do_redhat6_or_7(partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    // this function does handle the condition if only two partitions are available
    if !distro.is_ade {
        if let Some(root_info) = partition_info.iter().find(|x| x.contains("GiB")) {
            distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(root_info);
            distro.rescue_root.root_part_number = helper::get_partition_number_detail(root_info);
            distro.rescue_root.root_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.rescue_root.root_part_number
            );
        }

        if let Some(boot_info) = partition_info.iter().find(|x| x.contains("MiB")) {
            distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(boot_info);
            distro.boot_part.boot_part_number = helper::get_partition_number_detail(boot_info);
            distro.boot_part.boot_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.boot_part.boot_part_number
            );
        }

        distro.boot_part.boot_part_path = format!(
            "{}{}",
            helper::read_link(constants::RESCUE_DISK),
            distro.boot_part.boot_part_number
        );

        helper::fsck_partition(
            distro.rescue_root.root_part_path.as_str(),
            distro.rescue_root.root_part_fs.as_str(),
        );

        helper::fsck_partition(
            distro.boot_part.boot_part_path.as_str(),
            distro.boot_part.boot_part_fs.as_str(),
        );

        verify_redhat_nolvm(distro);
    } else {
        // ADE part
        ade::do_redhat6_or_7_ade(partition_info, distro);
    }
}

fn do_redhat_nolvm(mut partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    if !distro.is_ade {
        // 4 partitions with no LVM we find on an 'CentOS Linux release 7.7.1908' for instance
        /*
        Number  Start   End     Size    File system  Name                  Flags
        14      1049kB  5243kB  4194kB                                     bios_grub
        15      5243kB  524MB   519MB   fat16        EFI System Partition  boot
         1      525MB   1050MB  524MB   xfs
         2      1050MB  32.2GB  31.2GB  xfs
         */

        helper::log_info(
            "This is a recent RedHat or CentOS image with 4 partitions and no LVM signature",
        );

        distro.is_lvm = false;
        if let Some(uefi) = partition_info.iter().by_ref().find(|x| x.contains("EF00")) {
            helper::set_efi_part_number_and_fs(distro, uefi);
            helper::set_efi_part_path(distro);
        }

        partition_info.retain(|x| !(x.contains("EF00") || x.contains("EF02")));
        //remove the UEFI the bios_boot partition. We have two partitions left

        // We need to determine what part is the root and what part is the boot one.
        if let Some(root_info) = partition_info.iter().find(|x| x.contains("GiB")) {
            distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(root_info);
            distro.rescue_root.root_part_number = helper::get_partition_number_detail(root_info);
            distro.rescue_root.root_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.rescue_root.root_part_number
            );
        }

        if let Some(boot_info) = partition_info.iter().find(|x| x.contains("MiB")) {
            distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(boot_info);
            distro.boot_part.boot_part_number = helper::get_partition_number_detail(boot_info);
            distro.boot_part.boot_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.boot_part.boot_part_number
            );
        }

        distro.boot_part.boot_part_path = format!(
            "{}{}",
            helper::read_link(constants::RESCUE_DISK),
            distro.boot_part.boot_part_number
        );

        helper::fsck_partition(
            distro.rescue_root.root_part_path.as_str(),
            distro.rescue_root.root_part_fs.as_str(),
        );

        helper::fsck_partition(
            distro.boot_part.boot_part_path.as_str(),
            distro.boot_part.boot_part_fs.as_str(),
        );

        helper::fsck_partition(
            helper::get_efi_part_path(distro).as_str(),
            helper::get_efi_part_fs(distro).as_str(),
        );
        verify_redhat_nolvm(distro);
    } else {
        // This an ADE enabled OS

        helper::log_info(
            "This is a recent RedHat or CentOS image with 4 partitions and no LVM signature",
        );
        helper::log_info("An ADE signature got identified");
        distro.is_lvm = false;
        ade::do_redhat_nolvm_ade(partition_info, distro);
    }
}

fn do_redhat_lvm(mut partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    helper::log_info("This is a recent RedHat or CentOS image with 4 partitions and LVM signature");
    distro.is_lvm = true;

    // At first we need to prepare the LVM setup
    match run_cmd!(pvscan -q -q; vgscan -q -q; lvscan -q -q;) {
        Ok(_) => {}
        Err(error) => panic!("There is a problem to setup LVM correct. {error}"),
    }

    if !distro.is_ade {
        if let Some(lvm_info) = partition_info.iter().find(|x| x.contains("8E00")) {
            distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(lvm_info);
            distro.rescue_root.root_part_number = helper::get_partition_number_detail(lvm_info);
            distro.rescue_root.root_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.rescue_root.root_part_number
            );
        }

        if let Some(uefi) = partition_info.iter().by_ref().find(|x| x.contains("EF00")) {
            helper::set_efi_part_number_and_fs(distro, uefi);
            helper::set_efi_part_path(distro);
        }

        partition_info
            .retain(|x| !(x.contains("EF00") || x.contains("EF02") || x.contains("8E00")));
        //remove the UEFI the bios_boot and the root partition to get the boot partition only

        distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(&partition_info[0]);
        distro.boot_part.boot_part_number = helper::get_partition_number_detail(&partition_info[0]);
        distro.boot_part.boot_part_path = format!(
            "{}{}",
            helper::read_link(constants::RESCUE_DISK),
            distro.boot_part.boot_part_number
        );

        distro.boot_part.boot_part_path = format!(
            "{}{}",
            helper::read_link(constants::RESCUE_DISK),
            distro.boot_part.boot_part_number
        );

        helper::fsck_partition(
            distro.rescue_root.root_part_path.as_str(),
            distro.rescue_root.root_part_fs.as_str(),
        );

        helper::fsck_partition(
            distro.boot_part.boot_part_path.as_str(),
            distro.boot_part.boot_part_fs.as_str(),
        );

        helper::fsck_partition(
            helper::get_efi_part_path(distro).as_str(),
            helper::get_efi_part_fs(distro).as_str(),
        );

        // Set the path details for later usage
        distro.lvm_details.lvm_root_part = lvm_path_helper("rootlv");
        distro.lvm_details.lvm_usr_part = lvm_path_helper("usrlv");
        distro.lvm_details.lvm_var_part = lvm_path_helper("varlv");

        helper::log_info(&format!("LVM Details '{:?}'", &distro.lvm_details));
        verify_redhat_lvm(distro);
    } else {
        // Ade part
        helper::log_info(
            "This is a recent RedHat or CentOS image with 4 partitions and LVM signature",
        );
        helper::log_info("An ADE signature got identified");

        ade::do_redhat_lvm_ade(partition_info, distro);
    }
}

// verify_redhat_nolvm does set the DistroKind to either RedHatCentOS or RedHatCentOS6
// if the verification is succesful
pub(crate) fn verify_redhat_nolvm(distro: &mut distro::Distro) {
    if let Err(e) = mount::mkdir_assert() {
        panic!(
            "Creating assert directory is not possible : {e}. ALAR is not able to proceed further"
        );
    }

    mount::mount_path_assert(distro.rescue_root.root_part_path.as_str());

    set_redhat_kind(distro);

    mount::umount(constants::ASSERT_PATH);
    if mount::rmdir(constants::ASSERT_PATH).is_err() {
        helper::log_debug("ASSERT_PATH can not be removed. This is a minor issue. ALAR is able to continue further");
    }
}

pub(crate) fn verify_redhat_lvm(distro: &mut distro::Distro) {
    if let Err(e) = mount::mkdir_assert() {
        panic!(
            "Creating assert directory is not possible : {e}. ALAR is not able to proceed further"
        );
    }
    mount::mount_path_assert(distro.lvm_details.lvm_root_part.as_str());

    set_redhat_kind(distro);

    mount::umount(constants::ASSERT_PATH);
}

fn set_redhat_kind(mut distro: &mut distro::Distro) {
    let pretty_name = helper::get_pretty_name(constants::OS_RELEASE);
    if pretty_name.contains("6.") {
        helper::log_info(format!("Pretty Name is : {}", &pretty_name).as_str());
        distro.kind = distro::DistroKind::RedHatCentOS6;
    } else {
        helper::log_info(format!("Pretty Name is : {}", &pretty_name).as_str());
        distro.kind = distro::DistroKind::RedHatCentOS;
    }
}

pub(crate) fn lvm_path_helper(lvname: &str) -> String {
    let mut lvpath: String = "".to_string();
    if let Ok(value) = run_fun!(lvscan | grep $lvname) {
        if let Some(path) = value.split('\'').nth(1) {
            lvpath = path.to_string();
        }
    }
    lvpath
}

pub(crate) fn do_centos_single_partition(mut distro: &mut distro::Distro) {
    // It is safe to use hardcoded values
    distro.rescue_root.root_part_path =
        format!("{}{}", helper::read_link(constants::RESCUE_DISK), 1);
    helper::fsck_partition(
        distro.rescue_root.root_part_path.as_str(),
        distro.rescue_root.root_part_fs.as_str(),
    );

    // We have a single partition only boot and efi partitions do not need to be set
    verify_redhat_nolvm(distro);
}


use crate::cli;
use crate::constants;
use crate::helper;
use std::{fs, io, process};

pub(crate) fn download_action_scripts(cli_info: &cli::CliInfo) -> io::Result<()> {
    // At first clean
    if let Err(err) = fs::remove_dir_all(constants::ACTION_IMPL_DIR) {
        println!(
            "Directory {} can not be removed : '{}'",
            constants::ACTION_IMPL_DIR,
            err
        );
    }

    if cli_info.action_directory.is_empty() {
        // First download the git archive
        // Process::Command used in order to ensure we finish the download process
        if let Ok(mut child) = process::Command::new("curl")
            .args([
                "-o",
                "/tmp/alar2.tar.gz",
                "-L",
               constants::TARBALL,
            ])
            .spawn()
        {
            child.wait().expect("Archive alar2.tar.gz not downloaded");
        } else {
            helper::log_error("Not able to downlod the action scripts due to a curl issue");
            process::exit(1);
        }

        // Expand the action_implementation directory
        cmd_lib::run_cmd!(tar --wildcards --strip-component=2 -xzf /tmp/alar2.tar.gz -C /tmp *action_implementation)?;

        Ok(())
    } else {
        // In case we have a local directory for our action scripts we need to copy the actions to
        // tmp/action_implementation
        if let Err(e) = load_local_action(cli_info.action_directory.as_str()) {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                format!("Load local action failed : '{e}'"),
            ));
        }
        Ok(())
    }
}

fn load_local_action(directory_source: &str) -> fs_extra::error::Result<u64> {
    let _ = fs::remove_dir_all(constants::ACTION_IMPL_DIR);
    let mut options = fs_extra::dir::CopyOptions::new();
    options.skip_exist = true;
    options.copy_inside = true;
    fs_extra::dir::copy(directory_source, constants::ACTION_IMPL_DIR, &options)
}


use crate::constants;
use crate::distro;
use crate::helper;

pub(crate) fn do_suse(mut partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    if !distro.is_ade {
        distro.kind = distro::DistroKind::Suse;
        if let Some(uefi) = partition_info.iter().by_ref().find(|x| x.contains("EF00")) {
            helper::set_efi_part_number_and_fs(distro, uefi);
            helper::set_efi_part_path(distro);
        }

        partition_info.retain(|x| !(x.contains("EF00") || x.contains("EF02")));
        //remove the UEFI the bios_boot partition. We have two partitions left

        // We need to determine what part is the root and what part is the boot one.
        if let Some(root_info) = partition_info.iter().find(|x| x.contains("GiB")) {
            distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(root_info);
            distro.rescue_root.root_part_number = helper::get_partition_number_detail(root_info);
            distro.rescue_root.root_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.rescue_root.root_part_number
            );
        }

        if let Some(boot_info) = partition_info.iter().find(|x| x.contains("MiB")) {
            distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(boot_info);
            distro.boot_part.boot_part_number = helper::get_partition_number_detail(boot_info);
            distro.boot_part.boot_part_path = format!(
                "{}{}",
                helper::read_link(constants::RESCUE_DISK),
                distro.boot_part.boot_part_number
            );
        }

        helper::fsck_partition(
            distro.rescue_root.root_part_path.as_str(),
            distro.rescue_root.root_part_fs.as_str(),
        );

        helper::fsck_partition(
            distro.boot_part.boot_part_path.as_str(),
            distro.boot_part.boot_part_fs.as_str(),
        );

        helper::fsck_partition(
            helper::get_efi_part_path(distro).as_str(),
            helper::get_efi_part_fs(distro).as_str(),
        );
    } else {
        // ADE part
        // Not yet available
        // See --> https://docs.microsoft.com/en-us/azure/virtual-machines/linux/disk-encryption-overview
    }
}


#![allow(non_snake_case)]

use crate::constants;
use crate::distro;
use crate::helper;
use crate::mount;

pub(crate) fn verify_ubuntu(mut distro: &mut distro::Distro) {
    if let Err(e) = mount::mkdir_assert() {
        panic!(
            "Creating assert directory is not possible: {e} ALAR is not able to proceed further"
        );
    }

    mount::mount_path_assert(distro.rescue_root.root_part_path.as_str());
    let pretty_name = helper::get_pretty_name(constants::OS_RELEASE);
    mount::umount(constants::ASSERT_PATH);

    if mount::rmdir(constants::ASSERT_PATH).is_err() {
        helper::log_debug("ASSERT_PATH can not be removed. This is a minor issue. ALAR is able to continue further");
    }

    if pretty_name.contains("Ubuntu") {
        distro.kind = distro::DistroKind::Ubuntu;
    }

    if pretty_name.contains("Debian") {
        distro.kind = distro::DistroKind::Debian;
    }
}

pub(crate) fn do_ubuntu(mut partition_info: Vec<String>, mut distro: &mut distro::Distro) {
    // Get the right partitions
    // Get the root partition info first
    let mut partition_info_copy = partition_info.to_owned(); // we need a copy for later usage
    partition_info.retain(|x| !(x.contains("EF00") || x.contains("EF02"))); //remove the UEFI and the bios_boot partition

    distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(&partition_info[0]);
    distro.rescue_root.root_part_number = helper::get_partition_number_detail(&partition_info[0]);
    distro.rescue_root.root_part_path = format!(
        "{}{}",
        helper::read_link(constants::RESCUE_DISK),
        distro.rescue_root.root_part_number
    );

    helper::log_info(&distro.rescue_root.root_part_path);

    helper::fsck_partition(
        distro.rescue_root.root_part_path.as_str(),
        distro.rescue_root.root_part_fs.as_str(),
    );

    // Get EFI partition
    partition_info_copy.retain(|x| x.contains("EF00")); //Get the UEFI partition
    helper::set_efi_part_number_and_fs(distro, &partition_info_copy[0]);
    helper::set_efi_part_path(distro);
    helper::fsck_partition(
        helper::get_efi_part_path(distro).as_str(),
        helper::get_efi_part_fs(distro).as_str(),
    );

    //verify_ubuntu(distro);
}


// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(feature = "dockerproto")]
    tonic_build::compile_protos("/app/proto/demo.proto")?;
    #[cfg(not(feature = "dockerproto"))]
    tonic_build::compile_protos("../../pb/demo.proto")?;
    Ok(())
}


// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

use opentelemetry::trace::TraceError;
use opentelemetry::{
    global,
    sdk::{
        propagation::TraceContextPropagator,
        resource::{
            OsResourceDetector, ProcessResourceDetector, ResourceDetector,
            EnvResourceDetector,
            SdkProvidedResourceDetector,
        },
        trace as sdktrace,
    },
};
use opentelemetry_otlp::{self, WithExportConfig};

use tonic::transport::Server;

use tracing_subscriber::Registry;
use tracing_subscriber::layer::SubscriberExt;

use log::*;
use simplelog::*;

use std::env;
use std::time::Duration;

mod shipping_service;
use shipping_service::shop::shipping_service_server::ShippingServiceServer;
use shipping_service::ShippingServer;

fn init_logger() -> Result<(), log::SetLoggerError> {
    CombinedLogger::init(vec![
        SimpleLogger::new(LevelFilter::Info, Config::default()),
        SimpleLogger::new(LevelFilter::Warn, Config::default()),
        SimpleLogger::new(LevelFilter::Error, Config::default()),
    ])
    // debug is used on lower level apis and not used here.
}

fn init_tracer() -> Result<sdktrace::Tracer, TraceError> {
    global::set_text_map_propagator(TraceContextPropagator::new());
    let os_resource = OsResourceDetector.detect(Duration::from_secs(0));
    let process_resource = ProcessResourceDetector.detect(Duration::from_secs(0));
    let sdk_resource = SdkProvidedResourceDetector.detect(Duration::from_secs(0));
    let env_resource = EnvResourceDetector::new().detect(Duration::from_secs(0));
    opentelemetry_otlp::new_pipeline()
        .tracing()
        .with_exporter(
            opentelemetry_otlp::new_exporter()
                .tonic()
                .with_endpoint(format!(
                    "{}{}",
                    env::var("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")
                        .unwrap_or_else(|_| "http://otelcol:4317".to_string()),
                    "/v1/traces"
                )), // TODO: assume this ^ is true from config when opentelemetry crate > v0.17.0
                    // https://github.com/open-telemetry/opentelemetry-rust/pull/806 includes the environment variable.
        )
        .with_trace_config(
            sdktrace::config()
                .with_resource(os_resource.merge(&process_resource).merge(&sdk_resource).merge(&env_resource)),
        )
        .install_batch(opentelemetry::runtime::Tokio)
}

fn init_reqwest_tracing(tracer: sdktrace::Tracer) -> Result<(), tracing::subscriber::SetGlobalDefaultError> {
    let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
    let subscriber = Registry::default().with(telemetry);
    tracing::subscriber::set_global_default(subscriber)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (mut health_reporter, health_service) = tonic_health::server::health_reporter();
    health_reporter
        .set_serving::<ShippingServiceServer<ShippingServer>>()
        .await;

    init_logger()?;
    init_reqwest_tracing(init_tracer()?)?;
    info!("OTel pipeline created");
    let port = env::var("SHIPPING_SERVICE_PORT").expect("$SHIPPING_SERVICE_PORT is not set");
    let addr = format!("0.0.0.0:{}", port).parse()?;
    info!("listening on {}", addr);
    let shipper = ShippingServer::default();

    Server::builder()
        .add_service(ShippingServiceServer::new(shipper))
        .add_service(health_service)
        .serve(addr)
        .await?;

    Ok(())
}