package sshpush import ( "context" "crypto/sha256" "encoding/hex" "fmt" "math/rand" "os" "strings" ) // WriteFileIdempotent writes content to peer:path atomically (write-to-tmp // + mv, REQ-074) with mode, but only if the remote file's SHA-256 differs // from the local content's SHA-256 (C-18 content-addressed idempotency — // the SSH-push equivalent of the v0.8 X-Orca-Idempotency-Key). // // Returns written=true if the file was written, written=false if the // content already matched (skip). The default per-SCP timeout is // SCPTimeout (I-B-001). // // Atomicity: the content is written to a temp file in the same directory // as the target, then `mv`'d into place. The temp file is mode-appended // (e.g. `/etc/orca/foo.conf.orca-tmp-`) so the rename is atomic on // POSIX filesystems. func (t *Transport) WriteFileIdempotent(ctx context.Context, peer string, path string, content []byte, mode os.FileMode) (bool, error) { localHash := sha256Hex(content) remoteHash, err := t.remoteSHA256(ctx, peer, path) if err == nil && remoteHash != "" && strings.EqualFold(remoteHash, localHash) { return false, nil } if err := t.writeFile(ctx, peer, path, content, mode); err != nil { return false, err } return true, nil } // writeFile writes content to peer:path atomically (write-to-tmp + mv). // It writes the content via a single SSH exec (cat heredoc + chmod + mv), // keeping the transfer in one round-trip. The temp file lives next to the // target so the rename is atomic. func (t *Transport) writeFile(ctx context.Context, peer string, path string, content []byte, mode os.FileMode) error { dir, base := splitDir(path) tmpName := fmt.Sprintf(".orca-tmp-%s", randomToken(8)) tmpPath := base + "/" + tmpName if dir == "" { tmpPath = tmpName } // Build the remote command: mkdir -p && cat > <<'EOF' // ... EOF && chmod && mv . The heredoc // delimiter is per-write random and verified absent from content // to prevent command injection via crafted file content. eof := "ORCA_PUSH_EOF_" + randomToken(16) for strings.Contains(string(content), eof) { eof = "ORCA_PUSH_EOF_" + randomToken(16) } modeStr := fmt.Sprintf("%04o", uint32(mode.Perm())) cmd := fmt.Sprintf( "mkdir -p %s && cat > %s <<'%s'\n%s\n%s\nchmod %s %s && mv -f %s %s", shellQuote(base), shellQuote(tmpPath), eof, string(content), eof, modeStr, shellQuote(tmpPath), shellQuote(tmpPath), shellQuote(path), ) execCtx, cancel := context.WithTimeout(ctx, SCPTimeout) defer cancel() if _, err := t.execWithRetry(execCtx, peer, cmd, true); err != nil { return fmt.Errorf("sshpush: write %s: %w", path, err) } return nil } // sha256Hex returns the lowercase hex SHA-256 digest of b. func sha256Hex(b []byte) string { sum := sha256.Sum256(b) return hex.EncodeToString(sum[:]) } // splitDir returns the directory and the directory itself (for mkdir). // For "/etc/orca/foo.conf" it returns ("/etc/orca", "/etc/orca"). For // "foo.conf" it returns ("", "."). func splitDir(path string) (dir, base string) { idx := strings.LastIndex(path, "/") if idx < 0 { return "", "." } return path[:idx], path[:idx] } // randomToken returns a random hex token of the given byte length. Used // for temp-file naming to avoid collisions under parallel fan-out. func randomToken(n int) string { b := make([]byte, n) for i := range b { b[i] = byte(rand.Intn(256)) } return hex.EncodeToString(b) }