chore: rewrite bbolt cachefile implements
Some checks are pending
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run

never use returned byte slices outside the transaction, ref:
https://pkg.go.dev/go.etcd.io/bbolt#hdr-Caveats
This commit is contained in:
wwqgtxx 2024-09-23 09:35:48 +08:00
parent 150c6ccd25
commit 966eeae41b
12 changed files with 212 additions and 168 deletions

45
common/utils/hash.go Normal file
View File

@ -0,0 +1,45 @@
package utils
import (
"crypto/md5"
"encoding/hex"
)
// HashType warps hash array inside struct
// someday can change to other hash algorithm simply
type HashType struct {
md5 [md5.Size]byte // MD5
}
func MakeHash(data []byte) HashType {
return HashType{md5.Sum(data)}
}
func MakeHashFromBytes(hashBytes []byte) (h HashType) {
if len(hashBytes) != md5.Size {
return
}
copy(h.md5[:], hashBytes)
return
}
func (h HashType) Equal(hash HashType) bool {
return h.md5 == hash.md5
}
func (h HashType) Bytes() []byte {
return h.md5[:]
}
func (h HashType) String() string {
return hex.EncodeToString(h.Bytes())
}
func (h HashType) Len() int {
return len(h.md5)
}
func (h HashType) IsValid() bool {
var zero HashType
return h != zero
}

View File

@ -7,46 +7,32 @@ import (
) )
type cachefileStore struct { type cachefileStore struct {
cache *cachefile.CacheFile cache *cachefile.FakeIpStore
} }
// GetByHost implements store.GetByHost // GetByHost implements store.GetByHost
func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) { func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) {
elm := c.cache.GetFakeip([]byte(host)) return c.cache.GetByHost(host)
if elm == nil {
return netip.Addr{}, false
}
if len(elm) == 4 {
return netip.AddrFrom4(*(*[4]byte)(elm)), true
} else {
return netip.AddrFrom16(*(*[16]byte)(elm)), true
}
} }
// PutByHost implements store.PutByHost // PutByHost implements store.PutByHost
func (c *cachefileStore) PutByHost(host string, ip netip.Addr) { func (c *cachefileStore) PutByHost(host string, ip netip.Addr) {
c.cache.PutFakeip([]byte(host), ip.AsSlice()) c.cache.PutByHost(host, ip)
} }
// GetByIP implements store.GetByIP // GetByIP implements store.GetByIP
func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) { func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) {
elm := c.cache.GetFakeip(ip.AsSlice()) return c.cache.GetByIP(ip)
if elm == nil {
return "", false
}
return string(elm), true
} }
// PutByIP implements store.PutByIP // PutByIP implements store.PutByIP
func (c *cachefileStore) PutByIP(ip netip.Addr, host string) { func (c *cachefileStore) PutByIP(ip netip.Addr, host string) {
c.cache.PutFakeip(ip.AsSlice(), []byte(host)) c.cache.PutByIP(ip, host)
} }
// DelByIP implements store.DelByIP // DelByIP implements store.DelByIP
func (c *cachefileStore) DelByIP(ip netip.Addr) { func (c *cachefileStore) DelByIP(ip netip.Addr) {
addr := ip.AsSlice() c.cache.DelByIP(ip)
c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr))
} }
// Exist implements store.Exist // Exist implements store.Exist
@ -63,3 +49,7 @@ func (c *cachefileStore) CloneTo(store store) {}
func (c *cachefileStore) FlushFakeIP() error { func (c *cachefileStore) FlushFakeIP() error {
return c.cache.FlushFakeIP() return c.cache.FlushFakeIP()
} }
func newCachefileStore(cache *cachefile.CacheFile) *cachefileStore {
return &cachefileStore{cache.FakeIpStore()}
}

View File

