/* print mkdirs.sh */ void dir_print_mkdirs_sh(FILE *out, const parray *files, const char *root) { int i; for (i = 0; i < parray_num(files); i++) { pgFile *file = (pgFile *) parray_get(files, i); if (S_ISDIR(file->mode)) { if (strstr(file->path, root) == file->path) { fprintf(out, "mkdir -m 700 -p %s\n", file->path + strlen(root) + 1); } else { fprintf(out, "mkdir -m 700 -p %s\n", file->path); } } } fprintf(out, "\n"); for (i = 0; i < parray_num(files); i++) { pgFile *file = (pgFile *) parray_get(files, i); if (S_ISLNK(file->mode)) { fprintf(out, "rm -f %s\n", file->path + strlen(root) + 1); fprintf(out, "ln -s %s %s\n", file->linked, file->path + strlen(root) + 1); } } }
/* * Validate files in the backup with size or CRC. */ static bool pgBackupValidateFiles(parray *files, const char *root, bool size_only) { int i; for (i = 0; i < parray_num(files); i++) { struct stat st; pgFile *file = (pgFile *) parray_get(files, i); if (interrupted) elog(ERROR_INTERRUPTED, _("interrupted during validate")); /* skipped backup while incremental backup */ if (file->write_size == BYTES_INVALID || !S_ISREG(file->mode)) continue; /* print progress */ elog(LOG, _("(%d/%lu) %s"), i + 1, (unsigned long) parray_num(files), get_relative_path(file->path, root)); /* always validate file size */ if (stat(file->path, &st) == -1) { if (errno == ENOENT) elog(WARNING, _("backup file \"%s\" vanished"), file->path); else elog(ERROR_SYSTEM, _("can't stat backup file \"%s\": %s"), get_relative_path(file->path, root), strerror(errno)); return false; } if (file->write_size != st.st_size) { elog(WARNING, _("size of backup file \"%s\" must be %lu but %lu"), get_relative_path(file->path, root), (unsigned long) file->write_size, (unsigned long) st.st_size); return false; } /* validate CRC too */ if (!size_only) { pg_crc32 crc; crc = pgFileGetCRC(file); if (crc != file->crc) { elog(WARNING, _("CRC of backup file \"%s\" must be %X but %X"), get_relative_path(file->path, root), file->crc, crc); return false; } } } return true; }
/* print file list */ void dir_print_file_list(FILE *out, const parray *files, const char *root, const char *prefix) { int i; int root_len = 0; /* calculate length of root directory portion */ if (root) { root_len = strlen(root); if (root[root_len - 1] != '/') root_len++; } /* print each file in the list */ for (i = 0; i < parray_num(files); i++) { pgFile *file = (pgFile *)parray_get(files, i); char path[MAXPGPATH]; char *ptr = file->path; char type; /* omit root directory portion */ if (root && strstr(ptr, root) == ptr) ptr = JoinPathEnd(ptr, root); /* append prefix if not NULL */ if (prefix) join_path_components(path, prefix, ptr); else strcpy(path, ptr); if (S_ISREG(file->mode) && file->is_datafile) type = 'F'; else if (S_ISREG(file->mode) && !file->is_datafile) type = 'f'; else if (S_ISDIR(file->mode)) type = 'd'; else if (S_ISLNK(file->mode)) type = 'l'; else type = '?'; fprintf(out, "%s %c %lu %u 0%o", path, type, (unsigned long) file->write_size, file->crc, file->mode & (S_IRWXU | S_IRWXG | S_IRWXO)); if (S_ISLNK(file->mode)) fprintf(out, " %s\n", file->linked); else { char timestamp[20]; time2iso(timestamp, 20, file->mtime); fprintf(out, " %s\n", timestamp); } } }
/* * Concatinate two parray. * parray_concat() appends the copy of the content of src to the end of dest. */ parray * parray_concat(parray *dest, const parray *src) { /* expand head array */ parray_expand(dest, dest->used + src->used); /* copy content of src after content of dest */ memcpy(dest->data + dest->used, src->data, src->used * sizeof(void *)); dest->used += parray_num(src); return dest; }
/* * Validate files in the backup and update its status to OK. * If any of files are corrupted, update its stutus to CORRUPT. */ int do_validate(pgBackupRange *range) { int i; parray *backup_list; int ret; bool another_pg_rman = false; ret = catalog_lock(); if (ret == 1) another_pg_rman = true; /* get backup list matches given range */ backup_list = catalog_get_backup_list(range); if(!backup_list) { elog(ERROR_SYSTEM, _("can't process any more.")); } parray_qsort(backup_list, pgBackupCompareId); for (i = 0; i < parray_num(backup_list); i++) { pgBackup *backup = (pgBackup *)parray_get(backup_list, i); /* clean extra backups (switch STATUS to ERROR) */ if(!another_pg_rman && (backup->status == BACKUP_STATUS_RUNNING || backup->status == BACKUP_STATUS_DELETING)) { backup->status = BACKUP_STATUS_ERROR; pgBackupWriteIni(backup); } /* Validate completed backups only. */ if (backup->status != BACKUP_STATUS_DONE) continue; /* validate with CRC value and update status to OK */ pgBackupValidate(backup, false, false, (HAVE_DATABASE(backup))); } /* cleanup */ parray_walk(backup_list, pgBackupFree); parray_free(backup_list); catalog_unlock(); return 0; }
/* copy contents of directory from_root into to_root */ void dir_copy_files(const char *from_root, const char *to_root) { int i; parray *files = parray_new(); /* don't copy root directory */ dir_list_file(files, from_root, NULL, true, false); for (i = 0; i < parray_num(files); i++) { pgFile *file = (pgFile *) parray_get(files, i); if (S_ISDIR(file->mode)) { char to_path[MAXPGPATH]; join_path_components(to_path, to_root, file->path + strlen(from_root) + 1); if (verbose && !check) printf(_("create directory \"%s\"\n"), file->path + strlen(from_root) + 1); if (!check) { dir_create_dir(to_path, DIR_PERMISSION); } continue; } else if(S_ISREG(file->mode)) { if (verbose && !check) printf(_("copy \"%s\"\n"), file->path + strlen(from_root) + 1); if (!check) copy_file(from_root, to_root, file, NO_COMPRESSION); } } /* cleanup */ parray_walk(files, pgFileFree); parray_free(files); }
/* * Delete backup files of the backup and update the status of the backup to * BACKUP_STATUS_DELETED. */ static int pgBackupDeleteFiles(pgBackup *backup) { int i; char path[MAXPGPATH]; char timestamp[20]; parray *files; /* * If the backup was deleted already, there is nothing to do. */ if (backup->status == BACKUP_STATUS_DELETED) return 0; time2iso(timestamp, lengthof(timestamp), backup->start_time); elog(INFO, "delete: %s", timestamp); /* * Update STATUS to BACKUP_STATUS_DELETING in preparation for the case which * the error occurs before deleting all backup files. */ if (!check) { backup->status = BACKUP_STATUS_DELETING; pgBackupWriteIni(backup); } /* list files to be deleted */ files = parray_new(); pgBackupGetPath(backup, path, lengthof(path), DATABASE_DIR); dir_list_file(files, path, NULL, true, true); /* delete leaf node first */ parray_qsort(files, pgFileComparePathDesc); for (i = 0; i < parray_num(files); i++) { pgFile *file = (pgFile *) parray_get(files, i); /* print progress */ elog(LOG, "delete file(%d/%lu) \"%s\"", i + 1, (unsigned long) parray_num(files), file->path); /* skip actual deletion in check mode */ if (!check) { if (remove(file->path)) { elog(WARNING, "can't remove \"%s\": %s", file->path, strerror(errno)); parray_walk(files, pgFileFree); parray_free(files); return 1; } } } /* * After deleting all of the backup files, update STATUS to * BACKUP_STATUS_DELETED. */ if (!check) { backup->status = BACKUP_STATUS_DELETED; pgBackupWriteIni(backup); } parray_walk(files, pgFileFree); parray_free(files); return 0; }
int do_delete(pgBackupRange *range) { int i; int ret; parray *backup_list; bool do_delete = false; XLogRecPtr oldest_lsn = InvalidXLogRecPtr; TimeLineID oldest_tli; /* DATE are always required */ if (!pgBackupRangeIsValid(range)) elog(ERROR, "required delete range option not specified: delete DATE"); /* Lock backup catalog */ ret = catalog_lock(); if (ret == -1) elog(ERROR, "can't lock backup catalog."); else if (ret == 1) elog(ERROR, "another pg_arman is running, stop delete."); /* Get complete list of backups */ backup_list = catalog_get_backup_list(NULL); if (!backup_list) elog(ERROR, "No backup list found, can't process any more."); /* Find backups to be deleted */ for (i = 0; i < parray_num(backup_list); i++) { pgBackup *backup = (pgBackup *) parray_get(backup_list, i); /* delete backup and update status to DELETED */ if (do_delete) { /* check for interrupt */ if (interrupted) elog(ERROR, "interrupted during delete backup"); pgBackupDeleteFiles(backup); continue; } /* Found the latest full backup */ if (backup->backup_mode >= BACKUP_MODE_FULL && backup->status == BACKUP_STATUS_OK && backup->start_time <= range->begin) { do_delete = true; oldest_lsn = backup->start_lsn; oldest_tli = backup->tli; } } /* release catalog lock */ catalog_unlock(); /* cleanup */ parray_walk(backup_list, pgBackupFree); parray_free(backup_list); /* * Delete in archive WAL segments that are not needed anymore. The oldest * segment to be kept is the first segment that the oldest full backup * found around needs to keep. */ if (!XLogRecPtrIsInvalid(oldest_lsn)) { XLogSegNo targetSegNo; char oldestSegmentNeeded[MAXFNAMELEN]; DIR *arcdir; struct dirent *arcde; char wal_file[MAXPGPATH]; int rc; XLByteToSeg(oldest_lsn, targetSegNo); XLogFileName(oldestSegmentNeeded, oldest_tli, targetSegNo); elog(LOG, "Removing segments older than %s", oldestSegmentNeeded); /* * Now is time to do the actual work and to remove all the segments * not needed anymore. */ if ((arcdir = opendir(arclog_path)) != NULL) { while (errno = 0, (arcde = readdir(arcdir)) != NULL) { /* * We ignore the timeline part of the XLOG segment identifiers in * deciding whether a segment is still needed. This ensures that * we won't prematurely remove a segment from a parent timeline. * We could probably be a little more proactive about removing * segments of non-parent timelines, but that would be a whole lot * more complicated. * * We use the alphanumeric sorting property of the filenames to * decide which ones are earlier than the exclusiveCleanupFileName * file. Note that this means files are not removed in the order * they were originally written, in case this worries you. */ if ((IsXLogFileName(arcde->d_name) || IsPartialXLogFileName(arcde->d_name)) && strcmp(arcde->d_name + 8, oldestSegmentNeeded + 8) < 0) { /* * Use the original file name again now, including any * extension that might have been chopped off before testing * the sequence. */ snprintf(wal_file, MAXPGPATH, "%s/%s", arclog_path, arcde->d_name); rc = unlink(wal_file); if (rc != 0) { elog(WARNING, "could not remove file \"%s\": %s", wal_file, strerror(errno)); break; } elog(LOG, "removed WAL segment \"%s\"", wal_file); } } if (errno) elog(WARNING, "could not read archive location \"%s\": %s", arclog_path, strerror(errno)); if (closedir(arcdir)) elog(WARNING, "could not close archive location \"%s\": %s", arclog_path, strerror(errno)); } else elog(WARNING, "could not open archive location \"%s\": %s", arclog_path, strerror(errno)); } return 0; }
/* * Delete backups that are older than KEEP_xxx_DAYS and have more generations * than KEEP_xxx_FILES. */ void pgBackupDelete(int keep_generations, int keep_days) { int i; parray *backup_list; int backup_num; time_t days_threshold = current.start_time - (keep_days * 60 * 60 * 24); if (verbose) { char generations_str[100]; char days_str[100]; if (keep_generations == KEEP_INFINITE) strncpy(generations_str, "INFINITE", lengthof(generations_str)); else snprintf(generations_str, lengthof(generations_str), "%d", keep_generations); if (keep_days == KEEP_INFINITE) strncpy(days_str, "INFINITE", lengthof(days_str)); else snprintf(days_str, lengthof(days_str), "%d", keep_days); elog(LOG, "deleted old backups (generations=%s, days=%s)", generations_str, days_str); } /* Leave if an infinite generation of backups is kept */ if (keep_generations == KEEP_INFINITE && keep_days == KEEP_INFINITE) { elog(LOG, "%s() infinite", __FUNCTION__); return; } /* Get a complete list of backups. */ backup_list = catalog_get_backup_list(NULL); /* Find target backups to be deleted */ backup_num = 0; for (i = 0; i < parray_num(backup_list); i++) { pgBackup *backup = (pgBackup *) parray_get(backup_list, i); int backup_num_evaluate = backup_num; elog(LOG, "%s() %lu", __FUNCTION__, backup->start_time); /* * When a validate full backup was found, we can delete the * backup that is older than it using the number of generations. */ if (backup->backup_mode == BACKUP_MODE_FULL && backup->status == BACKUP_STATUS_OK) backup_num++; /* Evaluate if this backup is eligible for removal */ if (backup_num_evaluate + 1 <= keep_generations && keep_generations != KEEP_INFINITE) { /* Do not include the latest full backup in this count */ elog(LOG, "%s() backup are only %d", __FUNCTION__, backup_num); continue; } else if (backup->start_time >= days_threshold && keep_days != KEEP_INFINITE) { /* * If the start time of the backup is older than the threshold and * there are enough generations of full backups, delete the backup. */ elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__, backup->start_time, days_threshold); continue; } elog(LOG, "%s() %lu is older than %lu", __FUNCTION__, backup->start_time, days_threshold); /* delete backup and update status to DELETED */ pgBackupDeleteFiles(backup); } /* cleanup */ parray_walk(backup_list, pgBackupFree); parray_free(backup_list); }