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

View File

@@ -30,6 +30,7 @@
#include "access/xlog_internal.h"
#include "common/file_perm.h"
#include "common/logging.h"
#include "common/pg_parse_lsn.h"
#include "fe_utils/option_utils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -651,8 +652,6 @@ main(int argc, char **argv)
int c;
int option_index;
char *db_name;
uint32 hi,
lo;
pg_compress_specification compression_spec;
char *compression_detail = NULL;
char *compression_algorithm_str = "none";
@@ -689,9 +688,8 @@ main(int argc, char **argv)
basedir = pg_strdup(optarg);
break;
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);
endpos = ((uint64) hi) << 32 | lo;
break;
case 'h':
dbhost = pg_strdup(optarg);

View File

@@ -20,6 +20,7 @@
#include "common/file_perm.h"
#include "common/logging.h"
#include "common/pg_parse_lsn.h"
#include "fe_utils/option_utils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -729,8 +730,6 @@ main(int argc, char **argv)
};
int c;
int option_index;
uint32 hi,
lo;
char *db_name;
pg_logging_init(argv[0]);
@@ -801,14 +800,12 @@ main(int argc, char **argv)
break;
/* replication options */
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);
startpos = ((uint64) hi) << 32 | lo;
break;
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);
endpos = ((uint64) hi) << 32 | lo;
break;
case 'o':
{

View File

@@ -24,6 +24,14 @@ mkdir($stream_dir);
# Sanity checks for command line options.
$primary->command_fails(['pg_receivewal'],
'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(
[
'pg_receivewal',

View File

@@ -43,6 +43,22 @@ $node->command_fails(
'--start',
],
'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(
[

View File

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

View File

@@ -54,6 +54,22 @@ command_fails_like(
[ 'pg_waldump', '--end' => 'bad' ],
qr/error: invalid WAL location/,
'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
# case exercising the new rmgr below.

View File

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

View File

@@ -24,6 +24,7 @@ common_sources = files(
'percentrepl.c',
'pg_get_line.c',
'pg_lzcompress.c',
'pg_parse_lsn.c',
'pg_prng.c',
'pgfnames.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 */