From f9f569461a4eb71bf922ef15cd80b7f3a23ae1bf Mon Sep 17 00:00:00 2001 From: Mykhailo Kan Date: Mon, 16 Oct 2023 14:56:18 +0200 Subject: [PATCH 1/2] add file locking capabilities --- go.mod | 7 +++++++ go.sum | 4 ++++ hashing.go | 13 +++++++++++++ serial_linux.go | 41 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hashing.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..915666b --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/mv-kan/serial + +go 1.20 + +require golang.org/x/sys v0.13.0 + +require github.com/gofrs/flock v0.8.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d68ba34 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/hashing.go b/hashing.go new file mode 100644 index 0000000..573a8da --- /dev/null +++ b/hashing.go @@ -0,0 +1,13 @@ +package serial + +import ( + "crypto/sha256" + "encoding/hex" +) + +// 6 letter hash +func shortHash(str string) string { + bs := sha256.Sum256([]byte(str)) + hash := hex.EncodeToString(bs[:])[:6] + return hash +} diff --git a/serial_linux.go b/serial_linux.go index 65420f3..3f67c2b 100644 --- a/serial_linux.go +++ b/serial_linux.go @@ -1,16 +1,25 @@ +//go:build linux // +build linux package serial import ( + "errors" "fmt" "os" + "path" + "path/filepath" "time" "unsafe" + "github.com/gofrs/flock" "golang.org/x/sys/unix" ) +var ( + ErrAlreadyLocked = errors.New("this port already has locked file") +) + func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) { var bauds = map[int]uint32{ 50: unix.B50, @@ -46,17 +55,36 @@ func openPort(name string, baud int, databits byte, parity Parity, stopbits Stop } rate, ok := bauds[baud] - if !ok { - return nil, fmt.Errorf("Unrecognized baud rate") + return nil, fmt.Errorf("unrecognized baud rate") } - f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666) + fpath, err := filepath.Abs(name) + if err != nil { + return nil, err + } + lockdir := "/var/lock" + lockpath := path.Join(lockdir, fmt.Sprintf("%s_%s.lock", filepath.Base(fpath), shortHash(fpath))) + lock := flock.New(lockpath) + + locked, err := lock.TryLock() + if err != nil { + return nil, err + } + if !locked { + return nil, ErrAlreadyLocked + } + + f, err := os.OpenFile(fpath, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666) if err != nil { return nil, err } defer func() { + if err != nil { + lock.Unlock() + os.Remove(lockpath) + } if err != nil && f != nil { f.Close() } @@ -125,13 +153,14 @@ func openPort(name string, baud int, databits byte, parity Parity, stopbits Stop return } - return &Port{f: f}, nil + return &Port{f: f, lock: lock}, nil } type Port struct { // We intentionly do not use an "embedded" struct so that we // don't export File - f *os.File + f *os.File + lock *flock.Flock } func (p *Port) Read(b []byte) (n int, err error) { @@ -160,5 +189,7 @@ func (p *Port) Flush() error { } func (p *Port) Close() (err error) { + p.lock.Unlock() + os.Remove(p.lock.Path()) return p.f.Close() } From cac16970e46c5a19f54458ec02fa0d9faefe1b9c Mon Sep 17 00:00:00 2001 From: Mykhailo Kan Date: Mon, 16 Oct 2023 15:21:49 +0200 Subject: [PATCH 2/2] test locking functionality --- basic_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/basic_test.go b/basic_test.go index 7b9af3e..cabb537 100644 --- a/basic_test.go +++ b/basic_test.go @@ -1,13 +1,41 @@ +//go:build linux // +build linux +/* +How to run tests +// socat virtual serial ports +socat -d -d pty,rawer,echo=0,link=/tmp/ttyV0 pty,rawer,echo=0,link=/tmp/ttyV1 +// run test +PORT0=/tmp/ttyV0 PORT1=/tmp/ttyV1 go test -tags=linux -p 1 -v . +*/ package serial import ( + "errors" "os" "testing" "time" ) +func TestLocking(t *testing.T) { + port0 := "/tmp/ttyV0" //os.Getenv("PORT0") + if port0 == "" { + t.Skip("Skipping test because PORT0 environment variable is not set") + } + c0 := &Config{Name: port0, Baud: 115200} + + s1, err := OpenPort(c0) + if err != nil { + t.Fatal(err) + } + + _, err = OpenPort(c0) + if !errors.Is(ErrAlreadyLocked, err) { + t.Fatal(err) + } + s1.Close() +} + func TestConnection(t *testing.T) { port0 := os.Getenv("PORT0") port1 := os.Getenv("PORT1") @@ -66,4 +94,7 @@ func TestConnection(t *testing.T) { if c >= exp { t.Fatalf("Expected less than %v read, got %v", exp, c) } + // these close() may cause some issues + // s1.Close() + // s2.Close() }