Jump to content
msd

Backup the FirebirdSQL database from ext server locally

Recommended Posts

Hello.

 

I found a few Firebird/Interbase managers have the option to backup databases from a remote server (for example, a Linux server) locally in some directory.

When I set all properties to FireDAC Firebid Backup Component, everything is working fine. If I choose a remote instance, I don't have any errors, but there is no locally backup file.

 

Do I miss something?

 

Thanks for the assistance in advance...

Share this post


Link to post

You will find the backup file at the FB-Server PC!

  • Like 1

Share this post


Link to post
7 hours ago, msd said:

If I choose a remote instance, I don't have any errors, but there is no locally backup file.

You need to have the client tools installed on the remote machine, then use gbak like this:

gbak -b -user SYSDBA -password masterkey server:/path/to/database.fdb client:/path/to/backup.fbk

 

  • Like 1

Share this post


Link to post

Hello,

 

This is nice advice, but I need to solve it over my Delphi app, so I need to make some class that will make backups from the remote server to the local machine, zip it, and copy it to the FTP backup location.

 

 

Share this post


Link to post

I'm not sure about the latest version of Firebird, but as with Interbase, only gbak utility can make archives on a remote disk. Therefore, CreateProcess and Waitforsingleobject for Gbak.exe can be used in Delphi

Edited by tgbs
  • Like 1

Share this post


Link to post

I give a sample with FireDAC; it is not mandatory to be with FireDAC. I have UniDAC, FibPlus, and UIB, all full-source x32/x64.

If there is some experience about this subject, it will be nice 🙂 

Share this post


Link to post
function RunProcess(CommandLine: String; Var ErrMessage : String; WaitForProcess : Boolean; CreationFlags : Word): Boolean;
Var
  ResultCode : LongWord;
  StartInfo: TStartUpInfo;
  ProcInfo: TProcessInformation;
begin
  Result := False;
  ResultCode := 1;

  FillChar(StartInfo, SizeOf(TStartUpInfo), #0);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
  StartInfo.cb := SizeOf(TStartUpInfo);
  StartInfo.dwFlags := STARTF_USESHOWWINDOW;
 // StartInfo.wShowWindow := SW_SHOWMINIMIZED;

  UniqueString(CommandLine);
try
  try
    IF NOT CreateProcess(nil, 
                   PChar(CommandLine),
                   nil,               
                   nil,               
                   FALSE,             
                   CreationFlags,     
                   nil,               
                   nil,               
                   StartInfo,         
                   ProcInfo)          
       THEN BEGIN
         ErrMessage := SysErrorMessage(GetLastError);
         Exit;
       END;
  except
    on e : exception do
      begin
        ErrMessage := e.Message + #13#10 + SysErrorMessage(GetLastError);
        Exit;
      end;
  end;

  if not WaitForProcess then ResultCode := 0
    else begin
      WaitForSingleObject(ProcInfo.hProcess, INFINITE);
      GetExitCodeProcess(ProcInfo.hProcess, ResultCode);
    end;
finally
  Result := (ResultCode = 0);
  //--------
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread);
end;
end;

Set CommandLine to somehing like  @corneliusdavid comment

  • Like 1

Share this post


Link to post
3 hours ago, msd said:

I give a sample with FireDAC; it is not mandatory to be with FireDAC. I have UniDAC, FibPlus, and UIB, all full-source x32/x64.

IBX2 for Lazarus can do that. DevArt IBDAC can't.

  • Like 1

Share this post


Link to post
13 hours ago, tgbs said:

function RunProcess(CommandLine: String; Var ErrMessage : String; WaitForProcess : Boolean; CreationFlags : Word): Boolean;
Var
  ResultCode : LongWord;
  StartInfo: TStartUpInfo;
  ProcInfo: TProcessInformation;
begin
  Result := False;
  ResultCode := 1;

  FillChar(StartInfo, SizeOf(TStartUpInfo), #0);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
  StartInfo.cb := SizeOf(TStartUpInfo);
  StartInfo.dwFlags := STARTF_USESHOWWINDOW;
 // StartInfo.wShowWindow := SW_SHOWMINIMIZED;

  UniqueString(CommandLine);
try
  try
    IF NOT CreateProcess(nil, 
                   PChar(CommandLine),
                   nil,               
                   nil,               
                   FALSE,             
                   CreationFlags,     
                   nil,               
                   nil,               
                   StartInfo,         
                   ProcInfo)          
       THEN BEGIN
         ErrMessage := SysErrorMessage(GetLastError);
         Exit;
       END;
  except
    on e : exception do
      begin
        ErrMessage := e.Message + #13#10 + SysErrorMessage(GetLastError);
        Exit;
      end;
  end;

  if not WaitForProcess then ResultCode := 0
    else begin
      WaitForSingleObject(ProcInfo.hProcess, INFINITE);
      GetExitCodeProcess(ProcInfo.hProcess, ResultCode);
    end;
finally
  Result := (ResultCode = 0);
  //--------
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread);
end;
end;

Set CommandLine to somehing like  @corneliusdavid comment

This code is OK; I made some modifications, but I have one small problem.

