zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/storage/local/driver.go (about)

     1  package local
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"io"
     8  	"io/fs"
     9  	"os"
    10  	"path"
    11  	"sort"
    12  	"syscall"
    13  	"time"
    14  	"unicode/utf8"
    15  
    16  	storagedriver "github.com/docker/distribution/registry/storage/driver"
    17  
    18  	zerr "zotregistry.io/zot/errors"
    19  	storageConstants "zotregistry.io/zot/pkg/storage/constants"
    20  	"zotregistry.io/zot/pkg/test/inject"
    21  )
    22  
    23  type Driver struct {
    24  	commit bool
    25  }
    26  
    27  func New(commit bool) *Driver {
    28  	return &Driver{commit: commit}
    29  }
    30  
    31  func (driver *Driver) Name() string {
    32  	return storageConstants.LocalStorageDriverName
    33  }
    34  
    35  func (driver *Driver) EnsureDir(path string) error {
    36  	err := os.MkdirAll(path, storageConstants.DefaultDirPerms)
    37  
    38  	return driver.formatErr(err)
    39  }
    40  
    41  func (driver *Driver) DirExists(path string) bool {
    42  	if !utf8.ValidString(path) {
    43  		return false
    44  	}
    45  
    46  	fileInfo, err := os.Stat(path)
    47  	if err != nil {
    48  		if e, ok := err.(*fs.PathError); ok && errors.Is(e.Err, syscall.ENAMETOOLONG) || //nolint: errorlint
    49  			errors.Is(e.Err, syscall.EINVAL) {
    50  			return false
    51  		}
    52  	}
    53  
    54  	if err != nil && os.IsNotExist(err) {
    55  		return false
    56  	}
    57  
    58  	if !fileInfo.IsDir() {
    59  		return false
    60  	}
    61  
    62  	return true
    63  }
    64  
    65  func (driver *Driver) Reader(path string, offset int64) (io.ReadCloser, error) {
    66  	file, err := os.OpenFile(path, os.O_RDONLY, storageConstants.DefaultFilePerms)
    67  	if err != nil {
    68  		if os.IsNotExist(err) {
    69  			return nil, storagedriver.PathNotFoundError{Path: path}
    70  		}
    71  
    72  		return nil, driver.formatErr(err)
    73  	}
    74  
    75  	seekPos, err := file.Seek(offset, io.SeekStart)
    76  	if err != nil {
    77  		file.Close()
    78  
    79  		return nil, driver.formatErr(err)
    80  	} else if seekPos < offset {
    81  		file.Close()
    82  
    83  		return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset}
    84  	}
    85  
    86  	return file, nil
    87  }
    88  
    89  func (driver *Driver) ReadFile(path string) ([]byte, error) {
    90  	reader, err := driver.Reader(path, 0)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	defer reader.Close()
    96  
    97  	buf, err := io.ReadAll(reader)
    98  	if err != nil {
    99  		return nil, driver.formatErr(err)
   100  	}
   101  
   102  	return buf, nil
   103  }
   104  
   105  func (driver *Driver) Delete(path string) error {
   106  	_, err := os.Stat(path)
   107  	if err != nil && !os.IsNotExist(err) {
   108  		return driver.formatErr(err)
   109  	} else if err != nil {
   110  		return storagedriver.PathNotFoundError{Path: path}
   111  	}
   112  
   113  	return os.RemoveAll(path)
   114  }
   115  
   116  func (driver *Driver) Stat(path string) (storagedriver.FileInfo, error) {
   117  	fi, err := os.Stat(path) //nolint: varnamelen
   118  	if err != nil {
   119  		if os.IsNotExist(err) {
   120  			return nil, storagedriver.PathNotFoundError{Path: path}
   121  		}
   122  
   123  		return nil, driver.formatErr(err)
   124  	}
   125  
   126  	return fileInfo{
   127  		path:     path,
   128  		FileInfo: fi,
   129  	}, nil
   130  }
   131  
   132  func (driver *Driver) Writer(filepath string, append bool) (storagedriver.FileWriter, error) { //nolint:predeclared
   133  	if append {
   134  		_, err := os.Stat(filepath)
   135  		if err != nil {
   136  			if os.IsNotExist(err) {
   137  				return nil, storagedriver.PathNotFoundError{Path: filepath}
   138  			}
   139  
   140  			return nil, driver.formatErr(err)
   141  		}
   142  	}
   143  
   144  	parentDir := path.Dir(filepath)
   145  	if err := os.MkdirAll(parentDir, storageConstants.DefaultDirPerms); err != nil {
   146  		return nil, driver.formatErr(err)
   147  	}
   148  
   149  	file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, storageConstants.DefaultFilePerms)
   150  	if err != nil {
   151  		return nil, driver.formatErr(err)
   152  	}
   153  
   154  	var offset int64
   155  
   156  	if !append {
   157  		err := file.Truncate(0)
   158  		if err != nil {
   159  			file.Close()
   160  
   161  			return nil, driver.formatErr(err)
   162  		}
   163  	} else {
   164  		n, err := file.Seek(0, io.SeekEnd) //nolint: varnamelen
   165  		if err != nil {
   166  			file.Close()
   167  
   168  			return nil, driver.formatErr(err)
   169  		}
   170  		offset = n
   171  	}
   172  
   173  	return newFileWriter(file, offset, driver.commit), nil
   174  }
   175  
   176  func (driver *Driver) WriteFile(filepath string, content []byte) (int, error) {
   177  	writer, err := driver.Writer(filepath, false)
   178  	if err != nil {
   179  		return -1, err
   180  	}
   181  
   182  	nbytes, err := io.Copy(writer, bytes.NewReader(content))
   183  	if err != nil {
   184  		_ = writer.Cancel()
   185  
   186  		return -1, driver.formatErr(err)
   187  	}
   188  
   189  	return int(nbytes), writer.Close()
   190  }
   191  
   192  func (driver *Driver) Walk(path string, walkFn storagedriver.WalkFn) error {
   193  	children, err := driver.List(path)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	sort.Stable(sort.StringSlice(children))
   199  
   200  	for _, child := range children {
   201  		// Calling driver.Stat for every entry is quite
   202  		// expensive when running against backends with a slow Stat
   203  		// implementation, such as s3. This is very likely a serious
   204  		// performance bottleneck.
   205  		fileInfo, err := driver.Stat(child)
   206  		if err != nil {
   207  			switch errors.As(err, &storagedriver.PathNotFoundError{}) {
   208  			case true:
   209  				// repository was removed in between listing and enumeration. Ignore it.
   210  				continue
   211  			default:
   212  				return err
   213  			}
   214  		}
   215  		err = walkFn(fileInfo)
   216  		//nolint: gocritic
   217  		if err == nil && fileInfo.IsDir() {
   218  			if err := driver.Walk(child, walkFn); err != nil {
   219  				return err
   220  			}
   221  		} else if errors.Is(err, storagedriver.ErrSkipDir) {
   222  			// Stop iteration if it's a file, otherwise noop if it's a directory
   223  			if !fileInfo.IsDir() {
   224  				return nil
   225  			}
   226  		} else if err != nil {
   227  			return driver.formatErr(err)
   228  		}
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func (driver *Driver) List(fullpath string) ([]string, error) {
   235  	dir, err := os.Open(fullpath)
   236  	if err != nil {
   237  		if os.IsNotExist(err) {
   238  			return nil, storagedriver.PathNotFoundError{Path: fullpath}
   239  		}
   240  
   241  		return nil, driver.formatErr(err)
   242  	}
   243  
   244  	defer dir.Close()
   245  
   246  	fileNames, err := dir.Readdirnames(0)
   247  	if err != nil {
   248  		return nil, driver.formatErr(err)
   249  	}
   250  
   251  	keys := make([]string, 0, len(fileNames))
   252  	for _, fileName := range fileNames {
   253  		keys = append(keys, path.Join(fullpath, fileName))
   254  	}
   255  
   256  	return keys, nil
   257  }
   258  
   259  func (driver *Driver) Move(sourcePath string, destPath string) error {
   260  	if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
   261  		return storagedriver.PathNotFoundError{Path: sourcePath}
   262  	}
   263  
   264  	if err := os.MkdirAll(path.Dir(destPath), storageConstants.DefaultDirPerms); err != nil {
   265  		return driver.formatErr(err)
   266  	}
   267  
   268  	return driver.formatErr(os.Rename(sourcePath, destPath))
   269  }
   270  
   271  func (driver *Driver) SameFile(path1, path2 string) bool {
   272  	file1, err := os.Stat(path1)
   273  	if err != nil {
   274  		return false
   275  	}
   276  
   277  	file2, err := os.Stat(path2)
   278  	if err != nil {
   279  		return false
   280  	}
   281  
   282  	return os.SameFile(file1, file2)
   283  }
   284  
   285  func (driver *Driver) Link(src, dest string) error {
   286  	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
   287  		return err
   288  	}
   289  
   290  	if err := os.Link(src, dest); err != nil {
   291  		return driver.formatErr(err)
   292  	}
   293  
   294  	/* also update the modtime, so that gc won't remove recently linked blobs
   295  	otherwise ifBlobOlderThan(gcDelay) will return the modtime of the inode */
   296  	currentTime := time.Now() //nolint: gosmopolitan
   297  	if err := os.Chtimes(dest, currentTime, currentTime); err != nil {
   298  		return driver.formatErr(err)
   299  	}
   300  
   301  	return nil
   302  }
   303  
   304  func (driver *Driver) formatErr(err error) error {
   305  	switch actual := err.(type) { //nolint: errorlint
   306  	case nil:
   307  		return nil
   308  	case storagedriver.PathNotFoundError:
   309  		actual.DriverName = driver.Name()
   310  
   311  		return actual
   312  	case storagedriver.InvalidPathError:
   313  		actual.DriverName = driver.Name()
   314  
   315  		return actual
   316  	case storagedriver.InvalidOffsetError:
   317  		actual.DriverName = driver.Name()
   318  
   319  		return actual
   320  	default:
   321  		storageError := storagedriver.Error{
   322  			DriverName: driver.Name(),
   323  			Enclosed:   err,
   324  		}
   325  
   326  		return storageError
   327  	}
   328  }
   329  
   330  type fileInfo struct {
   331  	os.FileInfo
   332  	path string
   333  }
   334  
   335  // asserts fileInfo implements storagedriver.FileInfo.
   336  var _ storagedriver.FileInfo = fileInfo{}
   337  
   338  // Path provides the full path of the target of this file info.
   339  func (fi fileInfo) Path() string {
   340  	return fi.path
   341  }
   342  
   343  // Size returns current length in bytes of the file. The return value can
   344  // be used to write to the end of the file at path. The value is
   345  // meaningless if IsDir returns true.
   346  func (fi fileInfo) Size() int64 {
   347  	if fi.IsDir() {
   348  		return 0
   349  	}
   350  
   351  	return fi.FileInfo.Size()
   352  }
   353  
   354  // ModTime returns the modification time for the file. For backends that
   355  // don't have a modification time, the creation time should be returned.
   356  func (fi fileInfo) ModTime() time.Time {
   357  	return fi.FileInfo.ModTime()
   358  }
   359  
   360  // IsDir returns true if the path is a directory.
   361  func (fi fileInfo) IsDir() bool {
   362  	return fi.FileInfo.IsDir()
   363  }
   364  
   365  type fileWriter struct {
   366  	file      *os.File
   367  	size      int64
   368  	bw        *bufio.Writer
   369  	closed    bool
   370  	committed bool
   371  	cancelled bool
   372  	commit    bool
   373  }
   374  
   375  func newFileWriter(file *os.File, size int64, commit bool) *fileWriter {
   376  	return &fileWriter{
   377  		file:   file,
   378  		size:   size,
   379  		commit: commit,
   380  		bw:     bufio.NewWriter(file),
   381  	}
   382  }
   383  
   384  func (fw *fileWriter) Write(buf []byte) (int, error) {
   385  	//nolint: gocritic
   386  	if fw.closed {
   387  		return 0, zerr.ErrFileAlreadyClosed
   388  	} else if fw.committed {
   389  		return 0, zerr.ErrFileAlreadyCommitted
   390  	} else if fw.cancelled {
   391  		return 0, zerr.ErrFileAlreadyCancelled
   392  	}
   393  
   394  	n, err := fw.bw.Write(buf)
   395  	fw.size += int64(n)
   396  
   397  	return n, err
   398  }
   399  
   400  func (fw *fileWriter) Size() int64 {
   401  	return fw.size
   402  }
   403  
   404  func (fw *fileWriter) Close() error {
   405  	if fw.closed {
   406  		return zerr.ErrFileAlreadyClosed
   407  	}
   408  
   409  	if err := fw.bw.Flush(); err != nil {
   410  		return err
   411  	}
   412  
   413  	if fw.commit {
   414  		if err := inject.Error(fw.file.Sync()); err != nil {
   415  			return err
   416  		}
   417  	}
   418  
   419  	if err := inject.Error(fw.file.Close()); err != nil {
   420  		return err
   421  	}
   422  
   423  	fw.closed = true
   424  
   425  	return nil
   426  }
   427  
   428  func (fw *fileWriter) Cancel() error {
   429  	if fw.closed {
   430  		return zerr.ErrFileAlreadyClosed
   431  	}
   432  
   433  	fw.cancelled = true
   434  	fw.file.Close()
   435  
   436  	return os.Remove(fw.file.Name())
   437  }
   438  
   439  func (fw *fileWriter) Commit() error {
   440  	//nolint: gocritic
   441  	if fw.closed {
   442  		return zerr.ErrFileAlreadyClosed
   443  	} else if fw.committed {
   444  		return zerr.ErrFileAlreadyCommitted
   445  	} else if fw.cancelled {
   446  		return zerr.ErrFileAlreadyCancelled
   447  	}
   448  
   449  	if err := fw.bw.Flush(); err != nil {
   450  		return err
   451  	}
   452  
   453  	if fw.commit {
   454  		if err := fw.file.Sync(); err != nil {
   455  			return err
   456  		}
   457  	}
   458  
   459  	fw.committed = true
   460  
   461  	return nil
   462  }
   463  
   464  func ValidateHardLink(rootDir string) error {
   465  	if err := os.MkdirAll(rootDir, storageConstants.DefaultDirPerms); err != nil {
   466  		return err
   467  	}
   468  
   469  	err := os.WriteFile(path.Join(rootDir, "hardlinkcheck.txt"),
   470  		[]byte("check whether hardlinks work on filesystem"), storageConstants.DefaultFilePerms)
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	err = os.Link(path.Join(rootDir, "hardlinkcheck.txt"), path.Join(rootDir, "duphardlinkcheck.txt"))
   476  	if err != nil {
   477  		// Remove hardlinkcheck.txt if hardlink fails
   478  		zerr := os.RemoveAll(path.Join(rootDir, "hardlinkcheck.txt"))
   479  		if zerr != nil {
   480  			return zerr
   481  		}
   482  
   483  		return err
   484  	}
   485  
   486  	err = os.RemoveAll(path.Join(rootDir, "hardlinkcheck.txt"))
   487  	if err != nil {
   488  		return err
   489  	}
   490  
   491  	return os.RemoveAll(path.Join(rootDir, "duphardlinkcheck.txt"))
   492  }