Line data Source code
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 2 : func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
22 :
23 : // SafeFormat implements redact.SafeFormatter.
24 2 : func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) {
25 2 : w.Printf("%06d", redact.SafeUint(fn))
26 2 : }
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 2 : func (fn FileNum) DiskFileNum() DiskFileNum {
33 2 : return DiskFileNum(fn)
34 2 : }
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 uint64
43 :
44 2 : func (dfn DiskFileNum) String() string { return fmt.Sprintf("%06d", dfn) }
45 :
46 : // SafeFormat implements redact.SafeFormatter.
47 2 : func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) {
48 2 : w.Printf("%06d", redact.SafeUint(dfn))
49 2 : }
50 :
51 : // FileNum converts a DiskFileNum to a FileNum. This conversion is always valid.
52 2 : func (dfn DiskFileNum) FileNum() FileNum {
53 2 : return FileNum(dfn)
54 2 : }
55 :
56 : // FileType enumerates the types of files found in a DB.
57 : type FileType int
58 :
59 : // The FileType enumeration.
60 : const (
61 : FileTypeLog FileType = iota
62 : FileTypeLock
63 : FileTypeTable
64 : FileTypeManifest
65 : FileTypeOptions
66 : FileTypeOldTemp
67 : FileTypeTemp
68 : )
69 :
70 : // MakeFilename builds a filename from components.
71 2 : func MakeFilename(fileType FileType, dfn DiskFileNum) string {
72 2 : switch fileType {
73 2 : case FileTypeLog:
74 2 : return fmt.Sprintf("%s.log", dfn)
75 2 : case FileTypeLock:
76 2 : return "LOCK"
77 2 : case FileTypeTable:
78 2 : return fmt.Sprintf("%s.sst", dfn)
79 2 : case FileTypeManifest:
80 2 : return fmt.Sprintf("MANIFEST-%s", dfn)
81 2 : case FileTypeOptions:
82 2 : return fmt.Sprintf("OPTIONS-%s", dfn)
83 1 : case FileTypeOldTemp:
84 1 : return fmt.Sprintf("CURRENT.%s.dbtmp", dfn)
85 2 : case FileTypeTemp:
86 2 : return fmt.Sprintf("temporary.%s.dbtmp", dfn)
87 : }
88 0 : panic("unreachable")
89 : }
90 :
91 : // MakeFilepath builds a filepath from components.
92 2 : func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string {
93 2 : return fs.PathJoin(dirname, MakeFilename(fileType, dfn))
94 2 : }
95 :
96 : // ParseFilename parses the components from a filename.
97 2 : func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) {
98 2 : filename = fs.PathBase(filename)
99 2 : switch {
100 2 : case filename == "LOCK":
101 2 : return FileTypeLock, 0, true
102 2 : case strings.HasPrefix(filename, "MANIFEST-"):
103 2 : dfn, ok = parseDiskFileNum(filename[len("MANIFEST-"):])
104 2 : if !ok {
105 1 : break
106 : }
107 2 : return FileTypeManifest, dfn, true
108 2 : case strings.HasPrefix(filename, "OPTIONS-"):
109 2 : dfn, ok = parseDiskFileNum(filename[len("OPTIONS-"):])
110 2 : if !ok {
111 1 : break
112 : }
113 2 : return FileTypeOptions, dfn, ok
114 1 : case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"):
115 1 : s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp")
116 1 : dfn, ok = parseDiskFileNum(s)
117 1 : if !ok {
118 1 : break
119 : }
120 1 : return FileTypeOldTemp, dfn, ok
121 1 : case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"):
122 1 : s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp")
123 1 : dfn, ok = parseDiskFileNum(s)
124 1 : if !ok {
125 0 : break
126 : }
127 1 : return FileTypeTemp, dfn, ok
128 2 : default:
129 2 : i := strings.IndexByte(filename, '.')
130 2 : if i < 0 {
131 2 : break
132 : }
133 2 : dfn, ok = parseDiskFileNum(filename[:i])
134 2 : if !ok {
135 2 : break
136 : }
137 2 : switch filename[i+1:] {
138 2 : case "sst":
139 2 : return FileTypeTable, dfn, true
140 2 : case "log":
141 2 : return FileTypeLog, dfn, true
142 : }
143 : }
144 2 : return 0, dfn, false
145 : }
146 :
147 2 : func parseDiskFileNum(s string) (dfn DiskFileNum, ok bool) {
148 2 : u, err := strconv.ParseUint(s, 10, 64)
149 2 : if err != nil {
150 2 : return dfn, false
151 2 : }
152 2 : return DiskFileNum(u), true
153 : }
154 :
155 : // A Fataler fatals a process with a message when called.
156 : type Fataler interface {
157 : Fatalf(format string, args ...interface{})
158 : }
159 :
160 : // MustExist checks if err is an error indicating a file does not exist.
161 : // If it is, it lists the containing directory's files to annotate the error
162 : // with counts of the various types of files and invokes the provided fataler.
163 : // See cockroachdb/cockroach#56490.
164 2 : func MustExist(fs vfs.FS, filename string, fataler Fataler, err error) {
165 2 : if err == nil || !oserror.IsNotExist(err) {
166 2 : return
167 2 : }
168 :
169 1 : ls, lsErr := fs.List(fs.PathDir(filename))
170 1 : if lsErr != nil {
171 0 : // TODO(jackson): if oserror.IsNotExist(lsErr), the the data directory
172 0 : // doesn't exist anymore. Another process likely deleted it before
173 0 : // killing the process. We want to fatal the process, but without
174 0 : // triggering error reporting like Sentry.
175 0 : fataler.Fatalf("%s:\norig err: %s\nlist err: %s", redact.Safe(fs.PathBase(filename)), err, lsErr)
176 0 : }
177 1 : var total, unknown, tables, logs, manifests int
178 1 : total = len(ls)
179 1 : for _, f := range ls {
180 1 : typ, _, ok := ParseFilename(fs, f)
181 1 : if !ok {
182 1 : unknown++
183 1 : continue
184 : }
185 1 : switch typ {
186 1 : case FileTypeTable:
187 1 : tables++
188 1 : case FileTypeLog:
189 1 : logs++
190 1 : case FileTypeManifest:
191 1 : manifests++
192 : }
193 : }
194 :
195 1 : fataler.Fatalf("%s:\n%s\ndirectory contains %d files, %d unknown, %d tables, %d logs, %d manifests",
196 1 : fs.PathBase(filename), err, total, unknown, tables, logs, manifests)
197 : }
|