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