|
@@ -1,12 +1,16 @@
|
|
|
package raid
|
|
|
|
|
|
import (
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
"log"
|
|
|
"os"
|
|
|
"os/exec"
|
|
|
+ "sort"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
+
|
|
|
+ "imuslab.com/arozos/mod/utils"
|
|
|
)
|
|
|
|
|
|
/*
|
|
@@ -15,6 +19,7 @@ import (
|
|
|
This script handles the interaction with mdadm
|
|
|
*/
|
|
|
|
|
|
+// Storage Device meta was generated by lsblk
|
|
|
type StorageDeviceMeta struct {
|
|
|
Name string
|
|
|
Size int64
|
|
@@ -23,6 +28,19 @@ type StorageDeviceMeta struct {
|
|
|
MountPoint string
|
|
|
}
|
|
|
|
|
|
+// RAIDDevice represents information about a RAID device.
|
|
|
+type RAIDMember struct {
|
|
|
+ Name string
|
|
|
+ Seq int
|
|
|
+}
|
|
|
+
|
|
|
+type RAIDDevice struct {
|
|
|
+ Name string
|
|
|
+ Status string
|
|
|
+ Level string
|
|
|
+ Members []*RAIDMember
|
|
|
+}
|
|
|
+
|
|
|
// List all the storage device in the system, set minSize to 0 for no filter
|
|
|
func (m *Manager) ListAllStorageDevices(minSize int64) ([]*StorageDeviceMeta, error) {
|
|
|
cmd := exec.Command("sudo", "lsblk", "-b")
|
|
@@ -96,6 +114,11 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
|
|
|
raidDev := len(raidDeviceIds)
|
|
|
spareDevice := len(spareDeviceIds)
|
|
|
|
|
|
+ //Validate if raid level
|
|
|
+ if !IsValidRAIDLevel("raid" + strconv.Itoa(raidLevel)) {
|
|
|
+ return fmt.Errorf("invalid or unsupported raid level given: raid" + strconv.Itoa(raidLevel))
|
|
|
+ }
|
|
|
+
|
|
|
//Validate the number of disk is enough for the raid
|
|
|
if raidLevel == 0 && raidDev < 2 {
|
|
|
return fmt.Errorf("not enough disks for raid0")
|
|
@@ -107,6 +130,16 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
|
|
|
return fmt.Errorf("not enough disk for raid6")
|
|
|
}
|
|
|
|
|
|
+ //Append /dev to the name if missing
|
|
|
+ if !strings.HasPrefix(devName, "/dev/") {
|
|
|
+ devName = "/dev/" + devName
|
|
|
+ }
|
|
|
+
|
|
|
+ if utils.FileExists(devName) {
|
|
|
+ //RAID device already exists
|
|
|
+ return errors.New(devName + " already been used")
|
|
|
+ }
|
|
|
+
|
|
|
// Concatenate RAID and spare device arrays
|
|
|
allDeviceIds := append(raidDeviceIds, spareDeviceIds...)
|
|
|
|
|
@@ -125,86 +158,102 @@ func (m *Manager) CreateRAIDDevice(devName string, raidName string, raidLevel in
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// Updates the mdadm configuration file with the details of RAID arrays
|
|
|
-// so the RAID drive will still be seen after a reboot (hopefully)
|
|
|
-// this function will not remove any record from the config file. If you need to remove
|
|
|
-// a RAID volume from config, call to RemoveVolmeFromMDADMConfig()
|
|
|
-func UpdateMDADMConfig() error {
|
|
|
- cmdMdadm := exec.Command("sudo", "mdadm", "--detail", "--scan", "--verbose")
|
|
|
+// GetRAIDDevicesFromProcMDStat retrieves information about RAID devices from /proc/mdstat.
|
|
|
+// if your RAID array is in auto-read-only mode, it is (usually) brand new
|
|
|
+func (m *Manager) GetRAIDDevicesFromProcMDStat() ([]RAIDDevice, error) {
|
|
|
+ // Execute the cat command to read /proc/mdstat
|
|
|
+ cmd := exec.Command("cat", "/proc/mdstat")
|
|
|
|
|
|
// Run the command and capture its output
|
|
|
- output, err := cmdMdadm.Output()
|
|
|
+ output, err := cmd.Output()
|
|
|
if err != nil {
|
|
|
- return fmt.Errorf("error running mdadm command: %v", err)
|
|
|
+ return nil, fmt.Errorf("error running cat command: %v", err)
|
|
|
}
|
|
|
|
|
|
- //Load the config from system
|
|
|
- currentConfigBytes, err := os.ReadFile("/etc/mdadm/mdadm.conf")
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("unable to open mdadm.conf: " + err.Error())
|
|
|
- }
|
|
|
- currentConf := string(currentConfigBytes)
|
|
|
-
|
|
|
- //Check if the current config already contains the setting
|
|
|
- newConfigLines := []string{}
|
|
|
- arrayConfigs := strings.TrimSpace(string(output))
|
|
|
- lines := strings.Split(arrayConfigs, "ARRAY")
|
|
|
- for _, line := range lines {
|
|
|
- //For each line, you should have something like this
|
|
|
- //ARRAY /dev/md0 metadata=1.2 name=debian:0 UUID=cbc11a2b:fbd42653:99c1340b:9c4962fb
|
|
|
- // devices=/dev/sdb,/dev/sdc
|
|
|
-
|
|
|
- line = strings.ReplaceAll(line, "\n", " ")
|
|
|
- fields := strings.Fields(line)
|
|
|
- if len(fields) < 5 {
|
|
|
+ // Convert the output to a string and split it into lines
|
|
|
+ lines := strings.Split(string(output), "\n")
|
|
|
+
|
|
|
+ // Initialize an empty slice to store RAID devices
|
|
|
+ raidDevices := make([]RAIDDevice, 0)
|
|
|
+
|
|
|
+ // Iterate over the lines, skipping the first line (Personalities)
|
|
|
+ // Lines usually looks like this
|
|
|
+ // md0 : active raid1 sdc[1] sdb[0]
|
|
|
+ for _, line := range lines[1:] {
|
|
|
+ // Skip empty lines
|
|
|
+ if line == "" {
|
|
|
continue
|
|
|
}
|
|
|
- poolUUID := strings.TrimPrefix(fields[3], "UUID=")
|
|
|
|
|
|
- //Check if this uuid already in the config file
|
|
|
- if strings.Contains(currentConf, poolUUID) {
|
|
|
+ // Split the line by colon (:)
|
|
|
+ parts := strings.SplitN(line, " : ", 2)
|
|
|
+ if len(parts) != 2 {
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
- //This config not exists in the settings. Add it to append lines
|
|
|
- settingLine := "ARRAY " + strings.Join(fields, " ")
|
|
|
- newConfigLines = append(newConfigLines, settingLine)
|
|
|
-
|
|
|
- }
|
|
|
+ // Extract device name and status
|
|
|
+ deviceName := parts[0]
|
|
|
|
|
|
- if len(newConfigLines) == 0 {
|
|
|
- //Nothing to write
|
|
|
- log.Println("Nothing to write. Skipping mdadm config update.")
|
|
|
- return nil
|
|
|
- }
|
|
|
+ // Split the members string by space to get individual member devices
|
|
|
+ info := strings.Fields(parts[1])
|
|
|
+ if len(info) < 4 {
|
|
|
+ //Malform output
|
|
|
+ continue
|
|
|
+ }
|
|
|
|
|
|
- // Construct the bash command to append the line to mdadm.conf using echo and tee
|
|
|
- for _, configLine := range newConfigLines {
|
|
|
- cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "%s" | sudo tee -a /etc/mdadm/mdadm.conf`, configLine))
|
|
|
+ deviceStatus := info[0]
|
|
|
|
|
|
- // Run the command
|
|
|
- err := cmd.Run()
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("error injecting line into mdadm.conf: %v", err)
|
|
|
+ //Raid level usually appears at position 1 - 2, check both
|
|
|
+ raidLevel := ""
|
|
|
+ if strings.HasPrefix(info[1], "raid") {
|
|
|
+ raidLevel = info[1]
|
|
|
+ } else if strings.HasPrefix(info[2], "raid") {
|
|
|
+ raidLevel = info[2]
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// Removes a RAID volume from the mdadm configuration file given its volume UUID.
|
|
|
-// Note that this only remove a single line of config. If your line consists of multiple lines
|
|
|
-// you might need to remove it manually
|
|
|
-func RemoveVolumeFromMDADMConfig(volumeUUID string) error {
|
|
|
- // Construct the sed command to remove the line containing the volume UUID from mdadm.conf
|
|
|
- sedCommand := fmt.Sprintf(`sudo sed -i '/UUID=%s/d' /etc/mdadm/mdadm.conf`, volumeUUID)
|
|
|
+ //Get the members (disks) of the array
|
|
|
+ members := []*RAIDMember{}
|
|
|
+ for _, disk := range info[2:] {
|
|
|
+ if !strings.HasPrefix(disk, "sd") {
|
|
|
+ //Probably not a storage device
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ //In sda[0] format, we need to split out the number from the disk seq
|
|
|
+ tmp := strings.Split(disk, "[")
|
|
|
+ if len(tmp) != 2 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ //Convert the sequence to id
|
|
|
+ seqInt, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(tmp[1]), "]"))
|
|
|
+ if err != nil {
|
|
|
+ //Not an integer?
|
|
|
+ log.Println("[RAID] Unable to parse " + disk + " sequence ID")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ member := RAIDMember{
|
|
|
+ Name: strings.TrimSpace(tmp[0]),
|
|
|
+ Seq: seqInt,
|
|
|
+ }
|
|
|
+
|
|
|
+ members = append(members, &member)
|
|
|
+ }
|
|
|
|
|
|
- // Execute the sed command
|
|
|
- cmd := exec.Command("bash", "-c", sedCommand)
|
|
|
- err := cmd.Run()
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("error removing volume from mdadm.conf: %v", err)
|
|
|
+ //Sort the member disks
|
|
|
+ sort.Slice(members[:], func(i, j int) bool {
|
|
|
+ return members[i].Seq < members[j].Seq
|
|
|
+ })
|
|
|
+
|
|
|
+ // Create a RAIDDevice struct and append it to the slice
|
|
|
+ raidDevice := RAIDDevice{
|
|
|
+ Name: deviceName,
|
|
|
+ Status: deviceStatus,
|
|
|
+ Level: raidLevel,
|
|
|
+ Members: members,
|
|
|
+ }
|
|
|
+ raidDevices = append(raidDevices, raidDevice)
|
|
|
}
|
|
|
|
|
|
- return nil
|
|
|
+ return raidDevices, nil
|
|
|
}
|