74 lines
1.7 KiB
Go
74 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// runCmd runs name with args, returning trimmed combined output.
|
|
func runCmd(name string, args ...string) (string, error) {
|
|
out, err := exec.Command(name, args...).CombinedOutput()
|
|
return strings.TrimSpace(string(out)), err
|
|
}
|
|
|
|
// runCmdStdin is runCmd with a fixed stdin string (for tools that prompt).
|
|
func runCmdStdin(stdin, name string, args ...string) (string, error) {
|
|
c := exec.Command(name, args...)
|
|
c.Stdin = strings.NewReader(stdin)
|
|
out, err := c.CombinedOutput()
|
|
return strings.TrimSpace(string(out)), err
|
|
}
|
|
|
|
// limitedBuffer keeps only the last maxLen bytes written to it — used to
|
|
// capture a bounded tail of a subprocess's stderr for error messages
|
|
// without risking unbounded memory growth from a noisy remote command.
|
|
type limitedBuffer struct {
|
|
mu sync.Mutex
|
|
buf []byte
|
|
maxLen int
|
|
}
|
|
|
|
func newLimitedBuffer(maxLen int) *limitedBuffer {
|
|
return &limitedBuffer{maxLen: maxLen}
|
|
}
|
|
|
|
func (b *limitedBuffer) Write(p []byte) (int, error) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
b.buf = append(b.buf, p...)
|
|
if len(b.buf) > b.maxLen {
|
|
b.buf = b.buf[len(b.buf)-b.maxLen:]
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (b *limitedBuffer) String() string {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return string(b.buf)
|
|
}
|
|
|
|
// roundUp rounds n up to the next multiple of to (to <= 1 is a no-op).
|
|
func roundUp(n, to int64) int64 {
|
|
if to <= 1 {
|
|
return n
|
|
}
|
|
return (n + to - 1) / to * to
|
|
}
|
|
|
|
func humanBytes(n int64) string {
|
|
const unit = 1024
|
|
if n < unit {
|
|
return fmt.Sprintf("%d B", n)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for m := n / unit; m >= unit; m /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
units := "KMGTPE"
|
|
return fmt.Sprintf("%.1f%ciB", float64(n)/float64(div), units[exp])
|
|
}
|