1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Add common LSN parser for user-supplied locations

pg_waldump (--start/--end), pg_recvlogical (--startpos/--endpos), and
pg_receivewal (--endpos) parsed user-supplied WAL locations with
sscanf("%X/%08X").  That accepts several forms rejected by pg_lsn input
and can proceed with a different location than the user specified:
overlong components, trailing characters, leading whitespace, signs, and
0x prefixes.

Add pg_parse_lsn() to src/common and use it for those command-line
options.  The helper accepts the pg_lsn text syntax only: one to eight
hexadecimal digits, a slash, one to eight hexadecimal digits, and no
trailing characters.  Each frontend tool keeps its existing error
message.

Use the same helper from pg_lsn_in_safe(), leaving only backend-specific
soft error reporting there.  This keeps frontend command-line validation
and backend pg_lsn input tied to one parser.

Bug: #19598
Reported-by: Michael Malis <malis@pgrust.com>
Author: Zexin Li <lizi.openmind@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/19598-aa67c8f4331611b4@postgresql.org
This commit is contained in:
Fujii Masao
2026-08-12 17:03:46 +09:00
parent 2eb9a8e84a
commit d6bf0ab170
11 changed files with 128 additions and 38 deletions

View File

@@ -13,13 +13,13 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "common/pg_parse_lsn.h"
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "utils/fmgrprotos.h" #include "utils/fmgrprotos.h"
#include "utils/numeric.h" #include "utils/numeric.h"
#include "utils/pg_lsn.h" #include "utils/pg_lsn.h"
#define MAXPG_LSNLEN 17 #define MAXPG_LSNLEN 17
#define MAXPG_LSNCOMPONENT 8
/*---------------------------------------------------------- /*----------------------------------------------------------
* Formatting and conversion routines. * Formatting and conversion routines.
@@ -31,29 +31,11 @@
XLogRecPtr XLogRecPtr
pg_lsn_in_safe(const char *str, Node *escontext) pg_lsn_in_safe(const char *str, Node *escontext)
{ {
int len1,
len2;
uint32 id,
off;
XLogRecPtr result; XLogRecPtr result;
/* Sanity check input format. */ if (pg_parse_lsn(str, &result))
len1 = strspn(str, "0123456789abcdefABCDEF"); return result;
if (len1 < 1 || len1 > MAXPG_LSNCOMPONENT || str[len1] != '/')
goto syntax_error;
len2 = strspn(str + len1 + 1, "0123456789abcdefABCDEF");
if (len2 < 1 || len2 > MAXPG_LSNCOMPONENT || str[len1 + 1 + len2] != '\0')
goto syntax_error;
/* Decode result. */
id = (uint32) strtoul(str, NULL, 16);
off = (uint32) strtoul(str + len1 + 1, NULL, 16);
result = ((uint64) id << 32) | off;
return result;
syntax_error:
ereturn(escontext, InvalidXLogRecPtr, ereturn(escontext, InvalidXLogRecPtr,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"", errmsg("invalid input syntax for type %s: \"%s\"",

View File

@@ -30,6 +30,7 @@
#include "access/xlog_internal.h" #include "access/xlog_internal.h"
#include "common/file_perm.h" #include "common/file_perm.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/pg_parse_lsn.h"
#include "fe_utils/option_utils.h" #include "fe_utils/option_utils.h"
#include "getopt_long.h" #include "getopt_long.h"
#include "libpq-fe.h" #include "libpq-fe.h"
@@ -651,8 +652,6 @@ main(int argc, char **argv)
int c; int c;
int option_index; int option_index;
char *db_name; char *db_name;
uint32 hi,
lo;
pg_compress_specification compression_spec; pg_compress_specification compression_spec;
char *compression_detail = NULL; char *compression_detail = NULL;
char *compression_algorithm_str = "none"; char *compression_algorithm_str = "none";
@@ -689,9 +688,8 @@ main(int argc, char **argv)
basedir = pg_strdup(optarg); basedir = pg_strdup(optarg);
break; break;
case 'E': case 'E':
if (sscanf(optarg, "%X/%08X", &hi, &lo) != 2) if (!pg_parse_lsn(optarg, &endpos))
pg_fatal("could not parse end position \"%s\"", optarg); pg_fatal("could not parse end position \"%s\"", optarg);
endpos = ((uint64) hi) << 32 | lo;
break; break;
case 'h': case 'h':
dbhost = pg_strdup(optarg); dbhost = pg_strdup(optarg);

View File

@@ -20,6 +20,7 @@
#include "common/file_perm.h" #include "common/file_perm.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/pg_parse_lsn.h"
#include "fe_utils/option_utils.h" #include "fe_utils/option_utils.h"
#include "getopt_long.h" #include "getopt_long.h"
#include "libpq-fe.h" #include "libpq-fe.h"
@@ -729,8 +730,6 @@ main(int argc, char **argv)
}; };
int c; int c;
int option_index; int option_index;
uint32 hi,
lo;
char *db_name; char *db_name;
pg_logging_init(argv[0]); pg_logging_init(argv[0]);
@@ -801,14 +800,12 @@ main(int argc, char **argv)
break; break;
/* replication options */ /* replication options */
case 'I': case 'I':
if (sscanf(optarg, "%X/%08X", &hi, &lo) != 2) if (!pg_parse_lsn(optarg, &startpos))
pg_fatal("could not parse start position \"%s\"", optarg); pg_fatal("could not parse start position \"%s\"", optarg);
startpos = ((uint64) hi) << 32 | lo;
break; break;
case 'E': case 'E':
if (sscanf(optarg, "%X/%08X", &hi, &lo) != 2) if (!pg_parse_lsn(optarg, &endpos))
pg_fatal("could not parse end position \"%s\"", optarg); pg_fatal("could not parse end position \"%s\"", optarg);
endpos = ((uint64) hi) << 32 | lo;
break; break;
case 'o': case 'o':
{ {

View File

@@ -24,6 +24,14 @@ mkdir($stream_dir);
# Sanity checks for command line options. # Sanity checks for command line options.
$primary->command_fails(['pg_receivewal'], $primary->command_fails(['pg_receivewal'],
'pg_receivewal needs target directory specified'); 'pg_receivewal needs target directory specified');
$primary->command_fails_like(
[ 'pg_receivewal', '--endpos' => '123456789/0' ],
qr/error: could not parse end position/,
'end position with first component wider than 32 bits');
$primary->command_fails_like(
[ 'pg_receivewal', '--endpos' => '1/2/3' ],
qr/error: could not parse end position/,
'end position with trailing garbage');
$primary->command_fails( $primary->command_fails(
[ [
'pg_receivewal', 'pg_receivewal',

View File

@@ -43,6 +43,22 @@ $node->command_fails(
'--start', '--start',
], ],
'no destination file'); 'no destination file');
$node->command_fails_like(
[ 'pg_recvlogical', '--startpos' => '123456789/0' ],
qr/error: could not parse start position/,
'start position with first component wider than 32 bits');
$node->command_fails_like(
[ 'pg_recvlogical', '--startpos' => '0x1/0' ],
qr/error: could not parse start position/,
'start position with 0x prefix');
$node->command_fails_like(
[ 'pg_recvlogical', '--endpos' => '0/123456789' ],
qr/error: could not parse end position/,
'end position with second component wider than 32 bits');
$node->command_fails_like(
[ 'pg_recvlogical', '--endpos' => '1/2/3' ],
qr/error: could not parse end position/,
'end position with trailing garbage');
$node->command_ok( $node->command_ok(
[ [

View File

@@ -27,6 +27,7 @@
#include "common/file_perm.h" #include "common/file_perm.h"
#include "common/file_utils.h" #include "common/file_utils.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/pg_parse_lsn.h"
#include "common/relpath.h" #include "common/relpath.h"
#include "getopt_long.h" #include "getopt_long.h"
#include "pg_waldump.h" #include "pg_waldump.h"
@@ -928,8 +929,6 @@ usage(void)
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
uint32 xlogid;
uint32 xrecoff;
XLogReaderState *xlogreader_state; XLogReaderState *xlogreader_state;
XLogDumpPrivate private; XLogDumpPrivate private;
XLogDumpConfig config; XLogDumpConfig config;
@@ -1047,13 +1046,12 @@ main(int argc, char **argv)
config.filter_by_extended = true; config.filter_by_extended = true;
break; break;
case 'e': case 'e':
if (sscanf(optarg, "%X/%08X", &xlogid, &xrecoff) != 2) if (!pg_parse_lsn(optarg, &private.endptr))
{ {
pg_log_error("invalid WAL location: \"%s\"", pg_log_error("invalid WAL location: \"%s\"",
optarg); optarg);
goto bad_argument; goto bad_argument;
} }
private.endptr = (uint64) xlogid << 32 | xrecoff;
break; break;
case 'f': case 'f':
config.follow = true; config.follow = true;
@@ -1145,14 +1143,12 @@ main(int argc, char **argv)
config.filter_by_extended = true; config.filter_by_extended = true;
break; break;
case 's': case 's':
if (sscanf(optarg, "%X/%08X", &xlogid, &xrecoff) != 2) if (!pg_parse_lsn(optarg, &private.startptr))
{ {
pg_log_error("invalid WAL location: \"%s\"", pg_log_error("invalid WAL location: \"%s\"",
optarg); optarg);
goto bad_argument; goto bad_argument;
} }
else
private.startptr = (uint64) xlogid << 32 | xrecoff;
break; break;
case 't': case 't':

View File

@@ -54,6 +54,22 @@ command_fails_like(
[ 'pg_waldump', '--end' => 'bad' ], [ 'pg_waldump', '--end' => 'bad' ],
qr/error: invalid WAL location/, qr/error: invalid WAL location/,
'invalid end LSN'); 'invalid end LSN');
command_fails_like(
[ 'pg_waldump', '--start' => '123456789/0' ],
qr/error: invalid WAL location/,
'start LSN with first component wider than 32 bits');
command_fails_like(
[ 'pg_waldump', '--start' => '0/123456789' ],
qr/error: invalid WAL location/,
'start LSN with second component wider than 32 bits');
command_fails_like(
[ 'pg_waldump', '--end' => '1/2/3' ],
qr/error: invalid WAL location/,
'end LSN with trailing garbage');
command_fails_like(
[ 'pg_waldump', '--end' => '0x1/0' ],
qr/error: invalid WAL location/,
'end LSN with 0x prefix');
# rmgr list: If you add one to the list, consider also adding a test # rmgr list: If you add one to the list, consider also adding a test
# case exercising the new rmgr below. # case exercising the new rmgr below.

View File

@@ -70,6 +70,7 @@ OBJS_COMMON = \
percentrepl.o \ percentrepl.o \
pg_get_line.o \ pg_get_line.o \
pg_lzcompress.o \ pg_lzcompress.o \
pg_parse_lsn.o \
pg_prng.o \ pg_prng.o \
pgfnames.o \ pgfnames.o \
psprintf.o \ psprintf.o \

View File

@@ -24,6 +24,7 @@ common_sources = files(
'percentrepl.c', 'percentrepl.c',
'pg_get_line.c', 'pg_get_line.c',
'pg_lzcompress.c', 'pg_lzcompress.c',
'pg_parse_lsn.c',
'pg_prng.c', 'pg_prng.c',
'pgfnames.c', 'pgfnames.c',
'psprintf.c', 'psprintf.c',

55
src/common/pg_parse_lsn.c Normal file
View File

@@ -0,0 +1,55 @@
/*-------------------------------------------------------------------------
*
* pg_parse_lsn.c
* Parse a WAL location (LSN) in its text form.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/common/pg_parse_lsn.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/pg_parse_lsn.h"
#define MAXPG_LSNCOMPONENT 8
/*
* pg_parse_lsn
*
* Parse a WAL location in the "%X/%X" text form used for pg_lsn values,
* requiring one to eight hexadecimal digits in each component and nothing
* else. Unlike sscanf(), this rejects components longer than eight
* hexadecimal digits, leading whitespace, signs, "0x" prefixes, and
* trailing characters.
*
* Returns true and sets *result on success; returns false on syntax
* error, leaving *result unchanged.
*/
bool
pg_parse_lsn(const char *str, XLogRecPtr *result)
{
size_t len1,
len2;
len1 = strspn(str, "0123456789abcdefABCDEF");
if (len1 < 1 || len1 > MAXPG_LSNCOMPONENT || str[len1] != '/')
return false;
len2 = strspn(str + len1 + 1, "0123456789abcdefABCDEF");
if (len2 < 1 || len2 > MAXPG_LSNCOMPONENT || str[len1 + 1 + len2] != '\0')
return false;
*result = ((uint64) strtoul(str, NULL, 16)) << 32 |
(uint32) strtoul(str + len1 + 1, NULL, 16);
return true;
}

View File

@@ -0,0 +1,20 @@
/*-------------------------------------------------------------------------
*
* pg_parse_lsn.h
* Parse a WAL location (LSN) in its text form.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/common/pg_parse_lsn.h
*
*-------------------------------------------------------------------------
*/
#ifndef PG_PARSE_LSN_H
#define PG_PARSE_LSN_H
#include "access/xlogdefs.h"
extern bool pg_parse_lsn(const char *str, XLogRecPtr *result);
#endif /* PG_PARSE_LSN_H */