device_unix.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. //go:build !windows
  2. // +build !windows
  3. package devices
  4. import (
  5. "errors"
  6. "os"
  7. "path/filepath"
  8. "golang.org/x/sys/unix"
  9. )
  10. // ErrNotADevice denotes that a file is not a valid linux device.
  11. var ErrNotADevice = errors.New("not a device node")
  12. // Testing dependencies
  13. var (
  14. unixLstat = unix.Lstat
  15. osReadDir = os.ReadDir
  16. )
  17. func mkDev(d *Rule) (uint64, error) {
  18. if d.Major == Wildcard || d.Minor == Wildcard {
  19. return 0, errors.New("cannot mkdev() device with wildcards")
  20. }
  21. return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil
  22. }
  23. // DeviceFromPath takes the path to a device and its cgroup_permissions (which
  24. // cannot be easily queried) to look up the information about a linux device
  25. // and returns that information as a Device struct.
  26. func DeviceFromPath(path, permissions string) (*Device, error) {
  27. var stat unix.Stat_t
  28. err := unixLstat(path, &stat)
  29. if err != nil {
  30. return nil, err
  31. }
  32. var (
  33. devType Type
  34. mode = stat.Mode
  35. devNumber = uint64(stat.Rdev) //nolint:unconvert // Rdev is uint32 on e.g. MIPS.
  36. major = unix.Major(devNumber)
  37. minor = unix.Minor(devNumber)
  38. )
  39. switch mode & unix.S_IFMT {
  40. case unix.S_IFBLK:
  41. devType = BlockDevice
  42. case unix.S_IFCHR:
  43. devType = CharDevice
  44. case unix.S_IFIFO:
  45. devType = FifoDevice
  46. default:
  47. return nil, ErrNotADevice
  48. }
  49. return &Device{
  50. Rule: Rule{
  51. Type: devType,
  52. Major: int64(major),
  53. Minor: int64(minor),
  54. Permissions: Permissions(permissions),
  55. },
  56. Path: path,
  57. FileMode: os.FileMode(mode &^ unix.S_IFMT),
  58. Uid: stat.Uid,
  59. Gid: stat.Gid,
  60. }, nil
  61. }
  62. // HostDevices returns all devices that can be found under /dev directory.
  63. func HostDevices() ([]*Device, error) {
  64. return GetDevices("/dev")
  65. }
  66. // GetDevices recursively traverses a directory specified by path
  67. // and returns all devices found there.
  68. func GetDevices(path string) ([]*Device, error) {
  69. files, err := osReadDir(path)
  70. if err != nil {
  71. return nil, err
  72. }
  73. var out []*Device
  74. for _, f := range files {
  75. switch {
  76. case f.IsDir():
  77. switch f.Name() {
  78. // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
  79. // ".udev" added to address https://github.com/opencontainers/runc/issues/2093
  80. case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
  81. continue
  82. default:
  83. sub, err := GetDevices(filepath.Join(path, f.Name()))
  84. if err != nil {
  85. return nil, err
  86. }
  87. out = append(out, sub...)
  88. continue
  89. }
  90. case f.Name() == "console":
  91. continue
  92. }
  93. device, err := DeviceFromPath(filepath.Join(path, f.Name()), "rwm")
  94. if err != nil {
  95. if errors.Is(err, ErrNotADevice) {
  96. continue
  97. }
  98. if os.IsNotExist(err) {
  99. continue
  100. }
  101. return nil, err
  102. }
  103. if device.Type == FifoDevice {
  104. continue
  105. }
  106. out = append(out, device)
  107. }
  108. return out, nil
  109. }