diff --git a/cmd/shadowsocks-go-domain-set-converter/main.go b/cmd/shadowsocks-go-domain-set-converter/main.go index 431a03c..709a678 100644 --- a/cmd/shadowsocks-go-domain-set-converter/main.go +++ b/cmd/shadowsocks-go-domain-set-converter/main.go @@ -65,12 +65,12 @@ func main() { os.Exit(1) } - data, err := mmap.ReadFile[string](inPath) + data, close, err := mmap.ReadFile[string](inPath) if err != nil { fmt.Fprintln(os.Stderr, "Failed to read input file:", err) os.Exit(1) } - defer mmap.Unmap(data) + defer close() dsb, err := inFunc(data) if err != nil { diff --git a/cred/manager.go b/cred/manager.go index 9e1bf22..8549dcb 100644 --- a/cred/manager.go +++ b/cred/manager.go @@ -254,11 +254,11 @@ func (s *ManagedServer) DeleteCredential(username string) error { // LoadFromFile loads credentials from the configured credential file // and applies the changes to the associated credential stores. func (s *ManagedServer) LoadFromFile() error { - content, err := mmap.ReadFile[string](s.path) + content, close, err := mmap.ReadFile[string](s.path) if err != nil { return err } - defer mmap.Unmap(content) + defer close() s.mu.Lock() // Skip if the file content is unchanged. diff --git a/domainset/domainset.go b/domainset/domainset.go index a4105d4..c6f7566 100644 --- a/domainset/domainset.go +++ b/domainset/domainset.go @@ -41,11 +41,11 @@ type Config struct { // DomainSet creates a DomainSet from the configuration. func (dsc Config) DomainSet() (DomainSet, error) { - data, err := mmap.ReadFile[string](dsc.Path) + data, close, err := mmap.ReadFile[string](dsc.Path) if err != nil { return nil, fmt.Errorf("failed to load domain set %s: %w", dsc.Name, err) } - defer mmap.Unmap(data) + defer close() var dsb Builder diff --git a/go.mod b/go.mod index afb92c8..cf016f2 100644 --- a/go.mod +++ b/go.mod @@ -4,28 +4,28 @@ go 1.22.2 require ( github.com/database64128/tfo-go/v2 v2.2.1 - github.com/gofiber/contrib/fiberzap/v2 v2.1.3 + github.com/gofiber/contrib/fiberzap/v2 v2.1.4 github.com/gofiber/fiber/v2 v2.52.5 github.com/oschwald/geoip2-golang v1.11.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/net v0.26.0 - golang.org/x/sys v0.21.0 + golang.org/x/sys v0.22.0 lukechampine.com/blake3 v1.3.0 ) require ( github.com/andybalholm/brotli v1.1.0 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/klauspost/compress v1.17.6 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/oschwald/maxminddb-golang v1.13.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/oschwald/maxminddb-golang v1.13.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.52.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index e18dad1..0c10800 100644 --- a/go.sum +++ b/go.sum @@ -4,16 +4,16 @@ github.com/database64128/tfo-go/v2 v2.2.1 h1:IFctPnetEQtGo7gI84QfR1pAbi6TRPkxCFy github.com/database64128/tfo-go/v2 v2.2.1/go.mod h1:SI59G6MmmzSm9/fSRTiyYyyInHp4WEF9lTldnm3mxmA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gofiber/contrib/fiberzap/v2 v2.1.3 h1:znIDjHJUyhp11h5w8AaABCtEJejxjiCe47O0OC5We/g= -github.com/gofiber/contrib/fiberzap/v2 v2.1.3/go.mod h1:9KrEpG4A8g1Y4J7OS5WYq6rDyy+sXAEDSgGdCGPnH1Y= +github.com/gofiber/contrib/fiberzap/v2 v2.1.4 h1:GCtCQnT4Cr9az4qab2Ozmqsomkxm4Ei86MfKk/1p5+0= +github.com/gofiber/contrib/fiberzap/v2 v2.1.4/go.mod h1:PkdXgUzw+oj4m6ksfKJ0Hs3H7iPhwvhfI4b2LSA9hhA= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= -github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -23,19 +23,19 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w= github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= -github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= -github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= -github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -51,8 +51,8 @@ golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/mmap/mmap.go b/mmap/mmap.go new file mode 100644 index 0000000..13f085d --- /dev/null +++ b/mmap/mmap.go @@ -0,0 +1,48 @@ +package mmap + +import ( + "errors" + "io" + "os" + "unsafe" +) + +// ReadFile maps the named file into memory for reading. +// On success, it returns the mapped data as a byte slice or a string, +// and a function that unmaps the data. +func ReadFile[T ~[]byte | ~string](name string) (data T, close func() error, err error) { + f, err := os.Open(name) + if err != nil { + return + } + defer f.Close() + + fs, err := f.Stat() + if err != nil { + return + } + + size := fs.Size() + if size == 0 { + return + } + + addr, close, err := readFile(f, uintptr(size)) + if err != nil { + if errors.Is(err, errors.ErrUnsupported) { + return readFileFallback[T](f, size) + } + return + } + + b := unsafe.Slice((*byte)(addr), size) + return *(*T)(unsafe.Pointer(&b)), close, nil +} + +func readFileFallback[T ~[]byte | ~string](f *os.File, size int64) (data T, close func() error, err error) { + b := make([]byte, size) + if _, err = io.ReadFull(f, b); err != nil { + return + } + return *(*T)(unsafe.Pointer(&b)), func() error { return nil }, nil +} diff --git a/mmap/mmap_generic.go b/mmap/mmap_generic.go deleted file mode 100644 index eada981..0000000 --- a/mmap/mmap_generic.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build !unix && !windows - -package mmap - -import ( - "os" - "unsafe" -) - -// ReadFile maps the named file into memory for reading. -func ReadFile[T ~[]byte | ~string](name string) (data T, err error) { - b, err := os.ReadFile(name) - if err != nil { - return - } - return *(*T)(unsafe.Pointer(&b)), nil -} - -// Unmap removes the memory mapping. -func Unmap[T ~[]byte | ~string](b T) error { - return nil -} diff --git a/mmap/mmap_stub.go b/mmap/mmap_stub.go new file mode 100644 index 0000000..eb3f668 --- /dev/null +++ b/mmap/mmap_stub.go @@ -0,0 +1,23 @@ +//go:build !unix && !windows + +package mmap + +import ( + "errors" + "os" + "unsafe" +) + +type mmapUnsupportError struct{} + +func (mmapUnsupportError) Error() string { + return "mmap is not supported on this platform" +} + +func (mmapUnsupportError) Is(target error) bool { + return target == errors.ErrUnsupported +} + +func readFile(_ *os.File, _ uintptr) (addr unsafe.Pointer, close func() error, err error) { + return nil, nil, mmapUnsupportError{} +} diff --git a/mmap/mmap_test.go b/mmap/mmap_test.go index 21b725a..7d152e5 100644 --- a/mmap/mmap_test.go +++ b/mmap/mmap_test.go @@ -19,17 +19,17 @@ func TestReadFile(t *testing.T) { t.Fatal(err) } - t.Logf("Created temporary test file: %s", name) + t.Logf("Created temporary test file: %q", name) - data, err := ReadFile[string](name) + data, close, err := ReadFile[string](name) if err != nil { t.Fatal(err) } if data != name { - t.Errorf("Expected file content %s, got %s", name, data) + t.Errorf("Expected file content %q, got %q", name, data) } - if err = Unmap(data); err != nil { + if err = close(); err != nil { t.Fatal(err) } } diff --git a/mmap/mmap_unix.go b/mmap/mmap_unix.go new file mode 100644 index 0000000..c40b64c --- /dev/null +++ b/mmap/mmap_unix.go @@ -0,0 +1,31 @@ +//go:build unix + +package mmap + +import ( + "os" + "unsafe" + + "golang.org/x/sys/unix" +) + +func readFile(f *os.File, size uintptr) (addr unsafe.Pointer, close func() error, err error) { + rawConn, err := f.SyscallConn() + if err != nil { + return nil, nil, err + } + + if cerr := rawConn.Control(func(fd uintptr) { + addr, err = unix.MmapPtr(int(fd), 0, nil, size, unix.PROT_READ, unix.MAP_SHARED) + }); cerr != nil { + return nil, nil, cerr + } + + if err != nil { + return nil, nil, err + } + + return addr, func() error { + return unix.MunmapPtr(addr, size) + }, nil +} diff --git a/mmap/mmap_unix_fast.go b/mmap/mmap_unix_fast.go deleted file mode 100644 index 247106a..0000000 --- a/mmap/mmap_unix_fast.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build (freebsd || linux) && (amd64 || arm64 || loong64 || mips64 || mips64le || ppc64 || ppc64le || riscv64 || sparc64) - -package mmap - -import ( - "os" - - "golang.org/x/sys/unix" -) - -func readFile(f *os.File, size int64) (uintptr, error) { - r0, _, e1 := unix.Syscall6(unix.SYS_MMAP, 0, uintptr(size), unix.PROT_READ, unix.MAP_SHARED, f.Fd(), 0) - if e1 != 0 { - return 0, os.NewSyscallError("mmap", e1) - } - return r0, nil -} - -func unmap(addr uintptr, length int) error { - _, _, e1 := unix.Syscall(unix.SYS_MUNMAP, addr, uintptr(length), 0) - if e1 != 0 { - return os.NewSyscallError("munmap", e1) - } - return nil -} diff --git a/mmap/mmap_unix_generic.go b/mmap/mmap_unix_generic.go deleted file mode 100644 index dba748b..0000000 --- a/mmap/mmap_unix_generic.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build unix && ((!freebsd && !linux) || (!amd64 && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64 && !sparc64)) - -package mmap - -import ( - "os" - "unsafe" - - "golang.org/x/sys/unix" -) - -func readFile(f *os.File, size int64) (uintptr, error) { - data, err := unix.Mmap(int(f.Fd()), 0, int(size), unix.PROT_READ, unix.MAP_SHARED) - if err != nil { - return 0, err - } - return *(*uintptr)(unsafe.Pointer(&data)), nil -} - -func unmap(addr uintptr, length int) error { - b := sliceHeader{ - data: addr, - len: length, - cap: length, - } - return unix.Munmap(*(*[]byte)(unsafe.Pointer(&b))) -} diff --git a/mmap/mmap_unixwindows.go b/mmap/mmap_unixwindows.go deleted file mode 100644 index 41a2325..0000000 --- a/mmap/mmap_unixwindows.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build unix || windows - -package mmap - -import ( - "os" - "unsafe" -) - -type sliceHeader struct { - data uintptr - len int - cap int -} - -// ReadFile maps the named file into memory for reading. -func ReadFile[T ~[]byte | ~string](name string) (data T, err error) { - f, err := os.Open(name) - if err != nil { - return - } - defer f.Close() - - fs, err := f.Stat() - if err != nil { - return - } - - size := fs.Size() - if size == 0 { - return - } - - addr, err := readFile(f, size) - if err != nil { - return - } - - b := sliceHeader{ - data: addr, - len: int(size), - cap: int(size), - } - return *(*T)(unsafe.Pointer(&b)), nil -} - -// Unmap removes the memory mapping. -func Unmap[T ~[]byte | ~string](data T) error { - if len(data) == 0 { - return nil - } - return unmap(*(*uintptr)(unsafe.Pointer(&data)), len(data)) -} diff --git a/mmap/mmap_windows.go b/mmap/mmap_windows.go index a4ec050..55c9e78 100644 --- a/mmap/mmap_windows.go +++ b/mmap/mmap_windows.go @@ -2,24 +2,24 @@ package mmap import ( "os" + "unsafe" "golang.org/x/sys/windows" ) -func readFile(f *os.File, size int64) (uintptr, error) { +func readFile(f *os.File, _ uintptr) (addr unsafe.Pointer, close func() error, err error) { handle, err := windows.CreateFileMapping(windows.Handle(f.Fd()), nil, windows.PAGE_READONLY, 0, 0, nil) if err != nil { - return 0, os.NewSyscallError("CreateFileMappingW", err) + return nil, nil, os.NewSyscallError("CreateFileMappingW", err) } defer windows.CloseHandle(handle) - addr, err := windows.MapViewOfFile(handle, windows.FILE_MAP_READ, 0, 0, 0) + addrUintptr, err := windows.MapViewOfFile(handle, windows.FILE_MAP_READ, 0, 0, 0) if err != nil { - return 0, os.NewSyscallError("MapViewOfFile", err) + return nil, nil, os.NewSyscallError("MapViewOfFile", err) } - return addr, nil -} - -func unmap(addr uintptr, length int) error { - return windows.UnmapViewOfFile(addr) + return *(*unsafe.Pointer)(unsafe.Pointer(&addrUintptr)), // workaround for unsafeptr check in go vet, see https://github.com/golang/go/issues/58625 + func() error { + return windows.UnmapViewOfFile(addrUintptr) + }, nil } diff --git a/prefixset/prefixset.go b/prefixset/prefixset.go index 50b8da3..9535b9e 100644 --- a/prefixset/prefixset.go +++ b/prefixset/prefixset.go @@ -17,11 +17,11 @@ type Config struct { // IPSet creates a prefix set from the configuration. func (psc Config) IPSet() (*netipx.IPSet, error) { - data, err := mmap.ReadFile[string](psc.Path) + data, close, err := mmap.ReadFile[string](psc.Path) if err != nil { return nil, fmt.Errorf("failed to load prefix set %s: %w", psc.Name, err) } - defer mmap.Unmap(data) + defer close() return IPSetFromText(data) }