/*------------------------------------------------------------------------- * * shell_archive.c * * This archiving function uses a user-specified shell command (the * archive_command GUC) to copy write-ahead log files. It is used as the * default, but other modules may define their own custom archiving logic. * * Copyright (c) 2022, PostgreSQL Global Development Group * * IDENTIFICATION * src/backend/postmaster/shell_archive.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "access/xlog.h" #include "pgstat.h" #include "postmaster/pgarch.h" static bool shell_archive_configured(void); static bool shell_archive_file(const char *file, const char *path); static void shell_archive_shutdown(void); void shell_archive_init(ArchiveModuleCallbacks *cb) { AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit); cb->check_configured_cb = shell_archive_configured; cb->archive_file_cb = shell_archive_file; cb->shutdown_cb = shell_archive_shutdown; } static bool shell_archive_configured(void) { return XLogArchiveCommand[0] != '\0'; } static bool shell_archive_file(const char *file, const char *path) { char xlogarchcmd[MAXPGPATH]; char *dp; char *endp; const char *sp; int rc; /* * construct the command to be executed */ dp = xlogarchcmd; endp = xlogarchcmd + MAXPGPATH - 1; *endp = '\0'; for (sp = XLogArchiveCommand; *sp; sp++) { if (*sp == '%') { switch (sp[1]) { case 'p': /* %p: relative path of source file */ sp++; strlcpy(dp, path, endp - dp); make_native_path(dp); dp += strlen(dp); break; case 'f': /* %f: filename of source file */ sp++; strlcpy(dp, file, endp - dp); dp += strlen(dp); break; case '%': /* convert %% to a single % */ sp++; if (dp < endp) *dp++ = *sp; break; default: /* otherwise treat the % as not special */ if (dp < endp) *dp++ = *sp; break; } } else { if (dp < endp) *dp++ = *sp; } } *dp = '\0'; ereport(DEBUG3, (errmsg_internal("executing archive command \"%s\"", xlogarchcmd))); pgstat_report_wait_start(WAIT_EVENT_ARCHIVE_COMMAND); rc = system(xlogarchcmd); pgstat_report_wait_end(); if (rc != 0) { /* * If either the shell itself, or a called command, died on a signal, * abort the archiver. We do this because system() ignores SIGINT and * SIGQUIT while waiting; so a signal is very likely something that * should have interrupted us too. Also die if the shell got a hard * "command not found" type of error. If we overreact it's no big * deal, the postmaster will just start the archiver again. */ int lev = wait_result_is_any_signal(rc, true) ? FATAL : LOG; if (WIFEXITED(rc)) { ereport(lev, (errmsg("archive command failed with exit code %d", WEXITSTATUS(rc)), errdetail("The failed archive command was: %s", xlogarchcmd))); } else if (WIFSIGNALED(rc)) { #if defined(WIN32) ereport(lev, (errmsg("archive command was terminated by exception 0x%X", WTERMSIG(rc)), errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."), errdetail("The failed archive command was: %s", xlogarchcmd))); #else ereport(lev, (errmsg("archive command was terminated by signal %d: %s", WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))), errdetail("The failed archive command was: %s", xlogarchcmd))); #endif } else { ereport(lev, (errmsg("archive command exited with unrecognized status %d", rc), errdetail("The failed archive command was: %s", xlogarchcmd))); } return false; } elog(DEBUG1, "archived write-ahead log file \"%s\"", file); return true; } static void shell_archive_shutdown(void) { elog(DEBUG1, "archiver process shutting down"); }