feature.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  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. if ufe.MinimumVersion.Unspecified() {
  19. return fmt.Sprintf("%s not supported", ufe.Name)
  20. }
  21. return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
  22. }
  23. // Is indicates that UnsupportedFeatureError is ErrNotSupported.
  24. func (ufe *UnsupportedFeatureError) Is(target error) bool {
  25. return target == ErrNotSupported
  26. }
  27. type featureTest struct {
  28. sync.RWMutex
  29. successful bool
  30. result error
  31. }
  32. // FeatureTestFn is used to determine whether the kernel supports
  33. // a certain feature.
  34. //
  35. // The return values have the following semantics:
  36. //
  37. // err == ErrNotSupported: the feature is not available
  38. // err == nil: the feature is available
  39. // err != nil: the test couldn't be executed
  40. type FeatureTestFn func() error
  41. // FeatureTest wraps a function so that it is run at most once.
  42. //
  43. // name should identify the tested feature, while version must be in the
  44. // form Major.Minor[.Patch].
  45. //
  46. // Returns an error wrapping ErrNotSupported if the feature is not supported.
  47. func FeatureTest(name, version string, fn FeatureTestFn) func() error {
  48. ft := new(featureTest)
  49. return func() error {
  50. ft.RLock()
  51. if ft.successful {
  52. defer ft.RUnlock()
  53. return ft.result
  54. }
  55. ft.RUnlock()
  56. ft.Lock()
  57. defer ft.Unlock()
  58. // check one more time on the off
  59. // chance that two go routines
  60. // were able to call into the write
  61. // lock
  62. if ft.successful {
  63. return ft.result
  64. }
  65. err := fn()
  66. switch {
  67. case errors.Is(err, ErrNotSupported):
  68. v, err := NewVersion(version)
  69. if err != nil {
  70. return err
  71. }
  72. ft.result = &UnsupportedFeatureError{
  73. MinimumVersion: v,
  74. Name: name,
  75. }
  76. fallthrough
  77. case err == nil:
  78. ft.successful = true
  79. default:
  80. // We couldn't execute the feature test to a point
  81. // where it could make a determination.
  82. // Don't cache the result, just return it.
  83. return fmt.Errorf("detect support for %s: %w", name, err)
  84. }
  85. return ft.result
  86. }
  87. }