Update App version to 0.0.4

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2025-11-11 20:38:22 +08:00
parent 4e82b8a11c
commit 5e17c52afc
41 changed files with 3537 additions and 598 deletions

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"time"
"github.com/prometheus/procfs"
@@ -406,33 +407,79 @@ func (c *ChangeSet) ApplyChanges() error {
}
func (c *ChangeSet) applyChange(change *FileChange) error {
// 记录操作详情
contentPreview := ""
if len(change.ExpectedContent) > 0 && len(change.ExpectedContent) <= 64 {
contentPreview = string(change.ExpectedContent)
} else if len(change.ExpectedContent) > 64 {
contentPreview = string(change.ExpectedContent[:64]) + "..."
}
defaultLogger.Debug().
Str("operation", FileChangeResolvedActionString[change.Action()]).
Str("path", change.Path).
Str("content_preview", contentPreview).
Int("content_length", len(change.ExpectedContent)).
Msg("executing file operation")
switch change.Action() {
case FileChangeResolvedActionWriteFile:
defaultLogger.Debug().Str("path", change.Path).Msg("writing file")
return os.WriteFile(change.Path, change.ExpectedContent, 0644)
case FileChangeResolvedActionUpdateFile:
return os.WriteFile(change.Path, change.ExpectedContent, 0644)
defaultLogger.Debug().Str("path", change.Path).Msg("updating file")
err := os.WriteFile(change.Path, change.ExpectedContent, 0644)
if err != nil && strings.Contains(err.Error(), "device or resource busy") {
defaultLogger.Error().
Str("path", change.Path).
Str("content", contentPreview).
Msg("device or resource busy - gadget may be bound to UDC")
return fmt.Errorf("%w (hint: gadget may be bound to UDC, try unbinding first)", err)
}
return err
case FileChangeResolvedActionCreateFile:
defaultLogger.Debug().Str("path", change.Path).Msg("creating file")
return os.WriteFile(change.Path, change.ExpectedContent, 0644)
case FileChangeResolvedActionCreateSymlink:
return os.Symlink(string(change.ExpectedContent), change.Path)
target := string(change.ExpectedContent)
defaultLogger.Debug().
Str("path", change.Path).
Str("target", target).
Msg("creating symlink")
return os.Symlink(target, change.Path)
case FileChangeResolvedActionRecreateSymlink:
target := string(change.ExpectedContent)
defaultLogger.Debug().
Str("path", change.Path).
Str("target", target).
Msg("recreating symlink")
if err := os.Remove(change.Path); err != nil {
return fmt.Errorf("failed to remove symlink: %w", err)
}
return os.Symlink(string(change.ExpectedContent), change.Path)
return os.Symlink(target, change.Path)
case FileChangeResolvedActionReorderSymlinks:
defaultLogger.Debug().
Str("path", change.Path).
Int("symlink_count", len(change.ParamSymlinks)).
Msg("reordering symlinks")
return recreateSymlinks(change, nil)
case FileChangeResolvedActionCreateDirectory:
defaultLogger.Debug().Str("path", change.Path).Msg("creating directory")
return os.MkdirAll(change.Path, 0755)
case FileChangeResolvedActionRemove:
defaultLogger.Debug().Str("path", change.Path).Msg("removing file")
return os.Remove(change.Path)
case FileChangeResolvedActionRemoveDirectory:
defaultLogger.Debug().Str("path", change.Path).Msg("removing directory")
return os.RemoveAll(change.Path)
case FileChangeResolvedActionTouch:
defaultLogger.Debug().Str("path", change.Path).Msg("touching file")
return os.Chtimes(change.Path, time.Now(), time.Now())
case FileChangeResolvedActionMountConfigFS:
defaultLogger.Debug().Str("path", change.Path).Msg("mounting configfs")
return mountConfigFS(change.Path)
case FileChangeResolvedActionMountFunctionFS:
defaultLogger.Debug().Str("path", change.Path).Msg("mounting functionfs")
return mountFunctionFS(change.Path)
case FileChangeResolvedActionDoNothing:
return nil

View File

@@ -5,7 +5,9 @@ import (
"fmt"
"os"
"os/exec"
"path"
"strings"
"time"
)
type gadgetConfigItem struct {
@@ -238,19 +240,73 @@ func (u *UsbGadget) Init() error {
udcs := getUdcs()
if len(udcs) < 1 {
u.log.Warn().Msg("no UDC found, skipping USB stack init")
return u.logWarn("no udc found, skipping USB stack init", nil)
}
u.udc = udcs[0]
if err := u.ensureGadgetUnbound(); err != nil {
u.log.Warn().Err(err).Msg("failed to ensure gadget is unbound, will continue")
} else {
u.log.Info().Msg("gadget unbind check completed")
}
err := u.configureUsbGadget(false)
if err != nil {
u.log.Error().Err(err).
Str("udc", u.udc).
Interface("enabled_devices", u.enabledDevices).
Msg("USB gadget initialization FAILED")
return u.logError("unable to initialize USB stack", err)
}
return nil
}
func (u *UsbGadget) ensureGadgetUnbound() error {
udcPath := path.Join(u.kvmGadgetPath, "UDC")
if _, err := os.Stat(u.kvmGadgetPath); os.IsNotExist(err) {
return nil
}
udcContent, err := os.ReadFile(udcPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("failed to read UDC file: %w", err)
}
currentUDC := strings.TrimSpace(string(udcContent))
if currentUDC == "" || currentUDC == "none" {
return nil
}
u.log.Info().
Str("current_udc", currentUDC).
Str("target_udc", u.udc).
Msg("unbinding existing UDC before reconfiguration")
if err := u.UnbindUDC(); err != nil {
u.log.Warn().Err(err).Msg("failed to unbind via UDC file, trying DWC3")
if err := u.UnbindUDCToDWC3(); err != nil {
return fmt.Errorf("failed to unbind UDC: %w", err)
}
}
time.Sleep(200 * time.Millisecond)
if content, err := os.ReadFile(udcPath); err == nil {
if strings.TrimSpace(string(content)) != "none" {
u.log.Warn().Msg("UDC still bound after unbind attempt")
}
}
return nil
}
func (u *UsbGadget) UpdateGadgetConfig() error {
u.configLock.Lock()
defer u.configLock.Unlock()
@@ -266,13 +322,48 @@ func (u *UsbGadget) UpdateGadgetConfig() error {
}
func (u *UsbGadget) configureUsbGadget(resetUsb bool) error {
u.log.Info().
Bool("reset_usb", resetUsb).
Msg("configuring USB gadget via transaction")
return u.WithTransaction(func() error {
u.log.Info().Msg("Transaction: Mounting configfs")
u.tx.MountConfigFS()
u.log.Info().Msg("Transaction: Creating config path")
u.tx.CreateConfigPath()
u.log.Info().Msg("Transaction: Writing gadget configuration")
u.tx.WriteGadgetConfig()
if resetUsb {
u.log.Info().Msg("Transaction: Rebinding USB")
u.tx.RebindUsb(true)
}
return nil
})
}
func (u *UsbGadget) VerifyMassStorage() error {
if !u.enabledDevices.MassStorage {
return nil
}
massStoragePath := path.Join(u.kvmGadgetPath, "functions/mass_storage.usb0")
if _, err := os.Stat(massStoragePath); err != nil {
return fmt.Errorf("mass_storage function not found: %w", err)
}
lunPath := path.Join(massStoragePath, "lun.0")
if _, err := os.Stat(lunPath); err != nil {
return fmt.Errorf("mass_storage LUN not found: %w", err)
}
configLink := path.Join(u.configC1Path, "mass_storage.usb0")
if _, err := os.Lstat(configLink); err != nil {
return fmt.Errorf("mass_storage symlink not found: %w", err)
}
u.log.Info().Msg("mass storage verified")
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"path"
"path/filepath"
"sort"
"strings"
"github.com/rs/zerolog"
)
@@ -151,7 +152,10 @@ func (tx *UsbGadgetTransaction) CreateConfigPath() {
}
func (tx *UsbGadgetTransaction) WriteGadgetConfig() {
tx.log.Info().Msg("=== Building USB gadget configuration ===")
// create kvm gadget path
tx.log.Info().Str("path", tx.kvmGadgetPath).Msg("creating kvm gadget path")
tx.mkdirAll(
"gadget",
tx.kvmGadgetPath,
@@ -162,22 +166,43 @@ func (tx *UsbGadgetTransaction) WriteGadgetConfig() {
deps := make([]string, 0)
deps = append(deps, tx.kvmGadgetPath)
enabledCount := 0
disabledCount := 0
for _, val := range tx.orderedConfigItems {
key := val.key
item := val.item
// check if the item is enabled in the config
if !tx.isGadgetConfigItemEnabled(key) {
tx.log.Debug().
Str("key", key).
Str("device", item.device).
Msg("disabling gadget item (not enabled in config)")
tx.DisableGadgetItemConfig(item)
disabledCount++
continue
}
tx.log.Info().
Str("key", key).
Str("device", item.device).
Uint("order", item.order).
Msg("configuring gadget item")
deps = tx.writeGadgetItemConfig(item, deps)
enabledCount++
}
tx.log.Info().
Int("enabled_items", enabledCount).
Int("disabled_items", disabledCount).
Msg("gadget items configuration completed")
if tx.isGadgetConfigItemEnabled("mtp") {
tx.log.Info().Msg("MTP enabled, mounting functionfs and binding UDC")
tx.MountFunctionFS()
tx.WriteUDC(true)
} else {
tx.log.Info().Msg("MTP disabled, binding UDC directly")
tx.WriteUDC(false)
}
}
@@ -226,6 +251,22 @@ func (tx *UsbGadgetTransaction) writeGadgetItemConfig(item gadgetConfigItem, dep
beforeChange = append(beforeChange, tx.getDisableKeys()...)
}
// 对于 mass storage LUN 属性,需要确保 UDC 未绑定
needsUnbind := strings.Contains(gadgetItemPath, "mass_storage") && strings.Contains(gadgetItemPath, "lun.")
if needsUnbind {
// 添加一个 unbind UDC 的步骤(如果已绑定)
udcPath := path.Join(tx.kvmGadgetPath, "UDC")
tx.addFileChange("udc-unbind", RequestedFileChange{
Key: "udc-unbind-check",
Path: udcPath,
ExpectedState: FileStateFile,
Description: "check and unbind UDC if needed",
DependsOn: files,
When: "beforeChange", // 只在需要时执行
})
beforeChange = append(beforeChange, "udc-unbind-check")
}
if len(item.attrs) > 0 {
// write attributes for the item
files = append(files, tx.writeGadgetAttrs(
@@ -355,9 +396,12 @@ func (tx *UsbGadgetTransaction) WriteUDC(mtpServer bool) {
}
func (tx *UsbGadgetTransaction) RebindUsb(ignoreUnbindError bool) {
unbindPath := path.Join(tx.dwc3Path, "unbind")
bindPath := path.Join(tx.dwc3Path, "bind")
// remove the gadget from the UDC
tx.addFileChange("udc", RequestedFileChange{
Path: path.Join(tx.dwc3Path, "unbind"),
Path: unbindPath,
ExpectedState: FileStateFileWrite,
ExpectedContent: []byte(tx.udc),
Description: "unbind UDC",
@@ -366,10 +410,10 @@ func (tx *UsbGadgetTransaction) RebindUsb(ignoreUnbindError bool) {
})
// bind the gadget to the UDC
tx.addFileChange("udc", RequestedFileChange{
Path: path.Join(tx.dwc3Path, "bind"),
Path: bindPath,
ExpectedState: FileStateFileWrite,
ExpectedContent: []byte(tx.udc),
Description: "bind UDC",
DependsOn: []string{path.Join(tx.dwc3Path, "unbind")},
DependsOn: []string{unbindPath},
})
}

View File

@@ -2,9 +2,11 @@ package usbgadget
import (
"context"
"errors"
"fmt"
"os"
"reflect"
"strings"
"time"
)
@@ -118,6 +120,10 @@ func (u *UsbGadget) SetOnKeyboardStateChange(f func(state KeyboardState)) {
u.onKeyboardStateChange = &f
}
func (u *UsbGadget) SetOnHidDeviceMissing(f func(device string, err error)) {
u.onHidDeviceMissing = &f
}
func (u *UsbGadget) GetKeyboardState() KeyboardState {
u.keyboardStateLock.Lock()
defer u.keyboardStateLock.Unlock()
@@ -177,6 +183,16 @@ func (u *UsbGadget) openKeyboardHidFile() error {
var err error
u.keyboardHidFile, err = os.OpenFile("/dev/hidg0", os.O_RDWR, 0666)
if err != nil {
if errors.Is(err, os.ErrNotExist) || strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "no such device") {
u.log.Error().
Str("device", "hidg0").
Str("device_name", "keyboard").
Err(err).
Msg("HID device file missing, gadget may need reinitialization")
if u.onHidDeviceMissing != nil {
(*u.onHidDeviceMissing)("keyboard", err)
}
}
return fmt.Errorf("failed to open hidg0: %w", err)
}

View File

@@ -1,8 +1,10 @@
package usbgadget
import (
"errors"
"fmt"
"os"
"strings"
)
var absoluteMouseConfig = gadgetConfigItem{
@@ -69,6 +71,17 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
var err error
u.absMouseHidFile, err = os.OpenFile("/dev/hidg1", os.O_RDWR, 0666)
if err != nil {
if errors.Is(err, os.ErrNotExist) || strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "no such device") {
u.log.Error().
Str("device", "hidg1").
Str("device_name", "absolute_mouse").
Err(err).
Msg("HID device file missing, gadget may need reinitialization")
if u.onHidDeviceMissing != nil {
(*u.onHidDeviceMissing)("absolute_mouse", err)
}
}
return fmt.Errorf("failed to open hidg1: %w", err)
}
}

View File

@@ -1,8 +1,10 @@
package usbgadget
import (
"errors"
"fmt"
"os"
"strings"
)
var relativeMouseConfig = gadgetConfigItem{
@@ -59,7 +61,19 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
var err error
u.relMouseHidFile, err = os.OpenFile("/dev/hidg2", os.O_RDWR, 0666)
if err != nil {
return fmt.Errorf("failed to open hidg1: %w", err)
if errors.Is(err, os.ErrNotExist) || strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "no such device") {
u.log.Error().
Str("device", "hidg2").
Str("device_name", "relative_mouse").
Err(err).
Msg("HID device file missing, gadget may need reinitialization")
if u.onHidDeviceMissing != nil {
(*u.onHidDeviceMissing)("relative_mouse", err)
}
}
return fmt.Errorf("failed to open hidg2: %w", err)
}
}

View File

@@ -80,6 +80,7 @@ type UsbGadget struct {
txLock sync.Mutex
onKeyboardStateChange *func(state KeyboardState)
onHidDeviceMissing *func(device string, err error)
log *zerolog.Logger
@@ -140,8 +141,7 @@ func newUsbGadget(name string, configMap map[string]gadgetConfigItem, enabledDev
absMouseAccumulatedWheelY: 0,
}
if err := g.Init(); err != nil {
logger.Error().Err(err).Msg("failed to init USB gadget")
return nil
logger.Error().Err(err).Msg("failed to init USB gadget (will retry later)")
}
return g