forked from HouzuoGuo/tiedot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
file.go
141 lines (133 loc) · 3.58 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Common data file features - enlarge, close, close, etc.
package data
import (
"github.com/HouzuoGuo/tiedot/gommap"
"github.com/HouzuoGuo/tiedot/tdlog"
"os"
)
// Data file keeps track of the amount of total and used space.
type DataFile struct {
Path string
Size, Used, Growth int
Fh *os.File
Buf gommap.MMap
}
// Return true if the buffer begins with 64 consecutive zero bytes.
func LooksEmpty(buf gommap.MMap) bool {
upTo := 1024
if upTo >= len(buf) {
upTo = len(buf) - 1
}
for i := 0; i < upTo; i++ {
if buf[i] != 0 {
return false
}
}
return true
}
// Open a data file that grows by the specified size.
func OpenDataFile(path string, growth int) (file *DataFile, err error) {
file = &DataFile{Path: path, Growth: growth}
if file.Fh, err = os.OpenFile(file.Path, os.O_CREATE|os.O_RDWR, 0600); err != nil {
return
}
var size int64
if size, err = file.Fh.Seek(0, os.SEEK_END); err != nil {
return
}
// Ensure the file is not smaller than file growth
if file.Size = int(size); file.Size < file.Growth {
if err = file.EnsureSize(file.Growth); err != nil {
return
}
}
if file.Buf == nil {
file.Buf, err = gommap.Map(file.Fh)
}
defer tdlog.Infof("%s opened: %d of %d bytes in-use", file.Path, file.Used, file.Size)
// Bi-sect file buffer to find out how much space is in-use
for low, mid, high := 0, file.Size/2, file.Size; ; {
switch {
case high-mid == 1:
if LooksEmpty(file.Buf[mid:]) {
if mid > 0 && LooksEmpty(file.Buf[mid-1:]) {
file.Used = mid - 1
} else {
file.Used = mid
}
return
}
file.Used = high
return
case LooksEmpty(file.Buf[mid:]):
high = mid
mid = low + (mid-low)/2
default:
low = mid
mid = mid + (high-mid)/2
}
}
return
}
// Fill up portion of a file with 0s.
func (file *DataFile) overwriteWithZero(from int, size int) (err error) {
if _, err = file.Fh.Seek(int64(from), os.SEEK_SET); err != nil {
return
}
zeroSize := 1048576 * 8 // Fill 8 MB at a time
zero := make([]byte, zeroSize)
for i := 0; i < size; i += zeroSize {
var zeroSlice []byte
if i+zeroSize > size {
zeroSlice = zero[0 : size-i]
} else {
zeroSlice = zero
}
if _, err = file.Fh.Write(zeroSlice); err != nil {
return
}
}
return file.Fh.Sync()
}
// Ensure there is enough room for that many bytes of data.
func (file *DataFile) EnsureSize(more int) (err error) {
if file.Used+more <= file.Size {
return
} else if file.Buf != nil {
if err = file.Buf.Unmap(); err != nil {
return
}
}
if err = file.overwriteWithZero(file.Size, file.Growth); err != nil {
return
} else if file.Buf, err = gommap.Map(file.Fh); err != nil {
return
}
file.Size += file.Growth
tdlog.Infof("%s grown: %d -> %d bytes (%d bytes in-use)", file.Path, file.Size-file.Growth, file.Size, file.Used)
return file.EnsureSize(more)
}
// Un-map the file buffer and close the file handle.
func (file *DataFile) Close() (err error) {
if err = file.Buf.Unmap(); err != nil {
return
}
return file.Fh.Close()
}
// Clear the entire file and resize it to initial size.
func (file *DataFile) Clear() (err error) {
if err = file.Close(); err != nil {
return
} else if err = os.Truncate(file.Path, 0); err != nil {
return
} else if file.Fh, err = os.OpenFile(file.Path, os.O_CREATE|os.O_RDWR, 0600); err != nil {
return
} else if err = file.overwriteWithZero(0, file.Growth); err != nil {
return
} else if file.Buf, err = gommap.Map(file.Fh); err != nil {
return
}
file.Used, file.Size = 0, file.Growth
tdlog.Infof("%s cleared: %d of %d bytes in-use", file.Path, file.Used, file.Size)
return
}