1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2003-2011 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 Planets Communications B.V.
6 Copyright (C) 2013-2020 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23
24 #include "include/bareos.h"
25
26 #ifdef HAVE_POSTGRESQL
27
28 # include "cats.h"
29 # include "libpq-fe.h"
30 # include "postgres_ext.h" /* needed for NAMEDATALEN */
31 # include "pg_config_manual.h" /* get NAMEDATALEN on version 8.3 or later */
32 # include "bdb_postgresql.h"
33 # include "lib/edit.h"
34 # include "lib/berrno.h"
35 # include "lib/dlist.h"
36
SqlBatchStartFileTable(JobControlRecord * jcr)37 bool BareosDbPostgresql::SqlBatchStartFileTable(JobControlRecord* jcr)
38 {
39 const char* query = "COPY batch FROM STDIN";
40
41 Dmsg0(500, "SqlBatchStartFileTable started\n");
42
43 if (!SqlQueryWithoutHandler("CREATE TEMPORARY TABLE batch ("
44 "FileIndex int,"
45 "JobId int,"
46 "Path varchar,"
47 "Name varchar,"
48 "LStat varchar,"
49 "Md5 varchar,"
50 "DeltaSeq smallint,"
51 "Fhinfo NUMERIC(20),"
52 "Fhnode NUMERIC(20))")) {
53 Dmsg0(500, "SqlBatchStartFileTable failed\n");
54 return false;
55 }
56
57 /*
58 * We are starting a new query. reset everything.
59 */
60 num_rows_ = -1;
61 row_number_ = -1;
62 field_number_ = -1;
63
64 SqlFreeResult();
65
66 for (int i = 0; i < 10; i++) {
67 result_ = PQexec(db_handle_, query);
68 if (result_) { break; }
69 Bmicrosleep(5, 0);
70 }
71 if (!result_) {
72 Dmsg1(50, "Query failed: %s\n", query);
73 goto bail_out;
74 }
75
76 status_ = PQresultStatus(result_);
77 if (status_ == PGRES_COPY_IN) {
78 /*
79 * How many fields in the set?
80 */
81 num_fields_ = (int)PQnfields(result_);
82 num_rows_ = 0;
83 status_ = 1;
84 } else {
85 Dmsg1(50, "Result status failed: %s\n", query);
86 goto bail_out;
87 }
88
89 Dmsg0(500, "SqlBatchStartFileTable finishing\n");
90
91 return true;
92
93 bail_out:
94 Mmsg1(errmsg, _("error starting batch mode: %s"), PQerrorMessage(db_handle_));
95 status_ = 0;
96 PQclear(result_);
97 result_ = NULL;
98 return false;
99 }
100
101 /**
102 * Set error to something to abort operation
103 */
SqlBatchEndFileTable(JobControlRecord * jcr,const char * error)104 bool BareosDbPostgresql::SqlBatchEndFileTable(JobControlRecord* jcr,
105 const char* error)
106 {
107 int res;
108 int count = 30;
109 PGresult* pg_result;
110
111 Dmsg0(500, "SqlBatchEndFileTable started\n");
112
113 do {
114 res = PQputCopyEnd(db_handle_, error);
115 } while (res == 0 && --count > 0);
116
117 if (res == 1) {
118 Dmsg0(500, "ok\n");
119 status_ = 1;
120 }
121
122 if (res <= 0) {
123 Dmsg0(500, "we failed\n");
124 status_ = 0;
125 Mmsg1(errmsg, _("error ending batch mode: %s"), PQerrorMessage(db_handle_));
126 Dmsg1(500, "failure %s\n", errmsg);
127 }
128
129 /*
130 * Check command status and return to normal libpq state
131 */
132 pg_result = PQgetResult(db_handle_);
133 if (PQresultStatus(pg_result) != PGRES_COMMAND_OK) {
134 Mmsg1(errmsg, _("error ending batch mode: %s"), PQerrorMessage(db_handle_));
135 status_ = 0;
136 }
137
138 PQclear(pg_result);
139
140 Dmsg0(500, "SqlBatchEndFileTable finishing\n");
141
142 return true;
143 }
144
145 /**
146 * Escape strings so that PostgreSQL is happy on COPY
147 *
148 * NOTE! len is the length of the old string. Your new
149 * string must be long enough (max 2*old+1) to hold
150 * the escaped output.
151 */
pgsql_copy_escape(char * dest,const char * src,size_t len)152 static char* pgsql_copy_escape(char* dest, const char* src, size_t len)
153 {
154 char c = '\0';
155
156 while (len > 0 && *src) {
157 switch (*src) {
158 case '\b':
159 c = 'b';
160 break;
161 case '\f':
162 c = 'f';
163 break;
164 case '\n':
165 c = 'n';
166 break;
167 case '\\':
168 c = '\\';
169 break;
170 case '\t':
171 c = 't';
172 break;
173 case '\r':
174 c = 'r';
175 break;
176 case '\v':
177 c = 'v';
178 break;
179 case '\'':
180 c = '\'';
181 break;
182 default:
183 c = '\0';
184 break;
185 }
186
187 if (c) {
188 *dest = '\\';
189 dest++;
190 *dest = c;
191 } else {
192 *dest = *src;
193 }
194
195 len--;
196 src++;
197 dest++;
198 }
199
200 *dest = '\0';
201 return dest;
202 }
203
SqlBatchInsertFileTable(JobControlRecord * jcr,AttributesDbRecord * ar)204 bool BareosDbPostgresql::SqlBatchInsertFileTable(JobControlRecord* jcr,
205 AttributesDbRecord* ar)
206 {
207 int res;
208 int count = 30;
209 size_t len;
210 const char* digest;
211 char ed1[50], ed2[50], ed3[50];
212
213 esc_name = CheckPoolMemorySize(esc_name, fnl * 2 + 1);
214 pgsql_copy_escape(esc_name, fname, fnl);
215
216 esc_path = CheckPoolMemorySize(esc_path, pnl * 2 + 1);
217 pgsql_copy_escape(esc_path, path, pnl);
218
219 if (ar->Digest == NULL || ar->Digest[0] == 0) {
220 digest = "0";
221 } else {
222 digest = ar->Digest;
223 }
224
225 len = Mmsg(cmd, "%u\t%s\t%s\t%s\t%s\t%s\t%u\t%s\t%s\n", ar->FileIndex,
226 edit_int64(ar->JobId, ed1), esc_path, esc_name, ar->attr, digest,
227 ar->DeltaSeq, edit_uint64(ar->Fhinfo, ed2),
228 edit_uint64(ar->Fhnode, ed3));
229
230 do {
231 res = PQputCopyData(db_handle_, cmd, len);
232 } while (res == 0 && --count > 0);
233
234 if (res == 1) {
235 Dmsg0(500, "ok\n");
236 changes++;
237 status_ = 1;
238 }
239
240 if (res <= 0) {
241 Dmsg0(500, "we failed\n");
242 status_ = 0;
243 Mmsg1(errmsg, _("error copying in batch mode: %s"),
244 PQerrorMessage(db_handle_));
245 Dmsg1(500, "failure %s\n", errmsg);
246 }
247
248 Dmsg0(500, "SqlBatchInsertFileTable finishing\n");
249
250 return true;
251 }
252
253
254 /* ************************************* *
255 * ** Generic SQL Copy used by dbcopy ** *
256 * ************************************* */
257
258 class CleanupResult {
259 public:
CleanupResult(PGresult ** r,int * s)260 CleanupResult(PGresult** r, int* s) : result(r), status(s) {}
release()261 void release() { do_cleanup = false; }
262
~CleanupResult()263 ~CleanupResult()
264 {
265 if (do_cleanup) {
266 *status = 0;
267 PQclear(*result);
268 *result = nullptr;
269 }
270 }
271
272 private:
273 PGresult** result;
274 int* status;
275 bool do_cleanup{true};
276 };
277
SqlCopyStart(const std::string & table_name,const std::vector<std::string> & column_names)278 bool BareosDbPostgresql::SqlCopyStart(
279 const std::string& table_name,
280 const std::vector<std::string>& column_names)
281 {
282 CleanupResult result_cleanup(&result_, &status_);
283
284 num_rows_ = -1;
285 row_number_ = -1;
286 field_number_ = -1;
287
288 SqlFreeResult();
289
290 std::string query{"COPY " + table_name + " ("};
291
292 for (const auto& column_name : column_names) {
293 query += column_name;
294 query += ", ";
295 }
296 query.resize(query.size() - 2);
297 query +=
298 ") FROM STDIN WITH ("
299 " FORMAT text"
300 ", DELIMITER '\t'"
301 ")";
302
303 result_ = PQexec(db_handle_, query.c_str());
304 if (!result_) {
305 Mmsg1(errmsg, _("error copying in batch mode: %s"),
306 PQerrorMessage(db_handle_));
307 return false;
308 }
309
310 status_ = PQresultStatus(result_);
311 if (status_ != PGRES_COPY_IN) {
312 Mmsg1(errmsg, _("Result status failed: %s"), PQerrorMessage(db_handle_));
313 return false;
314 }
315
316 std::size_t n = (int)PQnfields(result_);
317 if (n != column_names.size()) {
318 Mmsg1(errmsg, _("wrong number of rows: %d"), n);
319 return false;
320 }
321
322 num_rows_ = 0;
323 status_ = 1;
324
325 result_cleanup.release();
326 return true;
327 }
328
SqlCopyInsert(const std::vector<DatabaseField> & data_fields)329 bool BareosDbPostgresql::SqlCopyInsert(
330 const std::vector<DatabaseField>& data_fields)
331 {
332 CleanupResult result_cleanup(&result_, &status_);
333
334 std::string query;
335
336 std::vector<char> buffer;
337 for (const auto& field : data_fields) {
338 if (strlen(field.data_pointer) != 0U) {
339 buffer.resize(strlen(field.data_pointer) * 2 + 1);
340 pgsql_copy_escape(buffer.data(), field.data_pointer, buffer.size());
341 query += buffer.data();
342 }
343 query += "\t";
344 }
345 query.resize(query.size() - 1);
346 query += "\n";
347
348 int res = 0;
349 int count = 30;
350
351 do {
352 res = PQputCopyData(db_handle_, query.data(), query.size());
353 } while (res == 0 && --count > 0);
354
355 if (res == 1) { status_ = 1; }
356
357 if (res <= 0) {
358 status_ = 0;
359 Mmsg1(errmsg, _("error copying in batch mode: %s"),
360 PQerrorMessage(db_handle_));
361 }
362 return true;
363 }
364
SqlCopyEnd()365 bool BareosDbPostgresql::SqlCopyEnd()
366 {
367 int res;
368 int count = 30;
369
370 CleanupResult result_cleanup(&result_, &status_);
371
372 do {
373 res = PQputCopyEnd(db_handle_, nullptr);
374 } while (res == 0 && --count > 0);
375
376 if (res <= 0) {
377 Mmsg1(errmsg, _("error ending batch mode: %s"), PQerrorMessage(db_handle_));
378 return false;
379 }
380
381 status_ = 1;
382
383 result_ = PQgetResult(db_handle_);
384 if (PQresultStatus(result_) != PGRES_COMMAND_OK) {
385 Mmsg1(errmsg, _("error ending batch mode: %s"), PQerrorMessage(db_handle_));
386 return false;
387 }
388
389 result_cleanup.release();
390 return true;
391 }
392
393
394 #endif // HAVE_POSTGRESQL
395