rs2322tcp/internal/client/virtual_port_linux.go

149 lines
3.5 KiB
Go

//go:build linux
/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/virtual_port_linux.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Linux-spezifische Verwaltung der internen Symlinks für virtuelle
* rs2322tcp-Seriellschnittstellen.
*
* Der öffentliche Gerätename /dev/ttyUSBxxx wird bei der Installation
* vorbereitet. Der Client verwaltet ausschließlich den benutzerbezogenen
* Link unter ~/.rs2322tcp/virtual/.
* ============================================================================
*/
package client
import (
"fmt"
"os"
"path/filepath"
)
const (
virtualDirectoryName = ".rs2322tcp/virtual"
)
// virtualPortDirectory returns the directory in the current user's home
// directory used for the internal virtual-port links.
func virtualPortDirectory() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("get user home directory: %w", err)
}
return filepath.Join(home, virtualDirectoryName), nil
}
// virtualPortLinkPath returns the path of the internal link for a virtual
// serial port.
func virtualPortLinkPath(portPath string) (string, error) {
directory, err := virtualPortDirectory()
if err != nil {
return "", err
}
return filepath.Join(directory, filepath.Base(portPath)), nil
}
// ensureVirtualPortDirectory creates the internal virtual-port directory
// if it does not already exist.
func ensureVirtualPortDirectory() error {
directory, err := virtualPortDirectory()
if err != nil {
return err
}
if err := os.MkdirAll(directory, 0755); err != nil {
return fmt.Errorf(
"create virtual port directory %q: %w",
directory,
err,
)
}
return nil
}
// setVirtualPortLink creates or replaces the internal link for a virtual
// port.
//
// The link itself is located below ~/.rs2322tcp/virtual/ and points to the
// 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 {
if portPath == "" {
return fmt.Errorf("virtual port path is empty")
}
if target == "" {
return fmt.Errorf("virtual port target is empty")
}
if err := ensureVirtualPortDirectory(); err != nil {
return err
}
linkPath, err := virtualPortLinkPath(portPath)
if err != nil {
return err
}
// Replace only the user-owned internal link.
//
// The installation-owned /dev/ttyUSBxxx link is deliberately never
// touched here.
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf(
"remove existing virtual port link %q: %w",
linkPath,
err,
)
}
if err := os.Symlink(target, linkPath); err != nil {
return fmt.Errorf(
"create virtual port link %q -> %q: %w",
linkPath,
target,
err,
)
}
return nil
}
// removeVirtualPortLink removes the user-owned internal link for a virtual
// port.
//
// 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 {
if portPath == "" {
return fmt.Errorf("virtual port path is empty")
}
linkPath, err := virtualPortLinkPath(portPath)
if err != nil {
return err
}
if err := os.Remove(linkPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf(
"remove existing virtual port link %q: %w",
linkPath,
err,
)
}
return nil
}