| /*------------------------------------------------------------------------- |
| * |
| * bbstreamer_inject.c |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * src/bin/pg_basebackup/bbstreamer_inject.c |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres_fe.h" |
| |
| #include "bbstreamer.h" |
| #include "common/file_perm.h" |
| #include "common/logging.h" |
| |
| typedef struct bbstreamer_recovery_injector |
| { |
| bbstreamer base; |
| bool skip_file; |
| bool is_recovery_guc_supported; |
| bool is_postgresql_auto_conf; |
| bool found_postgresql_auto_conf; |
| PQExpBuffer recoveryconfcontents; |
| bbstreamer_member member; |
| } bbstreamer_recovery_injector; |
| |
| static void bbstreamer_recovery_injector_content(bbstreamer *streamer, |
| bbstreamer_member *member, |
| const char *data, int len, |
| bbstreamer_archive_context context); |
| static void bbstreamer_recovery_injector_finalize(bbstreamer *streamer); |
| static void bbstreamer_recovery_injector_free(bbstreamer *streamer); |
| |
| const bbstreamer_ops bbstreamer_recovery_injector_ops = { |
| .content = bbstreamer_recovery_injector_content, |
| .finalize = bbstreamer_recovery_injector_finalize, |
| .free = bbstreamer_recovery_injector_free |
| }; |
| |
| /* |
| * Create a bbstreamer that can edit recoverydata into an archive stream. |
| * |
| * The input should be a series of typed chunks (not BBSTREAMER_UNKNOWN) as |
| * per the conventions described in bbstreamer.h; the chunks forwarded to |
| * the next bbstreamer will be similarly typed, but the |
| * BBSTREAMER_MEMBER_HEADER chunks may be zero-length in cases where we've |
| * edited the archive stream. |
| * |
| * Our goal is to do one of the following three things with the content passed |
| * via recoveryconfcontents: (1) if is_recovery_guc_supported is false, then |
| * put the content into recovery.conf, replacing any existing archive member |
| * by that name; (2) if is_recovery_guc_supported is true and |
| * postgresql.auto.conf exists in the archive, then append the content |
| * provided to the existing file; and (3) if is_recovery_guc_supported is |
| * true but postgresql.auto.conf does not exist in the archive, then create |
| * it with the specified content. |
| * |
| * In addition, if is_recovery_guc_supported is true, then we create a |
| * zero-length standby.signal file, dropping any file with that name from |
| * the archive. |
| */ |
| extern bbstreamer * |
| bbstreamer_recovery_injector_new(bbstreamer *next, |
| bool is_recovery_guc_supported, |
| PQExpBuffer recoveryconfcontents) |
| { |
| bbstreamer_recovery_injector *streamer; |
| |
| streamer = palloc0(sizeof(bbstreamer_recovery_injector)); |
| *((const bbstreamer_ops **) &streamer->base.bbs_ops) = |
| &bbstreamer_recovery_injector_ops; |
| streamer->base.bbs_next = next; |
| streamer->is_recovery_guc_supported = is_recovery_guc_supported; |
| streamer->recoveryconfcontents = recoveryconfcontents; |
| |
| return &streamer->base; |
| } |
| |
| /* |
| * Handle each chunk of tar content while injecting recovery configuration. |
| */ |
| static void |
| bbstreamer_recovery_injector_content(bbstreamer *streamer, |
| bbstreamer_member *member, |
| const char *data, int len, |
| bbstreamer_archive_context context) |
| { |
| bbstreamer_recovery_injector *mystreamer; |
| |
| mystreamer = (bbstreamer_recovery_injector *) streamer; |
| Assert(member != NULL || context == BBSTREAMER_ARCHIVE_TRAILER); |
| |
| switch (context) |
| { |
| case BBSTREAMER_MEMBER_HEADER: |
| /* Must copy provided data so we have the option to modify it. */ |
| memcpy(&mystreamer->member, member, sizeof(bbstreamer_member)); |
| |
| /* |
| * On v12+, skip standby.signal and edit postgresql.auto.conf; on |
| * older versions, skip recovery.conf. |
| */ |
| if (mystreamer->is_recovery_guc_supported) |
| { |
| mystreamer->skip_file = |
| (strcmp(member->pathname, "standby.signal") == 0); |
| mystreamer->is_postgresql_auto_conf = |
| (strcmp(member->pathname, "postgresql.auto.conf") == 0); |
| if (mystreamer->is_postgresql_auto_conf) |
| { |
| /* Remember we saw it so we don't add it again. */ |
| mystreamer->found_postgresql_auto_conf = true; |
| |
| /* Increment length by data to be injected. */ |
| mystreamer->member.size += |
| mystreamer->recoveryconfcontents->len; |
| |
| /* |
| * Zap data and len because the archive header is no |
| * longer valid; some subsequent bbstreamer must |
| * regenerate it if it's necessary. |
| */ |
| data = NULL; |
| len = 0; |
| } |
| } |
| else |
| mystreamer->skip_file = |
| (strcmp(member->pathname, "recovery.conf") == 0); |
| |
| /* Do not forward if the file is to be skipped. */ |
| if (mystreamer->skip_file) |
| return; |
| break; |
| |
| case BBSTREAMER_MEMBER_CONTENTS: |
| /* Do not forward if the file is to be skipped. */ |
| if (mystreamer->skip_file) |
| return; |
| break; |
| |
| case BBSTREAMER_MEMBER_TRAILER: |
| /* Do not forward it the file is to be skipped. */ |
| if (mystreamer->skip_file) |
| return; |
| |
| /* Append provided content to whatever we already sent. */ |
| if (mystreamer->is_postgresql_auto_conf) |
| bbstreamer_content(mystreamer->base.bbs_next, member, |
| mystreamer->recoveryconfcontents->data, |
| mystreamer->recoveryconfcontents->len, |
| BBSTREAMER_MEMBER_CONTENTS); |
| break; |
| |
| case BBSTREAMER_ARCHIVE_TRAILER: |
| if (mystreamer->is_recovery_guc_supported) |
| { |
| /* |
| * If we didn't already find (and thus modify) |
| * postgresql.auto.conf, inject it as an additional archive |
| * member now. |
| */ |
| if (!mystreamer->found_postgresql_auto_conf) |
| bbstreamer_inject_file(mystreamer->base.bbs_next, |
| "postgresql.auto.conf", |
| mystreamer->recoveryconfcontents->data, |
| mystreamer->recoveryconfcontents->len); |
| |
| /* Inject empty standby.signal file. */ |
| bbstreamer_inject_file(mystreamer->base.bbs_next, |
| "standby.signal", "", 0); |
| } |
| else |
| { |
| /* Inject recovery.conf file with specified contents. */ |
| bbstreamer_inject_file(mystreamer->base.bbs_next, |
| "recovery.conf", |
| mystreamer->recoveryconfcontents->data, |
| mystreamer->recoveryconfcontents->len); |
| } |
| |
| /* Nothing to do here. */ |
| break; |
| |
| default: |
| /* Shouldn't happen. */ |
| pg_fatal("unexpected state while injecting recovery settings"); |
| } |
| |
| bbstreamer_content(mystreamer->base.bbs_next, &mystreamer->member, |
| data, len, context); |
| } |
| |
| /* |
| * End-of-stream processing for this bbstreamer. |
| */ |
| static void |
| bbstreamer_recovery_injector_finalize(bbstreamer *streamer) |
| { |
| bbstreamer_finalize(streamer->bbs_next); |
| } |
| |
| /* |
| * Free memory associated with this bbstreamer. |
| */ |
| static void |
| bbstreamer_recovery_injector_free(bbstreamer *streamer) |
| { |
| bbstreamer_free(streamer->bbs_next); |
| pfree(streamer); |
| } |
| |
| /* |
| * Inject a member into the archive with specified contents. |
| */ |
| void |
| bbstreamer_inject_file(bbstreamer *streamer, char *pathname, char *data, |
| int len) |
| { |
| bbstreamer_member member; |
| |
| strlcpy(member.pathname, pathname, MAXPGPATH); |
| member.size = len; |
| member.mode = pg_file_create_mode; |
| member.is_directory = false; |
| member.is_link = false; |
| member.linktarget[0] = '\0'; |
| |
| /* |
| * There seems to be no principled argument for these values, but they are |
| * what PostgreSQL has historically used. |
| */ |
| member.uid = 04000; |
| member.gid = 02000; |
| |
| /* |
| * We don't know here how to generate valid member headers and trailers |
| * for the archiving format in use, so if those are needed, some successor |
| * bbstreamer will have to generate them using the data from 'member'. |
| */ |
| bbstreamer_content(streamer, &member, NULL, 0, |
| BBSTREAMER_MEMBER_HEADER); |
| bbstreamer_content(streamer, &member, data, len, |
| BBSTREAMER_MEMBER_CONTENTS); |
| bbstreamer_content(streamer, &member, NULL, 0, |
| BBSTREAMER_MEMBER_TRAILER); |
| } |