Files
2026-08-30 17:41:45 +08:00

125 lines
3.4 KiB
Go

package main
import (
"context"
"fmt"
"os"
"slices"
"strings"
"github.com/sagernet/sing-box/daemon"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/emptypb"
)
var commandAPIUsbipStatus = &cobra.Command{
Use: "status",
Short: "Print the shared USB devices",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runAPIUsbipStatus()
},
}
func init() {
commandAPIUsbipStatus.Flags().StringVar(&commandAPIUsbipStatusFlagService, "service", "", "Restrict to one usbip-server tag")
commandAPIUsbip.AddCommand(commandAPIUsbipStatus)
}
func runAPIUsbipStatus() error {
clientConn, client, err := createAPIClient()
if err != nil {
return err
}
defer clientConn.Close()
ctx, cancel := context.WithCancel(globalCtx)
defer cancel()
servers, err := fetchUsbipServers(ctx, client)
if err != nil {
return err
}
if commandAPIUsbipStatusFlagService != "" {
server, findErr := resolveUsbipServer(servers, commandAPIUsbipStatusFlagService)
if findErr != nil {
return findErr
}
servers = []*daemon.USBIPServerStatus{server}
}
for index, server := range servers {
if len(servers) > 1 {
if index > 0 {
os.Stdout.WriteString("\n")
}
os.Stdout.WriteString(server.GetServerTag() + "\n")
}
table := tableWriter{
header: []string{"BUSID", "VID:PID", "PRODUCT", "STATE"},
emptyMessage: "no shared devices",
}
for _, device := range server.GetDevices() {
table.addRow(
device.GetBusId(),
usbipVendorProduct(uint16(device.GetDescriptor_().GetVendorId()), uint16(device.GetDescriptor_().GetProductId())),
device.GetDescriptor_().GetProduct(),
usbipDeviceStateString(device.GetState()),
)
}
table.flush()
}
return nil
}
// SubscribeUSBIPServerStatus never completes: with no dynamic usbip-server it sends
// a single empty update and then blocks until the client cancels.
func fetchUsbipServers(ctx context.Context, client daemon.StartedServiceClient) ([]*daemon.USBIPServerStatus, error) {
stream, err := client.SubscribeUSBIPServerStatus(ctx, &emptypb.Empty{})
if err != nil {
return nil, err
}
update, err := stream.Recv()
if err != nil {
return nil, err
}
return update.GetServers(), nil
}
func resolveUsbipServer(servers []*daemon.USBIPServerStatus, tag string) (*daemon.USBIPServerStatus, error) {
if tag != "" {
index := slices.IndexFunc(servers, func(it *daemon.USBIPServerStatus) bool {
return it.GetServerTag() == tag
})
if index == -1 {
return nil, E.New("usbip-server not found: ", tag, usbipServerTagHint(servers))
}
return servers[index], nil
}
switch len(servers) {
case 0:
return nil, E.New("no usbip-server found")
case 1:
return servers[0], nil
default:
return nil, E.New("multiple usbip-servers found, select one with --service", usbipServerTagHint(servers))
}
}
func usbipServerTagHint(servers []*daemon.USBIPServerStatus) string {
if len(servers) == 0 {
return " (no usbip-server with dynamic provider found)"
}
return " (known tags: " + strings.Join(common.Map(servers, func(it *daemon.USBIPServerStatus) string {
return it.GetServerTag()
}), ", ") + ")"
}
func usbipVendorProduct(vendorID uint16, productID uint16) string {
return fmt.Sprintf("%04x:%04x", vendorID, productID)
}
func usbipDeviceStateString(state daemon.USBDeviceState) string {
return strings.ToLower(strings.TrimPrefix(state.String(), "USB_DEVICE_STATE_"))
}