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