diff --git a/recording/recorder.go b/recording/recorder.go index c1e7365..6da9c83 100644 --- a/recording/recorder.go +++ b/recording/recorder.go @@ -57,18 +57,29 @@ func New(directory string, format string, now time.Time, frameSize int, interval if err := os.MkdirAll(directory, 0755); err != nil { return nil, err } - path, err := reservePath(directory, now, format) + output, path, err := reserveOutput(directory, now, format) if err != nil { return nil, err } - args := ffmpegArgs(format, path) + args := ffmpegArgs(format) cmd := exec.Command("ffmpeg", args...) + // Pass the reserved file descriptor directly to ffmpeg. The file is never + // reopened by pathname, preventing replacement between reservation and use. + cmd.ExtraFiles = []*os.File{output} stdin, err := cmd.StdinPipe() if err != nil { + _ = output.Close() _ = os.Remove(path) return nil, err } if err := cmd.Start(); err != nil { + _ = output.Close() + _ = os.Remove(path) + return nil, err + } + if err := output.Close(); err != nil { + _ = cmd.Process.Kill() + _ = cmd.Wait() _ = os.Remove(path) return nil, err } @@ -96,7 +107,7 @@ func NormalizeFormat(format string) string { return format } -func reservePath(directory string, now time.Time, format string) (string, error) { +func reserveOutput(directory string, now time.Time, format string) (*os.File, string, error) { for { path := UniquePath(directory, now, format) file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) @@ -104,16 +115,25 @@ func reservePath(directory string, now time.Time, format string) (string, error) continue } if err != nil { - return "", err + return nil, "", err } - if err := file.Close(); err != nil { - _ = os.Remove(path) - return "", err - } - return path, nil + return file, path, nil } } +// reservePath remains available for callers that only need to reserve a name. +func reservePath(directory string, now time.Time, format string) (string, error) { + file, path, err := reserveOutput(directory, now, format) + if err != nil { + return "", err + } + if err := file.Close(); err != nil { + _ = os.Remove(path) + return "", err + } + return path, nil +} + func UniquePath(directory string, now time.Time, format string) string { base := fmt.Sprintf("barnard-recording-%s", now.Format("20060102-150405")) path := filepath.Join(directory, base+"."+format) @@ -270,7 +290,7 @@ func writePCM(w io.Writer, samples []int16) error { return err } -func ffmpegArgs(format string, path string) []string { +func ffmpegArgs(format string) []string { args := []string{ "-loglevel", "error", "-f", "s16le", @@ -281,5 +301,5 @@ func ffmpegArgs(format string, path string) []string { if format == FormatOpus { args = append(args, "-c:a", "libopus") } - return append(args, "-y", path) + return append(args, "-f", format, "pipe:3") }