mirror of
https://github.com/kovidgoyal/kitty.git
synced 2025-12-13 20:36:22 +01:00
Switch to external shm package
This commit is contained in:
1
go.mod
1
go.mod
@@ -13,6 +13,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1
|
||||
github.com/kovidgoyal/go-parallel v1.1.1
|
||||
github.com/kovidgoyal/go-shm v1.0.0
|
||||
github.com/kovidgoyal/imaging v1.8.8
|
||||
github.com/seancfoley/ipaddress-go v1.7.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.10
|
||||
|
||||
2
go.sum
2
go.sum
@@ -32,6 +32,8 @@ github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1 h1:rMY/hWfcVzBm6BL
|
||||
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1/go.mod h1:RbNG3Q1g6GUy1/WzWVx+S24m7VKyvl57vV+cr2hpt50=
|
||||
github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811uiUXyI7ok=
|
||||
github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
|
||||
github.com/kovidgoyal/go-shm v1.0.0 h1:HJEel9D1F9YhULvClEHJLawoRSj/1u/EDV7MJbBPgQo=
|
||||
github.com/kovidgoyal/go-shm v1.0.0/go.mod h1:Yzb80Xf9L3kaoB2RGok9hHwMIt7Oif61kT6t3+VnZds=
|
||||
github.com/kovidgoyal/imaging v1.8.8 h1:PohlAOYuokFtmt6sjhgA90YAUKhuuL3i0dhd5gepp4g=
|
||||
github.com/kovidgoyal/imaging v1.8.8/go.mod h1:GAbZkbyB86PSfosof5EnS2o6N15yUk9Vy2r61EWy1Wg=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/graphics"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/loop"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
@@ -15,12 +15,12 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/tools/tui"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/graphics"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/loop"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/tools/cli"
|
||||
"github.com/kovidgoyal/kitty/tools/tty"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/tools/cli"
|
||||
"github.com/kovidgoyal/kitty/tools/themes"
|
||||
"github.com/kovidgoyal/kitty/tools/tty"
|
||||
@@ -37,7 +38,6 @@ import (
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/secrets"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shlex"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/tools/cli"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
@@ -4,19 +4,62 @@ package pytest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/kittens/ssh"
|
||||
"github.com/kovidgoyal/kitty/tools/cli"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func test_integration_with_python(args []string) (rc int, err error) {
|
||||
switch args[0] {
|
||||
default:
|
||||
return 1, fmt.Errorf("Unknown test type: %s", args[0])
|
||||
case "read":
|
||||
data, err := shm.ReadWithSizeAndUnlink(args[1])
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
case "write":
|
||||
data, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
mmap, err := shm.CreateTemp("shmtest-", uint64(len(data)+shm.NUM_BYTES_FOR_SIZE))
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if err = shm.WriteWithSize(mmap, data, 0); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
mmap.Close()
|
||||
fmt.Println(mmap.Name())
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func shm_entry_point(root *cli.Command) {
|
||||
root.AddSubCommand(&cli.Command{
|
||||
Name: "shm",
|
||||
OnlyArgsAllowed: true,
|
||||
Run: func(cmd *cli.Command, args []string) (rc int, err error) {
|
||||
return test_integration_with_python(args)
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
func EntryPoint(root *cli.Command) {
|
||||
root = root.AddSubCommand(&cli.Command{
|
||||
Name: "__pytest__",
|
||||
Hidden: true,
|
||||
})
|
||||
shm.TestEntryPoint(root)
|
||||
shm_entry_point(root)
|
||||
ssh.TestEntryPoint(root)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/kitty/tools/tui"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/loop"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/images"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/go-shm"
|
||||
"github.com/kovidgoyal/imaging/nrgb"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/utils/shm"
|
||||
|
||||
"github.com/kovidgoyal/imaging"
|
||||
)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func Fallocate_simple(fd int, size int64) (err error) {
|
||||
for {
|
||||
if err = unix.Fallocate(fd, 0, 0, size); !errors.Is(err, unix.EINTR) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
//go:build !linux
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func Fallocate_simple(fd int, size int64) (err error) {
|
||||
return errors.ErrUnsupported
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/cli"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
type MMap interface {
|
||||
Close() error
|
||||
Unlink() error
|
||||
Slice() []byte
|
||||
Name() string
|
||||
IsFileSystemBacked() bool
|
||||
FileSystemName() string
|
||||
Stat() (fs.FileInfo, error)
|
||||
Flush() error
|
||||
Seek(offset int64, whence int) (ret int64, err error)
|
||||
Read(b []byte) (n int, err error)
|
||||
Write(b []byte) (n int, err error)
|
||||
}
|
||||
|
||||
type AccessFlags int
|
||||
|
||||
const (
|
||||
READ AccessFlags = iota
|
||||
WRITE
|
||||
COPY
|
||||
)
|
||||
|
||||
func mmap(sz int, access AccessFlags, fd int, off int64) ([]byte, error) {
|
||||
flags := unix.MAP_SHARED
|
||||
prot := unix.PROT_READ
|
||||
switch access {
|
||||
case COPY:
|
||||
prot |= unix.PROT_WRITE
|
||||
flags = unix.MAP_PRIVATE
|
||||
case WRITE:
|
||||
prot |= unix.PROT_WRITE
|
||||
}
|
||||
|
||||
b, err := unix.Mmap(fd, off, sz, prot, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func munmap(s []byte) error {
|
||||
return unix.Munmap(s)
|
||||
}
|
||||
|
||||
func CreateTemp(pattern string, size uint64) (MMap, error) {
|
||||
return create_temp(pattern, size)
|
||||
}
|
||||
|
||||
func truncate_or_unlink(ans *os.File, size uint64, unlink func(string) error) (err error) {
|
||||
fd := int(ans.Fd())
|
||||
sz := int64(size)
|
||||
if err = Fallocate_simple(fd, sz); err != nil {
|
||||
if !errors.Is(err, errors.ErrUnsupported) {
|
||||
return fmt.Errorf("fallocate() failed on fd from shm_open(%s) with size: %d with error: %w", ans.Name(), size, err)
|
||||
}
|
||||
for {
|
||||
if err = unix.Ftruncate(fd, sz); !errors.Is(err, unix.EINTR) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
_ = ans.Close()
|
||||
_ = unlink(ans.Name())
|
||||
return fmt.Errorf("Failed to ftruncate() SHM file %s to size: %d with error: %w", ans.Name(), size, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const NUM_BYTES_FOR_SIZE = 4
|
||||
|
||||
var ErrRegionTooSmall = errors.New("mmaped region too small")
|
||||
|
||||
func WriteWithSize(self MMap, b []byte, at int) error {
|
||||
if len(self.Slice()) < at+len(b)+NUM_BYTES_FOR_SIZE {
|
||||
return ErrRegionTooSmall
|
||||
}
|
||||
binary.BigEndian.PutUint32(self.Slice()[at:], uint32(len(b)))
|
||||
copy(self.Slice()[at+NUM_BYTES_FOR_SIZE:], b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadWithSize(self MMap, at int) ([]byte, error) {
|
||||
s := self.Slice()[at:]
|
||||
if len(s) < NUM_BYTES_FOR_SIZE {
|
||||
return nil, ErrRegionTooSmall
|
||||
}
|
||||
size := int(binary.BigEndian.Uint32(self.Slice()[at : at+NUM_BYTES_FOR_SIZE]))
|
||||
s = s[NUM_BYTES_FOR_SIZE:]
|
||||
if len(s) < size {
|
||||
return nil, ErrRegionTooSmall
|
||||
}
|
||||
return s[:size], nil
|
||||
}
|
||||
|
||||
func ReadWithSizeAndUnlink(name string, file_callback ...func(fs.FileInfo) error) ([]byte, error) {
|
||||
mmap, err := Open(name, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(file_callback) > 0 {
|
||||
s, err := mmap.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to stat SHM file with error: %w", err)
|
||||
}
|
||||
for _, f := range file_callback {
|
||||
err = f(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
mmap.Close()
|
||||
_ = mmap.Unlink()
|
||||
}()
|
||||
slice, err := ReadWithSize(mmap, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ans := make([]byte, len(slice))
|
||||
copy(ans, slice)
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func Read(self MMap, b []byte) (n int, err error) {
|
||||
pos, err := self.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if pos < 0 {
|
||||
pos = 0
|
||||
}
|
||||
s := self.Slice()
|
||||
sz := int64(len(s))
|
||||
if pos >= sz {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(b, s[pos:])
|
||||
_, err = self.Seek(int64(n), io.SeekCurrent)
|
||||
return
|
||||
}
|
||||
|
||||
func Write(self MMap, b []byte) (n int, err error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
pos, _ := self.Seek(0, io.SeekCurrent)
|
||||
if pos < 0 {
|
||||
pos = 0
|
||||
}
|
||||
s := self.Slice()
|
||||
if pos >= int64(len(s)) {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
n = copy(s[pos:], b)
|
||||
if _, err = self.Seek(int64(n), io.SeekCurrent); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if n < len(b) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func test_integration_with_python(args []string) (rc int, err error) {
|
||||
switch args[0] {
|
||||
default:
|
||||
return 1, fmt.Errorf("Unknown test type: %s", args[0])
|
||||
case "read":
|
||||
data, err := ReadWithSizeAndUnlink(args[1])
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
case "write":
|
||||
data, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
mmap, err := CreateTemp("shmtest-", uint64(len(data)+NUM_BYTES_FOR_SIZE))
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if err = WriteWithSize(mmap, data, 0); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
mmap.Close()
|
||||
fmt.Println(mmap.Name())
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func TestEntryPoint(root *cli.Command) {
|
||||
root.AddSubCommand(&cli.Command{
|
||||
Name: "shm",
|
||||
OnlyArgsAllowed: true,
|
||||
Run: func(cmd *cli.Command, args []string) (rc int, err error) {
|
||||
return test_integration_with_python(args)
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
//go:build linux || netbsd || openbsd || dragonfly
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type file_based_mmap struct {
|
||||
f *os.File
|
||||
pos int64
|
||||
region []byte
|
||||
unlinked bool
|
||||
special_name string
|
||||
}
|
||||
|
||||
func ShmUnlink(name string) error {
|
||||
if runtime.GOOS == "openbsd" {
|
||||
return os.Remove(openbsd_shm_path(name))
|
||||
}
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
return os.Remove(filepath.Join(SHM_DIR, name))
|
||||
}
|
||||
|
||||
func file_mmap(f *os.File, size uint64, access AccessFlags, truncate bool, special_name string) (MMap, error) {
|
||||
if truncate {
|
||||
err := truncate_or_unlink(f, size, os.Remove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
region, err := mmap(int(size), access, int(f.Fd()), 0)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
return nil, err
|
||||
}
|
||||
return &file_based_mmap{f: f, region: region, special_name: special_name}, nil
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Seek(offset int64, whence int) (ret int64, err error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
self.pos = offset
|
||||
case io.SeekEnd:
|
||||
self.pos = int64(len(self.region)) + offset
|
||||
case io.SeekCurrent:
|
||||
self.pos += offset
|
||||
}
|
||||
return self.pos, nil
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Read(b []byte) (n int, err error) {
|
||||
return Read(self, b)
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Write(b []byte) (n int, err error) {
|
||||
return Write(self, b)
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Stat() (fs.FileInfo, error) {
|
||||
return self.f.Stat()
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Name() string {
|
||||
if self.special_name != "" {
|
||||
return self.special_name
|
||||
}
|
||||
return filepath.Base(self.f.Name())
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Flush() error {
|
||||
return unix.Msync(self.region, unix.MS_SYNC)
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) FileSystemName() string {
|
||||
return self.f.Name()
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Slice() []byte {
|
||||
return self.region
|
||||
}
|
||||
|
||||
func (self *file_based_mmap) Close() (err error) {
|
||||
if self.region != nil {
|
||||
self.f.Close()
|
||||
err = munmap(self.region)
|
||||
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 (self *file_based_mmap) IsFileSystemBacked() bool { return true }
|
||||
|
||||
func openbsd_shm_path(name string) string {
|
||||
hash := sha256.Sum256(utils.UnsafeStringToBytes(name))
|
||||
return filepath.Join(SHM_DIR, utils.UnsafeBytesToString(hash[:])+".shm")
|
||||
}
|
||||
|
||||
func file_path_from_name(name string) string {
|
||||
// See https://github.com/openbsd/src/blob/master/lib/libc/gen/shm_open.c
|
||||
if runtime.GOOS == "openbsd" {
|
||||
return openbsd_shm_path(name)
|
||||
}
|
||||
return filepath.Join(SHM_DIR, name)
|
||||
}
|
||||
|
||||
func create_temp(pattern string, size uint64) (ans MMap, err error) {
|
||||
special_name := ""
|
||||
var prefix, suffix string
|
||||
prefix, suffix, err = prefix_and_suffix(pattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var f *os.File
|
||||
try := 0
|
||||
for {
|
||||
name := prefix + utils.RandomFilename() + suffix
|
||||
path := file_path_from_name(name)
|
||||
f, err = os.OpenFile(path, os.O_EXCL|os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrExist) {
|
||||
try += 1
|
||||
if try > 10000 {
|
||||
return nil, &os.PathError{Op: "createtemp", Path: prefix + "*" + suffix, Err: fs.ErrExist}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, &ErrNotSupported{err: err}
|
||||
}
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
return file_mmap(f, size, WRITE, true, special_name)
|
||||
}
|
||||
|
||||
func open(name string) (*os.File, error) {
|
||||
ans, err := os.OpenFile(file_path_from_name(name), os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if _, serr := os.Stat(SHM_DIR); serr != nil && errors.Is(serr, fs.ErrNotExist) {
|
||||
return nil, &ErrNotSupported{err: serr}
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
|
||||
func Open(name string, size uint64) (MMap, error) {
|
||||
ans, err := open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size == 0 {
|
||||
s, err := ans.Stat()
|
||||
if err != nil {
|
||||
ans.Close()
|
||||
return nil, fmt.Errorf("Failed to stat SHM file with error: %w", err)
|
||||
}
|
||||
size = uint64(s.Size())
|
||||
}
|
||||
return file_mmap(ans, size, READ, false, name)
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
//go:build darwin || freebsd
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
|
||||
"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_UNLINK, uintptr(unsafe.Pointer(bname)), 0, 0)
|
||||
if errno != unix.EINTR {
|
||||
if errno != 0 {
|
||||
if errno == unix.ENOENT {
|
||||
err = fs.ErrNotExist
|
||||
} else {
|
||||
err = fmt.Errorf("shm_unlink() failed with error: %w", errno)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ShmUnlink(name string) error {
|
||||
return shm_unlink(name)
|
||||
}
|
||||
|
||||
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
|
||||
pos int64
|
||||
region []byte
|
||||
unlinked bool
|
||||
}
|
||||
|
||||
func syscall_mmap(f *os.File, size uint64, access AccessFlags, truncate bool) (MMap, error) {
|
||||
if truncate {
|
||||
err := truncate_or_unlink(f, size, shm_unlink)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("truncate failed with error: %w", err)
|
||||
}
|
||||
}
|
||||
region, err := mmap(int(size), access, int(f.Fd()), 0)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
_ = shm_unlink(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) Stat() (fs.FileInfo, error) {
|
||||
return self.f.Stat()
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Flush() error {
|
||||
return unix.Msync(self.region, unix.MS_SYNC)
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Slice() []byte {
|
||||
return self.region
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Close() (err error) {
|
||||
if self.region != nil {
|
||||
self.f.Close()
|
||||
munmap(self.region)
|
||||
self.region = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Unlink() (err error) {
|
||||
if self.unlinked {
|
||||
return nil
|
||||
}
|
||||
self.unlinked = true
|
||||
return shm_unlink(self.Name())
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Seek(offset int64, whence int) (ret int64, err error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
self.pos = offset
|
||||
case io.SeekEnd:
|
||||
self.pos = int64(len(self.region)) + offset
|
||||
case io.SeekCurrent:
|
||||
self.pos += offset
|
||||
}
|
||||
return self.pos, nil
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Read(b []byte) (n int, err error) {
|
||||
return Read(self, b)
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) Write(b []byte) (n int, err error) {
|
||||
return Write(self, b)
|
||||
}
|
||||
|
||||
func (self *syscall_based_mmap) IsFileSystemBacked() bool { return false }
|
||||
func (self *syscall_based_mmap) FileSystemName() string { return "" }
|
||||
|
||||
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 + utils.RandomFilename() + 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: fs.ErrExist}
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return syscall_mmap(f, size, WRITE, true)
|
||||
}
|
||||
|
||||
func Open(name string, size uint64) (MMap, error) {
|
||||
ans, err := shm_open(name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size == 0 {
|
||||
s, err := ans.Stat()
|
||||
if err != nil {
|
||||
ans.Close()
|
||||
return nil, fmt.Errorf("Failed to stat SHM file with error: %w", err)
|
||||
}
|
||||
size = uint64(s.Size())
|
||||
}
|
||||
return syscall_mmap(ans, size, READ, false)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"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)
|
||||
err = mm.Flush()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to msync() with error: %v", err)
|
||||
}
|
||||
err = mm.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to close with error: %v", err)
|
||||
}
|
||||
|
||||
g, err := Open(mm.Name(), uint64(len(data)))
|
||||
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))
|
||||
}
|
||||
err = g.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to close with error: %v", err)
|
||||
}
|
||||
err = g.Unlink()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unlink with error: %v", err)
|
||||
}
|
||||
g, err = Open(mm.Name(), uint64(len(data)))
|
||||
if err == nil {
|
||||
t.Fatalf("Unlinking failed could re-open the SHM data. Data equal: %v Data length: %d", reflect.DeepEqual(g.Slice(), data), len(g.Slice()))
|
||||
}
|
||||
if mm.IsFileSystemBacked() {
|
||||
_, err = os.Stat(mm.FileSystemName())
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
t.Fatalf("Unlinking %s did not work", mm.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
const SHM_NAME_MAX = 30
|
||||
const SHM_REQUIRED_PREFIX = ""
|
||||
const SHM_DIR = ""
|
||||
@@ -1,12 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
// https://www.dragonflybsd.org/cgi/web-man?command=shm_open§ion=3
|
||||
const SHM_DIR = "/var/run/shm"
|
||||
@@ -1,7 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
const SHM_NAME_MAX = 1023
|
||||
const SHM_REQUIRED_PREFIX = "/"
|
||||
const SHM_DIR = ""
|
||||
@@ -1,11 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const SHM_DIR = "/dev/shm"
|
||||
@@ -1,11 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const SHM_DIR = "/var/shm"
|
||||
@@ -1,11 +0,0 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package shm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const SHM_DIR = "/tmp"
|
||||
Reference in New Issue
Block a user