github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/dependencies.go (about) 1 package modules 2 3 import ( 4 "errors" 5 "io" 6 "io/ioutil" 7 "net" 8 "os" 9 "sync" 10 "time" 11 12 "SiaPrime/build" 13 "SiaPrime/persist" 14 "gitlab.com/NebulousLabs/fastrand" 15 ) 16 17 // ProdDependencies act as a global instance of the production dependencies to 18 // avoid having to instantiate new dependencies every time we want to pass 19 // production dependencies. 20 var ProdDependencies = new(ProductionDependencies) 21 22 // Dependencies defines dependencies used by all of Sia's modules. Custom 23 // dependencies can be created to inject certain behavior during testing. 24 type ( 25 Dependencies interface { 26 // AtLeastOne will return a value that is at least one. In production, 27 // the value should always be one. This function is used to test the 28 // idempotency of actions, so during testing sometimes the value 29 // returned will be higher, causing an idempotent action to be 30 // committed multiple times. If the action is truly idempotent, 31 // committing it multiple times should not cause any problems or 32 // changes. 33 AtLeastOne() uint64 34 35 // CreateFile gives the host the ability to create files on the 36 // operating system. 37 CreateFile(string) (File, error) 38 39 // Destruct will clean up the dependencies, panicking if there are 40 // unclosed resources. 41 Destruct() 42 43 // DialTimeout tries to create a tcp connection to the specified 44 // address with a certain timeout. 45 DialTimeout(NetAddress, time.Duration) (net.Conn, error) 46 47 // Disrupt can be inserted in the code as a way to inject problems, 48 // such as a network call that take 10 minutes or a disk write that 49 // never completes. disrupt will return true if the disruption is 50 // forcibly triggered. In production, disrupt will always return false. 51 Disrupt(string) bool 52 53 // Listen gives the host the ability to receive incoming connections. 54 Listen(string, string) (net.Listener, error) 55 56 // LoadFile allows the host to load a persistence structure form disk. 57 LoadFile(persist.Metadata, interface{}, string) error 58 59 // LookupIP resolves a hostname to a number of IP addresses. If an IP 60 // address is provided as an argument it will just return that IP. 61 LookupIP(string) ([]net.IP, error) 62 63 // MkdirAll gives the host the ability to create chains of folders 64 // within the filesystem. 65 MkdirAll(string, os.FileMode) error 66 67 // NewLogger creates a logger that the host can use to log messages and 68 // write critical statements. 69 NewLogger(string) (*persist.Logger, error) 70 71 // OpenDatabase creates a database that the host can use to interact 72 // with large volumes of persistent data. 73 OpenDatabase(persist.Metadata, string) (*persist.BoltDatabase, error) 74 75 // OpenFile opens a file for the host. 76 OpenFile(string, int, os.FileMode) (File, error) 77 78 // Resolver returns a Resolver which can resolve hostnames to IPs. 79 Resolver() Resolver 80 81 // RandRead fills the input bytes with random data. 82 RandRead([]byte) (int, error) 83 84 // ReadFile reads a file in full from the filesystem. 85 ReadFile(string) ([]byte, error) 86 87 // RemoveFile removes a file from file filesystem. 88 RemoveFile(string) error 89 90 // RenameFile renames a file on disk to another name. 91 RenameFile(string, string) error 92 93 // SaveFileSync writes JSON encoded data to disk and syncs the file 94 // afterwards. 95 SaveFileSync(persist.Metadata, interface{}, string) error 96 97 // Sleep blocks the calling thread for at least the specified duration. 98 Sleep(time.Duration) 99 100 // Symlink creates a sym link between a source and a destination. 101 Symlink(s1, s2 string) error 102 103 // WriteFile writes data to the filesystem using the provided filename. 104 WriteFile(string, []byte, os.FileMode) error 105 } 106 107 // File implements all of the methods that can be called on an os.File. 108 File interface { 109 io.ReadWriteCloser 110 Name() string 111 ReadAt([]byte, int64) (int, error) 112 Sync() error 113 Truncate(int64) error 114 WriteAt([]byte, int64) (int, error) 115 } 116 ) 117 118 type ( 119 // ProductionDependencies are the dependencies used in a Release or Debug 120 // production build. 121 ProductionDependencies struct { 122 shouldInit bool 123 openFiles map[string]int 124 mu sync.Mutex 125 } 126 127 // ProductionFile is the implementation of the File interface that is used 128 // in a Release or Debug production build. 129 ProductionFile struct { 130 pd *ProductionDependencies 131 *os.File 132 } 133 ) 134 135 // Close will close a file, checking whether the file handle is open somewhere 136 // else before closing completely. This check is performed on Windows but not 137 // Linux, therefore a mock is used to ensure that linux testing picks up 138 // potential problems that would be seen on Windows. 139 func (pf *ProductionFile) Close() error { 140 if !build.DEBUG { 141 return pf.File.Close() 142 } 143 144 pf.pd.mu.Lock() 145 if pf.pd.openFiles == nil { 146 pf.pd.openFiles = make(map[string]int) 147 } 148 v, exists := pf.pd.openFiles[pf.File.Name()] 149 if !exists { 150 panic("file not registered") 151 } 152 if v == 1 { 153 delete(pf.pd.openFiles, pf.File.Name()) 154 } else if v > 1 { 155 pf.pd.openFiles[pf.File.Name()] = v - 1 156 } else { 157 panic("inconsistent state") 158 } 159 pf.pd.mu.Unlock() 160 return pf.File.Close() 161 } 162 163 // AtLeastOne will return a value that is equal to 1 if debugging is disabled. 164 // If debugging is enabled, a higher value may be returned. 165 func (*ProductionDependencies) AtLeastOne() uint64 { 166 if !build.DEBUG { 167 return 1 168 } 169 170 // Probabilistically return a number greater than one. 171 val := uint64(1) 172 for fastrand.Intn(2) != 0 { 173 val++ 174 } 175 return val 176 } 177 178 // CreateFile gives the host the ability to create files on the operating 179 // system. 180 func (pd *ProductionDependencies) CreateFile(s string) (File, error) { 181 if !build.DEBUG { 182 return os.Create(s) 183 } 184 185 f, err := os.Create(s) 186 if err != nil { 187 return f, err 188 } 189 190 pd.mu.Lock() 191 if pd.openFiles == nil { 192 pd.openFiles = make(map[string]int) 193 } 194 v := pd.openFiles[s] 195 pd.openFiles[s] = v + 1 196 pd.mu.Unlock() 197 return &ProductionFile{ 198 pd: pd, 199 File: f, 200 }, nil 201 } 202 203 // Destruct checks that all resources have been cleaned up correctly. 204 func (pd *ProductionDependencies) Destruct() { 205 if !build.DEBUG { 206 return 207 } 208 209 pd.mu.Lock() 210 l := len(pd.openFiles) 211 pd.mu.Unlock() 212 if l != 0 { 213 panic("unclosed resources - most likely file handles") 214 } 215 } 216 217 // DialTimeout creates a tcp connection to a certain address with the specified 218 // timeout. 219 func (*ProductionDependencies) DialTimeout(addr NetAddress, timeout time.Duration) (net.Conn, error) { 220 return net.DialTimeout("tcp", string(addr), timeout) 221 } 222 223 // Disrupt can be used to inject specific behavior into a module by overwriting 224 // it using a custom dependency. 225 func (*ProductionDependencies) Disrupt(string) bool { 226 return false 227 } 228 229 // Listen gives the host the ability to receive incoming connections. 230 func (*ProductionDependencies) Listen(s1, s2 string) (net.Listener, error) { 231 return net.Listen(s1, s2) 232 } 233 234 // LoadFile loads JSON encoded data from a file. 235 func (*ProductionDependencies) LoadFile(meta persist.Metadata, data interface{}, filename string) error { 236 return persist.LoadJSON(meta, data, filename) 237 } 238 239 // LookupIP resolves a hostname to a number of IP addresses. If an IP address 240 // is provided as an argument it will just return that IP. 241 func (*ProductionDependencies) LookupIP(host string) ([]net.IP, error) { 242 return net.LookupIP(host) 243 } 244 245 // SaveFileSync writes JSON encoded data to a file and syncs the file to disk 246 // afterwards. 247 func (*ProductionDependencies) SaveFileSync(meta persist.Metadata, data interface{}, filename string) error { 248 return persist.SaveJSON(meta, data, filename) 249 } 250 251 // MkdirAll gives the host the ability to create chains of folders within the 252 // filesystem. 253 func (*ProductionDependencies) MkdirAll(s string, fm os.FileMode) error { 254 return os.MkdirAll(s, fm) 255 } 256 257 // NewLogger creates a logger that the host can use to log messages and write 258 // critical statements. 259 func (*ProductionDependencies) NewLogger(s string) (*persist.Logger, error) { 260 return persist.NewFileLogger(s) 261 } 262 263 // OpenDatabase creates a database that the host can use to interact with large 264 // volumes of persistent data. 265 func (*ProductionDependencies) OpenDatabase(m persist.Metadata, s string) (*persist.BoltDatabase, error) { 266 return persist.OpenDatabase(m, s) 267 } 268 269 // OpenFile opens a file for the contract manager. 270 func (pd *ProductionDependencies) OpenFile(s string, i int, fm os.FileMode) (File, error) { 271 if !build.DEBUG { 272 return os.OpenFile(s, i, fm) 273 } 274 275 f, err := os.OpenFile(s, i, fm) 276 if err != nil { 277 return f, err 278 } 279 280 pd.mu.Lock() 281 if pd.openFiles == nil { 282 pd.openFiles = make(map[string]int) 283 } 284 v := pd.openFiles[s] 285 pd.openFiles[s] = v + 1 286 pd.mu.Unlock() 287 return &ProductionFile{ 288 pd: pd, 289 File: f, 290 }, nil 291 } 292 293 // RandRead fills the input bytes with random data. 294 func (*ProductionDependencies) RandRead(b []byte) (int, error) { 295 return fastrand.Reader.Read(b) 296 } 297 298 // ReadFile reads a file from the filesystem. 299 func (*ProductionDependencies) ReadFile(s string) ([]byte, error) { 300 return ioutil.ReadFile(s) 301 } 302 303 // RemoveFile will remove a file from disk. 304 func (pd *ProductionDependencies) RemoveFile(s string) error { 305 if !build.DEBUG { 306 return os.Remove(s) 307 } 308 309 pd.mu.Lock() 310 if pd.openFiles == nil { 311 pd.openFiles = make(map[string]int) 312 } 313 v, exists := pd.openFiles[s] 314 pd.mu.Unlock() 315 if exists && v > 0 { 316 return errors.New("cannot remove the file, it's open somewhere else right now") 317 } 318 return os.Remove(s) 319 } 320 321 // RenameFile renames a file on disk. 322 func (pd *ProductionDependencies) RenameFile(s1 string, s2 string) error { 323 if !build.DEBUG { 324 return os.Rename(s1, s2) 325 } 326 327 pd.mu.Lock() 328 if pd.openFiles == nil { 329 pd.openFiles = make(map[string]int) 330 } 331 v1, exists1 := pd.openFiles[s1] 332 v2, exists2 := pd.openFiles[s2] 333 pd.mu.Unlock() 334 if exists1 && v1 > 0 { 335 return errors.New("cannot remove the file, it's open somewhere else right now") 336 } 337 if exists2 && v2 > 0 { 338 return errors.New("cannot remove the file, it's open somewhere else right now") 339 } 340 return os.Rename(s1, s2) 341 } 342 343 // Sleep blocks the calling thread for a certain duration. 344 func (*ProductionDependencies) Sleep(d time.Duration) { 345 time.Sleep(d) 346 } 347 348 // Symlink creates a symlink between a source and a destination file. 349 func (*ProductionDependencies) Symlink(s1, s2 string) error { 350 return os.Symlink(s1, s2) 351 } 352 353 // WriteFile writes a file to the filesystem. 354 func (*ProductionDependencies) WriteFile(s string, b []byte, fm os.FileMode) error { 355 return ioutil.WriteFile(s, b, fm) 356 } 357 358 // Resolver is an interface that allows resolving a hostname into IP 359 // addresses. 360 type Resolver interface { 361 LookupIP(string) ([]net.IP, error) 362 } 363 364 // ProductionResolver is the hostname resolver used in production builds. 365 type ProductionResolver struct{} 366 367 // LookupIP is a passthrough function to net.LookupIP. In testing builds it 368 // returns a random IP. 369 func (ProductionResolver) LookupIP(host string) ([]net.IP, error) { 370 if build.Release == "testing" { 371 rawIP := make([]byte, 16) 372 fastrand.Read(rawIP) 373 return []net.IP{net.IP(rawIP)}, nil 374 } 375 return net.LookupIP(host) 376 } 377 378 // Resolver returns the ProductionResolver. 379 func (*ProductionDependencies) Resolver() Resolver { 380 return ProductionResolver{} 381 }