github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ios/dutils_linux.go (about)

     1  // Package ios is a collection of interfaces to the local storage subsystem;
     2  // the package includes OS-dependent implementations for those interfaces.
     3  /*
     4   * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
     5   */
     6  package ios
     7  
     8  import (
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"os/exec"
    13  	"strings"
    14  
    15  	"github.com/NVIDIA/aistore/cmn"
    16  	"github.com/NVIDIA/aistore/cmn/cos"
    17  	"github.com/NVIDIA/aistore/cmn/nlog"
    18  	jsoniter "github.com/json-iterator/go"
    19  )
    20  
    21  // parse `lsblk -Jt` to associate filesystem (`fs`) with its disks;
    22  // devPrefix* constants are the (two) distinct prefixes expected in `lsblk` output
    23  const (
    24  	devPrefixReg = "/dev/"
    25  	devPrefixLVM = "/dev/mapper/"
    26  )
    27  
    28  type (
    29  	LsBlk struct {
    30  		BlockDevices []*blkdev `json:"blockdevices"`
    31  	}
    32  
    33  	// `lsblk -Jt` structure
    34  	blkdev struct {
    35  		Name         string          `json:"name"`
    36  		PhySec       jsoniter.Number `json:"phy-sec"`
    37  		BlockDevices []*blkdev       `json:"children"`
    38  	}
    39  )
    40  
    41  func lsblk(fs string, testingEnv bool) (res *LsBlk) {
    42  	// skip docker union mounts
    43  	if fs == "overlay" {
    44  		return
    45  	}
    46  	var (
    47  		cmd      = exec.Command("lsblk", "-Jt") // JSON output format (TODO: '-e7' to skip loopbacks)
    48  		out, err = cmd.CombinedOutput()
    49  	)
    50  	if err != nil {
    51  		switch {
    52  		case len(out) == 0:
    53  		case strings.Contains(err.Error(), "exit status"):
    54  			err = errors.New(string(out))
    55  		default:
    56  			err = fmt.Errorf("%s: %v", string(out), err)
    57  		}
    58  		if !testingEnv {
    59  			cos.ExitLog(err) // FATAL
    60  		}
    61  		nlog.Errorln(err)
    62  		return
    63  	}
    64  	if len(out) == 0 {
    65  		nlog.Errorf("%s: no disks (empty lsblk output)", fs)
    66  		return
    67  	}
    68  
    69  	// unmarshal
    70  	res = &LsBlk{}
    71  	if err = jsoniter.Unmarshal(out, res); err != nil {
    72  		err = fmt.Errorf("failed to unmarshal lsblk output: %v", err)
    73  		if !testingEnv {
    74  			cos.ExitLog(err) // FATAL
    75  		}
    76  		nlog.Errorln(err)
    77  		res = nil
    78  	}
    79  	return
    80  }
    81  
    82  // given parsed lsblk and `fs` (filesystem) fs2disks retrieves the underlying
    83  // disk or disks; it may return multiple disks but only if the filesystem is
    84  // RAID; it is called upong adding/enabling mountpath
    85  func fs2disks(res *LsBlk, fs string, label Label, num int, testingEnv bool) (disks FsDisks, err error) {
    86  	var trimmedFS string
    87  	if strings.HasPrefix(fs, devPrefixLVM) {
    88  		trimmedFS = strings.TrimPrefix(fs, devPrefixLVM)
    89  	} else {
    90  		trimmedFS = strings.TrimPrefix(fs, devPrefixReg)
    91  	}
    92  	disks = make(FsDisks, num)
    93  	findDevs(res.BlockDevices, trimmedFS, label, disks) // map trimmed(fs) <= disk(s)
    94  
    95  	if !flag.Parsed() {
    96  		return disks, nil // unit tests
    97  	}
    98  
    99  	switch {
   100  	case len(disks) > 0:
   101  		s := disks._str()
   102  		nlog.Infoln("["+fs+label.ToLog()+"]:", s)
   103  	case testingEnv || cmn.AllowSharedDisksAndNoDisks:
   104  		// anything goes
   105  	case label.IsNil():
   106  		err = fmt.Errorf("No disks for %s(%q) (empty label implies _resolvable_ underlying disk(s))", fs, trimmedFS)
   107  		nlog.Errorln(err)
   108  		dump, _ := jsoniter.MarshalIndent(res.BlockDevices, "", " ")
   109  		nlog.Infoln("Begin lsblk output ================================")
   110  		nlog.Infoln(string(dump))
   111  		nlog.Infoln("End lsblk output ==================================")
   112  	default:
   113  		nlog.Infof("No disks for %s(%q, disk label: %s)", fs, trimmedFS, label)
   114  	}
   115  	return disks, err
   116  }
   117  
   118  //
   119  // private
   120  //
   121  
   122  func findDevs(devList []*blkdev, trimmedFS string, label Label, disks FsDisks) {
   123  	for _, bd := range devList {
   124  		// by dev name
   125  		if bd.Name == trimmedFS {
   126  			_add(bd, disks)
   127  			continue
   128  		}
   129  		// NOTE: by label
   130  		if label != "" && strings.Contains(bd.Name, string(label)) {
   131  			_add(bd, disks)
   132  			continue
   133  		}
   134  		if len(bd.BlockDevices) > 0 && _match(bd.BlockDevices, trimmedFS) {
   135  			_add(bd, disks)
   136  		}
   137  	}
   138  }
   139  
   140  func _add(bd *blkdev, disks FsDisks) {
   141  	var err error
   142  	if disks[bd.Name], err = bd.PhySec.Int64(); err != nil {
   143  		nlog.Errorf("%s[%v]: failed to parse sector: %v", bd.Name, bd, err)
   144  		disks[bd.Name] = 512
   145  	}
   146  }
   147  
   148  func _match(devList []*blkdev, device string) bool {
   149  	for _, dev := range devList {
   150  		if dev.Name == device {
   151  			return true
   152  		}
   153  		// recurs
   154  		if len(dev.BlockDevices) != 0 && _match(dev.BlockDevices, device) {
   155  			return true
   156  		}
   157  	}
   158  	return false
   159  }