diff --git a/src/bin/Makefile b/src/bin/Makefile index b4dfdba..9992f7a 100644 --- a/src/bin/Makefile +++ b/src/bin/Makefile @@ -14,7 +14,7 @@ top_builddir = ../.. include $(top_builddir)/src/Makefile.global SUBDIRS = initdb pg_ctl pg_dump \ - psql scripts pg_config pg_controldata pg_resetxlog pg_basebackup + psql scripts pg_config pg_controldata pg_resetxlog pg_basebackup xlogdump ifeq ($(PORTNAME), win32) SUBDIRS += pgevent diff --git a/src/bin/xlogdump/Makefile b/src/bin/xlogdump/Makefile new file mode 100644 index 0000000..d54640a --- /dev/null +++ b/src/bin/xlogdump/Makefile @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------- +# +# Makefile for src/bin/xlogdump +# +# Copyright (c) 1998-2012, PostgreSQL Global Development Group +# +# src/bin/pg_resetxlog/Makefile +# +#------------------------------------------------------------------------- + +PGFILEDESC = "xlogdump" +PGAPPICON=win32 + +subdir = src/bin/xlogdump +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS= xlogdump.o \ + $(WIN32RES) + +all: xlogdump + + +xlogdump: $(OBJS) $(shell find ../../backend ../../timezone -name objfiles.txt|xargs cat|tr -s " " "\012"|grep -v /main.o|sed 's/^/..\/..\/..\//') + $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) diff --git a/src/bin/xlogdump/xlogdump.c b/src/bin/xlogdump/xlogdump.c new file mode 100644 index 0000000..0f984e4 --- /dev/null +++ b/src/bin/xlogdump/xlogdump.c @@ -0,0 +1,468 @@ +#include "postgres.h" + +#include + +#include "access/xlogreader.h" +#include "access/rmgr.h" +#include "miscadmin.h" +#include "storage/ipc.h" +#include "utils/memutils.h" +#include "utils/guc.h" + +#include "getopt_long.h" + +/* + * needs to be declared because otherwise its defined in main.c which we cannot + * link from here. + */ +const char *progname = "xlogdump"; + +typedef struct XLogDumpPrivateData { + TimeLineID timeline; + char* outpath; + char* inpath; +} XLogDumpPrivateData; + +static void +XLogDumpXLogRead(const char *directory, TimeLineID timeline_id, + XLogRecPtr startptr, char *buf, Size count); + +static void +XLogDumpXLogWrite(const char *directory, TimeLineID timeline_id, + XLogRecPtr startptr, const char *buf, Size count); + +#define XLogFilePathWrite(path, base, tli, logSegNo) \ + snprintf(path, MAXPGPATH, "%s/%08X%08X%08X", base, tli, \ + (uint32) ((logSegNo) / XLogSegmentsPerXLogId), \ + (uint32) ((logSegNo) % XLogSegmentsPerXLogId)) + +static void +XLogDumpXLogWrite(const char *directory, TimeLineID timeline_id, + XLogRecPtr startptr, const char *buf, Size count) +{ + const char *p; + XLogRecPtr recptr; + Size nbytes; + + static int sendFile = -1; + static XLogSegNo sendSegNo = 0; + static uint32 sendOff = 0; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + uint32 startoff; + int segbytes; + int writebytes; + + startoff = recptr % XLogSegSize; + + if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo)) + { + char path[MAXPGPATH]; + + /* Switch to another logfile segment */ + if (sendFile >= 0) + close(sendFile); + + XLByteToSeg(recptr, sendSegNo); + XLogFilePathWrite(path, directory, timeline_id, sendSegNo); + + sendFile = open(path, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR); + if (sendFile < 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + path))); + } + sendOff = 0; + } + + /* Need to seek in the file? */ + if (sendOff != startoff) + { + if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0){ + char fname[MAXPGPATH]; + XLogFileName(fname, timeline_id, sendSegNo); + + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in log segment %s to offset %u: %m", + fname, + startoff))); + } + sendOff = startoff; + } + + /* How many bytes are within this segment? */ + if (nbytes > (XLogSegSize - startoff)) + segbytes = XLogSegSize - startoff; + else + segbytes = nbytes; + + writebytes = write(sendFile, p, segbytes); + if (writebytes <= 0) + { + char fname[MAXPGPATH]; + XLogFileName(fname, timeline_id, sendSegNo); + + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to log segment %s, offset %u, length %lu: %m", + fname, + sendOff, (unsigned long) segbytes))); + } + + /* Update state for read */ + XLByteAdvance(recptr, writebytes); + + sendOff += writebytes; + nbytes -= writebytes; + p += writebytes; + } +} + +/* this should probably be put in a general implementation */ +static void +XLogDumpXLogRead(const char *directory, TimeLineID timeline_id, + XLogRecPtr startptr, char *buf, Size count) +{ + char *p; + XLogRecPtr recptr; + Size nbytes; + + static int sendFile = -1; + static XLogSegNo sendSegNo = 0; + static uint32 sendOff = 0; + + p = buf; + recptr = startptr; + nbytes = count; + + while (nbytes > 0) + { + uint32 startoff; + int segbytes; + int readbytes; + + startoff = recptr % XLogSegSize; + + if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo)) + { + char fname[MAXFNAMELEN]; + char fpath[MAXPGPATH]; + + /* Switch to another logfile segment */ + if (sendFile >= 0) + close(sendFile); + + XLByteToSeg(recptr, sendSegNo); + + XLogFileName(fname, timeline_id, sendSegNo); + + snprintf(fpath, MAXPGPATH, "%s/%s", + directory == NULL ? XLOGDIR : directory, fname); + + sendFile = open(fpath, O_RDONLY, 0); + if (sendFile < 0) + { + /* + * If the file is not found, assume it's because the standby + * asked for a too old WAL segment that has already been + * removed or recycled. + */ + if (errno == ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("requested WAL segment %s has already been removed", + fname))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + fpath))); + } + sendOff = 0; + } + + /* Need to seek in the file? */ + if (sendOff != startoff) + { + if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0){ + char fname[MAXPGPATH]; + XLogFileName(fname, timeline_id, sendSegNo); + + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not seek in log segment %s to offset %u: %m", + fname, + startoff))); + } + sendOff = startoff; + } + + /* How many bytes are within this segment? */ + if (nbytes > (XLogSegSize - startoff)) + segbytes = XLogSegSize - startoff; + else + segbytes = nbytes; + + readbytes = read(sendFile, p, segbytes); + if (readbytes <= 0) + { + char fname[MAXPGPATH]; + XLogFileName(fname, timeline_id, sendSegNo); + + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from log segment %s, offset %u, length %lu: %m", + fname, + sendOff, (unsigned long) segbytes))); + } + + /* Update state for read */ + XLByteAdvance(recptr, readbytes); + + sendOff += readbytes; + nbytes -= readbytes; + p += readbytes; + } +} + +static void +XLogDumpReadPage(XLogReaderState* state, char* cur_page, XLogRecPtr startptr) +{ + XLogPageHeader page_header; + XLogDumpPrivateData *private = state->private_data; + + Assert((startptr % XLOG_BLCKSZ) == 0); + + XLogDumpXLogRead(private->inpath, private->timeline, startptr, + cur_page, XLOG_BLCKSZ); + + page_header = (XLogPageHeader)cur_page; + + if (page_header->xlp_magic != XLOG_PAGE_MAGIC) + { + elog(FATAL, "page header magic %x, should be %x at %X/%X", page_header->xlp_magic, + XLOG_PAGE_MAGIC, (uint32)(startptr << 32), (uint32)startptr); + } +} + +static void +XLogDumpWrite(XLogReaderState* state, char* data, Size len) +{ + static char zero[XLOG_BLCKSZ]; + XLogDumpPrivateData *private = state->private_data; + + if (data == NULL) + data = zero; + + if (private->outpath == NULL) + return; + + XLogDumpXLogWrite(private->outpath, private->timeline, state->curptr, + data, len); +} + +static void +XLogDumpFinishedRecord(XLogReaderState* state, XLogRecordBuffer* buf) +{ + XLogRecord *record = &buf->record; + const RmgrData *rmgr = &RmgrTable[record->xl_rmid]; + + StringInfo str = makeStringInfo(); + initStringInfo(str); + + rmgr->rm_desc(str, state->buf.record.xl_info, buf->record_data); + + fprintf(stdout, "xlog record: rmgr: %-11s, record_len: %6u, tot_len: %6u, tx: %10u, lsn: %X/%-8X, prev %X/%-8X, bkp: %u%u%u%u, desc: %s\n", + rmgr->rm_name, + record->xl_len, record->xl_tot_len, + record->xl_xid, + (uint32)(buf->origptr >> 32), (uint32)buf->origptr, + (uint32)(record->xl_prev >> 32), (uint32)record->xl_prev, + !!(XLR_BKP_BLOCK(0) & buf->record.xl_info), + !!(XLR_BKP_BLOCK(1) & buf->record.xl_info), + !!(XLR_BKP_BLOCK(2) & buf->record.xl_info), + !!(XLR_BKP_BLOCK(3) & buf->record.xl_info), + str->data); + +} + + +static void init() +{ + MemoryContextInit(); + IsPostmasterEnvironment = false; + log_min_messages = DEBUG1; + Log_error_verbosity = PGERROR_TERSE; + pg_timezone_initialize(); +} + +static void +usage(void) +{ + printf(_("%s reads/writes postgres transaction logs for debugging.\n\n"), + progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]...\n"), progname); + printf(_("\nOptions:\n")); + printf(_(" -v, --version output version information, then exit\n")); + printf(_(" -h, --help show this help, then exit\n")); + printf(_(" -s, --start from where recptr onwards to read\n")); + printf(_(" -e, --end up to which recptr to read\n")); + printf(_(" -t, --timeline which timeline do we want to read\n")); + printf(_(" -i, --inpath from where do we want to read? cwd/pg_xlog is the default\n")); + printf(_(" -o, --output where to write [start, end]\n")); + printf(_(" -f, --file wal file to parse\n")); +} + +int main(int argc, char **argv) +{ + uint32 xlogid; + uint32 xrecoff; + XLogReaderState *xlogreader_state; + XLogDumpPrivateData private; + XLogRecPtr from = InvalidXLogRecPtr; + XLogRecPtr to = InvalidXLogRecPtr; + bool bad_argument = false; + + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {"start", required_argument, NULL, 's'}, + {"end", required_argument, NULL, 'e'}, + {"timeline", required_argument, NULL, 't'}, + {"inpath", required_argument, NULL, 'i'}, + {"outpath", required_argument, NULL, 'o'}, + {"file", required_argument, NULL, 'f'}, + {NULL, 0, NULL, 0} + }; + int c; + int option_index; + + memset(&private, 0, sizeof(XLogDumpPrivateData)); + + while ((c = getopt_long(argc, argv, "hvs:e:t:i:o:f:", + long_options, &option_index)) != -1) + { + switch (c) + { + case 'h': + usage(); + exit(0); + break; + case 'v': + printf("Version: 0.1\n"); + exit(0); + break; + case 's': + if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2) + { + bad_argument = true; + fprintf(stderr, "couldn't parse -s\n"); + } + else + from = (uint64)xlogid << 32 | xrecoff; + break; + case 'e': + if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2) + { + bad_argument = true; + fprintf(stderr, "couldn't parse -e\n"); + } + else + to = (uint64)xlogid << 32 | xrecoff; + break; + case 't': + if (sscanf(optarg, "%d", &private.timeline) != 1) + { + bad_argument = true; + fprintf(stderr, "couldn't parse timeline -t\n"); + } + break; + case 'i': + private.inpath = strdup(optarg); + break; + case 'o': + private.outpath = strdup(optarg); + break; + case 'f': + fprintf(stderr, "--file is not yet implemented\n"); + bad_argument = true; + break; + default: + bad_argument = true; + break; + } + } + + if (optind < argc) + { + bad_argument = true; + fprintf(stderr, + _("%s: too many command-line arguments (first is \"%s\")\n"), + progname, argv[optind]); + } + + if (XLByteEQ(from, InvalidXLogRecPtr)) + { + bad_argument = true; + fprintf(stderr, + _("%s: -s invalid or missing: %s\n"), + progname, argv[optind]); + } + else if (XLByteEQ(to, InvalidXLogRecPtr)) + { + bad_argument = true; + fprintf(stderr, + _("%s: -e invalid or missing: %s\n"), + progname, argv[optind]); + } + else if (private.timeline == 0) + { + bad_argument = true; + fprintf(stderr, + _("%s: -t invalid or missing: %s\n"), + progname, argv[optind]); + } + + if (bad_argument) + { + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + progname); + exit(-1); + } + + init(); + + xlogreader_state = XLogReaderAllocate(); + + /* + * not set because we want all records, perhaps we want filtering later? + * xlogreader_state->is_record_interesting = + */ + xlogreader_state->finished_record = XLogDumpFinishedRecord; + + /* + * not set because we do not want to copy data to somewhere yet + * xlogreader_state->writeout_data = ; + */ + xlogreader_state->writeout_data = XLogDumpWrite; + + xlogreader_state->read_page = XLogDumpReadPage; + + xlogreader_state->private_data = &private; + + xlogreader_state->startptr = from; + xlogreader_state->endptr = to; + + XLogReaderRead(xlogreader_state); + XLogReaderFree(xlogreader_state); + return 0; +}