runc/devices/devices.go

130 lines
3.2 KiB
Go
Raw Normal View History

package devices
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
)
const (
Wildcard = -1
)
var (
ErrNotADeviceNode = errors.New("not a device node")
)
// Testing dependencies
var (
osLstat = os.Lstat
ioutilReadDir = ioutil.ReadDir
)
type Device struct {
Type rune `json:"type,omitempty"`
Path string `json:"path,omitempty"` // It is fine if this is an empty string in the case that you are using Wildcards
MajorNumber int64 `json:"major_number,omitempty"` // Use the wildcard constant for wildcards.
MinorNumber int64 `json:"minor_number,omitempty"` // Use the wildcard constant for wildcards.
CgroupPermissions string `json:"cgroup_permissions,omitempty"` // Typically just "rwm"
FileMode os.FileMode `json:"file_mode,omitempty"` // The permission bits of the file's mode
Uid uint32 `json:"uid,omitempty"`
Gid uint32 `json:"gid,omitempty"`
}
func GetDeviceNumberString(deviceNumber int64) string {
if deviceNumber == Wildcard {
return "*"
} else {
return fmt.Sprintf("%d", deviceNumber)
}
}
func (device *Device) GetCgroupAllowString() string {
return fmt.Sprintf("%c %s:%s %s", device.Type, GetDeviceNumberString(device.MajorNumber), GetDeviceNumberString(device.MinorNumber), device.CgroupPermissions)
}
// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct.
func GetDevice(path, cgroupPermissions string) (*Device, error) {
fileInfo, err := osLstat(path)
if err != nil {
return nil, err
}
var (
devType rune
mode = fileInfo.Mode()
fileModePermissionBits = os.FileMode.Perm(mode)
)
switch {
case mode&os.ModeDevice == 0:
return nil, ErrNotADeviceNode
case mode&os.ModeCharDevice != 0:
fileModePermissionBits |= syscall.S_IFCHR
devType = 'c'
default:
fileModePermissionBits |= syscall.S_IFBLK
devType = 'b'
}
stat_t, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return nil, fmt.Errorf("cannot determine the device number for device %s", path)
}
devNumber := int(stat_t.Rdev)
return &Device{
Type: devType,
Path: path,
MajorNumber: Major(devNumber),
MinorNumber: Minor(devNumber),
CgroupPermissions: cgroupPermissions,
FileMode: fileModePermissionBits,
Uid: stat_t.Uid,
Gid: stat_t.Gid,
}, nil
}
func GetHostDeviceNodes() ([]*Device, error) {
return getDeviceNodes("/dev")
}
func getDeviceNodes(path string) ([]*Device, error) {
files, err := ioutilReadDir(path)
if err != nil {
return nil, err
}
out := []*Device{}
for _, f := range files {
if f.IsDir() {
switch f.Name() {
case "pts", "shm", "fd":
continue
default:
sub, err := getDeviceNodes(filepath.Join(path, f.Name()))
if err != nil {
return nil, err
}
out = append(out, sub...)
continue
}
}
device, err := GetDevice(filepath.Join(path, f.Name()), "rwm")
if err != nil {
if err == ErrNotADeviceNode {
continue
}
return nil, err
}
out = append(out, device)
}
return out, nil
}