github.com/matrixorigin/matrixone@v0.7.0/pkg/fileservice/local_fs.go (about)

     1  // Copyright 2022 Matrix Origin
     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  
    15  package fileservice
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"io"
    22  	"io/fs"
    23  	"os"
    24  	pathpkg "path"
    25  	"path/filepath"
    26  	"sort"
    27  	"strings"
    28  	"sync"
    29  
    30  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    31  	"github.com/matrixorigin/matrixone/pkg/logutil"
    32  	"go.uber.org/zap"
    33  )
    34  
    35  // LocalFS is a FileService implementation backed by local file system
    36  type LocalFS struct {
    37  	name     string
    38  	rootPath string
    39  
    40  	sync.RWMutex
    41  	dirFiles map[string]*os.File
    42  
    43  	memCache *MemCache
    44  }
    45  
    46  var _ FileService = new(LocalFS)
    47  
    48  const (
    49  	localFSSentinelFileName = ".thisisalocalfileservicedir"
    50  )
    51  
    52  func NewLocalFS(
    53  	name string,
    54  	rootPath string,
    55  	memCacheCapacity int64,
    56  ) (*LocalFS, error) {
    57  
    58  	// ensure dir
    59  	f, err := os.Open(rootPath)
    60  	if os.IsNotExist(err) {
    61  		// not exists, create
    62  		err := os.MkdirAll(rootPath, 0755)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		err = os.WriteFile(filepath.Join(rootPath, localFSSentinelFileName), nil, 0644)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  
    71  	} else if err != nil {
    72  		// stat error
    73  		return nil, err
    74  
    75  	} else {
    76  		// existed, check if a real file service dir
    77  		defer f.Close()
    78  		entries, err := f.ReadDir(1)
    79  		if len(entries) == 0 {
    80  			if errors.Is(err, io.EOF) {
    81  				// empty dir, ok
    82  			} else if err != nil {
    83  				// ReadDir error
    84  				return nil, err
    85  			}
    86  		} else {
    87  			// not empty, check sentinel file
    88  			_, err := os.Stat(filepath.Join(rootPath, localFSSentinelFileName))
    89  			if os.IsNotExist(err) {
    90  				return nil, moerr.NewInternalErrorNoCtx("%s is not a file service dir", rootPath)
    91  			} else if err != nil {
    92  				return nil, err
    93  			}
    94  		}
    95  	}
    96  
    97  	// create tmp dir
    98  	if err := os.MkdirAll(filepath.Join(rootPath, ".tmp"), 0755); err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	fs := &LocalFS{
   103  		name:     name,
   104  		rootPath: rootPath,
   105  		dirFiles: make(map[string]*os.File),
   106  	}
   107  	if memCacheCapacity > 0 {
   108  		fs.memCache = NewMemCache(memCacheCapacity)
   109  		logutil.Info("fileservice: cache initialized", zap.Any("fs-name", name), zap.Any("capacity", memCacheCapacity))
   110  	}
   111  
   112  	return fs, nil
   113  }
   114  
   115  func (l *LocalFS) Name() string {
   116  	return l.name
   117  }
   118  
   119  func (l *LocalFS) Write(ctx context.Context, vector IOVector) error {
   120  	select {
   121  	case <-ctx.Done():
   122  		return ctx.Err()
   123  	default:
   124  	}
   125  
   126  	path, err := ParsePathAtService(vector.FilePath, l.name)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	nativePath := l.toNativeFilePath(path.File)
   131  
   132  	// check existence
   133  	_, err = os.Stat(nativePath)
   134  	if err == nil {
   135  		// existed
   136  		return moerr.NewFileAlreadyExistsNoCtx(path.File)
   137  	}
   138  
   139  	return l.write(ctx, vector)
   140  }
   141  
   142  func (l *LocalFS) write(ctx context.Context, vector IOVector) error {
   143  	select {
   144  	case <-ctx.Done():
   145  		return ctx.Err()
   146  	default:
   147  	}
   148  
   149  	path, err := ParsePathAtService(vector.FilePath, l.name)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	nativePath := l.toNativeFilePath(path.File)
   154  
   155  	// sort
   156  	sort.Slice(vector.Entries, func(i, j int) bool {
   157  		return vector.Entries[i].Offset < vector.Entries[j].Offset
   158  	})
   159  
   160  	// size
   161  	var size int64
   162  	if len(vector.Entries) > 0 {
   163  		last := vector.Entries[len(vector.Entries)-1]
   164  		size = int64(last.Offset + last.Size)
   165  	}
   166  
   167  	// write
   168  	f, err := os.CreateTemp(
   169  		filepath.Join(l.rootPath, ".tmp"),
   170  		"*.tmp",
   171  	)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	fileWithChecksum := NewFileWithChecksum(f, _BlockContentSize)
   176  	n, err := io.Copy(fileWithChecksum, newIOEntriesReader(ctx, vector.Entries))
   177  	if err != nil {
   178  		return err
   179  	}
   180  	if n != size {
   181  		sizeUnknown := false
   182  		for _, entry := range vector.Entries {
   183  			if entry.Size < 0 {
   184  				sizeUnknown = true
   185  				break
   186  			}
   187  		}
   188  		if !sizeUnknown {
   189  			return moerr.NewSizeNotMatchNoCtx(path.File)
   190  		}
   191  	}
   192  	if err := f.Sync(); err != nil {
   193  		return err
   194  	}
   195  	if err := f.Close(); err != nil {
   196  		return err
   197  	}
   198  
   199  	// ensure parent dir
   200  	parentDir, _ := filepath.Split(nativePath)
   201  	err = l.ensureDir(parentDir)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	// move
   207  	if err := os.Rename(f.Name(), nativePath); err != nil {
   208  		return err
   209  	}
   210  
   211  	if err := l.syncDir(parentDir); err != nil {
   212  		return err
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  func (l *LocalFS) Read(ctx context.Context, vector *IOVector) (err error) {
   219  	select {
   220  	case <-ctx.Done():
   221  		return ctx.Err()
   222  	default:
   223  	}
   224  
   225  	if len(vector.Entries) == 0 {
   226  		return moerr.NewEmptyVectorNoCtx()
   227  	}
   228  
   229  	if l.memCache != nil {
   230  		if err := l.memCache.Read(ctx, vector); err != nil {
   231  			return err
   232  		}
   233  		defer func() {
   234  			if err != nil {
   235  				return
   236  			}
   237  			err = l.memCache.Update(ctx, vector)
   238  		}()
   239  	}
   240  
   241  	if err := l.read(ctx, vector); err != nil {
   242  		return err
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  func (l *LocalFS) read(ctx context.Context, vector *IOVector) error {
   249  	if vector.allDone() {
   250  		return nil
   251  	}
   252  
   253  	path, err := ParsePathAtService(vector.FilePath, l.name)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	nativePath := l.toNativeFilePath(path.File)
   258  
   259  	file, err := os.Open(nativePath)
   260  	if os.IsNotExist(err) {
   261  		return moerr.NewFileNotFoundNoCtx(path.File)
   262  	}
   263  	if err != nil {
   264  		return err
   265  	}
   266  	defer file.Close()
   267  
   268  	for i, entry := range vector.Entries {
   269  		if entry.Size == 0 {
   270  			return moerr.NewEmptyRangeNoCtx(path.File)
   271  		}
   272  
   273  		if entry.done {
   274  			continue
   275  		}
   276  
   277  		if entry.WriterForRead != nil {
   278  			fileWithChecksum := NewFileWithChecksum(file, _BlockContentSize)
   279  
   280  			if entry.Offset > 0 {
   281  				_, err := fileWithChecksum.Seek(int64(entry.Offset), io.SeekStart)
   282  				if err != nil {
   283  					return err
   284  				}
   285  			}
   286  			r := (io.Reader)(fileWithChecksum)
   287  			if entry.Size > 0 {
   288  				r = io.LimitReader(r, int64(entry.Size))
   289  			}
   290  
   291  			if entry.ToObject != nil {
   292  				r = io.TeeReader(r, entry.WriterForRead)
   293  				cr := &countingReader{
   294  					R: r,
   295  				}
   296  				obj, size, err := entry.ToObject(cr, nil)
   297  				if err != nil {
   298  					return err
   299  				}
   300  				vector.Entries[i].Object = obj
   301  				vector.Entries[i].ObjectSize = size
   302  				if entry.Size > 0 && cr.N != entry.Size {
   303  					return moerr.NewUnexpectedEOFNoCtx(path.File)
   304  				}
   305  
   306  			} else {
   307  				n, err := io.Copy(entry.WriterForRead, r)
   308  				if err != nil {
   309  					return err
   310  				}
   311  				if entry.Size > 0 && n != int64(entry.Size) {
   312  					return moerr.NewUnexpectedEOFNoCtx(path.File)
   313  				}
   314  			}
   315  
   316  		} else if entry.ReadCloserForRead != nil {
   317  			file, err := os.Open(nativePath)
   318  			if os.IsNotExist(err) {
   319  				return moerr.NewFileNotFoundNoCtx(path.File)
   320  			}
   321  			if err != nil {
   322  				return err
   323  			}
   324  			fileWithChecksum := NewFileWithChecksum(file, _BlockContentSize)
   325  
   326  			if entry.Offset > 0 {
   327  				_, err := fileWithChecksum.Seek(int64(entry.Offset), io.SeekStart)
   328  				if err != nil {
   329  					return err
   330  				}
   331  			}
   332  			r := (io.Reader)(fileWithChecksum)
   333  			if entry.Size > 0 {
   334  				r = io.LimitReader(r, int64(entry.Size))
   335  			}
   336  
   337  			if entry.ToObject == nil {
   338  				*entry.ReadCloserForRead = &readCloser{
   339  					r:         r,
   340  					closeFunc: file.Close,
   341  				}
   342  
   343  			} else {
   344  				buf := new(bytes.Buffer)
   345  				*entry.ReadCloserForRead = &readCloser{
   346  					r: io.TeeReader(r, buf),
   347  					closeFunc: func() error {
   348  						defer file.Close()
   349  						obj, size, err := entry.ToObject(buf, buf.Bytes())
   350  						if err != nil {
   351  							return err
   352  						}
   353  						vector.Entries[i].Object = obj
   354  						vector.Entries[i].ObjectSize = size
   355  						return nil
   356  					},
   357  				}
   358  			}
   359  
   360  		} else {
   361  			fileWithChecksum := NewFileWithChecksum(file, _BlockContentSize)
   362  
   363  			if entry.Offset > 0 {
   364  				_, err := fileWithChecksum.Seek(int64(entry.Offset), io.SeekStart)
   365  				if err != nil {
   366  					return err
   367  				}
   368  			}
   369  			r := (io.Reader)(fileWithChecksum)
   370  			if entry.Size > 0 {
   371  				r = io.LimitReader(r, int64(entry.Size))
   372  			}
   373  
   374  			if entry.Size < 0 {
   375  				data, err := io.ReadAll(r)
   376  				if err != nil {
   377  					return err
   378  				}
   379  				entry.Data = data
   380  				entry.Size = int64(len(data))
   381  
   382  			} else {
   383  				if int64(len(entry.Data)) < entry.Size {
   384  					entry.Data = make([]byte, entry.Size)
   385  				}
   386  				n, err := io.ReadFull(r, entry.Data)
   387  				if err != nil {
   388  					return err
   389  				}
   390  				if int64(n) != entry.Size {
   391  					return moerr.NewUnexpectedEOFNoCtx(path.File)
   392  				}
   393  			}
   394  
   395  			if err := entry.setObjectFromData(); err != nil {
   396  				return err
   397  			}
   398  
   399  			vector.Entries[i] = entry
   400  
   401  		}
   402  
   403  	}
   404  
   405  	return nil
   406  
   407  }
   408  
   409  func (l *LocalFS) List(ctx context.Context, dirPath string) (ret []DirEntry, err error) {
   410  	select {
   411  	case <-ctx.Done():
   412  		return nil, ctx.Err()
   413  	default:
   414  	}
   415  
   416  	path, err := ParsePathAtService(dirPath, l.name)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	nativePath := l.toNativeFilePath(path.File)
   421  
   422  	f, err := os.Open(nativePath)
   423  	if os.IsNotExist(err) {
   424  		err = nil
   425  		return
   426  	}
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  	defer f.Close()
   431  
   432  	entries, err := f.ReadDir(-1)
   433  	for _, entry := range entries {
   434  		name := entry.Name()
   435  		if strings.HasPrefix(name, ".") {
   436  			continue
   437  		}
   438  		info, err := entry.Info()
   439  		if err != nil {
   440  			return nil, err
   441  		}
   442  		fileSize := info.Size()
   443  		nBlock := ceilingDiv(fileSize, _BlockSize)
   444  		contentSize := fileSize - _ChecksumSize*nBlock
   445  
   446  		isDir, err := entryIsDir(nativePath, name, info)
   447  		if err != nil {
   448  			return nil, err
   449  		}
   450  		ret = append(ret, DirEntry{
   451  			Name:  name,
   452  			IsDir: isDir,
   453  			Size:  contentSize,
   454  		})
   455  	}
   456  
   457  	sort.Slice(ret, func(i, j int) bool {
   458  		return ret[i].Name < ret[j].Name
   459  	})
   460  
   461  	if err != nil {
   462  		return ret, err
   463  	}
   464  
   465  	return
   466  }
   467  
   468  func (l *LocalFS) StatFile(ctx context.Context, filePath string) (*DirEntry, error) {
   469  	select {
   470  	case <-ctx.Done():
   471  		return nil, ctx.Err()
   472  	default:
   473  	}
   474  
   475  	path, err := ParsePathAtService(filePath, l.name)
   476  	if err != nil {
   477  		return nil, err
   478  	}
   479  	nativePath := l.toNativeFilePath(path.File)
   480  
   481  	stat, err := os.Stat(nativePath)
   482  	if os.IsNotExist(err) {
   483  		return nil, moerr.NewFileNotFound(ctx, filePath)
   484  	}
   485  
   486  	if stat.IsDir() {
   487  		return nil, moerr.NewFileNotFound(ctx, filePath)
   488  	}
   489  
   490  	fileSize := stat.Size()
   491  	nBlock := ceilingDiv(fileSize, _BlockSize)
   492  	contentSize := fileSize - _ChecksumSize*nBlock
   493  
   494  	return &DirEntry{
   495  		Name:  pathpkg.Base(filePath),
   496  		IsDir: false,
   497  		Size:  contentSize,
   498  	}, nil
   499  }
   500  
   501  func (l *LocalFS) Delete(ctx context.Context, filePaths ...string) error {
   502  	select {
   503  	case <-ctx.Done():
   504  		return ctx.Err()
   505  	default:
   506  	}
   507  
   508  	for _, filePath := range filePaths {
   509  		if err := l.deleteSingle(ctx, filePath); err != nil {
   510  			return err
   511  		}
   512  	}
   513  	return nil
   514  }
   515  
   516  func (l *LocalFS) deleteSingle(ctx context.Context, filePath string) error {
   517  	path, err := ParsePathAtService(filePath, l.name)
   518  	if err != nil {
   519  		return err
   520  	}
   521  	nativePath := l.toNativeFilePath(path.File)
   522  
   523  	_, err = os.Stat(nativePath)
   524  	if os.IsNotExist(err) {
   525  		return moerr.NewFileNotFoundNoCtx(path.File)
   526  	}
   527  	if err != nil {
   528  		return err
   529  	}
   530  
   531  	err = os.Remove(nativePath)
   532  	if err != nil {
   533  		return err
   534  	}
   535  
   536  	parentDir, _ := filepath.Split(nativePath)
   537  	err = l.syncDir(parentDir)
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	return nil
   543  }
   544  
   545  func (l *LocalFS) ensureDir(nativePath string) error {
   546  	nativePath = filepath.Clean(nativePath)
   547  	if nativePath == "" {
   548  		return nil
   549  	}
   550  
   551  	// check existence by l.dirFiles
   552  	l.RLock()
   553  	_, ok := l.dirFiles[nativePath]
   554  	if ok {
   555  		// dir existed
   556  		l.RUnlock()
   557  		return nil
   558  	}
   559  	l.RUnlock()
   560  
   561  	// check existence by fstat
   562  	_, err := os.Stat(nativePath)
   563  	if err == nil {
   564  		// existed
   565  		return nil
   566  	}
   567  
   568  	// ensure parent
   569  	parent, _ := filepath.Split(nativePath)
   570  	if parent != nativePath {
   571  		if err := l.ensureDir(parent); err != nil {
   572  			return err
   573  		}
   574  	}
   575  
   576  	// create
   577  	if err := os.Mkdir(nativePath, 0755); err != nil {
   578  		return err
   579  	}
   580  
   581  	// sync parent dir
   582  	if err := l.syncDir(parent); err != nil {
   583  		return err
   584  	}
   585  
   586  	return nil
   587  }
   588  
   589  func (l *LocalFS) syncDir(nativePath string) error {
   590  	l.Lock()
   591  	f, ok := l.dirFiles[nativePath]
   592  	if !ok {
   593  		var err error
   594  		f, err = os.Open(nativePath)
   595  		if err != nil {
   596  			l.Unlock()
   597  			return err
   598  		}
   599  		l.dirFiles[nativePath] = f
   600  	}
   601  	l.Unlock()
   602  	if err := f.Sync(); err != nil {
   603  		return err
   604  	}
   605  	return nil
   606  }
   607  
   608  func (l *LocalFS) toNativeFilePath(filePath string) string {
   609  	return filepath.Join(l.rootPath, toOSPath(filePath))
   610  }
   611  
   612  var _ MutableFileService = new(LocalFS)
   613  
   614  func (l *LocalFS) NewMutator(filePath string) (Mutator, error) {
   615  	path, err := ParsePathAtService(filePath, l.name)
   616  	if err != nil {
   617  		return nil, err
   618  	}
   619  	nativePath := l.toNativeFilePath(path.File)
   620  	f, err := os.OpenFile(nativePath, os.O_RDWR, 0644)
   621  	if os.IsNotExist(err) {
   622  		return nil, moerr.NewFileNotFoundNoCtx(path.File)
   623  	}
   624  	return &LocalFSMutator{
   625  		osFile:           f,
   626  		fileWithChecksum: NewFileWithChecksum(f, _BlockContentSize),
   627  	}, nil
   628  }
   629  
   630  type LocalFSMutator struct {
   631  	osFile           *os.File
   632  	fileWithChecksum *FileWithChecksum[*os.File]
   633  }
   634  
   635  func (l *LocalFSMutator) Mutate(ctx context.Context, entries ...IOEntry) error {
   636  	return l.mutate(ctx, 0, entries...)
   637  }
   638  
   639  func (l *LocalFSMutator) Append(ctx context.Context, entries ...IOEntry) error {
   640  	offset, err := l.fileWithChecksum.Seek(0, io.SeekEnd)
   641  	if err != nil {
   642  		return err
   643  	}
   644  	return l.mutate(ctx, offset, entries...)
   645  }
   646  
   647  func (l *LocalFSMutator) mutate(ctx context.Context, baseOffset int64, entries ...IOEntry) error {
   648  	select {
   649  	case <-ctx.Done():
   650  		return ctx.Err()
   651  	default:
   652  	}
   653  
   654  	// write
   655  	for _, entry := range entries {
   656  
   657  		if entry.ReaderForWrite != nil {
   658  			// seek and copy
   659  			_, err := l.fileWithChecksum.Seek(entry.Offset+baseOffset, 0)
   660  			if err != nil {
   661  				return err
   662  			}
   663  			n, err := io.Copy(l.fileWithChecksum, entry.ReaderForWrite)
   664  			if err != nil {
   665  				return err
   666  			}
   667  			if int64(n) != entry.Size {
   668  				return moerr.NewSizeNotMatchNoCtx("")
   669  			}
   670  
   671  		} else {
   672  			// WriteAt
   673  			n, err := l.fileWithChecksum.WriteAt(entry.Data, int64(entry.Offset+baseOffset))
   674  			if err != nil {
   675  				return err
   676  			}
   677  			if int64(n) != entry.Size {
   678  				return moerr.NewSizeNotMatchNoCtx("")
   679  			}
   680  		}
   681  	}
   682  
   683  	return nil
   684  }
   685  
   686  func (l *LocalFSMutator) Close() error {
   687  	// sync
   688  	if err := l.osFile.Sync(); err != nil {
   689  		return err
   690  	}
   691  
   692  	// close
   693  	if err := l.osFile.Close(); err != nil {
   694  		return err
   695  	}
   696  
   697  	return nil
   698  }
   699  
   700  var _ ReplaceableFileService = new(LocalFS)
   701  
   702  func (l *LocalFS) Replace(ctx context.Context, vector IOVector) error {
   703  	return l.write(ctx, vector)
   704  }
   705  
   706  var _ CachingFileService = new(LocalFS)
   707  
   708  func (l *LocalFS) FlushCache() {
   709  	if l.memCache != nil {
   710  		l.memCache.Flush()
   711  	}
   712  }
   713  
   714  func (l *LocalFS) CacheStats() *CacheStats {
   715  	if l.memCache != nil {
   716  		return l.memCache.CacheStats()
   717  	}
   718  	return nil
   719  }
   720  
   721  func entryIsDir(path string, name string, entry fs.FileInfo) (bool, error) {
   722  	if entry.IsDir() {
   723  		return true, nil
   724  	}
   725  	if entry.Mode().Type()&fs.ModeSymlink > 0 {
   726  		stat, err := os.Stat(filepath.Join(path, name))
   727  		if err != nil {
   728  			return false, err
   729  		}
   730  		return entryIsDir(path, name, stat)
   731  	}
   732  	return false, nil
   733  }