diff --git a/map.json b/map.json index 77743562..9e8f4285 100644 --- a/map.json +++ b/map.json @@ -39,6 +39,11 @@ "path" : "src/linux/linux-alar-fki.sh", "description" : "alar-fki allows to recover a failed VM. Three recover scenarios are possible: fstab, initrd and kernel. NOTE: use option --run-on-repair. Se the docu for more details: https://github.com/Azure/repair-script-library/blob/master/src/linux/common/helpers/alar/README.md" }, + { + "id" : "linux-alar2", + "path" : "src/linux/linux-alar2.sh", + "description" : "alar2 allows to recover a failed VM. Various ations are available like: fstab, initrd and kernel. NOTE: use option --run-on-repair. See the docu for more details: https://github.com/Azure/repair-script-library/blob/master/src/linux/common/helpers/alar2/README.md. The script takes around 2min to run." + }, { "id" : "win-enable-nested-hyperv", "path" : "src/windows/win-enable-nested-hyperv.ps1", diff --git a/src/linux/common/helpers/alar/alar-fki/kernel.sh b/src/linux/common/helpers/alar/alar-fki/kernel.sh index cf5d570c..459c8372 100644 --- a/src/linux/common/helpers/alar/alar-fki/kernel.sh +++ b/src/linux/common/helpers/alar/alar-fki/kernel.sh @@ -21,6 +21,10 @@ if [[ $isRedHat == "true" ]]; then else set_grub_default grubby --set-default=1 # This is the previous kernel + + if [[ $(grep -qe 'VERSION_ID=\"7.\?[1-9]\?\"' /etc/os-release) ]]; then + grub2-mkconfig -o /boot/grub2/grub.cfg + fi # Exception for RedHat 8.0 i.e sku RedHat:RHEL-HA:8.0:8.0.2020021914 # here we don't have to run the patch operation diff --git a/src/linux/common/helpers/alar2/Cargo.lock b/src/linux/common/helpers/alar2/Cargo.lock new file mode 100644 index 00000000..e2675b49 --- /dev/null +++ b/src/linux/common/helpers/alar2/Cargo.lock @@ -0,0 +1,425 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "alar2" +version = "0.9.0" +dependencies = [ + "chrono", + "clap", + "cmd_lib", + "copy_dir", + "fs_extra", + "sys-mount", + "uapi", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi 0.3.9", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cmd_lib" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42443b644d30f401746fb4483fba96b13076e1ef6cb5bca7d6a14d1d8f9f2bd9" +dependencies = [ + "cmd_lib_core", + "cmd_lib_macros", +] + +[[package]] +name = "cmd_lib_core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50c50a1536e882455aeaff22015146ea143b9106fc8e116669dd078ec7b7fc8" + +[[package]] +name = "cmd_lib_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d202b0cfc28d8928ba89138c0a8248cf921b9870c6c9d60c9951092df5b62b2" +dependencies = [ + "cmd_lib_core", + "proc-macro2", + "quote", +] + +[[package]] +name = "copy_dir" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4281031634644843bd2f5aa9c48cf98fc48d6b083bd90bb11becf10deaf8b0" +dependencies = [ + "walkdir", +] + +[[package]] +name = "errno" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "loopdev" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9e35cfb6646d67059f2ca8913a90e6c60633053c103df423975297f33d6fcc" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "sys-mount" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f5703caf67c45ad3450104001b4620a605e9def0cef13dde3c9add23f73cee" +dependencies = [ + "bitflags", + "libc", + "loopdev", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi 0.3.9", +] + +[[package]] +name = "uapi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63da6a15df1fe51c36d8de9813932bfc67a2a9d12fc48ff474c6129623c8d504" +dependencies = [ + "cc", + "cfg-if", + "libc", + "uapi-proc", +] + +[[package]] +name = "uapi-proc" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abed350c54016be348226f23db01ad43b1998fee70aca4fd10b7c79c731653" +dependencies = [ + "lazy_static", + "libc", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "walkdir" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66c0b9792f0a765345452775f3adbd28dde9d33f30d13e5dcc5ae17cf6f3780" +dependencies = [ + "kernel32-sys", + "winapi 0.2.8", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/src/linux/common/helpers/alar2/Cargo.toml b/src/linux/common/helpers/alar2/Cargo.toml new file mode 100644 index 00000000..e8c0dd3e --- /dev/null +++ b/src/linux/common/helpers/alar2/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "alar2" +version = "0.9.0" +authors = ["malachma "] +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4" +sys-mount = "1.2.1" +cmd_lib = "0.8.5" +fs_extra = "1.2.0" +clap = "2.33.3" +copy_dir = "0.1.2" +uapi = "0.2.1" \ No newline at end of file diff --git a/src/linux/common/helpers/alar2/README.md b/src/linux/common/helpers/alar2/README.md new file mode 100644 index 00000000..c1b11070 --- /dev/null +++ b/src/linux/common/helpers/alar2/README.md @@ -0,0 +1,52 @@ +# Azure Linux Auto Recover v2 + + +The Azure Linux Auto Recover v2 (ALAR2) tool is intended to fix boot issue for the most common issues. ALAR2 superceeds the previous version. ALAR2 is completely rewritten in Rust. It provides also a standalone mode to run the tool without the help of the 'vm repair extension'.o + + +The most common scenarios which are covered at the moment are: + +* malformed /etc/fstab + * syntax error + * missing disk +* damaged initrd or missing initrd line in the /boot/grub/grub.cfg +* last installed kernel is not bootable + +### FSTAB +This option does strip off any lines in the /etc/fstab file which are not needed to boot a system +It makes a copy of the original file first. So after the start of the OS the admin is able to edit the fstab again and correct any errors which didn’t allow a reboot of the system before + +### Kernel +This option does change the default kernel. +It modifies the configuration so that the previous kernel version gets booted. After the boot the admin is able to replace the broken kernel. + +### Initrd +This option corrects two issues that can happen when a new kernel gets installed + +1. The grub.cfg file is missing an “initrd” line or is missing the file to be use +2. The initrd image is missing +So it either fixes the grub.cfg file and/or creates a new initrd image + +### How can I recover my failed VM? +To use the ALAR2 tool with the help of the vm repair extension you have to utilize the command ‘run’ and its option ‘--run-id’ +The script-id for the automated recovery is: linux-alar2 + +#### Example #### + +az vm repair create --verbose -g centos7 -n cent7 --repair-username rescue --repair-password 'password!234’ + +az vm repair run --verbose -g centos7 -n cent7 --run-id linux-alar2 --parameters initrd --run-on-repair + +az vm repair restore --verbose -g centos7 -n cent7 + +You can pass over either a single recover-operation or multiple operations, i.e., fstab; ‘fstab,initrd’ +Separate the recover operation with a comma in this case – no spaces allowed! + +### Limitation +* Classic VMs are not supported + +### Distributions supported +* CentOS/Redhat 6.8 - 8.2 +* Ubuntu 16.4 LTS and Ubuntu 18.4 LTS +* Suse 12 and 15 +* Debain 9 and 10 \ No newline at end of file diff --git a/src/linux/common/helpers/alar2/src/action.rs b/src/linux/common/helpers/alar2/src/action.rs new file mode 100644 index 00000000..a3b96618 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/action.rs @@ -0,0 +1,78 @@ + +use crate::{constants, distro, helper}; +use distro::DistroKind; +use std::{env, fs, io, process}; +use std::io::Write; +use uapi; + +pub(crate) fn run_repair_script(distro: &distro::Distro, action_name: &str) -> io::Result<()> { + helper::log_info("----- Start action -----"); + + // At first make the script executable + uapi::chmod(format!("{}/{}-impl.sh", constants::ACTION_IMPL_DIR, action_name), uapi::c::S_IXUSR | uapi::c::S_IRUSR)?; + + //if let Err(e) = cmd_lib::run_fun!(chmod 700 /tmp/action_implementation/${action_name}-impl.sh) { + // helper::log_error(format!("Setting the execute permission bit failed! {}",e).as_str()); + //} + + match env::set_current_dir(constants::RESCUE_ROOT) { + Ok(_) => {} + Err(e) => println!("Error in set current dir : {}", e), + } + + // Set the environment correct + + match distro.kind { + DistroKind::Debian | DistroKind::Ubuntu => { + env::set_var("isUbuntu", "true"); + env::remove_var("isSuse"); + env::remove_var("isRedHat"); + env::remove_var("isRedHat6"); + + } + DistroKind::Suse => { + env::set_var("isSuse", "true"); + env::remove_var("isUbuntu"); + env::remove_var("isRedHat"); + env::remove_var("isRedHat6"); + } + DistroKind::RedHatCentOS => { + env::set_var("isRedHat", "true"); + env::remove_var("isUbuntu"); + env::remove_var("isSuse"); + env::remove_var("isRedHat6"); + } + DistroKind::RedHatCentOS6 => { + env::set_var("isRedHat", "true"); + env::set_var("isRedHat6", "true"); + env::remove_var("isUbuntu"); + env::remove_var("isSuse"); + } + DistroKind::Undefined => {} // Nothing to do + } + // Execute the action script + + 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 { + let dircontent = fs::read_dir(constants::ACTION_IMPL_DIR)?; + let mut actions = Vec::new(); + for item in dircontent { + if let Ok(entry) = item { + // We need to strip off the leading path details + actions.push(entry.path().file_name().unwrap().to_str().unwrap().to_string()); + } + } + Ok(actions.contains( &format!("{}-impl.sh",action_name) )) +} diff --git a/src/linux/common/helpers/alar2/src/action_implementation/fstab-impl.sh b/src/linux/common/helpers/alar2/src/action_implementation/fstab-impl.sh new file mode 100644 index 00000000..acab7c3e --- /dev/null +++ b/src/linux/common/helpers/alar2/src/action_implementation/fstab-impl.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +mv -f /etc/fstab{,.copy} + +# For Debian we need to instal gawk first. It comes only with mawk +if [[ -f /usr/bin/apt ]]; then + apt-get install -qq -y gawk +fi + +awk '/[[:space:]]+\/[[:space:]]+/ {print}' /etc/fstab.copy >>/etc/fstab +awk '/[[:space:]]+\/boot[[:space:]]+/ {print}' /etc/fstab.copy >>/etc/fstab +# For Suse +awk '/[[:space:]]+\/boot\/efi[[:space:]]+/ {print}' /etc/fstab.copy >>/etc/fstab +# In case we have a LVM system +awk '/rootvg-homelv/ {print}' /etc/fstab.copy >>/etc/fstab +awk '/rootvg-optlv/ {print}' /etc/fstab.copy >>/etc/fstab +awk '/rootvg-tmplv/ {print}' /etc/fstab.copy >>/etc/fstab +awk '/rootvg-usrlv/ {print}' /etc/fstab.copy >>/etc/fstab +awk '/rootvg-varlv/ {print}' /etc/fstab.copy >>/etc/fstab + +echo "Content of fstab after running the script -->" +cat /etc/fstab + +exit 0 \ No newline at end of file diff --git a/src/linux/common/helpers/alar2/src/action_implementation/grub.awk b/src/linux/common/helpers/alar2/src/action_implementation/grub.awk new file mode 100644 index 00000000..e693c948 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/action_implementation/grub.awk @@ -0,0 +1,75 @@ +#!/bin/awk + + +# +# this file is needed for RedHat 6.x distros. The grub.conf file is not regenerated by any tool +# compared to grub2. this is solved with the help of AWK and SED +# All modifications are only related to initrd. As this line is missing quite often after a kernel update +# + + +BEGIN { + i=0 + kernel_version="" +} +$0 ~ /kernel|initrd/ && $0 !~ /^#|.*#/{ + grub_directive[i]=$1 + directive_value[i]=$2 + indexLine[i]=NR + i++ + } + + +# +# Build a substring delimited by '-' but start at the second character +# +# +function substr_c2(_i) { + return substr(directive_value[_i],index(directive_value[_i],"-")+1) +} + + +# +# Find out whether the grub directive follow the right order or whether we have one missing (initrd) +# In that case we need to know the number of the lines in order to insert an initrd line +# +function directiveMissing() { + k=0 + for (j=0; j /boot/grub2/grub-cfg.patch < https://bugzilla.redhat.com/show_bug.cgi?id=1850193 + if [[ -L /boot/grub2/grubenv ]]; then + yum install -y patch + patch /boot/grub2/grub.cfg /boot/grub2/grub-cfg.patch + fi + + # These lines are required as we have the ld.so.cache not build correct + # Otherwise this can lead in no functional network afterwards + # TODO find a better solution and the root cause for it + mv /sbin/dhclient /sbin/dhclient.org +cat > /sbin/dhclient <> /etc/sysctl.conf +fi + +if [[ $isUbuntu == "true" ]]; then + set_grub_default + sed -i -e 's/GRUB_DEFAULT=.*/GRUB_DEFAULT="1>2"/' /etc/default/grub + update-grub +fi + +if [[ $isSuse == "true" ]]; then + set_grub_default + sed -i -e 's/GRUB_DEFAULT=.*/GRUB_DEFAULT="1>2"/' /etc/default/grub + grub2-mkconfig -o /boot/grub2/grub.cfg +fi + +# For reference --> https://www.linuxsecrets.com/2815-grub2-submenu-change-boot-order + +exit 0 \ No newline at end of file diff --git a/src/linux/common/helpers/alar2/src/action_implementation/test-impl.sh b/src/linux/common/helpers/alar2/src/action_implementation/test-impl.sh new file mode 100644 index 00000000..00668b13 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/action_implementation/test-impl.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# This is just a simple demo in order to print out the environment seen by the script +# The calling process is preparing the environment accordingly +printenv +pwd \ No newline at end of file diff --git a/src/linux/common/helpers/alar2/src/ade.rs b/src/linux/common/helpers/alar2/src/ade.rs new file mode 100644 index 00000000..fb46d3ed --- /dev/null +++ b/src/linux/common/helpers/alar2/src/ade.rs @@ -0,0 +1,289 @@ +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(partition_info: &Vec, mut distro: &mut distro::Distro) { +pub(crate) fn do_ubuntu_ade(partition_info: &[String], mut distro: &mut distro::Distro) { + helper::log_info("This is a recent Ubuntu 16.x/18.x with ADE enabled"); + + // Only get the EFI partition + // The root part we set manually later as we have a crypt filesystem on the usual root partition + for partition in partition_info { + if partition.contains("boot") { + helper::set_efi_part_number_and_fs(&mut distro, partition); + } + } + helper::set_efi_part_path(&mut distro); + + // Set the root_part_path manually + distro.rescue_root.root_part_path = constants::OSENCRYPT_PATH.to_string(); + + set_root_part_fs(&mut 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 = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + // 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, mut distro: &mut distro::Distro) { +pub(crate) fn do_redhat_nolvm_ade(partition_info: &[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(&mut 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(&mut distro, &partition); + } + } + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + helper::set_efi_part_path(&mut 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, mut distro: &mut distro::Distro) { +pub(crate) fn do_redhat6_or_7_ade(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(&mut 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(); + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + //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(partition_info: &[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 + */ + + distro.boot_part.boot_part_fs = "xfs".to_string(); + distro.boot_part.boot_part_number = 2; + + set_root_part_fs(&mut 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 + for partition in partition_info.iter() { + if partition.contains("EFI") { + helper::set_efi_part_number_and_fs(&mut distro, &partition); + } + } + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + helper::set_efi_part_path(&mut 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 + mount::umount(helper::get_ade_mounpoint(helper::get_efi_part_path(distro).as_str()).as_str()); + + // umount boot + mount::umount(helper::get_ade_mounpoint(distro.boot_part.boot_part_path.as_str()).as_str()); + + // umount osencrypt + if !distro.is_lvm { + // If it is LVM we have already unmounted the '/investigationroot' + mount::umount(helper::get_ade_mounpoint(distro.rescue_root.root_part_path.as_str()).as_str()); + } + +} + +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(); + } +} diff --git a/src/linux/common/helpers/alar2/src/cli.rs b/src/linux/common/helpers/alar2/src/cli.rs new file mode 100644 index 00000000..0be2d052 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/cli.rs @@ -0,0 +1,75 @@ +/* + +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) standalone: bool, + pub(crate) action_directory: String, + pub(crate) actions: String, +} + +impl CliInfo { + pub(crate) fn new() -> Self { + Self { standalone : false, 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("0.9") + .author("Marcus Lachmanez , malachma@microsoft.com") + .about(about) + .arg(Arg::with_name("standalone") + .short("s") + .long("standalone") + .help("Operates the tool in a standalone mode.") + .takes_value(false)) + .arg(Arg::with_name("directory") + .short("d") + .long("directory") + .takes_value(true) + .requires("standalone") // if directory is set + // it is mandatory to have standalone set as well + .help("The directory in which the actions are defined.\nRequires the standalone flag") + ) + .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(); + cli_info.standalone = matches.is_present("standalone"); + if cli_info.standalone && matches.is_present("directory") { + cli_info.action_directory = matches.value_of("directory").unwrap().to_string(); + } + + cli_info +} diff --git a/src/linux/common/helpers/alar2/src/constants.rs b/src/linux/common/helpers/alar2/src/constants.rs new file mode 100644 index 00000000..a251363a --- /dev/null +++ b/src/linux/common/helpers/alar2/src/constants.rs @@ -0,0 +1,15 @@ +pub(crate) const RESCUE_DISK: &str = r#"/dev/disk/azure/scsi1/lun0"#; +pub(crate) const RESCUE_ROOT: &str = r#"/mnt/rescue-root/"#; +pub(crate) const RESCUE_ROOT_RUN: &str = r#"/mnt/rescue-root/run"#; +pub(crate) const RESCUE_ROOT_BOOT: &str = r#"/mnt/rescue-root/boot"#; +pub(crate) const RESCUE_ROOT_BOOT_EFI: &str = r#"/mnt/rescue-root/boot/efi"#; +pub(crate) const RESCUE_ROOT_USR: &str = r#"/mnt/rescue-root/usr"#; +pub(crate) const RESCUE_ROOT_VAR: &str = r#"/mnt/rescue-root/var"#; +pub(crate) const SUPPORT_FILESYSTEMS: &str = r#"dev proc sys tmp dev/pts"#; +pub(crate) const LUN_PART_PATH: &str = r#"/dev/disk/azure/scsi1/lun0-part"#; +pub(crate) const ASSERT_PATH: &str = r#"/tmp/assert"#; +pub(crate) const REDHAT_RELEASE: &str = "/tmp/assert/etc/redhat-release"; +pub(crate) const OS_RELEASE: &str = r#"/tmp/assert/etc/os-release"#; +pub(crate) const PARTITION_TMP: &str = r#"/tmp/partition_file.tmp"#; +pub(crate) const OSENCRYPT_PATH: &str = r#"/dev/mapper/osencrypt"#; +pub(crate) const ACTION_IMPL_DIR: &str = r#"/tmp/action_implementation"#; diff --git a/src/linux/common/helpers/alar2/src/distro.rs b/src/linux/common/helpers/alar2/src/distro.rs new file mode 100644 index 00000000..ad60bf3f --- /dev/null +++ b/src/linux/common/helpers/alar2/src/distro.rs @@ -0,0 +1,294 @@ +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 cmd_lib; +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)] +pub enum DistroKind { + Debian, + Suse, + RedHatCentOS, + RedHatCentOS6, + Ubuntu, + 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)] +pub enum EfiPartT { + EfiPart(EfiPartition), + NoEFI, +} + +impl EfiPartT { + pub fn new() -> EfiPartT { + EfiPartT::EfiPart(EfiPartition::new()) + } +} + +impl Default for EfiPartT { + fn default() -> Self { + EfiPartT::NoEFI + } +} + +impl Default for DistroKind { + fn default() -> Self { + DistroKind::Undefined + } +} + +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 = 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: boot_part, + rescue_root: root_part, + efi_part: efi_part, + is_lvm: false, + is_ade: false, + lvm_details: lvm_details, + kind: 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) { + let link = read_link(constants::RESCUE_DISK); + let out = cmd_lib::run_fun!(parted -m ${link} print | grep -E "^ ?[0-9]{1,2} *"); + + match out { + Ok(v) => { + for line in v.lines() { + partitions.push(line.to_string()); + } + helper::log_info( + format!( + "We have the following partitions determined: {:?}", + partitions + ) + .as_str(), + ); + } + Err(e) => panic!("Fehler {:?}", e), + } +} + +// If there is only one partition detected +//fn do_old_ubuntu_or_centos(partition_info: &Vec, mut distro: &mut Distro) { +fn do_old_ubuntu_or_centos(partition_info: &[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 : '{}'. ALAR is not able to proceed further",e); + } + + 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 = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + 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, distro: &mut Distro) { +fn do_red_hat(partition_info: &[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 +//fn do_recent_ubuntu(partition_info: &Vec, distro: &mut Distro) { +fn do_recent_ubuntu(partition_info: &[String], distro: &mut Distro) { + helper::log_info("This could be a recent Ubuntu 16.x or 18.x image"); + ubuntu::do_ubuntu(partition_info, distro); +} + +// if we have 4 partition detected +fn do_suse_or_lvm_or_ubuntu(partition_info: &[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("'/investigationrooot' 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, mut distro: &mut Distro) { +fn dispatch(partition_info: &[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(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 and Ubuntu 18.4 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); + } + } +} diff --git a/src/linux/common/helpers/alar2/src/helper.rs b/src/linux/common/helpers/alar2/src/helper.rs new file mode 100644 index 00000000..f2d2f986 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/helper.rs @@ -0,0 +1,240 @@ +use crate::constants; +use crate::distro::{Distro, EfiPartT, EfiPartition}; +use crate::mount; +use chrono::prelude::Utc; +use std::process::Stdio; +use std::{fs, process}; +use cmd_lib::run_fun; + + +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 fn read_link(path: &str) -> String { + let real_path: String; + match fs::metadata(&path) { + Ok(_) => { + real_path = fs::canonicalize(&path) + .unwrap_or_default() + .as_os_str() + .to_str() + .unwrap_or_default() + .to_string(); + /* let os_option = fs::canonicalize(&path).ok().unwrap_or_default().as_os_str().to_str(); + if let Some(inner_real_path) = os_option { real_path = &inner_real_path.to_string(); } + */ + } + Err(_) => { + let message = format!("{} {}", "Path not found", &path); + log_error(&message); + panic!("Panic in function read_link"); + } + } + real_path +} + +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 fn get_partition_number_detail(source: &str) -> u8 { + cut(source, ":", 0).parse::().unwrap() +} + +pub fn get_partition_filesystem_detail(source: &str) -> String { + cut(source, ":", 4).to_string() +} + +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(); + } + pretty_name +} + +pub(crate) fn get_ade_mounpoint(source: &str) -> String { + let mut mountpoint = "".to_string(); + if let Ok(path) = cmd_lib::run_fun!(cat /proc/mounts | grep $source | cut -dr#" "# -f2) { + mountpoint = path; + } + log_info(format!("ADE mountpoint is: {}", &mountpoint).as_str()); + mountpoint +} + +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 = 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 : '{}'. ALAR is not able to proceed further",e); + } + + // 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(); + } + } + "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 stat 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 = + read_link(format!("{}{}", constants::LUN_PART_PATH, part_number).as_str()); + } +} + +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 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 +} diff --git a/src/linux/common/helpers/alar2/src/main.rs b/src/linux/common/helpers/alar2/src/main.rs new file mode 100644 index 00000000..21355f45 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/main.rs @@ -0,0 +1,82 @@ +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(); + + // At first we need to 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. 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 and Ubuntu 18.4 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); + } + + // 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, &cli_info); + + // 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 '{}' is not available", action_name).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); + } +} diff --git a/src/linux/common/helpers/alar2/src/mount.rs b/src/linux/common/helpers/alar2/src/mount.rs new file mode 100644 index 00000000..8e3118b1 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/mount.rs @@ -0,0 +1,114 @@ +use crate::helper; +use crate::constants; +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::new(source, destination, &supported, sys_mount::MountFlags::empty(), option) { + Ok(_) => { + helper::log_info(format!("mounted {} to {}", source, &destination).as_str() ); + } + Err(why) => { + helper::log_error(format!("failed to mount {} to {}: {}", source, 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) { + Ok(_) => { + //helper::log_info(format!("mounted {} to {}", source, &destination).as_str() ); + } + Err(why) => { + helper::log_error(format!("failed to mount {} to {}: {}", source, 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 sever issue for ALAR."); + } + } +} + +pub(crate) fn rmdir(path: &str) -> std::io::Result<()> { + fs::remove_dir_all(path)?; + Ok(()) +} + + + + diff --git a/src/linux/common/helpers/alar2/src/prepare_action.rs b/src/linux/common/helpers/alar2/src/prepare_action.rs new file mode 100644 index 00000000..bfb72dcf --- /dev/null +++ b/src/linux/common/helpers/alar2/src/prepare_action.rs @@ -0,0 +1,240 @@ +use crate::constants; +use crate::distro; +use crate::distro::DistroKind; +use crate::helper; +use crate::mount; +use crate::cli; +use crate::standalone; + +use fs_extra::dir; +use std::{env, fs, io, process}; +// use std::os::unix::fs::symlink as softlink; + +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; + } + + 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); + } + 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!("{}{}", constants::RESCUE_ROOT, fs).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, cli_info: &cli::CliInfo) { + 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 + } + // Also copy the recovery scripts to /tmp in order to make them available for the chroot + // operation we do later + copy_actions_totmp(distro, cli_info); +} + +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 + } +} + +fn copy_actions_totmp(distro: &distro::Distro, cli_info: &cli::CliInfo) { + // We need to copy the action scripts to /tmp + // This is the directory chroot can access + + if let Err(err) = fs::remove_dir_all(constants::ACTION_IMPL_DIR) { + println!("Directory {} can not be removed : '{}'", constants::ACTION_IMPL_DIR, err ); + //distro_umount(distro); + //process::exit(1); + } + if !cli_info.standalone { + let mut options = dir::CopyOptions::new(); //Initialize default values for CopyOptions + options.skip_exist = true; + + match env::current_dir() { + Ok(cd) => println!("The current dir is : {}", cd.display() ), + Err(e) => println!("Error : {}", e), + } + + // base directory already set correct by linux-alar2.sh + match dir::copy("src/action_implementation", "/tmp", &options) { + Ok(_) => {}, + Err(e) => { + println!("Copy operation for action_implementation directory failed. ALAR needs to stop: {}", e); + distro_umount(distro); + process::exit(1); + } + } + + +} else if let Err(e) = standalone::download_action_scripts(cli_info) { + distro_umount(distro); + panic!("action scripts are not able to be copied or downloadable : '{}'", e); +} + +} diff --git a/src/linux/common/helpers/alar2/src/redhat.rs b/src/linux/common/helpers/alar2/src/redhat.rs new file mode 100644 index 00000000..ec81555c --- /dev/null +++ b/src/linux/common/helpers/alar2/src/redhat.rs @@ -0,0 +1,334 @@ +#![allow(non_snake_case)] +use std::{fs, process}; + +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: &[String], distro: &mut distro::Distro) { + let mut contains_lvm_partition: bool = false; + + for partition in partition_info.iter() { + if partition.contains("lvm") { + 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: &[String], mut distro: &mut distro::Distro) { + if !distro.is_ade { + for partition in partition_info { + if helper::get_partition_number_detail(partition) == 1 { + distro.boot_part.boot_part_number = 1; + distro.boot_part.boot_part_fs = helper::get_partition_filesystem_detail(partition); + } + + if helper::get_partition_number_detail(partition) == 2 { + distro.rescue_root.root_part_number = 2; + distro.rescue_root.root_part_fs = + helper::get_partition_filesystem_detail(partition); + } + } + + distro.rescue_root.root_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.rescue_root.root_part_path.as_str(), + distro.rescue_root.root_part_fs.as_str(), + ); + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .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( distro); + } +} + +fn do_redhat_nolvm(partition_info: &[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; + + // 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; + + distro.rescue_root.root_part_fs = "xfs".to_string(); + distro.rescue_root.root_part_number = 2; + + //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(&mut distro, &partition); + } + } + + // In the next steps we have to set the partition path correct and do a fsck on them + + distro.rescue_root.root_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.rescue_root.root_part_path.as_str(), + distro.rescue_root.root_part_fs.as_str(), + ); + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.boot_part.boot_part_path.as_str(), + distro.boot_part.boot_part_fs.as_str(), + ); + + helper::set_efi_part_path(&mut distro); + + 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(partition_info: &[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 { + // TMP is required for the macro run_fun! or run_cmd! + let TMP = constants::PARTITION_TMP; + + for partition in partition_info { + let _ = run_cmd!(echo $partition > $TMP); + if let Ok(name) = run_fun!(grep -s -v EFI $TMP | grep -v lvm | grep -v bios) { + helper::log_debug(&name); + distro.boot_part.boot_part_fs = + helper::get_partition_filesystem_detail(&name.as_str()); + distro.boot_part.boot_part_number = + helper::get_partition_number_detail(&name.as_str()); + } + + if let Ok(name) = run_fun!(grep -s lvm $TMP) { + helper::log_debug(&name); + distro.rescue_root.root_part_fs = + helper::get_partition_filesystem_detail(&name.as_str()); + distro.rescue_root.root_part_number = + helper::get_partition_number_detail(&name.as_str()); + } + + if partition.contains("EFI") { + helper::log_debug(format!("UEFI partition is '{}'", &partition).as_str()); + helper::set_efi_part_number_and_fs(&mut distro, &partition); + } + } + + distro.rescue_root.root_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.rescue_root.root_part_path.as_str(), + distro.rescue_root.root_part_fs.as_str(), + ); + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.boot_part.boot_part_path.as_str(), + distro.boot_part.boot_part_fs.as_str(), + ); + + helper::set_efi_part_path(&mut distro); + + 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"); + + 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 : {}. ALAR is not able to proceed further", + e + ); + } + + 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) { + 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 mut pretty_name = helper::get_pretty_name(constants::OS_RELEASE); + if pretty_name.is_empty() { + // if len is 0 then it points to a RedHat or CentOS 6 distro + // let us read the correct file instead + match fs::read_to_string(constants::REDHAT_RELEASE) { + Ok(value) => pretty_name = value, + Err(_) => { + helper::log_error( "It is not possible to determine the OS kind. ALAR is not able to proceed further"); + process::exit(1); + } + } + if pretty_name.contains("CentOS") || pretty_name.contains("Red Hat") { + helper::log_info(format!("Pretty Name is : {}", &pretty_name).as_str()); + distro.kind = distro::DistroKind::RedHatCentOS6; + } + } else if pretty_name.contains("CentOS") || pretty_name.contains("Red Hat") { + 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 +} + +fn _lvm_get_filesystem(lvpath: &str) -> String { + let mut filesystem = "".to_string(); + if let Ok(value) = run_fun!(parted -m $lvpath print | grep -E "^ ?[0-9]{1,2} *") { + if let Some(path) = value.split(':').nth(4) { + filesystem = path.to_string(); + } + } + filesystem +} + +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 = + helper::read_link(format!("{}{}", constants::LUN_PART_PATH, 1).as_str()); + + 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); +} diff --git a/src/linux/common/helpers/alar2/src/standalone.rs b/src/linux/common/helpers/alar2/src/standalone.rs new file mode 100644 index 00000000..37e922de --- /dev/null +++ b/src/linux/common/helpers/alar2/src/standalone.rs @@ -0,0 +1,45 @@ +use cmd_lib; +use crate::helper; +use crate::cli; +use crate::constants; +use std::{io,process,fs}; +use fs_extra; + +pub(crate) fn download_action_scripts(cli_info: &cli::CliInfo) -> io::Result<()> { + 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","https://api.github.com/repos/Azure/repair-script-library/tarball/master"]).spawn() { + child.wait().expect("Archive alar2.tar.gz not downloaded"); + } else { + helper::log_error("Command curl not executed"); + process::exit(1); + } + + // Expand the action_implementation directory + cmd_lib::run_cmd!(tar --wildcards --strip-component=7 -xzf /tmp/alar2.tar.gz -C /tmp *src/linux/common/helpers/alar2/src/action_implementation)?; + + // Get two further files + //cmd_lib::run_cmd!(tar --wildcards --strip-component=1 -xzf /tmp/alar2.tar.gz -C /tmp *src/linux/common/helpers/Logger.sh)?; + //cmd_lib::run_cmd!(tar --wildcards --strip-component=1 -xzf /tmp/alar2.tar.gz -C /tmp *src/linux/common/setup/init.sh)?; + 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 { + 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; + match fs_extra::dir::copy(directory_source, constants::ACTION_IMPL_DIR, &options) { + Ok(v) => Ok(v), + Err(e) => Err(e), + } +} diff --git a/src/linux/common/helpers/alar2/src/suse.rs b/src/linux/common/helpers/alar2/src/suse.rs new file mode 100644 index 00000000..01d32ecb --- /dev/null +++ b/src/linux/common/helpers/alar2/src/suse.rs @@ -0,0 +1,68 @@ +use crate::constants; +use crate::distro; +use crate::helper; + +pub(crate) fn do_suse(partition_info: &[String], mut distro: &mut distro::Distro) { + if !distro.is_ade { + distro.kind = distro::DistroKind::Suse; + + for partition in partition_info { + if partition.contains("lxboot") { + distro.boot_part.boot_part_fs = + helper::get_partition_filesystem_detail(partition.as_str()); + distro.boot_part.boot_part_number = + helper::get_partition_number_detail(partition.as_str()); + } + + if partition.contains("lxroot") { + distro.rescue_root.root_part_fs = + helper::get_partition_filesystem_detail(partition.as_str()); + distro.rescue_root.root_part_number = + helper::get_partition_number_detail(partition.as_str()); + } + + if partition.contains("UEFI") { + helper::set_efi_part_number_and_fs(&mut distro, &partition); + } + } + + distro.rescue_root.root_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.rescue_root.root_part_path.as_str(), + distro.rescue_root.root_part_fs.as_str(), + ); + + distro.boot_part.boot_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.boot_part.boot_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.boot_part.boot_part_path.as_str(), + distro.boot_part.boot_part_fs.as_str(), + ); + + helper::set_efi_part_path(&mut distro); + + 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 + } +} diff --git a/src/linux/common/helpers/alar2/src/ubuntu.rs b/src/linux/common/helpers/alar2/src/ubuntu.rs new file mode 100644 index 00000000..47f6fb94 --- /dev/null +++ b/src/linux/common/helpers/alar2/src/ubuntu.rs @@ -0,0 +1,96 @@ +#![allow(non_snake_case)] + +use crate::constants; +use crate::distro; +use crate::helper; +use crate::mount; +use cmd_lib::{run_cmd, run_fun}; + +pub(crate) fn verify_ubuntu(mut distro: &mut distro::Distro) { + + if let Err(e) = mount::mkdir_assert() { + panic!("Creating assert directory is not possible: {} ALAR is not able to proceed further", e); + } + + 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"); + } + + println!("pretty : {}", &pretty_name); + 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(partition_info: &[String], mut distro: &mut distro::Distro) { + for partition in partition_info { + // TMP is required for the macro run_fun! or run_cmd! + let _TMP = constants::PARTITION_TMP; + + // Write the partition details into the file referenced in _TMP + let _ = run_cmd!(echo $partition > $_TMP); + if let Ok(name) = run_fun!(grep -s -v boot $_TMP | grep -v bios) { + println!("{}", name); + distro.rescue_root.root_part_fs = helper::get_partition_filesystem_detail(&name.as_str()); + distro.rescue_root.root_part_number = helper::get_partition_number_detail(&name.as_str()); + } + + if partition.contains("boot") { + helper::set_efi_part_number_and_fs(&mut distro, partition); + } + } + + distro.rescue_root.root_part_path = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + + + helper::fsck_partition( + distro.rescue_root.root_part_path.as_str(), + distro.rescue_root.root_part_fs.as_str(), + ); + + helper::set_efi_part_path(&mut distro); + helper::fsck_partition( + helper::get_efi_part_path(&distro).as_str(), + helper::get_efi_part_fs(&distro).as_str(), + ); + + // If we have ADE enabled on the disk to be recovered then there is a 4th partition to hold the boot + // part information + if distro.is_ade { + // 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 = helper::read_link( + format!( + "{}{}", + constants::LUN_PART_PATH, + distro.rescue_root.root_part_number + ) + .as_str(), + ); + + helper::fsck_partition( + distro.boot_part.boot_part_path.as_str(), + distro.boot_part.boot_part_fs.as_str(), + ); + } + + verify_ubuntu(distro); +} diff --git a/src/linux/common/helpers/alar2/target/.rustc_info.json b/src/linux/common/helpers/alar2/target/.rustc_info.json new file mode 100644 index 00000000..da33d0c5 --- /dev/null +++ b/src/linux/common/helpers/alar2/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":17359379515387336335,"outputs":{"17598535894874457435":{"success":true,"status":"","code":0,"stdout":"rustc 1.54.0-nightly (e4a603270 2021-06-07)\nbinary: rustc\ncommit-hash: e4a60327063e82413eed50a10df3b7d19b77bda0\ncommit-date: 2021-06-07\nhost: x86_64-unknown-linux-gnu\nrelease: 1.54.0-nightly\nLLVM version: 12.0.1\n","stderr":""},"2797684049618456168":{"success":false,"status":"exit status: 1","code":1,"stdout":"","stderr":"error: `-Csplit-debuginfo` is unstable on this platform\n\n"},"931469667778813386":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/src/linux/linux-alar2.sh b/src/linux/linux-alar2.sh new file mode 100644 index 00000000..b839b3b6 --- /dev/null +++ b/src/linux/linux-alar2.sh @@ -0,0 +1,14 @@ +#!/bin/bash +. ./src/linux/common/setup/init.sh + +Log-Output "Starting the recovery" +cd ./src/linux/common/helpers/alar2 +/root/.cargo/bin/cargo build -q --release +mkdir bin +cp target/release/alar2 bin/ +chmod 700 ./bin/alar2 +./bin/alar2 $1 +# Save the error state from alar2 +error_state=$? +Log-Output "Recovery script finished" +exit $error_state