file.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /*
  2. * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package z
  6. import (
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "os"
  12. "path/filepath"
  13. )
  14. // MmapFile represents an mmapd file and includes both the buffer to the data
  15. // and the file descriptor.
  16. type MmapFile struct {
  17. Data []byte
  18. Fd *os.File
  19. }
  20. var NewFile = errors.New("Create a new file")
  21. func OpenMmapFileUsing(fd *os.File, sz int, writable bool) (*MmapFile, error) {
  22. filename := fd.Name()
  23. fi, err := fd.Stat()
  24. if err != nil {
  25. return nil, errors.Join(err, fmt.Errorf("cannot stat file: %s", filename))
  26. }
  27. var rerr error
  28. fileSize := fi.Size()
  29. if sz > 0 && fileSize == 0 {
  30. // If file is empty, truncate it to sz.
  31. if err := fd.Truncate(int64(sz)); err != nil {
  32. return nil, errors.Join(err, errors.New("error while truncation"))
  33. }
  34. fileSize = int64(sz)
  35. rerr = NewFile
  36. }
  37. // fmt.Printf("Mmaping file: %s with writable: %v filesize: %d\n", fd.Name(), writable, fileSize)
  38. buf, err := Mmap(fd, writable, fileSize) // Mmap up to file size.
  39. if err != nil {
  40. return nil, errors.Join(err, fmt.Errorf("while mmapping %s with size: %d", fd.Name(), fileSize))
  41. }
  42. if fileSize == 0 {
  43. dir, _ := filepath.Split(filename)
  44. if err := SyncDir(dir); err != nil {
  45. return nil, err
  46. }
  47. }
  48. return &MmapFile{
  49. Data: buf,
  50. Fd: fd,
  51. }, rerr
  52. }
  53. // OpenMmapFile opens an existing file or creates a new file. If the file is
  54. // created, it would truncate the file to maxSz. In both cases, it would mmap
  55. // the file to maxSz and returned it. In case the file is created, z.NewFile is
  56. // returned.
  57. func OpenMmapFile(filename string, flag int, maxSz int) (*MmapFile, error) {
  58. // fmt.Printf("opening file %s with flag: %v\n", filename, flag)
  59. fd, err := os.OpenFile(filename, flag, 0666)
  60. if err != nil {
  61. return nil, errors.Join(err, fmt.Errorf("unable to open: %s", filename))
  62. }
  63. writable := true
  64. if flag == os.O_RDONLY {
  65. writable = false
  66. }
  67. return OpenMmapFileUsing(fd, maxSz, writable)
  68. }
  69. type mmapReader struct {
  70. Data []byte
  71. offset int
  72. }
  73. func (mr *mmapReader) Read(buf []byte) (int, error) {
  74. if mr.offset > len(mr.Data) {
  75. return 0, io.EOF
  76. }
  77. n := copy(buf, mr.Data[mr.offset:])
  78. mr.offset += n
  79. if n < len(buf) {
  80. return n, io.EOF
  81. }
  82. return n, nil
  83. }
  84. func (m *MmapFile) NewReader(offset int) io.Reader {
  85. return &mmapReader{
  86. Data: m.Data,
  87. offset: offset,
  88. }
  89. }
  90. // Bytes returns data starting from offset off of size sz. If there's not enough data, it would
  91. // return nil slice and io.EOF.
  92. func (m *MmapFile) Bytes(off, sz int) ([]byte, error) {
  93. if len(m.Data[off:]) < sz {
  94. return nil, io.EOF
  95. }
  96. return m.Data[off : off+sz], nil
  97. }
  98. // Slice returns the slice at the given offset.
  99. func (m *MmapFile) Slice(offset int) []byte {
  100. sz := binary.BigEndian.Uint32(m.Data[offset:])
  101. start := offset + 4
  102. next := start + int(sz)
  103. if next > len(m.Data) {
  104. return []byte{}
  105. }
  106. res := m.Data[start:next]
  107. return res
  108. }
  109. // AllocateSlice allocates a slice of the given size at the given offset.
  110. func (m *MmapFile) AllocateSlice(sz, offset int) ([]byte, int, error) {
  111. start := offset + 4
  112. // If the file is too small, double its size or increase it by 1GB, whichever is smaller.
  113. if start+sz > len(m.Data) {
  114. const oneGB = 1 << 30
  115. growBy := len(m.Data)
  116. if growBy > oneGB {
  117. growBy = oneGB
  118. }
  119. if growBy < sz+4 {
  120. growBy = sz + 4
  121. }
  122. if err := m.Truncate(int64(len(m.Data) + growBy)); err != nil {
  123. return nil, 0, err
  124. }
  125. }
  126. binary.BigEndian.PutUint32(m.Data[offset:], uint32(sz))
  127. return m.Data[start : start+sz], start + sz, nil
  128. }
  129. func (m *MmapFile) Sync() error {
  130. if m == nil {
  131. return nil
  132. }
  133. return Msync(m.Data)
  134. }
  135. func (m *MmapFile) Delete() error {
  136. // Badger can set the m.Data directly, without setting any Fd. In that case, this should be a
  137. // NOOP.
  138. if m.Fd == nil {
  139. return nil
  140. }
  141. if err := Munmap(m.Data); err != nil {
  142. return fmt.Errorf("while munmap file: %s, error: %v\n", m.Fd.Name(), err)
  143. }
  144. m.Data = nil
  145. if err := m.Fd.Truncate(0); err != nil {
  146. return fmt.Errorf("while truncate file: %s, error: %v\n", m.Fd.Name(), err)
  147. }
  148. if err := m.Fd.Close(); err != nil {
  149. return fmt.Errorf("while close file: %s, error: %v\n", m.Fd.Name(), err)
  150. }
  151. return os.Remove(m.Fd.Name())
  152. }
  153. // Close would close the file. It would also truncate the file if maxSz >= 0.
  154. func (m *MmapFile) Close(maxSz int64) error {
  155. // Badger can set the m.Data directly, without setting any Fd. In that case, this should be a
  156. // NOOP.
  157. if m.Fd == nil {
  158. return nil
  159. }
  160. if err := m.Sync(); err != nil {
  161. return fmt.Errorf("while sync file: %s, error: %v\n", m.Fd.Name(), err)
  162. }
  163. if err := Munmap(m.Data); err != nil {
  164. return fmt.Errorf("while munmap file: %s, error: %v\n", m.Fd.Name(), err)
  165. }
  166. if maxSz >= 0 {
  167. if err := m.Fd.Truncate(maxSz); err != nil {
  168. return fmt.Errorf("while truncate file: %s, error: %v\n", m.Fd.Name(), err)
  169. }
  170. }
  171. return m.Fd.Close()
  172. }
  173. func SyncDir(dir string) error {
  174. df, err := os.Open(dir)
  175. if err != nil {
  176. return errors.Join(err, fmt.Errorf("while opening %s", dir))
  177. }
  178. if err := df.Sync(); err != nil {
  179. return errors.Join(err, fmt.Errorf("while syncing %s", dir))
  180. }
  181. if err := df.Close(); err != nil {
  182. return errors.Join(err, fmt.Errorf("while closing %s", dir))
  183. }
  184. return nil
  185. }