├── .cirrus.yml ├── .editorconfig ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── __open_relation ├── btree_hexedit ├── gin_hexedit ├── hexedit.cfg ├── pg_filenodemapdata.c ├── pg_hexedit.c ├── pg_type.png ├── pg_type_typname_nsp_index.png ├── relation_hexedit └── t ├── 1249 ├── 2685 ├── .gitignore ├── expected_attributes.tags ├── expected_attributes_idx.tags ├── expected_empty_lsn.tags ├── expected_leaf_idx.tags ├── expected_no_attributes.tags ├── expected_no_attributes_idx.tags └── test_pg_hexedit /.cirrus.yml: -------------------------------------------------------------------------------- 1 | task: 2 | name: Linux - Debian Bullseye - Autoconf 3 | 4 | env: 5 | CPUS: 4 6 | BUILD_JOBS: 4 7 | 8 | CCACHE_DIR: /tmp/ccache_dir 9 | 10 | CFLAGS: -Og -g3 11 | CXXFLAGS: $CFLAGS 12 | CC: ccache gcc 13 | CXX: ccache g++ 14 | 15 | matrix: 16 | - PGBRANCH: master 17 | - PGBRANCH: REL_17_STABLE 18 | - PGBRANCH: REL_16_STABLE 19 | - PGBRANCH: REL_15_STABLE 20 | - PGBRANCH: REL_14_STABLE 21 | - PGBRANCH: REL_13_STABLE 22 | - PGBRANCH: REL_12_STABLE 23 | PGHOME: /tmp/postgres-${PGBRANCH} 24 | 25 | compute_engine_instance: 26 | image_project: pg-ci-images 27 | image: family/pg-ci-bullseye 28 | platform: linux 29 | cpu: $CPUS 30 | memory: 4G 31 | 32 | ccache_cache: 33 | folder: ${CCACHE_DIR} 34 | 35 | sysinfo_script: | 36 | id 37 | uname -a 38 | cat /proc/cmdline 39 | ulimit -a -H && ulimit -a -S 40 | export 41 | create_user_script: | 42 | useradd -m postgres 43 | chown -R postgres:postgres . 44 | mkdir -p ${CCACHE_DIR} 45 | chown -R postgres:postgres ${CCACHE_DIR} 46 | echo '* - memlock 134217728' > /etc/security/limits.d/postgres.conf 47 | su postgres -c "ulimit -l -H && ulimit -l -S" 48 | setup_core_files_script: | 49 | mkdir -m 770 /tmp/cores 50 | chown root:postgres /tmp/cores 51 | sysctl kernel.core_pattern='/tmp/cores/%e-%s-%p.core' 52 | configure_script: | 53 | su postgres <<-EOF 54 | git clone https://github.com/postgres/postgres.git 55 | cd ./postgres 56 | git checkout ${PGBRANCH} 57 | ./configure --prefix=${PGHOME} --enable-cassert 58 | EOF 59 | build_script: | 60 | su postgres <<-EOF 61 | cd ./postgres 62 | make -j${BUILD_JOBS} 63 | EOF 64 | upload_caches: ccache 65 | install_script: | 66 | su postgres <<-EOF 67 | cd ./postgres 68 | make install 69 | EOF 70 | test_script: | 71 | su postgres <<-EOF 72 | export PATH=${PGHOME}/bin:${PATH} 73 | make PG_CONFIG=${PGHOME}/bin/pg_config 74 | make PG_CONFIG=${PGHOME}/bin/pg_config dist 75 | make check 76 | EOF 77 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig, for indentation on Github 2 | # Per http://EditorConfig.org 3 | root = true 4 | 5 | # Unix-style newlines 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | 10 | # 4 space tabs for indentation 11 | [*.{c,h,y,l}] 12 | indent_style = tab 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .wxHexEditor 3 | /*.o 4 | /pg_filenodemapdata 5 | /pg_hexedit 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The GNU General Public License, Version 2, June 1991 (GPLv2) 2 | ============================================================ 3 | 4 | Copyright (c) 2018-2021, Crunchy Data Solutions, Inc. 5 | Copyright (c) 2017-2018, VMware, Inc. 6 | Copyright (c) 2002-2010, Red Hat, Inc. 7 | Copyright (c) 2011-2021, PostgreSQL Global Development Group 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this license 10 | document, but changing it is not allowed. 11 | 12 | Preamble 13 | -------- 14 | 15 | The licenses for most software are designed to take away your freedom to share 16 | and change it. By contrast, the GNU General Public License is intended to 17 | guarantee your freedom to share and change free software--to make sure the 18 | software is free for all its users. This General Public License applies to most 19 | of the Free Software Foundation's software and to any other program whose 20 | authors commit to using it. (Some other Free Software Foundation software is 21 | covered by the GNU Lesser General Public License instead.) You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not price. Our 25 | General Public Licenses are designed to make sure that you have the freedom to 26 | distribute copies of free software (and charge for this service if you wish), 27 | that you receive source code or can get it if you want it, that you can change 28 | the software or use pieces of it in new free programs; and that you know you can 29 | do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid anyone to deny 32 | you these rights or to ask you to surrender the rights. These restrictions 33 | translate to certain responsibilities for you if you distribute copies of the 34 | software, or if you modify it. 35 | 36 | For example, if you distribute copies of such a program, whether gratis or for a 37 | fee, you must give the recipients all the rights that you have. You must make 38 | sure that they, too, receive or can get the source code. And you must show them 39 | these terms so they know their rights. 40 | 41 | We protect your rights with two steps: (1) copyright the software, and (2) offer 42 | you this license which gives you legal permission to copy, distribute and/or 43 | modify the software. 44 | 45 | Also, for each author's protection and ours, we want to make certain that 46 | everyone understands that there is no warranty for this free software. If the 47 | software is modified by someone else and passed on, we want its recipients to 48 | know that what they have is not the original, so that any problems introduced by 49 | others will not reflect on the original authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software patents. We wish 52 | to avoid the danger that redistributors of a free program will individually 53 | obtain patent licenses, in effect making the program proprietary. To prevent 54 | this, we have made it clear that any patent must be licensed for everyone's free 55 | use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and modification 58 | follow. 59 | 60 | Terms And Conditions For Copying, Distribution And Modification 61 | --------------------------------------------------------------- 62 | 63 | **0.** This License applies to any program or other work which contains a notice 64 | placed by the copyright holder saying it may be distributed under the terms of 65 | this General Public License. The "Program", below, refers to any such program or 66 | work, and a "work based on the Program" means either the Program or any 67 | derivative work under copyright law: that is to say, a work containing the 68 | Program or a portion of it, either verbatim or with modifications and/or 69 | translated into another language. (Hereinafter, translation is included without 70 | limitation in the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not covered by 73 | this License; they are outside its scope. The act of running the Program is not 74 | restricted, and the output from the Program is covered only if its contents 75 | constitute a work based on the Program (independent of having been made by 76 | running the Program). Whether that is true depends on what the Program does. 77 | 78 | **1.** You may copy and distribute verbatim copies of the Program's source code 79 | as you receive it, in any medium, provided that you conspicuously and 80 | appropriately publish on each copy an appropriate copyright notice and 81 | disclaimer of warranty; keep intact all the notices that refer to this License 82 | and to the absence of any warranty; and give any other recipients of the Program 83 | a copy of this License along with the Program. 84 | 85 | You may charge a fee for the physical act of transferring a copy, and you may at 86 | your option offer warranty protection in exchange for a fee. 87 | 88 | **2.** You may modify your copy or copies of the Program or any portion of it, 89 | thus forming a work based on the Program, and copy and distribute such 90 | modifications or work under the terms of Section 1 above, provided that you also 91 | meet all of these conditions: 92 | 93 | * **a)** You must cause the modified files to carry prominent notices stating 94 | that you changed the files and the date of any change. 95 | 96 | * **b)** You must cause any work that you distribute or publish, that in whole 97 | or in part contains or is derived from the Program or any part thereof, to 98 | be licensed as a whole at no charge to all third parties under the terms of 99 | this License. 100 | 101 | * **c)** If the modified program normally reads commands interactively when 102 | run, you must cause it, when started running for such interactive use in the 103 | most ordinary way, to print or display an announcement including an 104 | appropriate copyright notice and a notice that there is no warranty (or 105 | else, saying that you provide a warranty) and that users may redistribute 106 | the program under these conditions, and telling the user how to view a copy 107 | of this License. (Exception: if the Program itself is interactive but does 108 | not normally print such an announcement, your work based on the Program is 109 | not required to print an announcement.) 110 | 111 | These requirements apply to the modified work as a whole. If identifiable 112 | sections of that work are not derived from the Program, and can be reasonably 113 | considered independent and separate works in themselves, then this License, and 114 | its terms, do not apply to those sections when you distribute them as separate 115 | works. But when you distribute the same sections as part of a whole which is a 116 | work based on the Program, the distribution of the whole must be on the terms of 117 | this License, whose permissions for other licensees extend to the entire whole, 118 | and thus to each and every part regardless of who wrote it. 119 | 120 | Thus, it is not the intent of this section to claim rights or contest your 121 | rights to work written entirely by you; rather, the intent is to exercise the 122 | right to control the distribution of derivative or collective works based on the 123 | Program. 124 | 125 | In addition, mere aggregation of another work not based on the Program with the 126 | Program (or with a work based on the Program) on a volume of a storage or 127 | distribution medium does not bring the other work under the scope of this 128 | License. 129 | 130 | **3.** You may copy and distribute the Program (or a work based on it, under 131 | Section 2) in object code or executable form under the terms of Sections 1 and 2 132 | above provided that you also do one of the following: 133 | 134 | * **a)** Accompany it with the complete corresponding machine-readable source 135 | code, which must be distributed under the terms of Sections 1 and 2 above on 136 | a medium customarily used for software interchange; or, 137 | 138 | * **b)** Accompany it with a written offer, valid for at least three years, to 139 | give any third party, for a charge no more than your cost of physically 140 | performing source distribution, a complete machine-readable copy of the 141 | corresponding source code, to be distributed under the terms of Sections 1 142 | and 2 above on a medium customarily used for software interchange; or, 143 | 144 | * **c)** Accompany it with the information you received as to the offer to 145 | distribute corresponding source code. (This alternative is allowed only for 146 | noncommercial distribution and only if you received the program in object 147 | code or executable form with such an offer, in accord with Subsection b 148 | above.) 149 | 150 | The source code for a work means the preferred form of the work for making 151 | modifications to it. For an executable work, complete source code means all the 152 | source code for all modules it contains, plus any associated interface 153 | definition files, plus the scripts used to control compilation and installation 154 | of the executable. However, as a special exception, the source code distributed 155 | need not include anything that is normally distributed (in either source or 156 | binary form) with the major components (compiler, kernel, and so on) of the 157 | operating system on which the executable runs, unless that component itself 158 | accompanies the executable. 159 | 160 | If distribution of executable or object code is made by offering access to copy 161 | from a designated place, then offering equivalent access to copy the source code 162 | from the same place counts as distribution of the source code, even though third 163 | parties are not compelled to copy the source along with the object code. 164 | 165 | **4.** You may not copy, modify, sublicense, or distribute the Program except as 166 | expressly provided under this License. Any attempt otherwise to copy, modify, 167 | sublicense or distribute the Program is void, and will automatically terminate 168 | your rights under this License. However, parties who have received copies, or 169 | rights, from you under this License will not have their licenses terminated so 170 | long as such parties remain in full compliance. 171 | 172 | **5.** You are not required to accept this License, since you have not signed 173 | it. However, nothing else grants you permission to modify or distribute the 174 | Program or its derivative works. These actions are prohibited by law if you do 175 | not accept this License. Therefore, by modifying or distributing the Program (or 176 | any work based on the Program), you indicate your acceptance of this License to 177 | do so, and all its terms and conditions for copying, distributing or modifying 178 | the Program or works based on it. 179 | 180 | **6.** Each time you redistribute the Program (or any work based on the 181 | Program), the recipient automatically receives a license from the original 182 | licensor to copy, distribute or modify the Program subject to these terms and 183 | conditions. You may not impose any further restrictions on the recipients' 184 | exercise of the rights granted herein. You are not responsible for enforcing 185 | compliance by third parties to this License. 186 | 187 | **7.** If, as a consequence of a court judgment or allegation of patent 188 | infringement or for any other reason (not limited to patent issues), conditions 189 | are imposed on you (whether by court order, agreement or otherwise) that 190 | contradict the conditions of this License, they do not excuse you from the 191 | conditions of this License. If you cannot distribute so as to satisfy 192 | simultaneously your obligations under this License and any other pertinent 193 | obligations, then as a consequence you may not distribute the Program at all. 194 | For example, if a patent license would not permit royalty-free redistribution of 195 | the Program by all those who receive copies directly or indirectly through you, 196 | then the only way you could satisfy both it and this License would be to refrain 197 | entirely from distribution of the Program. 198 | 199 | If any portion of this section is held invalid or unenforceable under any 200 | particular circumstance, the balance of the section is intended to apply and the 201 | section as a whole is intended to apply in other circumstances. 202 | 203 | It is not the purpose of this section to induce you to infringe any patents or 204 | other property right claims or to contest validity of any such claims; this 205 | section has the sole purpose of protecting the integrity of the free software 206 | distribution system, which is implemented by public license practices. Many 207 | people have made generous contributions to the wide range of software 208 | distributed through that system in reliance on consistent application of that 209 | system; it is up to the author/donor to decide if he or she is willing to 210 | distribute software through any other system and a licensee cannot impose that 211 | choice. 212 | 213 | This section is intended to make thoroughly clear what is believed to be a 214 | consequence of the rest of this License. 215 | 216 | **8.** If the distribution and/or use of the Program is restricted in certain 217 | countries either by patents or by copyrighted interfaces, the original copyright 218 | holder who places the Program under this License may add an explicit 219 | geographical distribution limitation excluding those countries, so that 220 | distribution is permitted only in or among countries not thus excluded. In such 221 | case, this License incorporates the limitation as if written in the body of this 222 | License. 223 | 224 | **9.** The Free Software Foundation may publish revised and/or new versions of 225 | the General Public License from time to time. Such new versions will be similar 226 | in spirit to the present version, but may differ in detail to address new 227 | problems or concerns. 228 | 229 | Each version is given a distinguishing version number. If the Program specifies 230 | a version number of this License which applies to it and "any later version", 231 | you have the option of following the terms and conditions either of that version 232 | or of any later version published by the Free Software Foundation. If the 233 | Program does not specify a version number of this License, you may choose any 234 | version ever published by the Free Software Foundation. 235 | 236 | **10.** If you wish to incorporate parts of the Program into other free programs 237 | whose distribution conditions are different, write to the author to ask for 238 | permission. For software which is copyrighted by the Free Software Foundation, 239 | write to the Free Software Foundation; we sometimes make exceptions for this. 240 | Our decision will be guided by the two goals of preserving the free status of 241 | all derivatives of our free software and of promoting the sharing and reuse of 242 | software generally. 243 | 244 | No Warranty 245 | ----------- 246 | 247 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR 248 | THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE 249 | STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 250 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 251 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 252 | PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 253 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 254 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 255 | 256 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 257 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 258 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 259 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR 260 | INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA 261 | BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 262 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER 263 | OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 264 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------- 2 | # 3 | # Makefile for pg_hexedit 4 | # 5 | # Copyright (c) 2018-2021, Crunchy Data Solutions, Inc. 6 | # Copyright (c) 2017-2018, VMware, Inc. 7 | # Copyright (c) 2002-2010, Red Hat, Inc. 8 | # Copyright (c) 2011-2021, PostgreSQL Global Development Group 9 | # 10 | #------------------------------------------------------------------------- 11 | 12 | PGFILEDESC = "pg_hexedit - emits descriptive XML tag format for PostgreSQL relation files" 13 | PGAPPICON=win32 14 | HEXEDIT_VERSION = 0.1 15 | 16 | PG_CONFIG = pg_config 17 | PGSQL_CFLAGS = $(shell $(PG_CONFIG) --cflags) 18 | PGSQL_INCLUDE_DIR = $(shell $(PG_CONFIG) --includedir-server) 19 | PGSQL_LDFLAGS = $(shell $(PG_CONFIG) --ldflags) 20 | PGSQL_LIB_DIR = $(shell $(PG_CONFIG) --libdir) 21 | PGSQL_PKGLIB_DIR = $(shell $(PG_CONFIG) --pkglibdir) 22 | PGSQL_BIN_DIR = $(shell $(PG_CONFIG) --bindir) 23 | 24 | DISTFILES= README.md Makefile pg_hexedit.c pg_filenodemapdata.c 25 | TESTFILES= t/1249 t/2685 t/expected_attributes.tags \ 26 | t/expected_attributes_idx.tags t/expected_empty_lsn.tags \ 27 | t/expected_leaf_idx.tags t/expected_no_attributes.tags \ 28 | t/expected_no_attributes_idx.tags t/test_pg_hexedit 29 | 30 | all: pg_hexedit pg_filenodemapdata 31 | 32 | pg_hexedit: pg_hexedit.o 33 | ${CC} ${PGSQL_LDFLAGS} ${LDFLAGS} -o pg_hexedit pg_hexedit.o -L${PGSQL_LIB_DIR} -L${PGSQL_PKGLIB_DIR} -lpgport -lpgcommon 34 | 35 | pg_filenodemapdata: pg_filenodemapdata.o 36 | ${CC} ${PGSQL_LDFLAGS} ${LDFLAGS} -o pg_filenodemapdata pg_filenodemapdata.o -L${PGSQL_LIB_DIR} -L${PGSQL_PKGLIB_DIR} -lpgport 37 | 38 | pg_hexedit.o: pg_hexedit.c 39 | ${CC} ${PGSQL_CFLAGS} ${CFLAGS} -I${PGSQL_INCLUDE_DIR} pg_hexedit.c -c 40 | 41 | pg_filenodemapdata.o: pg_filenodemapdata.c 42 | ${CC} ${PGSQL_CFLAGS} ${CFLAGS} -I${PGSQL_INCLUDE_DIR} pg_filenodemapdata.c -c 43 | 44 | check: 45 | t/test_pg_hexedit 46 | 47 | dist: 48 | rm -rf pg_hexedit-${HEXEDIT_VERSION} pg_hexedit-${HEXEDIT_VERSION}.tar.gz 49 | mkdir pg_hexedit-${HEXEDIT_VERSION} 50 | cp -p ${DISTFILES} pg_hexedit-${HEXEDIT_VERSION} 51 | mkdir pg_hexedit-${HEXEDIT_VERSION}/t 52 | cp -p ${TESTFILES} pg_hexedit-${HEXEDIT_VERSION}/t 53 | tar cfz pg_hexedit-${HEXEDIT_VERSION}.tar.gz pg_hexedit-${HEXEDIT_VERSION} 54 | rm -rf pg_hexedit-${HEXEDIT_VERSION} 55 | 56 | install: 57 | mkdir -p $(DESTDIR)$(PGSQL_BIN_DIR) 58 | install pg_hexedit $(DESTDIR)$(PGSQL_BIN_DIR) 59 | install pg_filenodemapdata $(DESTDIR)$(PGSQL_BIN_DIR) 60 | 61 | uninstall: 62 | rm -f '$(DESTDIR)$(PGSQL_BIN_DIR)/pg_hexedit$(X)' 63 | rm -f '$(DESTDIR)$(PGSQL_BIN_DIR)/pg_filenodemapdata$(X)' 64 | 65 | clean: 66 | rm -f *.o pg_hexedit pg_filenodemapdata 67 | rm -f t/*diff 68 | rm -f t/output*tags 69 | 70 | distclean: 71 | rm -f *.o pg_hexedit pg_filenodemapdata 72 | rm -f t/*diff 73 | rm -f t/output*tags 74 | rm -rf pg_hexedit-${HEXEDIT_VERSION} pg_hexedit-${HEXEDIT_VERSION}.tar.gz 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pg_hexedit 2 | ========== 3 | 4 | ## Open PostgreSQL relation files in a hex editor with tags and annotations 5 | 6 | Copyright (c) 2018-2021, Crunchy Data Solutions, Inc. 7 | Copyright (c) 2017-2018, VMware, Inc. 8 | Copyright (c) 2002-2010, Red Hat, Inc. 9 | Copyright (c) 2011-2021, PostgreSQL Global Development Group 10 | 11 | Author: Peter Geoghegan [``](mailto:pg@bowt.ie) 12 | Author (pg_filedump): Patrick Macdonald [``](mailto:patrickm@redhat.com) 13 | 14 | License: [GNU General Public License version 2](https://opensource.org/licenses/GPL-2.0) 15 | 16 | [![Build Status](https://img.shields.io/cirrus/github/petergeoghegan/pg_hexedit/master)](https://cirrus-ci.com/github/petergeoghegan/pg_hexedit) 17 | 18 | ## Overview 19 | 20 | pg_hexedit is an experimental toolkit to format PostgreSQL heap, sequence, and 21 | index files (B-Tree, GiST, GIN, hash, BRIN, and SP-GiST indexes) when opened 22 | within the open source GUI hex editor 23 | [wxHexEditor](https://github.com/EUA/wxHexEditor). It makes viewing and 24 | editing PostgreSQL relation files *significantly* easier. PostgreSQL versions 25 | 12+ are supported. 26 | 27 | ![wxHexEditor with pg_type](./pg_type.png) 28 | *wxHexEditor with pg_type system catalog table* 29 | 30 | ![wxHexEditor with pg_type_typname_nsp_index](./pg_type_typname_nsp_index.png) 31 | *wxHexEditor with pg_type_typname_nsp_index system catalog index* 32 | 33 | __CAUTION:__ Do not use pg_hexedit with a PostgreSQL data directory if you are 34 | not prepared to have it __corrupt data__! pg_hexedit is primarily made 35 | available for educational purposes. It is an *experimental* tool, originally 36 | used for simulating corruption/corruption analysis. 37 | 38 | pg_hexedit is a fork of 39 | [pg_filedump](https://wiki.postgresql.org/wiki/Pg_filedump). The pg_hexedit 40 | frontend executable generates wxHexEditor format XML. pg_hexedit also provides 41 | "convenience" bash scripts. The scripts automate everything: they take the 42 | name of a table or index as an argument, connect to a database and find where 43 | the underlying file for the table or index is located, generate wxHexEditor 44 | format XML for the relevant file, and open the wxHexEditor GUI to display the 45 | relevant file with tags and annotations. 46 | 47 | ### Annotation, tags, and the use of color 48 | 49 | pg_hexedit uses color to convey conceptual similarities between distinct 50 | fields. For example, the `t_infomask2` and `t_infomask` heap tuple fields are 51 | both green. *Font* color is sometimes used to convey the status of a value 52 | contained within a particular field, without that information necessarily being 53 | directly inferred from the affected field (it could come from another metadata 54 | field within the same tuple). For example, MultiXact `xmax` values have green 55 | text, though the enclosing `xmax` tag's color is unaffected (it is always red). 56 | Similarly, non-contrasting font colors are used to deemphasize the contents of 57 | a field where the contents are redundant or otherwise insignificant. For 58 | example, in the common case where a never-updated heap tuple's `t_ctid` fields 59 | point to the heap tuple itself, a non-contrasting font color is used (a shade 60 | of blue is used that is very similar to the enclosing `t_ctid` field color). 61 | 62 | pg_hexedit annotations use field names and status flags that are grep'able from 63 | a PostgreSQL source code directory. While pg_hexedit aims to make interpreting 64 | the contents of pages as intuitive as possible, it does not go as far as 65 | interpreting the contents on the user's behalf. For example, `t_infomask` flag 66 | bits appear in annotations as a simple combination of "base" flag bits, rather 67 | than presenting the user with the logical state of `t_infomask` bits based on 68 | the combination of bits set: composite/logical flags such as `HEAP_XMIN_FROZEN` 69 | and `HEAP_XMAX_SHR_LOCK` will never appear in `t_infomask` annotations. 70 | 71 | pg_hexedit dynamically assigns colors to columns/attributes based on the 72 | attribute name. Indexes will have the same attribute field colors as the 73 | corresponding attributes in the tables that are indexed, since their 74 | `pg_attribute.attname` values will match. 75 | 76 | See also: 77 | [PostgreSQL documentation - heap_tuple_infomask_flags() contrib/pageinspect 78 | function](https://www.postgresql.org/docs/13/pageinspect.html#id-1.11.7.31.5) 79 | [PostgreSQL documentation - Database Page 80 | Layout](https://www.postgresql.org/docs/current/static/storage-page-layout.html) 81 | [PostgreSQL documentation - Preventing Transaction ID Wraparound Failures (xmin 82 | and xmax fields, Multixacts, and 83 | freezing)](https://www.postgresql.org/docs/current/static/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND) 84 | 85 | ## Initial setup on Linux 86 | 87 | There are no pg_hexedit packages available. pg_hexedit follows a simple 88 | trunk-based development model without any formal assigned versions. The only 89 | way to get pg_hexedit is to build it from source yourself. This is a 90 | deliberate trade-off; pg_hexedit is experimental, and it's not difficult to 91 | build from source anyway. 92 | 93 | ### Building pg_hexedit 94 | 95 | To build pg_hexedit you will need to have a PostgreSQL source tree or complete 96 | install tree (with include files). PostgreSQL system packages should provide 97 | all you need. If you run into trouble when building against system packages, 98 | please open a Github issue. 99 | 100 | The Makefile builds the pg_hexedit and pg_filenodemapdata frontend utility 101 | programs. 102 | [pg_config](https://www.postgresql.org/docs/current/app-pgconfig.html) must be 103 | visible in your $PATH. To build pg_hexedit from within the source directory: 104 | 105 | ```shell 106 | $ which pg_config # Check which Postgres installation you'll build against: 107 | /code/postgresql/REL_16_STABLE/install/bin/pg_config 108 | $ make 109 | ``` 110 | 111 | Once this step succeeds, you can usually move on to the next initial setup step 112 | (which is to install wxHexEditor). You probably won't need to actually install 113 | the pg_hexedit executables you just built. (You'll probably only ever use 114 | pg_hexedit through the convenience bash scripts, which are designed to be run 115 | from the pg_hexedit source directory directly. The convenience scripts expect 116 | to find the pg_hexedit frontend program in the current working directory.) 117 | 118 | If you really want to install the pg_hexedit frontend utility programs, you can 119 | do so in the usual way: 120 | 121 | ```shell 122 | $ make install 123 | ``` 124 | 125 | Note that the convenience scripts won't be installed. 126 | 127 | ### Installing wxHexEditor 128 | 129 | There are wxHexEditor packages available on major Linux platforms, though they 130 | all seem to use older versions. It is recommended that you use wxHexEditor 131 | version 0.25 or higher, since that version has fixes for a couple of bugs that 132 | affect the tag feature that pg_hexedit targets (see also: [wxHexEditor bug 133 | 87](https://github.com/EUA/wxHexEditor/issues/87)). At the time of writing 134 | there are no version 0.25 packages, so installing 0.25 actually means building 135 | wxHexEditor from its git master branch. You can get by with an older version, 136 | though you can expect some aspects of how tags are displayed to be glitchy. 137 | 138 | #### Installing wxHexEditor system package (easier, currently not recommended) 139 | 140 | On Debian-based systems you can install wxhexeditor in the usual way: 141 | 142 | ```shell 143 | $ sudo apt-get install wxhexeditor 144 | ``` 145 | 146 | #### Building wxHexEditor from source 147 | 148 | It's only mildly inconvenient to build wxHexEditor on a modern desktop Linux 149 | system. On Debian-based systems with source repositories set up, obtaining all 150 | build dependencies quickly should be straightforward: 151 | 152 | ```shell 153 | $ sudo apt-get build-dep wxhexeditor 154 | $ # (Install dependencies) 155 | $ sudo apt-get install libtool 156 | ``` 157 | 158 | Note that this doesn't actually install any wxHexEditor package. It just 159 | installs the dependencies to build the wxHexEditor source package, which, along 160 | with libtool, should be all we need to build wxHexEditor from git tip. 161 | 162 | Build wxHexEditor's master branch from source/git by cloning the official 163 | git repo, and following the instructions that it provides: 164 | 165 | [https://github.com/EUA/wxHexEditor](https://github.com/EUA/wxHexEditor) 166 | 167 | ## Initial setup on MacOS 168 | 169 | These steps should help you install pg_hexedit on MacOS (tested on Catalina 170 | with installed [Homebrew](https://brew.sh/)): 171 | 172 | 1. `brew install automake autoconf libtool wxmac` 173 | 1. Get [wxHexEditor source code](https://github.com/EUA/wxHexEditor) and edit 174 | `Makefile` removing the line mentioning OpenMP (`LIBS += -lgomp`) 175 | 1. Build wxHexEditor (`make`) 176 | 1. Get pg_hexedit source code, build (`make`) with `$PATH` including path 177 | to PostgreSQL binaries (Postgres installed via Homebrew – 178 | `brew install postgresql` – should be enough) 179 | 1. In hexedit.cfg, check `export HEXEDITOR`, it needs to point to 180 | the `wxHexEditor` binary (created above) 181 | 182 | Note: The convenience scripts set `$HOME` to the current working directory so 183 | that wxHexEditor reads its settings from a convenience-script-generated 184 | .wxHexEditor config file rather than the true user-wide/system-wide config 185 | file. This is like an `.ini` file. This hack is currently broken on MacOS. 186 | The relation_hexedit script works, but not exactly as designed. For example, 187 | the optimal "Bytes per line limit" for pg_hexedit seems to be 32, but that's 188 | not the default. 189 | 190 | (When wxHexEditor is built on MacOS, it will look for the config file in 191 | /Library/Preferences or ~/Library/Preferences - not in the user's home 192 | directory. It's not clear how the hack that works on Linux can be adopted to 193 | MacOS.) 194 | 195 | Suggested partial workaround: comment out the `HOME=$(pwd)` line in the 196 | `__open_relation` file (which is used by relation_hexedit). This will at least 197 | allow you to start up pg_hexedit in a way that works with your system/user wide 198 | wxHexEditor config. 199 | 200 | ## Quickstart guide - Using the convenience scripts 201 | 202 | pg_hexedit and wxHexEditor can be invoked using convenience scripts that take 203 | care of everything. These are designed to be run on a PostgreSQL backend 204 | hacker's laptop, while the target PostgreSQL server is actually running. The 205 | server is queried to locate the relevant relation files. The scripts also take 206 | care of adding convenience offsets to the wxHexEditor cache, which can be used 207 | to quickly locate internal pages of a B-Tree, for example. The wxHexEditor 208 | shortcut for accessing the offsets is Ctrl + G. 209 | 210 | The convenience scripts will download a portion of the relation using psql in 211 | the event of not finding relation files at the expected location on the 212 | filesystem. For the fallback to work, `contrib/pageinspect` must be installed 213 | and tagging must start at the beginning of the relation file (see `hexedit.cfg`). 214 | 215 | The convenience scripts automate away starting pg_hexedit in test environments, 216 | but it is still highly recommended that you familiarize yourself with 217 | PostgreSQL's file layout. See: [PostgreSQL documentation - Database File 218 | Layout](https://www.postgresql.org/docs/current/static/storage-file-layout.html). 219 | 220 | ### Requirements 221 | 222 | `psql` should be within your $PATH when the scripts are invoked. libpq 223 | environment variables like $PGDATABASE can be set within the `hexedit.cfg` 224 | file. These control what database is opened by wxHexEditor, and other such 225 | standard details. Note that just like pg_filedump, pg_hexeditor has no 226 | dependency on a running server, and is generally safer to use offline, despite 227 | the fact that it is typically used online. It is convenient to invoke 228 | wxHexEditor using the scripts provided during analysis of *in situ* issues, or 229 | when learning about PostgreSQL internals. 230 | 231 | Having a PostgreSQL relfilenode file open in a hex editor __risks data 232 | corruption__, especially when the PostgreSQL server is actually running 233 | throughout. The scripts were designed with backend development convenience in 234 | mind, where __the database should only contain disposable test data__. 235 | 236 | Convenience script assumptions: 237 | 238 | * The scripts initially assume that they're run as an OS user that has the 239 | operating system level permissions needed to open/read all PostgreSQL 240 | relation files, using the same absolute paths as PostgreSQL. Be very careful 241 | if the Postgres data directory is containerized; a convenience script might 242 | open relation files from an unrelated installation if this assumption is not 243 | fully met. 244 | 245 | * Most convenience scripts rely on `CREATE EXTENSION IF NOT EXISTS pageinspect` 246 | running and making available various SQL-callable functions. These functions 247 | are used to generate interesting offsets, or to display hints on index 248 | structure. (This is highly recommended, but not actually required.) 249 | 250 | * `psql` must connect using a PostgreSQL role with superuser permissions. This 251 | is needed to determine the path of Postgres relfiles (and to install 252 | `contrib/pageinspect`). 253 | 254 | [`contrib/pageinspect`](https://www.postgresql.org/docs/current/static/pageinspect.html) 255 | must be available (the extensions supporting files must be installed) to use 256 | the convenience scripts that depend on `contrib/pageinspect`. Note that the 257 | relation_hexedit script does not depend on `contrib/pageinspect`. 258 | relation_hexedit is designed to work equally well with relations of any access 259 | method, and uses simple convenience offsets (decile offsets). 260 | 261 | To open the Postgres table `pg_type` with tags and annotations: 262 | 263 | ```shell 264 | # Should be invoked with CWD that finds pg_hexedit executable: 265 | $ pwd 266 | /home/pg/code/pg_hexedit 267 | # Confirm configuration: 268 | $ $EDITOR hexedit.cfg 269 | # Invoke generic script (works on tables and indexes): 270 | $ ./relation_hexedit pg_type 271 | Replacing /home/pg/code/pg_hexedit/.wxHexEditor with pg_hexedit optimized settings... 272 | ... 273 | ``` 274 | 275 | To open the Postgres B-Tree `pg_type_typname_nsp_index` with tags and annotations: 276 | 277 | ```shell 278 | $ ./btree_hexedit pg_type_typname_nsp_index 279 | Replacing /home/pg/code/pg_hexedit/.wxHexEditor with pg_hexedit optimized settings... 280 | ... 281 | ``` 282 | 283 | The advantage of using the btree_hexedit script for B-Tree indexes over the 284 | generic relation_hexedit script is that btree_hexedit sets offsets for every 285 | non-leaf block that is a direct child of the root page (and for the root page 286 | itself). 287 | 288 | There is also a gin_hexedit convenience script. This does not set offsets 289 | automatically. Instead, it runs an SQL query that summarizes contiguous ranges 290 | within the index based on block type (this is output to stdout). Byte-wise 291 | offsets are output, which may be manually input using the offsets dialog. GIN 292 | indexes are often made up of a fairly small number of contiguous ranges of a 293 | single page type (e.g., 'data', 'leaf'), so a high level summary can help when 294 | locating the section that is of interest. Note that there might be 295 | fragmentation (many distinct, smaller contiguous ranges) in uncommon cases. 296 | 297 | The scripts will only open the first 1GB segment file in the relation. Note 298 | also that these convenience scripts limit the range of blocks that are 299 | summarized, to keep the overhead acceptable. (This can be changed by modifying 300 | hexedit.cfg.) 301 | 302 | If there is concurrent write activity by Postgres, the process of building XML 303 | tags may error out before finishing. In practice there is 304 | unlikely to be trouble. The scripts perform a `CHECKPOINT` before opening 305 | relation files. 306 | 307 | ### Getting acceptable performance 308 | 309 | While wxHexEditor compares favorably with other hex editors when tasked with 310 | editing very large files, it appears to be far more likely to become 311 | unresponsive when there are many tags. It may be necessary to work around this 312 | limitation at some point. 313 | 314 | Generalize from the example of the convenience scripts for guidance on this. 315 | Limiting the range that is summarized can be very effective in simple cases. 316 | 317 | pg_hexedit's `-x` flag can be used to specify a page LSN before which pages 318 | should not have tags emitted. This is another option to reduce the overhead of 319 | tags within wxHexEditor by avoiding generating tags for non-interesting 320 | blocks/pages in the first place. It can be useful during debugging to specify 321 | an LSN that is only a few checkpoints old, to limit annotations to recently 322 | modified blocks. This advanced option isn't used by the convenience scripts. 323 | [pg_waldump](https://www.postgresql.org/docs/current/static/pgwaldump.html) may 324 | be used to find a relevant cutoff point's LSN value (e.g., based on a commit 325 | WAL record's timestamp value). 326 | 327 | pg_hexedit's `-l` flag can be used when the target is an nbtree, GiST, GIN, or 328 | SP-GiST index relation. This will have pg_hexedit emit all-green, single-page 329 | tags for leaf pages. Leaf pages can be much less interesting than internal 330 | pages in some debugging scenarios. For example, when the balance of a tree 331 | structure must be examined, only the internal pages are relevant. This is yet 332 | another option for limiting the number of tags generated to control overhead 333 | within wxHexEditor. This advanced option isn't currently used by the 334 | convenience scripts. 335 | 336 | ## Direct invocation 337 | 338 | Invoking pg_hexedit directly (not using convenience scripts) is useful when you 339 | want to work on a copy of the database that is not under the control of a 340 | running PostgreSQL server, or when a psql connection to the running PostgreSQL 341 | server cannot be established. pg_hexedit should have stdout redirected to a 342 | file. wxHexEditor will automatically open tags for a target file when it is 343 | opened and a tag file is found in the same directory (provided the tag file has 344 | the same name as the target file with a ".tags" extension postfixed). 345 | Alternatively, tags can be directly imported once wxHexEditor has opened a 346 | file. 347 | 348 | pg_hexedit retains a minority of the flags that appear in pg_filedump. Usage: 349 | 350 | ```shell 351 | pg_hexedit [options] file 352 | ``` 353 | 354 | The `-D` flag can be used to decode tuples. The flag should be followed by a 355 | tuple descriptor string in pg_hexedit's "attrlist" format. Decoding allows 356 | pg_hexedit to generate distinct tags for each user attribute/column value in 357 | each tuple, rather than just creating a single tag for all column data within 358 | each tuple. The attrlist format consists of a list with an entry for each 359 | pg_attribute entry's attlen, attname, and attalign, which should be specified 360 | as: 361 | 362 | `-D 'attlen,attname,attalign,attlen,attname,attalign,...'` 363 | 364 | Each attribute's triple of metadata should appear in pg_attribute.attnum order. 365 | The convenience scripts use an SQL query to form the string (see the 366 | `__open_relation` utility script). The attrlist SQL query will produce a 367 | correct `-D` argument when run against a relation with the same schema as the 368 | target relation/file, even when run against an unrelated PostgreSQL 369 | installation, provided a compatible CPU architecture is used. Note that 370 | dropped columns need to be represented in the attrlist string. Elements that 371 | contain whitespace or comma characters can be parsed as a single element by 372 | appearing within double quotes. It's good practice to use single quotes for 373 | the attrlist argument as a whole. 374 | 375 | See `pg_hexedit -h` for full details of all available options. 376 | 377 | ### Using pg_hexedit while debugging Postgres with GDB 378 | 379 | Sometimes, it is useful to invoke pg_hexedit/wxHexEditor to visualize an 380 | arbitrary Postgres page image from within an interactive GDB debugging session 381 | that is attached to a Postgres backend. This also works well with core dumps. 382 | 383 | Generalize from the following example, which can be added to your .gdbinit 384 | dotfile: 385 | 386 | ``` 387 | define dump_page 388 | dump binary memory /tmp/gdb_postgres_page.dump $arg0 ($arg0 + 8192) 389 | echo Invoking pg_hexedit + wxHexEditor on page...\n 390 | ! ~/code/pg_hexedit/pg_hexedit -n 1 /tmp/gdb_postgres_page.dump > /tmp/gdb_postgres_page.dump.tags 391 | ! ~/code/wxHexEditor/wxHexEditor /tmp/gdb_postgres_page.dump &> /dev/null 392 | end 393 | ``` 394 | 395 | This creates a user-defined GDB command that dumps 8192 bytes (default BLCKSZ) 396 | at an arbitrary memory address to a temp file. From there, pg_hexedit is 397 | invoked on the temp file. 398 | 399 | If we wanted to see a page image of an nbtree page that is about to be split, 400 | we can set a breakpoint within `_bt_split()`, insert tuples until there is a 401 | page split, and invoke pg_hexedit using the `dump_page` GDB command: 402 | 403 | ``` 404 | Breakpoint 1, _bt_split (rel=0x7f555b6f3460, itup_key=0x55d03a745d40, buf=232, cbuf=0, firstright=366, newitemoff=216, newitemsz=16, newitem=0x55d03a745d18, newitemonleft=true) at nbtinsert.c:1205 405 | 1205 { 406 | (gdb) n 407 | 1215 Buffer sbuf = InvalidBuffer; 408 | (gdb) 409 | 1216 Page spage = NULL; 410 | (gdb) 411 | 1217 BTPageOpaque sopaque = NULL; 412 | (gdb) 413 | 1227 int indnatts = IndexRelationGetNumberOfAttributes(rel); 414 | (gdb) 415 | 1228 int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel); 416 | (gdb) 417 | 1231 rbuf = _bt_getbuf(rel, P_NEW, BT_WRITE); 418 | (gdb) 419 | 1244 origpage = BufferGetPage(buf); 420 | (gdb) 421 | 1245 leftpage = PageGetTempPage(origpage); 422 | (gdb) 423 | 1246 rightpage = BufferGetPage(rbuf); 424 | (gdb) 425 | 1248 origpagenumber = BufferGetBlockNumber(buf); 426 | (gdb) 427 | 1249 rightpagenumber = BufferGetBlockNumber(rbuf); 428 | (gdb) dump_page origpage 429 | Invoking pg_hexedit + wxHexEditor on page... 430 | 431 | ``` 432 | 433 | Note that the block number displayed by pg_hexedit's annotations will be 434 | spurious when produced by `dump_page`, because there is no general way to 435 | determine the correct block number without more context. This is usually only 436 | a minor annoyance, though. 437 | 438 | ### Determining catalog relation file mappings without a database connection 439 | 440 | pg_filenodemapdata is a program that prints the contents of a specified catalog 441 | relation to relfile mapping (pg_filenode.map) file. It is distributed with 442 | pg_hexedit. pg_filenodemapdata can be used to determine which system catalog 443 | relfiles to examine in cases where a database connection cannot be established 444 | due to severe system catalog corruption. It's usually easier to figure this 445 | out using the generic SQL-based approach that the convenience scripts take, but 446 | that isn't always possible. 447 | 448 | To print the mappings for the database with pg_database OID 12389 (and 449 | verify the pg_filenode.map checksum in passing): 450 | 451 | ```shell 452 | $ cd $PGDATA 453 | $ pg_filenodemapdata base/12389/pg_filenode.map 454 | magic: 0x00592717 455 | num_mappings: 15 456 | 457 | 0) 1259 - pg_class: 1259 458 | 1) 1249 - pg_attribute: 1249 459 | 2) 1255 - pg_proc: 1255 460 | 3) 1247 - pg_type: 1247 461 | 4) 2836 - pg_toast_1255: 2836 462 | 5) 2837 - pg_toast_1255_index: 2837 463 | 6) 2658 - pg_attribute_relid_attnam_index: 2658 464 | 7) 2659 - pg_attribute_relid_attnum_index: 2659 465 | 8) 2662 - pg_class_oid_index: 2662 466 | 9) 2663 - pg_class_relname_nsp_index: 2663 467 | 10) 3455 - pg_class_tblspc_relfilenode_index: 3455 468 | 11) 2690 - pg_proc_oid_index: 2690 469 | 12) 2691 - pg_proc_proname_args_nsp_index: 2691 470 | 13) 2703 - pg_type_oid_index: 2703 471 | 14) 2704 - pg_type_typname_nsp_index: 2704 472 | 473 | file checksum: 0x3AA59965 474 | ``` 475 | 476 | In this example, all mapped system catalog relations within the database have 477 | relfilenode numbers that match their universal, fixed pg_class OID identifiers. 478 | This is often not the case, though. Operations like VACUUM FULL will assign a 479 | new relfilenode to the target table relation, and to all associated index 480 | relations. 481 | 482 | Installations with many small databases may require an additional step. It may 483 | be unclear which subdirectory of the base directory corresponds to a database 484 | that happens to be of interest. The relevant metadata is stored in the 485 | pg_database *shared* system catalog: the names of base subdirectories 486 | correspond to a pg_database entry OID. The location of the relfile for the 487 | global pg_database table might need to be determined first, so that a 488 | particular base directory can be identified: 489 | 490 | ```shell 491 | $ pg_filenodemapdata global/pg_filenode.map | grep pg_database 492 | 0) 1262 - pg_database: 1262 493 | 21) 2671 - pg_database_datname_index: 2671 494 | 22) 2672 - pg_database_oid_index: 2672 495 | ``` 496 | 497 | The file global/1262 can now be opened using wxHexEditor, to examine the 498 | contents of pg_database. pg_database tuples contain a database name, so it 499 | should be possible to search for the entry of interest using the wxHexEditor 500 | search dialog. The OID for each tuple/database is a 4 byte unsigned integer 501 | that appears in heap tuple headers with `HEAP_HASOID` set. 502 | 503 | ## Supporting other hex editors 504 | 505 | While pg_hexedit targets wxHexEditor, it should not be difficult to adopt it to 506 | other hex editors with a similar tag import feature if that becomes a 507 | requirement in the future. (Alternative hex editors should ideally be able to 508 | represent tag color using HTML color codes, and support setting both font and 509 | tag colors.) 510 | 511 | Actually generating raw tag output is confined to the following simple C 512 | functions: 513 | 514 | ``` 515 | EmitXmlDocHeader() 516 | EmitXmlFooter() 517 | EmitXmlTag() 518 | EmitXmlItemId() 519 | EmitXmlTupleTag() 520 | EmitXmlTupleTagFont() 521 | EmitXmlTupleTagFontTwoName() 522 | ``` 523 | 524 | These routines could be changed to call a per-hexeditor callback. Each 525 | supported hex editor could have its own "provider" routines. 526 | -------------------------------------------------------------------------------- /__open_relation: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script contains code that is common to all convenience scripts. 4 | # 5 | # Utility scripts mostly consist of some logic for exporting wxHexEditor-format 6 | # offset cache entries to the OFFSETS environment variable, and/or logic for 7 | # printing something to stdout that's useful to the user. Everything else 8 | # happens here. 9 | # 10 | # Most convenience scripts set the $OFFSETS env var to something useful for us. 11 | # Utility scripts must have run "source ./hexedit.cfg" for us. 12 | 13 | relname=$1 14 | 15 | # Spoof home director, to avoid clobbering real /.wxHexEditor file 16 | HOME=$(pwd) 17 | wxconfig="$HOME/.wxHexEditor" 18 | 19 | if ! psql --no-psqlrc -c "CHECKPOINT" 20 | then 21 | echo "CHECKPOINT failed" 22 | exit 1 23 | fi 24 | 25 | if ! RFN=$(psql --no-psqlrc -tA -c "SELECT pg_relation_filepath('$relname')") 26 | then 27 | echo "invoking pg_relation_filepath() failed" 28 | exit 1 29 | fi 30 | 31 | if [ "$RFN" = "" ] 32 | then 33 | echo "relation $relname has no associated relfile" 34 | exit 1 35 | fi 36 | 37 | # Put minimal .wxHexEditor registry style config file in place, so old tags are 38 | # forgotten. This is also where we generate convenience "Go to Offset" dialog 39 | # offsets in the registry/cache (this comes from OFFSETS env var). 40 | echo "Replacing $wxconfig with pg_hexedit optimized settings..." 41 | cat > "$wxconfig" <<- EOM 42 | UpdateCheck=0 43 | FakeBlockLines=1 44 | FakeBlockSize=8k 45 | ColourHexBackground=#FFFFFF 46 | ColourHexBackgroundZebra=#FFFFFF 47 | UseBytesPerLineLimit=1 48 | BytesPerLineLimit=$BYTES_PER_LINE_LIMIT 49 | FontSize=$FONTSIZE 50 | CharacterEncodingFamily=Code for Information Interchange 51 | CharacterEncoding=ASCII - American Standard Code for Information Interchange 52 | ScreenFullScreen=1 53 | AutoShowTagPanel=0 54 | GoToOptions=7 55 | $OFFSETS 56 | EOM 57 | 58 | PGDATA=$(psql --no-psqlrc -tA -c "SELECT setting FROM pg_settings WHERE name = 'data_directory'") 59 | echo "Determined that data directory is $PGDATA" 60 | 61 | # wxHexEditor XML encoding is always UTF-8. Make sure that attname fields come 62 | # back as UTF-8. 63 | export PGCLIENTENCODING="UTF8" 64 | if ! ATTRLIST=$(psql --no-psqlrc -tA -c "SELECT string_agg(attlen || ',\"' || attname || '\",' || attalign::text, ',') 65 | FROM 66 | (SELECT * FROM pg_attribute WHERE attrelid = '$relname'::regclass AND attnum > 0 ORDER BY attnum ASC) t;") 67 | then 68 | echo "failed to generate attrlist string for -D option" 69 | exit 1 70 | fi 71 | 72 | # Check if data file exists locally, download $MAX_BLOCK_TAGS to /tmp if it doesn't 73 | FULLPATH="$PGDATA/$RFN" 74 | if [ -f "$FULLPATH" ]; then 75 | echo "Running pg_hexedit against $FULLPATH, the first segment in relation $relname..." 76 | echo "Note: Only blocks $MIN_BLOCK_TAGS - $MAX_BLOCK_TAGS will be annotated, to keep overhead low." 77 | else 78 | PAGEINSPECT_INSTALLED=$(psql -XAtc "select count(*) from pg_extension where extname='pageinspect'") 79 | if (( PAGEINSPECT_INSTALLED && MIN_BLOCK_TAGS == 0 )); then 80 | TMPDIR="/tmp/postgresql-relfiles/$PGHOST-$PGPORT-DB/$(dirname "$RFN")" 81 | mkdir -p "$TMPDIR" 82 | FULLPATH="$TMPDIR/$(basename "$RFN")" 83 | echo "Downloading blocks from $MIN_BLOCK_TAGS to $MAX_BLOCK_TAGS inclusive from relation $relname to $FULLPATH..." 84 | psql -XAtc "SELECT encode(get_raw_page('$relname', block::int4),'base64') 85 | FROM generate_series(0, least(pg_relation_size('$relname') / 8192 - 1, $MAX_BLOCK_TAGS)) 86 | block" | base64 -d > "$FULLPATH" 87 | echo "Opening local temp file..." 88 | else 89 | echo "File $FULLPATH doesn't exist." 90 | if (( MIN_BLOCK_TAGS == 0 )); then 91 | echo "Tip: Install pageinspect to automatically download remote files." 92 | else 93 | echo "Note: Cannot automatically download remote files when MIN_BLOCK_TAGS is non-zero." 94 | fi 95 | exit 1 96 | fi 97 | fi 98 | 99 | # Deliberately put space before pg_hexedit frontend debug output here: 100 | echo -e "pg_hexedit frontend utility debug/notice output:\n" 101 | 102 | # Generate tags at a path that we know wxHexEditor will look for them: 103 | # shellcheck disable=SC2086 104 | if ! ./pg_hexedit $EXTRAFLAGS -D "$ATTRLIST" -z -R "$MIN_BLOCK_TAGS" "$MAX_BLOCK_TAGS" "$FULLPATH" > "$FULLPATH.tags" 105 | then 106 | echo "Error encountered by pg_hexedit. Could not generate all tags." 107 | echo "You may still wish to run: $HEXEDITOR $FULLPATH" 108 | exit 1 109 | fi 110 | 111 | if [ ! -f "$HEXEDITOR" ] 112 | then 113 | echo "\"$HEXEDITOR\" executable not found" 114 | echo "\"$FULLPATH.tags\" annotation file was still created" 115 | exit 1 116 | fi 117 | 118 | # Deliberately put space after pg_hexedit frontend debug output here: 119 | echo 120 | 121 | echo "Opening $FULLPATH with $HEXEDITOR..." 122 | # Output from wxHexEditor is verbose, and not terribly useful, so we redirect: 123 | $HEXEDITOR "$FULLPATH" &> /dev/null 124 | -------------------------------------------------------------------------------- /btree_hexedit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # btree_hexedit: Similar to relation_hexedit, but sets the B-Tree non-leaf 4 | # children of the root as offsets, as well as an offset to the root itself. 5 | 6 | usage() { 7 | cat < 1)) 39 | ) 40 | SELECT string_agg(line, chr(10)) FROM offset_strings;") 41 | then 42 | echo "obtaining non-leaf children of root block for offset dialog failed" 43 | exit 1 44 | fi 45 | # The root page is also added as a separate offset. 46 | OFFSETS="${TRUE_ROOT_OFFSET}"$'\n'"${ROOT_CHILD_OFFSETS}" 47 | 48 | export OFFSETS 49 | echo "Tip: 'Go to Offset' dialog (shortcut: Ctrl + G) will have children of root and root offsets cached" 50 | ./__open_relation "$relname" 51 | -------------------------------------------------------------------------------- /gin_hexedit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # gin_hexedit: Don't set any offsets in wxHexEditor cache. Instead, output 4 | # a summary of interesting offsets for the GIN index to stdout for user. 5 | # 6 | # Unlike relation_hexedit, this script does not set any offsets in the 7 | # wxHexEditor cache/registry. 8 | 9 | usage() { 10 | cat < TAG Panel" to see it 53 | 54 | #export EXTRAFLAGS="-x 0/167F070" 55 | 56 | # -l flag in $EXTRAFLAGS: Only emit whole-page annotations for leaf pages. 57 | # 58 | # Leaf pages are typically the great majority within nbtree, GiST, GIN, or 59 | # SP-GiST index relations. This option is useful with larger indexes in cases 60 | # where only the root and other internal pages are of interest. 61 | 62 | #export EXTRAFLAGS="-l" 63 | -------------------------------------------------------------------------------- /pg_filenodemapdata.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pg_filenodemapdata.c - PostgreSQL utility 3 | * that prints the contents of pg_filenode.map files. 4 | * 5 | * Copyright (c) 2018-2021, Crunchy Data Solutions, Inc. 6 | * Copyright (c) 2018, VMware, Inc. 7 | * Copyright (c) 2018-2021, PostgreSQL Global Development Group 8 | * 9 | * This is a standalone utlity for displaying the mappings within either a 10 | * global or per-database pg_filenode.map. It is heavily based on the native 11 | * PostgreSQL logic for reading pg_filenode.map files, which can be found 12 | * within cache/relfilenodemap.c 13 | * 14 | * This program is free software; you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation; either version 2 of the License, or 17 | * (at your option) any later version. 18 | * 19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with this program; if not, write to the Free Software 26 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 27 | * 28 | * pg_filenodemapdata author: Peter Geoghegan 29 | */ 30 | #include "postgres.h" 31 | 32 | #include "catalog/indexing.h" 33 | #include "catalog/pg_auth_members.h" 34 | #include "catalog/pg_authid.h" 35 | #include "catalog/pg_class.h" 36 | #include "catalog/pg_database.h" 37 | #include "catalog/pg_db_role_setting.h" 38 | #if PG_VERSION_NUM < 130000 39 | #include "catalog/pg_pltemplate.h" 40 | #endif 41 | #include "catalog/pg_proc.h" 42 | #include "catalog/pg_replication_origin.h" 43 | #include "catalog/pg_shdepend.h" 44 | #include "catalog/pg_shdescription.h" 45 | #include "catalog/pg_shseclabel.h" 46 | #include "catalog/pg_subscription.h" 47 | #include "catalog/pg_tablespace.h" 48 | #include "catalog/pg_type.h" 49 | #include "catalog/toasting.h" 50 | #include "port/pg_crc32c.h" 51 | 52 | #define HEXEDIT_VERSION "0.1" 53 | 54 | /* Provide system catalog OIDs for old unsupported versions */ 55 | #define PgAuthidToastTable 4175 56 | #define PgAuthidToastIndex 4176 57 | #define PgDatabaseToastTable 4177 58 | #define PgDatabaseToastIndex 4178 59 | #define PgPlTemplateToastTable 4179 60 | #define PgPlTemplateToastIndex 4180 61 | #define PgReplicationOriginToastTable 4181 62 | #define PgReplicationOriginToastIndex 4182 63 | #define PgSubscriptionToastTable 4183 64 | #define PgSubscriptionToastIndex 4184 65 | #define PgTablespaceToastTable 4185 66 | #define PgTablespaceToastIndex 4186 67 | 68 | /* 69 | * Postgres 13 commit 50fc694e removed pl_template.h 70 | */ 71 | #if PG_VERSION_NUM >= 130000 72 | #define PLTemplateRelationId 1136 73 | #define PgPlTemplateToastTable 4179 74 | #define PgPlTemplateToastIndex 4180 75 | #define PLTemplateNameIndexId 1137 76 | #endif 77 | 78 | /* 79 | * catalog/toasting.h doesn't bother to provide constants for these two pg_proc 80 | * TOAST relations, presumably because no core code needs to reference the 81 | * toast tables. We invent our own constants, to be consistent. 82 | * 83 | * XXX: This list isn't comprehensive. 84 | */ 85 | #define OID_PG_TOAST_1255 2836 86 | #define OID_PG_TOAST_1255_INDEX 2837 87 | 88 | /* For calculating display padding */ 89 | #define CATALOG_NAME_COLS 45 90 | #define ENTRY_NUM_COLS 2 91 | 92 | /* These constants appear at the top of PostgreSQL's relmapper.c */ 93 | #define RELMAPPER_FILEMAGIC 0x592717 /* version ID value */ 94 | #define MAX_MAPPINGS 62 /* 62 * 8 + 16 = 512 */ 95 | 96 | /* Struct definitions from the top of PostgreSQL's relmapper.c */ 97 | typedef struct RelMapping 98 | { 99 | Oid mapoid; /* OID of a catalog */ 100 | Oid mapfilenode; /* its filenode number */ 101 | } RelMapping; 102 | 103 | typedef struct RelMapFile 104 | { 105 | int32 magic; /* always RELMAPPER_FILEMAGIC */ 106 | int32 num_mappings; /* number of valid RelMapping entries */ 107 | RelMapping mappings[MAX_MAPPINGS]; 108 | pg_crc32c crc; /* CRC of all above */ 109 | int32 pad; /* to make the struct size be 512 exactly */ 110 | } RelMapFile; 111 | 112 | /* Possible return codes from option validation routine */ 113 | typedef enum optionReturnCodes 114 | { 115 | OPT_RC_VALID, /* All options are valid */ 116 | OPT_RC_INVALID, /* Improper option string */ 117 | OPT_RC_COPYRIGHT /* Copyright should be displayed */ 118 | } optionReturnCodes; 119 | 120 | /* Program exit code */ 121 | static int exitCode = 0; 122 | 123 | static void DisplayOptions(unsigned int validOptions); 124 | static unsigned int ConsumeOptions(int numOptions, char **options); 125 | static const char *GetCatalogNameFromOid(Oid classOid); 126 | static void PrintRelMapContents(RelMapFile *map); 127 | static void VerifyRelMapContents(RelMapFile *map); 128 | static bool InitRelMapFromFile(char *mapFileName, RelMapFile *map); 129 | 130 | /* 131 | * Send properly formed usage information to the user 132 | */ 133 | static void 134 | DisplayOptions(unsigned int validOptions) 135 | { 136 | if (validOptions == OPT_RC_COPYRIGHT) 137 | printf 138 | ("pg_filenodemapdata %s (for PostgreSQL %s)\n" 139 | "Copyright (c) 2018-2021, Crunchy Data Solutions, Inc.\n" 140 | "Copyright (c) 2018, VMware, Inc.\n" 141 | "Copyright (c) 2018-2021, PostgreSQL Global Development Group\n", 142 | HEXEDIT_VERSION, PG_VERSION); 143 | printf 144 | ("\nUsage: pg_filenodemapdata file\n\n" 145 | "Displays details from a PostgreSQL pg_filenode.map file\n" 146 | "\nReport bugs to \n"); 147 | } 148 | 149 | /* 150 | * Iterate through the provided options and set the option flags. An error 151 | * will result in a positive rc and will force a display of the usage 152 | * information. This routine returns enum option ReturnCode values. 153 | */ 154 | static unsigned int 155 | ConsumeOptions(int numOptions, char **options) 156 | { 157 | unsigned int rc = OPT_RC_VALID; 158 | 159 | /* For now, only accept a single file argument, without flags */ 160 | if (numOptions != 2) 161 | rc = OPT_RC_INVALID; 162 | 163 | return rc; 164 | } 165 | 166 | /* 167 | * Get the name of a known system catalog from its pg_class OID 168 | */ 169 | static const char * 170 | GetCatalogNameFromOid(Oid classOid) 171 | { 172 | switch (classOid) 173 | { 174 | /* Local/nailed mappings */ 175 | case RelationRelationId: 176 | return "pg_class"; 177 | case AttributeRelationId: 178 | return "pg_attribute"; 179 | case ProcedureRelationId: 180 | return "pg_proc"; 181 | case TypeRelationId: 182 | return "pg_type"; 183 | case OID_PG_TOAST_1255: 184 | return "pg_toast_1255"; 185 | case OID_PG_TOAST_1255_INDEX: 186 | return "pg_toast_1255_index"; 187 | case AttributeRelidNameIndexId: 188 | return "pg_attribute_relid_attnam_index"; 189 | case AttributeRelidNumIndexId: 190 | return "pg_attribute_relid_attnum_index"; 191 | case ClassOidIndexId: 192 | return "pg_class_oid_index"; 193 | case ClassNameNspIndexId: 194 | return "pg_class_relname_nsp_index"; 195 | case ClassTblspcRelfilenodeIndexId: 196 | return "pg_class_tblspc_relfilenode_index"; 197 | case ProcedureOidIndexId: 198 | return "pg_proc_oid_index"; 199 | case ProcedureNameArgsNspIndexId: 200 | return "pg_proc_proname_args_nsp_index"; 201 | case TypeOidIndexId: 202 | return "pg_type_oid_index"; 203 | case TypeNameNspIndexId: 204 | return "pg_type_typname_nsp_index"; 205 | 206 | /* Global/shared mappings */ 207 | case DatabaseRelationId: 208 | return "pg_database"; 209 | case DbRoleSettingRelationId: 210 | return "pg_db_role_setting"; 211 | case TableSpaceRelationId: 212 | return "pg_tablespace"; 213 | case PLTemplateRelationId: 214 | return "pg_pltemplate"; 215 | case AuthIdRelationId: 216 | return "pg_authid"; 217 | case AuthMemRelationId: 218 | return "pg_auth_members"; 219 | case SharedDependRelationId: 220 | return "pg_shdepend"; 221 | case ReplicationOriginRelationId: 222 | return "pg_replication_origin"; 223 | case SharedDescriptionRelationId: 224 | return "pg_shdescription"; 225 | case SharedSecLabelRelationId: 226 | return "pg_shseclabel"; 227 | case SubscriptionRelationId: 228 | return "pg_subscription"; 229 | case PgAuthidToastTable: 230 | return "pg_toast_4175"; 231 | case PgAuthidToastIndex: 232 | return "pg_toast_4176_index"; 233 | case PgDatabaseToastTable: 234 | return "pg_toast_4177"; 235 | case PgDatabaseToastIndex: 236 | return "pg_toast_4178_index"; 237 | case PgDbRoleSettingToastTable: 238 | return "pg_toast_2964"; 239 | case PgDbRoleSettingToastIndex: 240 | return "pg_toast_2964_index"; 241 | case PgPlTemplateToastTable: 242 | return "pg_toast_4179"; 243 | case PgPlTemplateToastIndex: 244 | return "pg_toast_4180_index"; 245 | case PgReplicationOriginToastTable: 246 | return "pg_toast_4181"; 247 | case PgReplicationOriginToastIndex: 248 | return "pg_toast_4182_index"; 249 | case PgShdescriptionToastTable: 250 | return "pg_toast_2396"; 251 | case PgShdescriptionToastIndex: 252 | return "pg_toast_2396_index"; 253 | case PgShseclabelToastTable: 254 | return "pg_toast_3592"; 255 | case PgShseclabelToastIndex: 256 | return "pg_toast_3592_index"; 257 | case PgSubscriptionToastTable: 258 | return "pg_toast_4183"; 259 | case PgSubscriptionToastIndex: 260 | return "pg_toast_4184_index"; 261 | case PgTablespaceToastTable: 262 | return "pg_toast_4185"; 263 | case PgTablespaceToastIndex: 264 | return "pg_toast_4186_index"; 265 | case AuthIdRolnameIndexId: 266 | return "pg_authid_rolname_index"; 267 | case AuthIdOidIndexId: 268 | return "pg_authid_oid_index"; 269 | case AuthMemRoleMemIndexId: 270 | return "pg_auth_members_role_member_index"; 271 | case AuthMemMemRoleIndexId: 272 | return "pg_auth_members_member_role_index"; 273 | case DatabaseNameIndexId: 274 | return "pg_database_datname_index"; 275 | case DatabaseOidIndexId: 276 | return "pg_database_oid_index"; 277 | case SharedDescriptionObjIndexId: 278 | return "pg_shdescription_o_c_index"; 279 | case PLTemplateNameIndexId: 280 | return "pg_pltemplate_name_index"; 281 | case SharedDependDependerIndexId: 282 | return "pg_shdepend_depender_index"; 283 | case SharedDependReferenceIndexId: 284 | return "pg_shdepend_reference_index"; 285 | case TablespaceOidIndexId: 286 | return "pg_tablespace_oid_index"; 287 | case TablespaceNameIndexId: 288 | return "pg_tablespace_spcname_index"; 289 | case DbRoleSettingDatidRolidIndexId: 290 | return "pg_db_role_setting_databaseid_rol_index"; 291 | case SharedSecLabelObjectIndexId: 292 | return "pg_shseclabel_object_index"; 293 | case ReplicationOriginIdentIndex: 294 | return "pg_replication_origin_roiident_index"; 295 | case ReplicationOriginNameIndex: 296 | return "pg_replication_origin_roname_index"; 297 | case SubscriptionObjectIndexId: 298 | return "pg_subscription_oid_index"; 299 | case SubscriptionNameIndexId: 300 | return "pg_subscription_subname_index"; 301 | 302 | /* 303 | * We expect to be able to identify every mapped catalog on 304 | * supported versions. If we haven't got a record of this 305 | * catalog's OID, our assumption is that that's because it's from 306 | * a future PostgreSQL version. 307 | */ 308 | default: 309 | return "unlisted system catalog relation"; 310 | } 311 | } 312 | 313 | static void 314 | PrintRelMapContents(RelMapFile *map) 315 | { 316 | int32 num_mappings = Min(MAX_MAPPINGS, map->num_mappings); 317 | int i; 318 | 319 | /* Print pg_filenode.map file's header */ 320 | printf("magic: 0x%.8X\n" 321 | "num_mappings: %d\n\n", map->magic, map->num_mappings); 322 | 323 | /* Print mappings from file */ 324 | for (i = 0; i < num_mappings; i++) 325 | { 326 | Oid reloid = map->mappings[i].mapoid; 327 | Oid relfilenode = map->mappings[i].mapfilenode; 328 | const char *catalogname; 329 | 330 | catalogname = GetCatalogNameFromOid(reloid); 331 | 332 | printf("%*d) %u - %s: %*u\n", ENTRY_NUM_COLS, i, reloid, catalogname, 333 | CATALOG_NAME_COLS - (int) strlen(catalogname), relfilenode); 334 | } 335 | 336 | /* Print CRC-32C checksum at the end of the file */ 337 | printf("\nfile checksum: 0x%.8X\n", map->crc); 338 | } 339 | 340 | /* 341 | * Verify the consistency of a pg_relfilenode.map file. 342 | * 343 | * Checks that magic number matches, and verifies CRC. 344 | */ 345 | static void 346 | VerifyRelMapContents(RelMapFile *map) 347 | { 348 | pg_crc32c crc; 349 | 350 | if (map->magic != RELMAPPER_FILEMAGIC || 351 | map->num_mappings < 0 || 352 | map->num_mappings > MAX_MAPPINGS) 353 | { 354 | fprintf(stderr, "relation mapping file contains invalid data\n"); 355 | exitCode = 1; 356 | } 357 | 358 | /* Print our own CRC-32/CRC-32C calculation */ 359 | INIT_CRC32C(crc); 360 | COMP_CRC32C(crc, (char *) map, offsetof(RelMapFile, crc)); 361 | FIN_CRC32C(crc); 362 | 363 | /* Raise error if they don't match */ 364 | if (!EQ_CRC32C(crc, map->crc)) 365 | { 366 | fprintf(stderr, "calculated checksum 0x%.8X does not match file checksum\n", 367 | crc); 368 | exitCode = 1; 369 | } 370 | } 371 | 372 | /* 373 | * Given a path to a pg_relfilenode.map file, initialized caller's RelMapFile 374 | * structure. Returns true on success. 375 | */ 376 | static bool 377 | InitRelMapFromFile(char *mapFileName, RelMapFile *map) 378 | { 379 | FILE *file = fopen(mapFileName, "rb"); 380 | 381 | if (!file) 382 | { 383 | fprintf(stderr, "could not open file \"%s\": %s\n", mapFileName, 384 | strerror(errno)); 385 | exitCode = 1; 386 | return false; 387 | } 388 | 389 | if (fread(map, 1, sizeof(RelMapFile), file) != sizeof(RelMapFile)) 390 | { 391 | fprintf(stderr, "could not read relation mapping file \"%s\": %s\n", 392 | mapFileName, strerror(errno)); 393 | exitCode = 1; 394 | fclose(file); 395 | return false; 396 | } 397 | 398 | fclose(file); 399 | 400 | return true; 401 | } 402 | 403 | int 404 | main(int argv, char **argc) 405 | { 406 | unsigned int validOptions; 407 | RelMapFile map; 408 | 409 | /* If there is a parameter list, validate the options */ 410 | validOptions = (argv < 2) ? OPT_RC_COPYRIGHT : ConsumeOptions(argv, argc); 411 | 412 | /* 413 | * Display valid options if no parameters are received or invalid options 414 | * where encountered 415 | */ 416 | if (validOptions != OPT_RC_VALID) 417 | DisplayOptions(validOptions); 418 | else if (InitRelMapFromFile(argc[1], &map)) 419 | { 420 | /* Print file header, contents, and footer */ 421 | PrintRelMapContents(&map); 422 | 423 | /* Verify consistency of file last, so that errors appear last */ 424 | VerifyRelMapContents(&map); 425 | } 426 | 427 | exit(exitCode); 428 | } 429 | -------------------------------------------------------------------------------- /pg_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petergeoghegan/pg_hexedit/002c81583e9afc187a5fcbb8965b09bbd66d0b47/pg_type.png -------------------------------------------------------------------------------- /pg_type_typname_nsp_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petergeoghegan/pg_hexedit/002c81583e9afc187a5fcbb8965b09bbd66d0b47/pg_type_typname_nsp_index.png -------------------------------------------------------------------------------- /relation_hexedit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # relation_hexedit: Sets generic decile offsets in wxHexEditor cache/registry, 4 | # before opening relation. Unlike the other convenience scripts, this works 5 | # with any kind of relation, and has no dependency on contrib/pageinspect. 6 | 7 | usage() { 8 | cat < 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /t/expected_leaf_idx.tags: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 0 11 | 8191 12 | block 131072 (level 0) leaf page 13 | #313739 14 | #16A085 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /t/test_pg_hexedit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Simple pg_hexedit smoke tests. These are intended to be run by "make check", 4 | # with the root directory as current working directory. 5 | 6 | # The 1249 input file comes from the first block of pg_attribute after initdb. 7 | # This just tests the heap annotation functions, since the heapam hasn't really 8 | # changed across Postgres versions supported by pg_hexedit. 9 | 10 | # Generate tags with no attributes, using -x option equal to page's LSN (this 11 | # -x option does not affect output): 12 | set -x 13 | ./pg_hexedit -x "0/00000028" t/1249 > t/output_no_attributes.tags || exit 1 14 | set +x 15 | 16 | # Normalize: 17 | sed -i '2s/.*//' t/output_no_attributes.tags 18 | sed -i '6s/.*//' t/output_no_attributes.tags 19 | diff t/expected_no_attributes.tags t/output_no_attributes.tags > t/no_attributes.diff 20 | error=$? 21 | if [ $error -ne 0 ] 22 | then 23 | echo "Failed to generate correct pg_attribute tag file": 24 | cat t/no_attributes.diff 25 | exit 1 26 | fi 27 | 28 | # Generate tags with attributes: 29 | ATTRLIST='4,"attrelid",i,64,"attname",c,4,"atttypid",i,4,"attstattarget",i,2,"attlen",s,2,"attnum",s,4,"attndims",i,4,"attcacheoff",i,4,"atttypmod",i,1,"attbyval",c,1,"attstorage",c,1,"attalign",c,1,"attnotnull",c,1,"atthasdef",c,1,"atthasmissing",c,1,"attidentity",c,1,"attisdropped",c,1,"attislocal",c,4,"attinhcount",i,4,"attcollation",i,-1,"attacl",i,-1,"attoptions",i,-1,"attfdwoptions",i,-1,"attmissingval",d' 30 | set -x 31 | ./pg_hexedit -D "$ATTRLIST" t/1249 > t/output_attributes.tags || exit 1 32 | set +x 33 | 34 | # Normalize: 35 | sed -i '2s/.*//' t/output_attributes.tags 36 | sed -i '6s/.*//' t/output_attributes.tags 37 | diff t/expected_attributes.tags t/output_attributes.tags > t/attributes.diff 38 | error=$? 39 | if [ $error -ne 0 ] 40 | then 41 | echo "Failed to generate correct pg_attribute tag file (with attributes)": 42 | cat t/attributes.diff 43 | exit 1 44 | fi 45 | 46 | # Generate almost-empty tag file by avoiding page LSN: 47 | set -x 48 | ./pg_hexedit -x "0/00000029" t/1249 > t/output_empty_lsn.tags || exit 1 49 | set +x 50 | 51 | # Normalize: 52 | sed -i '2s/.*//' t/output_empty_lsn.tags 53 | sed -i '6s/.*//' t/output_empty_lsn.tags 54 | diff t/expected_empty_lsn.tags t/output_empty_lsn.tags > t/empty_lsn.diff 55 | error=$? 56 | if [ $error -ne 0 ] 57 | then 58 | echo "Failed to generate correct pg_attribute tag file (-x test)": 59 | cat t/empty_lsn.diff 60 | exit 1 61 | fi 62 | 63 | # The 2685 input file comes from the first non-metapage block of 64 | # pg_attribute_relid_attnam_index after initdb. This block doesn't have any 65 | # explicitly truncated attributes on PostgreSQL 11, allowing it to work on all 66 | # supported PostgreSQL versions without special consideration. 67 | 68 | # Generate tags with no attributes, forcing the segment number to 1 to avoid 69 | # interpreting this page as a metapage: 70 | set -x 71 | ./pg_hexedit -n 1 t/2685 > t/output_no_attributes_idx.tags || exit 1 72 | set +x 73 | 74 | # Normalize: 75 | sed -i '2s/.*//' t/output_no_attributes_idx.tags 76 | sed -i '6s/.*//' t/output_no_attributes_idx.tags 77 | diff t/expected_no_attributes_idx.tags t/output_no_attributes_idx.tags > t/no_attributes_idx.diff 78 | error=$? 79 | if [ $error -ne 0 ] 80 | then 81 | echo "Failed to generate correct pg_attribute_relid_attnam_index tag file": 82 | cat t/no_attributes_idx.diff 83 | exit 1 84 | fi 85 | 86 | # Generate tags with attributes, forcing the segment number to 1: 87 | ATTRLIST='4,"attrelid",i,-2,"attname",c' 88 | set -x 89 | ./pg_hexedit -n 1 -D "$ATTRLIST" t/2685 > t/output_attributes_idx.tags || exit 1 90 | set +x 91 | 92 | # Normalize: 93 | sed -i '2s/.*//' t/output_attributes_idx.tags 94 | sed -i '6s/.*//' t/output_attributes_idx.tags 95 | diff t/expected_attributes_idx.tags t/output_attributes_idx.tags > t/attributes_idx.diff 96 | error=$? 97 | if [ $error -ne 0 ] 98 | then 99 | echo "Failed to generate correct pg_attribute_relid_attnam_index tag file (with attributes)": 100 | cat t/attributes_idx.diff 101 | exit 1 102 | fi 103 | 104 | # Generate single leaf page tag with -l option, forcing the segment number to 1 105 | # to avoid interpreting this page as a metapage: 106 | set -x 107 | ./pg_hexedit -n 1 -l t/2685 > t/output_leaf_idx.tags || exit 1 108 | set +x 109 | 110 | # Normalize: 111 | sed -i '2s/.*//' t/output_leaf_idx.tags 112 | sed -i '6s/.*//' t/output_leaf_idx.tags 113 | diff t/expected_leaf_idx.tags t/output_leaf_idx.tags > t/leaf_idx.diff 114 | error=$? 115 | if [ $error -ne 0 ] 116 | then 117 | echo "Failed to generate correct pg_attribute_relid_attnam_index tag file (with -l option)": 118 | cat t/leaf_idx.diff 119 | exit 1 120 | fi 121 | 122 | echo -e "\nAll tests pass\n" 123 | echo -e "Tip: the file t/1249 can be opened within wxHexEditor. 124 | Import tags from either \"output_no_attributes.tags\" or \"output_attributes.tags\" or \"output_empty_lsn.tags\".\n" 125 | echo -e "Tip: the file t/2685 can be opened within wxHexEditor. 126 | Import tags from either \"output_no_attributes_idx.tags\" or \"output_attributes_idx.tags\" or \"output_leaf_idx.tags\".\n" 127 | --------------------------------------------------------------------------------