@ -201,9 +201,7 @@ func New(options Options) (*Pool, error) {
ipnet: options.IPNet, ipnet: options.IPNet,
} }
if options.Persistence { if options.Persistence {
pool.store = &cachefileStore{ pool.store = newCachefileStore(cachefile.Cache())
cache: cachefile.Cache(),
}
} else { } else {
pool.store = newMemoryStore(options.Size) pool.store = newMemoryStore(options.Size)
} }

View File

@ -43,9 +43,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
return nil, "", err return nil, "", err
} }
pool.store = &cachefileStore{ pool.store = newCachefileStore(&cachefile.CacheFile{DB: db})
cache: &cachefile.CacheFile{DB: db},
}
return pool, f.Name(), nil return pool, f.Name(), nil
} }

View File

@ -6,6 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/profile" "github.com/metacubex/mihomo/component/profile"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@ -71,93 +72,19 @@ func (c *CacheFile) SelectedMap() map[string]string {
return mapping return mapping
} }
func (c *CacheFile) PutFakeip(key, value []byte) error { func (c *CacheFile) SetETagWithHash(url string, hash utils.HashType, etag string) {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put(key, value)
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) DelFakeipPair(ip, host []byte) error {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
err = bucket.Delete(ip)
if len(host) > 0 {
if err := bucket.Delete(host); err != nil {
return err
}
}
return err
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) GetFakeip(key []byte) []byte {
if c.DB == nil {
return nil
}
tx, err := c.DB.Begin(false)
if err != nil {
return nil
}
defer tx.Rollback()
bucket := tx.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return bucket.Get(key)
}
func (c *CacheFile) FlushFakeIP() error {
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return t.DeleteBucket(bucketFakeip)
})
return err
}
func (c *CacheFile) SetETagWithHash(url string, hash []byte, etag string) {
if c.DB == nil { if c.DB == nil {
return return
} }
lenHash := len(hash) lenHash := hash.Len()
if lenHash > math.MaxUint8 { if lenHash > math.MaxUint8 {
return // maybe panic is better return // maybe panic is better
} }
data := make([]byte, 1, 1+lenHash+len(etag)) data := make([]byte, 1, 1+lenHash+len(etag))
data[0] = uint8(lenHash) data[0] = uint8(lenHash)
data = append(data, hash...) data = append(data, hash.Bytes()...)
data = append(data, etag...) data = append(data, etag...)
err := c.DB.Batch(func(t *bbolt.Tx) error { err := c.DB.Batch(func(t *bbolt.Tx) error {
@ -173,28 +100,27 @@ func (c *CacheFile) SetETagWithHash(url string, hash []byte, etag string) {
return return
} }
} }
func (c *CacheFile) GetETagWithHash(key string) (hash []byte, etag string) { func (c *CacheFile) GetETagWithHash(key string) (hash utils.HashType, etag string) {
if c.DB == nil { if c.DB == nil {
return return
} }
var value []byte
c.DB.View(func(t *bbolt.Tx) error { c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketETag); bucket != nil { if bucket := t.Bucket(bucketETag); bucket != nil {
if v := bucket.Get([]byte(key)); v != nil { if v := bucket.Get([]byte(key)); v != nil {
value = v if len(v) == 0 {
return nil
}
lenHash := int(v[0])
if len(v) < 1+lenHash {
return nil
}
hash = utils.MakeHashFromBytes(v[1 : 1+lenHash])
etag = string(v[1+lenHash:])
} }
} }
return nil return nil
}) })
if len(value) == 0 {
return
}
lenHash := int(value[0])
if len(value) < 1+lenHash {
return
}
hash = value[1 : 1+lenHash]
etag = string(value[1+lenHash:])
return return
} }

View File

