//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. 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 } // Remove an existing link. The installation-owned outer /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 internal link for a virtual port. // // The public /dev/ttyUSBxxx link is never modified. 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 virtual port link %q: %w", linkPath, err, ) } return nil }