zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/storage/local/driver.go (about)

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