Fix fseek() detection of unseekable files on WIN32

Started by Juan José Santamaría Flechaalmost 3 years ago12 messages
#1Juan José Santamaría Flecha
juanjo.santamaria@gmail.com
1 attachment(s)

Hello all,

As highlighted in [1]/messages/by-id/b1448cd7-871e-20e3-8398-895e2d1d3bf9@gmail.com fseek() might fail to error even when accessing
unseekable streams.

PFA a patch that checks the file type before the actual fseek(), so only
supported calls are made.

[1]: /messages/by-id/b1448cd7-871e-20e3-8398-895e2d1d3bf9@gmail.com
/messages/by-id/b1448cd7-871e-20e3-8398-895e2d1d3bf9@gmail.com

Regards,

Juan José Santamaría Flecha

Attachments:

0001-fix-fseek-detection-of-unseekable-files-for-WIN32.patchapplication/octet-stream; name=0001-fix-fseek-detection-of-unseekable-files-for-WIN32.patchDownload
From c7a183bc950495ddbf66aa31f3d6fe3074c5446a Mon Sep 17 00:00:00 2001
From: Juan Jose Santamaria Flecha <juanjo.santamaria@gmail.com>
Date: Thu, 9 Mar 2023 23:14:42 -0500
Subject: [PATCH] fix fseek detection of unseekable files for WIN32 Calling
 fseek() on a handle to a non-seeking device such as a pipe or a
 communications device is not supported, even though the fseek() may not
 return an error, so harden that funcion with our version.

---
 src/include/port/win32_port.h |  9 ++++++---
 src/port/dirmod.c             | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+), 3 deletions(-)

diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 9488195..8679a0a 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -206,13 +206,16 @@ int			setitimer(int which, const struct itimerval *value, struct itimerval *oval
 
 /*
  * WIN32 does not provide 64-bit off_t, but does provide the functions operating
- * with 64-bit offsets.
+ * with 64-bit offsets. Also, fseek() might not give an error for unseekable
+ * streams, so harden that funcion with our version.
  */
 #define pgoff_t __int64
 
 #ifdef _MSC_VER
-#define fseeko(stream, offset, origin) _fseeki64(stream, offset, origin)
-#define ftello(stream) _ftelli64(stream)
+extern int  	pgfseek64(FILE *stream, pgoff_t offset, int origin);
+extern pgoff_t	pgftell64(FILE *stream);
+#define fseeko(stream, offset, origin) pgfseek64(stream, offset, origin)
+#define ftello(stream) pgftell64(stream)
 #else
 #ifndef fseeko
 #define fseeko(stream, offset, origin) fseeko64(stream, offset, origin)
diff --git a/src/port/dirmod.c b/src/port/dirmod.c
index bf7f068..3770efc 100644
--- a/src/port/dirmod.c
+++ b/src/port/dirmod.c
@@ -419,4 +419,42 @@ pgreadlink(const char *path, char *buf, size_t size)
 	return r;
 }
 
+#ifdef _MSC_VER
+/*
+ * pgfseek64
+ *
+ * Calling fseek() on a handle to a non-seeking device such as a pipe or
+ * a communications device is not supported, even though the fseek() may
+ * not return an error.
+ */
+int
+pgfseek64(FILE *stream, pgoff_t offset, int origin)
+{
+	if (GetFileType((HANDLE) _get_osfhandle(_fileno(stream))) != FILE_TYPE_DISK)
+	{
+		errno = ESPIPE;
+		return -1;
+	}
+
+	return _fseeki64(stream, offset, origin);
+}
+
+/*
+ * pgftell64
+ *
+ * Same as pgfseek64().
+ */
+pgoff_t
+pgftell64(FILE *stream)
+{
+	if (GetFileType((HANDLE) _get_osfhandle(_fileno(stream))) != FILE_TYPE_DISK)
+	{
+		errno = ESPIPE;
+		return -1;
+	}
+
+	return _ftelli64(stream);
+}
+#endif							/* defined(_MSC_VER) */
+
 #endif							/* defined(WIN32) && !defined(__CYGWIN__) */
-- 
2.11.0

