diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-10 18:03:29 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-10 18:03:29 +0300 |
| commit | 28f6319b77d35c6da6b99ad7e35d0d5602dc2ee6 (patch) | |
| tree | 687b2c38755a087694cacacb73cd73b8ef244ce7 /internal/io | |
| parent | 13b21feb07c86f65760f7338f284f3b492364cd9 (diff) | |
Fix known-hosts trust deadlock, host key stat, and optional nozstd build
- stdout logger: release mutex while waiting on pause resume so prompt
callbacks can log (fixes hang after trusting new hosts; known_hosts
was written but Resume never ran).
- known hosts callback: stop borrowing the SSH dial throttle channel
(could block or interact badly with parallel handshakes).
- host key path: use errors.Is(..., fs.ErrNotExist) for RootedPath.Stat
wrapped errors; stat errors now fail fast instead of mis-read.
- public key path: same ErrNotExist check for authorized_keys miss.
- Build: optional DTAIL_NO_ZSTD=yes / nozstd tag for CGO-free builds;
split zstd readers into tagged files.
- Docs/examples: firewalld note for port 2222, log prune timer+script,
SSHBindAddress note, dserver unit disabled-by-default comment;
firewalld helper script example.
- Regression test for stdout pause/mutex behavior.
Made-with: Cursor
Diffstat (limited to 'internal/io')
| -rw-r--r-- | internal/io/dlog/loggers/stdout.go | 9 | ||||
| -rw-r--r-- | internal/io/dlog/loggers/stdout_test.go | 36 | ||||
| -rw-r--r-- | internal/io/fs/readfile.go | 7 | ||||
| -rw-r--r-- | internal/io/fs/readfile_nozstd.go | 16 | ||||
| -rw-r--r-- | internal/io/fs/readfile_zstd.go | 20 |
5 files changed, 79 insertions, 9 deletions
diff --git a/internal/io/dlog/loggers/stdout.go b/internal/io/dlog/loggers/stdout.go index a2575c8..b4e695a 100644 --- a/internal/io/dlog/loggers/stdout.go +++ b/internal/io/dlog/loggers/stdout.go @@ -44,14 +44,17 @@ func (s *stdout) RawWithColors(now time.Time, message, coloredMessage string) { func (s *stdout) log(message string, nl bool) { s.mutex.Lock() - defer s.mutex.Unlock() - select { case <-s.pauseCh: - // Pause until resumed. + // Wait for Resume without holding the mutex: the prompt path calls + // dlog after the user answers while Pause is still active; holding the + // mutex here would deadlock (Info blocks on Lock, Resume never runs). + s.mutex.Unlock() <-s.resumeCh + s.mutex.Lock() default: } + defer s.mutex.Unlock() if nl { fmt.Println(message) diff --git a/internal/io/dlog/loggers/stdout_test.go b/internal/io/dlog/loggers/stdout_test.go new file mode 100644 index 0000000..4f70efc --- /dev/null +++ b/internal/io/dlog/loggers/stdout_test.go @@ -0,0 +1,36 @@ +package loggers + +import ( + "testing" + "time" +) + +// Regression: during an interactive prompt, dlog.Common.Pause() unblocks when some +// goroutine hits stdout.log(); that goroutine must not hold the stdout mutex while +// waiting on resume, or dlog.Client.Info from the prompt callback deadlocks forever. +func TestStdoutSecondLogDuringPauseWaitDoesNotDeadlock(t *testing.T) { + s := newStdout() + + go s.Pause() + time.Sleep(50 * time.Millisecond) + + go func() { + s.Log(time.Now(), "first log consumes pause and waits on resume") + }() + time.Sleep(50 * time.Millisecond) + + secondDone := make(chan struct{}) + go func() { + s.Log(time.Now(), "second log must acquire mutex while first waits for Resume") + close(secondDone) + }() + + select { + case <-secondDone: + case <-time.After(2 * time.Second): + t.Fatal("deadlock: second Log blocked on mutex while first waits for Resume") + } + + s.Resume() + time.Sleep(50 * time.Millisecond) +} diff --git a/internal/io/fs/readfile.go b/internal/io/fs/readfile.go index d305c4d..5241556 100644 --- a/internal/io/fs/readfile.go +++ b/internal/io/fs/readfile.go @@ -19,8 +19,6 @@ import ( "github.com/mimecast/dtail/internal/io/pool" "github.com/mimecast/dtail/internal/lcontext" "github.com/mimecast/dtail/internal/regex" - - "github.com/DataDog/zstd" ) type readStatus int @@ -193,10 +191,7 @@ func (f *readFile) makeCompressedFileReader(fd *os.File) (reader *bufio.Reader, decompressor = gzipReader reader = bufio.NewReader(gzipReader) case strings.HasSuffix(f.FilePath(), ".zst"): - dlog.Common.Info(f.FilePath(), "Detected zstd compression format") - zstdReader := zstd.NewReader(fd) - decompressor = zstdReader - reader = bufio.NewReader(zstdReader) + return f.makeZstdReader(fd) default: reader = bufio.NewReader(fd) } diff --git a/internal/io/fs/readfile_nozstd.go b/internal/io/fs/readfile_nozstd.go new file mode 100644 index 0000000..afd4523 --- /dev/null +++ b/internal/io/fs/readfile_nozstd.go @@ -0,0 +1,16 @@ +//go:build nozstd + +package fs + +import ( + "bufio" + "fmt" + "io" + "os" +) + +func (f *readFile) makeZstdReader(fd *os.File) (reader *bufio.Reader, decompressor io.Closer, err error) { + _ = fd + err = fmt.Errorf("%s: zstd is not supported in this build (built with -tags nozstd)", f.FilePath()) + return +} diff --git a/internal/io/fs/readfile_zstd.go b/internal/io/fs/readfile_zstd.go new file mode 100644 index 0000000..a7e479b --- /dev/null +++ b/internal/io/fs/readfile_zstd.go @@ -0,0 +1,20 @@ +//go:build !nozstd + +package fs + +import ( + "bufio" + "io" + "os" + + "github.com/DataDog/zstd" + "github.com/mimecast/dtail/internal/io/dlog" +) + +func (f *readFile) makeZstdReader(fd *os.File) (reader *bufio.Reader, decompressor io.Closer, err error) { + dlog.Common.Info(f.FilePath(), "Detected zstd compression format") + zstdReader := zstd.NewReader(fd) + decompressor = zstdReader + reader = bufio.NewReader(zstdReader) + return +} |
