@ -35,8 +35,24 @@
# include "streamutil.h"
# define atooid(x) ((Oid) strtoul((x), NULL, 10))
typedef struct TablespaceListCell
{
struct TablespaceListCell * next ;
char old_dir [ MAXPGPATH ] ;
char new_dir [ MAXPGPATH ] ;
} TablespaceListCell ;
typedef struct TablespaceList
{
TablespaceListCell * head ;
TablespaceListCell * tail ;
} TablespaceList ;
/* Global options */
static char * basedir = NULL ;
static TablespaceList tablespace_dirs = { NULL , NULL } ;
static char * xlog_dir = " " ;
static char format = ' p ' ; /* p(lain)/t(ar) */
static char * label = " pg_basebackup base backup " ;
@ -90,6 +106,10 @@ static void BaseBackup(void);
static bool reached_end_position ( XLogRecPtr segendpos , uint32 timeline ,
bool segment_finished ) ;
static const char * get_tablespace_mapping ( const char * dir ) ;
static void update_tablespace_symlink ( Oid oid , const char * old_dir ) ;
static void tablespace_list_append ( const char * arg ) ;
static void disconnect_and_exit ( int code )
{
@ -110,6 +130,77 @@ static void disconnect_and_exit(int code)
}
/*
* Split argument into old_dir and new_dir and append to tablespace mapping
* list .
*/
static void
tablespace_list_append ( const char * arg )
{
TablespaceListCell * cell = ( TablespaceListCell * ) pg_malloc0 ( sizeof ( TablespaceListCell ) ) ;
char * dst ;
char * dst_ptr ;
const char * arg_ptr ;
dst_ptr = dst = cell - > old_dir ;
for ( arg_ptr = arg ; * arg_ptr ; arg_ptr + + )
{
if ( dst_ptr - dst > = MAXPGPATH )
{
fprintf ( stderr , _ ( " %s: directory name too long \n " ) , progname ) ;
exit ( 1 ) ;
}
if ( * arg_ptr = = ' \\ ' & & * ( arg_ptr + 1 ) = = ' = ' )
; /* skip backslash escaping = */
else if ( * arg_ptr = = ' = ' & & ( arg_ptr = = arg | | * ( arg_ptr - 1 ) ! = ' \\ ' ) )
{
if ( * cell - > new_dir )
{
fprintf ( stderr , _ ( " %s: multiple \" = \" signs in tablespace mapping \n " ) , progname ) ;
exit ( 1 ) ;
}
else
dst = dst_ptr = cell - > new_dir ;
}
else
* dst_ptr + + = * arg_ptr ;
}
if ( ! * cell - > old_dir | | ! * cell - > new_dir )
{
fprintf ( stderr ,
_ ( " %s: invalid tablespace mapping format \" %s \" , must be \" OLDDIR=NEWDIR \" \n " ) ,
progname , arg ) ;
exit ( 1 ) ;
}
/* This check isn't absolutely necessary. But all tablespaces are created
* with absolute directories , so specifying a non - absolute path here would
* just never match , possibly confusing users . It ' s also good to be
* consistent with the new_dir check . */
if ( ! is_absolute_path ( cell - > old_dir ) )
{
fprintf ( stderr , _ ( " %s: old directory not absolute in tablespace mapping: %s \n " ) ,
progname , cell - > old_dir ) ;
exit ( 1 ) ;
}
if ( ! is_absolute_path ( cell - > new_dir ) )
{
fprintf ( stderr , _ ( " %s: new directory not absolute in tablespace mapping: %s \n " ) ,
progname , cell - > new_dir ) ;
exit ( 1 ) ;
}
if ( tablespace_dirs . tail )
tablespace_dirs . tail - > next = cell ;
else
tablespace_dirs . head = cell ;
tablespace_dirs . tail = cell ;
}
# ifdef HAVE_LIBZ
static const char *
get_gz_error ( gzFile gzf )
@ -137,6 +228,8 @@ usage(void)
printf ( _ ( " -F, --format=p|t output format (plain (default), tar) \n " ) ) ;
printf ( _ ( " -R, --write-recovery-conf \n "
" write recovery.conf after backup \n " ) ) ;
printf ( _ ( " -T, --tablespace-mapping=OLDDIR=NEWDIR \n "
" relocate tablespace in OLDDIR to NEWDIR \n " ) ) ;
printf ( _ ( " -x, --xlog include required WAL files in backup (fetch mode) \n " ) ) ;
printf ( _ ( " -X, --xlog-method=fetch|stream \n "
" include required WAL files with specified method \n " ) ) ;
@ -899,6 +992,52 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
PQfreemem ( copybuf ) ;
}
/*
* Retrieve tablespace path , either relocated or original depending on whether
* - T was passed or not .
*/
static const char *
get_tablespace_mapping ( const char * dir )
{
TablespaceListCell * cell ;
for ( cell = tablespace_dirs . head ; cell ; cell = cell - > next )
if ( strcmp ( dir , cell - > old_dir ) = = 0 )
return cell - > new_dir ;
return dir ;
}
/*
* Update symlinks to reflect relocated tablespace .
*/
static void
update_tablespace_symlink ( Oid oid , const char * old_dir )
{
const char * new_dir = get_tablespace_mapping ( old_dir ) ;
if ( strcmp ( old_dir , new_dir ) ! = 0 )
{
char * linkloc = psprintf ( " %s/pg_tblspc/%d " , basedir , oid ) ;
if ( unlink ( linkloc ) ! = 0 & & errno ! = ENOENT )
{
fprintf ( stderr , _ ( " %s: could not remove symbolic link \" %s \" : %s " ) ,
progname , linkloc , strerror ( errno ) ) ;
disconnect_and_exit ( 1 ) ;
}
if ( symlink ( new_dir , linkloc ) ! = 0 )
{
fprintf ( stderr , _ ( " %s: could not create symbolic link \" %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
@ -906,8 +1045,7 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
*
* 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 .
* in the original or mapped directory .
*/
static void
ReceiveAndUnpackTarFile ( PGconn * conn , PGresult * res , int rownum )
@ -923,7 +1061,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
if ( basetablespace )
strlcpy ( current_path , basedir , sizeof ( current_path ) ) ;
else
strlcpy ( current_path , PQgetvalue ( res , rownum , 1 ) , sizeof ( current_path ) ) ;
strlcpy ( current_path , get_tablespace_mapping ( PQgetvalue ( res , rownum , 1 ) ) , sizeof ( current_path ) ) ;
/*
* Get the COPY data
@ -1503,7 +1641,10 @@ BaseBackup(void)
* we do anything anyway .
*/
if ( format = = ' p ' & & ! PQgetisnull ( res , i , 1 ) )
verify_dir_is_empty_or_create ( PQgetvalue ( res , i , 1 ) ) ;
{
char * path = ( char * ) get_tablespace_mapping ( PQgetvalue ( res , i , 1 ) ) ;
verify_dir_is_empty_or_create ( path ) ;
}
}
/*
@ -1545,6 +1686,17 @@ BaseBackup(void)
progress_report ( PQntuples ( res ) , NULL , true ) ;
fprintf ( stderr , " \n " ) ; /* Need to move to next line */
}
if ( format = = ' p ' & & tablespace_dirs . head ! = NULL )
{
for ( i = 0 ; i < PQntuples ( res ) ; i + + )
{
Oid tblspc_oid = atooid ( PQgetvalue ( res , i , 0 ) ) ;
if ( tblspc_oid )
update_tablespace_symlink ( tblspc_oid , PQgetvalue ( res , i , 1 ) ) ;
}
}
PQclear ( res ) ;
/*
@ -1696,6 +1848,7 @@ main(int argc, char **argv)
{ " format " , required_argument , NULL , ' F ' } ,
{ " checkpoint " , required_argument , NULL , ' c ' } ,
{ " write-recovery-conf " , no_argument , NULL , ' R ' } ,
{ " tablespace-mapping " , required_argument , NULL , ' T ' } ,
{ " xlog " , no_argument , NULL , ' x ' } ,
{ " xlog-method " , required_argument , NULL , ' X ' } ,
{ " gzip " , no_argument , NULL , ' z ' } ,
@ -1735,7 +1888,7 @@ main(int argc, char **argv)
}
}
while ( ( c = getopt_long ( argc , argv , " D:F:RxX:l:zZ:d:c:h:p:U:s:wWvP " ,
while ( ( c = getopt_long ( argc , argv , " D:F:RT: xX:l:zZ:d:c:h:p:U:s:wWvP " ,
long_options , & option_index ) ) ! = - 1 )
{
switch ( c )
@ -1759,6 +1912,9 @@ main(int argc, char **argv)
case ' R ' :
writerecoveryconf = true ;
break ;
case ' T ' :
tablespace_list_append ( optarg ) ;
break ;
case ' x ' :
if ( includewal )
{