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