/* * Convert a "text" filename argument to C string, and check it's allowable. * * Filename may be absolute or relative to the DataDir, but we only allow * absolute paths that match DataDir or Log_directory. */ static char * convert_and_check_filename(text *arg, bool logAllowed) { char *filename = text_to_cstring(arg); canonicalize_path(filename); /* filename can change length here */ if (is_absolute_path(filename)) { /* Disallow '/a/b/data/..' */ if (path_contains_parent_reference(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("reference to parent directory (\"..\") not allowed")))); /* * Allow absolute paths if within DataDir or Log_directory, even * though Log_directory might be outside DataDir. */ if (!path_is_prefix_of_path(DataDir, filename) && (!logAllowed || !is_absolute_path(Log_directory) || !path_is_prefix_of_path(Log_directory, filename))) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("absolute path not allowed")))); } else if (!path_is_relative_and_below_cwd(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("path must be in or below the current directory")))); return filename; }
/* * Convert a "text" filename argument to C string, and check it's allowable. * * Filename may be absolute or relative to the DataDir, but we only allow * absolute paths that match DataDir or Log_directory. */ static char * convert_and_check_filename(text *arg) { char *filename; filename = text_to_cstring(arg); canonicalize_path(filename); /* filename can change length here */ /* Disallow ".." in the path */ if (path_contains_parent_reference(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("reference to parent directory (\"..\") not allowed")))); if (is_absolute_path(filename)) { /* Allow absolute references within DataDir */ if (path_is_prefix_of_path(DataDir, filename)) return filename; /* The log directory might be outside our datadir, but allow it */ if (is_absolute_path(Log_directory) && path_is_prefix_of_path(Log_directory, filename)) return filename; ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("absolute path not allowed")))); return NULL; /* keep compiler quiet */ } else { return filename; } }
/* * Convert a "text" filename argument to C string, and check it's allowable. * * Filename may be absolute or relative to the DataDir, but we only allow * absolute paths that match DataDir or Log_directory. * * This does a privilege check against the 'pg_read_server_files' role, so * this function is really only appropriate for callers who are only checking * 'read' access. Do not use this function if you are looking for a check * for 'write' or 'program' access without updating it to access the type * of check as an argument and checking the appropriate role membership. */ static char * convert_and_check_filename(text *arg) { char *filename; filename = text_to_cstring(arg); canonicalize_path(filename); /* filename can change length here */ /* * Members of the 'pg_read_server_files' role are allowed to access any * files on the server as the PG user, so no need to do any further checks * here. */ if (is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_SERVER_FILES)) return filename; /* User isn't a member of the default role, so check if it's allowable */ if (is_absolute_path(filename)) { /* Disallow '/a/b/data/..' */ if (path_contains_parent_reference(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("reference to parent directory (\"..\") not allowed")))); /* * Allow absolute paths if within DataDir or Log_directory, even * though Log_directory might be outside DataDir. */ if (!path_is_prefix_of_path(DataDir, filename) && (!is_absolute_path(Log_directory) || !path_is_prefix_of_path(Log_directory, filename))) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("absolute path not allowed")))); } else if (!path_is_relative_and_below_cwd(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("path must be in or below the current directory")))); return filename; }
/* * create_script_for_old_cluster_deletion() * * This is particularly useful for tablespace deletion. */ void create_script_for_old_cluster_deletion(char **deletion_script_file_name) { FILE *script = NULL; int tblnum; char old_cluster_pgdata[MAXPGPATH]; *deletion_script_file_name = psprintf("%sdelete_old_cluster.%s", SCRIPT_PREFIX, SCRIPT_EXT); /* * Some users (oddly) create tablespaces inside the cluster data * directory. We can't create a proper old cluster delete script in that * case. */ strlcpy(old_cluster_pgdata, old_cluster.pgdata, MAXPGPATH); canonicalize_path(old_cluster_pgdata); for (tblnum = 0; tblnum < os_info.num_old_tablespaces; tblnum++) { char old_tablespace_dir[MAXPGPATH]; strlcpy(old_tablespace_dir, os_info.old_tablespaces[tblnum], MAXPGPATH); canonicalize_path(old_tablespace_dir); if (path_is_prefix_of_path(old_cluster_pgdata, old_tablespace_dir)) { /* Unlink file in case it is left over from a previous run. */ unlink(*deletion_script_file_name); pg_free(*deletion_script_file_name); *deletion_script_file_name = NULL; return; } } prep_status("Creating script to delete old cluster"); if ((script = fopen_priv(*deletion_script_file_name, "w")) == NULL) pg_fatal("Could not open file \"%s\": %s\n", *deletion_script_file_name, getErrorText(errno)); #ifndef WIN32 /* add shebang header */ fprintf(script, "#!/bin/sh\n\n"); #endif /* delete old cluster's default tablespace */ fprintf(script, RMDIR_CMD " \"%s\"\n", fix_path_separator(old_cluster.pgdata)); /* delete old cluster's alternate tablespaces */ for (tblnum = 0; tblnum < os_info.num_old_tablespaces; tblnum++) { /* * Do the old cluster's per-database directories share a directory * with a new version-specific tablespace? */ if (strlen(old_cluster.tablespace_suffix) == 0) { /* delete per-database directories */ int dbnum; fprintf(script, "\n"); /* remove PG_VERSION? */ if (GET_MAJOR_VERSION(old_cluster.major_version) <= 804) fprintf(script, RM_CMD " %s%cPG_VERSION\n", fix_path_separator(os_info.old_tablespaces[tblnum]), PATH_SEPARATOR); for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++) fprintf(script, RMDIR_CMD " \"%s%c%d\"\n", fix_path_separator(os_info.old_tablespaces[tblnum]), PATH_SEPARATOR, old_cluster.dbarr.dbs[dbnum].db_oid); } else { char *suffix_path = pg_strdup(old_cluster.tablespace_suffix); /* * Simply delete the tablespace directory, which might be ".old" * or a version-specific subdirectory. */ fprintf(script, RMDIR_CMD " \"%s%s\"\n", fix_path_separator(os_info.old_tablespaces[tblnum]), fix_path_separator(suffix_path)); pfree(suffix_path); } } fclose(script); #ifndef WIN32 if (chmod(*deletion_script_file_name, S_IRWXU) != 0) pg_fatal("Could not add execute permission to file \"%s\": %s\n", *deletion_script_file_name, getErrorText(errno)); #endif check_ok(); }
/* * Create a table space * * Only superusers can create a tablespace. This seems a reasonable restriction * since we're determining the system layout and, anyway, we probably have * root if we're doing this kind of activity */ Oid CreateTableSpace(CreateTableSpaceStmt *stmt) { #ifdef HAVE_SYMLINK Relation rel; Datum values[Natts_pg_tablespace]; bool nulls[Natts_pg_tablespace]; HeapTuple tuple; Oid tablespaceoid; char *location; Oid ownerId; Datum newOptions; /* Must be super user */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create tablespace \"%s\"", stmt->tablespacename), errhint("Must be superuser to create a tablespace."))); /* However, the eventual owner of the tablespace need not be */ if (stmt->owner) ownerId = get_rolespec_oid(stmt->owner, false); else ownerId = GetUserId(); /* Unix-ify the offered path, and strip any trailing slashes */ location = pstrdup(stmt->location); canonicalize_path(location); /* disallow quotes, else CREATE DATABASE would be at risk */ if (strchr(location, '\'')) ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("tablespace location cannot contain single quotes"))); /* * Allowing relative paths seems risky * * this also helps us ensure that location is not empty or whitespace */ if (!is_absolute_path(location)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("tablespace location must be an absolute path"))); /* * Check that location isn't too long. Remember that we're going to append * 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'. FYI, we never actually * reference the whole path here, but mkdir() uses the first two parts. */ if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 + OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > MAXPGPATH) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("tablespace location \"%s\" is too long", location))); /* Warn if the tablespace is in the data directory. */ if (path_is_prefix_of_path(DataDir, location)) ereport(WARNING, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("tablespace location should not be inside the data directory"))); /* * Disallow creation of tablespaces named "pg_xxx"; we reserve this * namespace for system purposes. */ if (!allowSystemTableMods && IsReservedName(stmt->tablespacename)) ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("unacceptable tablespace name \"%s\"", stmt->tablespacename), errdetail("The prefix \"pg_\" is reserved for system tablespaces."))); /* * Check that there is no other tablespace by this name. (The unique * index would catch this anyway, but might as well give a friendlier * message.) */ if (OidIsValid(get_tablespace_oid(stmt->tablespacename, true))) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("tablespace \"%s\" already exists", stmt->tablespacename))); /* * Insert tuple into pg_tablespace. The purpose of doing this first is to * lock the proposed tablename against other would-be creators. The * insertion will roll back if we find problems below. */ rel = heap_open(TableSpaceRelationId, RowExclusiveLock); MemSet(nulls, false, sizeof(nulls)); values[Anum_pg_tablespace_spcname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->tablespacename)); values[Anum_pg_tablespace_spcowner - 1] = ObjectIdGetDatum(ownerId); nulls[Anum_pg_tablespace_spcacl - 1] = true; /* Generate new proposed spcoptions (text array) */ newOptions = transformRelOptions((Datum) 0, stmt->options, NULL, NULL, false, false); (void) tablespace_reloptions(newOptions, true); if (newOptions != (Datum) 0) values[Anum_pg_tablespace_spcoptions - 1] = newOptions; else nulls[Anum_pg_tablespace_spcoptions - 1] = true; tuple = heap_form_tuple(rel->rd_att, values, nulls); tablespaceoid = simple_heap_insert(rel, tuple); CatalogUpdateIndexes(rel, tuple); heap_freetuple(tuple); /* Record dependency on owner */ recordDependencyOnOwner(TableSpaceRelationId, tablespaceoid, ownerId); /* Post creation hook for new tablespace */ InvokeObjectPostCreateHook(TableSpaceRelationId, tablespaceoid, 0); create_tablespace_directories(location, tablespaceoid); /* Record the filesystem change in XLOG */ { xl_tblspc_create_rec xlrec; xlrec.ts_id = tablespaceoid; XLogBeginInsert(); XLogRegisterData((char *) &xlrec, offsetof(xl_tblspc_create_rec, ts_path)); XLogRegisterData((char *) location, strlen(location) + 1); (void) XLogInsert(RM_TBLSPC_ID, XLOG_TBLSPC_CREATE); } /* * Force synchronous commit, to minimize the window between creating the * symlink on-disk and marking the transaction committed. It's not great * that there is any window at all, but definitely we don't want to make * it larger than necessary. */ ForceSyncCommit(); pfree(location); /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); return tablespaceoid; #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform"))); return InvalidOid; /* keep compiler quiet */ #endif /* HAVE_SYMLINK */ }
/* * parseCommandLine() * * Parses the command line (argc, argv[]) and loads structures */ void parseCommandLine(int argc, char *argv[]) { static struct option long_options[] = { {"old-datadir", required_argument, NULL, 'd'}, {"new-datadir", required_argument, NULL, 'D'}, {"old-bindir", required_argument, NULL, 'b'}, {"new-bindir", required_argument, NULL, 'B'}, {"old-options", required_argument, NULL, 'o'}, {"new-options", required_argument, NULL, 'O'}, {"old-port", required_argument, NULL, 'p'}, {"new-port", required_argument, NULL, 'P'}, {"username", required_argument, NULL, 'U'}, {"check", no_argument, NULL, 'c'}, {"link", no_argument, NULL, 'k'}, {"retain", no_argument, NULL, 'r'}, {"jobs", required_argument, NULL, 'j'}, {"verbose", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0} }; int option; /* Command line option */ int optindex = 0; /* used by getopt_long */ int os_user_effective_id; FILE *fp; char **filename; time_t run_time = time(NULL); user_opts.transfer_mode = TRANSFER_MODE_COPY; os_info.progname = get_progname(argv[0]); /* Process libpq env. variables; load values here for usage() output */ old_cluster.port = getenv("PGPORTOLD") ? atoi(getenv("PGPORTOLD")) : DEF_PGUPORT; new_cluster.port = getenv("PGPORTNEW") ? atoi(getenv("PGPORTNEW")) : DEF_PGUPORT; os_user_effective_id = get_user_info(&os_info.user); /* we override just the database user name; we got the OS id above */ if (getenv("PGUSER")) { pg_free(os_info.user); /* must save value, getenv()'s pointer is not stable */ os_info.user = pg_strdup(getenv("PGUSER")); } if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { usage(); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { puts("pg_upgrade (PostgreSQL) " PG_VERSION); exit(0); } } /* Allow help and version to be run as root, so do the test here. */ if (os_user_effective_id == 0) pg_fatal("%s: cannot be run as root\n", os_info.progname); if ((log_opts.internal = fopen_priv(INTERNAL_LOG_FILE, "a")) == NULL) pg_fatal("cannot write to log file %s\n", INTERNAL_LOG_FILE); while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rU:v", long_options, &optindex)) != -1) { switch (option) { case 'b': old_cluster.bindir = pg_strdup(optarg); break; case 'B': new_cluster.bindir = pg_strdup(optarg); break; case 'c': user_opts.check = true; break; case 'd': old_cluster.pgdata = pg_strdup(optarg); old_cluster.pgconfig = pg_strdup(optarg); break; case 'D': new_cluster.pgdata = pg_strdup(optarg); new_cluster.pgconfig = pg_strdup(optarg); break; case 'j': user_opts.jobs = atoi(optarg); break; case 'k': user_opts.transfer_mode = TRANSFER_MODE_LINK; break; case 'o': old_cluster.pgopts = pg_strdup(optarg); break; case 'O': new_cluster.pgopts = pg_strdup(optarg); break; /* * Someday, the port number option could be removed and passed * using -o/-O, but that requires postmaster -C to be * supported on all old/new versions. */ case 'p': if ((old_cluster.port = atoi(optarg)) <= 0) { pg_fatal("invalid old port number\n"); exit(1); } break; case 'P': if ((new_cluster.port = atoi(optarg)) <= 0) { pg_fatal("invalid new port number\n"); exit(1); } break; case 'r': log_opts.retain = true; break; case 'U': pg_free(os_info.user); os_info.user = pg_strdup(optarg); os_info.user_specified = true; /* * Push the user name into the environment so pre-9.1 * pg_ctl/libpq uses it. */ pg_putenv("PGUSER", os_info.user); break; case 'v': pg_log(PG_REPORT, "Running in verbose mode\n"); log_opts.verbose = true; break; default: pg_fatal("Try \"%s --help\" for more information.\n", os_info.progname); break; } } /* label start of upgrade in logfiles */ for (filename = output_files; *filename != NULL; filename++) { if ((fp = fopen_priv(*filename, "a")) == NULL) pg_fatal("cannot write to log file %s\n", *filename); /* Start with newline because we might be appending to a file. */ fprintf(fp, "\n" "-----------------------------------------------------------------\n" " pg_upgrade run on %s" "-----------------------------------------------------------------\n\n", ctime(&run_time)); fclose(fp); } /* Turn off read-only mode; add prefix to PGOPTIONS? */ if (getenv("PGOPTIONS")) { char *pgoptions = psprintf("%s %s", FIX_DEFAULT_READ_ONLY, getenv("PGOPTIONS")); pg_putenv("PGOPTIONS", pgoptions); pfree(pgoptions); } else pg_putenv("PGOPTIONS", FIX_DEFAULT_READ_ONLY); /* Get values from env if not already set */ check_required_directory(&old_cluster.bindir, NULL, "PGBINOLD", "-b", "old cluster binaries reside"); check_required_directory(&new_cluster.bindir, NULL, "PGBINNEW", "-B", "new cluster binaries reside"); check_required_directory(&old_cluster.pgdata, &old_cluster.pgconfig, "PGDATAOLD", "-d", "old cluster data resides"); check_required_directory(&new_cluster.pgdata, &new_cluster.pgconfig, "PGDATANEW", "-D", "new cluster data resides"); #ifdef WIN32 /* * On Windows, initdb --sync-only will fail with a "Permission denied" * error on file pg_upgrade_utility.log if pg_upgrade is run inside * the new cluster directory, so we do a check here. */ { char cwd[MAXPGPATH], new_cluster_pgdata[MAXPGPATH]; strlcpy(new_cluster_pgdata, new_cluster.pgdata, MAXPGPATH); canonicalize_path(new_cluster_pgdata); if (!getcwd(cwd, MAXPGPATH)) pg_fatal("cannot find current directory\n"); canonicalize_path(cwd); if (path_is_prefix_of_path(new_cluster_pgdata, cwd)) pg_fatal("cannot run pg_upgrade from inside the new cluster data directory on Windows\n"); } #endif }