When I call a command from Windows cmd everything is working fine, but over this function there are no executions; just blink the app, and nothing happens.

What can be wrong?

P.S. I chanced working folder to Firebird execution and run as administrator but nothing over the app; over Windows cmd works fine with the same command.

Share this post


Link to post

Hello,

 

On 10/28/2024 at 4:39 AM, corneliusdavid said:

You need to have the client tools installed on the remote machine, then use gbak like this:


gbak -b -user SYSDBA -password masterkey server:/path/to/database.fdb client:/path/to/backup.fbk

 

There is a -service option you have to use Remote Backup & Restore

 

Share this post


Link to post

Hello,

 

I have client tools on the server and client side, and the backup command is working fine when I call it from Windows cmd.

When I call the over function from the Delphi app, I just get cmd blink, and there are no backup files or any kind of errors.

Command is working fine from cmd but fails from Delphi.

Share this post


Link to post
  // FileName - full path to executable
  // Params - command line parameters or use empty string
  // Folder - working folder for called program - if empty path will be extracted from FileName
  // WaitUntilTerminated - if true function will wait for process to finish execution
  // WaitUntilIdle - if true function will call WaitForInputIdle function and wait until the specified process has finished processing its initial input and until there is no user input pending
  // RunMinimized - if true process will be run minimized
  // ErrorCode - if function fails this will contain encountered Windows Error Code

  function ExecuteProcess(const FileName, Params: string; Folder: string; WaitUntilTerminated, WaitUntilIdle, RunMinimized: boolean; var ErrorCode: integer): boolean;
  var
    CmdLine: string;
    WorkingDirP: PChar;
    StartupInfo: TStartupInfo;
    ProcessInfo: TProcessInformation;
  begin
    Result := true;
    CmdLine := '"' + FileName + '" ' + Params;
    if Folder = '' then
      Folder := ExcludeTrailingPathDelimiter(ExtractFilePath(FileName));
    ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
    StartupInfo.cb := SizeOf(StartupInfo);
    if RunMinimized then
    begin
      StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
      StartupInfo.wShowWindow := SW_SHOWMINIMIZED;
    end;
    if Folder <> '' then
      WorkingDirP := PChar(Folder)
    else
      WorkingDirP := nil;
    if not CreateProcess(nil, PChar(CmdLine), nil, nil, false, 0, nil, WorkingDirP, StartupInfo, ProcessInfo) then
    begin
      Result := false;
      ErrorCode := GetLastError;
      exit;
    end;
    with ProcessInfo do
    begin
      CloseHandle(hThread);
      if WaitUntilIdle then
        WaitForInputIdle(hProcess, INFINITE);
      if WaitUntilTerminated then
        repeat
          Application.ProcessMessages;
        until MsgWaitForMultipleObjects(1, hProcess, false, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0 + 1;
      CloseHandle(hProcess);
    end;
  end;

 

  FileName := Server.libPath + 'gbak.exe';
  WorkingFolder := Server.libPath; 
  Parameters := PChar(' -b -par 9 -se ' + PChar(Server.dbHost + ':gds_db') + ' -user SYSDBA -pass masterkey ' + PChar(db.fdbPath + db.fdbFile) + ' stdout > ' + PChar(db.fbkFile));
  OK := ExecuteProcess(FileName, Parameters, WorkingFolder, true, true, true, Error);

 

Share this post


Link to post
UniqueString(CmdLine);

Before CreateProcess ?

  • Like 1

Share this post


Link to post

Since the params parameter in the ExecuteProcess function is string, I would not use PChar casting in Parameters variable; use strings. And for testing I would try it without "stdout > ...". And I would try to use the path to the DB insterad of "gds_db".

  • Like 1

Share this post


Link to post

With UniqueString(CmdLine); + removing PChar, everything is working fine, but without a service call...

Share this post


Link to post

I've been doing this for a very long time, so surely I can share some experience.

 

Yes, you can use a backup component that employs the built-in service manager. In my case that's TIBOBackupService from IBObjects. The key is that you need to name the output file 'stdout' and have the option to capture that. This component does; I haven't looked into others. Most have similar methods, though. Keep in mind that it produces binary, so I wrote a subclass to handle that:

type
  TIBOBinaryCapableBackupService = class(TIBOBackupService)
  public
    function GetNextBinaryChunk: TBytes;
  end;

implementation

{ TIBOBinaryCapableBackupService }

function TIBOBinaryCapableBackupService.GetNextBinaryChunk: TBytes;
var
  LLength: Integer;
begin
  Result := nil;
  if EOF then
    Exit;

  GetNextChunk; // Fill buffer
  LLength := IB_Session.isc_vax_integer(Pointer(OutputBuffer + 1), 2);
  SetLength(Result, LLength);
  Move(OutputBuffer[3], Pointer(Result)^, LLength);
end;

The alternative is indeed having gbak on the client. Eventually I switched to that, because I wanted to be able to use a newer version (3.0) than the server itself (2.5) – that way I could exclude certain tables. Much of the code was inspired by @David Heffernan in my question here.

Edited by Thijs van Dien

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×