[PATCH] frontend: report non-seekable Windows handles to mpg123

"Maya R. Odinezenko via Lame-dev" <[email protected]> Sun, 26 Jul 2026 17:41:20 -0400
Newsgroups gmane.comp.audio.mp3.lame
Message-ID <CAFCgL3nowy41bdBmt=jzLM+BDqjHDdEh_4W3usXf2nyHft_fDg@mail.gmail.com>
Hello,

MP3 data piped into the Windows frontend can be decoded incorrectly
without a nonzero exit status. In native cmd.exe pipelines, the
unpatched frontend produced 182,204 bytes instead of 176,400 and exited
0. The cmd.exe pipeline output matched decoding the same audio encoded
without a Xing/Info frame, so encoder-delay and padding trimming were
lost. Under MSYS2 bash, the same defect failed with a nonzero status
instead.

The Windows mpg123 FILE * bridge supplies a seek callback.
mpg123_open_handle() calls get_fileinfo(), which seeks to the end and
peeks at 128 bytes for ID3v1. Microsoft documents the fseek() return
value as undefined on devices incapable of seeking. On the tested
pipe, the call reported success, causing mpg123 to treat the pipe as
seekable and consume those 128 bytes without being able to rewind. The
value reported by ftell() reflects bytes buffered in the pipe rather
than a stream length, so the failure mode depends on pipe occupancy.

The patch checks the native handle type with GetFileType() and retains
the existing fseek() path only for FILE_TYPE_DISK. Other handle types
are reported non-seekable before any data is read. Pathnames and
standard input redirected from a regular file remain disk handles and
follow the existing path.

Testing of the patched lame frontend covered macOS, Debian, Windows
UCRT64, and Windows MINGW64/MSVCRT. After the patch, decoded PCM was
identical for pathname, redirected regular-file standard input, and
genuine-pipe routes. The patch is against trunk r6662.

Yours,

Maya

-- >8 --
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Maya <[email protected]>
Date: Sun, 26 Jul 2026 15:33:17 -0400
Subject: [PATCH] frontend: report non-seekable Windows handles to mpg123

MP3 input from a genuine Windows pipe can silently produce incorrect
decoded output. In native cmd.exe pipelines, the affected frontend
produced 182,204 bytes instead of 176,400 and exited with status 0;
under MSYS2 it failed with a nonzero status. The cmd.exe pipeline output
matched decoding the same audio encoded without a Xing/Info frame, so
encoder-delay and padding trimming had been lost.

LAME's Windows FILE * reader bridge installs its seek callback with
mpg123_replace_reader_handle(). mpg123_open_handle() reaches
get_fileinfo(), whose first operation seeks to the end to determine the
stream length and inspect ID3v1. On Windows the fseek() return value is
undefined for a device that cannot seek. On a pipe it reported success,
so mpg123 classified the stream as seekable, read 128 bytes for the
ID3v1 check, and could not return to the beginning. The value reported
by ftell() reflects bytes buffered in the pipe rather than a stream
length, so the failure mode depends on pipe occupancy.

Classify the native handle with GetFileType() before seeking. Keep the
existing fseek() path only for FILE_TYPE_DISK and report other handles
as non-seekable without reading or repositioning the stream.

Ordinary pathnames and standard input redirected from a regular file
are disk handles and keep the existing path. Non-Windows builds are
unaffected because the added code remains within the existing _WIN32
branch.

With the patch, lame produced identical decoded PCM for pathname,
redirected regular-file standard input, and genuine-pipe routes on
macOS, Debian, Windows UCRT64, and Windows MINGW64/MSVCRT. On Windows,
the callback consumed zero bytes before recognizing the pipe as
non-seekable.
---
 frontend/get_audio.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/frontend/get_audio.c b/frontend/get_audio.c
index 16145c7..f75a170 100644
--- a/frontend/get_audio.c
+++ b/frontend/get_audio.c
@@ -77,8 +77,10 @@ char   *strchr(), *strrchr();
 # include <kernel.h>
 # include <sys/swis.h>
 #elif defined(_WIN32)
+# include <io.h>
 # include <sys/types.h>
 # include <sys/stat.h>
+# include <windows.h>
 #else
 # include <sys/stat.h>
 #endif
@@ -2064,6 +2066,13 @@ static mpg123_ssize_t
lame123_read_from_file(void* handle, void* buffer, size_t

 static off_t lame123_seek_in_file(void* handle, off_t offset, int direction)
 {
+   HANDLE const os_handle =
+       (HANDLE) _get_osfhandle(_fileno((FILE*) handle));
+
+   /* fseek() has undefined behavior for non-seeking Windows devices. */
+   if (os_handle == INVALID_HANDLE_VALUE ||
+       GetFileType(os_handle) != FILE_TYPE_DISK)
+      return (off_t)-1;
    if (fseek((FILE*)handle, offset, direction) != 0)
       return (off_t)-1;
    return ftell((FILE*)handle);