Skip to content

Commit bdb4ad6

Browse files
authored
Merge pull request #212 from cyphar/devices
devices: move libcontainer/devices to moby/sys
2 parents 469a746 + 1b36f79 commit bdb4ad6

6 files changed

Lines changed: 272 additions & 1 deletion

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
PACKAGES ?= atomicwriter capability mountinfo mount reexec sequential signal symlink user userns
1+
PACKAGES ?= atomicwriter capability devices mountinfo mount reexec sequential signal symlink user userns
22
CROSS ?= linux/arm linux/arm64 linux/ppc64le linux/s390x \
33
freebsd/amd64 openbsd/amd64 darwin/amd64 darwin/arm64 windows/amd64
44
SUDO ?= sudo -n

devices/device_unix.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//go:build !windows
2+
3+
// SPDX-License-Identifier: Apache-2.0
4+
/*
5+
* Copyright (C) 2015-2026 Open Containers Initiative Contributors
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
// This code originally comes from runc and was taken from this tree:
21+
// <https://github.com/opencontainers/runc/tree/v1.4.0/libcontainer/devices>.
22+
23+
package devices
24+
25+
import (
26+
"errors"
27+
"os"
28+
"path/filepath"
29+
30+
"github.com/opencontainers/cgroups/devices/config"
31+
"golang.org/x/sys/unix"
32+
)
33+
34+
// ErrNotADevice denotes that a file is not a valid linux device.
35+
var ErrNotADevice = errors.New("not a device node")
36+
37+
// Testing dependencies
38+
var (
39+
unixLstat = unix.Lstat
40+
osReadDir = os.ReadDir
41+
)
42+
43+
// DeviceFromPath takes the path to a device and its cgroup_permissions (which
44+
// cannot be easily queried) to look up the information about a linux device
45+
// and returns that information as a Device struct.
46+
func DeviceFromPath(path, permissions string) (*config.Device, error) {
47+
var stat unix.Stat_t
48+
err := unixLstat(path, &stat)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
var (
54+
devType config.Type
55+
mode = stat.Mode
56+
devNumber = uint64(stat.Rdev) //nolint:unconvert // Rdev is uint32 on e.g. MIPS.
57+
major = unix.Major(devNumber)
58+
minor = unix.Minor(devNumber)
59+
)
60+
switch mode & unix.S_IFMT {
61+
case unix.S_IFBLK:
62+
devType = config.BlockDevice
63+
case unix.S_IFCHR:
64+
devType = config.CharDevice
65+
case unix.S_IFIFO:
66+
devType = config.FifoDevice
67+
default:
68+
return nil, ErrNotADevice
69+
}
70+
return &config.Device{
71+
Rule: config.Rule{
72+
Type: devType,
73+
Major: int64(major),
74+
Minor: int64(minor),
75+
Permissions: config.Permissions(permissions),
76+
},
77+
Path: path,
78+
FileMode: os.FileMode(mode &^ unix.S_IFMT),
79+
Uid: stat.Uid,
80+
Gid: stat.Gid,
81+
}, nil
82+
}
83+
84+
// HostDevices returns all devices that can be found under /dev directory.
85+
func HostDevices() ([]*config.Device, error) {
86+
return GetDevices("/dev")
87+
}
88+
89+
// GetDevices recursively traverses a directory specified by path
90+
// and returns all devices found there.
91+
func GetDevices(path string) ([]*config.Device, error) {
92+
files, err := osReadDir(path)
93+
if err != nil {
94+
return nil, err
95+
}
96+
var out []*config.Device
97+
for _, f := range files {
98+
switch {
99+
case f.IsDir():
100+
switch f.Name() {
101+
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
102+
// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
103+
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
104+
continue
105+
default:
106+
sub, err := GetDevices(filepath.Join(path, f.Name()))
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
out = append(out, sub...)
112+
continue
113+
}
114+
case f.Name() == "console":
115+
continue
116+
}
117+
device, err := DeviceFromPath(filepath.Join(path, f.Name()), "rwm")
118+
if err != nil {
119+
if errors.Is(err, ErrNotADevice) {
120+
continue
121+
}
122+
if errors.Is(err, os.ErrNotExist) {
123+
continue
124+
}
125+
return nil, err
126+
}
127+
if device.Type == config.FifoDevice {
128+
continue
129+
}
130+
out = append(out, device)
131+
}
132+
return out, nil
133+
}

devices/device_unix_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//go:build !windows
2+
3+
// SPDX-License-Identifier: Apache-2.0
4+
/*
5+
* Copyright (C) 2015-2026 Open Containers Initiative Contributors
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
// This code originally comes from runc and was taken from this tree:
21+
// <https://github.com/opencontainers/runc/tree/v1.4.0/libcontainer/devices>.
22+
23+
package devices
24+
25+
import (
26+
"errors"
27+
"io/fs"
28+
"os"
29+
"runtime"
30+
"testing"
31+
32+
"github.com/opencontainers/cgroups/devices/config"
33+
"golang.org/x/sys/unix"
34+
)
35+
36+
func cleanupTest() {
37+
unixLstat = unix.Lstat
38+
osReadDir = os.ReadDir
39+
}
40+
41+
func TestDeviceFromPathLstatFailure(t *testing.T) {
42+
testError := errors.New("test error")
43+
44+
// Override unix.Lstat to inject error.
45+
unixLstat = func(path string, stat *unix.Stat_t) error {
46+
return testError
47+
}
48+
defer cleanupTest()
49+
50+
_, err := DeviceFromPath("", "")
51+
if !errors.Is(err, testError) {
52+
t.Fatalf("Unexpected error %v, expected %v", err, testError)
53+
}
54+
}
55+
56+
func TestHostDevicesIoutilReadDirFailure(t *testing.T) {
57+
testError := errors.New("test error")
58+
59+
// Override os.ReadDir to inject error.
60+
osReadDir = func(dirname string) ([]fs.DirEntry, error) {
61+
return nil, testError
62+
}
63+
defer cleanupTest()
64+
65+
_, err := HostDevices()
66+
if !errors.Is(err, testError) {
67+
t.Fatalf("Unexpected error %v, expected %v", err, testError)
68+
}
69+
}
70+
71+
func TestHostDevicesIoutilReadDirDeepFailure(t *testing.T) {
72+
testError := errors.New("test error")
73+
called := false
74+
75+
// Override os.ReadDir to inject error after the first call.
76+
osReadDir = func(dirname string) ([]fs.DirEntry, error) {
77+
if called {
78+
return nil, testError
79+
}
80+
called = true
81+
82+
// Provoke a second call.
83+
fi, err := os.Stat("/tmp")
84+
if err != nil {
85+
t.Fatalf("Unexpected error %v", err)
86+
}
87+
88+
return []fs.DirEntry{fs.FileInfoToDirEntry(fi)}, nil
89+
}
90+
defer cleanupTest()
91+
92+
_, err := HostDevices()
93+
if !errors.Is(err, testError) {
94+
t.Fatalf("Unexpected error %v, expected %v", err, testError)
95+
}
96+
}
97+
98+
func TestHostDevicesAllValid(t *testing.T) {
99+
devices, err := HostDevices()
100+
if err != nil {
101+
t.Fatalf("failed to get host devices: %v", err)
102+
}
103+
104+
for _, device := range devices {
105+
// Devices can't have major number 0 on Linux.
106+
if device.Major == 0 {
107+
logFn := t.Logf
108+
if runtime.GOOS == "linux" {
109+
logFn = t.Errorf
110+
}
111+
logFn("device entry %+v has zero major number", device)
112+
}
113+
switch device.Type {
114+
case config.BlockDevice, config.CharDevice:
115+
case config.FifoDevice:
116+
t.Logf("fifo devices shouldn't show up from HostDevices")
117+
fallthrough
118+
default:
119+
t.Errorf("device entry %+v has unexpected type %v", device, device.Type)
120+
}
121+
}
122+
}

devices/doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Package devices provides some helper functions for constructing device
2+
// configurations for runc. These are exclusively used by higher-level runtimes
3+
// that need to configure runc's device list based on existing devices.
4+
package devices

devices/go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/moby/sys/devices
2+
3+
go 1.24
4+
5+
require (
6+
github.com/opencontainers/cgroups v0.0.6
7+
golang.org/x/sys v0.30.0
8+
)

devices/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
github.com/opencontainers/cgroups v0.0.6 h1:tfZFWTIIGaUUFImTyuTg+Mr5x8XRiSdZESgEBW7UxuI=
2+
github.com/opencontainers/cgroups v0.0.6/go.mod h1:oWVzJsKK0gG9SCRBfTpnn16WcGEqDI8PAcpMGbqWxcs=
3+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
4+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

0 commit comments

Comments
 (0)