8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 09:23:03 +01:00
firebird-mirror/src/utilities/nbackup.cpp

1291 lines
36 KiB
C++
Raw Normal View History

/*
* PROGRAM: JRD Access Method
* MODULE: nbackup.cpp
* DESCRIPTION: Command line utility for physical backup/restore
*
* The contents of this file are subject to the Initial
* Developer's Public License Version 1.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
*
* Software distributed under the License is distributed AS IS,
* WITHOUT WARRANTY OF ANY KIND, either express or implied.
* See the License for the specific language governing rights
* and limitations under the License.
*
* The Original Code was created by Nickolay Samofatov
* for the Firebird Open Source RDBMS project.
*
* Copyright (c) 2004 Nickolay Samofatov <nickolay@broadviewsoftware.com>
* and all contributors signed below.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
*
2007-03-06 03:29:48 +01:00
* Adriano dos Santos Fernandes
*
*/
2008-12-05 02:20:14 +01:00
#include "firebird.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../jrd/common.h"
#include "../jrd/db_alias.h"
#include "../jrd/ods.h"
#include "../jrd/nbak.h"
#include "../jrd/gds_proto.h"
#include "../jrd/os/path_utils.h"
#include "../jrd/os/guid.h"
#include "../jrd/ibase.h"
#include "../common/utils_proto.h"
#include "../common/classes/array.h"
#include "../common/classes/ClumpletWriter.h"
#include "../utilities/nbackup/nbk_proto.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
2003-08-12 12:06:14 +02:00
#ifndef O_LARGEFILE
2003-08-11 13:21:21 +02:00
#define O_LARGEFILE 0
#endif
// How much we align memory when reading database header.
// Sector alignment of memory is necessary to use unbuffered IO on Windows.
// Actually, sectors may be bigger than 1K, but let's be consistent with
// JRD regarding the matter for the moment.
const size_t SECTOR_ALIGNMENT = MIN_PAGE_SIZE;
using namespace Firebird;
void usage(UtilSvc* uSvc, const char* message, ...)
2004-02-02 12:02:12 +01:00
{
string msg;
va_list params;
va_start(params, message);
msg.vprintf(message, params);
va_end(params);
2008-12-05 02:20:14 +01:00
if (uSvc->isService())
(Arg::Gds(isc_random) << msg).raise();
fprintf(stderr, "ERROR: %s.\n\n", msg.c_str());
2008-12-05 02:20:14 +01:00
fprintf(stderr,
"Physical Backup Manager Copyright (C) 2004 Firebird development team\n"
2003-09-15 12:14:22 +02:00
" Original idea is of Sean Leyne <sean@broadviewsoftware.com>\n"
" Designed and implemented by Nickolay Samofatov <skidder@bssys.com>\n"
" This work was funded through a grant from BroadView Software, Inc.\n\n"
"Usage: nbackup <options>\n"
"valid options are: \n"
" -L <database> Lock database for filesystem copy\n"
" -N <database> Unlock previously locked database\n"
" -F <database> Fixup database after filesystem copy\n"
" -B <level> <database> [<filename>] Create incremental backup\n"
" -R <database> [<file0> [<file1>...]] Restore incremental backup\n"
" -U <user> User name\n"
" -P <password> Password\n"
2008-12-03 02:05:53 +01:00
" -FE <file> Fetch password from file\n"
" -T Do not run database triggers\n"
" -S Print database size in pages after lock\n"
"Notes:\n"
" <database> may specify database alias\n"
" incremental backups of multi-file databases are not supported yet\n"
" \"stdout\" may be used as a value of <filename> for -B option\n"
);
exit(FINI_ERROR);
}
2008-12-05 02:20:14 +01:00
void missing_parameter_for_switch(UtilSvc* uSvc, const char* sw)
{
usage(uSvc, "Missing parameter for switch %s", sw);
2008-12-05 02:20:14 +01:00
}
2004-11-04 19:54:03 +01:00
void singleAction(UtilSvc* uSvc)
{
usage(uSvc, "Only one of -L, -N, -F, -B or -R should be specified");
}
2008-12-05 02:20:14 +01:00
namespace
{
const int MSG_LEN = 1024;
const size_t NBACKUP_FAILURE_SPACE = MSG_LEN * 4;
typedef Firebird::CircularStringsBuffer<NBACKUP_FAILURE_SPACE> NbkStringsBuffer;
GlobalPtr<NbkStringsBuffer> nbkStringsBuffer;
GlobalPtr<Mutex> nbkBufMutex;
}
class b_error : public LongJump
2004-02-02 12:02:12 +01:00
{
public:
2008-04-19 13:11:10 +02:00
explicit b_error(const char* message)
{
size_t len = sizeof(txt) - 1;
strncpy(txt, message, len);
txt[len] = 0;
}
virtual ~b_error() throw() {}
2008-04-19 13:11:10 +02:00
virtual const char* what() const throw() { return txt; }
static void raise(UtilSvc* uSvc, const char* message, ...)
2008-04-19 13:11:10 +02:00
{
char temp[MSG_LEN];
va_list params;
va_start(params, message);
VSNPRINTF(temp, sizeof(temp), message, params);
temp[sizeof(temp) - 1] = 0;
va_end(params);
if (!uSvc->isService())
fprintf(stderr, "Failure: %s\n", temp);
throw b_error(temp);
}
virtual ISC_STATUS stuff_exception(ISC_STATUS* const status_vector, StringsBuffer* sb = NULL) const throw()
{
if (! sb)
{
sb = &nbkStringsBuffer;
}
(Arg::Gds(isc_random) << txt).copyTo(status_vector);
MutexLockGuard guard(nbkBufMutex);
sb->makePermanentVector(status_vector, status_vector);
return status_vector[1];
}
private:
char txt[MSG_LEN];
};
#ifdef WIN_NT
#define FILE_HANDLE HANDLE
#else
#define FILE_HANDLE int
#endif
const char local_prefix[] = "localhost:";
const char backup_signature[4] = {'N','B','A','K'};
2008-04-19 13:11:10 +02:00
struct inc_header
{
2004-02-02 12:02:12 +01:00
char signature[4]; // 'NBAK'
SSHORT version; // Incremental backup format version.
SSHORT level; // Backup level.
// \\\\\ ---- this is 8 bytes. should not cause alignment problems
2004-02-02 12:02:12 +01:00
FB_GUID backup_guid; // GUID of this backup
FB_GUID prev_guid; // GUID of previous level backup
ULONG page_size; // Size of pages in the database and backup file
// These fields are currently filled, but not used. May be used in future versions
ULONG backup_scn; // SCN of this backup
ULONG prev_scn; // SCN of previous level backup
};
class NBackup
2008-04-19 13:11:10 +02:00
{
public:
2008-12-05 02:20:14 +01:00
NBackup(UtilSvc* _uSvc, const PathName& _database, const string& _username,
const string& _password, bool _run_db_triggers, const string& _trustedUser,
bool _trustedRole)
2008-12-05 02:20:14 +01:00
: uSvc(_uSvc), newdb(0), trans(0), database(_database),
username(_username), password(_password), trustedUser(_trustedUser),
run_db_triggers(_run_db_triggers), trustedRole(_trustedRole),
dbase(0), backup(0), db_size_pages(0)
{
// Recognition of local prefix allows to work with
// database using TCP/IP loopback while reading file locally.
// This makes NBACKUP compatible with Windows CS with XNET disabled
PathName db(_database);
if (strncmp(db.c_str(), local_prefix, sizeof(local_prefix) - 1) == 0)
db = db.substr(sizeof(local_prefix) - 1);
if (!ResolveDatabaseAlias(db, dbname))
dbname = db;
}
typedef ObjectsArray<PathName> BackupFiles;
// External calls must clean up resources after themselves
void fixup_database();
void lock_database(bool get_size);
void unlock_database();
void backup_database(int level, const PathName& fname);
void restore_database(const BackupFiles& files);
private:
UtilSvc* uSvc;
2007-03-05 07:40:57 +01:00
ISC_STATUS_ARRAY status; // status vector
isc_db_handle newdb; // database handle
isc_tr_handle trans; // transaction handle
2007-02-09 09:11:35 +01:00
PathName database;
string username, password, trustedUser;
bool run_db_triggers, trustedRole;
PathName dbname; // Database file name
PathName bakname;
FILE_HANDLE dbase;
FILE_HANDLE backup;
ULONG db_size_pages; // In pages
2007-02-09 09:11:35 +01:00
// IO functions
2008-12-05 02:20:14 +01:00
size_t read_file(FILE_HANDLE &file, void *buffer, size_t bufsize);
void write_file(FILE_HANDLE &file, void *buffer, size_t bufsize);
void seek_file(FILE_HANDLE &file, SINT64 pos);
2007-02-09 09:11:35 +01:00
2008-11-22 08:51:42 +01:00
void pr_error(const ISC_STATUS* status, const char* operation) const;
2007-02-09 09:11:35 +01:00
void internal_lock_database();
void get_database_size();
void internal_unlock_database();
void attach_database();
void detach_database();
2007-02-09 09:11:35 +01:00
// Create/open database and backup
void open_database_write();
void open_database_scan();
void create_database();
void close_database();
2007-02-09 09:11:35 +01:00
void open_backup_scan();
void create_backup();
void close_backup();
};
size_t NBackup::read_file(FILE_HANDLE &file, void *buffer, size_t bufsize)
{
#ifdef WIN_NT
DWORD bytesDone;
if (!ReadFile(file, buffer, bufsize, &bytesDone, NULL))
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "IO error (%d) reading file: %s",
GetLastError(),
&file == &dbase ? dbname.c_str() :
&file == &backup ? bakname.c_str() : "unknown");
return bytesDone;
#else
const ssize_t res = read(file, buffer, bufsize);
if (res < 0)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "IO error (%d) reading file: %s",
errno,
&file == &dbase ? dbname.c_str() :
&file == &backup ? bakname.c_str() : "unknown");
return res;
#endif
}
void NBackup::write_file(FILE_HANDLE &file, void *buffer, size_t bufsize)
{
#ifdef WIN_NT
DWORD bytesDone;
2009-01-18 12:29:24 +01:00
if (!WriteFile(file, buffer, bufsize, &bytesDone, NULL) || bytesDone != bufsize)
{
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "IO error (%d) writing file: %s",
GetLastError(),
&file == &dbase ? dbname.c_str() :
&file == &backup ? bakname.c_str() : "unknown");
}
#else
2009-01-18 12:29:24 +01:00
if (write(file, buffer, bufsize) != (ssize_t) bufsize)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "IO error (%d) writing file: %s",
errno,
&file == &dbase ? dbname.c_str() :
&file == &backup ? bakname.c_str() : "unknown");
#endif
}
void NBackup::seek_file(FILE_HANDLE &file, SINT64 pos)
{
#ifdef WIN_NT
LARGE_INTEGER offset;
offset.QuadPart = pos;
DWORD error;
2009-01-18 12:29:24 +01:00
if (SetFilePointer(dbase, offset.LowPart, &offset.HighPart, FILE_BEGIN) ==
2009-01-21 16:42:45 +01:00
INVALID_SET_FILE_POINTER &&
(error = GetLastError()) != NO_ERROR)
{
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "IO error (%d) seeking file: %s",
error,
&file == &dbase ? dbname.c_str() :
&file == &backup ? bakname.c_str() : "unknown");
}
#else
if (lseek(file, pos, SEEK_SET) == (off_t) - 1)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "IO error (%d) seeking file: %s",
errno,
&file == &dbase ? dbname.c_str() :
&file == &backup ? bakname.c_str() : "unknown");
#endif
}
void NBackup::open_database_write()
{
#ifdef WIN_NT
2008-12-05 02:20:14 +01:00
dbase = CreateFile(dbname.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (dbase == INVALID_HANDLE_VALUE)
b_error::raise(uSvc, "Error (%d) opening database file: %s", GetLastError(), dbname.c_str());
#else
2004-03-16 06:54:15 +01:00
dbase = open(dbname.c_str(), O_RDWR | O_LARGEFILE);
if (dbase < 0)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "Error (%d) opening database file: %s", errno, dbname.c_str());
#endif
}
void NBackup::open_database_scan()
{
#ifdef WIN_NT
// On Windows we use unbuffered IO to work around bug in Windows Server 2003
// which has little problems with managing size of disk cache. If you read
2008-12-05 02:20:14 +01:00
// very large file (5 GB or more) on this platform filesystem page cache
// consumes all RAM of machine and causes excessive paging of user programs
// and OS itself. Basically, reading any large file brings the whole system
// down for extended period of time. Documented workaround is to avoid using
// system cache when reading large files.
dbase = CreateFile(dbname.c_str(),
2008-12-05 02:20:14 +01:00
GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_NO_BUFFERING,
NULL);
if (dbase == INVALID_HANDLE_VALUE)
b_error::raise(uSvc, "Error (%d) opening database file: %s", GetLastError(), dbname.c_str());
#else // WIN_NT
#ifndef O_NOATIME
#define O_NOATIME 0
#endif // O_NOATIME
dbase = open(dbname.c_str(), O_RDONLY | O_LARGEFILE | O_NOATIME | O_DIRECT);
if (dbase < 0)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "Error (%d) opening database file: %s", errno, dbname.c_str());
#ifdef HAVE_POSIX_FADVISE
int rc = posix_fadvise(dbase, 0, 0, POSIX_FADV_SEQUENTIAL);
if (rc)
b_error::raise(uSvc, "Error (%d) in posix_fadvise(SEQUENTIAL) for %s", rc, dbname.c_str());
rc = posix_fadvise(dbase, 0, 0, POSIX_FADV_NOREUSE);
if (rc)
b_error::raise(uSvc, "Error (%d) in posix_fadvise(NOREUSE) for %s", rc, dbname.c_str());
2009-03-07 04:09:48 +01:00
#endif // HAVE_POSIX_FADVISE
#endif // WIN_NT
}
void NBackup::create_database()
{
#ifdef WIN_NT
2008-12-05 02:20:14 +01:00
dbase = CreateFile(dbname.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE,
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (dbase == INVALID_HANDLE_VALUE)
b_error::raise(uSvc, "Error (%d) creating database file: %s", GetLastError(), dbname.c_str());
#else
2004-03-16 06:54:15 +01:00
dbase = open(dbname.c_str(), O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE, 0660);
if (dbase < 0)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "Error (%d) creating database file: %s", errno, dbname.c_str());
#endif
}
void NBackup::close_database()
{
#ifdef WIN_NT
CloseHandle(dbase);
#else
close(dbase);
#endif
}
void NBackup::open_backup_scan()
{
#ifdef WIN_NT
2008-12-05 02:20:14 +01:00
backup = CreateFile(bakname.c_str(), GENERIC_READ, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (backup == INVALID_HANDLE_VALUE)
b_error::raise(uSvc, "Error (%d) opening backup file: %s", GetLastError(), bakname.c_str());
#else
2004-03-16 06:54:15 +01:00
backup = open(bakname.c_str(), O_RDONLY | O_LARGEFILE);
if (backup < 0)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "Error (%d) opening backup file: %s", errno, bakname.c_str());
#endif
}
void NBackup::create_backup()
{
#ifdef WIN_NT
2004-03-16 06:54:15 +01:00
if (bakname == "stdout") {
backup = GetStdHandle(STD_OUTPUT_HANDLE);
}
2008-12-05 02:20:14 +01:00
else {
backup = CreateFile(bakname.c_str(), GENERIC_WRITE, FILE_SHARE_DELETE,
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
}
if (backup == INVALID_HANDLE_VALUE)
b_error::raise(uSvc, "Error (%d) creating backup file: %s", GetLastError(), bakname.c_str());
#else
2004-03-16 06:54:15 +01:00
if (bakname == "stdout") {
2007-03-05 07:40:57 +01:00
backup = 1; // Posix file handle for stdout
}
else {
2004-03-16 06:54:15 +01:00
backup = open(bakname.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, 0660);
if (backup < 0)
b_error::raise(uSvc, "Error (%d) creating backup file: %s", errno, bakname.c_str());
}
#endif
}
void NBackup::close_backup()
{
if (bakname == "stdout")
return;
#ifdef WIN_NT
CloseHandle(backup);
#else
close(backup);
#endif
}
void NBackup::fixup_database()
{
open_database_write();
Ods::header_page header;
if (read_file(dbase, &header, sizeof(header)) != sizeof(header))
b_error::raise(uSvc, "Unexpected end of database file", errno);
const int backup_state = header.hdr_flags & Ods::hdr_backup_mask;
2008-12-05 02:20:14 +01:00
if (backup_state != Jrd::nbak_state_stalled)
b_error::raise(uSvc, "Database is not in state (%d) to be safely fixed up", backup_state);
2004-05-14 01:20:50 +02:00
header.hdr_flags = (header.hdr_flags & ~Ods::hdr_backup_mask) | Jrd::nbak_state_normal;
seek_file(dbase, 0);
2008-12-05 02:20:14 +01:00
write_file(dbase, &header, sizeof(header));
close_database();
}
/*
* Print the status, the SQLCODE, and exit.
* Also, indicate which operation the error occured on.
*/
2008-11-22 08:51:42 +01:00
void NBackup::pr_error (const ISC_STATUS* status, const char* operation) const
{
if (uSvc->isService())
status_exception::raise(status);
printf("[\n");
printf("PROBLEM ON \"%s\".\n", operation);
isc_print_status(status);
2006-01-16 17:49:15 +01:00
printf("SQLCODE:%"SLONGFORMAT"\n", isc_sqlcode(status));
printf("]\n");
b_error::raise(uSvc, "Database error");
}
void NBackup::attach_database()
{
if (username.length() > 255 || password.length() > 255)
b_error::raise(uSvc, "Username or password is too long");
ClumpletWriter dpb(ClumpletReader::Tagged, MAX_DPB_SIZE, isc_dpb_version1);
2007-02-09 09:11:35 +01:00
if (username.hasData()) {
dpb.insertString(isc_dpb_user_name, username);
}
if (password.hasData()) {
dpb.insertString(isc_dpb_password, password);
}
if (trustedUser.hasData()) {
uSvc->checkService();
dpb.insertString(isc_dpb_trusted_auth, trustedUser);
}
if (trustedRole) {
uSvc->checkService();
dpb.insertString(isc_dpb_trusted_role, ADMIN_ROLE, strlen(ADMIN_ROLE));
}
if (!run_db_triggers)
dpb.insertByte(isc_dpb_no_db_triggers, 1);
2008-12-05 02:20:14 +01:00
if (isc_attach_database(status, 0, database.c_str(), &newdb,
dpb.getBufferLength(), reinterpret_cast<const char*>(dpb.getBuffer())))
{
pr_error(status, "attach database");
}
}
void NBackup::detach_database()
{
if (trans) {
if (isc_rollback_transaction(status, &trans))
pr_error(status, "rollback transaction");
}
if (isc_detach_database(status, &newdb))
pr_error(status, "detach database");
}
void NBackup::internal_lock_database()
{
if (isc_start_transaction(status, &trans, 1, &newdb, 0, NULL))
pr_error(status, "start transaction");
2009-01-18 12:29:24 +01:00
if (isc_dsql_execute_immediate(status, &newdb, &trans, 0, "ALTER DATABASE BEGIN BACKUP", 1, NULL))
pr_error(status, "begin backup");
if (isc_commit_transaction(status, &trans))
pr_error(status, "begin backup: commit");
}
void NBackup::get_database_size()
{
db_size_pages = 0;
2009-01-18 12:29:24 +01:00
const char fs[] = {isc_info_db_file_size};
char res[128];
if (isc_database_info(status, &newdb, sizeof(fs), fs, sizeof(res), res))
{
pr_error(status, "size info");
}
else if (res[0] == isc_info_db_file_size)
{
USHORT len = isc_vax_integer (&res[1], 2);
db_size_pages = isc_vax_integer (&res[3], len);
}
}
void NBackup::internal_unlock_database()
{
if (isc_start_transaction(status, &trans, 1, &newdb, 0, NULL))
pr_error(status, "start transaction");
2009-01-18 12:29:24 +01:00
if (isc_dsql_execute_immediate(status, &newdb, &trans, 0, "ALTER DATABASE END BACKUP", 1, NULL))
pr_error(status, "end backup");
if (isc_commit_transaction(status, &trans))
pr_error(status, "end backup: commit");
}
void NBackup::lock_database(bool get_size)
{
attach_database();
db_size_pages = 0;
try {
internal_lock_database();
if (get_size)
{
get_database_size();
if (db_size_pages && (!uSvc->isService()))
printf("%d\n", db_size_pages);
}
2008-12-05 02:20:14 +01:00
}
catch (const Exception&) {
2008-12-05 02:20:14 +01:00
detach_database();
throw;
}
detach_database();
}
void NBackup::unlock_database()
{
attach_database();
try {
internal_unlock_database();
2008-12-05 02:20:14 +01:00
}
catch (const Exception&) {
2008-12-05 02:20:14 +01:00
detach_database();
throw;
}
detach_database();
}
void NBackup::backup_database(int level, const PathName& fname)
{
bool database_locked = false;
// We set this flag when backup file is in inconsistent state
bool delete_backup = false;
ULONG prev_scn = 0;
char prev_guid[GUID_BUFF_SIZE] = "";
Ods::pag* page_buff = NULL;
attach_database();
try {
// Look for SCN and GUID of previous-level backup in history table
2008-04-19 13:11:10 +02:00
if (level)
{
if (isc_start_transaction(status, &trans, 1, &newdb, 0, NULL))
pr_error(status, "start transaction");
char out_sqlda_data[XSQLDA_LENGTH(2)];
XSQLDA *out_sqlda = (XSQLDA*)out_sqlda_data;
out_sqlda->version = SQLDA_VERSION1;
out_sqlda->sqln = 2;
2007-02-09 09:11:35 +01:00
2004-05-03 01:06:37 +02:00
isc_stmt_handle stmt = 0;
if (isc_dsql_allocate_statement(status, &newdb, &stmt))
pr_error(status, "allocate statement");
char str[200];
sprintf(str, "select rdb$guid, rdb$scn from rdb$backup_history "
2009-01-18 12:29:24 +01:00
"where rdb$backup_id = "
"(select max(rdb$backup_id) from rdb$backup_history "
2004-11-30 07:18:39 +01:00
"where rdb$backup_level = %d)", level - 1);
if (isc_dsql_prepare(status, &trans, &stmt, 0, str, 1, NULL))
pr_error(status, "prepare history query");
if (isc_dsql_describe(status, &stmt, 1, out_sqlda))
pr_error(status, "describe history query");
short guid_null, scn_null;
out_sqlda->sqlvar[0].sqlind = &guid_null;
out_sqlda->sqlvar[0].sqldata = prev_guid;
out_sqlda->sqlvar[1].sqlind = &scn_null;
out_sqlda->sqlvar[1].sqldata = (char*)&prev_scn;
if (isc_dsql_execute(status, &trans, &stmt, 1, NULL))
pr_error(status, "execute history query");
2007-02-09 09:11:35 +01:00
2009-01-18 12:29:24 +01:00
switch (isc_dsql_fetch(status, &stmt, 1, out_sqlda))
{
2007-03-05 07:40:57 +01:00
case 100: // No more records available
b_error::raise(uSvc, "Cannot find record for database \"%s\" backup level %d "
2004-11-29 11:06:34 +01:00
"in the backup history", database.c_str(), level - 1);
2008-12-05 02:20:14 +01:00
case 0:
if (guid_null || scn_null)
b_error::raise(uSvc, "Internal error. History query returned null SCN or GUID");
2004-02-02 12:02:12 +01:00
prev_guid[sizeof(prev_guid) - 1] = 0;
break;
default:
pr_error(status, "fetch history query");
}
isc_dsql_free_statement(status, &stmt, DSQL_close);
if (isc_commit_transaction(status, &trans))
pr_error(status, "commit history query");
}
2007-02-09 09:11:35 +01:00
// Lock database for backup
internal_lock_database();
2008-12-05 02:20:14 +01:00
database_locked = true;
get_database_size();
2007-03-09 09:21:18 +01:00
detach_database();
time_t _time = time(NULL);
const struct tm *today = localtime(&_time);
2007-02-09 09:11:35 +01:00
if (fname.hasData())
2004-03-16 06:54:15 +01:00
bakname = fname;
else {
// Let's generate nice new filename
PathName begin, fil;
PathUtils::splitLastComponent(begin, fil, database);
bakname.printf("%s-%d-%04d%02d%02d-%02d%02d.nbk", fil.c_str(), level,
today->tm_year + 1900, today->tm_mon + 1, today->tm_mday,
2004-02-02 12:02:12 +01:00
today->tm_hour, today->tm_min);
if (!uSvc->isService())
printf("%s", bakname.c_str()); // Print out generated filename for script processing
}
// Level 0 backup is a full reconstructed database image that can be
2008-12-05 02:20:14 +01:00
// used directly after fixup. Incremenal backups of other levels are
// consisted of header followed by page data. Each page is preceded
// by 4-byte integer page number
2007-02-09 09:11:35 +01:00
2008-12-05 02:20:14 +01:00
// Actual IO is optimized to get maximum performance
// from the IO subsystem while taking as little CPU time as possible
2007-02-09 09:11:35 +01:00
// NOTE: this is still possible to improve performance by implementing
// version using asynchronous unbuffered IO on NT series of OS.
// But this task is for another day. 02 Aug 2003, Nickolay Samofatov.
2007-02-09 09:11:35 +01:00
// Create backup file and open database file
create_backup();
delete_backup = true;
2007-02-09 09:11:35 +01:00
open_database_scan();
2007-02-09 09:11:35 +01:00
// Read database header
char unaligned_header_buffer[SECTOR_ALIGNMENT * 2];
2008-12-05 02:20:14 +01:00
Ods::header_page *header =
reinterpret_cast<Ods::header_page*>(
2008-12-05 02:20:14 +01:00
FB_ALIGN((IPTR) unaligned_header_buffer, SECTOR_ALIGNMENT));
if (read_file(dbase, header, SECTOR_ALIGNMENT/*sizeof(*header)*/) != SECTOR_ALIGNMENT/*sizeof(*header)*/)
b_error::raise(uSvc, "Unexpected end of file when reading header of database file");
if ((header->hdr_flags & Ods::hdr_backup_mask) != Jrd::nbak_state_stalled)
2004-02-02 12:02:12 +01:00
{
b_error::raise(uSvc, "Internal error. Database file is not locked. Flags are %d",
header->hdr_flags);
2004-02-02 12:02:12 +01:00
}
2007-02-09 09:11:35 +01:00
Array<UCHAR> unaligned_page_buffer;
2009-01-18 12:29:24 +01:00
{ // scope
UCHAR* buf = unaligned_page_buffer.getBuffer(header->hdr_page_size + SECTOR_ALIGNMENT);
page_buff = reinterpret_cast<Ods::pag*>(FB_ALIGN((IPTR) buf, SECTOR_ALIGNMENT));
} // end scope
2007-02-09 09:11:35 +01:00
ULONG db_size = db_size_pages;
seek_file(dbase, 0);
2007-02-09 09:11:35 +01:00
if (read_file(dbase, page_buff, header->hdr_page_size) != header->hdr_page_size)
b_error::raise(uSvc, "Unexpected end of file when reading header of database file (stage 2)");
--db_size;
2007-02-09 09:11:35 +01:00
FB_GUID backup_guid;
bool guid_found = false;
const UCHAR* p = reinterpret_cast<Ods::header_page*>(page_buff)->hdr_data;
2009-01-18 12:29:24 +01:00
while (true)
{
switch (*p)
{
case Ods::HDR_backup_guid:
2004-02-02 12:02:12 +01:00
if (p[1] != sizeof(FB_GUID))
break;
memcpy(&backup_guid, p + 2, sizeof(FB_GUID));
guid_found = true;
break;
case Ods::HDR_difference_file:
2004-02-02 12:02:12 +01:00
p += p[1] + 2;
continue;
}
break;
}
2007-02-09 09:11:35 +01:00
if (!guid_found)
b_error::raise(uSvc, "Internal error. Cannot get backup guid clumplet");
2008-12-05 02:20:14 +01:00
// Write data to backup file
ULONG backup_scn = header->hdr_header.pag_scn - 1;
if (level) {
inc_header bh;
memcpy(bh.signature, backup_signature, sizeof(backup_signature));
bh.version = 1;
bh.level = level;
bh.backup_guid = backup_guid;
StringToGuid(&bh.prev_guid, prev_guid);
bh.page_size = header->hdr_page_size;
bh.backup_scn = backup_scn;
bh.prev_scn = prev_scn;
write_file(backup, &bh, sizeof(bh));
}
2007-02-09 09:11:35 +01:00
ULONG curPage = 0;
2008-12-05 02:20:14 +01:00
// Starting from ODS 11.1 we can expand file but never use some last
// pages in it. There are no need to backup this empty pages. More,
// we can't be sure its not used pages have right SCN assigned.
// How many pages are really used we know from pip_header.reserved
// where stored number of pages allocated from this pointer page.
// In ODS 12 it will be moved into corresponding field of page_inv_page.
2008-04-03 03:11:26 +02:00
const bool isODS11_x = ((header->hdr_ods_version & ~ODS_FIREBIRD_FLAG) == 11) &&
(header->hdr_ods_minor_original >= 1);
ULONG lastPage = 1; // first PIP must be at page number 1
2008-12-05 02:20:14 +01:00
const ULONG pagesPerPIP =
(header->hdr_page_size - OFFSETA(Ods::page_inv_page*, pip_bits)) * 8;
2009-01-18 12:29:24 +01:00
while (true)
{
2004-02-02 12:02:12 +01:00
if (curPage && page_buff->pag_scn > backup_scn)
b_error::raise(uSvc, "Internal error. Database page %d had been changed during backup"
2004-02-02 12:02:12 +01:00
" (page SCN=%d, backup SCN=%d)", curPage,
page_buff->pag_scn, backup_scn);
if (level) {
2004-02-02 12:02:12 +01:00
if (page_buff->pag_scn > prev_scn) {
write_file(backup, &curPage, sizeof(curPage));
write_file(backup, page_buff, header->hdr_page_size);
}
}
else
write_file(backup, page_buff, header->hdr_page_size);
2008-12-05 02:20:14 +01:00
if ((db_size_pages != 0) && (db_size == 0))
break;
const size_t bytesDone = read_file(dbase, page_buff, header->hdr_page_size);
--db_size;
if (bytesDone == 0)
break;
if (bytesDone != header->hdr_page_size)
b_error::raise(uSvc, "Database file size is not a multiply of page size");
curPage++;
if (isODS11_x && curPage == lastPage)
{
if (page_buff->pag_type == pag_pages)
{
if (lastPage == 1)
lastPage = page_buff->reserved - 1;
else
lastPage += page_buff->reserved;
if (page_buff->reserved < pagesPerPIP)
lastPage++;
}
else
{
fb_assert(page_buff->pag_type == pag_undefined);
break;
}
}
2008-12-05 02:20:14 +01:00
}
close_database();
close_backup();
2007-02-09 09:11:35 +01:00
delete_backup = false; // Backup file is consistent. No need to delete it
2007-02-09 09:11:35 +01:00
2007-03-09 09:21:18 +01:00
attach_database();
// Write about successful backup to backup history table
if (isc_start_transaction(status, &trans, 1, &newdb, 0, NULL))
pr_error(status, "start transaction");
char in_sqlda_data[XSQLDA_LENGTH(4)];
XSQLDA *in_sqlda = (XSQLDA *)in_sqlda_data;
in_sqlda->version = SQLDA_VERSION1;
in_sqlda->sqln = 4;
2004-05-03 01:06:37 +02:00
isc_stmt_handle stmt = 0;
if (isc_dsql_allocate_statement(status, &newdb, &stmt))
pr_error(status, "allocate statement");
2008-12-05 02:20:14 +01:00
if (isc_dsql_prepare(status, &trans, &stmt, 0,
"insert into rdb$backup_history(rdb$backup_id, rdb$timestamp,"
"rdb$backup_level, rdb$guid, rdb$scn, rdb$file_name)"
"values(gen_id(rdb$backup_history, 1), 'now', ?, ?, ?, ?)",
1, NULL))
{
pr_error(status, "prepare history insert");
}
if (isc_dsql_describe_bind(status, &stmt, 1, in_sqlda))
pr_error(status, "bind history insert");
short null_flag = 0;
in_sqlda->sqlvar[0].sqldata = (char*)&level;
in_sqlda->sqlvar[0].sqlind = &null_flag;
char temp[GUID_BUFF_SIZE];
GuidToString(temp, &backup_guid);
in_sqlda->sqlvar[1].sqldata = temp;
in_sqlda->sqlvar[1].sqlind = &null_flag;
in_sqlda->sqlvar[2].sqldata = (char*)&backup_scn;
in_sqlda->sqlvar[2].sqlind = &null_flag;
char buff[256]; // RDB$FILE_NAME has length of 253
2004-03-16 06:54:15 +01:00
size_t len = bakname.length();
2004-02-02 12:02:12 +01:00
if (len > 253)
len = 253;
*(USHORT*) buff = len;
2004-03-16 06:54:15 +01:00
memcpy(buff + 2, bakname.c_str(), len);
in_sqlda->sqlvar[3].sqldata = buff;
in_sqlda->sqlvar[3].sqlind = &null_flag;
if (isc_dsql_execute(status, &trans, &stmt, 1, in_sqlda))
pr_error(status, "execute history insert");
2007-03-09 09:21:18 +01:00
isc_dsql_free_statement(status, &stmt, DSQL_drop);
if (isc_commit_transaction(status, &trans))
pr_error(status, "commit history insert");
2007-02-09 09:11:35 +01:00
2008-12-05 02:20:14 +01:00
}
2009-01-18 12:29:24 +01:00
catch (const Exception&)
{
if (delete_backup)
remove(bakname.c_str());
if (trans) {
if (isc_rollback_transaction(status, &trans))
pr_error(status, "rollback transaction");
}
2007-03-09 09:21:18 +01:00
if (database_locked) {
if (!newdb)
attach_database();
internal_unlock_database();
2007-03-09 09:21:18 +01:00
}
if (newdb)
detach_database();
throw;
}
2007-03-09 09:21:18 +01:00
if (!newdb)
attach_database();
internal_unlock_database();
detach_database();
}
void NBackup::restore_database(const BackupFiles& files)
{
// We set this flag when database file is in inconsistent state
2008-12-05 02:20:14 +01:00
bool delete_database = false;
2008-11-22 08:51:42 +01:00
const int filecount = files.getCount();
#ifndef WIN_NT
create_database();
delete_database = true;
#endif
UCHAR *page_buffer = NULL;
try {
int curLevel = 0;
FB_GUID prev_guid;
2009-01-18 12:29:24 +01:00
while (true)
{
if (!filecount)
{
while (true)
{
if (uSvc->isService())
bakname = ".";
else
{
printf("Enter name of the backup file of level %d "
"(\".\" - do not restore further): \n", curLevel);
char temp[256];
scanf("%255s", temp);
bakname = temp;
}
2008-12-05 02:20:14 +01:00
if (bakname == ".")
2004-03-16 06:54:15 +01:00
{
close_database();
if (!curLevel) {
remove(dbname.c_str());
b_error::raise(uSvc, "Level 0 backup is not restored");
}
fixup_database();
delete[] page_buffer;
return;
}
2008-11-22 08:51:42 +01:00
// Never reaches this point when run as service
try {
2007-02-09 09:11:35 +01:00
#ifdef WIN_NT
2007-02-09 09:22:14 +01:00
if (curLevel)
2007-02-09 09:11:35 +01:00
#endif
open_backup_scan();
break;
2006-05-20 06:22:07 +02:00
}
catch (const Exception& e) {
printf("%s\n", e.what());
}
}
}
else {
if (curLevel >= filecount) {
close_database();
fixup_database();
delete[] page_buffer;
return;
}
2008-03-05 09:39:26 +01:00
bakname = files[curLevel];
#ifdef WIN_NT
2008-03-05 09:39:26 +01:00
if (curLevel)
#endif
2008-03-05 09:39:26 +01:00
open_backup_scan();
}
2007-02-09 09:11:35 +01:00
2009-01-18 12:29:24 +01:00
if (curLevel)
{
inc_header bakheader;
if (read_file(backup, &bakheader, sizeof(bakheader)) != sizeof(bakheader))
2009-01-18 12:29:24 +01:00
b_error::raise(uSvc, "Unexpected end of file when reading header of backup file: %s",
bakname.c_str());
2008-12-05 02:20:14 +01:00
if (memcmp(bakheader.signature, backup_signature, sizeof(backup_signature)) != 0)
b_error::raise(uSvc, "Invalid incremental backup file: %s", bakname.c_str());
if (bakheader.version != 1)
2009-01-18 12:29:24 +01:00
b_error::raise(uSvc, "Unsupported version %d of incremental backup file: %s",
bakheader.version, bakname.c_str());
if (bakheader.level != curLevel)
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "Invalid level %d of incremental backup file: %s, expected %d",
2004-03-16 06:54:15 +01:00
bakheader.level, bakname.c_str(), curLevel);
// We may also add SCN check, but GUID check covers this case too
if (memcmp(&bakheader.prev_guid, &prev_guid, sizeof(FB_GUID)) != 0)
2004-02-02 12:02:12 +01:00
{
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc,
"Wrong order of backup files or "
2004-03-16 06:54:15 +01:00
"invalid incremental backup file detected, file: %s", bakname.c_str());
2004-02-02 12:02:12 +01:00
}
delete_database = true;
prev_guid = bakheader.backup_guid;
2009-01-18 12:29:24 +01:00
while (true)
{
ULONG pageNum;
const size_t bytesDone = read_file(backup, &pageNum, sizeof(pageNum));
if (bytesDone == 0)
2008-12-05 02:20:14 +01:00
break;
if (bytesDone != sizeof(pageNum) ||
read_file(backup, page_buffer, bakheader.page_size) != bakheader.page_size)
{
b_error::raise(uSvc, "Unexpected end of backup file: %s", bakname.c_str());
}
seek_file(dbase, ((SINT64)pageNum)*bakheader.page_size);
write_file(dbase, page_buffer, bakheader.page_size);
}
delete_database = false;
}
2009-01-18 12:29:24 +01:00
else
{
#ifdef WIN_NT
if (!CopyFile(bakname.c_str(), dbname.c_str(), TRUE)) {
2008-12-05 02:20:14 +01:00
b_error::raise(uSvc, "Error (%d) creating database file: %s via copying from: %s",
2004-03-16 06:54:15 +01:00
GetLastError(), dbname.c_str(), bakname.c_str());
}
delete_database = true; // database is possibly broken
open_database_write();
#else
// Use relatively small buffer to make use of prefetch and lazy flush
2008-12-05 02:20:14 +01:00
char buffer[65536];
2004-02-02 12:02:12 +01:00
while (true) {
const size_t bytesRead = read_file(backup, buffer, sizeof(buffer));
if (bytesRead == 0)
break;
write_file(dbase, buffer, bytesRead);
}
seek_file(dbase, 0);
2008-12-05 02:20:14 +01:00
#endif
// Read database header
Ods::header_page header;
if (read_file(dbase, &header, sizeof(header)) != sizeof(header))
b_error::raise(uSvc, "Unexpected end of file when reading restored database header");
page_buffer = FB_NEW(*getDefaultMemoryPool()) UCHAR[header.hdr_page_size];
2007-02-09 09:11:35 +01:00
seek_file(dbase, 0);
2007-02-09 09:11:35 +01:00
if (read_file(dbase, page_buffer, header.hdr_page_size) != header.hdr_page_size)
b_error::raise(uSvc, "Unexpected end of file when reading header of restored database file (stage 2)");
2007-02-09 09:11:35 +01:00
bool guid_found = false;
const UCHAR* p = reinterpret_cast<Ods::header_page*>(page_buffer)->hdr_data;
2009-01-18 12:29:24 +01:00
while (true)
{
switch (*p)
{
case Ods::HDR_backup_guid:
2004-02-02 12:02:12 +01:00
if (p[1] != sizeof(FB_GUID))
break;
memcpy(&prev_guid, p + 2, sizeof(FB_GUID));
guid_found = true;
break;
case Ods::HDR_difference_file:
2004-02-02 12:02:12 +01:00
p += p[1] + 2;
continue;
}
break;
}
if (!guid_found)
b_error::raise(uSvc, "Cannot get backup guid clumplet from L0 backup");
// We are likely to have normal database here
delete_database = false;
}
2007-02-09 09:11:35 +01:00
#ifdef WIN_NT
if (curLevel)
#endif
close_backup();
curLevel++;
}
2008-12-05 02:20:14 +01:00
}
catch (const Exception&) {
delete[] page_buffer;
if (delete_database)
remove(dbname.c_str());
throw;
}
}
THREAD_ENTRY_DECLARE NBACKUP_main(THREAD_ENTRY_PARAM arg)
{
UtilSvc* uSvc = (UtilSvc*) arg;
int exit_code = FB_SUCCESS;
try {
nbackup(uSvc);
}
catch (const Exception& e)
{
e.stuff_exception(uSvc->getStatus());
exit_code = FB_FAILURE;
}
uSvc->started();
uSvc->finish();
return (THREAD_ENTRY_RETURN)(IPTR) exit_code;
}
enum NbOperation {nbNone, nbLock, nbUnlock, nbFixup, nbBackup, nbRestore};
void nbackup(UtilSvc* uSvc)
{
UtilSvc::ArgvType& argv = uSvc->argv;
2008-11-22 08:51:42 +01:00
const int argc = argv.getCount();
NbOperation op = nbNone;
string username, password;
PathName database, filename;
bool run_db_triggers = true;
NBackup::BackupFiles backup_files;
int level;
bool print_size = false;
string trustedUser;
bool trustedRole = false;
// Read global command line parameters
2009-01-18 12:29:24 +01:00
for (int itr = 1; itr < argc; ++itr)
{
// We must recognize all parameters here
if (argv[itr][0] != '-') {
usage(uSvc, "Unrecognized parameter %s", argv[itr]);
}
2007-02-09 09:11:35 +01:00
if (uSvc->isService())
{
2008-11-22 08:51:42 +01:00
const PathName sw = &argv[itr][1];
if (sw == TRUSTED_USER_SWITCH)
{
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
trustedUser = argv[itr];
continue;
}
if (sw == TRUSTED_ROLE_SWITCH)
{
trustedRole = true;
continue;
}
}
2009-01-18 12:29:24 +01:00
switch (UPPER(argv[itr][1]))
{
case 'U':
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
username = argv[itr];
break;
case 'P':
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
password = argv[itr];
uSvc->hidePasswd(argv, itr);
break;
case 'T':
run_db_triggers = false;
break;
case 'F':
if (UPPER(argv[itr][2]) == 'E')
{
if (uSvc->isService())
{
usage(uSvc, "Fetch password can't be used in service mode");
break;
}
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2008-12-03 02:05:53 +01:00
const char* passwd = NULL;
if (fb_utils::fetchPassword(argv[itr], passwd) != fb_utils::FETCH_PASS_OK)
{
usage(uSvc, "Error working with password file");
break;
}
password = passwd;
break;
}
2008-12-05 02:20:14 +01:00
if (op != nbNone)
singleAction(uSvc);
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
database = argv[itr];
op = nbFixup;
break;
case 'L':
2008-12-05 02:20:14 +01:00
if (op != nbNone)
singleAction(uSvc);
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
database = argv[itr];
op = nbLock;
break;
case 'N':
2008-12-05 02:20:14 +01:00
if (op != nbNone)
singleAction(uSvc);
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
database = argv[itr];
op = nbUnlock;
break;
case 'B':
2008-12-05 02:20:14 +01:00
if (op != nbNone)
singleAction(uSvc);
2004-11-04 19:54:03 +01:00
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
level = atoi(argv[itr]);
2004-11-04 19:54:03 +01:00
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 2]);
database = argv[itr];
if (itr + 1 < argc)
filename = argv[++itr];
op = nbBackup;
break;
case 'R':
2008-12-05 02:20:14 +01:00
if (op != nbNone)
singleAction(uSvc);
if (++itr >= argc)
missing_parameter_for_switch(uSvc, argv[itr - 1]);
2004-11-04 19:54:03 +01:00
database = argv[itr];
while (++itr < argc)
backup_files.push(argv[itr]);
op = nbRestore;
break;
case 'S':
print_size = true;
break;
default:
usage(uSvc, "Unknown switch %s", argv[itr]);
break;
}
}
2007-02-09 09:11:35 +01:00
if (print_size && (op != nbLock))
usage(uSvc, "Switch -S can be used only with -L");
NBackup nbk(uSvc, database, username, password, run_db_triggers, trustedUser, trustedRole);
2009-01-18 12:29:24 +01:00
switch (op)
{
case nbNone:
usage(uSvc, "None of -L, -N, -F, -B or -R specified");
break;
case nbLock:
nbk.lock_database(print_size);
break;
case nbUnlock:
nbk.unlock_database();
break;
case nbFixup:
nbk.fixup_database();
break;
case nbBackup:
nbk.backup_database(level, filename);
break;
case nbRestore:
nbk.restore_database(backup_files);
break;
}
}