#2Michael Paquier
michael@paquier.xyz
In reply to: Juan José Santamaría Flecha (#1)
Re: Fix fseek() detection of unseekable files on WIN32

On Tue, Mar 14, 2023 at 01:26:27PM +0100, Juan José Santamaría Flecha wrote:

As highlighted in [1] fseek() might fail to error even when accessing
unseekable streams.

PFA a patch that checks the file type before the actual fseek(), so only
supported calls are made.

+ * streams, so harden that funcion with our version.
s/funcion/function/.

+extern int     pgfseek64(FILE *stream, pgoff_t offset, int origin);
+extern pgoff_t pgftell64(FILE *stream);
+#define fseeko(stream, offset, origin) pgfseek64(stream, offset, origin)
+#define ftello(stream) pgftell64(stream)

What about naming the internal wrappers _pgfseeko64() and
_pgftello64(), located in a new file named win32fseek.c? It may be
possible that we would need a similar treatment for fseek(), in the
future, though I don't see an issue why this would be needed now.

+   if (GetFileType((HANDLE) _get_osfhandle(_fileno(stream))) != FILE_TYPE_DISK)
+   {
+       errno = ESPIPE;
+       return -1;
+   }
Shouldn't there be cases where we should return EINVAL for some of the
other types, like FILE_TYPE_REMOTE or FILE_TYPE_UNKNOWN?  We should
return ESPIPE only for FILE_TYPE_PIPE and FILE_TYPE_CHAR, then?
--
Michael
#3Juan José Santamaría Flecha
juanjo.santamaria@gmail.com
In reply to: Michael Paquier (#2)
1 attachment(s)
Re: Fix fseek() detection of unseekable files on WIN32

On Wed, Mar 15, 2023 at 5:57 AM Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Mar 14, 2023 at 01:26:27PM +0100, Juan José Santamaría Flecha
wrote:

As highlighted in [1] fseek() might fail to error even when accessing
unseekable streams.

PFA a patch that checks the file type before the actual fseek(), so only
supported calls are made.

+ * streams, so harden that funcion with our version.
s/funcion/function/.

Done.

+extern int pgfseek64(FILE *stream, pgoff_t offset, int origin);

+extern pgoff_t pgftell64(FILE *stream);
+#define fseeko(stream, offset, origin) pgfseek64(stream, offset, origin)
+#define ftello(stream) pgftell64(stream)

What about naming the internal wrappers _pgfseeko64() and
_pgftello64(), located in a new file named win32fseek.c? It may be
possible that we would need a similar treatment for fseek(), in the
future, though I don't see an issue why this would be needed now.

Done.

+   if (GetFileType((HANDLE) _get_osfhandle(_fileno(stream))) !=
FILE_TYPE_DISK)
+   {
+       errno = ESPIPE;
+       return -1;
+   }
Shouldn't there be cases where we should return EINVAL for some of the
other types, like FILE_TYPE_REMOTE or FILE_TYPE_UNKNOWN?  We should
return ESPIPE only for FILE_TYPE_PIPE and FILE_TYPE_CHAR, then?

Done.

PFA a new version of the patch.

Regards,

Juan José Santamaría Flecha

Attachments:

v2-0001-fix-fseek-detection-of-unseekable-files-for-WIN32.patchapplication/octet-stream; name=v2-0001-fix-fseek-detection-of-unseekable-files-for-WIN32.patchDownload
From 45ea3338e10c6b296ddc1cedb80ce8e5f103e51d Mon Sep 17 00:00:00 2001
From: Juan Jose Santamaria Flecha <juanjo.santamaria@gmail.com>
Date: Wed, 15 Mar 2023 07:04:44 -0400
Subject: [PATCH] fix fseek detection of unseekable files for WIN32

Calling fseek() on a handle to a non-seeking device such as a pipe or
a communications device is not supported, even though the fseek() may
not return an error, so harden that funcion with our version.
---
 src/include/port/win32_port.h |  9 ++++--
 src/port/meson.build          |  1 +
 src/port/win32fseek.c         | 68 +++++++++++++++++++++++++++++++++++++++++++
 src/tools/msvc/Mkvcbuild.pm   |  1 +
 4 files changed, 76 insertions(+), 3 deletions(-)
 create mode 100644 src/port/win32fseek.c

diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 9488195..66553fe 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -206,13 +206,16 @@ int			setitimer(int which, const struct itimerval *value, struct itimerval *oval
 
 /*
  * WIN32 does not provide 64-bit off_t, but does provide the functions operating
- * with 64-bit offsets.
+ * with 64-bit offsets. Also, fseek() might not give an error for unseekable
+ * streams, so harden that function with our version.
  */
 #define pgoff_t __int64
 
 #ifdef _MSC_VER
-#define fseeko(stream, offset, origin) _fseeki64(stream, offset, origin)
-#define ftello(stream) _ftelli64(stream)
+extern int  	_pgfseeko64(FILE *stream, pgoff_t offset, int origin);
+extern pgoff_t	_pgftello64(FILE *stream);
+#define fseeko(stream, offset, origin) _pgfseeko64(stream, offset, origin)
+#define ftello(stream) _pgftello64(stream)
 #else
 #ifndef fseeko
 #define fseeko(stream, offset, origin) fseeko64(stream, offset, origin)
diff --git a/src/port/meson.build b/src/port/meson.build
index b174b25..3d8eeb4 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -33,6 +33,7 @@ if host_system == 'windows'
     'win32env.c',
     'win32error.c',
     'win32fdatasync.c',
+    'win32fseek.c',
     'win32getrusage.c',
     'win32link.c',
     'win32ntdll.c',
diff --git a/src/port/win32fseek.c b/src/port/win32fseek.c
new file mode 100644
index 0000000..6cf62db
--- /dev/null
+++ b/src/port/win32fseek.c
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * win32fseek.c
+ *	  Replacements for fseeko() and ftello().
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/port/win32fseek.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#if defined(WIN32) && defined(_MSC_VER)
+
+/*
+ * _pgfseeko64
+ *
+ * Calling fseek() on a handle to a non-seeking device such as a pipe or
+ * a communications device is not supported, even though the fseek() may
+ * not return an error.
+ */
+int
+_pgfseeko64(FILE *stream, pgoff_t offset, int origin)
+{
+	DWORD			fileType;
+
+	fileType = GetFileType((HANDLE) _get_osfhandle(_fileno(stream)));
+
+	if (fileType == FILE_TYPE_DISK)
+		return _fseeki64(stream, offset, origin);
+	else if (fileType == FILE_TYPE_CHAR || fileType == FILE_TYPE_PIPE)
+		errno = ESPIPE;
+	else
+		errno = EINVAL;
+
+	return -1;
+}
+
+/*
+ * _pgftello64
+ *
+ * Same as _pgfseeko64().
+ */
+pgoff_t
+_pgftello64(FILE *stream)
+{
+	DWORD			fileType;
+
+	fileType = GetFileType((HANDLE) _get_osfhandle(_fileno(stream)));
+
+	if (fileType == FILE_TYPE_DISK)
+		return _ftelli64(stream);
+	else if (fileType == FILE_TYPE_CHAR || fileType == FILE_TYPE_PIPE)
+		errno = ESPIPE;
+	else
+		errno = EINVAL;
+
+	return -1;
+}
+
+#endif							/* defined(WIN32) && defined(_MSC_VER) */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f1c9ddf..61ace57 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,6 +111,7 @@ sub mkvcbuild
 	  win32dlopen.c
 	  win32env.c win32error.c
 	  win32fdatasync.c
+	  win32fseek.c
 	  win32getrusage.c
 	  win32gettimeofday.c
 	  win32link.c
-- 
2.11.0

#4Michael Paquier
michael@paquier.xyz
In reply to: Juan José Santamaría Flecha (#3)
Re: Fix fseek() detection of unseekable files on WIN32

On Wed, Mar 15, 2023 at 12:18:25PM +0100, Juan José Santamaría Flecha wrote:

PFA a new version of the patch.

+_pgftello64(FILE *stream)
+{
+   DWORD           fileType;
+
+   fileType = GetFileType((HANDLE) _get_osfhandle(_fileno(stream)));

Hmm. I am a bit surprised here.. It seems to me that we should make
sure that:
- We exist quickly if _get_osfhandle() returns -2 or
INVALID_HANDLE_VALUE, returning EINVAL?
- After GetFileType(), check for GetLastError() and the
FILE_TYPE_UNKNOWN case?

Do you think that these would be improvements?
--
Michael

#5Juan José Santamaría Flecha
juanjo.santamaria@gmail.com
In reply to: Michael Paquier (#4)
Re: Fix fseek() detection of unseekable files on WIN32

On Thu, Mar 16, 2023 at 2:05 AM Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Mar 15, 2023 at 12:18:25PM +0100, Juan José Santamaría Flecha
wrote:

PFA a new version of the patch.

+_pgftello64(FILE *stream)
+{
+   DWORD           fileType;
+
+   fileType = GetFileType((HANDLE) _get_osfhandle(_fileno(stream)));

Hmm. I am a bit surprised here.. It seems to me that we should make
sure that:
- We exist quickly if _get_osfhandle() returns -2 or
INVALID_HANDLE_VALUE, returning EINVAL?
- After GetFileType(), check for GetLastError() and the
FILE_TYPE_UNKNOWN case?

Do you think that these would be improvements?

IDK, this is just looking for the good case, anything else we'll fail with
ESPIPE or EINVAL anyway. If we want to get the proper file type we can call
fstat(), which has the full logic.

Regards,

Juan José Santamaría Flecha

#6Michael Paquier
michael@paquier.xyz
In reply to: Juan José Santamaría Flecha (#5)
Re: Fix fseek() detection of unseekable files on WIN32

On Thu, Mar 16, 2023 at 10:08:44AM +0100, Juan José Santamaría Flecha wrote:

IDK, this is just looking for the good case, anything else we'll fail with
ESPIPE or EINVAL anyway. If we want to get the proper file type we can call
fstat(), which has the full logic.

I am not sure, TBH. As presented, the two GetFileType() calls in
_pgftello64() and _pgfseeko64() ignore the case where it returns
FILE_TYPE_UNKNOWN and GetLastError() has something else than
NO_ERROR. The code would return EINVAL for all the errors happening.
Perhaps that's fine, though I am wondering if we should report
something more exact, based on _dosmaperr(GetLastError())?
--
Michael

#7Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#6)
1 attachment(s)
Re: Fix fseek() detection of unseekable files on WIN32

On Sun, Mar 19, 2023 at 08:20:27PM +0900, Michael Paquier wrote:

I am not sure, TBH. As presented, the two GetFileType() calls in
_pgftello64() and _pgfseeko64() ignore the case where it returns
FILE_TYPE_UNKNOWN and GetLastError() has something else than
NO_ERROR. The code would return EINVAL for all the errors happening.
Perhaps that's fine, though I am wondering if we should report
something more exact, based on _dosmaperr(GetLastError())?

In short, I was thinking among the lines of something like the
attached, where I have invented a pgwin32_get_file_type() that acts as
a wrapper of GetFileType() in a new file called win32common.c, with
all the error handling we would use between fstat(), fseeko() and
ftello() centralized in a single code path.

The refactoring with win32common.c had better be separated into its
own patch, at the end, if using an approach like that.
--
Michael

Attachments:

v3-0001-fix-fseek-detection-of-unseekable-files-for-WIN32.patchtext/x-diff; charset=us-asciiDownload
From 3e778828a58751a0b562908176dcb76cabcc7945 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 19 Mar 2023 20:40:22 +0900
Subject: [PATCH v3] fix fseek detection of unseekable files for WIN32

Calling fseek() on a handle to a non-seeking device such as a pipe or
a communications device is not supported, even though the fseek() may
not return an error, so harden that funcion with our version.
---
 src/include/port/win32_port.h | 12 ++++--
 src/port/meson.build          |  2 +
 src/port/win32common.c        | 68 ++++++++++++++++++++++++++++++++
 src/port/win32fseek.c         | 74 +++++++++++++++++++++++++++++++++++
 src/port/win32stat.c          | 22 ++---------
 configure                     |  6 +++
 configure.ac                  |  1 +
 src/tools/msvc/Mkvcbuild.pm   |  2 +
 8 files changed, 165 insertions(+), 22 deletions(-)
 create mode 100644 src/port/win32common.c
 create mode 100644 src/port/win32fseek.c

diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 5116c2fc06..19206ce922 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -204,15 +204,21 @@ struct itimerval
 
 int			setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
 
+/* Convenience wrapper for GetFileType() */
+extern DWORD pgwin32_get_file_type(HANDLE hFile);
+
 /*
  * WIN32 does not provide 64-bit off_t, but does provide the functions operating
- * with 64-bit offsets.
+ * with 64-bit offsets. Also, fseek() might not give an error for unseekable
+ * streams, so harden that function with our version.
  */
 #define pgoff_t __int64
 
 #ifdef _MSC_VER
-#define fseeko(stream, offset, origin) _fseeki64(stream, offset, origin)
-#define ftello(stream) _ftelli64(stream)
+extern int  	_pgfseeko64(FILE *stream, pgoff_t offset, int origin);
+extern pgoff_t	_pgftello64(FILE *stream);
+#define fseeko(stream, offset, origin) _pgfseeko64(stream, offset, origin)
+#define ftello(stream) _pgftello64(stream)
 #else
 #ifndef fseeko
 #define fseeko(stream, offset, origin) fseeko64(stream, offset, origin)
diff --git a/src/port/meson.build b/src/port/meson.build
index b174b25021..24416b9bfc 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -29,10 +29,12 @@ if host_system == 'windows'
     'kill.c',
     'open.c',
     'system.c',
+    'win32common.c',
     'win32dlopen.c',
     'win32env.c',
     'win32error.c',
     'win32fdatasync.c',
+    'win32fseek.c',
     'win32getrusage.c',
     'win32link.c',
     'win32ntdll.c',
diff --git a/src/port/win32common.c b/src/port/win32common.c
new file mode 100644
index 0000000000..2fd78f7f93
--- /dev/null
+++ b/src/port/win32common.c
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * win32common.c
+ *	  Common routines shared among the win32*.c ports.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/port/win32common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#ifdef WIN32
+
+/*
+ * pgwin32_get_file_type
+ *
+ * Convenience wrapper for GetFileType() with specific error handling for all the
+ * port implementations.  Returns the file type associated with a HANDLE.
+ *
+ * On error, sets errno with FILE_TYPE_UNKNOWN as file type.
+ */
+DWORD
+pgwin32_get_file_type(HANDLE hFile)
+{
+	DWORD		fileType = FILE_TYPE_UNKNOWN;
+	DWORD		lastError;
+
+	errno = 0;
+
+	/*
+	 * When stdin, stdout, and stderr aren't associated with a stream the
+	 * special value -2 is returned:
+	 * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle
+	 */
+	if (hFile == INVALID_HANDLE_VALUE || hFile == (HANDLE) -2)
+	{
+		errno = EINVAL;
+		return FILE_TYPE_UNKNOWN;
+	}
+
+	fileType = GetFileType(hFile);
+	lastError = GetLastError();
+
+	/*
+	 * Invoke GetLastError in order to distinguish between a "valid" return of
+	 * FILE_TYPE_UNKNOWN and its return due to a calling error.  In case of
+	 * success, GetLastError() returns NO_ERROR.
+	 */
+	if (fileType == FILE_TYPE_UNKNOWN && lastError != NO_ERROR)
+	{
+		_dosmaperr(lastError);
+		return FILE_TYPE_UNKNOWN;
+	}
+
+	return fileType;
+}
+
+#endif							/* WIN32 */
diff --git a/src/port/win32fseek.c b/src/port/win32fseek.c
new file mode 100644
index 0000000000..85545c1f09
--- /dev/null
+++ b/src/port/win32fseek.c
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * win32fseek.c
+ *	  Replacements for fseeko() and ftello().
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/port/win32fseek.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#if defined(WIN32) && defined(_MSC_VER)
+
+/*
+ * _pgfseeko64
+ *
+ * Calling fseek() on a handle to a non-seeking device such as a pipe or
+ * a communications device is not supported, even though the fseek() may
+ * not return an error.
+ */
+int
+_pgfseeko64(FILE *stream, pgoff_t offset, int origin)
+{
+	DWORD			fileType;
+	HANDLE			hFile = (HANDLE) _get_osfhandle(_fileno(stream));
+
+	fileType = pgwin32_get_file_type(hFile);
+	if (errno != 0)
+		return -1;
+
+	if (fileType == FILE_TYPE_DISK)
+		return _fseeki64(stream, offset, origin);
+	else if (fileType == FILE_TYPE_CHAR || fileType == FILE_TYPE_PIPE)
+		errno = ESPIPE;
+	else
+		errno = EINVAL;
+
+	return -1;
+}
+
+/*
+ * _pgftello64
+ *
+ * Same as _pgfseeko64().
+ */
+pgoff_t
+_pgftello64(FILE *stream)
+{
+	DWORD			fileType;
+	HANDLE			hFile = (HANDLE) _get_osfhandle(_fileno(stream));
+
+	fileType = pgwin32_get_file_type(hFile);
+	if (errno != 0)
+		return -1;
+
+	if (fileType == FILE_TYPE_DISK)
+		return _ftelli64(stream);
+	else if (fileType == FILE_TYPE_CHAR || fileType == FILE_TYPE_PIPE)
+		errno = ESPIPE;
+	else
+		errno = EINVAL;
+
+	return -1;
+}
+
+#endif							/* defined(WIN32) && defined(_MSC_VER) */
diff --git a/src/port/win32stat.c b/src/port/win32stat.c
index b79da738b2..aa3a0c174e 100644
--- a/src/port/win32stat.c
+++ b/src/port/win32stat.c
@@ -258,33 +258,17 @@ _pgfstat64(int fileno, struct stat *buf)
 {
 	HANDLE		hFile = (HANDLE) _get_osfhandle(fileno);
 	DWORD		fileType = FILE_TYPE_UNKNOWN;
-	DWORD		lastError;
 	unsigned short st_mode;
 
-	/*
-	 * When stdin, stdout, and stderr aren't associated with a stream the
-	 * special value -2 is returned:
-	 * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle
-	 */
-	if (hFile == INVALID_HANDLE_VALUE || hFile == (HANDLE) -2 || buf == NULL)
+	if (buf == NULL)
 	{
 		errno = EINVAL;
 		return -1;
 	}
 
-	fileType = GetFileType(hFile);
-	lastError = GetLastError();
-
-	/*
-	 * Invoke GetLastError in order to distinguish between a "valid" return of
-	 * FILE_TYPE_UNKNOWN and its return due to a calling error.  In case of
-	 * success, GetLastError returns NO_ERROR.
-	 */
-	if (fileType == FILE_TYPE_UNKNOWN && lastError != NO_ERROR)
-	{
-		_dosmaperr(lastError);
+	fileType = pgwin32_get_file_type(hFile);
+	if (errno != 0)
 		return -1;
-	}
 
 	switch (fileType)
 	{
diff --git a/configure b/configure
index e221dd5b0f..ded18a0ead 100755
--- a/configure
+++ b/configure
@@ -16490,6 +16490,12 @@ esac
  ;;
 esac
 
+  case " $LIBOBJS " in
+  *" win32common.$ac_objext "* ) ;;
+  *) LIBOBJS="$LIBOBJS win32common.$ac_objext"
+ ;;
+esac
+
   case " $LIBOBJS " in
   *" win32dlopen.$ac_objext "* ) ;;
   *) LIBOBJS="$LIBOBJS win32dlopen.$ac_objext"
diff --git a/configure.ac b/configure.ac
index 3aa6c15c13..7f6620a364 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1893,6 +1893,7 @@ if test "$PORTNAME" = "win32"; then
   AC_LIBOBJ(kill)
   AC_LIBOBJ(open)
   AC_LIBOBJ(system)
+  AC_LIBOBJ(win32common)
   AC_LIBOBJ(win32dlopen)
   AC_LIBOBJ(win32env)
   AC_LIBOBJ(win32error)
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index e3ffc653e5..24599feb33 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -108,9 +108,11 @@ sub mkvcbuild
 	  pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c
 	  pqsignal.c mkdtemp.c qsort.c qsort_arg.c bsearch_arg.c quotes.c system.c
 	  strerror.c tar.c
+	  win32common.c
 	  win32dlopen.c
 	  win32env.c win32error.c
 	  win32fdatasync.c
+	  win32fseek.c
 	  win32getrusage.c
 	  win32gettimeofday.c
 	  win32link.c
-- 
2.39.2

#8Juan José Santamaría Flecha
juanjo.santamaria@gmail.com
In reply to: Michael Paquier (#7)
Re: Fix fseek() detection of unseekable files on WIN32

On Sun, Mar 19, 2023 at 12:45 PM Michael Paquier <michael@paquier.xyz>
wrote:

In short, I was thinking among the lines of something like the
attached, where I have invented a pgwin32_get_file_type() that acts as
a wrapper of GetFileType() in a new file called win32common.c, with
all the error handling we would use between fstat(), fseeko() and
ftello() centralized in a single code path.

The refactoring with win32common.c had better be separated into its
own patch, at the end, if using an approach like that.

My approach was trying to make something minimal so it could be
backpatchable. This looks fine for HEAD, but are you planning on something
similar for the other branches?

Doesn't pgwin32_get_file_type() fit in dirmod.c? Might be a question of
personal taste, I don't really have strong feelings against win32common.c.

Regards,

Juan José Santamaría Flecha

#9Michael Paquier
michael@paquier.xyz
In reply to: Juan José Santamaría Flecha (#8)
Re: Fix fseek() detection of unseekable files on WIN32

On Sun, Mar 19, 2023 at 08:10:10PM +0100, Juan José Santamaría Flecha wrote:

My approach was trying to make something minimal so it could be
backpatchable. This looks fine for HEAD, but are you planning on something
similar for the other branches?

Yes. This is actually not invasive down to 14 as the code is
consistent for these branches.

Doesn't pgwin32_get_file_type() fit in dirmod.c? Might be a question of
personal taste, I don't really have strong feelings against win32common.c.

Not sure about this one. I have considered it and dirmod.c includes
also bits for cygwin, while being aimed for higher-level routines like
rename(), unlink() or symlink(). This patch is only for WIN32, and
aimed for common parts in win32*.c code, so a separate file seemed a
bit cleaner to me at the end.
--
Michael

#10Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#9)
Re: Fix fseek() detection of unseekable files on WIN32

On Mon, Mar 20, 2023 at 07:06:22AM +0900, Michael Paquier wrote:

Not sure about this one. I have considered it and dirmod.c includes
also bits for cygwin, while being aimed for higher-level routines like
rename(), unlink() or symlink(). This patch is only for WIN32, and
aimed for common parts in win32*.c code, so a separate file seemed a
bit cleaner to me at the end.

By the way, do you think that we could be able to get a TAP test for
that? It does not seem that it needs to be that complicated, as long
as we use a pg_dump command that pipes its output to a pg_restore
command launched by system()?
--
Michael

#11Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#9)
1 attachment(s)
Re: Fix fseek() detection of unseekable files on WIN32

On Mon, Mar 20, 2023 at 07:06:22AM +0900, Michael Paquier wrote:

Not sure about this one. I have considered it and dirmod.c includes
also bits for cygwin, while being aimed for higher-level routines like
rename(), unlink() or symlink(). This patch is only for WIN32, and
aimed for common parts in win32*.c code, so a separate file seemed a
bit cleaner to me at the end.

After going through the installation of a Windows setup with meson and
ninja under VS, I have checked that this is working correctly by
myself, so I am going to apply that. One of the tests I have done
involved feeding a dump of the regression data through a pipe to
pg_restore, and the whole was able to work fine, while head broke when
using a pipe.

Digressing a bit, while I don't forget..

Spoiler 1: I don't think that recommending ActivePerl in the
documentation is a good idea these days. They do not provide anymore
a standalone installer that deploys the binaries you can use, and
they've made it really difficult to even access a "perl" command as it
has become necessary to use an extra command "state activate
--default" to link with a project registered in their stuff, meaning a
connection to their project. Once this command is launched, the
terminal links to a cached state in AppData. This is very unfriendly.
In comparison, relying on StrawberryPerl and Chocolatey feels like a
breeze..

Spoiler 2: mingw.org seems to be dead, and we have two links in the
docs referring to it.
--
Michael

Attachments:

v4-0001-fix-fseek-detection-of-unseekable-files-for-WIN32.patchtext/x-diff; charset=us-asciiDownload
From 6ce0e6c996d263d1d9d7348e55756445c80daf2d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 11 Apr 2023 14:33:29 +0900
Subject: [PATCH v4] fix fseek detection of unseekable files for WIN32

Calling fseek() on a handle to a non-seeking device such as a pipe or
a communications device is not supported, even though the fseek() may
not return an error, so harden that funcion with our version.
---
 src/include/port/win32_port.h | 12 ++++--
 src/port/meson.build          |  2 +
 src/port/win32common.c        | 68 +++++++++++++++++++++++++++++++
 src/port/win32fseek.c         | 75 +++++++++++++++++++++++++++++++++++
 src/port/win32stat.c          | 22 ++--------
 configure                     |  6 +++
 configure.ac                  |  1 +
 src/tools/msvc/Mkvcbuild.pm   |  2 +
 8 files changed, 166 insertions(+), 22 deletions(-)
 create mode 100644 src/port/win32common.c
 create mode 100644 src/port/win32fseek.c

diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 5116c2fc06..58965e0dfd 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -204,15 +204,21 @@ struct itimerval
 
 int			setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
 
+/* Convenience wrapper for GetFileType() */
+extern DWORD pgwin32_get_file_type(HANDLE hFile);
+
 /*
  * WIN32 does not provide 64-bit off_t, but does provide the functions operating
- * with 64-bit offsets.
+ * with 64-bit offsets.  Also, fseek() might not give an error for unseekable
+ * streams, so harden that function with our version.
  */
 #define pgoff_t __int64
 
 #ifdef _MSC_VER
-#define fseeko(stream, offset, origin) _fseeki64(stream, offset, origin)
-#define ftello(stream) _ftelli64(stream)
+extern int	_pgfseeko64(FILE *stream, pgoff_t offset, int origin);
+extern pgoff_t _pgftello64(FILE *stream);
+#define fseeko(stream, offset, origin) _pgfseeko64(stream, offset, origin)
+#define ftello(stream) _pgftello64(stream)
 #else
 #ifndef fseeko
 #define fseeko(stream, offset, origin) fseeko64(stream, offset, origin)
diff --git a/src/port/meson.build b/src/port/meson.build
index b174b25021..24416b9bfc 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -29,10 +29,12 @@ if host_system == 'windows'
     'kill.c',
     'open.c',
     'system.c',
+    'win32common.c',
     'win32dlopen.c',
     'win32env.c',
     'win32error.c',
     'win32fdatasync.c',
+    'win32fseek.c',
     'win32getrusage.c',
     'win32link.c',
     'win32ntdll.c',
diff --git a/src/port/win32common.c b/src/port/win32common.c
new file mode 100644
index 0000000000..2fd78f7f93
--- /dev/null
+++ b/src/port/win32common.c
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * win32common.c
+ *	  Common routines shared among the win32*.c ports.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/port/win32common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#ifdef WIN32
+
+/*
+ * pgwin32_get_file_type
+ *
+ * Convenience wrapper for GetFileType() with specific error handling for all the
+ * port implementations.  Returns the file type associated with a HANDLE.
+ *
+ * On error, sets errno with FILE_TYPE_UNKNOWN as file type.
+ */
+DWORD
+pgwin32_get_file_type(HANDLE hFile)
+{
+	DWORD		fileType = FILE_TYPE_UNKNOWN;
+	DWORD		lastError;
+
+	errno = 0;
+
+	/*
+	 * When stdin, stdout, and stderr aren't associated with a stream the
+	 * special value -2 is returned:
+	 * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle
+	 */
+	if (hFile == INVALID_HANDLE_VALUE || hFile == (HANDLE) -2)
+	{
+		errno = EINVAL;
+		return FILE_TYPE_UNKNOWN;
+	}
+
+	fileType = GetFileType(hFile);
+	lastError = GetLastError();
+
+	/*
+	 * Invoke GetLastError in order to distinguish between a "valid" return of
+	 * FILE_TYPE_UNKNOWN and its return due to a calling error.  In case of
+	 * success, GetLastError() returns NO_ERROR.
+	 */
+	if (fileType == FILE_TYPE_UNKNOWN && lastError != NO_ERROR)
+	{
+		_dosmaperr(lastError);
+		return FILE_TYPE_UNKNOWN;
+	}
+
+	return fileType;
+}
+
+#endif							/* WIN32 */
diff --git a/src/port/win32fseek.c b/src/port/win32fseek.c
new file mode 100644
index 0000000000..985313c825
--- /dev/null
+++ b/src/port/win32fseek.c
@@ -0,0 +1,75 @@
+/*-------------------------------------------------------------------------
+ *
+ * win32fseek.c
+ *	  Replacements for fseeko() and ftello().
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/port/win32fseek.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
+#include "postgres.h"
+#endif
+
+#if defined(WIN32) && defined(_MSC_VER)
+
+/*
+ * _pgfseeko64
+ *
+ * Calling fseek() on a handle to a non-seeking device such as a pipe or
+ * a communications device is not supported, and fseek() may not return
+ * an error.  This wrapper relies on the file type to check which cases
+ * are supported.
+ */
+int
+_pgfseeko64(FILE *stream, pgoff_t offset, int origin)
+{
+	DWORD		fileType;
+	HANDLE		hFile = (HANDLE) _get_osfhandle(_fileno(stream));
+
+	fileType = pgwin32_get_file_type(hFile);
+	if (errno != 0)
+		return -1;
+
+	if (fileType == FILE_TYPE_DISK)
+		return _fseeki64(stream, offset, origin);
+	else if (fileType == FILE_TYPE_CHAR || fileType == FILE_TYPE_PIPE)
+		errno = ESPIPE;
+	else
+		errno = EINVAL;
+
+	return -1;
+}
+
+/*
+ * _pgftello64
+ *
+ * Same as _pgfseeko64().
+ */
+pgoff_t
+_pgftello64(FILE *stream)
+{
+	DWORD		fileType;
+	HANDLE		hFile = (HANDLE) _get_osfhandle(_fileno(stream));
+
+	fileType = pgwin32_get_file_type(hFile);
+	if (errno != 0)
+		return -1;
+
+	if (fileType == FILE_TYPE_DISK)
+		return _ftelli64(stream);
+	else if (fileType == FILE_TYPE_CHAR || fileType == FILE_TYPE_PIPE)
+		errno = ESPIPE;
+	else
+		errno = EINVAL;
+
+	return -1;
+}
+
+#endif							/* defined(WIN32) && defined(_MSC_VER) */
diff --git a/src/port/win32stat.c b/src/port/win32stat.c
index b79da738b2..aa3a0c174e 100644
--- a/src/port/win32stat.c
+++ b/src/port/win32stat.c
@@ -258,33 +258,17 @@ _pgfstat64(int fileno, struct stat *buf)
 {
 	HANDLE		hFile = (HANDLE) _get_osfhandle(fileno);
 	DWORD		fileType = FILE_TYPE_UNKNOWN;
-	DWORD		lastError;
 	unsigned short st_mode;
 
-	/*
-	 * When stdin, stdout, and stderr aren't associated with a stream the
-	 * special value -2 is returned:
-	 * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle
-	 */
-	if (hFile == INVALID_HANDLE_VALUE || hFile == (HANDLE) -2 || buf == NULL)
+	if (buf == NULL)
 	{
 		errno = EINVAL;
 		return -1;
 	}
 
-	fileType = GetFileType(hFile);
-	lastError = GetLastError();
-
-	/*
-	 * Invoke GetLastError in order to distinguish between a "valid" return of
-	 * FILE_TYPE_UNKNOWN and its return due to a calling error.  In case of
-	 * success, GetLastError returns NO_ERROR.
-	 */
-	if (fileType == FILE_TYPE_UNKNOWN && lastError != NO_ERROR)
-	{
-		_dosmaperr(lastError);
+	fileType = pgwin32_get_file_type(hFile);
+	if (errno != 0)
 		return -1;
-	}
 
 	switch (fileType)
 	{
diff --git a/configure b/configure
index 905be9568b..dbea7eaf5f 100755
--- a/configure
+++ b/configure
@@ -16492,6 +16492,12 @@ esac
  ;;
 esac
 
+  case " $LIBOBJS " in
+  *" win32common.$ac_objext "* ) ;;
+  *) LIBOBJS="$LIBOBJS win32common.$ac_objext"
+ ;;
+esac
+
   case " $LIBOBJS " in
   *" win32dlopen.$ac_objext "* ) ;;
   *) LIBOBJS="$LIBOBJS win32dlopen.$ac_objext"
diff --git a/configure.ac b/configure.ac
index 8095dfcf1d..dda34304db 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1894,6 +1894,7 @@ if test "$PORTNAME" = "win32"; then
   AC_LIBOBJ(kill)
   AC_LIBOBJ(open)
   AC_LIBOBJ(system)
+  AC_LIBOBJ(win32common)
   AC_LIBOBJ(win32dlopen)
   AC_LIBOBJ(win32env)
   AC_LIBOBJ(win32error)
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3d1e0041b2..958206f315 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -108,9 +108,11 @@ sub mkvcbuild
 	  pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c
 	  pqsignal.c mkdtemp.c qsort.c qsort_arg.c bsearch_arg.c quotes.c system.c
 	  strerror.c tar.c
+	  win32common.c
 	  win32dlopen.c
 	  win32env.c win32error.c
 	  win32fdatasync.c
+	  win32fseek.c
 	  win32getrusage.c
 	  win32gettimeofday.c
 	  win32link.c
-- 
2.40.0

#12Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#11)
Re: Fix fseek() detection of unseekable files on WIN32

On Tue, Apr 11, 2023 at 02:43:25PM +0900, Michael Paquier wrote:

After going through the installation of a Windows setup with meson and
ninja under VS, I have checked that this is working correctly by
myself, so I am going to apply that. One of the tests I have done
involved feeding a dump of the regression data through a pipe to
pg_restore, and the whole was able to work fine, while head broke when
using a pipe.

Applied this one down to 14. The first responses from the buildfarm
are good, I'll keep an eye on all that.
--
Michael