Prevent recording output path replacement

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-10 11:25:08 -04:00
committed by Brandon McGinty
parent 57961ffc9f
commit 65489fd367
+26 -6
View File
@@ -57,18 +57,29 @@ func New(directory string, format string, now time.Time, frameSize int, interval
if err := os.MkdirAll(directory, 0755); err != nil { if err := os.MkdirAll(directory, 0755); err != nil {
return nil, err return nil, err
} }
path, err := reservePath(directory, now, format) output, path, err := reserveOutput(directory, now, format)
if err != nil { if err != nil {
return nil, err return nil, err
} }
args := ffmpegArgs(format, path) args := ffmpegArgs(format)
cmd := exec.Command("ffmpeg", args...) 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() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
_ = output.Close()
_ = os.Remove(path) _ = os.Remove(path)
return nil, err return nil, err
} }
if err := cmd.Start(); err != nil { 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) _ = os.Remove(path)
return nil, err return nil, err
} }
@@ -96,13 +107,23 @@ func NormalizeFormat(format string) string {
return format 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 { for {
path := UniquePath(directory, now, format) path := UniquePath(directory, now, format)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) { if os.IsExist(err) {
continue continue
} }
if err != nil {
return nil, "", err
}
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 { if err != nil {
return "", err return "", err
} }
@@ -111,7 +132,6 @@ func reservePath(directory string, now time.Time, format string) (string, error)
return "", err return "", err
} }
return path, nil return path, nil
}
} }
func UniquePath(directory string, now time.Time, format string) string { func UniquePath(directory string, now time.Time, format string) string {
@@ -270,7 +290,7 @@ func writePCM(w io.Writer, samples []int16) error {
return err return err
} }
func ffmpegArgs(format string, path string) []string { func ffmpegArgs(format string) []string {
args := []string{ args := []string{
"-loglevel", "error", "-loglevel", "error",
"-f", "s16le", "-f", "s16le",
@@ -281,5 +301,5 @@ func ffmpegArgs(format string, path string) []string {
if format == FormatOpus { if format == FormatOpus {
args = append(args, "-c:a", "libopus") args = append(args, "-c:a", "libopus")
} }
return append(args, "-y", path) return append(args, "-f", format, "pipe:3")
} }