Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions acceptance_tests/file-descriptors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash

setUp() {
rm test-fd-*.yml 2>/dev/null || true
}

tearDown() {
rm test-fd-*.yml 2>/dev/null || true
}

createInputFiles() {
local index
for index in {1..80}; do
ln -s examples/sample.yaml "test-fd-${index}.yml"
done
}

testEvalClosesInputFiles() {
createInputFiles

(ulimit -n 32; GOGC=off ./yq eval 'select(false)' test-fd-*.yml)
assertEquals 0 "$?"
}

testEvalAllClosesInputFiles() {
createInputFiles

(ulimit -n 32; GOGC=off ./yq eval-all 'select(false)' test-fd-*.yml)
assertEquals 0 "$?"
}

source ./scripts/shunit2
17 changes: 11 additions & 6 deletions pkg/yqlib/all_at_once_evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,7 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string

var allDocuments = list.New()
for _, filename := range filenames {
reader, err := readStream(filename)
if err != nil {
return err
}

fileDocuments, err := readDocuments(reader, filename, fileIndex, decoder)
fileDocuments, err := readDocumentsFromFile(filename, fileIndex, decoder)
if err != nil {
return err
}
Expand All @@ -73,3 +68,13 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
}
return printer.PrintResults(matches)
}

func readDocumentsFromFile(filename string, fileIndex int, decoder Decoder) (*list.List, error) {
reader, err := readStream(filename)
if err != nil {
return nil, err
}
defer SafelyCloseReader(reader)

return readDocuments(reader, filename, fileIndex, decoder)
}
21 changes: 21 additions & 0 deletions pkg/yqlib/all_at_once_evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package yqlib

import (
"bufio"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -76,3 +77,23 @@ func TestTomlDecoderCanBeReinitializedAcrossDocuments(t *testing.T) {
}
test.AssertResult(t, "Banana", secondDocuments.Front().Value.(*CandidateNode).Content[1].Value)
}

func TestReadDocumentsLeavesReaderOpen(t *testing.T) {
filename := t.TempDir() + "/input.yml"
if err := os.WriteFile(filename, []byte("a: apple\n"), 0600); err != nil {
t.Fatalf("failed to write input file: %v", err)
}

file, err := os.Open(filename)
if err != nil {
t.Fatalf("failed to open input file: %v", err)
}
defer file.Close()

if _, err := ReadDocuments(file, NewYamlDecoder(ConfiguredYamlPreferences)); err != nil {
t.Fatalf("failed to read documents: %v", err)
}
if _, err := file.Seek(0, 0); err != nil {
t.Fatalf("ReadDocuments closed the caller-owned reader: %v", err)
}
}
4 changes: 4 additions & 0 deletions pkg/yqlib/file_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func SafelyCloseReader(reader io.Reader) {
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
case io.Closer:
if err := reader.Close(); err != nil {
log.Errorf("Error closing reader: %v", err)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions pkg/yqlib/front_matter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Some content
}
decoder := NewYamlDecoder(ConfiguredYamlPreferences)
docs, err := readDocuments(reader, tempFilename, 0, decoder)
SafelyCloseReader(reader)
if err != nil {
panic(err)
}
Expand Down
23 changes: 11 additions & 12 deletions pkg/yqlib/stream_evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
"os"
)

// A yaml expression evaluator that runs the expression multiple times for each given yaml document.
Expand Down Expand Up @@ -50,21 +49,11 @@ func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, p
}

for _, filename := range filenames {
reader, err := readStream(filename)

if err != nil {
return err
}
processedDocs, err := s.Evaluate(filename, reader, node, printer, decoder)
processedDocs, err := s.evaluateFile(filename, node, printer, decoder)
if err != nil {
return err
}
totalProcessDocs = totalProcessDocs + processedDocs

switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
}

if totalProcessDocs == 0 {
Expand All @@ -75,6 +64,16 @@ func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, p
return nil
}

func (s *streamEvaluator) evaluateFile(filename string, node *ExpressionNode, printer Printer, decoder Decoder) (uint, error) {
reader, err := readStream(filename)
if err != nil {
return 0, err
}
defer SafelyCloseReader(reader)

return s.Evaluate(filename, reader, node, printer, decoder)
}

func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer, decoder Decoder) (uint, error) {
filename = resolveFilename(filename)

Expand Down
30 changes: 14 additions & 16 deletions pkg/yqlib/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,23 @@ func resolveFilename(filename string) string {
return filename
}

func readStream(filename string) (io.Reader, error) {
var reader *bufio.Reader
type readCloser struct {
io.Reader
io.Closer
}

func readStream(filename string) (io.ReadCloser, error) {
if filename == "-" {
reader = bufio.NewReader(os.Stdin)
} else {
// ignore CWE-22 gosec issue - that's more targeted for http based apps that run in a public directory,
// and ensuring that it's not possible to give a path to a file outside that directory.
file, err := os.Open(filename) // #nosec
if err != nil {
return nil, err
}
reader = bufio.NewReader(file)
return io.NopCloser(bufio.NewReader(os.Stdin)), nil
}
return reader, nil

// ignore CWE-22 gosec issue - that's more targeted for http based apps that run in a public directory,
// and ensuring that it's not possible to give a path to a file outside that directory.
file, err := os.Open(filename) // #nosec
if err != nil {
return nil, err
}
return &readCloser{Reader: bufio.NewReader(file), Closer: file}, nil
}

func writeString(writer io.Writer, txt string) error {
Expand All @@ -71,10 +73,6 @@ func readDocuments(reader io.Reader, filename string, fileIndex int, decoder Dec
candidateNode, errorReading := decoder.Decode()

if errors.Is(errorReading, io.EOF) {
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
return inputList, nil
} else if errorReading != nil {
return nil, fmt.Errorf("bad file '%v': %w", filename, errorReading)
Expand Down