feature.go (2519B)
1 package internal 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 ) 8 9 // ErrNotSupported indicates that a feature is not supported by the current kernel. 10 var ErrNotSupported = errors.New("not supported") 11 12 // UnsupportedFeatureError is returned by FeatureTest() functions. 13 type UnsupportedFeatureError struct { 14 // The minimum Linux mainline version required for this feature. 15 // Used for the error string, and for sanity checking during testing. 16 MinimumVersion Version 17 18 // The name of the feature that isn't supported. 19 Name string 20 } 21 22 func (ufe *UnsupportedFeatureError) Error() string { 23 if ufe.MinimumVersion.Unspecified() { 24 return fmt.Sprintf("%s not supported", ufe.Name) 25 } 26 return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion) 27 } 28 29 // Is indicates that UnsupportedFeatureError is ErrNotSupported. 30 func (ufe *UnsupportedFeatureError) Is(target error) bool { 31 return target == ErrNotSupported 32 } 33 34 type featureTest struct { 35 sync.RWMutex 36 successful bool 37 result error 38 } 39 40 // FeatureTestFn is used to determine whether the kernel supports 41 // a certain feature. 42 // 43 // The return values have the following semantics: 44 // 45 // err == ErrNotSupported: the feature is not available 46 // err == nil: the feature is available 47 // err != nil: the test couldn't be executed 48 type FeatureTestFn func() error 49 50 // FeatureTest wraps a function so that it is run at most once. 51 // 52 // name should identify the tested feature, while version must be in the 53 // form Major.Minor[.Patch]. 54 // 55 // Returns an error wrapping ErrNotSupported if the feature is not supported. 56 func FeatureTest(name, version string, fn FeatureTestFn) func() error { 57 ft := new(featureTest) 58 return func() error { 59 ft.RLock() 60 if ft.successful { 61 defer ft.RUnlock() 62 return ft.result 63 } 64 ft.RUnlock() 65 ft.Lock() 66 defer ft.Unlock() 67 // check one more time on the off 68 // chance that two go routines 69 // were able to call into the write 70 // lock 71 if ft.successful { 72 return ft.result 73 } 74 err := fn() 75 switch { 76 case errors.Is(err, ErrNotSupported): 77 v, err := NewVersion(version) 78 if err != nil { 79 return err 80 } 81 82 ft.result = &UnsupportedFeatureError{ 83 MinimumVersion: v, 84 Name: name, 85 } 86 fallthrough 87 88 case err == nil: 89 ft.successful = true 90 91 default: 92 // We couldn't execute the feature test to a point 93 // where it could make a determination. 94 // Don't cache the result, just return it. 95 return fmt.Errorf("detect support for %s: %w", name, err) 96 } 97 98 return ft.result 99 } 100 }