github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/storage/temp_dir.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package storage
    12  
    13  import (
    14  	"bufio"
    15  	"context"
    16  	"io/ioutil"
    17  	"os"
    18  	"path/filepath"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/util/log"
    21  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  const lockFilename = `TEMP_DIR.LOCK`
    26  
    27  // CreateTempDir creates a temporary directory with a prefix under the given
    28  // parentDir and returns the absolute path of the temporary directory.
    29  // It is advised to invoke CleanupTempDirs before creating new temporary
    30  // directories in cases where the disk is completely full.
    31  func CreateTempDir(parentDir, prefix string, stopper *stop.Stopper) (string, error) {
    32  	// We generate a unique temporary directory with the specified prefix.
    33  	tempPath, err := ioutil.TempDir(parentDir, prefix)
    34  	if err != nil {
    35  		return "", err
    36  	}
    37  
    38  	// TempDir creates a directory with permissions 0700. Manually change the
    39  	// permissions to be 0755 like every other directory created by cockroach.
    40  	if err := os.Chmod(tempPath, 0755); err != nil {
    41  		return "", err
    42  	}
    43  
    44  	absPath, err := filepath.Abs(tempPath)
    45  	if err != nil {
    46  		return "", err
    47  	}
    48  
    49  	// Create a lock file.
    50  	flock, err := lockFile(filepath.Join(absPath, lockFilename))
    51  	if err != nil {
    52  		return "", errors.Wrapf(err, "could not create lock on new temporary directory")
    53  	}
    54  	stopper.AddCloser(stop.CloserFn(func() {
    55  		if err := unlockFile(flock); err != nil {
    56  			log.Errorf(context.TODO(), "could not unlock file lock on temporary directory: %s", err.Error())
    57  		}
    58  	}))
    59  
    60  	return absPath, nil
    61  }
    62  
    63  // RecordTempDir records tempPath to the record file specified by recordPath to
    64  // facilitate cleanup of the temporary directory on subsequent startups.
    65  func RecordTempDir(recordPath, tempPath string) error {
    66  	// If the file does not exist, create it, or append to the file.
    67  	f, err := os.OpenFile(recordPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	defer f.Close()
    72  
    73  	// Record tempPath to the record file.
    74  	_, err = f.Write(append([]byte(tempPath), '\n'))
    75  	return err
    76  }
    77  
    78  // CleanupTempDirs removes all directories listed in the record file specified
    79  // by recordPath.
    80  // It should be invoked before creating any new temporary directories to clean
    81  // up abandoned temporary directories.
    82  // It should also be invoked when a newly created temporary directory is no
    83  // longer needed and needs to be removed from the record file.
    84  func CleanupTempDirs(recordPath string) error {
    85  	// Reading the entire file into memory shouldn't be a problem since
    86  	// it is extremely rare for this record file to contain more than a few
    87  	// entries.
    88  	f, err := os.OpenFile(recordPath, os.O_RDWR, 0644)
    89  	// There is no existing record file and thus nothing to clean up.
    90  	if os.IsNotExist(err) {
    91  		return nil
    92  	}
    93  	if err != nil {
    94  		return err
    95  	}
    96  	defer f.Close()
    97  
    98  	scanner := bufio.NewScanner(f)
    99  	// Iterate through each temporary directory path and remove the
   100  	// directory.
   101  	for scanner.Scan() {
   102  		path := scanner.Text()
   103  		if path == "" {
   104  			continue
   105  		}
   106  
   107  		// Check if the temporary directory exists; if it does not, skip over it.
   108  		if _, err := os.Stat(path); os.IsNotExist(err) {
   109  			log.Warningf(context.Background(), "could not locate previous temporary directory %s, might require manual cleanup, or might have already been cleaned up.", path)
   110  			continue
   111  		}
   112  
   113  		// Check if another Cockroach instance is using this temporary
   114  		// directory i.e. has a lock on the temp dir lock file.
   115  		flock, err := lockFile(filepath.Join(path, lockFilename))
   116  		if err != nil {
   117  			return errors.Wrapf(err, "could not lock temporary directory %s, may still be in use", path)
   118  		}
   119  		// On Windows, file locks are mandatory, so we must remove our lock on the
   120  		// lock file before we can remove the temporary directory. This yields a
   121  		// race condition: another process could start using the now-unlocked
   122  		// directory before we can remove it. Luckily, this doesn't matter, because
   123  		// these temporary directories are never reused. Any other process trying to
   124  		// lock this temporary directory is just trying to clean it up, too. Only
   125  		// the original process wants the data in this directory, and we know that
   126  		// process is dead because we were able to acquire the lock in the first
   127  		// place.
   128  		if err := unlockFile(flock); err != nil {
   129  			log.Errorf(context.TODO(), "could not unlock file lock when removing temporary directory: %s", err.Error())
   130  		}
   131  
   132  		// If path/directory does not exist, error is nil.
   133  		if err := os.RemoveAll(path); err != nil {
   134  			return err
   135  		}
   136  	}
   137  
   138  	// Clear out the record file now that we're done.
   139  	return f.Truncate(0)
   140  }