style: fix whitespace/gofumpt/golines in linux/unix build-tagged files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-03-05 02:16:07 +09:00
parent 0db2afef68
commit 12c61899f5
4 changed files with 63 additions and 477 deletions

View file

@ -8,465 +8,285 @@ import (
)
// I2C ioctl constants from Linux kernel headers (<linux/i2c-dev.h>, <linux/i2c.h>)
const (
i2cSlave = 0x0703 // Set slave address (fails if in use by driver)
i2cFuncs = 0x0705 // Query adapter functionality bitmask
i2cSmbus = 0x0720 // Perform SMBus transaction
// I2C_FUNC capability bits
i2cFuncSmbusQuick = 0x00010000
i2cFuncSmbusQuick = 0x00010000
i2cFuncSmbusReadByte = 0x00020000
// SMBus transaction types
i2cSmbusRead = 0
i2cSmbusRead = 0
i2cSmbusWrite = 1
// SMBus protocol sizes
i2cSmbusQuick = 0
i2cSmbusByte = 1
i2cSmbusByte = 1
)
// i2cSmbusData matches the kernel union i2c_smbus_data (34 bytes max).
// For quick and byte transactions only the first byte is used (if at all).
type i2cSmbusData [34]byte
// i2cSmbusArgs matches the kernel struct i2c_smbus_ioctl_data.
type i2cSmbusArgs struct {
readWrite uint8
command uint8
size uint32
data *i2cSmbusData
command uint8
size uint32
data *i2cSmbusData
}
// smbusProbe performs a single SMBus probe at the given address.
// Uses SMBus Quick Write (safest) or falls back to SMBus Read Byte for
// EEPROM address ranges where quick write can corrupt AT24RF08 chips.
// This matches i2cdetect's MODE_AUTO behavior.
func smbusProbe(fd int, addr int, hasQuick bool) bool {
// EEPROM ranges: use read byte (quick write can corrupt AT24RF08)
useReadByte := (addr >= 0x30 && addr <= 0x37) || (addr >= 0x50 && addr <= 0x5F)
if !useReadByte && hasQuick {
// SMBus Quick Write: [START] [ADDR|W] [ACK/NACK] [STOP]
// Safest probe — no data transferred
args := i2cSmbusArgs{
readWrite: i2cSmbusWrite,
command: 0,
size: i2cSmbusQuick,
data: nil,
command: 0,
size: i2cSmbusQuick,
data: nil,
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
return errno == 0
}
// SMBus Read Byte: [START] [ADDR|R] [ACK/NACK] [DATA] [STOP]
var data i2cSmbusData
args := i2cSmbusArgs{
readWrite: i2cSmbusRead,
command: 0,
size: i2cSmbusByte,
data: &data,
command: 0,
size: i2cSmbusByte,
data: &data,
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSmbus, uintptr(unsafe.Pointer(&args)))
return errno == 0
}
// scan probes valid 7-bit addresses on a bus for connected devices.
// Uses the same hybrid probe strategy as i2cdetect's MODE_AUTO:
// SMBus Quick Write for most addresses, SMBus Read Byte for EEPROM ranges.
func (t *I2CTool) scan(args map[string]any) *ToolResult {
bus, errResult := parseI2CBus(args)
if errResult != nil {
return errResult
}
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and i2c-dev module)", devPath, err))
}
defer syscall.Close(fd)
// Query adapter capabilities to determine available probe methods.
// I2C_FUNCS writes an unsigned long, which is word-sized on Linux.
var funcs uintptr
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cFuncs, uintptr(unsafe.Pointer(&funcs)))
if errno != 0 {
return ErrorResult(fmt.Sprintf("failed to query I2C adapter capabilities on %s: %v", devPath, errno))
}
hasQuick := funcs&i2cFuncSmbusQuick != 0
hasReadByte := funcs&i2cFuncSmbusReadByte != 0
if !hasQuick && !hasReadByte {
return ErrorResult(
fmt.Sprintf("I2C adapter %s supports neither SMBus Quick nor Read Byte — cannot probe safely", devPath),
fmt.Sprintf(
"I2C adapter %s supports neither SMBus Quick nor Read Byte — cannot probe safely",
devPath,
),
)
}
type deviceEntry struct {
Address string `json:"address"`
Status string `json:"status,omitempty"`
Status string `json:"status,omitempty"`
}
var found []deviceEntry
// Scan 0x08-0x77, skipping I2C reserved addresses 0x00-0x07
for addr := 0x08; addr <= 0x77; addr++ {
// Set slave address — EBUSY means a kernel driver owns this address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 {
if errno == syscall.EBUSY {
found = append(found, deviceEntry{
Address: fmt.Sprintf("0x%02x", addr),
Status: "busy (in use by kernel driver)",
Status: "busy (in use by kernel driver)",
})
}
continue
}
if smbusProbe(fd, addr, hasQuick) {
found = append(found, deviceEntry{
Address: fmt.Sprintf("0x%02x", addr),
})
}
}
if len(found) == 0 {
return SilentResult(fmt.Sprintf("No devices found on %s. Check wiring and pull-up resistors.", devPath))
}
result, _ := json.MarshalIndent(map[string]any{
"bus": devPath,
"bus": devPath,
"devices": found,
"count": len(found),
"count": len(found),
}, "", " ")
return SilentResult(fmt.Sprintf("Scan of %s:\n%s", devPath, string(result)))
}
// readDevice reads bytes from an I2C device, optionally at a specific register
// readDevice reads bytes from an I2C device, optionally at a specific register.
func (t *I2CTool) readDevice(args map[string]any) *ToolResult {
bus, errResult := parseI2CBus(args)
if errResult != nil {
return errResult
}
addr, errResult := parseI2CAddress(args)
if errResult != nil {
return errResult
}
length := 1
if l, ok := args["length"].(float64); ok {
length = int(l)
}
if length < 1 || length > 256 {
return ErrorResult("length must be between 1 and 256")
}
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
}
defer syscall.Close(fd)
// Set slave address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 {
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
}
// If register is specified, write it first
if regFloat, ok := args["register"].(float64); ok {
reg := int(regFloat)
if reg < 0 || reg > 255 {
return ErrorResult("register must be between 0x00 and 0xFF")
}
_, err = syscall.Write(fd, []byte{byte(reg)})
if err != nil {
return ErrorResult(fmt.Sprintf("failed to write register 0x%02x: %v", reg, err))
}
}
// Read data
buf := make([]byte, length)
n, err := syscall.Read(fd, buf)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to read from device 0x%02x: %v", addr, err))
}
// Format as hex bytes
hexBytes := make([]string, n)
intBytes := make([]int, n)
for i := 0; i < n; i++ {
hexBytes[i] = fmt.Sprintf("0x%02x", buf[i])
intBytes[i] = int(buf[i])
}
result, _ := json.MarshalIndent(map[string]any{
"bus": devPath,
"bus": devPath,
"address": fmt.Sprintf("0x%02x", addr),
"bytes": intBytes,
"hex": hexBytes,
"length": n,
"bytes": intBytes,
"hex": hexBytes,
"length": n,
}, "", " ")
return SilentResult(string(result))
}
// writeDevice writes bytes to an I2C device, optionally at a specific register
// writeDevice writes bytes to an I2C device, optionally at a specific register.
func (t *I2CTool) writeDevice(args map[string]any) *ToolResult {
confirm, _ := args["confirm"].(bool)
if !confirm {
return ErrorResult(
"write operations require confirm: true. Please confirm with the user before writing to I2C devices, as incorrect writes can misconfigure hardware.",
"write operations require confirm: true." +
" Please confirm with the user before writing to I2C devices," +
" as incorrect writes can misconfigure hardware.",
)
}
bus, errResult := parseI2CBus(args)
if errResult != nil {
return errResult
}
addr, errResult := parseI2CAddress(args)
if errResult != nil {
return errResult
}
dataRaw, ok := args["data"].([]any)
if !ok || len(dataRaw) == 0 {
return ErrorResult("data is required for write (array of byte values 0-255)")
}
if len(dataRaw) > 256 {
return ErrorResult("data too long: maximum 256 bytes per I2C transaction")
}
data := make([]byte, 0, len(dataRaw)+1)
// If register is specified, prepend it to the data
if regFloat, ok := args["register"].(float64); ok {
reg := int(regFloat)
if reg < 0 || reg > 255 {
return ErrorResult("register must be between 0x00 and 0xFF")
}
data = append(data, byte(reg))
}
for i, v := range dataRaw {
f, ok := v.(float64)
if !ok {
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
}
b := int(f)
if b < 0 || b > 255 {
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
}
data = append(data, byte(b))
}
devPath := fmt.Sprintf("/dev/i2c-%s", bus)
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to open %s: %v", devPath, err))
}
defer syscall.Close(fd)
// Set slave address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 {
return ErrorResult(fmt.Sprintf("failed to set I2C address 0x%02x: %v", addr, errno))
}
// Write data
n, err := syscall.Write(fd, data)
if err != nil {
return ErrorResult(fmt.Sprintf("failed to write to device 0x%02x: %v", addr, err))
}
return SilentResult(fmt.Sprintf("Wrote %d byte(s) to device 0x%02x on %s", n, addr, devPath))
}

