github.com/jaypipes/ghw@v0.21.1/pkg/snapshot/clonetree_block_linux.go (about)

     1  //
     2  // Use and distribution licensed under the Apache license version 2.
     3  //
     4  // See the COPYING file in the root project directory for full text.
     5  //
     6  
     7  package snapshot
     8  
     9  import (
    10  	"errors"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  )
    15  
    16  func createBlockDevices(buildDir string) error {
    17  	// Grab all the block device pseudo-directories from /sys/block symlinks
    18  	// (excluding loopback devices) and inject them into our build filesystem
    19  	// with all but the circular symlink'd subsystem directories
    20  	devLinks, err := os.ReadDir("/sys/block")
    21  	if err != nil {
    22  		return err
    23  	}
    24  	for _, devLink := range devLinks {
    25  		dname := devLink.Name()
    26  		if strings.HasPrefix(dname, "loop") {
    27  			continue
    28  		}
    29  		devPath := filepath.Join("/sys/block", dname)
    30  		trace("processing block device %q\n", devPath)
    31  
    32  		// from the sysfs layout, we know this is always a symlink
    33  		linkContentPath, err := os.Readlink(devPath)
    34  		if err != nil {
    35  			return err
    36  		}
    37  		trace("link target for block device %q is %q\n", devPath, linkContentPath)
    38  
    39  		// Create a symlink in our build filesystem that is a directory
    40  		// pointing to the actual device bus path where the block device's
    41  		// information directory resides
    42  		linkPath := filepath.Join(buildDir, "sys/block", dname)
    43  		linkTargetPath := filepath.Join(
    44  			buildDir,
    45  			"sys/block",
    46  			strings.TrimPrefix(linkContentPath, string(os.PathSeparator)),
    47  		)
    48  		trace("creating device directory %s\n", linkTargetPath)
    49  		if err = os.MkdirAll(linkTargetPath, os.ModePerm); err != nil {
    50  			return err
    51  		}
    52  
    53  		trace("linking device directory %s to %s\n", linkPath, linkContentPath)
    54  		// Make sure the link target is a relative path!
    55  		// if we use absolute path, the link target will be an absolute path starting
    56  		// with buildDir, hence the snapshot will contain broken link.
    57  		// Otherwise, the unpack directory will never have the same prefix of buildDir!
    58  		if err = os.Symlink(linkContentPath, linkPath); err != nil {
    59  			return err
    60  		}
    61  		// Now read the source block device directory and populate the
    62  		// newly-created target link in the build directory with the
    63  		// appropriate block device pseudofiles
    64  		srcDeviceDir := filepath.Join(
    65  			"/sys/block",
    66  			strings.TrimPrefix(linkContentPath, string(os.PathSeparator)),
    67  		)
    68  		trace("creating device directory %q from %q\n", linkTargetPath, srcDeviceDir)
    69  		if err = createBlockDeviceDir(linkTargetPath, srcDeviceDir); err != nil {
    70  			return err
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  func createBlockDeviceDir(buildDeviceDir string, srcDeviceDir string) error {
    77  	// Populate the supplied directory (in our build filesystem) with all the
    78  	// appropriate information pseudofile contents for the block device.
    79  	devName := filepath.Base(srcDeviceDir)
    80  	devFiles, err := os.ReadDir(srcDeviceDir)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	for _, f := range devFiles {
    85  		fname := f.Name()
    86  		fp := filepath.Join(srcDeviceDir, fname)
    87  		fi, err := os.Lstat(fp)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		if fi.Mode()&os.ModeSymlink != 0 {
    92  			// Ignore any symlinks in the deviceDir since they simply point to
    93  			// either self-referential links or information we aren't
    94  			// interested in like "subsystem"
    95  			continue
    96  		} else if fi.IsDir() {
    97  			if strings.HasPrefix(fname, devName) {
    98  				// We're interested in are the directories that begin with the
    99  				// block device name. These are directories with information
   100  				// about the partitions on the device
   101  				buildPartitionDir := filepath.Join(
   102  					buildDeviceDir, fname,
   103  				)
   104  				srcPartitionDir := filepath.Join(
   105  					srcDeviceDir, fname,
   106  				)
   107  				trace("creating partition directory %s\n", buildPartitionDir)
   108  				err = os.MkdirAll(buildPartitionDir, os.ModePerm)
   109  				if err != nil {
   110  					return err
   111  				}
   112  				err = createPartitionDir(buildPartitionDir, srcPartitionDir)
   113  				if err != nil {
   114  					return err
   115  				}
   116  			}
   117  		} else if fi.Mode().IsRegular() {
   118  			// Regular files in the block device directory are both regular and
   119  			// pseudofiles containing information such as the size (in sectors)
   120  			// and whether the device is read-only
   121  			buf, err := os.ReadFile(fp)
   122  			if err != nil {
   123  				if errors.Is(err, os.ErrPermission) {
   124  					// example: /sys/devices/virtual/block/zram0/compact is 0400
   125  					trace("permission denied reading %q - skipped\n", fp)
   126  					continue
   127  				}
   128  				return err
   129  			}
   130  			targetPath := filepath.Join(buildDeviceDir, fname)
   131  			trace("creating %s\n", targetPath)
   132  			f, err := os.Create(targetPath)
   133  			if err != nil {
   134  				return err
   135  			}
   136  			if _, err = f.Write(buf); err != nil {
   137  				return err
   138  			}
   139  			f.Close()
   140  		}
   141  	}
   142  	// There is a special file $DEVICE_DIR/queue/rotational that, for some hard
   143  	// drives, contains a 1 or 0 indicating whether the device is a spinning
   144  	// disk or not
   145  	srcQueueDir := filepath.Join(
   146  		srcDeviceDir,
   147  		"queue",
   148  	)
   149  	buildQueueDir := filepath.Join(
   150  		buildDeviceDir,
   151  		"queue",
   152  	)
   153  	err = os.MkdirAll(buildQueueDir, os.ModePerm)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	fp := filepath.Join(srcQueueDir, "rotational")
   158  	buf, err := os.ReadFile(fp)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	targetPath := filepath.Join(buildQueueDir, "rotational")
   163  	trace("creating %s\n", targetPath)
   164  	f, err := os.Create(targetPath)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if _, err = f.Write(buf); err != nil {
   169  		return err
   170  	}
   171  	f.Close()
   172  
   173  	return nil
   174  }
   175  
   176  func createPartitionDir(buildPartitionDir string, srcPartitionDir string) error {
   177  	// Populate the supplied directory (in our build filesystem) with all the
   178  	// appropriate information pseudofile contents for the partition.
   179  	partFiles, err := os.ReadDir(srcPartitionDir)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	for _, f := range partFiles {
   184  		fname := f.Name()
   185  		fp := filepath.Join(srcPartitionDir, fname)
   186  		fi, err := os.Lstat(fp)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		if fi.Mode()&os.ModeSymlink != 0 {
   191  			// Ignore any symlinks in the partition directory since they simply
   192  			// point to information we aren't interested in like "subsystem"
   193  			continue
   194  		} else if fi.IsDir() {
   195  			// The subdirectories in the partition directory are not
   196  			// interesting for us. They have information about power events and
   197  			// traces
   198  			continue
   199  		} else if fi.Mode().IsRegular() {
   200  			// Regular files in the block device directory are both regular and
   201  			// pseudofiles containing information such as the size (in sectors)
   202  			// and whether the device is read-only
   203  			buf, err := os.ReadFile(fp)
   204  			if err != nil {
   205  				return err
   206  			}
   207  			targetPath := filepath.Join(buildPartitionDir, fname)
   208  			trace("creating %s\n", targetPath)
   209  			f, err := os.Create(targetPath)
   210  			if err != nil {
   211  				return err
   212  			}
   213  			if _, err = f.Write(buf); err != nil {
   214  				return err
   215  			}
   216  			f.Close()
   217  		}
   218  	}
   219  	return nil
   220  }