*** a/contrib/Makefile --- b/contrib/Makefile *************** *** 31,36 **** SUBDIRS = \ --- 31,37 ---- passwordcheck \ pg_archivecleanup \ pg_buffercache \ + pg_computemaxlsn \ pg_freespacemap \ pg_standby \ pg_stat_statements \ *** /dev/null --- b/contrib/pg_computemaxlsn/Makefile *************** *** 0 **** --- 1,22 ---- + # contrib/pg_computemaxlsn/Makefile + + PGFILEDESC = "pg_computemaxlsn - an utility to find max LSN from data pages" + PGAPPICON = win32 + + PROGRAM = pg_computemaxlsn + OBJS = pg_computemaxlsn.o $(WIN32RES) + + PG_CPPFLAGS = -I$(srcdir) + PG_LIBS = $(libpq_pgport) + + + ifdef USE_PGXS + PG_CONFIG = pg_config + PGXS := $(shell $(PG_CONFIG) --pgxs) + include $(PGXS) + else + subdir = contrib/pg_computemaxlsn + top_builddir = ../.. + include $(top_builddir)/src/Makefile.global + include $(top_srcdir)/contrib/contrib-global.mk + endif *** /dev/null --- b/contrib/pg_computemaxlsn/pg_computemaxlsn.c *************** *** 0 **** --- 1,545 ---- + /*------------------------------------------------------------------------- + * + * pg_computemaxlsn.c + * A utility to compute the maximum LSN in data pages + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/pg_computemaxlsn/pg_computemaxlsn.c + * + *------------------------------------------------------------------------- + */ + + /* + * We have to use postgres.h not postgres_fe.h here, because there's so much + * backend-only stuff for reading data files we need. But we need a + * frontend-ish environment otherwise. Hence this ugly hack. + */ + #define FRONTEND 1 + + #include "postgres.h" + + #include + #include + #include + #include + #include + #include + #include + #ifdef HAVE_GETOPT_H + #include + #endif + + #include "access/xlog_internal.h" + #include "catalog/catalog.h" + #include "storage/bufpage.h" + #include "storage/fd.h" + + /* Page header size */ + #define PAGEHDRSZ (sizeof(PageHeaderData)) + + /* + * relfile nodename validation allow only file name start with digit + */ + #define validateRelfilenodename(name) ((name[0] >= '0') && (name[0] <= '9')) + #define validateTablespaceDir(name) ((strlen(name) > 3) && (name[0] == 'P') && (name[1] == 'G') && (name[2] == '_')) + + + extern int optind; + extern char *optarg; + static const char *progname; + + static int FindMaxLSNinFile(char *filename, XLogRecPtr *maxlsn); + static int FindMaxLSNinDir(char *path, XLogRecPtr *maxlsn, bool is_fromlink); + static int FindMaxLSNinPgData(XLogRecPtr *maxlsn); + static void usage(void); + static int getLinkPath(struct stat * statbuf, char *path, char *linkpath, int length); + + + int + main(int argc, char *argv[]) + { + static struct option long_options[] = { + {"path", required_argument, NULL, 'p'}, + {"data-directory", no_argument, NULL, 'P'}, + {NULL, 0, NULL, 0} + }; + + int optindex; + int c; + char *DataDir; + int fd; + char path[MAXPGPATH]; + bool print_max_lsn = false; + bool print_pgdata_max_lsn = false; + char *LsnSearchPath = NULL; + XLogRecPtr maxLSN = 0; + XLogSegNo logSegNo = 0; + int result; + + set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_computemaxlsn")); + + progname = get_progname(argv[0]); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(); + exit(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("pg_computemaxlsn (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + while ((c = getopt_long(argc, argv, "p:P", long_options, &optindex)) != -1) + { + switch (c) + { + case 'p': + if (print_max_lsn) + { + fprintf(stderr, _("%s: multiple -p options are not supported.\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + print_max_lsn = true; + LsnSearchPath = strdup(optarg); + if (!path_is_relative_and_below_cwd(LsnSearchPath)) + { + fprintf(stderr, _("%s: Path \"%s\" should be relative and" + " must be in or below the data directory.\n"), + progname, LsnSearchPath); + exit(1); + } + break; + + case 'P': + if (print_pgdata_max_lsn) + { + fprintf(stderr, _("%s: multiple -P options are not supported.\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + print_pgdata_max_lsn = true; + break; + + default: + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + } + + if (print_max_lsn && print_pgdata_max_lsn) + { + fprintf(stderr, _("%s: both options -P and -p can not be combined.\n"), progname); + exit(1); + } + + if (optind == argc) + { + fprintf(stderr, _("%s: no data directory specified.\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + + if ((optind + 1) != argc) + { + fprintf(stderr, _("%s: mutiple data directories not supported.\n"), progname); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + + /* + * Don't allow pg_computemaxlsn to be run as root, to avoid overwriting + * the ownership of files in the data directory. We need only check for + * root -- any other user won't have sufficient permissions to modify + * files in the data directory. + */ + #ifndef WIN32 + if (geteuid() == 0) + { + fprintf(stderr, _("%s: cannot be executed by \"root\".\n"), + progname); + fprintf(stderr, _("You must run %s as the PostgreSQL superuser.\n"), + progname); + exit(1); + } + #endif + + DataDir = argv[optind]; + + if (chdir(DataDir) < 0) + { + fprintf(stderr, _("%s: could not change directory to \"%s\": %s\n"), + progname, DataDir, strerror(errno)); + exit(1); + } + + /* + * Check for a postmaster lock file --- if there is one, refuse to + * proceed, on grounds we might be interfering with a live installation. + */ + snprintf(path, MAXPGPATH, "postmaster.pid"); + + if ((fd = open(path, O_RDONLY, 0)) < 0) + { + if (errno != ENOENT) + { + fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"), + progname, path, strerror(errno)); + exit(1); + } + } + else + { + fprintf(stderr, _("%s: lock file \"%s\" exists\n" + "Is a server running? If not, delete the lock file and try again.\n"), + progname, path); + exit(1); + } + + /* By default we need to compute max lsn for database */ + if ((print_max_lsn == false) || (0 == strcmp(LsnSearchPath, "."))) + { + result = FindMaxLSNinPgData(&maxLSN); + } + else + { + struct stat fst; + + if (lstat(LsnSearchPath, &fst) < 0) + { + if (errno == ENOENT) + { + fprintf(stderr, _("%s: file or directory \"%s\" does not exists.\n"), + progname, LsnSearchPath); + } + else + { + fprintf(stderr, _("%s: could not stat file or directory \"%s\": %s\n"), + progname, LsnSearchPath, strerror(errno)); + } + exit(1); + } + + if (getLinkPath(&fst, LsnSearchPath, path, sizeof(path)) > 0) + { + result = FindMaxLSNinDir(path, &maxLSN, true); + } + else if (S_ISDIR(fst.st_mode)) + { + result = FindMaxLSNinDir(LsnSearchPath, &maxLSN, false); + } + else + { + result = FindMaxLSNinFile(LsnSearchPath, &maxLSN); + } + } + + if (0 == result) + { + XLByteToSeg(maxLSN, logSegNo); + + printf("Maximum LSN found is: %lX \n" + "WAL segment file name (fileid,seg): %X/%X\n", + maxLSN, (uint32) (logSegNo >> 32), (uint32) (logSegNo)); + } + + return 0; + } + + + /* + * PageHeaderIsValid: Check page is valid or not + */ + bool + PageHeaderIsValid(PageHeader page) + { + char *pagebytes; + int i; + + /* Check normal case */ + if (PageGetPageSize(page) == BLCKSZ && + PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION && + (page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 && + page->pd_lower >= SizeOfPageHeaderData && + page->pd_lower <= page->pd_upper && + page->pd_upper <= page->pd_special && + page->pd_special <= BLCKSZ && + page->pd_special == MAXALIGN(page->pd_special)) + return true; + + /* + * Check all-zeroes till page header; this is used only to log the page + * details even we detect invalid page we will continue to nex pages + */ + pagebytes = (char *) page; + for (i = 0; i < PAGEHDRSZ; i++) + { + if (pagebytes[i] != 0) + return false; + } + return true; + } + + + /* + * Read the maximum LSN number in the one of data file (relnode file). + * + */ + static int + FindMaxLSNinFile(char *filename, XLogRecPtr *maxlsn) + { + XLogRecPtr pagelsn; + off_t len, + seekpos; + uint32 nblocks, + blocknum; + char buffer[PAGEHDRSZ]; + int nbytes; + int fd; + + if ((fd = open(filename, O_RDONLY | PG_BINARY, 0)) < 0) + { + /* + * If file does not exist or or we can't read it. give error + */ + fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"), + progname, filename, strerror(errno)); + return -1; + } + + /* Calculate the number of pages in file */ + len = lseek(fd, 0L, SEEK_END); + if (len < 0) + { + close(fd); + fprintf(stderr, _("%s: .. file \"%s\" for seeking: %s\n"), + progname, filename, strerror(errno)); + return -1; + } + + nblocks = (len / BLCKSZ); + if (nblocks > RELSEG_SIZE) + { + /* + * In one relfilenode file length can't be more that RELSEG_SIZE + */ + close(fd); + fprintf(stderr, _("%s: .. file \"%s\" length is more than segment size: %d.\n"), + progname, filename, RELSEG_SIZE); + return -1; + } + + /* + * Read the only page header and validate; if we find invalid page log the + * details of page and continue to next page. + */ + seekpos = 0; + for (blocknum = 0; blocknum < nblocks; blocknum++) + { + len = lseek(fd, seekpos, SEEK_SET); + if (len != seekpos) + { + close(fd); + fprintf(stderr, _("%s: could not seek to next page \"%s\": %s\n"), + progname, filename, strerror(errno)); + return -1; + } + + nbytes = read(fd, buffer, PAGEHDRSZ); + if (nbytes < 0) + { + close(fd); + fprintf(stderr, _("%s: could not read file \"%s\": %s\n"), + progname, filename, strerror(errno)); + return -1; + } + + if (PageHeaderIsValid((PageHeader) buffer)) + { + pagelsn = PageGetLSN(buffer); + if (XLByteLT(*maxlsn, pagelsn)) + { + *maxlsn = pagelsn; + } + } + else + { + /* + * If page is invalid log the error and continue + */ + fprintf(stderr, _("%s: Invalid page found in file \"%s\" pagid:%d\n"), + progname, filename, blocknum); + } + seekpos += (off_t) BLCKSZ; + } + + close(fd); + return 0; + } + + /* + * Read the maximum LSN number in current directory; including sub directories + * and links. + */ + static int + FindMaxLSNinDir(char *path, XLogRecPtr *maxlsn, bool is_fromlink) + { + DIR *dir; + struct dirent *de; + char pathbuf[MAXPGPATH]; + struct stat statbuf; + char linkpath[MAXPGPATH]; + int result; + + dir = opendir(path); + if (NULL == dir) + { + fprintf(stderr, _("%s: could not open directory \"%s\": %s\n"), + progname, path, strerror(errno)); + return -1; + } + + while ((de = readdir(dir)) != NULL) + { + /* Skip special stuff */ + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + continue; + + /* Skip temporary files */ + if (strncmp(de->d_name, + PG_TEMP_FILE_PREFIX, + strlen(PG_TEMP_FILE_PREFIX)) == 0) + continue; + + /* + * Skip all the local/global temporary files, and read and read all + * reamining relfinenode files + */ + if (is_fromlink) + { + /* If directory is link then only allow PG_* path only */ + if (!validateTablespaceDir(de->d_name)) + continue; + } + else if (!validateRelfilenodename(de->d_name)) + continue; + + snprintf(pathbuf, MAXPGPATH, "%s/%s", path, de->d_name); + + if (lstat(pathbuf, &statbuf) != 0) + { + if (errno != ENOENT) + { + fprintf(stderr, _("%s: could not stat file or directory \"%s\": %s\n"), + progname, pathbuf, strerror(errno)); + } + /* If the file went away while scanning, it's no error. */ + continue; + } + + result = getLinkPath(&statbuf, pathbuf, linkpath, sizeof(linkpath)); + if (result < 0) + { + continue; + } + else if (result > 0) + { + (void) FindMaxLSNinDir(linkpath, maxlsn, true); + } + else if (S_ISDIR(statbuf.st_mode)) + (void) FindMaxLSNinDir(pathbuf, maxlsn, false); + else + (void) FindMaxLSNinFile(pathbuf, maxlsn); + } + + closedir(dir); + return 0; + } + + /* + * Get the link path. + * On success, returns length of link filename. + * and return zero incase of is file is not a link type. + * On failure, returns -1. + */ + static int + getLinkPath(struct stat * statbuf, char *path, char *linkpath, int length) + { + int rllen; + + if ( + #ifndef WIN32 + S_ISLNK(statbuf->st_mode) + #else + pgwin32_is_junction(path) + #endif + ) + { + #if defined(HAVE_READLINK) || defined(WIN32) + + rllen = readlink(path, linkpath, length); + if (rllen < 0) + { + fprintf(stderr, _("%s: could not read symbolic link \"%s\", so skipping file.\n"), + progname, path); + return -1; + } + + if (rllen >= length) + { + fprintf(stderr, _("%s: symbolic link \"%s\" target is too long, so skipping file.\n"), + progname, path); + + return -1; + } + + linkpath[rllen] = '\0'; + + return rllen; + #else + /* tablespaces are not supported on this platform */ + return -1; + #endif /* HAVE_READLINK */ + } + + return 0; + } + + + /* + * Read the maximum LSN number in the DATA directory. + */ + static int + FindMaxLSNinPgData(XLogRecPtr *maxlsn) + { + /* scan all the relfilenodes in data directory */ + if (0 != FindMaxLSNinDir("global", maxlsn, false)) + return -1; + if (0 != FindMaxLSNinDir("base", maxlsn, false)) + return -1; + if (0 != FindMaxLSNinDir("pg_tblspc", maxlsn, false)) + return -1; + + return 0; + } + + static void + usage(void) + { + printf(_("%s compute the maximum LSN in PostgreSQL data pages.\n\n"), progname); + printf(_("Usage:\n %s [OPTION]... DATADIR\n\n"), progname); + printf(_("Options:\n")); + printf(_(" -p, --path=RELATIVE_PATH print max LSN from file or directory name\n")); + printf(_(" -P, --data-directory print max LSN from whole data directory\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_("\nReport bugs to .\n")); + } *** a/doc/src/sgml/ref/allfiles.sgml --- b/doc/src/sgml/ref/allfiles.sgml *************** *** 177,182 **** Complete list of usable sgml source files in this directory. --- 177,183 ---- + *** /dev/null --- b/doc/src/sgml/ref/pg_computemaxlsn.sgml *************** *** 0 **** --- 1,79 ---- + + + + + pg_computemaxlsn + 1 + Application + + + + pg_computemaxlsn + computes the maximum LSN in database of a PostgreSQL database cluster + + + + pg_computemaxlsn + + + + + pg_computemaxlsn + + file-name | folder-name + datadir + + + + + Description + + pg_computemaxlsn computes maximun LSN from database pages. + + + + This utility can only be run by the user who installed the server, because + it requires read/write access to the data directory. + For safety reasons, you must specify the data directory on the command line. + pg_computemaxlsn does not use the environment variable + PGDATA. + + + + The + + + The or + for computing + maximun LSN from specific file or folder. File or folder should be relative and in or below the data directory. + + + + The + + + + + Notes + + + This command must not be used when the server is + running. pg_computemaxlsn will refuse to start up if + it finds a server lock file in the data directory. If the + server crashed then a lock file might have been left + behind; in that case you can remove the lock file to allow + pg_computemaxlsn to run. But before you do + so, make doubly certain that there is no server process still alive. + + + + *** a/doc/src/sgml/ref/pg_resetxlog.sgml --- b/doc/src/sgml/ref/pg_resetxlog.sgml *************** *** 135,140 **** PostgreSQL documentation --- 135,150 ---- largest entry in pg_xlog, use -l 00000001000000320000004B or higher. + + If pg_resetxlog complains that it cannot determine + valid data for pg_control, and if you do not have or corrupted + WAL segment files in the directory pg_xlog under the data directory, + then to identify larger WAL segment file from data files we can use utility pg_computemaxlsn + with + pg_resetxlog itself looks at the files in *** a/doc/src/sgml/reference.sgml --- b/doc/src/sgml/reference.sgml *************** *** 248,253 **** --- 248,254 ---- &pgControldata; &pgCtl; &pgResetxlog; + &pgComputemaxlsn; &postgres; &postmaster;