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:
@@ -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\"",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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':
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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
55
src/common/pg_parse_lsn.c
Normal 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;
|
||||||
|
}
|
||||||
20
src/include/common/pg_parse_lsn.h
Normal file
20
src/include/common/pg_parse_lsn.h
Normal 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 */
|
||||||
Reference in New Issue
Block a user