View file

@ -11,129 +11,76 @@ import (
)
func prepareCommandForTermination(cmd *exec.Cmd) {
if cmd == nil {
return
}
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
func terminateProcessTree(cmd *exec.Cmd) error {
if cmd == nil || cmd.Process == nil {
return nil
}
pid := cmd.Process.Pid
if pid <= 0 {
return nil
}
// Kill the entire process group spawned by the shell command.
_ = syscall.Kill(-pid, syscall.SIGKILL)
// Some shells/background jobs may still leave descendants around
// briefly; aggressively walk /proc and kill child processes too.
killDescendants(pid)
// Fallback kill on the shell process itself.
_ = cmd.Process.Kill()
return nil
}
func killDescendants(ppid int) {
if ppid <= 0 {
return
}
entries, err := os.ReadDir("/proc")
if err != nil {
return
}
for _, e := range entries {
if !e.IsDir() {
continue
}
childPID, err := strconv.Atoi(e.Name())
if err != nil || childPID <= 0 || childPID == ppid {
continue
}
statPath := "/proc/" + e.Name() + "/stat"
data, err := os.ReadFile(statPath)
if err != nil {
continue
}
// /proc/<pid>/stat: pid (comm) state ppid ...
raw := string(data)
end := strings.LastIndex(raw, ")")
if end == -1 || end+2 >= len(raw) {
continue
}
fields := strings.Fields(raw[end+2:])
if len(fields) < 2 {
continue
}
parent, err := strconv.Atoi(fields[1])
if err != nil || parent != ppid {
continue
}
// Recurse first, then kill child process/group.
killDescendants(childPID)
_ = syscall.Kill(-childPID, syscall.SIGKILL)
_ = syscall.Kill(childPID, syscall.SIGKILL)
}
}

View file

@ -14,122 +14,69 @@ import (
)
func processRunning(pid int) bool {
if pid <= 0 {
return false
}
// kill(0) can return success for zombie processes too, so inspect /proc
// state and treat zombies as not-running for timeout cleanup assertions.
err := syscall.Kill(pid, 0)
if err != nil && err != syscall.EPERM {
return false
}
data, readErr := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
if readErr != nil {
return false
}
raw := string(data)
end := strings.LastIndex(raw, ")")
if end == -1 || end+2 >= len(raw) {
return true // best effort fallback
}
fields := strings.Fields(raw[end+2:])
if len(fields) == 0 {
return true // best effort fallback
}
state := fields[0]
return state != "Z"
}
func TestShellTool_TimeoutKillsChildProcess(t *testing.T) {
tool, err := NewExecTool(t.TempDir(), false)
if err != nil {
t.Errorf("unable to configure exec tool: %s", err)
}
tool.SetTimeout(500 * time.Millisecond)
args := map[string]any{
// Spawn a child process that would outlive the shell unless process-group kill is used.
"command": "sleep 60 & echo $! > child.pid; wait",
}
result := tool.Execute(context.Background(), args)
if !result.IsError {
t.Fatalf("expected timeout error, got success: %s", result.ForLLM)
}
if !strings.Contains(result.ForLLM, "timed out") {
t.Fatalf("expected timeout message, got: %s", result.ForLLM)
}
childPIDPath := filepath.Join(tool.workingDir, "child.pid")
data, err := os.ReadFile(childPIDPath)
if err != nil {
t.Fatalf("failed to read child pid file: %v", err)
}
childPID, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
t.Fatalf("failed to parse child pid: %v", err)
}
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if !processRunning(childPID) {
return
}
time.Sleep(50 * time.Millisecond)
}
t.Fatalf("child process %d is still running after timeout", childPID)
}

