*** doc/src/sgml/ref/pg_basebackup.sgml
--- doc/src/sgml/ref/pg_basebackup.sgml
***************
*** 138,143 **** PostgreSQL documentation
--- 138,155 ----
       </varlistentry>
  
       <varlistentry>
+       <term><option>-T <replaceable class="parameter">oid:tablespacedir</replaceable></option></term>
+       <term><option>--tablespace=<replaceable class="parameter">oid:tablespacedir</replaceable></option></term>
+       <listitem>
+        <para>
+         Specifies the location where tablespace with <replaceable>oid</replaceable>
+         is written, <replaceable>tablespacedir</replaceable> must be an absolute path.
+         This options can be specified multiple times for multiple tablespaces.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
        <term><option>-F <replaceable class="parameter">format</replaceable></option></term>
        <term><option>--format=<replaceable class="parameter">format</replaceable></option></term>
        <listitem>
***************
*** 530,536 **** PostgreSQL documentation
    <para>
     The way <productname>PostgreSQL</productname> manages tablespaces, the path
     for all additional tablespaces must be identical whenever a backup is
!    restored. The main data directory, however, is relocatable to any location.
    </para>
  
    <para>
--- 542,548 ----
    <para>
     The way <productname>PostgreSQL</productname> manages tablespaces, the path
     for all additional tablespaces must be identical whenever a backup is
!    restored, if <replaceable>--tablespace</replaceable> isn't specified.
    </para>
  
    <para>
***************
*** 570,575 **** PostgreSQL documentation
--- 582,595 ----
     (This command will fail if there are multiple tablespaces in the
     database.)
    </para>
+ 
+   <para>
+    To create a backup of a two-tablespace local database where tablespace
+    <literal>archive</literal> is written to <literal>./backup/archive</literal>
+ <screen>
+ <prompt>$</prompt> <userinput>pg_basebackup -D $(pwd)/backup/data -T archive:$(pwd)/backup/archive</userinput>
+ </screen>
+   </para>
   </refsect1>
  
   <refsect1>
*** src/bin/pg_basebackup/pg_basebackup.c
--- src/bin/pg_basebackup/pg_basebackup.c
***************
*** 33,40 ****
--- 33,54 ----
  #include "streamutil.h"
  
  
+ #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
+ 
+ typedef struct TablespaceListCell {
+ 	struct TablespaceListCell *next;
+ 	Oid oid;
+ 	char path[1];
+ } TablespaceListCell;
+ 
+ typedef struct TablespaceList {
+ 	TablespaceListCell *head;
+ 	TablespaceListCell *tail;
+ } TablespaceList;
+ 
  /* Global options */
  static char *basedir = NULL;
+ static TablespaceList tablespace_locations = {NULL, NULL};
  static char *xlog_dir = "";
  static char	format = 'p';		/* p(lain)/t(ar) */
  static char *label = "pg_basebackup base backup";
***************
*** 86,91 **** static void BaseBackup(void);
--- 100,140 ----
  static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline,
  					 bool segment_finished);
  
+ static const char *get_tablespace_location(Oid oid, const char *location);
+ static void update_tablespace_symlink(Oid oid, const char *location);
+ static bool tablespace_list_append(TablespaceList *list, const char *tablespace);
+ 
+ static bool
+ tablespace_list_append(TablespaceList *list, const char *tablespace)
+ {
+ 	TablespaceListCell *cell;
+ 	char *path;
+ 	Oid oid = atooid(tablespace);
+ 
+ 	if (!oid)
+ 		return false;
+ 
+ 	path = strstr(tablespace, ":/");
+ 	if (!path)
+ 		return false;
+ 	path++;
+ 
+ 	cell = (TablespaceListCell *) pg_malloc(sizeof(TablespaceListCell) + strlen(path));
+ 
+ 	cell->next = NULL;
+ 	cell->oid = oid;
+ 	strcpy(cell->path, path);
+ 
+ 	if (list->tail)
+ 		list->tail->next = cell;
+ 	else
+ 		list->head = cell;
+ 	list->tail = cell;
+ 
+ 	return true;
+ }
+ 
+ 
  #ifdef HAVE_LIBZ
  static const char *
  get_gz_error(gzFile gzf)
***************
*** 110,115 **** usage(void)
--- 159,166 ----
  	printf(_("  %s [OPTION]...\n"), progname);
  	printf(_("\nOptions controlling the output:\n"));
  	printf(_("  -D, --pgdata=DIRECTORY receive base backup into directory\n"));
+ 	printf(_("  -T, --tablespace=OID:LOCATION\n"
+ 			 "                         Specify tablespace oid and its absolute location\n"));
  	printf(_("  -F, --format=p|t       output format (plain (default), tar)\n"));
  	printf(_("  -R, --write-recovery-conf\n"
  			 "                         write recovery.conf after backup\n"));
***************
*** 861,874 **** ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
  }
  
  /*
   * Receive a tar format stream from the connection to the server, and unpack
   * the contents of it into a directory. Only files, directories and
   * symlinks are supported, no other kinds of special files.
   *
   * If the data is for the main data directory, it will be restored in the
   * specified directory. If it's for another tablespace, it will be restored
!  * in the original directory, since relocation of tablespaces is not
!  * supported.
   */
  static void
  ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
