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 }