LCOV - code coverage report
Current view: top level - src/bin/pg_rewind - filemap.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 221 273 81.0 %
Date: 2020-06-03 11:07:14 Functions: 13 14 92.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * filemap.c
       4             :  *    A data structure for keeping track of files that have changed.
       5             :  *
       6             :  * Copyright (c) 2013-2020, PostgreSQL Global Development Group
       7             :  *
       8             :  *-------------------------------------------------------------------------
       9             :  */
      10             : 
      11             : #include "postgres_fe.h"
      12             : 
      13             : #include <sys/stat.h>
      14             : #include <unistd.h>
      15             : 
      16             : #include "catalog/pg_tablespace_d.h"
      17             : #include "common/string.h"
      18             : #include "datapagemap.h"
      19             : #include "filemap.h"
      20             : #include "pg_rewind.h"
      21             : #include "storage/fd.h"
      22             : 
      23             : filemap_t  *filemap = NULL;
      24             : 
      25             : static bool isRelDataFile(const char *path);
      26             : static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
      27             :                          BlockNumber segno);
      28             : static int  path_cmp(const void *a, const void *b);
      29             : static int  final_filemap_cmp(const void *a, const void *b);
      30             : static void filemap_list_to_array(filemap_t *map);
      31             : static bool check_file_excluded(const char *path, bool is_source);
      32             : 
      33             : /*
      34             :  * Definition of one element part of an exclusion list, used to exclude
      35             :  * contents when rewinding.  "name" is the name of the file or path to
      36             :  * check for exclusion.  If "match_prefix" is true, any items matching
      37             :  * the name as prefix are excluded.
      38             :  */
      39             : struct exclude_list_item
      40             : {
      41             :     const char *name;
      42             :     bool        match_prefix;
      43             : };
      44             : 
      45             : /*
      46             :  * The contents of these directories are removed or recreated during server
      47             :  * start so they are not included in data processed by pg_rewind.
      48             :  *
      49             :  * Note: those lists should be kept in sync with what basebackup.c provides.
      50             :  * Some of the values, contrary to what basebackup.c uses, are hardcoded as
      51             :  * they are defined in backend-only headers.  So this list is maintained
      52             :  * with a best effort in mind.
      53             :  */
      54             : static const char *excludeDirContents[] =
      55             : {
      56             :     /*
      57             :      * Skip temporary statistics files. PG_STAT_TMP_DIR must be skipped even
      58             :      * when stats_temp_directory is set because PGSS_TEXT_FILE is always
      59             :      * created there.
      60             :      */
      61             :     "pg_stat_tmp",                /* defined as PG_STAT_TMP_DIR */
      62             : 
      63             :     /*
      64             :      * It is generally not useful to backup the contents of this directory
      65             :      * even if the intention is to restore to another master. See backup.sgml
      66             :      * for a more detailed description.
      67             :      */
      68             :     "pg_replslot",
      69             : 
      70             :     /* Contents removed on startup, see dsm_cleanup_for_mmap(). */
      71             :     "pg_dynshmem",                /* defined as PG_DYNSHMEM_DIR */
      72             : 
      73             :     /* Contents removed on startup, see AsyncShmemInit(). */
      74             :     "pg_notify",
      75             : 
      76             :     /*
      77             :      * Old contents are loaded for possible debugging but are not required for
      78             :      * normal operation, see SerialInit().
      79             :      */
      80             :     "pg_serial",
      81             : 
      82             :     /* Contents removed on startup, see DeleteAllExportedSnapshotFiles(). */
      83             :     "pg_snapshots",
      84             : 
      85             :     /* Contents zeroed on startup, see StartupSUBTRANS(). */
      86             :     "pg_subtrans",
      87             : 
      88             :     /* end of list */
      89             :     NULL
      90             : };
      91             : 
      92             : /*
      93             :  * List of files excluded from filemap processing.   Files are excluded
      94             :  * if their prefix match.
      95             :  */
      96             : static const struct exclude_list_item excludeFiles[] =
      97             : {
      98             :     /* Skip auto conf temporary file. */
      99             :     {"postgresql.auto.conf.tmp", false},  /* defined as PG_AUTOCONF_FILENAME */
     100             : 
     101             :     /* Skip current log file temporary file */
     102             :     {"current_logfiles.tmp", false},  /* defined as
     103             :                                          * LOG_METAINFO_DATAFILE_TMP */
     104             : 
     105             :     /* Skip relation cache because it is rebuilt on startup */
     106             :     {"pg_internal.init", true}, /* defined as RELCACHE_INIT_FILENAME */
     107             : 
     108             :     /*
     109             :      * If there's a backup_label or tablespace_map file, it belongs to a
     110             :      * backup started by the user with pg_start_backup().  It is *not* correct
     111             :      * for this backup.  Our backup_label is written later on separately.
     112             :      */
     113             :     {"backup_label", false},  /* defined as BACKUP_LABEL_FILE */
     114             :     {"tablespace_map", false},    /* defined as TABLESPACE_MAP */
     115             : 
     116             :     /*
     117             :      * If there's a backup_manifest, it belongs to a backup that was used to
     118             :      * start this server. It is *not* correct for this backup. Our
     119             :      * backup_manifest is injected into the backup separately if users want
     120             :      * it.
     121             :      */
     122             :     {"backup_manifest", false},
     123             : 
     124             :     {"postmaster.pid", false},
     125             :     {"postmaster.opts", false},
     126             : 
     127             :     /* end of list */
     128             :     {NULL, false}
     129             : };
     130             : 
     131             : /*
     132             :  * Create a new file map (stored in the global pointer "filemap").
     133             :  */
     134             : void
     135          20 : filemap_create(void)
     136             : {
     137             :     filemap_t  *map;
     138             : 
     139          20 :     map = pg_malloc(sizeof(filemap_t));
     140          20 :     map->first = map->last = NULL;
     141          20 :     map->nlist = 0;
     142          20 :     map->array = NULL;
     143          20 :     map->narray = 0;
     144             : 
     145             :     Assert(filemap == NULL);
     146          20 :     filemap = map;
     147          20 : }
     148             : 
     149             : /*
     150             :  * Callback for processing source file list.
     151             :  *
     152             :  * This is called once for every file in the source server. We decide what
     153             :  * action needs to be taken for the file, depending on whether the file
     154             :  * exists in the target and whether the size matches.
     155             :  */
     156             : void
     157       23040 : process_source_file(const char *path, file_type_t type, size_t newsize,
     158             :                     const char *link_target)
     159             : {
     160             :     bool        exists;
     161             :     char        localpath[MAXPGPATH];
     162             :     struct stat statbuf;
     163       23040 :     filemap_t  *map = filemap;
     164       23040 :     file_action_t action = FILE_ACTION_NONE;
     165       23040 :     size_t      oldsize = 0;
     166             :     file_entry_t *entry;
     167             : 
     168             :     Assert(map->array == NULL);
     169             : 
     170             :     /*
     171             :      * Skip any files matching the exclusion filters. This has the effect to
     172             :      * remove all those files on the target.
     173             :      */
     174       23040 :     if (check_file_excluded(path, true))
     175         116 :         return;
     176             : 
     177             :     /*
     178             :      * Pretend that pg_wal is a directory, even if it's really a symlink. We
     179             :      * don't want to mess with the symlink itself, nor complain if it's a
     180             :      * symlink in source but not in target or vice versa.
     181             :      */
     182       22924 :     if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
     183           0 :         type = FILE_TYPE_DIRECTORY;
     184             : 
     185             :     /*
     186             :      * Skip temporary files, .../pgsql_tmp/... and .../pgsql_tmp.* in source.
     187             :      * This has the effect that all temporary files in the destination will be
     188             :      * removed.
     189             :      */
     190       22924 :     if (strstr(path, "/" PG_TEMP_FILE_PREFIX) != NULL)
     191           0 :         return;
     192       22924 :     if (strstr(path, "/" PG_TEMP_FILES_DIR "/") != NULL)
     193           0 :         return;
     194             : 
     195             :     /*
     196             :      * sanity check: a filename that looks like a data file better be a
     197             :      * regular file
     198             :      */
     199       22924 :     if (type != FILE_TYPE_REGULAR && isRelDataFile(path))
     200           0 :         pg_fatal("data file \"%s\" in source is not a regular file", path);
     201             : 
     202       22924 :     snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
     203             : 
     204             :     /* Does the corresponding file exist in the target data dir? */
     205       22924 :     if (lstat(localpath, &statbuf) < 0)
     206             :     {
     207        1280 :         if (errno != ENOENT)
     208           0 :             pg_fatal("could not stat file \"%s\": %m",
     209             :                      localpath);
     210             : 
     211        1280 :         exists = false;
     212             :     }
     213             :     else
     214       21644 :         exists = true;
     215             : 
     216       22924 :     switch (type)
     217             :     {
     218         528 :         case FILE_TYPE_DIRECTORY:
     219         528 :             if (exists && !S_ISDIR(statbuf.st_mode) && strcmp(path, "pg_wal") != 0)
     220             :             {
     221             :                 /* it's a directory in source, but not in target. Strange.. */
     222           0 :                 pg_fatal("\"%s\" is not a directory", localpath);
     223             :             }
     224             : 
     225         528 :             if (!exists)
     226          14 :                 action = FILE_ACTION_CREATE;
     227             :             else
     228         514 :                 action = FILE_ACTION_NONE;
     229         528 :             oldsize = 0;
     230         528 :             break;
     231             : 
     232           0 :         case FILE_TYPE_SYMLINK:
     233           0 :             if (exists &&
     234             : #ifndef WIN32
     235           0 :                 !S_ISLNK(statbuf.st_mode)
     236             : #else
     237             :                 !pgwin32_is_junction(localpath)
     238             : #endif
     239             :                 )
     240             :             {
     241             :                 /*
     242             :                  * It's a symbolic link in source, but not in target.
     243             :                  * Strange..
     244             :                  */
     245           0 :                 pg_fatal("\"%s\" is not a symbolic link", localpath);
     246             :             }
     247             : 
     248           0 :             if (!exists)
     249           0 :                 action = FILE_ACTION_CREATE;
     250             :             else
     251           0 :                 action = FILE_ACTION_NONE;
     252           0 :             oldsize = 0;
     253           0 :             break;
     254             : 
     255       22396 :         case FILE_TYPE_REGULAR:
     256       22396 :             if (exists && !S_ISREG(statbuf.st_mode))
     257           0 :                 pg_fatal("\"%s\" is not a regular file", localpath);
     258             : 
     259       22396 :             if (!exists || !isRelDataFile(path))
     260             :             {
     261             :                 /*
     262             :                  * File exists in source, but not in target. Or it's a
     263             :                  * non-data file that we have no special processing for. Copy
     264             :                  * it in toto.
     265             :                  *
     266             :                  * An exception: PG_VERSIONs should be identical, but avoid
     267             :                  * overwriting it for paranoia.
     268             :                  */
     269       13648 :                 if (pg_str_endswith(path, "PG_VERSION"))
     270             :                 {
     271          92 :                     action = FILE_ACTION_NONE;
     272          92 :                     oldsize = statbuf.st_size;
     273             :                 }
     274             :                 else
     275             :                 {
     276        6732 :                     action = FILE_ACTION_COPY;
     277        6732 :                     oldsize = 0;
     278             :                 }
     279             :             }
     280             :             else
     281             :             {
     282             :                 /*
     283             :                  * It's a data file that exists in both.
     284             :                  *
     285             :                  * If it's larger in target, we can truncate it. There will
     286             :                  * also be a WAL record of the truncation in the source
     287             :                  * system, so WAL replay would eventually truncate the target
     288             :                  * too, but we might as well do it now.
     289             :                  *
     290             :                  * If it's smaller in the target, it means that it has been
     291             :                  * truncated in the target, or enlarged in the source, or
     292             :                  * both. If it was truncated in the target, we need to copy
     293             :                  * the missing tail from the source system. If it was enlarged
     294             :                  * in the source system, there will be WAL records in the
     295             :                  * source system for the new blocks, so we wouldn't need to
     296             :                  * copy them here. But we don't know which scenario we're
     297             :                  * dealing with, and there's no harm in copying the missing
     298             :                  * blocks now, so do it now.
     299             :                  *
     300             :                  * If it's the same size, do nothing here. Any blocks modified
     301             :                  * in the target will be copied based on parsing the target
     302             :                  * system's WAL, and any blocks modified in the source will be
     303             :                  * updated after rewinding, when the source system's WAL is
     304             :                  * replayed.
     305             :                  */
     306       15572 :                 oldsize = statbuf.st_size;
     307       15572 :                 if (oldsize < newsize)
     308           8 :                     action = FILE_ACTION_COPY_TAIL;
     309       15564 :                 else if (oldsize > newsize)
     310           8 :                     action = FILE_ACTION_TRUNCATE;
     311             :                 else
     312       15556 :                     action = FILE_ACTION_NONE;
     313             :             }
     314       22396 :             break;
     315             :     }
     316             : 
     317             :     /* Create a new entry for this file */
     318       22924 :     entry = pg_malloc(sizeof(file_entry_t));
     319       22924 :     entry->path = pg_strdup(path);
     320       22924 :     entry->type = type;
     321       22924 :     entry->action = action;
     322       22924 :     entry->oldsize = oldsize;
     323       22924 :     entry->newsize = newsize;
     324       22924 :     entry->link_target = link_target ? pg_strdup(link_target) : NULL;
     325       22924 :     entry->next = NULL;
     326       22924 :     entry->pagemap.bitmap = NULL;
     327       22924 :     entry->pagemap.bitmapsize = 0;
     328       22924 :     entry->isrelfile = isRelDataFile(path);
     329             : 
     330       22924 :     if (map->last)
     331             :     {
     332       22904 :         map->last->next = entry;
     333       22904 :         map->last = entry;
     334             :     }
     335             :     else
     336          20 :         map->first = map->last = entry;
     337       22924 :     map->nlist++;
     338             : }
     339             : 
     340             : /*
     341             :  * Callback for processing target file list.
     342             :  *
     343             :  * All source files must be already processed before calling this. This only
     344             :  * marks target data directory's files that didn't exist in the source for
     345             :  * deletion.
     346             :  */
     347             : void
     348       22968 : process_target_file(const char *path, file_type_t type, size_t oldsize,
     349             :                     const char *link_target)
     350             : {
     351             :     bool        exists;
     352             :     char        localpath[MAXPGPATH];
     353             :     struct stat statbuf;
     354             :     file_entry_t key;
     355             :     file_entry_t *key_ptr;
     356       22968 :     filemap_t  *map = filemap;
     357             :     file_entry_t *entry;
     358             : 
     359             :     /*
     360             :      * Do not apply any exclusion filters here.  This has advantage to remove
     361             :      * from the target data folder all paths which have been filtered out from
     362             :      * the source data folder when processing the source files.
     363             :      */
     364             : 
     365       22968 :     snprintf(localpath, sizeof(localpath), "%s/%s", datadir_target, path);
     366       22968 :     if (lstat(localpath, &statbuf) < 0)
     367             :     {
     368           0 :         if (errno != ENOENT)
     369           0 :             pg_fatal("could not stat file \"%s\": %m",
     370             :                      localpath);
     371             : 
     372           0 :         exists = false;
     373             :     }
     374             : 
     375       22968 :     if (map->array == NULL)
     376             :     {
     377             :         /* on first call, initialize lookup array */
     378          20 :         if (map->nlist == 0)
     379             :         {
     380             :             /* should not happen */
     381           0 :             pg_fatal("source file list is empty");
     382             :         }
     383             : 
     384          20 :         filemap_list_to_array(map);
     385             : 
     386             :         Assert(map->array != NULL);
     387             : 
     388          20 :         qsort(map->array, map->narray, sizeof(file_entry_t *), path_cmp);
     389             :     }
     390             : 
     391             :     /*
     392             :      * Like in process_source_file, pretend that xlog is always a  directory.
     393             :      */
     394       22968 :     if (strcmp(path, "pg_wal") == 0 && type == FILE_TYPE_SYMLINK)
     395           4 :         type = FILE_TYPE_DIRECTORY;
     396             : 
     397       22968 :     key.path = (char *) path;
     398       22968 :     key_ptr = &key;
     399       22968 :     exists = (bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
     400             :                       path_cmp) != NULL);
     401             : 
     402             :     /* Remove any file or folder that doesn't exist in the source system. */
     403       22968 :     if (!exists)
     404             :     {
     405        1324 :         entry = pg_malloc(sizeof(file_entry_t));
     406        1324 :         entry->path = pg_strdup(path);
     407        1324 :         entry->type = type;
     408        1324 :         entry->action = FILE_ACTION_REMOVE;
     409        1324 :         entry->oldsize = oldsize;
     410        1324 :         entry->newsize = 0;
     411        1324 :         entry->link_target = link_target ? pg_strdup(link_target) : NULL;
     412        1324 :         entry->next = NULL;
     413        1324 :         entry->pagemap.bitmap = NULL;
     414        1324 :         entry->pagemap.bitmapsize = 0;
     415        1324 :         entry->isrelfile = isRelDataFile(path);
     416             : 
     417        1324 :         if (map->last == NULL)
     418          20 :             map->first = entry;
     419             :         else
     420        1304 :             map->last->next = entry;
     421        1324 :         map->last = entry;
     422        1324 :         map->nlist++;
     423             :     }
     424             :     else
     425             :     {
     426             :         /*
     427             :          * We already handled all files that exist in the source system in
     428             :          * process_source_file().
     429             :          */
     430             :     }
     431       22968 : }
     432             : 
     433             : /*
     434             :  * This callback gets called while we read the WAL in the target, for every
     435             :  * block that have changed in the target system. It makes note of all the
     436             :  * changed blocks in the pagemap of the file.
     437             :  */
     438             : void
     439      163010 : process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
     440             : {
     441             :     char       *path;
     442             :     file_entry_t key;
     443             :     file_entry_t *key_ptr;
     444             :     file_entry_t *entry;
     445             :     BlockNumber blkno_inseg;
     446             :     int         segno;
     447      163010 :     filemap_t  *map = filemap;
     448             :     file_entry_t **e;
     449             : 
     450             :     Assert(map->array);
     451             : 
     452      163010 :     segno = blkno / RELSEG_SIZE;
     453      163010 :     blkno_inseg = blkno % RELSEG_SIZE;
     454             : 
     455      163010 :     path = datasegpath(rnode, forknum, segno);
     456             : 
     457      163010 :     key.path = (char *) path;
     458      163010 :     key_ptr = &key;
     459             : 
     460      163010 :     e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
     461             :                 path_cmp);
     462      163010 :     if (e)
     463      162850 :         entry = *e;
     464             :     else
     465         160 :         entry = NULL;
     466      163010 :     pfree(path);
     467             : 
     468      163010 :     if (entry)
     469             :     {
     470             :         Assert(entry->isrelfile);
     471             : 
     472      162850 :         switch (entry->action)
     473             :         {
     474       80242 :             case FILE_ACTION_NONE:
     475             :             case FILE_ACTION_TRUNCATE:
     476             :                 /* skip if we're truncating away the modified block anyway */
     477       80242 :                 if ((blkno_inseg + 1) * BLCKSZ <= entry->newsize)
     478        1314 :                     datapagemap_add(&entry->pagemap, blkno_inseg);
     479       80242 :                 break;
     480             : 
     481       82608 :             case FILE_ACTION_COPY_TAIL:
     482             : 
     483             :                 /*
     484             :                  * skip the modified block if it is part of the "tail" that
     485             :                  * we're copying anyway.
     486             :                  */
     487       82608 :                 if ((blkno_inseg + 1) * BLCKSZ <= entry->oldsize)
     488         904 :                     datapagemap_add(&entry->pagemap, blkno_inseg);
     489       82608 :                 break;
     490             : 
     491           0 :             case FILE_ACTION_COPY:
     492             :             case FILE_ACTION_REMOVE:
     493           0 :                 break;
     494             : 
     495           0 :             case FILE_ACTION_CREATE:
     496           0 :                 pg_fatal("unexpected page modification for directory or symbolic link \"%s\"", entry->path);
     497             :         }
     498      162850 :     }
     499             :     else
     500             :     {
     501             :         /*
     502             :          * If we don't have any record of this file in the file map, it means
     503             :          * that it's a relation that doesn't exist in the source system, and
     504             :          * it was subsequently removed in the target system, too. We can
     505             :          * safely ignore it.
     506             :          */
     507             :     }
     508      163010 : }
     509             : 
     510             : /*
     511             :  * Is this the path of file that pg_rewind can skip copying?
     512             :  */
     513             : static bool
     514       23040 : check_file_excluded(const char *path, bool is_source)
     515             : {
     516             :     char        localpath[MAXPGPATH];
     517             :     int         excludeIdx;
     518             :     const char *filename;
     519             : 
     520             :     /* check individual files... */
     521      207024 :     for (excludeIdx = 0; excludeFiles[excludeIdx].name != NULL; excludeIdx++)
     522             :     {
     523      184072 :         int         cmplen = strlen(excludeFiles[excludeIdx].name);
     524             : 
     525      184072 :         filename = last_dir_separator(path);
     526      184072 :         if (filename == NULL)
     527        4080 :             filename = path;
     528             :         else
     529      179992 :             filename++;
     530             : 
     531      184072 :         if (!excludeFiles[excludeIdx].match_prefix)
     532      161032 :             cmplen++;
     533      184072 :         if (strncmp(filename, excludeFiles[excludeIdx].name, cmplen) == 0)
     534             :         {
     535          88 :             if (is_source)
     536          88 :                 pg_log_debug("entry \"%s\" excluded from source file list",
     537             :                              path);
     538             :             else
     539           0 :                 pg_log_debug("entry \"%s\" excluded from target file list",
     540             :                              path);
     541          88 :             return true;
     542             :         }
     543             :     }
     544             : 
     545             :     /*
     546             :      * ... And check some directories.  Note that this includes any contents
     547             :      * within the directories themselves.
     548             :      */
     549      183540 :     for (excludeIdx = 0; excludeDirContents[excludeIdx] != NULL; excludeIdx++)
     550             :     {
     551      160616 :         snprintf(localpath, sizeof(localpath), "%s/",
     552             :                  excludeDirContents[excludeIdx]);
     553      160616 :         if (strstr(path, localpath) == path)
     554             :         {
     555          28 :             if (is_source)
     556          28 :                 pg_log_debug("entry \"%s\" excluded from source file list",
     557             :                              path);
     558             :             else
     559           0 :                 pg_log_debug("entry \"%s\" excluded from target file list",
     560             :                              path);
     561          28 :             return true;
     562             :         }
     563             :     }
     564             : 
     565       22924 :     return false;
     566             : }
     567             : 
     568             : /*
     569             :  * Convert the linked list of entries in map->first/last to the array,
     570             :  * map->array.
     571             :  */
     572             : static void
     573          40 : filemap_list_to_array(filemap_t *map)
     574             : {
     575             :     int         narray;
     576             :     file_entry_t *entry,
     577             :                *next;
     578             : 
     579          40 :     map->array = (file_entry_t **)
     580          40 :         pg_realloc(map->array,
     581          40 :                    (map->nlist + map->narray) * sizeof(file_entry_t *));
     582             : 
     583          40 :     narray = map->narray;
     584       24288 :     for (entry = map->first; entry != NULL; entry = next)
     585             :     {
     586       24248 :         map->array[narray++] = entry;
     587       24248 :         next = entry->next;
     588       24248 :         entry->next = NULL;
     589             :     }
     590             :     Assert(narray == map->nlist + map->narray);
     591          40 :     map->narray = narray;
     592          40 :     map->nlist = 0;
     593          40 :     map->first = map->last = NULL;
     594          40 : }
     595             : 
     596             : void
     597          20 : filemap_finalize(void)
     598             : {
     599          20 :     filemap_t  *map = filemap;
     600             : 
     601          20 :     filemap_list_to_array(map);
     602          20 :     qsort(map->array, map->narray, sizeof(file_entry_t *),
     603             :           final_filemap_cmp);
     604          20 : }
     605             : 
     606             : static const char *
     607        8190 : action_to_str(file_action_t action)
     608             : {
     609        8190 :     switch (action)
     610             :     {
     611         104 :         case FILE_ACTION_NONE:
     612         104 :             return "NONE";
     613        6732 :         case FILE_ACTION_COPY:
     614        6732 :             return "COPY";
     615           8 :         case FILE_ACTION_TRUNCATE:
     616           8 :             return "TRUNCATE";
     617           8 :         case FILE_ACTION_COPY_TAIL:
     618           8 :             return "COPY_TAIL";
     619          14 :         case FILE_ACTION_CREATE:
     620          14 :             return "CREATE";
     621        1324 :         case FILE_ACTION_REMOVE:
     622        1324 :             return "REMOVE";
     623             : 
     624           0 :         default:
     625           0 :             return "unknown";
     626             :     }
     627             : }
     628             : 
     629             : /*
     630             :  * Calculate the totals needed for progress reports.
     631             :  */
     632             : void
     633           0 : calculate_totals(void)
     634             : {
     635             :     file_entry_t *entry;
     636             :     int         i;
     637           0 :     filemap_t  *map = filemap;
     638             : 
     639           0 :     map->total_size = 0;
     640           0 :     map->fetch_size = 0;
     641             : 
     642           0 :     for (i = 0; i < map->narray; i++)
     643             :     {
     644           0 :         entry = map->array[i];
     645             : 
     646           0 :         if (entry->type != FILE_TYPE_REGULAR)
     647           0 :             continue;
     648             : 
     649           0 :         map->total_size += entry->newsize;
     650             : 
     651           0 :         if (entry->action == FILE_ACTION_COPY)
     652             :         {
     653           0 :             map->fetch_size += entry->newsize;
     654           0 :             continue;
     655             :         }
     656             : 
     657           0 :         if (entry->action == FILE_ACTION_COPY_TAIL)
     658           0 :             map->fetch_size += (entry->newsize - entry->oldsize);
     659             : 
     660           0 :         if (entry->pagemap.bitmapsize > 0)
     661             :         {
     662             :             datapagemap_iterator_t *iter;
     663             :             BlockNumber blk;
     664             : 
     665           0 :             iter = datapagemap_iterate(&entry->pagemap);
     666           0 :             while (datapagemap_next(iter, &blk))
     667           0 :                 map->fetch_size += BLCKSZ;
     668             : 
     669           0 :             pg_free(iter);
     670             :         }
     671             :     }
     672           0 : }
     673             : 
     674             : void
     675          20 : print_filemap(void)
     676             : {
     677          20 :     filemap_t  *map = filemap;
     678             :     file_entry_t *entry;
     679             :     int         i;
     680             : 
     681       24268 :     for (i = 0; i < map->narray; i++)
     682             :     {
     683       24248 :         entry = map->array[i];
     684       24248 :         if (entry->action != FILE_ACTION_NONE ||
     685       16162 :             entry->pagemap.bitmapsize > 0)
     686             :         {
     687        8190 :             pg_log_debug("%s (%s)", entry->path,
     688             :                          action_to_str(entry->action));
     689             : 
     690        8190 :             if (entry->pagemap.bitmapsize > 0)
     691         120 :                 datapagemap_print(&entry->pagemap);
     692             :         }
     693             :     }
     694          20 :     fflush(stdout);
     695          20 : }
     696             : 
     697             : /*
     698             :  * Does it look like a relation data file?
     699             :  *
     700             :  * For our purposes, only files belonging to the main fork are considered
     701             :  * relation files. Other forks are always copied in toto, because we cannot
     702             :  * reliably track changes to them, because WAL only contains block references
     703             :  * for the main fork.
     704             :  */
     705             : static bool
     706       45906 : isRelDataFile(const char *path)
     707             : {
     708             :     RelFileNode rnode;
     709             :     unsigned int segNo;
     710             :     int         nmatch;
     711             :     bool        matched;
     712             : 
     713             :     /*----
     714             :      * Relation data files can be in one of the following directories:
     715             :      *
     716             :      * global/
     717             :      *      shared relations
     718             :      *
     719             :      * base/<db oid>/
     720             :      *      regular relations, default tablespace
     721             :      *
     722             :      * pg_tblspc/<tblspc oid>/<tblspc version>/
     723             :      *      within a non-default tablespace (the name of the directory
     724             :      *      depends on version)
     725             :      *
     726             :      * And the relation data files themselves have a filename like:
     727             :      *
     728             :      * <oid>.<segment number>
     729             :      *
     730             :      *----
     731             :      */
     732       45906 :     rnode.spcNode = InvalidOid;
     733       45906 :     rnode.dbNode = InvalidOid;
     734       45906 :     rnode.relNode = InvalidOid;
     735       45906 :     segNo = 0;
     736       45906 :     matched = false;
     737             : 
     738       45906 :     nmatch = sscanf(path, "global/%u.%u", &rnode.relNode, &segNo);
     739       45906 :     if (nmatch == 1 || nmatch == 2)
     740             :     {
     741        2200 :         rnode.spcNode = GLOBALTABLESPACE_OID;
     742        2200 :         rnode.dbNode = 0;
     743        2200 :         matched = true;
     744             :     }
     745             :     else
     746             :     {
     747       43706 :         nmatch = sscanf(path, "base/%u/%u.%u",
     748             :                         &rnode.dbNode, &rnode.relNode, &segNo);
     749       43706 :         if (nmatch == 2 || nmatch == 3)
     750             :         {
     751       41552 :             rnode.spcNode = DEFAULTTABLESPACE_OID;
     752       41552 :             matched = true;
     753             :         }
     754             :         else
     755             :         {
     756        2154 :             nmatch = sscanf(path, "pg_tblspc/%u/" TABLESPACE_VERSION_DIRECTORY "/%u/%u.%u",
     757             :                             &rnode.spcNode, &rnode.dbNode, &rnode.relNode,
     758             :                             &segNo);
     759        2154 :             if (nmatch == 3 || nmatch == 4)
     760           0 :                 matched = true;
     761             :         }
     762             :     }
     763             : 
     764             :     /*
     765             :      * The sscanf tests above can match files that have extra characters at
     766             :      * the end. To eliminate such cases, cross-check that GetRelationPath
     767             :      * creates the exact same filename, when passed the RelFileNode
     768             :      * information we extracted from the filename.
     769             :      */
     770       45906 :     if (matched)
     771             :     {
     772       43752 :         char       *check_path = datasegpath(rnode, MAIN_FORKNUM, segNo);
     773             : 
     774       43752 :         if (strcmp(check_path, path) != 0)
     775       10880 :             matched = false;
     776             : 
     777       43752 :         pfree(check_path);
     778             :     }
     779             : 
     780       45906 :     return matched;
     781             : }
     782             : 
     783             : /*
     784             :  * A helper function to create the path of a relation file and segment.
     785             :  *
     786             :  * The returned path is palloc'd
     787             :  */
     788             : static char *
     789      206762 : datasegpath(RelFileNode rnode, ForkNumber forknum, BlockNumber segno)
     790             : {
     791             :     char       *path;
     792             :     char       *segpath;
     793             : 
     794      206762 :     path = relpathperm(rnode, forknum);
     795      206762 :     if (segno > 0)
     796             :     {
     797           0 :         segpath = psprintf("%s.%u", path, segno);
     798           0 :         pfree(path);
     799           0 :         return segpath;
     800             :     }
     801             :     else
     802      206762 :         return path;
     803             : }
     804             : 
     805             : static int
     806     1861254 : path_cmp(const void *a, const void *b)
     807             : {
     808     1861254 :     file_entry_t *fa = *((file_entry_t **) a);
     809     1861254 :     file_entry_t *fb = *((file_entry_t **) b);
     810             : 
     811     1861254 :     return strcmp(fa->path, fb->path);
     812             : }
     813             : 
     814             : /*
     815             :  * In the final stage, the filemap is sorted so that removals come last.
     816             :  * From disk space usage point of view, it would be better to do removals
     817             :  * first, but for now, safety first. If a whole directory is deleted, all
     818             :  * files and subdirectories inside it need to removed first. On creation,
     819             :  * parent directory needs to be created before files and directories inside
     820             :  * it. To achieve that, the file_action_t enum is ordered so that we can
     821             :  * just sort on that first. Furthermore, sort REMOVE entries in reverse
     822             :  * path order, so that "foo/bar" subdirectory is removed before "foo".
     823             :  */
     824             : static int
     825      275780 : final_filemap_cmp(const void *a, const void *b)
     826             : {
     827      275780 :     file_entry_t *fa = *((file_entry_t **) a);
     828      275780 :     file_entry_t *fb = *((file_entry_t **) b);
     829             : 
     830      275780 :     if (fa->action > fb->action)
     831       10202 :         return 1;
     832      265578 :     if (fa->action < fb->action)
     833       13146 :         return -1;
     834             : 
     835      252432 :     if (fa->action == FILE_ACTION_REMOVE)
     836       10658 :         return strcmp(fb->path, fa->path);
     837             :     else
     838      241774 :         return strcmp(fa->path, fb->path);
     839             : }

Generated by: LCOV version 1.13