--- 912,967 ----
  }
  
  /*
+  * Retrieve tablespace location, either relocated or original depending on
+  * whether -T oid:tablespacedir was passed or not.
+  */
+ static const char *
+ get_tablespace_location(Oid oid, const char *location)
+ {
+ 	TablespaceListCell *cell;
+ 
+ 	for (cell = tablespace_locations.head; cell; cell = cell->next)
+ 		if (oid == cell->oid)
+ 			return cell->path;
+ 
+ 	return location;
+ }
+ 
+ /*
+  * Update symlinks to reflect relocated tablespace, only applied if
+  * tablespace isn't in its original location.
+  */
+ static void
+ update_tablespace_symlink(Oid oid, const char *location)
+ {
+ 	const char *new_location = get_tablespace_location(oid, location);
+ 	if (strcmp(new_location, location) != 0)
+ 	{
+ 		char linkloc[MAXPGPATH];
+ 		snprintf(linkloc, sizeof(linkloc), "%s/pg_tblspc/%d", basedir, oid);
+ 		if (unlink(linkloc) < 0 && errno != ENOENT)
+ 		{
+ 			fprintf(stderr, _("%s: unable to remove \"%s\": %s"),
+ 					progname, linkloc, strerror(errno));
+ 			disconnect_and_exit(1);
+ 		}
+ 		if (symlink(new_location, linkloc) < 0)
+ 		{
+ 			fprintf(stderr, _("%s: unable to create symlink \"%s\": %s"),
+ 					progname, linkloc, strerror(errno));
+ 			disconnect_and_exit(1);
+ 		}
+ 	}
+ }
+ 
+ /*
   * Receive a tar format stream from the connection to the server, and unpack
   * the contents of it into a directory. Only files, directories and
   * symlinks are supported, no other kinds of special files.
   *
   * If the data is for the main data directory, it will be restored in the
   * specified directory. If it's for another tablespace, it will be restored
!  * in the original directory, if tablespace relocation is not enabled.
   */
  static void
  ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
***************
*** 884,890 **** ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
  	if (basetablespace)
  		strcpy(current_path, basedir);
  	else
! 		strcpy(current_path, PQgetvalue(res, rownum, 1));
  
  	/*
  	 * Get the COPY data
--- 977,987 ----
  	if (basetablespace)
  		strcpy(current_path, basedir);
  	else
! 	{
! 		strcpy(current_path,
! 			   get_tablespace_location(atooid(PQgetvalue(res, rownum, 0)),
! 									   PQgetvalue(res, rownum, 1)));
! 	}
  
  	/*
  	 * Get the COPY data
***************
*** 1465,1471 **** BaseBackup(void)
  		 * we do anything anyway.
  		 */
  		if (format == 'p' && !PQgetisnull(res, i, 1))
! 			verify_dir_is_empty_or_create(PQgetvalue(res, i, 1));
  	}
  
  	/*
--- 1562,1572 ----
  		 * we do anything anyway.
  		 */
  		if (format == 'p' && !PQgetisnull(res, i, 1))
! 		{
! 			char *path = (char *) get_tablespace_location(atooid(PQgetvalue(res, i, 0)),
! 														  PQgetvalue(res, i, 1));
! 			verify_dir_is_empty_or_create(path);
! 		}
  	}
  
  	/*
***************
*** 1507,1512 **** BaseBackup(void)
--- 1608,1629 ----
  		progress_report(PQntuples(res), NULL);
  		fprintf(stderr, "\n");	/* Need to move to next line */
  	}
+ 
+ 	if (format == 'p' && tablespace_locations.head != NULL)
+ 	{
+ #ifdef HAVE_SYMLINK
+ 		for (i = 0; i < PQntuples(res); i++)
+ 		{
+ 			int tblspc_oid = atooid(PQgetvalue(res, i, 0));
+ 			if (tblspc_oid)
+ 				update_tablespace_symlink(tblspc_oid, PQgetvalue(res, i, 1));
+ 		}
+ #else
+ 		fprintf(stderr, _("%s: not updating pg_tblspc with new tablespace relocation\n"),
+ 				progname);
+ #endif
+ 	}
+ 
  	PQclear(res);
  
  	/*
***************
*** 1655,1660 **** main(int argc, char **argv)
--- 1772,1778 ----
  		{"help", no_argument, NULL, '?'},
  		{"version", no_argument, NULL, 'V'},
  		{"pgdata", required_argument, NULL, 'D'},
+ 		{"tablespace", required_argument, NULL, 'T'},
  		{"format", required_argument, NULL, 'F'},
  		{"checkpoint", required_argument, NULL, 'c'},
  		{"write-recovery-conf", no_argument, NULL, 'R'},
***************
*** 1697,1703 **** main(int argc, char **argv)
  		}
  	}
  
! 	while ((c = getopt_long(argc, argv, "D:F:RxX:l:zZ:d:c:h:p:U:s:wWvP",
  							long_options, &option_index)) != -1)
  	{
  		switch (c)
--- 1815,1821 ----
  		}
  	}
  
! 	while ((c = getopt_long(argc, argv, "D:T:F:RxX:l:zZ:d:c:h:p:U:s:wWvP",
  							long_options, &option_index)) != -1)
  	{
  		switch (c)
***************
*** 1705,1710 **** main(int argc, char **argv)
--- 1823,1837 ----
  			case 'D':
  				basedir = pg_strdup(optarg);
  				break;
+ 			case 'T':
+ 				if (!tablespace_list_append(&tablespace_locations, optarg))
+ 				{
+ 					fprintf(stderr,
+ 							_("%s: invalid tablespace format \"%s\", must be \"oid:/absolute-path\"\n"),
+ 							progname, optarg);
+ 					exit(1);
+ 				}
+ 				break;
  			case 'F':
  				if (strcmp(optarg, "p") == 0 || strcmp(optarg, "plain") == 0)
  					format = 'p';
