github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/base/filenames.go (about)

     1  // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package base
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/cockroachdb/errors/oserror"
    13  	"github.com/cockroachdb/pebble/vfs"
    14  	"github.com/cockroachdb/redact"
    15  )
    16  
    17  // FileNum is an internal DB identifier for a file.
    18  type FileNum uint64
    19  
    20  // String returns a string representation of the file number.
    21  func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
    22  
    23  // SafeFormat implements redact.SafeFormatter.
    24  func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) {
    25  	w.Printf("%06d", redact.SafeUint(fn))
    26  }
    27  
    28  // DiskFileNum converts a FileNum to a DiskFileNum. DiskFileNum should only be
    29  // called if the caller can ensure that the FileNum belongs to a physical file
    30  // on disk. These could be manifests, log files, physical sstables on disk, the
    31  // options file, but not virtual sstables.
    32  func (fn FileNum) DiskFileNum() DiskFileNum {
    33  	return DiskFileNum{fn}
    34  }
    35  
    36  // A DiskFileNum is just a FileNum belonging to a file which exists on disk.
    37  // Note that a FileNum is an internal DB identifier and it could belong to files
    38  // which don't exist on disk. An example would be virtual sstable FileNums.
    39  // Converting a DiskFileNum to a FileNum is always valid, whereas converting a
    40  // FileNum to DiskFileNum may not be valid and care should be taken to prove
    41  // that the FileNum actually exists on disk.
    42  type DiskFileNum struct {
    43  	fn FileNum
    44  }
    45  
    46  func (dfn DiskFileNum) String() string { return dfn.fn.String() }
    47  
    48  // SafeFormat implements redact.SafeFormatter.
    49  func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) {
    50  	dfn.fn.SafeFormat(w, verb)
    51  }
    52  
    53  // FileNum converts a DiskFileNum to a FileNum. This conversion is always valid.
    54  func (dfn DiskFileNum) FileNum() FileNum {
    55  	return dfn.fn
    56  }
    57  
    58  // FileType enumerates the types of files found in a DB.
    59  type FileType int
    60  
    61  // The FileType enumeration.
    62  const (
    63  	FileTypeLog FileType = iota
    64  	FileTypeLock
    65  	FileTypeTable
    66  	FileTypeManifest
    67  	FileTypeCurrent
    68  	FileTypeOptions
    69  	FileTypeOldTemp
    70  	FileTypeTemp
    71  )
    72  
    73  // MakeFilename builds a filename from components.
    74  func MakeFilename(fileType FileType, dfn DiskFileNum) string {
    75  	switch fileType {
    76  	case FileTypeLog:
    77  		return fmt.Sprintf("%s.log", dfn)
    78  	case FileTypeLock:
    79  		return "LOCK"
    80  	case FileTypeTable:
    81  		return fmt.Sprintf("%s.sst", dfn)
    82  	case FileTypeManifest:
    83  		return fmt.Sprintf("MANIFEST-%s", dfn)
    84  	case FileTypeCurrent:
    85  		return "CURRENT"
    86  	case FileTypeOptions:
    87  		return fmt.Sprintf("OPTIONS-%s", dfn)
    88  	case FileTypeOldTemp:
    89  		return fmt.Sprintf("CURRENT.%s.dbtmp", dfn)
    90  	case FileTypeTemp:
    91  		return fmt.Sprintf("temporary.%s.dbtmp", dfn)
    92  	}
    93  	panic("unreachable")
    94  }
    95  
    96  // MakeFilepath builds a filepath from components.
    97  func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string {
    98  	return fs.PathJoin(dirname, MakeFilename(fileType, dfn))
    99  }
   100  
   101  // ParseFilename parses the components from a filename.
   102  func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) {
   103  	filename = fs.PathBase(filename)
   104  	switch {
   105  	case filename == "CURRENT":
   106  		return FileTypeCurrent, DiskFileNum{0}, true
   107  	case filename == "LOCK":
   108  		return FileTypeLock, DiskFileNum{0}, true
   109  	case strings.HasPrefix(filename, "MANIFEST-"):
   110  		dfn, ok = parseDiskFileNum(filename[len("MANIFEST-"):])
   111  		if !ok {
   112  			break
   113  		}
   114  		return FileTypeManifest, dfn, true
   115  	case strings.HasPrefix(filename, "OPTIONS-"):
   116  		dfn, ok = parseDiskFileNum(filename[len("OPTIONS-"):])
   117  		if !ok {
   118  			break
   119  		}
   120  		return FileTypeOptions, dfn, ok
   121  	case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"):
   122  		s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp")
   123  		dfn, ok = parseDiskFileNum(s)
   124  		if !ok {
   125  			break
   126  		}
   127  		return FileTypeOldTemp, dfn, ok
   128  	case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"):
   129  		s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp")
   130  		dfn, ok = parseDiskFileNum(s)
   131  		if !ok {
   132  			break
   133  		}
   134  		return FileTypeTemp, dfn, ok
   135  	default:
   136  		i := strings.IndexByte(filename, '.')
   137  		if i < 0 {
   138  			break
   139  		}
   140  		dfn, ok = parseDiskFileNum(filename[:i])
   141  		if !ok {
   142  			break
   143  		}
   144  		switch filename[i+1:] {
   145  		case "sst":
   146  			return FileTypeTable, dfn, true
   147  		case "log":
   148  			return FileTypeLog, dfn, true
   149  		}
   150  	}
   151  	return 0, dfn, false
   152  }
   153  
   154  func parseDiskFileNum(s string) (dfn DiskFileNum, ok bool) {
   155  	u, err := strconv.ParseUint(s, 10, 64)
   156  	if err != nil {
   157  		return dfn, false
   158  	}
   159  	return DiskFileNum{FileNum(u)}, true
   160  }
   161  
   162  // A Fataler fatals a process with a message when called.
   163  type Fataler interface {
   164  	Fatalf(format string, args ...interface{})
   165  }
   166  
   167  // MustExist checks if err is an error indicating a file does not exist.
   168  // If it is, it lists the containing directory's files to annotate the error
   169  // with counts of the various types of files and invokes the provided fataler.
   170  // See cockroachdb/cockroach#56490.
   171  func MustExist(fs vfs.FS, filename string, fataler Fataler, err error) {
   172  	if err == nil || !oserror.IsNotExist(err) {
   173  		return
   174  	}
   175  
   176  	ls, lsErr := fs.List(fs.PathDir(filename))
   177  	if lsErr != nil {
   178  		// TODO(jackson): if oserror.IsNotExist(lsErr), the the data directory
   179  		// doesn't exist anymore. Another process likely deleted it before
   180  		// killing the process. We want to fatal the process, but without
   181  		// triggering error reporting like Sentry.
   182  		fataler.Fatalf("%s:\norig err: %s\nlist err: %s", redact.Safe(fs.PathBase(filename)), err, lsErr)
   183  	}
   184  	var total, unknown, tables, logs, manifests int
   185  	total = len(ls)
   186  	for _, f := range ls {
   187  		typ, _, ok := ParseFilename(fs, f)
   188  		if !ok {
   189  			unknown++
   190  			continue
   191  		}
   192  		switch typ {
   193  		case FileTypeTable:
   194  			tables++
   195  		case FileTypeLog:
   196  			logs++
   197  		case FileTypeManifest:
   198  			manifests++
   199  		}
   200  	}
   201  
   202  	fataler.Fatalf("%s:\n%s\ndirectory contains %d files, %d unknown, %d tables, %d logs, %d manifests",
   203  		fs.PathBase(filename), err, total, unknown, tables, logs, manifests)
   204  }