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