Skip to content

Commit 50752b2

Browse files
authored
Merge pull request #464 from dims/pci-vpd-chroot
pci: make Device.VPD respect the caller's chroot
2 parents ea9e990 + 73fb4e6 commit 50752b2

4 files changed

Lines changed: 65 additions & 19 deletions

File tree

pkg/pci/pci.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package pci
99
import (
1010
"encoding/json"
1111
"fmt"
12-
"sync"
1312

1413
"github.com/jaypipes/pcidb"
1514

@@ -56,11 +55,6 @@ type Device struct {
5655
// no particular order. Populated after enumeration; not included in
5756
// JSON output.
5857
Children []*Device `json:"-"`
59-
60-
// Cached VPD result (lazy). Guarded by vpdOnce.
61-
vpdOnce sync.Once
62-
vpd *VPD
63-
vpdErr error
6458
}
6559

6660
type devIdent struct {

pkg/pci/pci_stub.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ var ErrVPDUnavailable = errors.New("vpd: no sysfs directory associated with devi
5656
var ErrVPDNotPresent = errors.New("vpd: not present for device")
5757

5858
// VPD returns ErrVPDUnavailable on non-Linux platforms.
59-
func (d *Device) VPD() (*VPD, error) {
59+
func (d *Device) VPD(ctx context.Context) (*VPD, error) {
6060
return nil, ErrVPDUnavailable
6161
}

pkg/pci/vpd_linux.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ var ErrVPDUnavailable = errors.New("vpd: no sysfs directory associated with devi
8585
var ErrVPDNotPresent = errors.New("vpd: not present for device")
8686

8787
// VPD returns the parsed Vital Product Data for the device, reading it
88-
// from the device's sysfs `vpd` file on first call and caching the
89-
// result for subsequent calls.
88+
// from the device's sysfs `vpd` file. The sysfs root is taken from ctx,
89+
// so a ctx built via option.WithChroot reads VPD from inside the
90+
// chroot; context.Background() reads the live /sys.
9091
//
9192
// The sysfs VPD file is typically root-readable only. Callers running
9293
// without sufficient privilege will receive a wrapped permission
@@ -96,22 +97,15 @@ var ErrVPDNotPresent = errors.New("vpd: not present for device")
9697
// address (e.g. those constructed via Info.ParseDevice) and
9798
// ErrVPDNotPresent for devices whose sysfs entry exists but exposes
9899
// no `vpd` file.
99-
func (d *Device) VPD() (*VPD, error) {
100-
d.vpdOnce.Do(func() {
101-
d.vpd, d.vpdErr = d.readVPD()
102-
})
103-
return d.vpd, d.vpdErr
104-
}
105-
106-
func (d *Device) readVPD() (*VPD, error) {
100+
func (d *Device) VPD(ctx context.Context) (*VPD, error) {
107101
if d.Address == "" {
108102
return nil, ErrVPDUnavailable
109103
}
110104
pciAddr := pciaddr.FromString(d.Address)
111105
if pciAddr == nil {
112106
return nil, ErrVPDUnavailable
113107
}
114-
paths := linuxpath.New(context.Background())
108+
paths := linuxpath.New(ctx)
115109
vpdPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "vpd")
116110
data, err := os.ReadFile(vpdPath)
117111
if err != nil {

pkg/pci/vpd_test.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@
1010
package pci
1111

1212
import (
13+
"context"
1314
"encoding/binary"
1415
"errors"
16+
"os"
17+
"path/filepath"
1518
"reflect"
1619
"testing"
20+
21+
"github.com/jaypipes/ghw/internal/config"
22+
"github.com/jaypipes/ghw/pkg/option"
1723
)
1824

1925
// buildVPD assembles a synthetic VPD byte stream from a sequence of
@@ -211,7 +217,59 @@ func TestDeviceVPDUnavailableForParsedDevice(t *testing.T) {
211217
// Info.ParseDevice) must surface ErrVPDUnavailable rather than
212218
// trying to read a path-less file.
213219
d := &Device{}
214-
if _, err := d.VPD(); !errors.Is(err, ErrVPDUnavailable) {
220+
if _, err := d.VPD(context.Background()); !errors.Is(err, ErrVPDUnavailable) {
215221
t.Errorf("got %v, want ErrVPDUnavailable", err)
216222
}
217223
}
224+
225+
// TestDeviceVPDHonorsChroot is the regression test for Device.VPD
226+
// reading the live /sys regardless of option.WithChroot.
227+
func TestDeviceVPDHonorsChroot(t *testing.T) {
228+
chroot := t.TempDir()
229+
const addr = "0000:00:00.0"
230+
realDir := filepath.Join(chroot, "sys", "devices", "pci0000:00", addr)
231+
if err := os.MkdirAll(realDir, 0o755); err != nil {
232+
t.Fatal(err)
233+
}
234+
devsDir := filepath.Join(chroot, "sys", "bus", "pci", "devices")
235+
if err := os.MkdirAll(devsDir, 0o755); err != nil {
236+
t.Fatal(err)
237+
}
238+
const modalias = "pci:v000010DEd000022B1sv00000000sd00000000bc06sc04i00"
239+
if err := os.WriteFile(filepath.Join(realDir, "modalias"), []byte(modalias+"\n"), 0o644); err != nil {
240+
t.Fatal(err)
241+
}
242+
rel, err := filepath.Rel(devsDir, realDir)
243+
if err != nil {
244+
t.Fatal(err)
245+
}
246+
if err := os.Symlink(rel, filepath.Join(devsDir, addr)); err != nil {
247+
t.Fatal(err)
248+
}
249+
vpd := buildVPD([]struct {
250+
tag byte
251+
body []byte
252+
}{
253+
{vpdTagLargeIdent, []byte("chroot-test")},
254+
})
255+
if err := os.WriteFile(filepath.Join(realDir, "vpd"), vpd, 0o644); err != nil {
256+
t.Fatal(err)
257+
}
258+
259+
ctx := config.ContextFromArgs(option.WithChroot(chroot), config.WithDisableTopology())
260+
info, err := New(ctx)
261+
if err != nil {
262+
t.Fatalf("pci.New: %v", err)
263+
}
264+
if len(info.Devices) != 1 || info.Devices[0].Address != addr {
265+
t.Fatalf("expected one device at %s, got %+v", addr, info.Devices)
266+
}
267+
268+
v, err := info.Devices[0].VPD(ctx)
269+
if err != nil {
270+
t.Fatalf("VPD: %v", err)
271+
}
272+
if v.Identifier != "chroot-test" {
273+
t.Errorf("got identifier %q, want %q", v.Identifier, "chroot-test")
274+
}
275+
}

0 commit comments

Comments
 (0)