storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/os-reliable.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 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  	"fmt"
    21  	"os"
    22  	"path"
    23  )
    24  
    25  // Wrapper functions to os.RemoveAll, which calls reliableRemoveAll
    26  // this is to ensure that if there is a racy parent directory
    27  // create in between we can simply retry the operation.
    28  func removeAll(dirPath string) (err error) {
    29  	if dirPath == "" {
    30  		return errInvalidArgument
    31  	}
    32  
    33  	if err = checkPathLength(dirPath); err != nil {
    34  		return err
    35  	}
    36  
    37  	if err = reliableRemoveAll(dirPath); err != nil {
    38  		switch {
    39  		case isSysErrNotDir(err):
    40  			// File path cannot be verified since one of
    41  			// the parents is a file.
    42  			return errFileAccessDenied
    43  		case isSysErrPathNotFound(err):
    44  			// This is a special case should be handled only for
    45  			// windows, because windows API does not return "not a
    46  			// directory" error message. Handle this specifically
    47  			// here.
    48  			return errFileAccessDenied
    49  		}
    50  	}
    51  	return err
    52  }
    53  
    54  // Reliably retries os.RemoveAll if for some reason os.RemoveAll returns
    55  // syscall.ENOTEMPTY (children has files).
    56  func reliableRemoveAll(dirPath string) (err error) {
    57  	i := 0
    58  	for {
    59  		// Removes all the directories and files.
    60  		if err = RemoveAll(dirPath); err != nil {
    61  			// Retry only for the first retryable error.
    62  			if isSysErrNotEmpty(err) && i == 0 {
    63  				i++
    64  				continue
    65  			}
    66  		}
    67  		break
    68  	}
    69  	return err
    70  }
    71  
    72  // Wrapper functions to os.MkdirAll, which calls reliableMkdirAll
    73  // this is to ensure that if there is a racy parent directory
    74  // delete in between we can simply retry the operation.
    75  func mkdirAll(dirPath string, mode os.FileMode) (err error) {
    76  	if dirPath == "" {
    77  		return errInvalidArgument
    78  	}
    79  
    80  	if err = checkPathLength(dirPath); err != nil {
    81  		return err
    82  	}
    83  
    84  	if err = reliableMkdirAll(dirPath, mode); err != nil {
    85  		// File path cannot be verified since one of the parents is a file.
    86  		if isSysErrNotDir(err) {
    87  			return errFileAccessDenied
    88  		} else if isSysErrPathNotFound(err) {
    89  			// This is a special case should be handled only for
    90  			// windows, because windows API does not return "not a
    91  			// directory" error message. Handle this specifically here.
    92  			return errFileAccessDenied
    93  		}
    94  	}
    95  	return err
    96  }
    97  
    98  // Reliably retries os.MkdirAll if for some reason os.MkdirAll returns
    99  // syscall.ENOENT (parent does not exist).
   100  func reliableMkdirAll(dirPath string, mode os.FileMode) (err error) {
   101  	i := 0
   102  	for {
   103  		// Creates all the parent directories, with mode 0777 mkdir honors system umask.
   104  		if err = MkdirAll(dirPath, mode); err != nil {
   105  			// Retry only for the first retryable error.
   106  			if osIsNotExist(err) && i == 0 {
   107  				i++
   108  				continue
   109  			}
   110  		}
   111  		break
   112  	}
   113  	return err
   114  }
   115  
   116  // Wrapper function to os.Rename, which calls reliableMkdirAll
   117  // and reliableRenameAll. This is to ensure that if there is a
   118  // racy parent directory delete in between we can simply retry
   119  // the operation.
   120  func renameAll(srcFilePath, dstFilePath string) (err error) {
   121  	if srcFilePath == "" || dstFilePath == "" {
   122  		return errInvalidArgument
   123  	}
   124  
   125  	if err = checkPathLength(srcFilePath); err != nil {
   126  		return err
   127  	}
   128  	if err = checkPathLength(dstFilePath); err != nil {
   129  		return err
   130  	}
   131  
   132  	if err = reliableRename(srcFilePath, dstFilePath); err != nil {
   133  		switch {
   134  		case isSysErrNotDir(err) && !osIsNotExist(err):
   135  			// Windows can have both isSysErrNotDir(err) and osIsNotExist(err) returning
   136  			// true if the source file path contains an inexistant directory. In that case,
   137  			// we want to return errFileNotFound instead, which will honored in subsequent
   138  			// switch cases
   139  			return errFileAccessDenied
   140  		case isSysErrPathNotFound(err):
   141  			// This is a special case should be handled only for
   142  			// windows, because windows API does not return "not a
   143  			// directory" error message. Handle this specifically here.
   144  			return errFileAccessDenied
   145  		case isSysErrCrossDevice(err):
   146  			return fmt.Errorf("%w (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath)
   147  		case osIsNotExist(err):
   148  			return errFileNotFound
   149  		case osIsExist(err):
   150  			// This is returned only when destination is a directory and we
   151  			// are attempting a rename from file to directory.
   152  			return errIsNotRegular
   153  		default:
   154  			return err
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  // Reliably retries os.RenameAll if for some reason os.RenameAll returns
   161  // syscall.ENOENT (parent does not exist).
   162  func reliableRename(srcFilePath, dstFilePath string) (err error) {
   163  	if err = reliableMkdirAll(path.Dir(dstFilePath), 0777); err != nil {
   164  		return err
   165  	}
   166  	i := 0
   167  	for {
   168  		// After a successful parent directory create attempt a renameAll.
   169  		if err = Rename(srcFilePath, dstFilePath); err != nil {
   170  			// Retry only for the first retryable error.
   171  			if osIsNotExist(err) && i == 0 {
   172  				i++
   173  				continue
   174  			}
   175  		}
   176  		break
   177  	}
   178  	return err
   179  }