daemon: rewrite git identity sync + file_sync on ext4 toolkit
ensureGitIdentityOnWorkDisk, writeGitIdentity, runFileSync, and copyHostDir all dropped their mount + sudo install/mkdir/chmod/chown scaffolding. Every write now goes through MkdirExt4, WriteExt4FileOwned, ReadExt4File, and the new MkdirAllExt4 helper — all sudoless against user-owned ext4 images. Net effect with the prior two commits: ensureWorkDisk, authsync, image seeding, git identity sync, and file_sync no longer mount the work disk or spawn sudo mkdir/chmod/chown/cat/install. Only the image-build path (which legitimately produces root-owned artifacts) still touches MountTempDir. The filesystemRunner test harness grew a small debugfs/e2cp/e2rm emulator so the WorkspaceService tests keep exercising their real code paths without a live ext4 image. The mock is deliberately dumb — it only implements the subset runFileSync and writeGitIdentity drive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f0685366ec
commit
6ab1a2b844
3 changed files with 253 additions and 74 deletions
|
|
@ -2031,11 +2031,147 @@ func (r *filesystemRunner) RunSudo(ctx context.Context, args ...string) ([]byte,
|
|||
default:
|
||||
return nil, fmt.Errorf("unexpected chown args: %v", args)
|
||||
}
|
||||
case "debugfs":
|
||||
return runFakeDebugfs(args[1:])
|
||||
case "e2cp":
|
||||
// e2cp SRC IMAGE:/GUEST → plain file copy into IMAGE dir
|
||||
if len(args) != 3 {
|
||||
return nil, fmt.Errorf("unexpected e2cp args: %v", args)
|
||||
}
|
||||
image, guest, ok := splitImageColonPath(args[2])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("e2cp dst missing image:path separator: %v", args)
|
||||
}
|
||||
target := filepath.Join(image, guest)
|
||||
data, err := os.ReadFile(args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, os.WriteFile(target, data, 0o600)
|
||||
case "e2rm":
|
||||
// e2rm IMAGE:/GUEST → plain file delete; missing is not fatal
|
||||
if len(args) != 2 {
|
||||
return nil, fmt.Errorf("unexpected e2rm args: %v", args)
|
||||
}
|
||||
image, guest, ok := splitImageColonPath(args[1])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("e2rm missing image:path separator: %v", args)
|
||||
}
|
||||
target := filepath.Join(image, guest)
|
||||
if err := os.Remove(target); err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected sudo command: %v", args)
|
||||
}
|
||||
}
|
||||
|
||||
// runFakeDebugfs emulates the subset of debugfs commands the ext4
|
||||
// toolkit drives in per-line mode (the stdin-batched path doesn't run
|
||||
// under filesystemRunner because it doesn't implement StdinRunner).
|
||||
// Supported: stat/cat, plus -w mkdir/set_inode_field. Inode 2 <2>
|
||||
// set_inode_field is a no-op — tests don't care about root-inode mode
|
||||
// beyond it not exploding.
|
||||
func runFakeDebugfs(args []string) ([]byte, error) {
|
||||
// Forms:
|
||||
// debugfs -R "<cmd>" <image> (read-only)
|
||||
// debugfs -w -R "<cmd>" <image> (single write)
|
||||
if len(args) < 3 {
|
||||
return nil, fmt.Errorf("unexpected debugfs args: %v", args)
|
||||
}
|
||||
write := false
|
||||
rest := args
|
||||
if rest[0] == "-w" {
|
||||
write = true
|
||||
rest = rest[1:]
|
||||
}
|
||||
if len(rest) != 3 || rest[0] != "-R" {
|
||||
return nil, fmt.Errorf("unexpected debugfs args: %v", args)
|
||||
}
|
||||
cmdLine := strings.TrimSpace(rest[1])
|
||||
image := rest[2]
|
||||
|
||||
fields := strings.Fields(cmdLine)
|
||||
if len(fields) == 0 {
|
||||
return nil, fmt.Errorf("empty debugfs command")
|
||||
}
|
||||
switch fields[0] {
|
||||
case "stat":
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("unexpected debugfs stat: %q", cmdLine)
|
||||
}
|
||||
target := filepath.Join(image, strings.Trim(fields[1], `"`))
|
||||
if _, err := os.Stat(target); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []byte("stat: File not found by ext2_lookup while starting pathname"), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return []byte("Inode: 12 Type: directory"), nil
|
||||
case "cat":
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("unexpected debugfs cat: %q", cmdLine)
|
||||
}
|
||||
target := filepath.Join(image, strings.Trim(fields[1], `"`))
|
||||
data, err := os.ReadFile(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
case "mkdir":
|
||||
if !write {
|
||||
return nil, fmt.Errorf("debugfs mkdir requires -w: %q", cmdLine)
|
||||
}
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("unexpected debugfs mkdir: %q", cmdLine)
|
||||
}
|
||||
target := filepath.Join(image, strings.Trim(fields[1], `"`))
|
||||
return nil, os.MkdirAll(target, 0o755)
|
||||
case "set_inode_field":
|
||||
// set_inode_field <path-or-<2>> <field> <value>
|
||||
// Mode changes on non-root targets: honour the perm bits so
|
||||
// tests can assert file mode. Root inode <2>, uid, gid are
|
||||
// no-ops — tests don't inspect them.
|
||||
if !write {
|
||||
return nil, fmt.Errorf("debugfs set_inode_field requires -w: %q", cmdLine)
|
||||
}
|
||||
if len(fields) != 4 {
|
||||
return nil, fmt.Errorf("unexpected set_inode_field: %q", cmdLine)
|
||||
}
|
||||
target := strings.Trim(fields[1], `"`)
|
||||
if target == "<2>" || fields[2] != "mode" {
|
||||
return nil, nil
|
||||
}
|
||||
raw := strings.TrimPrefix(fields[3], "0")
|
||||
v, err := strconv.ParseUint(raw, 8, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse set_inode_field mode %q: %w", fields[3], err)
|
||||
}
|
||||
return nil, os.Chmod(filepath.Join(image, target), os.FileMode(v)&os.ModePerm)
|
||||
case "rdump":
|
||||
// rdump <src> <dst>
|
||||
return nil, fmt.Errorf("rdump not supported in filesystemRunner")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported debugfs cmd: %q", cmdLine)
|
||||
}
|
||||
}
|
||||
|
||||
// splitImageColonPath splits an e2cp/e2rm "image:path" argument.
|
||||
// Returns image, path, true on success. Only the LAST colon is split
|
||||
// on since image paths on disk may contain one (rare) and guest paths
|
||||
// always start with "/".
|
||||
func splitImageColonPath(arg string) (string, string, bool) {
|
||||
idx := strings.LastIndex(arg, ":/")
|
||||
if idx < 0 {
|
||||
return "", "", false
|
||||
}
|
||||
return arg[:idx], arg[idx+1:], true
|
||||
}
|
||||
|
||||
// parseInstallArgs recognises the `install` invocations banger emits
|
||||
// and returns (source, destination, parsed mode). Anything else is an
|
||||
// error so the test stub stays a closed set.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue