github.com/safing/portbase@v0.19.5/config/set.go (about) 1 package config 2 3 import ( 4 "errors" 5 "sync" 6 7 "github.com/tevino/abool" 8 ) 9 10 var ( 11 // ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json. 12 ErrInvalidJSON = errors.New("json string invalid") 13 14 // ErrInvalidOptionType is returned by SetConfigOption and SetDefaultConfigOption if given an unsupported option type. 15 ErrInvalidOptionType = errors.New("invalid option value type") 16 17 validityFlag = abool.NewBool(true) 18 validityFlagLock sync.RWMutex 19 ) 20 21 // getValidityFlag returns a flag that signifies if the configuration has been changed. This flag must not be changed, only read. 22 func getValidityFlag() *abool.AtomicBool { 23 validityFlagLock.RLock() 24 defer validityFlagLock.RUnlock() 25 return validityFlag 26 } 27 28 // signalChanges marks the configs validtityFlag as dirty and eventually 29 // triggers a config change event. 30 func signalChanges() { 31 // reset validity flag 32 validityFlagLock.Lock() 33 validityFlag.SetTo(false) 34 validityFlag = abool.NewBool(true) 35 validityFlagLock.Unlock() 36 37 module.TriggerEvent(ChangeEvent, nil) 38 } 39 40 // ValidateConfig validates the given configuration and returns all validation 41 // errors as well as whether the given configuration contains unknown keys. 42 func ValidateConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool, containsUnknown bool) { 43 // RLock the options because we are not adding or removing 44 // options from the registration but rather only checking the 45 // options value which is guarded by the option's lock itself. 46 optionsLock.RLock() 47 defer optionsLock.RUnlock() 48 49 var checked int 50 for key, option := range options { 51 newValue, ok := newValues[key] 52 if ok { 53 checked++ 54 55 func() { 56 option.Lock() 57 defer option.Unlock() 58 59 newValue = migrateValue(option, newValue) 60 _, err := validateValue(option, newValue) 61 if err != nil { 62 validationErrors = append(validationErrors, err) 63 } 64 65 if option.RequiresRestart { 66 requiresRestart = true 67 } 68 }() 69 } 70 } 71 72 return validationErrors, requiresRestart, checked < len(newValues) 73 } 74 75 // ReplaceConfig sets the (prioritized) user defined config. 76 func ReplaceConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool) { 77 // RLock the options because we are not adding or removing 78 // options from the registration but rather only update the 79 // options value which is guarded by the option's lock itself. 80 optionsLock.RLock() 81 defer optionsLock.RUnlock() 82 83 for key, option := range options { 84 newValue, ok := newValues[key] 85 86 func() { 87 option.Lock() 88 defer option.Unlock() 89 90 option.activeValue = nil 91 if ok { 92 newValue = migrateValue(option, newValue) 93 valueCache, err := validateValue(option, newValue) 94 if err == nil { 95 option.activeValue = valueCache 96 } else { 97 validationErrors = append(validationErrors, err) 98 } 99 } 100 handleOptionUpdate(option, true) 101 102 if option.RequiresRestart { 103 requiresRestart = true 104 } 105 }() 106 } 107 108 signalChanges() 109 110 return validationErrors, requiresRestart 111 } 112 113 // ReplaceDefaultConfig sets the (fallback) default config. 114 func ReplaceDefaultConfig(newValues map[string]interface{}) (validationErrors []*ValidationError, requiresRestart bool) { 115 // RLock the options because we are not adding or removing 116 // options from the registration but rather only update the 117 // options value which is guarded by the option's lock itself. 118 optionsLock.RLock() 119 defer optionsLock.RUnlock() 120 121 for key, option := range options { 122 newValue, ok := newValues[key] 123 124 func() { 125 option.Lock() 126 defer option.Unlock() 127 128 option.activeDefaultValue = nil 129 if ok { 130 newValue = migrateValue(option, newValue) 131 valueCache, err := validateValue(option, newValue) 132 if err == nil { 133 option.activeDefaultValue = valueCache 134 } else { 135 validationErrors = append(validationErrors, err) 136 } 137 } 138 handleOptionUpdate(option, true) 139 140 if option.RequiresRestart { 141 requiresRestart = true 142 } 143 }() 144 } 145 146 signalChanges() 147 148 return validationErrors, requiresRestart 149 } 150 151 // SetConfigOption sets a single value in the (prioritized) user defined config. 152 func SetConfigOption(key string, value any) error { 153 return setConfigOption(key, value, true) 154 } 155 156 func setConfigOption(key string, value any, push bool) (err error) { 157 option, err := GetOption(key) 158 if err != nil { 159 return err 160 } 161 162 option.Lock() 163 if value == nil { 164 option.activeValue = nil 165 } else { 166 value = migrateValue(option, value) 167 valueCache, vErr := validateValue(option, value) 168 if vErr == nil { 169 option.activeValue = valueCache 170 } else { 171 err = vErr 172 } 173 } 174 175 // Add the "restart pending" annotation if the settings requires a restart. 176 if option.RequiresRestart { 177 option.setAnnotation(RestartPendingAnnotation, true) 178 } 179 180 handleOptionUpdate(option, push) 181 option.Unlock() 182 183 if err != nil { 184 return err 185 } 186 187 // finalize change, activate triggers 188 signalChanges() 189 190 return SaveConfig() 191 } 192 193 // SetDefaultConfigOption sets a single value in the (fallback) default config. 194 func SetDefaultConfigOption(key string, value interface{}) error { 195 return setDefaultConfigOption(key, value, true) 196 } 197 198 func setDefaultConfigOption(key string, value interface{}, push bool) (err error) { 199 option, err := GetOption(key) 200 if err != nil { 201 return err 202 } 203 204 option.Lock() 205 if value == nil { 206 option.activeDefaultValue = nil 207 } else { 208 value = migrateValue(option, value) 209 valueCache, vErr := validateValue(option, value) 210 if vErr == nil { 211 option.activeDefaultValue = valueCache 212 } else { 213 err = vErr 214 } 215 } 216 217 // Add the "restart pending" annotation if the settings requires a restart. 218 if option.RequiresRestart { 219 option.setAnnotation(RestartPendingAnnotation, true) 220 } 221 222 handleOptionUpdate(option, push) 223 option.Unlock() 224 225 if err != nil { 226 return err 227 } 228 229 // finalize change, activate triggers 230 signalChanges() 231 232 // Do not save the configuration, as it only saves the active values, not the 233 // active default value. 234 return nil 235 }