storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/fs-v1-helpers.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package cmd
    18  
    19  import (
    20  	"context"
    21  	"io"
    22  	"os"
    23  	pathutil "path"
    24  	"runtime"
    25  	"strings"
    26  
    27  	"storj.io/minio/cmd/logger"
    28  	"storj.io/minio/pkg/lock"
    29  )
    30  
    31  // Removes only the file at given path does not remove
    32  // any parent directories, handles long paths for
    33  // windows automatically.
    34  func fsRemoveFile(ctx context.Context, filePath string) (err error) {
    35  	if filePath == "" {
    36  		logger.LogIf(ctx, errInvalidArgument)
    37  		return errInvalidArgument
    38  	}
    39  
    40  	if err = checkPathLength(filePath); err != nil {
    41  		logger.LogIf(ctx, err)
    42  		return err
    43  	}
    44  
    45  	if err = os.Remove(filePath); err != nil {
    46  		if err = osErrToFileErr(err); err != errFileNotFound {
    47  			logger.LogIf(ctx, err)
    48  		}
    49  	}
    50  
    51  	return err
    52  }
    53  
    54  // Removes all files and folders at a given path, handles
    55  // long paths for windows automatically.
    56  func fsRemoveAll(ctx context.Context, dirPath string) (err error) {
    57  	if dirPath == "" {
    58  		logger.LogIf(ctx, errInvalidArgument)
    59  		return errInvalidArgument
    60  	}
    61  
    62  	if err = checkPathLength(dirPath); err != nil {
    63  		logger.LogIf(ctx, err)
    64  		return err
    65  	}
    66  
    67  	if err = removeAll(dirPath); err != nil {
    68  		if osIsPermission(err) {
    69  			logger.LogIf(ctx, errVolumeAccessDenied)
    70  			return errVolumeAccessDenied
    71  		} else if isSysErrNotEmpty(err) {
    72  			logger.LogIf(ctx, errVolumeNotEmpty)
    73  			return errVolumeNotEmpty
    74  		}
    75  		logger.LogIf(ctx, err)
    76  		return err
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  // Removes a directory only if its empty, handles long
    83  // paths for windows automatically.
    84  func fsRemoveDir(ctx context.Context, dirPath string) (err error) {
    85  	if dirPath == "" {
    86  		logger.LogIf(ctx, errInvalidArgument)
    87  		return errInvalidArgument
    88  	}
    89  
    90  	if err = checkPathLength(dirPath); err != nil {
    91  		logger.LogIf(ctx, err)
    92  		return err
    93  	}
    94  
    95  	if err = os.Remove((dirPath)); err != nil {
    96  		if osIsNotExist(err) {
    97  			return errVolumeNotFound
    98  		} else if isSysErrNotEmpty(err) {
    99  			return errVolumeNotEmpty
   100  		}
   101  		logger.LogIf(ctx, err)
   102  		return err
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // Creates a new directory, parent dir should exist
   109  // otherwise returns an error. If directory already
   110  // exists returns an error. Windows long paths
   111  // are handled automatically.
   112  func fsMkdir(ctx context.Context, dirPath string) (err error) {
   113  	if dirPath == "" {
   114  		logger.LogIf(ctx, errInvalidArgument)
   115  		return errInvalidArgument
   116  	}
   117  
   118  	if err = checkPathLength(dirPath); err != nil {
   119  		logger.LogIf(ctx, err)
   120  		return err
   121  	}
   122  
   123  	if err = os.Mkdir((dirPath), 0777); err != nil {
   124  		switch {
   125  		case osIsExist(err):
   126  			return errVolumeExists
   127  		case osIsPermission(err):
   128  			logger.LogIf(ctx, errDiskAccessDenied)
   129  			return errDiskAccessDenied
   130  		case isSysErrNotDir(err):
   131  			// File path cannot be verified since
   132  			// one of the parents is a file.
   133  			logger.LogIf(ctx, errDiskAccessDenied)
   134  			return errDiskAccessDenied
   135  		case isSysErrPathNotFound(err):
   136  			// Add specific case for windows.
   137  			logger.LogIf(ctx, errDiskAccessDenied)
   138  			return errDiskAccessDenied
   139  		default:
   140  			logger.LogIf(ctx, err)
   141  			return err
   142  		}
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  // fsStat is a low level call which validates input arguments
   149  // and checks input length upto supported maximum. Does
   150  // not perform any higher layer interpretation of files v/s
   151  // directories. For higher level interpretation look at
   152  // fsStatFileDir, fsStatFile, fsStatDir.
   153  func fsStat(ctx context.Context, statLoc string) (os.FileInfo, error) {
   154  	if statLoc == "" {
   155  		logger.LogIf(ctx, errInvalidArgument)
   156  		return nil, errInvalidArgument
   157  	}
   158  	if err := checkPathLength(statLoc); err != nil {
   159  		logger.LogIf(ctx, err)
   160  		return nil, err
   161  	}
   162  	fi, err := os.Stat(statLoc)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	return fi, nil
   168  }
   169  
   170  // Lookup if volume exists, returns volume attributes upon success.
   171  func fsStatVolume(ctx context.Context, volume string) (os.FileInfo, error) {
   172  	fi, err := fsStat(ctx, volume)
   173  	if err != nil {
   174  		if osIsNotExist(err) {
   175  			return nil, errVolumeNotFound
   176  		} else if osIsPermission(err) {
   177  			return nil, errVolumeAccessDenied
   178  		}
   179  		return nil, err
   180  	}
   181  
   182  	if !fi.IsDir() {
   183  		return nil, errVolumeAccessDenied
   184  	}
   185  
   186  	return fi, nil
   187  }
   188  
   189  // Lookup if directory exists, returns directory attributes upon success.
   190  func fsStatDir(ctx context.Context, statDir string) (os.FileInfo, error) {
   191  	fi, err := fsStat(ctx, statDir)
   192  	if err != nil {
   193  		err = osErrToFileErr(err)
   194  		if err != errFileNotFound {
   195  			logger.LogIf(ctx, err)
   196  		}
   197  		return nil, err
   198  	}
   199  	if !fi.IsDir() {
   200  		return nil, errFileNotFound
   201  	}
   202  	return fi, nil
   203  }
   204  
   205  // Lookup if file exists, returns file attributes upon success.
   206  func fsStatFile(ctx context.Context, statFile string) (os.FileInfo, error) {
   207  	fi, err := fsStat(ctx, statFile)
   208  	if err != nil {
   209  		err = osErrToFileErr(err)
   210  		if err != errFileNotFound {
   211  			logger.LogIf(ctx, err)
   212  		}
   213  		return nil, err
   214  	}
   215  	if fi.IsDir() {
   216  		return nil, errFileNotFound
   217  	}
   218  	return fi, nil
   219  }
   220  
   221  // Returns if the filePath is a regular file.
   222  func fsIsFile(ctx context.Context, filePath string) bool {
   223  	fi, err := fsStat(ctx, filePath)
   224  	if err != nil {
   225  		return false
   226  	}
   227  	return fi.Mode().IsRegular()
   228  }
   229  
   230  // Opens the file at given path, optionally from an offset. Upon success returns
   231  // a readable stream and the size of the readable stream.
   232  func fsOpenFile(ctx context.Context, readPath string, offset int64) (io.ReadCloser, int64, error) {
   233  	if readPath == "" || offset < 0 {
   234  		logger.LogIf(ctx, errInvalidArgument)
   235  		return nil, 0, errInvalidArgument
   236  	}
   237  	if err := checkPathLength(readPath); err != nil {
   238  		logger.LogIf(ctx, err)
   239  		return nil, 0, err
   240  	}
   241  
   242  	fr, err := os.Open(readPath)
   243  	if err != nil {
   244  		return nil, 0, osErrToFileErr(err)
   245  	}
   246  
   247  	// Stat to get the size of the file at path.
   248  	st, err := fr.Stat()
   249  	if err != nil {
   250  		err = osErrToFileErr(err)
   251  		if err != errFileNotFound {
   252  			logger.LogIf(ctx, err)
   253  		}
   254  		return nil, 0, err
   255  	}
   256  
   257  	// Verify if its not a regular file, since subsequent Seek is undefined.
   258  	if !st.Mode().IsRegular() {
   259  		return nil, 0, errIsNotRegular
   260  	}
   261  
   262  	// Seek to the requested offset.
   263  	if offset > 0 {
   264  		_, err = fr.Seek(offset, io.SeekStart)
   265  		if err != nil {
   266  			logger.LogIf(ctx, err)
   267  			return nil, 0, err
   268  		}
   269  	}
   270  
   271  	// Success.
   272  	return fr, st.Size(), nil
   273  }
   274  
   275  // Creates a file and copies data from incoming reader.
   276  func fsCreateFile(ctx context.Context, filePath string, reader io.Reader, fallocSize int64) (int64, error) {
   277  	if filePath == "" || reader == nil {
   278  		logger.LogIf(ctx, errInvalidArgument)
   279  		return 0, errInvalidArgument
   280  	}
   281  
   282  	if err := checkPathLength(filePath); err != nil {
   283  		logger.LogIf(ctx, err)
   284  		return 0, err
   285  	}
   286  
   287  	if err := mkdirAll(pathutil.Dir(filePath), 0777); err != nil {
   288  		switch {
   289  		case osIsPermission(err):
   290  			return 0, errFileAccessDenied
   291  		case osIsExist(err):
   292  			return 0, errFileAccessDenied
   293  		case isSysErrIO(err):
   294  			return 0, errFaultyDisk
   295  		case isSysErrInvalidArg(err):
   296  			return 0, errUnsupportedDisk
   297  		case isSysErrNoSpace(err):
   298  			return 0, errDiskFull
   299  		}
   300  		return 0, err
   301  	}
   302  
   303  	flags := os.O_CREATE | os.O_WRONLY
   304  	if globalFSOSync {
   305  		flags = flags | os.O_SYNC
   306  	}
   307  	writer, err := lock.Open(filePath, flags, 0666)
   308  	if err != nil {
   309  		return 0, osErrToFileErr(err)
   310  	}
   311  	defer writer.Close()
   312  
   313  	// Fallocate only if the size is final object is known.
   314  	if fallocSize > 0 {
   315  		if err = fsFAllocate(int(writer.Fd()), 0, fallocSize); err != nil {
   316  			logger.LogIf(ctx, err)
   317  			return 0, err
   318  		}
   319  	}
   320  
   321  	bytesWritten, err := io.Copy(writer, reader)
   322  	if err != nil {
   323  		logger.LogIf(ctx, err)
   324  		return 0, err
   325  	}
   326  
   327  	return bytesWritten, nil
   328  }
   329  
   330  // fsFAllocate is similar to Fallocate but provides a convenient
   331  // wrapper to handle various operating system specific errors.
   332  func fsFAllocate(fd int, offset int64, len int64) (err error) {
   333  	e := Fallocate(fd, offset, len)
   334  	if e != nil {
   335  		switch {
   336  		case isSysErrNoSpace(e):
   337  			err = errDiskFull
   338  		case isSysErrNoSys(e) || isSysErrOpNotSupported(e):
   339  			// Ignore errors when Fallocate is not supported in the current system
   340  		case isSysErrInvalidArg(e):
   341  			// Workaround for Windows Docker Engine 19.03.8.
   342  			// See https://github.com/minio/minio/issues/9726
   343  		case isSysErrIO(e):
   344  			err = e
   345  		default:
   346  			// For errors: EBADF, EINTR, EINVAL, ENODEV, EPERM, ESPIPE  and ETXTBSY
   347  			// Appending was failed anyway, returns unexpected error
   348  			err = errUnexpected
   349  		}
   350  		return err
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  // Renames source path to destination path, fails if the destination path
   357  // parents are not already created.
   358  func fsSimpleRenameFile(ctx context.Context, sourcePath, destPath string) error {
   359  	if err := checkPathLength(sourcePath); err != nil {
   360  		logger.LogIf(ctx, err)
   361  		return err
   362  	}
   363  	if err := checkPathLength(destPath); err != nil {
   364  		logger.LogIf(ctx, err)
   365  		return err
   366  	}
   367  
   368  	if err := os.Rename(sourcePath, destPath); err != nil {
   369  		logger.LogIf(ctx, err)
   370  		return osErrToFileErr(err)
   371  	}
   372  
   373  	return nil
   374  }
   375  
   376  // Renames source path to destination path, creates all the
   377  // missing parents if they don't exist.
   378  func fsRenameFile(ctx context.Context, sourcePath, destPath string) error {
   379  	if err := checkPathLength(sourcePath); err != nil {
   380  		logger.LogIf(ctx, err)
   381  		return err
   382  	}
   383  	if err := checkPathLength(destPath); err != nil {
   384  		logger.LogIf(ctx, err)
   385  		return err
   386  	}
   387  
   388  	if err := renameAll(sourcePath, destPath); err != nil {
   389  		logger.LogIf(ctx, err)
   390  		return err
   391  	}
   392  
   393  	return nil
   394  }
   395  
   396  func deleteFile(basePath, deletePath string, recursive bool) error {
   397  	if basePath == "" || deletePath == "" {
   398  		return nil
   399  	}
   400  	isObjectDir := HasSuffix(deletePath, SlashSeparator)
   401  	basePath = pathutil.Clean(basePath)
   402  	deletePath = pathutil.Clean(deletePath)
   403  	if !strings.HasPrefix(deletePath, basePath) || deletePath == basePath {
   404  		return nil
   405  	}
   406  
   407  	var err error
   408  	if recursive {
   409  		os.RemoveAll(deletePath)
   410  	} else {
   411  		err = os.Remove(deletePath)
   412  	}
   413  	if err != nil {
   414  		switch {
   415  		case isSysErrNotEmpty(err):
   416  			// if object is a directory, but if its not empty
   417  			// return FileNotFound to indicate its an empty prefix.
   418  			if isObjectDir {
   419  				return errFileNotFound
   420  			}
   421  			// Ignore errors if the directory is not empty. The server relies on
   422  			// this functionality, and sometimes uses recursion that should not
   423  			// error on parent directories.
   424  			return nil
   425  		case osIsNotExist(err):
   426  			return errFileNotFound
   427  		case osIsPermission(err):
   428  			return errFileAccessDenied
   429  		case isSysErrIO(err):
   430  			return errFaultyDisk
   431  		default:
   432  			return err
   433  		}
   434  	}
   435  
   436  	deletePath = pathutil.Dir(deletePath)
   437  
   438  	// Delete parent directory obviously not recursively. Errors for
   439  	// parent directories shouldn't trickle down.
   440  	deleteFile(basePath, deletePath, false)
   441  
   442  	return nil
   443  }
   444  
   445  // fsDeleteFile is a wrapper for deleteFile(), after checking the path length.
   446  func fsDeleteFile(ctx context.Context, basePath, deletePath string) error {
   447  	if err := checkPathLength(basePath); err != nil {
   448  		logger.LogIf(ctx, err)
   449  		return err
   450  	}
   451  
   452  	if err := checkPathLength(deletePath); err != nil {
   453  		logger.LogIf(ctx, err)
   454  		return err
   455  	}
   456  
   457  	if err := deleteFile(basePath, deletePath, false); err != nil {
   458  		if err != errFileNotFound {
   459  			logger.LogIf(ctx, err)
   460  		}
   461  		return err
   462  	}
   463  	return nil
   464  }
   465  
   466  // fsRemoveMeta safely removes a locked file and takes care of Windows special case
   467  func fsRemoveMeta(ctx context.Context, basePath, deletePath, tmpDir string) error {
   468  	// Special case for windows please read through.
   469  	if runtime.GOOS == globalWindowsOSName {
   470  		// Ordinarily windows does not permit deletion or renaming of files still
   471  		// in use, but if all open handles to that file were opened with FILE_SHARE_DELETE
   472  		// then it can permit renames and deletions of open files.
   473  		//
   474  		// There are however some gotchas with this, and it is worth listing them here.
   475  		// Firstly, Windows never allows you to really delete an open file, rather it is
   476  		// flagged as delete pending and its entry in its directory remains visible
   477  		// (though no new file handles may be opened to it) and when the very last
   478  		// open handle to the file in the system is closed, only then is it truly
   479  		// deleted. Well, actually only sort of truly deleted, because Windows only
   480  		// appears to remove the file entry from the directory, but in fact that
   481  		// entry is merely hidden and actually still exists and attempting to create
   482  		// a file with the same name will return an access denied error. How long it
   483  		// silently exists for depends on a range of factors, but put it this way:
   484  		// if your code loops creating and deleting the same file name as you might
   485  		// when operating a lock file, you're going to see lots of random spurious
   486  		// access denied errors and truly dismal lock file performance compared to POSIX.
   487  		//
   488  		// We work-around these un-POSIX file semantics by taking a dual step to
   489  		// deleting files. Firstly, it renames the file to tmp location into multipartTmpBucket
   490  		// We always open files with FILE_SHARE_DELETE permission enabled, with that
   491  		// flag Windows permits renaming and deletion, and because the name was changed
   492  		// to a very random name somewhere not in its origin directory before deletion,
   493  		// you don't see those unexpected random errors when creating files with the
   494  		// same name as a recently deleted file as you do anywhere else on Windows.
   495  		// Because the file is probably not in its original containing directory any more,
   496  		// deletions of that directory will not fail with "directory not empty" as they
   497  		// otherwise normally would either.
   498  
   499  		tmpPath := pathJoin(tmpDir, mustGetUUID())
   500  
   501  		fsRenameFile(ctx, deletePath, tmpPath)
   502  
   503  		// Proceed to deleting the directory if empty
   504  		fsDeleteFile(ctx, basePath, pathutil.Dir(deletePath))
   505  
   506  		// Finally delete the renamed file.
   507  		return fsDeleteFile(ctx, tmpDir, tmpPath)
   508  	}
   509  	return fsDeleteFile(ctx, basePath, deletePath)
   510  }