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