github.com/uber/kraken@v0.1.4/lib/store/base/file_entry.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package base
    15  
    16  import (
    17  	"bytes"
    18  	"errors"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/uber/kraken/lib/store/metadata"
    27  	"github.com/uber/kraken/utils/stringset"
    28  )
    29  
    30  // FileEntry errors.
    31  var (
    32  	ErrFilePersisted = errors.New("file is persisted")
    33  	ErrInvalidName   = errors.New("invalid name")
    34  )
    35  
    36  // FileState decides what directory a file is in.
    37  // A file can only be in one state at any given time.
    38  type FileState struct {
    39  	directory string
    40  }
    41  
    42  // NewFileState creates a new FileState for directory.
    43  func NewFileState(directory string) FileState {
    44  	return FileState{directory}
    45  }
    46  
    47  // GetDirectory returns the FileState's directory.
    48  func (s FileState) GetDirectory() string {
    49  	return s.directory
    50  }
    51  
    52  // FileEntryFactory initializes FileEntry obj.
    53  type FileEntryFactory interface {
    54  	// Create creates a file entry given a state directory and a name.
    55  	// It calls GetRelativePath to generate the actual file path under given directory,
    56  	Create(name string, state FileState) (FileEntry, error)
    57  
    58  	// GetRelativePath returns the relative path for a file entry.
    59  	// The path is relative to the state directory that file entry belongs to.
    60  	// i.e. a file entry can have a relative path of 00/0e/filename under directory /var/cache/
    61  	GetRelativePath(name string) string
    62  
    63  	// ListNames lists all file entry names in state.
    64  	ListNames(state FileState) ([]string, error)
    65  }
    66  
    67  // FileEntry manages one file and its metadata.
    68  // It doesn't guarantee thread-safety; That should be handled by FileMap.
    69  type FileEntry interface {
    70  	GetState() FileState
    71  	GetName() string
    72  	GetPath() string
    73  	GetStat() (os.FileInfo, error)
    74  
    75  	Create(targetState FileState, len int64) error
    76  	Reload() error
    77  	MoveFrom(targetState FileState, sourcePath string) error
    78  	Move(targetState FileState) error
    79  	LinkTo(targetPath string) error
    80  	Delete() error
    81  
    82  	GetReader() (FileReader, error)
    83  	GetReadWriter() (FileReadWriter, error)
    84  
    85  	AddMetadata(md metadata.Metadata) error
    86  
    87  	GetMetadata(md metadata.Metadata) error
    88  	SetMetadata(md metadata.Metadata) (bool, error)
    89  	SetMetadataAt(md metadata.Metadata, b []byte, offset int64) (updated bool, err error)
    90  	GetOrSetMetadata(md metadata.Metadata) error
    91  	DeleteMetadata(md metadata.Metadata) error
    92  
    93  	RangeMetadata(f func(md metadata.Metadata) error) error
    94  }
    95  
    96  var _ FileEntryFactory = (*localFileEntryFactory)(nil)
    97  var _ FileEntryFactory = (*casFileEntryFactory)(nil)
    98  var _ FileEntry = (*localFileEntry)(nil)
    99  
   100  // localFileEntryFactory initializes localFileEntry obj.
   101  type localFileEntryFactory struct{}
   102  
   103  // NewLocalFileEntryFactory is the constructor for localFileEntryFactory.
   104  func NewLocalFileEntryFactory() FileEntryFactory {
   105  	return &localFileEntryFactory{}
   106  }
   107  
   108  // Create initializes and returns a FileEntry object.
   109  func (f *localFileEntryFactory) Create(name string, state FileState) (FileEntry, error) {
   110  	if name != filepath.Clean(name) {
   111  		return nil, ErrInvalidName
   112  	}
   113  	if strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") || strings.HasPrefix(name, "../") {
   114  		return nil, ErrInvalidName
   115  	}
   116  	return newLocalFileEntry(state, name, f.GetRelativePath(name)), nil
   117  }
   118  
   119  // GetRelativePath returns name because file entries are stored flat under state directory.
   120  func (f *localFileEntryFactory) GetRelativePath(name string) string {
   121  	return filepath.Join(name, DefaultDataFileName)
   122  }
   123  
   124  // ListNames returns the names of all entries in state's directory.
   125  func (f *localFileEntryFactory) ListNames(state FileState) ([]string, error) {
   126  	var names []string
   127  
   128  	var readNames func(string) error
   129  	readNames = func(dir string) error {
   130  		infos, err := ioutil.ReadDir(dir)
   131  		if err != nil {
   132  			if os.IsNotExist(err) {
   133  				return nil
   134  			}
   135  			return err
   136  		}
   137  		for _, info := range infos {
   138  			if info.IsDir() {
   139  				if err := readNames(filepath.Join(dir, info.Name())); err != nil {
   140  					return err
   141  				}
   142  				continue
   143  			}
   144  			if info.Name() == DefaultDataFileName {
   145  				name, err := filepath.Rel(state.GetDirectory(), dir)
   146  				if err != nil {
   147  					return err
   148  				}
   149  				names = append(names, name)
   150  			}
   151  		}
   152  		return nil
   153  	}
   154  
   155  	err := readNames(state.GetDirectory())
   156  
   157  	return names, err
   158  }
   159  
   160  // casFileEntryFactory initializes localFileEntry obj.
   161  // It uses the first few bytes of file digest (which is also used as file name) as shard ID.
   162  // For every byte, one more level of directories will be created.
   163  type casFileEntryFactory struct{}
   164  
   165  // NewCASFileEntryFactory is the constructor for casFileEntryFactory.
   166  func NewCASFileEntryFactory() FileEntryFactory {
   167  	return &casFileEntryFactory{}
   168  }
   169  
   170  // Create initializes and returns a FileEntry object.
   171  // TODO: verify name.
   172  func (f *casFileEntryFactory) Create(name string, state FileState) (FileEntry, error) {
   173  	return newLocalFileEntry(state, name, f.GetRelativePath(name)), nil
   174  }
   175  
   176  // GetRelativePath returns content-addressable file path under state directory.
   177  // Example:
   178  // name = 07123e1f482356c415f684407a3b8723e10b2cbbc0b8fcd6282c49d37c9c1abc
   179  // shardIDLength = 2
   180  // relative path = 07/12/07123e1f482356c415f684407a3b8723e10b2cbbc0b8fcd6282c49d37c9c1abc
   181  func (f *casFileEntryFactory) GetRelativePath(name string) string {
   182  	filePath := ""
   183  	for i := 0; i < int(DefaultShardIDLength) && i < len(name)/2; i++ {
   184  		// (1 byte = 2 char of file name assumming file name is in HEX)
   185  		dirName := name[i*2 : i*2+2]
   186  		filePath = filepath.Join(filePath, dirName)
   187  	}
   188  
   189  	return filepath.Join(filePath, name, DefaultDataFileName)
   190  }
   191  
   192  // ListNames returns the names of all entries within the shards of state.
   193  func (f *casFileEntryFactory) ListNames(state FileState) ([]string, error) {
   194  	var names []string
   195  
   196  	var readNames func(string, int) error
   197  	readNames = func(dir string, depth int) error {
   198  		infos, err := ioutil.ReadDir(dir)
   199  		if err != nil {
   200  			return err
   201  		}
   202  		for _, info := range infos {
   203  			if depth == 0 {
   204  				names = append(names, info.Name())
   205  			} else {
   206  				if !info.IsDir() {
   207  					continue
   208  				}
   209  				if err := readNames(filepath.Join(dir, info.Name()), depth-1); err != nil {
   210  					return err
   211  				}
   212  			}
   213  		}
   214  		return nil
   215  	}
   216  
   217  	err := readNames(state.GetDirectory(), DefaultShardIDLength)
   218  
   219  	return names, err
   220  }
   221  
   222  // localFileEntry implements FileEntry interface, handles IO operations for one file on local disk.
   223  type localFileEntry struct {
   224  	sync.RWMutex
   225  
   226  	state            FileState
   227  	name             string
   228  	relativeDataPath string        // Relative path to data file.
   229  	metadata         stringset.Set // Metadata is identified by suffix.
   230  }
   231  
   232  func newLocalFileEntry(
   233  	state FileState,
   234  	name string,
   235  	relativeDataPath string,
   236  ) *localFileEntry {
   237  	return &localFileEntry{
   238  		state:            state,
   239  		name:             name,
   240  		relativeDataPath: relativeDataPath,
   241  		metadata:         make(stringset.Set),
   242  	}
   243  }
   244  
   245  // GetState returns current state of the file.
   246  func (entry *localFileEntry) GetState() FileState {
   247  	return entry.state
   248  }
   249  
   250  // GetName returns name of the file.
   251  func (entry *localFileEntry) GetName() string {
   252  	return entry.name
   253  }
   254  
   255  // GetPath returns current path of the file.
   256  func (entry *localFileEntry) GetPath() string {
   257  	return filepath.Join(entry.state.GetDirectory(), entry.relativeDataPath)
   258  }
   259  
   260  // GetStat returns a FileInfo describing the named file.
   261  func (entry *localFileEntry) GetStat() (os.FileInfo, error) {
   262  	return os.Stat(entry.GetPath())
   263  }
   264  
   265  // Create creates a file on disk.
   266  func (entry *localFileEntry) Create(targetState FileState, size int64) error {
   267  	if entry.state != targetState {
   268  		return &FileStateError{
   269  			Op:    "Create",
   270  			Name:  entry.name,
   271  			State: entry.state,
   272  			Msg:   fmt.Sprintf("localFileEntry obj has state: %v", entry.state),
   273  		}
   274  	}
   275  
   276  	// Verify if file was already created.
   277  	targetPath := entry.GetPath()
   278  	if _, err := os.Stat(targetPath); err == nil {
   279  		return os.ErrExist
   280  	}
   281  
   282  	// Create dir.
   283  	if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil {
   284  		return err
   285  	}
   286  
   287  	// Create file.
   288  	f, err := os.Create(targetPath)
   289  	if err != nil {
   290  		return err
   291  	}
   292  	defer f.Close()
   293  
   294  	// Change size.
   295  	err = f.Truncate(size)
   296  	if err != nil {
   297  		// Try to delete file.
   298  		os.RemoveAll(filepath.Dir(targetPath))
   299  		return err
   300  	}
   301  
   302  	return f.Close()
   303  }
   304  
   305  // Reload tries to reload a file that doesn't exist in memory from disk.
   306  func (entry *localFileEntry) Reload() error {
   307  	// Verify the file is still on disk.
   308  	if _, err := os.Stat(entry.GetPath()); err != nil {
   309  		// Return os.ErrNotExist.
   310  		return err
   311  	}
   312  
   313  	// Load metadata.
   314  	files, err := ioutil.ReadDir(filepath.Dir(entry.GetPath()))
   315  	if err != nil {
   316  		return err
   317  	}
   318  	for _, currFile := range files {
   319  		// Glob could return the data file itself, and directories.
   320  		// Verify it's actually a metadata file.
   321  		if currFile.Name() != DefaultDataFileName {
   322  			md := metadata.CreateFromSuffix(currFile.Name())
   323  			if md != nil {
   324  				// Add metadata
   325  				entry.AddMetadata(md)
   326  			}
   327  		}
   328  	}
   329  	return nil
   330  }
   331  
   332  // MoveFrom moves an unmanaged file in.
   333  func (entry *localFileEntry) MoveFrom(targetState FileState, sourcePath string) error {
   334  	if entry.state != targetState {
   335  		return &FileStateError{
   336  			Op:    "MoveFrom",
   337  			Name:  entry.name,
   338  			State: entry.state,
   339  			Msg:   fmt.Sprintf("localFileEntry obj has state: %v", entry.state),
   340  		}
   341  	}
   342  
   343  	// Verify if file was already created.
   344  	targetPath := entry.GetPath()
   345  	if _, err := os.Stat(targetPath); err == nil {
   346  		return os.ErrExist
   347  	}
   348  
   349  	// Verify the source file exists.
   350  	if _, err := os.Stat(sourcePath); err != nil {
   351  		// Return os.ErrNotExist.
   352  		return err
   353  	}
   354  
   355  	// Create dir.
   356  	if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil {
   357  		return err
   358  	}
   359  
   360  	// Move data.
   361  	return os.Rename(sourcePath, targetPath)
   362  }
   363  
   364  // Move moves file to target dir under the same name, moves all metadata that's `movable`, and
   365  // updates state in memory.
   366  // If for any reason the target path already exists, it will be overwritten.
   367  func (entry *localFileEntry) Move(targetState FileState) error {
   368  	sourcePath := entry.GetPath()
   369  	targetPath := filepath.Join(targetState.GetDirectory(), entry.relativeDataPath)
   370  	if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil {
   371  		return err
   372  	}
   373  
   374  	// Get file stats.
   375  	if _, err := os.Stat(sourcePath); err != nil {
   376  		// Return os.ErrNotExist.
   377  		return err
   378  	}
   379  
   380  	// Copy metadata first.
   381  	performCopy := func(md metadata.Metadata) error {
   382  		if md.Movable() {
   383  			sourceMetadataPath := entry.getMetadataPath(md)
   384  			targetMetadataPath := filepath.Join(filepath.Dir(targetPath), md.GetSuffix())
   385  			bytes, err := ioutil.ReadFile(sourceMetadataPath)
   386  			if err != nil {
   387  				return err
   388  			}
   389  			if _, err := compareAndWriteFile(targetMetadataPath, bytes); err != nil {
   390  				return err
   391  			}
   392  		}
   393  		return nil
   394  	}
   395  	if err := entry.RangeMetadata(performCopy); err != nil {
   396  		return err
   397  	}
   398  
   399  	// Move data. This could be a slow operation if source and target are not on the same FS.
   400  	if err := os.Rename(sourcePath, targetPath); err != nil {
   401  		return err
   402  	}
   403  
   404  	// Update parent dir in memory.
   405  	entry.state = targetState
   406  
   407  	// Delete source dir.
   408  	return os.RemoveAll(filepath.Dir(sourcePath))
   409  }
   410  
   411  // LinkTo creates a hardlink to an unmanaged path.
   412  func (entry *localFileEntry) LinkTo(targetPath string) error {
   413  	// Create dir.
   414  	if err := os.MkdirAll(filepath.Dir(targetPath), DefaultDirPermission); err != nil {
   415  		return err
   416  	}
   417  
   418  	// Move data.
   419  	return os.Link(entry.GetPath(), targetPath)
   420  }
   421  
   422  // Delete removes file and all of its metedata files from disk. If persist
   423  // metadata is present and true, delete returns ErrFilePersisted.
   424  func (entry *localFileEntry) Delete() error {
   425  	var persist metadata.Persist
   426  	if err := entry.GetMetadata(&persist); err != nil {
   427  		if !os.IsNotExist(err) {
   428  			return fmt.Errorf("get persist metadata: %s", err)
   429  		}
   430  	} else {
   431  		if persist.Value {
   432  			return ErrFilePersisted
   433  		}
   434  	}
   435  
   436  	// Remove files.
   437  	return os.RemoveAll(filepath.Dir(entry.GetPath()))
   438  }
   439  
   440  // GetReader returns a FileReader object for read operations.
   441  func (entry *localFileEntry) GetReader() (FileReader, error) {
   442  	f, err := os.OpenFile(entry.GetPath(), os.O_RDONLY, 0775)
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  
   447  	reader := &localFileReadWriter{
   448  		entry:      entry,
   449  		descriptor: f,
   450  	}
   451  	return reader, nil
   452  }
   453  
   454  // GetReadWriter returns a FileReadWriter object for read/write operations.
   455  func (entry *localFileEntry) GetReadWriter() (FileReadWriter, error) {
   456  	f, err := os.OpenFile(entry.GetPath(), os.O_RDWR, 0775)
   457  	if err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	readWriter := &localFileReadWriter{
   462  		entry:      entry,
   463  		descriptor: f,
   464  	}
   465  	return readWriter, nil
   466  }
   467  
   468  func (entry *localFileEntry) getMetadataPath(md metadata.Metadata) string {
   469  	return filepath.Join(filepath.Dir(entry.GetPath()), md.GetSuffix())
   470  }
   471  
   472  // AddMetadata adds a new metadata type to metadata. This is primirily used during reload.
   473  func (entry *localFileEntry) AddMetadata(md metadata.Metadata) error {
   474  	filePath := entry.getMetadataPath(md)
   475  
   476  	// Check existence.
   477  	if _, err := os.Stat(filePath); err != nil {
   478  		return err
   479  	}
   480  	entry.metadata.Add(md.GetSuffix())
   481  	return nil
   482  }
   483  
   484  // GetMetadata reads and unmarshals metadata into md.
   485  func (entry *localFileEntry) GetMetadata(md metadata.Metadata) error {
   486  	filePath := entry.getMetadataPath(md)
   487  	b, err := ioutil.ReadFile(filePath)
   488  	if err != nil {
   489  		return err
   490  	}
   491  	return md.Deserialize(b)
   492  }
   493  
   494  // SetMetadata updates metadata and returns true only if the file is updated correctly.
   495  // It returns false if error happened or file already contains desired content.
   496  func (entry *localFileEntry) SetMetadata(md metadata.Metadata) (bool, error) {
   497  	filePath := entry.getMetadataPath(md)
   498  	b, err := md.Serialize()
   499  	if err != nil {
   500  		return false, fmt.Errorf("marshal metadata: %s", err)
   501  	}
   502  	updated, err := compareAndWriteFile(filePath, b)
   503  	if err == nil {
   504  		entry.metadata.Add(md.GetSuffix())
   505  	}
   506  	return updated, err
   507  }
   508  
   509  // SetMetadataAt overwrites a single byte of metadata. Returns true if the byte
   510  // was overwritten.
   511  func (entry *localFileEntry) SetMetadataAt(
   512  	md metadata.Metadata, b []byte, offset int64) (updated bool, err error) {
   513  
   514  	filePath := entry.getMetadataPath(md)
   515  	f, err := os.OpenFile(filePath, os.O_RDWR, 0775)
   516  	if err != nil {
   517  		return false, err
   518  	}
   519  	defer f.Close()
   520  
   521  	prev := make([]byte, len(b))
   522  	if _, err := f.ReadAt(prev, offset); err != nil {
   523  		return false, err
   524  	}
   525  	if bytes.Compare(prev, b) == 0 {
   526  		return false, nil
   527  	}
   528  	if _, err := f.WriteAt(b, offset); err != nil {
   529  		return false, err
   530  	}
   531  	return true, nil
   532  }
   533  
   534  // GetOrSetMetadata writes b under metadata md if md has not been initialized yet.
   535  // If the given metadata is not initialized, md is overwritten.
   536  func (entry *localFileEntry) GetOrSetMetadata(md metadata.Metadata) error {
   537  	if entry.metadata.Has(md.GetSuffix()) {
   538  		return entry.GetMetadata(md)
   539  	}
   540  	b, err := md.Serialize()
   541  	if err != nil {
   542  		return fmt.Errorf("marshal metadata: %s", err)
   543  	}
   544  	filePath := filepath.Join(filepath.Dir(entry.GetPath()), md.GetSuffix())
   545  	if _, err := compareAndWriteFile(filePath, b); err != nil {
   546  		return err
   547  	}
   548  	entry.metadata.Add(md.GetSuffix())
   549  	return nil
   550  }
   551  
   552  // DeleteMetadata deletes metadata of the specified type.
   553  func (entry *localFileEntry) DeleteMetadata(md metadata.Metadata) error {
   554  	filePath := entry.getMetadataPath(md)
   555  
   556  	// Remove from map no matter if the actual metadata file is removed from disk.
   557  	defer entry.metadata.Remove(md.GetSuffix())
   558  
   559  	return os.RemoveAll(filePath)
   560  }
   561  
   562  // RangeMetadata loops through all metadata and applies function f, until an error happens.
   563  func (entry *localFileEntry) RangeMetadata(f func(md metadata.Metadata) error) error {
   564  	for suffix := range entry.metadata {
   565  		md := metadata.CreateFromSuffix(suffix)
   566  		if md == nil {
   567  			return fmt.Errorf("cannot create metadata from suffix %s", suffix)
   568  		}
   569  		if err := f(md); err != nil {
   570  			return err
   571  		}
   572  	}
   573  	return nil
   574  }
   575  
   576  // compareAndWriteFile updates file with given bytes and returns true only if the file is updated
   577  // correctly.
   578  // It returns false if error happened or file already contains desired content.
   579  func compareAndWriteFile(filePath string, b []byte) (bool, error) {
   580  	// Check existence.
   581  	fs, err := os.Stat(filePath)
   582  	if err != nil && !os.IsNotExist(err) {
   583  		return false, err
   584  	}
   585  
   586  	if os.IsNotExist(err) {
   587  		if err := os.MkdirAll(filepath.Dir(filePath), 0775); err != nil {
   588  			return false, err
   589  		}
   590  
   591  		if err := ioutil.WriteFile(filePath, b, 0775); err != nil {
   592  			return false, err
   593  		}
   594  		return true, nil
   595  	}
   596  
   597  	f, err := os.OpenFile(filePath, os.O_RDWR, 0775)
   598  	if err != nil {
   599  		return false, err
   600  	}
   601  	defer f.Close()
   602  
   603  	// Compare with existing data, overwrite if different.
   604  	buf := make([]byte, int(fs.Size()))
   605  	if _, err := f.Read(buf); err != nil {
   606  		return false, err
   607  	}
   608  	if bytes.Compare(buf, b) == 0 {
   609  		return false, nil
   610  	}
   611  
   612  	if len(buf) != len(b) {
   613  		if err := f.Truncate(int64(len(b))); err != nil {
   614  			return false, err
   615  		}
   616  	}
   617  
   618  	if _, err := f.WriteAt(b, 0); err != nil {
   619  		return false, err
   620  	}
   621  	return true, nil
   622  }