1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2020-2020 Bareos GmbH & Co. KG
5 
6    This program is Free Software; you can redistribute it and/or
7    modify it under the terms of version three of the GNU Affero General Public
8    License as published by the Free Software Foundation, which is
9    listed in the file LICENSE.
10 
11    This program is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14    Affero General Public License for more details.
15 
16    You should have received a copy of the GNU Affero General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19    02110-1301, USA.
20 */
21 
22 #include "include/bareos.h"
23 #include "cats/cats.h"
24 #include "cats/cats_backends.h"
25 #include "cats/sql_pooling.h"
26 #include "dird/dbcopy/database_connection.h"
27 #include "dird/dbcopy/database_export.h"
28 #include "dird/dbcopy/database_export_postgresql.h"
29 #include "dird/dbcopy/database_import.h"
30 #include "dird/dird_conf.h"
31 #include "dird/dird_globals.h"
32 #include "dird/get_database_connection.h"
33 #include "dird/jcr_private.h"
34 #include "dird/job.h"
35 #include "include/make_unique.h"
36 #include "lib/parse_conf.h"
37 #include "lib/util.h"
38 
39 #include <array>
40 #include <iostream>
41 
42 #if !defined HAVE_DYNAMIC_CATS_BACKENDS
43 #error "NOT DEFINED: HAVE_DYNAMIC_CATS_BACKENDS"
44 #endif
45 
46 namespace directordaemon {
DoReloadConfig()47 bool DoReloadConfig() { return false; }
48 }  // namespace directordaemon
49 
50 class DbCopy {
51  public:
DbCopy(int argc,char ** argv)52   explicit DbCopy(int argc, char** argv)
53   {
54     InitMsg(nullptr, nullptr);
55     SetWorkingDir();
56     cl.ParseCommandLine(argc, argv);
57 
58     ParseConfig();
59     std::cout << "Copying tables from \"" << cl.source_db_resource_name
60               << "\" to \"" << cl.destination_db_resource_name << "\""
61               << std::endl;
62     ConnectToDatabases();
63   }
64 
DoDatabaseCopy()65   void DoDatabaseCopy()
66   {
67     std::cout << "gathering information about source catalog \""
68               << cl.source_db_resource_name << "\"..." << std::endl;
69     std::unique_ptr<DatabaseImport> imp(
70         DatabaseImport::Create(*source_db_, cl.maximum_number_of_rows));
71 
72     std::cout << "gathering information about destination catalog \""
73               << cl.destination_db_resource_name << "\"..." << std::endl;
74     std::unique_ptr<DatabaseExport> exp(
75         DatabaseExport::Create(*destination_db_,
76                                cl.use_sql_insert_statements_instead_of_copy
77                                    ? DatabaseExport::InsertMode::kSqlInsert
78                                    : DatabaseExport::InsertMode::kSqlCopy,
79                                cl.empty_destination_tables));
80 
81     std::cout << "copying tables..." << std::endl;
82     imp->ExportTo(*exp);
83   }
84 
85  private:
SetWorkingDir()86   void SetWorkingDir()
87   {
88     if (getcwd(current_working_directory_.data(),
89                current_working_directory_.size()) == nullptr) {
90       throw std::runtime_error(
91           "Could not determine current working directory.");
92     }
93     SetWorkingDirectory(current_working_directory_.data());
94   }
95 
ParseConfig()96   void ParseConfig()
97   {
98     directordaemon::my_config =
99         directordaemon::InitDirConfig(cl.configpath_.c_str(), M_ERROR_TERM);
100 
101     my_config_.reset(directordaemon::my_config);
102 
103     if (!directordaemon::my_config->ParseConfig()) {
104       throw std::runtime_error("Error when loading config.");
105     }
106 
107     directordaemon::me = dynamic_cast<directordaemon::DirectorResource*>(
108         my_config->GetNextRes(directordaemon::R_DIRECTOR, nullptr));
109 
110     if (directordaemon::me == nullptr) {
111       throw std::runtime_error("Could not find director resource.");
112     }
113 
114     DbSetBackendDirs(directordaemon::me->backend_directories);
115   }
116 
ConnectToDatabases()117   void ConnectToDatabases()
118   {
119     try {
120       source_db_ = std::make_unique<DatabaseConnection>(
121           cl.source_db_resource_name, directordaemon::my_config);
122 
123       destination_db_ = std::make_unique<DatabaseConnection>(
124           cl.destination_db_resource_name, directordaemon::my_config);
125 
126       if (source_db_->db_type != DatabaseType::Enum::kMysql) {
127         throw std::runtime_error("Error: Source database is not mysql");
128       }
129 
130       if (destination_db_->db_type != DatabaseType::Enum::kPostgresql) {
131         throw std::runtime_error(
132             "Error: Destination database is not postgresql");
133       }
134 
135     } catch (const std::runtime_error& e) {
136       throw e;
137     }
138   }
139 
140   class CommandLineParser {
141     friend class DbCopy;
142 
143    public:
ParseCommandLine(int argc,char ** argv)144     void ParseCommandLine(int argc, char** argv)
145     {
146       int c{};
147       bool options_error{false};
148       int argument_count{};
149 
150       while ((c = getopt(argc, argv, "ic:l:?")) != -1 && !options_error) {
151         switch (c) {
152           case 'c':
153             configpath_ = optarg;
154             argument_count += 2;
155             break;
156           case 'i':
157             use_sql_insert_statements_instead_of_copy = true;
158             argument_count += 1;
159             break;
160 #if 0
161           case 'd':
162             empty_destination_tables = true;
163             ++argument_count;
164             break;
165 #endif
166           case 'l':
167             try {
168               maximum_number_of_rows = std::stoul(optarg);
169             } catch (...) {
170               throw std::runtime_error("Wrong argument for 'l'");
171             }
172             argument_count += 2;
173             break;
174           case '?':
175           default:
176             options_error = true;
177             break;
178         }
179       }
180 
181       ++argument_count;  // program name
182       ++argument_count;  // source catalog name
183       ++argument_count;  // destination catalog name
184 
185       if (options_error || argc != argument_count) {
186         usage();
187         throw std::runtime_error(std::string());
188       }
189       source_db_resource_name = argv[argument_count - 2];
190       destination_db_resource_name = argv[argument_count - 1];
191     }
192 
193    private:
194     std::string configpath_{"/etc/bareos"};
195     std::string source_db_resource_name, destination_db_resource_name;
196     bool empty_destination_tables{false};
197     bool use_sql_insert_statements_instead_of_copy{false};
198     std::size_t maximum_number_of_rows{};
199     static constexpr std::size_t year_of_release = 2020;
200 
usage()201     static void usage() noexcept
202     {
203       kBareosVersionStrings.PrintCopyright(stderr, year_of_release);
204 
205       fprintf(
206           stderr,
207           _("Usage: bareos-dbcopy [options] Source Destination\n"
208             "        -c <path>   use <path> as configuration file or "
209             "directory\n"
210             "        -i          use SQL INSERT statements instead of COPY\n"
211             "        -?          print this message.\n"
212             "\n"));
213     }
214   };  // class CommandLineParser
215 
216  public:
217   ~DbCopy() = default;
218   DbCopy(const DbCopy& other) = delete;
219   DbCopy(const DbCopy&& other) = delete;
220   DbCopy& operator=(const DbCopy& rhs) = delete;
221   DbCopy& operator=(const DbCopy&& rhs) = delete;
222 
223  private:
224   CommandLineParser cl;
225   std::unique_ptr<DatabaseConnection> source_db_;
226   std::unique_ptr<DatabaseConnection> destination_db_;
227   std::unique_ptr<ConfigurationParser> my_config_;
228   static constexpr std::size_t sizeof_workingdir = 10 * 1024;
229   std::array<char, sizeof_workingdir> current_working_directory_{};
230 };
231 
232 class Cleanup {
233  public:
234   Cleanup() = default;
~Cleanup()235   ~Cleanup()
236   {
237     DbSqlPoolDestroy();
238     DbFlushBackends();
239   }
240 };
241 
main(int argc,char ** argv)242 int main(int argc, char** argv)
243 {
244   Cleanup c;
245 
246   try {
247     DbCopy dbcopy(argc, argv);
248     dbcopy.DoDatabaseCopy();
249   } catch (const std::runtime_error& e) {
250     std::string errstring{e.what()};
251     if (!errstring.empty()) {
252       std::cerr << std::endl << std::endl << e.what() << std::endl;
253     }
254     return 1;
255   }
256   std::cout << "database copy completed successfully" << std::endl;
257   return 0;
258 }
259