Start work on a SHM implementation in Go
This commit is contained in:
parent
7a1140cd03
commit
d01d5297b8
151
tools/utils/shm/shm.go
Normal file
151
tools/utils/shm/shm.go
Normal file
@ -0,0 +1,151 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
var ErrPatternHasSeparator = errors.New("The specified pattern has file path separators in it")
|
||||
var ErrPatternTooLong = errors.New("The specified pattern for the SHM name is too long")
|
||||
|
||||
type ErrNotSupported struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (self *ErrNotSupported) Error() string {
|
||||
return fmt.Sprintf("POSIX shared memory not supported on this platform: with underlying error: %v", self.err)
|
||||
}
|
||||
|
||||
// prefix_and_suffix splits pattern by the last wildcard "*", if applicable,
|
||||
// returning prefix as the part before "*" and suffix as the part after "*".
|
||||
func prefix_and_suffix(pattern string) (prefix, suffix string, err error) {
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
if os.IsPathSeparator(pattern[i]) {
|
||||
return "", "", ErrPatternHasSeparator
|
||||
}
|
||||
}
|
||||
if pos := strings.LastIndexByte(pattern, '*'); pos != -1 {
|
||||
prefix, suffix = pattern[:pos], pattern[pos+1:]
|
||||
} else {
|
||||
prefix = pattern
|
||||
}
|
||||
return prefix, suffix, nil
|
||||
}
|
||||
|
||||
func next_random() string {
|
||||
num := rand.Uint32()
|
||||
return strconv.FormatUint(uint64(num), 16)
|
||||
}
|
||||
|
||||
type MMap interface {
|
||||
Close() error
|
||||
Unlink() error
|
||||
Slice() []byte
|
||||
Name() string
|
||||
}
|
||||
|
||||
type ProtectionFlags int
|
||||
|
||||
const (
|
||||
READ ProtectionFlags = iota
|
||||
COPY
|
||||
RDWR
|
||||
EXEC
|
||||
ANON
|
||||
)
|
||||
|
||||
func mmap(sz int, inprot ProtectionFlags, anonymous bool, fd int, off int64) ([]byte, error) {
|
||||
flags := unix.MAP_SHARED
|
||||
prot := unix.PROT_READ
|
||||
switch {
|
||||
case inprot© != 0:
|
||||
prot |= unix.PROT_WRITE
|
||||
flags = unix.MAP_PRIVATE
|
||||
case inprot&RDWR != 0:
|
||||
prot |= unix.PROT_WRITE
|
||||
}
|
||||
if inprot&EXEC != 0 {
|
||||
prot |= unix.PROT_EXEC
|
||||
}
|
||||
if anonymous {
|
||||
flags |= unix.MAP_ANON
|
||||
}
|
||||
|
||||
b, err := unix.Mmap(fd, off, sz, prot, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type file_based_mmap struct {
|
||||
f *os.File
|
||||
region []byte
|
||||
unlinked bool
|
||||
}
|
||||
|
||||
func file_mmap(f *os.File, size uint64, access ProtectionFlags, truncate bool) (MMap, error) {
|
||||
if truncate {
|
||||
err := truncate_or_unlink(f, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
region, err := mmap(int(size), access, false, int(f.Fd()), 0)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
return nil, err
|
||||
}
|
||||
return &file_based_mmap{f: f, region: region}, nil
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Name() string {
|
||||
return self.f.Name()
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Slice() []byte {
|
||||
return self.region
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Close() error {
|
||||
err := self.f.Close()
|
||||
self.region = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Unlink() (err error) {
|
||||
if self.unlinked {
|
||||
return nil
|
||||
}
|
||||
self.unlinked = true
|
||||
return os.Remove(self.f.Name())
|
||||
}
|
||||
|
||||
func CreateTemp(pattern string, size uint64) (MMap, error) {
|
||||
return create_temp(pattern, size)
|
||||
}
|
||||
|
||||
func truncate_or_unlink(ans *os.File, size uint64) (err error) {
|
||||
for {
|
||||
err = unix.Ftruncate(int(ans.Fd()), int64(size))
|
||||
if !errors.Is(err, unix.EINTR) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
ans.Close()
|
||||
os.Remove(ans.Name())
|
||||
return fmt.Errorf("Failed to ftruncate() SHM file %s to size: %d with error: %w", ans.Name(), size, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
36
tools/utils/shm/shm_fs.go
Normal file
36
tools/utils/shm/shm_fs.go
Normal file
@ -0,0 +1,36 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
//go:build linux || netbsd
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func create_temp(pattern string, size uint64) (MMap, error) {
|
||||
ans, err := os.CreateTemp(SHM_DIR, pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file_mmap(ans, size, RDWR, true)
|
||||
}
|
||||
|
||||
func Open(name string) (MMap, error) {
|
||||
if !filepath.IsAbs(name) {
|
||||
name = filepath.Join(SHM_DIR, name)
|
||||
}
|
||||
ans, err := os.OpenFile(name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := os.Stat(name)
|
||||
if err != nil {
|
||||
ans.Close()
|
||||
return nil, err
|
||||
}
|
||||
return file_mmap(ans, uint64(s.Size()), READ, false)
|
||||
}
|
||||
40
tools/utils/shm/shm_openbsd.go
Normal file
40
tools/utils/shm/shm_openbsd.go
Normal file
@ -0,0 +1,40 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const SHM_DIR = "/tmp"
|
||||
|
||||
func create_temp(pattern string) (*os.File, error) {
|
||||
ans, err := os.CreateTemp(SHM_DIR, pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func Open(name string) (*os.File, error) {
|
||||
if !filepath.IsAbs(name) {
|
||||
name = filepath.Join(SHM_DIR, name)
|
||||
}
|
||||
ans, err := os.OpenFile(name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func Unlink(name string) error {
|
||||
if !filepath.IsAbs(name) {
|
||||
name = filepath.Join(SHM_DIR, name)
|
||||
}
|
||||
return os.Remove(name)
|
||||
|
||||
}
|
||||
152
tools/utils/shm/shm_syscall.go
Normal file
152
tools/utils/shm/shm_syscall.go
Normal file
@ -0,0 +1,152 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
//go:build darwin || freebsd
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
// ByteSliceFromString makes a zero terminated byte slice from the string
|
||||
func ByteSliceFromString(s string) []byte {
|
||||
a := make([]byte, len(s)+1)
|
||||
copy(a, s)
|
||||
return a
|
||||
}
|
||||
|
||||
func BytePtrFromString(s string) *byte {
|
||||
a := ByteSliceFromString(s)
|
||||
return &a[0]
|
||||
}
|
||||
|
||||
func shm_unlink(name string) (err error) {
|
||||
bname := BytePtrFromString(name)
|
||||
for {
|
||||
_, _, errno := unix.Syscall(unix.SYS_SHM_OPEN, uintptr(unsafe.Pointer(bname)), 0, 0)
|
||||
if errno != unix.EINTR {
|
||||
if errno != 0 {
|
||||
err = fmt.Errorf("shm_unlink() failed with error: %w", errno)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func shm_open(name string, flags, perm int) (ans *os.File, err error) {
|
||||
bname := BytePtrFromString(name)
|
||||
var fd uintptr
|
||||
var errno unix.Errno
|
||||
for {
|
||||
fd, _, errno = unix.Syscall(unix.SYS_SHM_OPEN, uintptr(unsafe.Pointer(bname)), uintptr(flags), uintptr(perm))
|
||||
if errno != unix.EINTR {
|
||||
if errno != 0 {
|
||||
err = fmt.Errorf("shm_open() failed with error: %w", errno)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
ans = os.NewFile(fd, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type syscall_based_mmap struct {
|
||||
f *os.File
|
||||
region []byte
|
||||
unlinked bool
|
||||
}
|
||||
|
||||
func syscall_mmap(f *os.File, size uint64, access ProtectionFlags, truncate bool) (MMap, error) {
|
||||
if truncate {
|
||||
err := truncate_or_unlink(f, size)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("truncate failed with error: %w", err)
|
||||
}
|
||||
}
|
||||
region, err := mmap(int(size), access, false, int(f.Fd()), 0)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
return nil, fmt.Errorf("mmap failed with error: %w", err)
|
||||
}
|
||||
return &syscall_based_mmap{f: f, region: region}, nil
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Name() string {
|
||||
return self.f.Name()
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Slice() []byte {
|
||||
return self.region
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Close() error {
|
||||
err := self.f.Close()
|
||||
self.region = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Unlink() (err error) {
|
||||
if self.unlinked {
|
||||
return nil
|
||||
}
|
||||
self.unlinked = true
|
||||
return shm_unlink(self.Name())
|
||||
}
|
||||
|
||||
func create_temp(pattern string, size uint64) (ans MMap, err error) {
|
||||
var prefix, suffix string
|
||||
prefix, suffix, err = prefix_and_suffix(pattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if SHM_REQUIRED_PREFIX != "" && !strings.HasPrefix(pattern, SHM_REQUIRED_PREFIX) {
|
||||
// FreeBSD requires name to start with /
|
||||
prefix = SHM_REQUIRED_PREFIX + prefix
|
||||
}
|
||||
var f *os.File
|
||||
try := 0
|
||||
for {
|
||||
name := prefix + next_random() + suffix
|
||||
if len(name) > SHM_NAME_MAX {
|
||||
return nil, ErrPatternTooLong
|
||||
}
|
||||
f, err = shm_open(name, os.O_EXCL|os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil && (errors.Is(err, fs.ErrExist) || errors.Unwrap(err) == unix.EEXIST) {
|
||||
try += 1
|
||||
if try > 10000 {
|
||||
return nil, &os.PathError{Op: "createtemp", Path: prefix + "*" + suffix, Err: ErrExist}
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return syscall_mmap(f, size, RDWR, true)
|
||||
}
|
||||
|
||||
func Open(name string) (MMap, error) {
|
||||
ans, err := shm_open(name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := os.Stat(name)
|
||||
if err != nil {
|
||||
ans.Close()
|
||||
return nil, err
|
||||
}
|
||||
return syscall_mmap(ans, uint64(s.Size()), READ, false)
|
||||
}
|
||||
43
tools/utils/shm/shm_test.go
Normal file
43
tools/utils/shm/shm_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func TestSHM(t *testing.T) {
|
||||
data := make([]byte, 13347)
|
||||
rand.Read(data)
|
||||
mm, err := CreateTemp("test-kitty-shm-", uint64(len(data)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
copy(mm.Slice(), data)
|
||||
mm.Close()
|
||||
|
||||
g, err := Open(mm.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data2 := g.Slice()
|
||||
if !reflect.DeepEqual(data, data2) {
|
||||
t.Fatalf("Could not read back written data: Written data length: %d Read data length: %d", len(data), len(data2))
|
||||
}
|
||||
g.Close()
|
||||
g.Unlink()
|
||||
_, err = os.Stat(mm.Name())
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
t.Fatalf("Unlinking %s did not work", mm.Name())
|
||||
}
|
||||
|
||||
}
|
||||
6
tools/utils/shm/specific_darwin.go
Normal file
6
tools/utils/shm/specific_darwin.go
Normal file
@ -0,0 +1,6 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
const SHM_NAME_MAX = 30
|
||||
const SHM_REQUIRED_PREFIX = ""
|
||||
6
tools/utils/shm/specific_freebsd.go
Normal file
6
tools/utils/shm/specific_freebsd.go
Normal file
@ -0,0 +1,6 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
const SHM_NAME_MAX = 1023
|
||||
const SHM_REQUIRED_PREFIX = "/"
|
||||
12
tools/utils/shm/specific_linux.go
Normal file
12
tools/utils/shm/specific_linux.go
Normal file
@ -0,0 +1,12 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const SHM_DIR = "/dev/shm"
|
||||
const SHM_NAME_MAX = 1023
|
||||
12
tools/utils/shm/specific_netbsd.go
Normal file
12
tools/utils/shm/specific_netbsd.go
Normal file
@ -0,0 +1,12 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const SHM_DIR = "/var/shm"
|
||||
const SHM_NAME_MAX = 1023
|
||||
Loading…
x
Reference in New Issue
Block a user