LCOV - code coverage report
Current view: top level - src/bin/pg_upgrade - exec.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 97 124 78.2 %
Date: 2025-11-19 07:17:45 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  *  exec.c
       3             :  *
       4             :  *  execution functions
       5             :  *
       6             :  *  Copyright (c) 2010-2025, PostgreSQL Global Development Group
       7             :  *  src/bin/pg_upgrade/exec.c
       8             :  */
       9             : 
      10             : #include "postgres_fe.h"
      11             : 
      12             : #include <fcntl.h>
      13             : 
      14             : #include "common/string.h"
      15             : #include "fe_utils/version.h"
      16             : #include "pg_upgrade.h"
      17             : 
      18             : static void check_data_dir(ClusterInfo *cluster);
      19             : static void check_bin_dir(ClusterInfo *cluster, bool check_versions);
      20             : static void get_bin_version(ClusterInfo *cluster);
      21             : static void check_exec(const char *dir, const char *program, bool check_version);
      22             : 
      23             : #ifdef WIN32
      24             : static int  win32_check_directory_write_permissions(void);
      25             : #endif
      26             : 
      27             : 
      28             : /*
      29             :  * get_bin_version
      30             :  *
      31             :  *  Fetch major version of binaries for cluster.
      32             :  */
      33             : static void
      34          68 : get_bin_version(ClusterInfo *cluster)
      35             : {
      36             :     char        cmd[MAXPGPATH],
      37             :                 cmd_output[MAX_STRING];
      38             :     FILE       *output;
      39             :     int         rc;
      40          68 :     int         v1 = 0,
      41          68 :                 v2 = 0;
      42             : 
      43          68 :     snprintf(cmd, sizeof(cmd), "\"%s/pg_ctl\" --version", cluster->bindir);
      44          68 :     fflush(NULL);
      45             : 
      46         136 :     if ((output = popen(cmd, "r")) == NULL ||
      47          68 :         fgets(cmd_output, sizeof(cmd_output), output) == NULL)
      48           0 :         pg_fatal("could not get pg_ctl version data using %s: %m", cmd);
      49             : 
      50          68 :     rc = pclose(output);
      51          68 :     if (rc != 0)
      52           0 :         pg_fatal("could not get pg_ctl version data using %s: %s",
      53             :                  cmd, wait_result_to_str(rc));
      54             : 
      55          68 :     if (sscanf(cmd_output, "%*s %*s %d.%d", &v1, &v2) < 1)
      56           0 :         pg_fatal("could not get pg_ctl version output from %s", cmd);
      57             : 
      58          68 :     if (v1 < 10)
      59             :     {
      60             :         /* old style, e.g. 9.6.1 */
      61           0 :         cluster->bin_version = v1 * 10000 + v2 * 100;
      62             :     }
      63             :     else
      64             :     {
      65             :         /* new style, e.g. 10.1 */
      66          68 :         cluster->bin_version = v1 * 10000;
      67             :     }
      68          68 : }
      69             : 
      70             : 
      71             : /*
      72             :  * exec_prog()
      73             :  *      Execute an external program with stdout/stderr redirected, and report
      74             :  *      errors
      75             :  *
      76             :  * Formats a command from the given argument list, logs it to the log file,
      77             :  * and attempts to execute that command.  If the command executes
      78             :  * successfully, exec_prog() returns true.
      79             :  *
      80             :  * If the command fails, an error message is optionally written to the specified
      81             :  * log_file, and the program optionally exits.
      82             :  *
      83             :  * The code requires it be called first from the primary thread on Windows.
      84             :  */
      85             : bool
      86         542 : exec_prog(const char *log_filename, const char *opt_log_file,
      87             :           bool report_error, bool exit_on_error, const char *fmt,...)
      88             : {
      89         542 :     int         result = 0;
      90             :     int         written;
      91             :     char        log_file[MAXPGPATH];
      92             : 
      93             : #define MAXCMDLEN (2 * MAXPGPATH)
      94             :     char        cmd[MAXCMDLEN];
      95             :     FILE       *log;
      96             :     va_list     ap;
      97             : 
      98             : #ifdef WIN32
      99             :     static DWORD mainThreadId = 0;
     100             : 
     101             :     /* We assume we are called from the primary thread first */
     102             :     if (mainThreadId == 0)
     103             :         mainThreadId = GetCurrentThreadId();
     104             : #endif
     105             : 
     106         542 :     snprintf(log_file, MAXPGPATH, "%s/%s", log_opts.logdir, log_filename);
     107             : 
     108         542 :     written = 0;
     109         542 :     va_start(ap, fmt);
     110         542 :     written += vsnprintf(cmd + written, MAXCMDLEN - written, fmt, ap);
     111         542 :     va_end(ap);
     112         542 :     if (written >= MAXCMDLEN)
     113           0 :         pg_fatal("command too long");
     114         542 :     written += snprintf(cmd + written, MAXCMDLEN - written,
     115             :                         " >> \"%s\" 2>&1", log_file);
     116         542 :     if (written >= MAXCMDLEN)
     117           0 :         pg_fatal("command too long");
     118             : 
     119         542 :     pg_log(PG_VERBOSE, "%s", cmd);
     120             : 
     121             : #ifdef WIN32
     122             : 
     123             :     /*
     124             :      * For some reason, Windows issues a file-in-use error if we write data to
     125             :      * the log file from a non-primary thread just before we create a
     126             :      * subprocess that also writes to the same log file.  One fix is to sleep
     127             :      * for 100ms.  A cleaner fix is to write to the log file _after_ the
     128             :      * subprocess has completed, so we do this only when writing from a
     129             :      * non-primary thread.  fflush(), running system() twice, and pre-creating
     130             :      * the file do not see to help.
     131             :      */
     132             :     if (mainThreadId != GetCurrentThreadId())
     133             :     {
     134             :         fflush(NULL);
     135             :         result = system(cmd);
     136             :     }
     137             : #endif
     138             : 
     139         542 :     log = fopen(log_file, "a");
     140             : 
     141             : #ifdef WIN32
     142             :     {
     143             :         /*
     144             :          * "pg_ctl -w stop" might have reported that the server has stopped
     145             :          * because the postmaster.pid file has been removed, but "pg_ctl -w
     146             :          * start" might still be in the process of closing and might still be
     147             :          * holding its stdout and -l log file descriptors open.  Therefore,
     148             :          * try to open the log file a few more times.
     149             :          */
     150             :         int         iter;
     151             : 
     152             :         for (iter = 0; iter < 4 && log == NULL; iter++)
     153             :         {
     154             :             pg_usleep(1000000); /* 1 sec */
     155             :             log = fopen(log_file, "a");
     156             :         }
     157             :     }
     158             : #endif
     159             : 
     160         542 :     if (log == NULL)
     161           0 :         pg_fatal("could not open log file \"%s\": %m", log_file);
     162             : 
     163             : #ifdef WIN32
     164             :     /* Are we printing "command:" before its output? */
     165             :     if (mainThreadId == GetCurrentThreadId())
     166             :         fprintf(log, "\n\n");
     167             : #endif
     168         542 :     fprintf(log, "command: %s\n", cmd);
     169             : #ifdef WIN32
     170             :     /* Are we printing "command:" after its output? */
     171             :     if (mainThreadId != GetCurrentThreadId())
     172             :         fprintf(log, "\n\n");
     173             : #endif
     174             : 
     175             :     /*
     176             :      * In Windows, we must close the log file at this point so the file is not
     177             :      * open while the command is running, or we get a share violation.
     178             :      */
     179         542 :     fclose(log);
     180             : 
     181             : #ifdef WIN32
     182             :     /* see comment above */
     183             :     if (mainThreadId == GetCurrentThreadId())
     184             : #endif
     185             :     {
     186         542 :         fflush(NULL);
     187         542 :         result = system(cmd);
     188             :     }
     189             : 
     190         542 :     if (result != 0 && report_error)
     191             :     {
     192             :         /* we might be in on a progress status line, so go to the next line */
     193           0 :         report_status(PG_REPORT, "\n*failure*");
     194           0 :         fflush(stdout);
     195             : 
     196           0 :         pg_log(PG_VERBOSE, "There were problems executing \"%s\"", cmd);
     197           0 :         if (opt_log_file)
     198           0 :             pg_log(exit_on_error ? PG_FATAL : PG_REPORT,
     199             :                    "Consult the last few lines of \"%s\" or \"%s\" for\n"
     200             :                    "the probable cause of the failure.",
     201             :                    log_file, opt_log_file);
     202             :         else
     203           0 :             pg_log(exit_on_error ? PG_FATAL : PG_REPORT,
     204             :                    "Consult the last few lines of \"%s\" for\n"
     205             :                    "the probable cause of the failure.",
     206             :                    log_file);
     207             :     }
     208             : 
     209             : #ifndef WIN32
     210             : 
     211             :     /*
     212             :      * We can't do this on Windows because it will keep the "pg_ctl start"
     213             :      * output filename open until the server stops, so we do the \n\n above on
     214             :      * that platform.  We use a unique filename for "pg_ctl start" that is
     215             :      * never reused while the server is running, so it works fine.  We could
     216             :      * log these commands to a third file, but that just adds complexity.
     217             :      */
     218         542 :     if ((log = fopen(log_file, "a")) == NULL)
     219           0 :         pg_fatal("could not write to log file \"%s\": %m", log_file);
     220         542 :     fprintf(log, "\n\n");
     221         542 :     fclose(log);
     222             : #endif
     223             : 
     224         542 :     return result == 0;
     225             : }
     226             : 
     227             : 
     228             : /*
     229             :  * pid_lock_file_exists()
     230             :  *
     231             :  * Checks whether the postmaster.pid file exists.
     232             :  */
     233             : bool
     234          68 : pid_lock_file_exists(const char *datadir)
     235             : {
     236             :     char        path[MAXPGPATH];
     237             :     int         fd;
     238             : 
     239          68 :     snprintf(path, sizeof(path), "%s/postmaster.pid", datadir);
     240             : 
     241          68 :     if ((fd = open(path, O_RDONLY, 0)) < 0)
     242             :     {
     243             :         /* ENOTDIR means we will throw a more useful error later */
     244          68 :         if (errno != ENOENT && errno != ENOTDIR)
     245           0 :             pg_fatal("could not open file \"%s\" for reading: %m", path);
     246             : 
     247          68 :         return false;
     248             :     }
     249             : 
     250           0 :     close(fd);
     251           0 :     return true;
     252             : }
     253             : 
     254             : 
     255             : /*
     256             :  * verify_directories()
     257             :  *
     258             :  * does all the hectic work of verifying directories and executables
     259             :  * of old and new server.
     260             :  *
     261             :  * NOTE: May update the values of all parameters
     262             :  */
     263             : void
     264          36 : verify_directories(void)
     265             : {
     266             : #ifndef WIN32
     267          36 :     if (access(".", R_OK | W_OK | X_OK) != 0)
     268             : #else
     269             :     if (win32_check_directory_write_permissions() != 0)
     270             : #endif
     271           0 :         pg_fatal("You must have read and write access in the current directory.");
     272             : 
     273          36 :     check_bin_dir(&old_cluster, false);
     274          34 :     check_data_dir(&old_cluster);
     275          34 :     check_bin_dir(&new_cluster, true);
     276          34 :     check_data_dir(&new_cluster);
     277          34 : }
     278             : 
     279             : 
     280             : #ifdef WIN32
     281             : /*
     282             :  * win32_check_directory_write_permissions()
     283             :  *
     284             :  *  access() on WIN32 can't check directory permissions, so we have to
     285             :  *  optionally create, then delete a file to check.
     286             :  *      http://msdn.microsoft.com/en-us/library/1w06ktdy%28v=vs.80%29.aspx
     287             :  */
     288             : static int
     289             : win32_check_directory_write_permissions(void)
     290             : {
     291             :     int         fd;
     292             : 
     293             :     /*
     294             :      * We open a file we would normally create anyway.  We do this even in
     295             :      * 'check' mode, which isn't ideal, but this is the best we can do.
     296             :      */
     297             :     if ((fd = open(GLOBALS_DUMP_FILE, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0)
     298             :         return -1;
     299             :     close(fd);
     300             : 
     301             :     return unlink(GLOBALS_DUMP_FILE);
     302             : }
     303             : #endif
     304             : 
     305             : 
     306             : /*
     307             :  * check_single_dir()
     308             :  *
     309             :  *  Check for the presence of a single directory in PGDATA, and fail if
     310             :  * is it missing or not accessible.
     311             :  */
     312             : static void
     313         612 : check_single_dir(const char *pg_data, const char *subdir)
     314             : {
     315             :     struct stat statBuf;
     316             :     char        subDirName[MAXPGPATH];
     317             : 
     318         612 :     snprintf(subDirName, sizeof(subDirName), "%s%s%s", pg_data,
     319             :     /* Win32 can't stat() a directory with a trailing slash. */
     320         612 :              *subdir ? "/" : "",
     321             :              subdir);
     322             : 
     323         612 :     if (stat(subDirName, &statBuf) != 0)
     324           0 :         report_status(PG_FATAL, "check for \"%s\" failed: %m",
     325             :                       subDirName);
     326         612 :     else if (!S_ISDIR(statBuf.st_mode))
     327           0 :         report_status(PG_FATAL, "\"%s\" is not a directory",
     328             :                       subDirName);
     329         612 : }
     330             : 
     331             : 
     332             : /*
     333             :  * check_data_dir()
     334             :  *
     335             :  *  This function validates the given cluster directory - we search for a
     336             :  *  small set of subdirectories that we expect to find in a valid $PGDATA
     337             :  *  directory.  If any of the subdirectories are missing (or secured against
     338             :  *  us) we display an error message and exit()
     339             :  *
     340             :  */
     341             : static void
     342          68 : check_data_dir(ClusterInfo *cluster)
     343             : {
     344          68 :     const char *pg_data = cluster->pgdata;
     345             : 
     346             :     /* get the cluster version */
     347          68 :     cluster->major_version = get_pg_version(cluster->pgdata,
     348             :                                             &cluster->major_version_str);
     349          68 :     check_single_dir(pg_data, "");
     350          68 :     check_single_dir(pg_data, "base");
     351          68 :     check_single_dir(pg_data, "global");
     352          68 :     check_single_dir(pg_data, "pg_multixact");
     353          68 :     check_single_dir(pg_data, "pg_subtrans");
     354          68 :     check_single_dir(pg_data, PG_TBLSPC_DIR);
     355          68 :     check_single_dir(pg_data, "pg_twophase");
     356             : 
     357             :     /* pg_xlog has been renamed to pg_wal in v10 */
     358          68 :     if (GET_MAJOR_VERSION(cluster->major_version) <= 906)
     359           0 :         check_single_dir(pg_data, "pg_xlog");
     360             :     else
     361          68 :         check_single_dir(pg_data, "pg_wal");
     362             : 
     363             :     /* pg_clog has been renamed to pg_xact in v10 */
     364          68 :     if (GET_MAJOR_VERSION(cluster->major_version) <= 906)
     365           0 :         check_single_dir(pg_data, "pg_clog");
     366             :     else
     367          68 :         check_single_dir(pg_data, "pg_xact");
     368          68 : }
     369             : 
     370             : 
     371             : /*
     372             :  * check_bin_dir()
     373             :  *
     374             :  *  This function searches for the executables that we expect to find
     375             :  *  in the binaries directory.  If we find that a required executable
     376             :  *  is missing (or secured against us), we display an error message and
     377             :  *  exit().
     378             :  *
     379             :  *  If check_versions is true, then the versions of the binaries are checked
     380             :  *  against the version of this pg_upgrade.  This is for checking the target
     381             :  *  bindir.
     382             :  */
     383             : static void
     384          70 : check_bin_dir(ClusterInfo *cluster, bool check_versions)
     385             : {
     386             :     struct stat statBuf;
     387             : 
     388             :     /* check bindir */
     389          70 :     if (stat(cluster->bindir, &statBuf) != 0)
     390           2 :         report_status(PG_FATAL, "check for \"%s\" failed: %m",
     391             :                       cluster->bindir);
     392          68 :     else if (!S_ISDIR(statBuf.st_mode))
     393           0 :         report_status(PG_FATAL, "\"%s\" is not a directory",
     394             :                       cluster->bindir);
     395             : 
     396          68 :     check_exec(cluster->bindir, "postgres", check_versions);
     397          68 :     check_exec(cluster->bindir, "pg_controldata", check_versions);
     398          68 :     check_exec(cluster->bindir, "pg_ctl", check_versions);
     399             : 
     400             :     /*
     401             :      * Fetch the binary version after checking for the existence of pg_ctl.
     402             :      * This way we report a useful error if the pg_ctl binary used for version
     403             :      * fetching is missing/broken.
     404             :      */
     405          68 :     get_bin_version(cluster);
     406             : 
     407             :     /* pg_resetxlog has been renamed to pg_resetwal in version 10 */
     408          68 :     if (GET_MAJOR_VERSION(cluster->bin_version) <= 906)
     409           0 :         check_exec(cluster->bindir, "pg_resetxlog", check_versions);
     410             :     else
     411          68 :         check_exec(cluster->bindir, "pg_resetwal", check_versions);
     412             : 
     413          68 :     if (cluster == &new_cluster)
     414             :     {
     415             :         /*
     416             :          * These binaries are only needed for the target version. pg_dump and
     417             :          * pg_dumpall are used to dump the old cluster, but must be of the
     418             :          * target version.
     419             :          */
     420          34 :         check_exec(cluster->bindir, "initdb", check_versions);
     421          34 :         check_exec(cluster->bindir, "pg_dump", check_versions);
     422          34 :         check_exec(cluster->bindir, "pg_dumpall", check_versions);
     423          34 :         check_exec(cluster->bindir, "pg_restore", check_versions);
     424          34 :         check_exec(cluster->bindir, "psql", check_versions);
     425          34 :         check_exec(cluster->bindir, "vacuumdb", check_versions);
     426             :     }
     427          68 : }
     428             : 
     429             : static void
     430         476 : check_exec(const char *dir, const char *program, bool check_version)
     431             : {
     432             :     char        path[MAXPGPATH];
     433             :     char       *line;
     434             :     char        cmd[MAXPGPATH];
     435             :     char        versionstr[128];
     436             : 
     437         476 :     snprintf(path, sizeof(path), "%s/%s", dir, program);
     438             : 
     439         476 :     if (validate_exec(path) != 0)
     440           0 :         pg_fatal("check for \"%s\" failed: %m", path);
     441             : 
     442         476 :     snprintf(cmd, sizeof(cmd), "\"%s\" -V", path);
     443             : 
     444         476 :     if ((line = pipe_read_line(cmd)) == NULL)
     445           0 :         pg_fatal("check for \"%s\" failed: cannot execute",
     446             :                  path);
     447             : 
     448         476 :     if (check_version)
     449             :     {
     450         340 :         pg_strip_crlf(line);
     451             : 
     452         340 :         snprintf(versionstr, sizeof(versionstr), "%s (PostgreSQL) " PG_VERSION, program);
     453             : 
     454         340 :         if (strcmp(line, versionstr) != 0)
     455           0 :             pg_fatal("check for \"%s\" failed: incorrect version: found \"%s\", expected \"%s\"",
     456             :                      path, line, versionstr);
     457             :     }
     458             : 
     459         476 :     pg_free(line);
     460         476 : }

Generated by: LCOV version 1.16