LCOV - code coverage report
Current view: top level - src/bin/pg_upgrade - exec.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 80.2 % 116 93
Test Date: 2026-07-16 11:17:59 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 48.1 % 54 26

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

Generated by: LCOV version 2.0-1