Line data Source code
1 : /*
2 : * option.c
3 : *
4 : * options functions
5 : *
6 : * Copyright (c) 2010-2025, PostgreSQL Global Development Group
7 : * src/bin/pg_upgrade/option.c
8 : */
9 :
10 : #include "postgres_fe.h"
11 :
12 : #ifdef WIN32
13 : #include <io.h>
14 : #endif
15 :
16 : #include "common/string.h"
17 : #include "fe_utils/option_utils.h"
18 : #include "getopt_long.h"
19 : #include "pg_upgrade.h"
20 : #include "utils/pidfile.h"
21 :
22 : static void usage(void);
23 : static void check_required_directory(char **dirpath,
24 : const char *envVarName, bool useCwd,
25 : const char *cmdLineOption, const char *description,
26 : bool missingOk);
27 : #define FIX_DEFAULT_READ_ONLY "-c default_transaction_read_only=false"
28 :
29 :
30 : UserOpts user_opts;
31 :
32 :
33 : /*
34 : * parseCommandLine()
35 : *
36 : * Parses the command line (argc, argv[]) and loads structures
37 : */
38 : void
39 40 : parseCommandLine(int argc, char *argv[])
40 : {
41 : static struct option long_options[] = {
42 : {"old-datadir", required_argument, NULL, 'd'},
43 : {"new-datadir", required_argument, NULL, 'D'},
44 : {"old-bindir", required_argument, NULL, 'b'},
45 : {"new-bindir", required_argument, NULL, 'B'},
46 : {"no-sync", no_argument, NULL, 'N'},
47 : {"old-options", required_argument, NULL, 'o'},
48 : {"new-options", required_argument, NULL, 'O'},
49 : {"old-port", required_argument, NULL, 'p'},
50 : {"new-port", required_argument, NULL, 'P'},
51 :
52 : {"username", required_argument, NULL, 'U'},
53 : {"check", no_argument, NULL, 'c'},
54 : {"link", no_argument, NULL, 'k'},
55 : {"retain", no_argument, NULL, 'r'},
56 : {"jobs", required_argument, NULL, 'j'},
57 : {"socketdir", required_argument, NULL, 's'},
58 : {"verbose", no_argument, NULL, 'v'},
59 : {"clone", no_argument, NULL, 1},
60 : {"copy", no_argument, NULL, 2},
61 : {"copy-file-range", no_argument, NULL, 3},
62 : {"sync-method", required_argument, NULL, 4},
63 : {"no-statistics", no_argument, NULL, 5},
64 : {"set-char-signedness", required_argument, NULL, 6},
65 : {"swap", no_argument, NULL, 7},
66 :
67 : {NULL, 0, NULL, 0}
68 : };
69 : int option; /* Command line option */
70 40 : int optindex = 0; /* used by getopt_long */
71 : int os_user_effective_id;
72 : DataDirSyncMethod unused;
73 :
74 40 : user_opts.do_sync = true;
75 40 : user_opts.transfer_mode = TRANSFER_MODE_COPY;
76 40 : user_opts.do_statistics = true;
77 40 : user_opts.char_signedness = -1;
78 :
79 40 : os_info.progname = get_progname(argv[0]);
80 :
81 : /* Process libpq env. variables; load values here for usage() output */
82 40 : old_cluster.port = getenv("PGPORTOLD") ? atoi(getenv("PGPORTOLD")) : DEF_PGUPORT;
83 40 : new_cluster.port = getenv("PGPORTNEW") ? atoi(getenv("PGPORTNEW")) : DEF_PGUPORT;
84 :
85 40 : os_user_effective_id = get_user_info(&os_info.user);
86 : /* we override just the database user name; we got the OS id above */
87 40 : if (getenv("PGUSER"))
88 : {
89 0 : pg_free(os_info.user);
90 : /* must save value, getenv()'s pointer is not stable */
91 0 : os_info.user = pg_strdup(getenv("PGUSER"));
92 : }
93 :
94 40 : if (argc > 1)
95 : {
96 40 : if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
97 : {
98 2 : usage();
99 2 : exit(0);
100 : }
101 38 : if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
102 : {
103 2 : puts("pg_upgrade (PostgreSQL) " PG_VERSION);
104 2 : exit(0);
105 : }
106 : }
107 :
108 : /* Allow help and version to be run as root, so do the test here. */
109 36 : if (os_user_effective_id == 0)
110 0 : pg_fatal("%s: cannot be run as root", os_info.progname);
111 :
112 354 : while ((option = getopt_long(argc, argv, "b:B:cd:D:j:kNo:O:p:P:rs:U:v",
113 : long_options, &optindex)) != -1)
114 : {
115 320 : switch (option)
116 : {
117 34 : case 'b':
118 34 : old_cluster.bindir = pg_strdup(optarg);
119 34 : break;
120 :
121 34 : case 'B':
122 34 : new_cluster.bindir = pg_strdup(optarg);
123 34 : break;
124 :
125 10 : case 'c':
126 10 : user_opts.check = true;
127 10 : break;
128 :
129 34 : case 'd':
130 34 : old_cluster.pgdata = pg_strdup(optarg);
131 34 : break;
132 :
133 34 : case 'D':
134 34 : new_cluster.pgdata = pg_strdup(optarg);
135 34 : break;
136 :
137 0 : case 'j':
138 0 : user_opts.jobs = atoi(optarg);
139 0 : break;
140 :
141 2 : case 'k':
142 2 : user_opts.transfer_mode = TRANSFER_MODE_LINK;
143 2 : break;
144 :
145 34 : case 'N':
146 34 : user_opts.do_sync = false;
147 34 : break;
148 :
149 0 : case 'o':
150 : /* append option? */
151 0 : if (!old_cluster.pgopts)
152 0 : old_cluster.pgopts = pg_strdup(optarg);
153 : else
154 : {
155 0 : char *old_pgopts = old_cluster.pgopts;
156 :
157 0 : old_cluster.pgopts = psprintf("%s %s", old_pgopts, optarg);
158 0 : free(old_pgopts);
159 : }
160 0 : break;
161 :
162 0 : case 'O':
163 : /* append option? */
164 0 : if (!new_cluster.pgopts)
165 0 : new_cluster.pgopts = pg_strdup(optarg);
166 : else
167 : {
168 0 : char *new_pgopts = new_cluster.pgopts;
169 :
170 0 : new_cluster.pgopts = psprintf("%s %s", new_pgopts, optarg);
171 0 : free(new_pgopts);
172 : }
173 0 : break;
174 :
175 34 : case 'p':
176 34 : if ((old_cluster.port = atoi(optarg)) <= 0)
177 0 : pg_fatal("invalid old port number");
178 34 : break;
179 :
180 34 : case 'P':
181 34 : if ((new_cluster.port = atoi(optarg)) <= 0)
182 0 : pg_fatal("invalid new port number");
183 34 : break;
184 :
185 0 : case 'r':
186 0 : log_opts.retain = true;
187 0 : break;
188 :
189 34 : case 's':
190 34 : user_opts.socketdir = pg_strdup(optarg);
191 34 : break;
192 :
193 0 : case 'U':
194 0 : pg_free(os_info.user);
195 0 : os_info.user = pg_strdup(optarg);
196 0 : os_info.user_specified = true;
197 0 : break;
198 :
199 0 : case 'v':
200 0 : log_opts.verbose = true;
201 0 : break;
202 :
203 2 : case 1:
204 2 : user_opts.transfer_mode = TRANSFER_MODE_CLONE;
205 2 : break;
206 :
207 26 : case 2:
208 26 : user_opts.transfer_mode = TRANSFER_MODE_COPY;
209 26 : break;
210 :
211 2 : case 3:
212 2 : user_opts.transfer_mode = TRANSFER_MODE_COPY_FILE_RANGE;
213 2 : break;
214 0 : case 4:
215 0 : if (!parse_sync_method(optarg, &unused))
216 0 : exit(1);
217 0 : user_opts.sync_method = pg_strdup(optarg);
218 0 : break;
219 :
220 0 : case 5:
221 0 : user_opts.do_statistics = false;
222 0 : break;
223 :
224 2 : case 6:
225 2 : if (pg_strcasecmp(optarg, "signed") == 0)
226 2 : user_opts.char_signedness = 1;
227 0 : else if (pg_strcasecmp(optarg, "unsigned") == 0)
228 0 : user_opts.char_signedness = 0;
229 : else
230 0 : pg_fatal("invalid argument for option %s", "--set-char-signedness");
231 2 : break;
232 :
233 2 : case 7:
234 2 : user_opts.transfer_mode = TRANSFER_MODE_SWAP;
235 2 : break;
236 :
237 2 : default:
238 2 : fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
239 : os_info.progname);
240 2 : exit(1);
241 : }
242 : }
243 :
244 34 : if (optind < argc)
245 0 : pg_fatal("too many command-line arguments (first is \"%s\")", argv[optind]);
246 :
247 34 : if (!user_opts.sync_method)
248 34 : user_opts.sync_method = pg_strdup("fsync");
249 :
250 34 : if (log_opts.verbose)
251 0 : pg_log(PG_REPORT, "Running in verbose mode");
252 :
253 34 : log_opts.isatty = isatty(fileno(stdout));
254 :
255 : /* Turn off read-only mode; add prefix to PGOPTIONS? */
256 34 : if (getenv("PGOPTIONS"))
257 : {
258 0 : char *pgoptions = psprintf("%s %s", FIX_DEFAULT_READ_ONLY,
259 : getenv("PGOPTIONS"));
260 :
261 0 : setenv("PGOPTIONS", pgoptions, 1);
262 0 : pfree(pgoptions);
263 : }
264 : else
265 34 : setenv("PGOPTIONS", FIX_DEFAULT_READ_ONLY, 1);
266 :
267 : /* Get values from env if not already set */
268 34 : check_required_directory(&old_cluster.bindir, "PGBINOLD", false,
269 34 : "-b", _("old cluster binaries reside"), false);
270 34 : check_required_directory(&new_cluster.bindir, "PGBINNEW", false,
271 34 : "-B", _("new cluster binaries reside"), true);
272 34 : check_required_directory(&old_cluster.pgdata, "PGDATAOLD", false,
273 34 : "-d", _("old cluster data resides"), false);
274 34 : check_required_directory(&new_cluster.pgdata, "PGDATANEW", false,
275 34 : "-D", _("new cluster data resides"), false);
276 34 : check_required_directory(&user_opts.socketdir, "PGSOCKETDIR", true,
277 34 : "-s", _("sockets will be created"), false);
278 :
279 : #ifdef WIN32
280 :
281 : /*
282 : * On Windows, initdb --sync-only will fail with a "Permission denied"
283 : * error on file pg_upgrade_utility.log if pg_upgrade is run inside the
284 : * new cluster directory, so we do a check here.
285 : */
286 : {
287 : char cwd[MAXPGPATH],
288 : new_cluster_pgdata[MAXPGPATH];
289 :
290 : strlcpy(new_cluster_pgdata, new_cluster.pgdata, MAXPGPATH);
291 : canonicalize_path(new_cluster_pgdata);
292 :
293 : if (!getcwd(cwd, MAXPGPATH))
294 : pg_fatal("could not determine current directory");
295 : canonicalize_path(cwd);
296 : if (path_is_prefix_of_path(new_cluster_pgdata, cwd))
297 : pg_fatal("cannot run pg_upgrade from inside the new cluster data directory on Windows");
298 : }
299 : #endif
300 34 : }
301 :
302 :
303 : static void
304 2 : usage(void)
305 : {
306 2 : printf(_("pg_upgrade upgrades a PostgreSQL cluster to a different major version.\n\n"));
307 2 : printf(_("Usage:\n"));
308 2 : printf(_(" pg_upgrade [OPTION]...\n\n"));
309 2 : printf(_("Options:\n"));
310 2 : printf(_(" -b, --old-bindir=BINDIR old cluster executable directory\n"));
311 2 : printf(_(" -B, --new-bindir=BINDIR new cluster executable directory (default\n"
312 : " same directory as pg_upgrade)\n"));
313 2 : printf(_(" -c, --check check clusters only, don't change any data\n"));
314 2 : printf(_(" -d, --old-datadir=DATADIR old cluster data directory\n"));
315 2 : printf(_(" -D, --new-datadir=DATADIR new cluster data directory\n"));
316 2 : printf(_(" -j, --jobs=NUM number of simultaneous processes or threads to use\n"));
317 2 : printf(_(" -k, --link link instead of copying files to new cluster\n"));
318 2 : printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
319 2 : printf(_(" -o, --old-options=OPTIONS old cluster options to pass to the server\n"));
320 2 : printf(_(" -O, --new-options=OPTIONS new cluster options to pass to the server\n"));
321 2 : printf(_(" -p, --old-port=PORT old cluster port number (default %d)\n"), old_cluster.port);
322 2 : printf(_(" -P, --new-port=PORT new cluster port number (default %d)\n"), new_cluster.port);
323 2 : printf(_(" -r, --retain retain SQL and log files after success\n"));
324 2 : printf(_(" -s, --socketdir=DIR socket directory to use (default current dir.)\n"));
325 2 : printf(_(" -U, --username=NAME cluster superuser (default \"%s\")\n"), os_info.user);
326 2 : printf(_(" -v, --verbose enable verbose internal logging\n"));
327 2 : printf(_(" -V, --version display version information, then exit\n"));
328 2 : printf(_(" --clone clone instead of copying files to new cluster\n"));
329 2 : printf(_(" --copy copy files to new cluster (default)\n"));
330 2 : printf(_(" --copy-file-range copy files to new cluster with copy_file_range\n"));
331 2 : printf(_(" --no-statistics do not import statistics from old cluster\n"));
332 2 : printf(_(" --set-char-signedness=OPTION set new cluster char signedness to \"signed\" or\n"
333 : " \"unsigned\"\n"));
334 2 : printf(_(" --swap move data directories to new cluster\n"));
335 2 : printf(_(" --sync-method=METHOD set method for syncing files to disk\n"));
336 2 : printf(_(" -?, --help show this help, then exit\n"));
337 2 : printf(_("\n"
338 : "Before running pg_upgrade you must:\n"
339 : " create a new database cluster (using the new version of initdb)\n"
340 : " shutdown the postmaster servicing the old cluster\n"
341 : " shutdown the postmaster servicing the new cluster\n"));
342 2 : printf(_("\n"
343 : "When you run pg_upgrade, you must provide the following information:\n"
344 : " the data directory for the old cluster (-d DATADIR)\n"
345 : " the data directory for the new cluster (-D DATADIR)\n"
346 : " the \"bin\" directory for the old version (-b BINDIR)\n"
347 : " the \"bin\" directory for the new version (-B BINDIR)\n"));
348 2 : printf(_("\n"
349 : "For example:\n"
350 : " pg_upgrade -d oldCluster/data -D newCluster/data -b oldCluster/bin -B newCluster/bin\n"
351 : "or\n"));
352 : #ifndef WIN32
353 2 : printf(_(" $ export PGDATAOLD=oldCluster/data\n"
354 : " $ export PGDATANEW=newCluster/data\n"
355 : " $ export PGBINOLD=oldCluster/bin\n"
356 : " $ export PGBINNEW=newCluster/bin\n"
357 : " $ pg_upgrade\n"));
358 : #else
359 : printf(_(" C:\\> set PGDATAOLD=oldCluster/data\n"
360 : " C:\\> set PGDATANEW=newCluster/data\n"
361 : " C:\\> set PGBINOLD=oldCluster/bin\n"
362 : " C:\\> set PGBINNEW=newCluster/bin\n"
363 : " C:\\> pg_upgrade\n"));
364 : #endif
365 2 : printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
366 2 : printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
367 2 : }
368 :
369 :
370 : /*
371 : * check_required_directory()
372 : *
373 : * Checks a directory option.
374 : * dirpath - the directory name supplied on the command line, or NULL
375 : * envVarName - the name of an environment variable to get if dirpath is NULL
376 : * useCwd - true if OK to default to CWD
377 : * cmdLineOption - the command line option for this directory
378 : * description - a description of this directory option
379 : * missingOk - true if OK that both dirpath and envVarName are not existing
380 : *
381 : * We use the last two arguments to construct a meaningful error message if the
382 : * user hasn't provided the required directory name.
383 : */
384 : static void
385 170 : check_required_directory(char **dirpath, const char *envVarName, bool useCwd,
386 : const char *cmdLineOption, const char *description,
387 : bool missingOk)
388 : {
389 170 : if (*dirpath == NULL || strlen(*dirpath) == 0)
390 : {
391 : const char *envVar;
392 :
393 0 : if ((envVar = getenv(envVarName)) && strlen(envVar))
394 0 : *dirpath = pg_strdup(envVar);
395 0 : else if (useCwd)
396 : {
397 : char cwd[MAXPGPATH];
398 :
399 0 : if (!getcwd(cwd, MAXPGPATH))
400 0 : pg_fatal("could not determine current directory");
401 0 : *dirpath = pg_strdup(cwd);
402 : }
403 0 : else if (missingOk)
404 0 : return;
405 : else
406 0 : pg_fatal("You must identify the directory where the %s.\n"
407 : "Please use the %s command-line option or the %s environment variable.",
408 : description, cmdLineOption, envVarName);
409 : }
410 :
411 : /*
412 : * Clean up the path, in particular trimming any trailing path separators,
413 : * because we construct paths by appending to this path.
414 : */
415 170 : canonicalize_path(*dirpath);
416 : }
417 :
418 : /*
419 : * adjust_data_dir
420 : *
421 : * If a configuration-only directory was specified, find the real data dir
422 : * by querying the running server. This has limited checking because we
423 : * can't check for a running server because we can't find postmaster.pid.
424 : *
425 : * On entry, cluster->pgdata has been set from command line or env variable,
426 : * but cluster->pgconfig isn't set. We fill both variables with corrected
427 : * values.
428 : */
429 : void
430 68 : adjust_data_dir(ClusterInfo *cluster)
431 : {
432 : char filename[MAXPGPATH];
433 : char cmd[MAXPGPATH],
434 : cmd_output[MAX_STRING];
435 : FILE *fp,
436 : *output;
437 : int rc;
438 :
439 : /* Initially assume config dir and data dir are the same */
440 68 : cluster->pgconfig = pg_strdup(cluster->pgdata);
441 :
442 : /* If there is no postgresql.conf, it can't be a config-only dir */
443 68 : snprintf(filename, sizeof(filename), "%s/postgresql.conf", cluster->pgconfig);
444 68 : if ((fp = fopen(filename, "r")) == NULL)
445 68 : return;
446 68 : fclose(fp);
447 :
448 : /* If PG_VERSION exists, it can't be a config-only dir */
449 68 : snprintf(filename, sizeof(filename), "%s/PG_VERSION", cluster->pgconfig);
450 68 : if ((fp = fopen(filename, "r")) != NULL)
451 : {
452 68 : fclose(fp);
453 68 : return;
454 : }
455 :
456 : /* Must be a configuration directory, so find the real data directory. */
457 :
458 0 : if (cluster == &old_cluster)
459 0 : prep_status("Finding the real data directory for the source cluster");
460 : else
461 0 : prep_status("Finding the real data directory for the target cluster");
462 :
463 : /*
464 : * We don't have a data directory yet, so we can't check the PG version,
465 : * so this might fail --- only works for PG 9.2+. If this fails,
466 : * pg_upgrade will fail anyway because the data files will not be found.
467 : */
468 0 : snprintf(cmd, sizeof(cmd), "\"%s/postgres\" -D \"%s\" -C data_directory",
469 : cluster->bindir, cluster->pgconfig);
470 0 : fflush(NULL);
471 :
472 0 : if ((output = popen(cmd, "r")) == NULL ||
473 0 : fgets(cmd_output, sizeof(cmd_output), output) == NULL)
474 0 : pg_fatal("could not get data directory using %s: %m", cmd);
475 :
476 0 : rc = pclose(output);
477 0 : if (rc != 0)
478 0 : pg_fatal("could not get data directory using %s: %s",
479 : cmd, wait_result_to_str(rc));
480 :
481 : /* strip trailing newline and carriage return */
482 0 : (void) pg_strip_crlf(cmd_output);
483 :
484 0 : cluster->pgdata = pg_strdup(cmd_output);
485 :
486 0 : check_ok();
487 : }
488 :
489 :
490 : /*
491 : * get_sock_dir
492 : *
493 : * Identify the socket directory to use for this cluster. If we're doing
494 : * a live check (old cluster only), we need to find out where the postmaster
495 : * is listening. Otherwise, we're going to put the socket into the current
496 : * directory.
497 : */
498 : void
499 60 : get_sock_dir(ClusterInfo *cluster)
500 : {
501 : #if !defined(WIN32)
502 60 : if (!user_opts.live_check || cluster == &new_cluster)
503 60 : cluster->sockdir = user_opts.socketdir;
504 : else
505 : {
506 : /*
507 : * If we are doing a live check, we will use the old cluster's Unix
508 : * domain socket directory so we can connect to the live server.
509 : */
510 0 : unsigned short orig_port = cluster->port;
511 : char filename[MAXPGPATH],
512 : line[MAXPGPATH];
513 : FILE *fp;
514 : int lineno;
515 :
516 0 : snprintf(filename, sizeof(filename), "%s/postmaster.pid",
517 : cluster->pgdata);
518 0 : if ((fp = fopen(filename, "r")) == NULL)
519 0 : pg_fatal("could not open file \"%s\": %m", filename);
520 :
521 0 : for (lineno = 1;
522 : lineno <= Max(LOCK_FILE_LINE_PORT, LOCK_FILE_LINE_SOCKET_DIR);
523 0 : lineno++)
524 : {
525 0 : if (fgets(line, sizeof(line), fp) == NULL)
526 0 : pg_fatal("could not read line %d from file \"%s\": %m",
527 : lineno, filename);
528 :
529 : /* potentially overwrite user-supplied value */
530 0 : if (lineno == LOCK_FILE_LINE_PORT)
531 0 : sscanf(line, "%hu", &old_cluster.port);
532 0 : if (lineno == LOCK_FILE_LINE_SOCKET_DIR)
533 : {
534 : /* strip trailing newline and carriage return */
535 0 : cluster->sockdir = pg_strdup(line);
536 0 : (void) pg_strip_crlf(cluster->sockdir);
537 : }
538 : }
539 0 : fclose(fp);
540 :
541 : /* warn of port number correction */
542 0 : if (orig_port != DEF_PGUPORT && old_cluster.port != orig_port)
543 0 : pg_log(PG_WARNING, "user-supplied old port number %hu corrected to %hu",
544 0 : orig_port, cluster->port);
545 : }
546 : #else /* WIN32 */
547 : cluster->sockdir = NULL;
548 : #endif
549 60 : }
|