Linux-Portverwaltung für Windows-Erweiterung korrigieren
This commit is contained in:
parent
a6cd04ece4
commit
61bd03b863
4 changed files with 66 additions and 249 deletions
|
|
@ -11,6 +11,11 @@
|
||||||
{
|
{
|
||||||
"port": "COM100",
|
"port": "COM100",
|
||||||
"remote_device": "rotor"
|
"remote_device": "rotor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": "COM101",
|
||||||
|
"remote_device": "radio"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,13 @@
|
||||||
* Link unter ~/.rs2322tcp/virtual/.
|
* Link unter ~/.rs2322tcp/virtual/.
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"git.lang-dieter.de/rs2322tcp/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -77,6 +76,9 @@ func ensureVirtualPortDirectory() error {
|
||||||
//
|
//
|
||||||
// The link itself is located below ~/.rs2322tcp/virtual/ and points to the
|
// The link itself is located below ~/.rs2322tcp/virtual/ and points to the
|
||||||
// current PTY, for example /dev/pts/2.
|
// current PTY, for example /dev/pts/2.
|
||||||
|
//
|
||||||
|
// The public /dev/ttyUSBxxx installation link is deliberately never
|
||||||
|
// modified here.
|
||||||
func setVirtualPortLink(portPath string, target string) error {
|
func setVirtualPortLink(portPath string, target string) error {
|
||||||
if portPath == "" {
|
if portPath == "" {
|
||||||
return fmt.Errorf("virtual port path is empty")
|
return fmt.Errorf("virtual port path is empty")
|
||||||
|
|
@ -95,8 +97,10 @@ func setVirtualPortLink(portPath string, target string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove an existing link. The installation-owned outer /dev/ttyUSBxxx
|
// Replace only the user-owned internal link.
|
||||||
// link is deliberately never touched here.
|
//
|
||||||
|
// The installation-owned /dev/ttyUSBxxx link is deliberately never
|
||||||
|
// touched here.
|
||||||
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"remove existing virtual port link %q: %w",
|
"remove existing virtual port link %q: %w",
|
||||||
|
|
@ -117,9 +121,12 @@ func setVirtualPortLink(portPath string, target string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeVirtualPortLink removes the internal link for a virtual port.
|
// removeVirtualPortLink removes the user-owned internal link for a virtual
|
||||||
|
// port.
|
||||||
//
|
//
|
||||||
// The public /dev/ttyUSBxxx link is never modified.
|
// This function is retained for explicit link-management operations and
|
||||||
|
// tests. It is NOT called by ManagedVirtualPort.Close(), because the
|
||||||
|
// installation-owned symlink chain must remain available.
|
||||||
func removeVirtualPortLink(portPath string) error {
|
func removeVirtualPortLink(portPath string) error {
|
||||||
if portPath == "" {
|
if portPath == "" {
|
||||||
return fmt.Errorf("virtual port path is empty")
|
return fmt.Errorf("virtual port path is empty")
|
||||||
|
|
@ -140,73 +147,3 @@ func removeVirtualPortLink(portPath string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// existingVirtualPortLinks returns all public virtual-port paths that
|
|
||||||
// currently exist as symbolic links.
|
|
||||||
//
|
|
||||||
// Only ports inside the configured virtual-port range are considered.
|
|
||||||
// The public /dev/ttyUSBxxx links are only inspected; they are never
|
|
||||||
// created, changed, or removed by this function.
|
|
||||||
//
|
|
||||||
// This check only requires read access and therefore does not require
|
|
||||||
// root privileges.
|
|
||||||
func existingVirtualPortLinks(
|
|
||||||
cfg config.VirtualPortRangeConfig,
|
|
||||||
) ([]string, error) {
|
|
||||||
return existingVirtualPortLinksInDirectory(
|
|
||||||
cfg,
|
|
||||||
"/dev",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// existingVirtualPortLinksInDirectory returns all virtual-port paths in
|
|
||||||
// directory that currently exist as symbolic links.
|
|
||||||
//
|
|
||||||
// This helper is deliberately separated from existingVirtualPortLinks so
|
|
||||||
// the filesystem inspection can be tested without modifying /dev.
|
|
||||||
func existingVirtualPortLinksInDirectory(
|
|
||||||
cfg config.VirtualPortRangeConfig,
|
|
||||||
directory string,
|
|
||||||
) ([]string, error) {
|
|
||||||
if directory == "" {
|
|
||||||
return nil, fmt.Errorf("virtual port directory is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := NewVirtualPortManager(cfg)
|
|
||||||
if manager == nil {
|
|
||||||
return nil, fmt.Errorf("virtual port manager is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
links := make([]string, 0)
|
|
||||||
|
|
||||||
for number := cfg.First; number <= cfg.Last; number++ {
|
|
||||||
portName := filepath.Base(
|
|
||||||
manager.PortPath(number),
|
|
||||||
)
|
|
||||||
portPath := filepath.Join(directory, portName)
|
|
||||||
|
|
||||||
info, err := os.Lstat(portPath)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"inspect virtual port %q: %w",
|
|
||||||
portPath,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Mode()&os.ModeSymlink == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
links = append(
|
|
||||||
links,
|
|
||||||
filepath.Join(directory, portName),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return links, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,186 +9,39 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Beschreibung:
|
* Beschreibung:
|
||||||
* Linux-spezifische Erzeugung und Verwaltung virtueller rs2322tcp-Ports.
|
* Linux-spezifische Erzeugung eines virtuellen rs2322tcp-Ports.
|
||||||
*
|
*
|
||||||
* Der öffentliche /dev/ttyUSBxxx-Eintrag wird bei der Installation
|
* Die gemeinsame Verwaltung von ManagedVirtualPort und VirtualPortManager
|
||||||
* bereitgestellt. Der Client verwaltet ausschließlich den internen Link
|
* befindet sich in virtual_port_manager_open.go.
|
||||||
* unter ~/.rs2322tcp/virtual/.
|
|
||||||
*
|
*
|
||||||
* Virtuelle Ports können entweder automatisch über den nächsten freien Port
|
* Unter Linux wird der PTY über newVirtualSerial() erzeugt und der interne
|
||||||
* oder gezielt über eine vorgegebene Portnummer geöffnet werden.
|
* Link unter ~/.rs2322tcp/virtual/ auf diesen PTY gesetzt.
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
// openReserved creates the actual Linux virtual serial port for an already
|
||||||
// Managed virtual port
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// ManagedVirtualPort verbindet einen reservierten Port mit einer
|
|
||||||
// VirtualSerial-Instanz und verwaltet deren Lebenszyklus.
|
|
||||||
//
|
|
||||||
// Der Mutex schützt den internen Port-Pointer gegen gleichzeitige Zugriffe
|
|
||||||
// aus Read, Write und Close.
|
|
||||||
type ManagedVirtualPort struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
port *VirtualPort
|
|
||||||
manager *VirtualPortManager
|
|
||||||
number int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the device path visible to the external application.
|
|
||||||
func (p *ManagedVirtualPort) Path() string {
|
|
||||||
if p == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
if p.port == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.port.Path()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from the underlying virtual serial device.
|
|
||||||
func (p *ManagedVirtualPort) Read(b []byte) (int, error) {
|
|
||||||
if p == nil {
|
|
||||||
return 0, fmt.Errorf("managed virtual port is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
port := p.port
|
|
||||||
p.mu.Unlock()
|
|
||||||
|
|
||||||
if port == nil {
|
|
||||||
return 0, fmt.Errorf("managed virtual port is closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return port.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes data to the underlying virtual serial device.
|
|
||||||
func (p *ManagedVirtualPort) Write(b []byte) (int, error) {
|
|
||||||
if p == nil {
|
|
||||||
return 0, fmt.Errorf("managed virtual port is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
port := p.port
|
|
||||||
p.mu.Unlock()
|
|
||||||
|
|
||||||
if port == nil {
|
|
||||||
return 0, fmt.Errorf("managed virtual port is closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return port.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes the internal link, closes the PTY and releases the
|
|
||||||
// reservation.
|
|
||||||
//
|
|
||||||
// The managed port is detached from the object before the underlying PTY
|
|
||||||
// is closed. This prevents a concurrent Read or Write from accessing the
|
|
||||||
// managed port pointer after Close has taken ownership of it.
|
|
||||||
func (p *ManagedVirtualPort) Close() error {
|
|
||||||
if p == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
|
|
||||||
port := p.port
|
|
||||||
p.port = nil
|
|
||||||
|
|
||||||
manager := p.manager
|
|
||||||
number := p.number
|
|
||||||
|
|
||||||
p.mu.Unlock()
|
|
||||||
|
|
||||||
if port == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstErr error
|
|
||||||
|
|
||||||
if err := removeVirtualPortLink(port.Path()); err != nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := port.Close(); err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
|
|
||||||
if manager != nil {
|
|
||||||
manager.Release(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Open
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Open creates a new virtual serial port using the next free port number.
|
|
||||||
//
|
|
||||||
// The internal PTY is created by newVirtualSerial(). The internal
|
|
||||||
// ~/.rs2322tcp/virtual/ttyUSBxxx link is then pointed at the PTY.
|
|
||||||
//
|
|
||||||
// Open retains the automatic allocation behavior used by the existing
|
|
||||||
// client code.
|
|
||||||
func (m *VirtualPortManager) Open() (*ManagedVirtualPort, error) {
|
|
||||||
if m == nil {
|
|
||||||
return nil, fmt.Errorf("virtual port manager is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
number, err := m.Reserve()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.openReserved(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenSpecific creates a new virtual serial port using exactly the specified
|
|
||||||
// port number.
|
|
||||||
//
|
|
||||||
// Unlike Open, this method does not select the next free port. The requested
|
|
||||||
// number is reserved explicitly and is therefore suitable for the
|
|
||||||
// VirtualPortConfig.Port value loaded from client.json.
|
|
||||||
//
|
|
||||||
// If creation of the PTY or the internal link fails, the reservation is
|
|
||||||
// released again.
|
|
||||||
func (m *VirtualPortManager) OpenSpecific(number int) (*ManagedVirtualPort, error) {
|
|
||||||
if m == nil {
|
|
||||||
return nil, fmt.Errorf("virtual port manager is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.ReserveSpecific(number); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.openReserved(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Internal open helper
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// openReserved creates the actual virtual serial port for an already
|
|
||||||
// reserved port number.
|
// reserved port number.
|
||||||
//
|
//
|
||||||
// The caller must reserve the port before calling this method.
|
// The caller must reserve the port before calling this method.
|
||||||
func (m *VirtualPortManager) openReserved(number int) (*ManagedVirtualPort, error) {
|
//
|
||||||
|
// The public /dev/ttyUSBxxx link is not modified here. The installation-owned
|
||||||
|
// link remains untouched. Only the user-owned internal link below
|
||||||
|
// ~/.rs2322tcp/virtual/ is created or updated to point to the newly created
|
||||||
|
// PTY.
|
||||||
|
func openReserved(
|
||||||
|
m *VirtualPortManager,
|
||||||
|
number int,
|
||||||
|
) (*ManagedVirtualPort, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, fmt.Errorf("virtual port manager is nil")
|
||||||
|
}
|
||||||
|
|
||||||
portPath := m.PortPath(number)
|
portPath := m.PortPath(number)
|
||||||
|
|
||||||
serial, err := newVirtualSerial()
|
serial, err := newVirtualSerial()
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Beschreibung:
|
* Beschreibung:
|
||||||
* Linux-spezifische Tests für die Erzeugung virtueller rs2322tcp-Ports.
|
* Linux-spezifische Tests für die Erzeugung und Verwaltung virtueller
|
||||||
|
* rs2322tcp-Ports.
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*/
|
*/
|
||||||
package client
|
package client
|
||||||
|
|
@ -71,10 +72,20 @@ func TestVirtualPortManagerOpen(t *testing.T) {
|
||||||
t.Fatalf("Close() failed: %v", err)
|
t.Fatalf("Close() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Lstat(linkPath); !os.IsNotExist(err) {
|
targetAfterClose, err := os.Readlink(linkPath)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"internal link still exists after Close(): %q",
|
"internal link does not exist after Close(): %q: %v",
|
||||||
linkPath,
|
linkPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetAfterClose != target {
|
||||||
|
t.Errorf(
|
||||||
|
"internal link target after Close() = %q, want %q",
|
||||||
|
targetAfterClose,
|
||||||
|
target,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,13 +136,24 @@ func TestVirtualPortManagerOpenSpecific(t *testing.T) {
|
||||||
t.Fatalf("Close() failed: %v", err)
|
t.Fatalf("Close() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Lstat(linkPath); !os.IsNotExist(err) {
|
targetAfterClose, err := os.Readlink(linkPath)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"internal link still exists after Close(): %q",
|
"internal link does not exist after Close(): %q: %v",
|
||||||
linkPath,
|
linkPath,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetAfterClose != target {
|
||||||
|
t.Errorf(
|
||||||
|
"internal link target after Close() = %q, want %q",
|
||||||
|
targetAfterClose,
|
||||||
|
target,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVirtualPortManagerOpenUsesNextPort(t *testing.T) {
|
func TestVirtualPortManagerOpenUsesNextPort(t *testing.T) {
|
||||||
tempHome := t.TempDir()
|
tempHome := t.TempDir()
|
||||||
t.Setenv("HOME", tempHome)
|
t.Setenv("HOME", tempHome)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue