feature.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package internal
  2. import (
  3. "errors"
  4. "fmt"
  5. "sync"
  6. )
  7. // ErrNotSupported indicates that a feature is not supported by the current kernel.
  8. var ErrNotSupported = errors.New("not supported")
  9. // UnsupportedFeatureError is returned by FeatureTest() functions.
  10. type UnsupportedFeatureError struct {
  11. // The minimum Linux mainline version required for this feature.
  12. // Used for the error string, and for sanity checking during testing.
  13. MinimumVersion Version
  14. // The name of the feature that isn't supported.
  15. Name string
  16. }
  17. func (ufe *UnsupportedFeatureError) Error() string {
  18. return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
  19. }
  20. // Is indicates that UnsupportedFeatureError is ErrNotSupported.
  21. func (ufe *UnsupportedFeatureError) Is(target error) bool {
  22. return target == ErrNotSupported
  23. }
  24. type featureTest struct {
  25. sync.Mutex
  26. successful bool
  27. result error
  28. }
  29. // FeatureTestFn is used to determine whether the kernel supports
  30. // a certain feature.
  31. //
  32. // The return values have the following semantics:
  33. //
  34. // err != nil: the test couldn't be executed
  35. // err == nil && available: the feature is available
  36. // err == nil && !available: the feature isn't available
  37. type FeatureTestFn func() (available bool, err error)
  38. // FeatureTest wraps a function so that it is run at most once.
  39. //
  40. // name should identify the tested feature, while version must be in the
  41. // form Major.Minor[.Patch].
  42. //
  43. // Returns an error wrapping ErrNotSupported if the feature is not supported.
  44. func FeatureTest(name, version string, fn FeatureTestFn) func() error {
  45. v, err := NewVersion(version)
  46. if err != nil {
  47. return func() error { return err }
  48. }
  49. ft := new(featureTest)
  50. return func() error {
  51. ft.Lock()
  52. defer ft.Unlock()
  53. if ft.successful {
  54. return ft.result
  55. }
  56. available, err := fn()
  57. if errors.Is(err, ErrNotSupported) {
  58. // The feature test aborted because a dependent feature
  59. // is missing, which we should cache.
  60. available = false
  61. } else if err != nil {
  62. // We couldn't execute the feature test to a point
  63. // where it could make a determination.
  64. // Don't cache the result, just return it.
  65. return fmt.Errorf("can't detect support for %s: %w", name, err)
  66. }
  67. ft.successful = true
  68. if !available {
  69. ft.result = &UnsupportedFeatureError{
  70. MinimumVersion: v,
  71. Name: name,
  72. }
  73. }
  74. return ft.result
  75. }
  76. }
  77. // A Version in the form Major.Minor.Patch.
  78. type Version [3]uint16
  79. // NewVersion creates a version from a string like "Major.Minor.Patch".
  80. //
  81. // Patch is optional.
  82. func NewVersion(ver string) (Version, error) {
  83. var major, minor, patch uint16
  84. n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
  85. if n < 2 {
  86. return Version{}, fmt.Errorf("invalid version: %s", ver)
  87. }
  88. return Version{major, minor, patch}, nil
  89. }
  90. func (v Version) String() string {
  91. if v[2] == 0 {
  92. return fmt.Sprintf("v%d.%d", v[0], v[1])
  93. }
  94. return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
  95. }
  96. // Less returns true if the version is less than another version.
  97. func (v Version) Less(other Version) bool {
  98. for i, a := range v {
  99. if a == other[i] {
  100. continue
  101. }
  102. return a < other[i]
  103. }
  104. return false
  105. }