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  }