View file

@ -9,321 +9,193 @@ import (
)
// SPI ioctl constants from Linux kernel headers.
// Calculated from _IOW('k', nr, size) macro:
//
// direction(1)<<30 | size<<16 | type(0x6B)<<8 | nr
const (
spiIocWrMode = 0x40016B01 // _IOW('k', 1, __u8)
spiIocWrMode = 0x40016B01 // _IOW('k', 1, __u8)
spiIocWrBitsPerWord = 0x40016B03 // _IOW('k', 3, __u8)
spiIocWrMaxSpeedHz = 0x40046B04 // _IOW('k', 4, __u32)
spiIocMessage1 = 0x40206B00 // _IOW('k', 0, struct spi_ioc_transfer) — 32 bytes
spiIocWrMaxSpeedHz = 0x40046B04 // _IOW('k', 4, __u32)
spiIocMessage1 = 0x40206B00 // _IOW('k', 0, struct spi_ioc_transfer) — 32 bytes
)
// spiTransfer matches Linux kernel struct spi_ioc_transfer (32 bytes on all architectures).
type spiTransfer struct {
txBuf uint64
rxBuf uint64
length uint32
speedHz uint32
delayUsecs uint16
txBuf uint64
rxBuf uint64
length uint32
speedHz uint32
delayUsecs uint16
bitsPerWord uint8
csChange uint8
txNbits uint8
rxNbits uint8
wordDelay uint8
pad uint8
csChange uint8
txNbits uint8
rxNbits uint8
wordDelay uint8
pad uint8
}
// configureSPI opens an SPI device and sets mode, bits per word, and speed
func configureSPI(devPath string, mode uint8, bits uint8, speed uint32) (int, *ToolResult) {
// configureSPI opens an SPI device and sets mode, bits per word, and speed.
func configureSPI(
devPath string, mode uint8, bits uint8, speed uint32,
) (int, *ToolResult) {
fd, err := syscall.Open(devPath, syscall.O_RDWR, 0)
if err != nil {
return -1, ErrorResult(fmt.Sprintf("failed to open %s: %v (check permissions and spidev module)", devPath, err))
}
// Set SPI mode
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrMode, uintptr(unsafe.Pointer(&mode)))
if errno != 0 {
syscall.Close(fd)
return -1, ErrorResult(fmt.Sprintf("failed to set SPI mode %d: %v", mode, errno))
}
// Set bits per word
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrBitsPerWord, uintptr(unsafe.Pointer(&bits)))
if errno != 0 {
syscall.Close(fd)
return -1, ErrorResult(fmt.Sprintf("failed to set bits per word %d: %v", bits, errno))
}
// Set max speed
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocWrMaxSpeedHz, uintptr(unsafe.Pointer(&speed)))
if errno != 0 {
syscall.Close(fd)
return -1, ErrorResult(fmt.Sprintf("failed to set SPI speed %d Hz: %v", speed, errno))
}
return fd, nil
}
// transfer performs a full-duplex SPI transfer
// transfer performs a full-duplex SPI transfer.
func (t *SPITool) transfer(args map[string]any) *ToolResult {
confirm, _ := args["confirm"].(bool)
if !confirm {
return ErrorResult(
"transfer operations require confirm: true. Please confirm with the user before sending data to SPI devices.",
"transfer operations require confirm: true." +
" Please confirm with the user before sending data to SPI devices.",
)
}
dev, speed, mode, bits, errMsg := parseSPIArgs(args)
if errMsg != "" {
return ErrorResult(errMsg)
}
dataRaw, ok := args["data"].([]any)
if !ok || len(dataRaw) == 0 {
return ErrorResult("data is required for transfer (array of byte values 0-255)")
}
if len(dataRaw) > 4096 {
return ErrorResult("data too long: maximum 4096 bytes per SPI transfer")
}
txBuf := make([]byte, len(dataRaw))
for i, v := range dataRaw {
f, ok := v.(float64)
if !ok {
return ErrorResult(fmt.Sprintf("data[%d] is not a valid byte value", i))
}
b := int(f)
if b < 0 || b > 255 {
return ErrorResult(fmt.Sprintf("data[%d] = %d is out of byte range (0-255)", i, b))
}
txBuf[i] = byte(b)
}
devPath := fmt.Sprintf("/dev/spidev%s", dev)
fd, errResult := configureSPI(devPath, mode, bits, speed)
if errResult != nil {
return errResult
}
defer syscall.Close(fd)
rxBuf := make([]byte, len(txBuf))
xfer := spiTransfer{
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
length: uint32(len(txBuf)),
speedHz: speed,
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
length: uint32(len(txBuf)),
speedHz: speed,
bitsPerWord: bits,
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocMessage1, uintptr(unsafe.Pointer(&xfer)))
runtime.KeepAlive(txBuf)
runtime.KeepAlive(rxBuf)
if errno != 0 {
return ErrorResult(fmt.Sprintf("SPI transfer failed: %v", errno))
}
// Format received bytes
hexBytes := make([]string, len(rxBuf))
intBytes := make([]int, len(rxBuf))
for i, b := range rxBuf {
hexBytes[i] = fmt.Sprintf("0x%02x", b)
intBytes[i] = int(b)
}
result, _ := json.MarshalIndent(map[string]any{
"device": devPath,
"sent": len(txBuf),
"device": devPath,
"sent": len(txBuf),
"received": intBytes,
"hex": hexBytes,
"hex": hexBytes,
}, "", " ")
return SilentResult(string(result))
}
// readDevice reads bytes from SPI by sending zeros (read-only, no confirm needed)
// readDevice reads bytes from SPI by sending zeros (read-only, no confirm needed).
func (t *SPITool) readDevice(args map[string]any) *ToolResult {
dev, speed, mode, bits, errMsg := parseSPIArgs(args)
if errMsg != "" {
return ErrorResult(errMsg)
}
length := 0
if l, ok := args["length"].(float64); ok {
length = int(l)
}
if length < 1 || length > 4096 {
return ErrorResult("length is required for read (1-4096)")
}
devPath := fmt.Sprintf("/dev/spidev%s", dev)
fd, errResult := configureSPI(devPath, mode, bits, speed)
if errResult != nil {
return errResult
}
defer syscall.Close(fd)
txBuf := make([]byte, length) // zeros
rxBuf := make([]byte, length)
xfer := spiTransfer{
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
length: uint32(length),
speedHz: speed,
txBuf: uint64(uintptr(unsafe.Pointer(&txBuf[0]))),
rxBuf: uint64(uintptr(unsafe.Pointer(&rxBuf[0]))),
length: uint32(length),
speedHz: speed,
bitsPerWord: bits,
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), spiIocMessage1, uintptr(unsafe.Pointer(&xfer)))
runtime.KeepAlive(txBuf)
runtime.KeepAlive(rxBuf)
if errno != 0 {
return ErrorResult(fmt.Sprintf("SPI read failed: %v", errno))
}
hexBytes := make([]string, len(rxBuf))
intBytes := make([]int, len(rxBuf))
for i, b := range rxBuf {
hexBytes[i] = fmt.Sprintf("0x%02x", b)
intBytes[i] = int(b)
}
result, _ := json.MarshalIndent(map[string]any{
"device": devPath,
"bytes": intBytes,
"hex": hexBytes,
"bytes": intBytes,
"hex": hexBytes,
"length": len(rxBuf),
}, "", " ")
return SilentResult(string(result))
}