@ -0,0 +1,115 @@
package cachefile
import (
"net/netip"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/bbolt"
)
type FakeIpStore struct {
*CacheFile
}
func (c *CacheFile) FakeIpStore() *FakeIpStore {
return &FakeIpStore{c}
}
func (c *FakeIpStore) GetByHost(host string) (ip netip.Addr, exist bool) {
if c.DB == nil {
return
}
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketFakeip); bucket != nil {
if v := bucket.Get([]byte(host)); v != nil {
ip, exist = netip.AddrFromSlice(v)
}
}
return nil
})
return
}
func (c *FakeIpStore) PutByHost(host string, ip netip.Addr) {
if c.DB == nil {
return
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put([]byte(host), ip.AsSlice())
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
}
func (c *FakeIpStore) GetByIP(ip netip.Addr) (host string, exist bool) {
if c.DB == nil {
return
}
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketFakeip); bucket != nil {
if v := bucket.Get(ip.AsSlice()); v != nil {
host, exist = string(v), true
}
}
return nil
})
return
}
func (c *FakeIpStore) PutByIP(ip netip.Addr, host string) {
if c.DB == nil {
return
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put(ip.AsSlice(), []byte(host))
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
}
func (c *FakeIpStore) DelByIP(ip netip.Addr) {
if c.DB == nil {
return
}
addr := ip.AsSlice()
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
host := bucket.Get(addr)
err = bucket.Delete(addr)
if len(host) > 0 {
if err = bucket.Delete(host); err != nil {
return err
}
}
return err
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
}
func (c *FakeIpStore) FlushFakeIP() error {
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return t.DeleteBucket(bucketFakeip)
})
return err
}

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"time" "time"
"github.com/metacubex/mihomo/common/utils"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@ -21,7 +22,7 @@ type Fetcher[V any] struct {
name string name string
vehicle types.Vehicle vehicle types.Vehicle
updatedAt time.Time updatedAt time.Time
hash types.HashType hash utils.HashType
parser Parser[V] parser Parser[V]
interval time.Duration interval time.Duration
onUpdate func(V) onUpdate func(V)
@ -55,7 +56,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
// local file exists, use it first // local file exists, use it first
buf, err = os.ReadFile(f.vehicle.Path()) buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime() modTime := stat.ModTime()
contents, _, err = f.loadBuf(buf, types.MakeHash(buf), false) contents, _, err = f.loadBuf(buf, utils.MakeHash(buf), false)
f.updatedAt = modTime // reset updatedAt to file's modTime f.updatedAt = modTime // reset updatedAt to file's modTime
if err == nil { if err == nil {
@ -89,10 +90,10 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
} }
func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) { func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) {
return f.loadBuf(buf, types.MakeHash(buf), true) return f.loadBuf(buf, utils.MakeHash(buf), true)
} }
func (f *Fetcher[V]) loadBuf(buf []byte, hash types.HashType, updateFile bool) (V, bool, error) { func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) (V, bool, error) {
now := time.Now() now := time.Now()
if f.hash.Equal(hash) { if f.hash.Equal(hash) {
if updateFile { if updateFile {

View File

@ -9,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/metacubex/mihomo/common/utils"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
@ -61,12 +62,12 @@ func (f *FileVehicle) Url() string {
return "file://" + f.path return "file://" + f.path
} }
func (f *FileVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []byte, hash types.HashType, err error) { func (f *FileVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
buf, err = os.ReadFile(f.path) buf, err = os.ReadFile(f.path)
if err != nil { if err != nil {
return return
} }
hash = types.MakeHash(buf) hash = utils.MakeHash(buf)
return return
} }
@ -110,14 +111,14 @@ func (h *HTTPVehicle) Write(buf []byte) error {
return safeWrite(h.path, buf) return safeWrite(h.path, buf)
} }
func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []byte, hash types.HashType, err error) { func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
ctx, cancel := context.WithTimeout(ctx, h.timeout) ctx, cancel := context.WithTimeout(ctx, h.timeout)
defer cancel() defer cancel()
header := h.header header := h.header
setIfNoneMatch := false setIfNoneMatch := false
if etag && oldHash.IsValid() { if etag && oldHash.IsValid() {
hashBytes, etag := cachefile.Cache().GetETagWithHash(h.url) hashBytes, etag := cachefile.Cache().GetETagWithHash(h.url)
if oldHash.EqualBytes(hashBytes) && etag != "" { if oldHash.Equal(hashBytes) && etag != "" {
if header == nil { if header == nil {
header = http.Header{} header = http.Header{}
} else { } else {
@ -143,9 +144,9 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []b
if err != nil { if err != nil {
return return
} }
hash = types.MakeHash(buf) hash = utils.MakeHash(buf)
if etag { if etag {
cachefile.Cache().SetETagWithHash(h.url, hash.Bytes(), resp.Header.Get("ETag")) cachefile.Cache().SetETagWithHash(h.url, hash, resp.Header.Get("ETag"))
} }
return return
} }

View File

@ -10,12 +10,12 @@ import (
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/batch" "github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard" _ "github.com/metacubex/mihomo/component/geodata/standard"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/maxminddb-golang"
@ -46,9 +46,9 @@ func SetGeoUpdateInterval(newGeoUpdateInterval int) {
func UpdateMMDB() (err error) { func UpdateMMDB() (err error) {
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout) vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout)
var oldHash P.HashType var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil { if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf) oldHash = utils.MakeHash(buf)
} }
data, hash, err := vehicle.Read(context.Background(), oldHash) data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {
@ -77,9 +77,9 @@ func UpdateMMDB() (err error) {
func UpdateASN() (err error) { func UpdateASN() (err error) {
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout) vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout)
var oldHash P.HashType var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil { if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf) oldHash = utils.MakeHash(buf)
} }
data, hash, err := vehicle.Read(context.Background(), oldHash) data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {
@ -110,9 +110,9 @@ func UpdateGeoIp() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard") geoLoader, err := geodata.GetGeoDataLoader("standard")
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout) vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout)
var oldHash P.HashType var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil { if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf) oldHash = utils.MakeHash(buf)
} }
data, hash, err := vehicle.Read(context.Background(), oldHash) data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {
@ -140,9 +140,9 @@ func UpdateGeoSite() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard") geoLoader, err := geodata.GetGeoDataLoader("standard")
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout) vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout)
var oldHash P.HashType var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil { if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf) oldHash = utils.MakeHash(buf)
} }
data, hash, err := vehicle.Read(context.Background(), oldHash) data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {

View File

@ -1,14 +1,13 @@
package constant package constant
import ( import (
"crypto/md5"
"encoding/hex"
"os" "os"
P "path" P "path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/constant/features"
) )
@ -89,8 +88,8 @@ func (p *path) IsSafePath(path string) bool {
} }
func (p *path) GetPathByHash(prefix, name string) string { func (p *path) GetPathByHash(prefix, name string) string {
hash := md5.Sum([]byte(name)) hash := utils.MakeHash([]byte(name))
filename := hex.EncodeToString(hash[:]) filename := hash.String()
return filepath.Join(p.HomeDir(), prefix, filename) return filepath.Join(p.HomeDir(), prefix, filename)
} }

View File

@ -1,29 +0,0 @@
package provider
import (
"bytes"
"crypto/md5"
)
type HashType [md5.Size]byte // MD5
func MakeHash(data []byte) HashType {
return md5.Sum(data)
}
func (h HashType) Equal(hash HashType) bool {
return h == hash
}
func (h HashType) EqualBytes(hashBytes []byte) bool {
return bytes.Equal(hashBytes, h[:])
}
func (h HashType) Bytes() []byte {
return h[:]
}
func (h HashType) IsValid() bool {
var zero HashType
return h != zero
}

View File

@ -32,7 +32,7 @@ func (v VehicleType) String() string {
} }
type Vehicle interface { type Vehicle interface {
Read(ctx context.Context, oldHash HashType) (buf []byte, hash HashType, err error) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error)
Write(buf []byte) error Write(buf []byte) error
Path() string Path() string
Url() string Url() string