1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2009-2010 Free Software Foundation Europe e.V.
5    Copyright (C) 2016-2016 Planets Communications B.V.
6    Copyright (C) 2016-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, which is
11    listed 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  * @file
25  * bareos virtual filesystem layer
26  */
27 #include "include/bareos.h"
28 
29 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI
30 
31 #  include "cats/cats.h"
32 #  include "cats/sql.h"
33 #  include "lib/htable.h"
34 #  include "cats/bvfs.h"
35 #  include "lib/edit.h"
36 
37 #  define dbglevel 10
38 #  define dbglevel_sql 15
39 
40 /*
41  * Working Object to store PathId already seen (avoid database queries),
42  */
43 #  define NITEMS 50000
44 class pathid_cache {
45  private:
46   hlink* nodes;
47   int nb_node;
48   int max_node;
49   alist* table_node;
50   htable* cache_ppathid;
51 
52  public:
pathid_cache()53   pathid_cache()
54   {
55     hlink link;
56     cache_ppathid = (htable*)malloc(sizeof(htable));
57     cache_ppathid->init(&link, &link, NITEMS);
58     max_node = NITEMS;
59     nodes = (hlink*)malloc(max_node * sizeof(hlink));
60     nb_node = 0;
61     table_node = new alist(5, owned_by_alist);
62     table_node->append(nodes);
63   }
64 
get_hlink()65   hlink* get_hlink()
66   {
67     if (++nb_node >= max_node) {
68       nb_node = 0;
69       nodes = (hlink*)malloc(max_node * sizeof(hlink));
70       table_node->append(nodes);
71     }
72     return nodes + nb_node;
73   }
74 
lookup(char * pathid)75   bool lookup(char* pathid) { return (cache_ppathid->lookup(pathid) != NULL); }
76 
insert(char * pathid)77   void insert(char* pathid)
78   {
79     hlink* h = get_hlink();
80     cache_ppathid->insert(pathid, h);
81   }
82 
~pathid_cache()83   ~pathid_cache()
84   {
85     cache_ppathid->destroy();
86     free(cache_ppathid);
87     delete table_node;
88   }
89 
90  private:
91   pathid_cache(const pathid_cache&);            /* prohibit pass by value */
92   pathid_cache& operator=(const pathid_cache&); /* prohibit class assignment*/
93 };
94 
95 /*
96  * Generic path handlers used for database queries.
97  */
GetPathHandler(void * ctx,int fields,char ** row)98 static int GetPathHandler(void* ctx, int fields, char** row)
99 {
100   PoolMem* buf = (PoolMem*)ctx;
101 
102   PmStrcpy(*buf, row[0]);
103 
104   return 0;
105 }
106 
PathHandler(void * ctx,int fields,char ** row)107 static int PathHandler(void* ctx, int fields, char** row)
108 {
109   Bvfs* fs = (Bvfs*)ctx;
110 
111   return fs->_handlePath(ctx, fields, row);
112 }
113 
114 /*
115  * BVFS specific methods part of the BareosDb database abstraction.
116  */
BuildPathHierarchy(JobControlRecord * jcr,pathid_cache & ppathid_cache,char * org_pathid,char * new_path)117 void BareosDb::BuildPathHierarchy(JobControlRecord* jcr,
118                                   pathid_cache& ppathid_cache,
119                                   char* org_pathid,
120                                   char* new_path)
121 {
122   char pathid[50];
123   AttributesDbRecord parent;
124   char* bkp = path;
125 
126   Dmsg1(dbglevel, "BuildPathHierarchy(%s)\n", new_path);
127   bstrncpy(pathid, org_pathid, sizeof(pathid));
128 
129   /*
130    * Does the ppathid exist for this? use a memory cache ...
131    * In order to avoid the full loop, we consider that if a dir is already in
132    * the PathHierarchy table, then there is no need to calculate all the
133    * hierarchy
134    */
135   while (new_path && *new_path) {
136     if (ppathid_cache.lookup(pathid)) {
137       /*
138        * It's already in the cache.  We can leave, no time to waste here,
139        * all the parent dirs have already been done
140        */
141       goto bail_out;
142     } else {
143       Mmsg(cmd, "SELECT PPathId FROM PathHierarchy WHERE PathId = %s", pathid);
144 
145       if (!QUERY_DB(jcr, cmd)) { goto bail_out; /* Query failed, just leave */ }
146 
147       /*
148        * Do we have a result ?
149        */
150       if (SqlNumRows() > 0) {
151         ppathid_cache.insert(pathid);
152         /* This dir was in the db ...
153          * It means we can leave, the tree has already been built for
154          * this dir
155          */
156         goto bail_out;
157       } else {
158         /*
159          * Search or create parent PathId in Path table
160          */
161         path = bvfs_parent_dir(new_path);
162         pnl = strlen(path);
163 
164         if (!CreatePathRecord(jcr, &parent)) { goto bail_out; }
165         ppathid_cache.insert(pathid);
166 
167         Mmsg(cmd,
168              "INSERT INTO PathHierarchy (PathId, PPathId) VALUES (%s,%lld)",
169              pathid, (uint64_t)parent.PathId);
170         if (!INSERT_DB(jcr, cmd)) {
171           goto bail_out; /* Can't insert the record, just leave */
172         }
173 
174         edit_uint64(parent.PathId, pathid);
175         /* continue with parent directory */
176         new_path = path;
177       }
178     }
179   }
180 
181 bail_out:
182   path = bkp;
183   fnl = 0;
184 }
185 
186 /**
187  * Internal function to update path_hierarchy cache with a shared pathid cache
188  * return Error 0
189  *        OK    1
190  */
UpdatePathHierarchyCache(JobControlRecord * jcr,pathid_cache & ppathid_cache,JobId_t JobId)191 bool BareosDb::UpdatePathHierarchyCache(JobControlRecord* jcr,
192                                         pathid_cache& ppathid_cache,
193                                         JobId_t JobId)
194 {
195   Dmsg0(dbglevel, "UpdatePathHierarchyCache()\n");
196   bool retval = false;
197   uint32_t num;
198   char jobid[50];
199   edit_uint64(JobId, jobid);
200 
201   DbLock(this);
202   StartTransaction(jcr);
203 
204   Mmsg(cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
205 
206   if (!QUERY_DB(jcr, cmd) || SqlNumRows() > 0) {
207     Dmsg1(dbglevel, "Already computed %d\n", (uint32_t)JobId);
208     retval = true;
209     goto bail_out;
210   }
211 
212   /* prevent from DB lock waits when already in progress */
213   Mmsg(cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=-1", jobid);
214 
215   if (!QUERY_DB(jcr, cmd) || SqlNumRows() > 0) {
216     Dmsg1(dbglevel, "already in progress %d\n", (uint32_t)JobId);
217     retval = false;
218     goto bail_out;
219   }
220 
221   /* set HasCache to -1 in Job (in progress) */
222   Mmsg(cmd, "UPDATE Job SET HasCache=-1 WHERE JobId=%s", jobid);
223   UPDATE_DB(jcr, cmd);
224 
225   /*
226    * need to COMMIT here to ensure that other concurrent .bvfs_update runs
227    * see the current HasCache value. A new transaction must only be started
228    * after having finished PathHierarchy processing, otherwise prevention
229    * from duplicate key violations in BuildPathHierarchy() will not work.
230    */
231   EndTransaction(jcr);
232 
233   /* Inserting path records for JobId */
234   Mmsg(cmd,
235        "INSERT INTO PathVisibility (PathId, JobId) "
236        "SELECT DISTINCT PathId, JobId "
237        "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
238        "UNION "
239        "SELECT PathId, BaseFiles.JobId "
240        "FROM BaseFiles JOIN File AS F USING (FileId) "
241        "WHERE BaseFiles.JobId = %s) AS B",
242        jobid, jobid);
243 
244   if (!QUERY_DB(jcr, cmd)) {
245     Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId);
246     goto bail_out;
247   }
248 
249   /*
250    * Now we have to do the directory recursion stuff to determine missing
251    * visibility.
252    * We try to avoid recursion, to be as fast as possible.
253    * We also only work on not already hierarchised directories ...
254    */
255   Mmsg(cmd,
256        "SELECT PathVisibility.PathId, Path "
257        "FROM PathVisibility "
258        "JOIN Path ON (PathVisibility.PathId = Path.PathId) "
259        "LEFT JOIN PathHierarchy "
260        "ON (PathVisibility.PathId = PathHierarchy.PathId) "
261        "WHERE PathVisibility.JobId = %s "
262        "AND PathHierarchy.PathId IS NULL "
263        "ORDER BY Path",
264        jobid);
265 
266   if (!QUERY_DB(jcr, cmd)) {
267     Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId);
268     goto bail_out;
269   }
270 
271   /* TODO: I need to reuse the DB connection without emptying the result
272    * So, now i'm copying the result in memory to be able to query the
273    * catalog descriptor again.
274    */
275   num = SqlNumRows();
276   if (num > 0) {
277     char** result = (char**)malloc(num * 2 * sizeof(char*));
278 
279     SQL_ROW row;
280     int i = 0;
281     while ((row = SqlFetchRow())) {
282       result[i++] = strdup(row[0]);
283       result[i++] = strdup(row[1]);
284     }
285 
286     /* The PathHierarchy table needs exclusive write lock here to
287      * prevent from unique key constraint violations (PostgreSQL)
288      * or duplicate entry errors (MySQL/MariaDB) when multiple
289      * bvfs update operations are run simultaneously.
290      */
291     FillQuery(cmd, SQL_QUERY::bvfs_lock_pathhierarchy_0);
292     if (!QUERY_DB(jcr, cmd)) { goto bail_out; }
293 
294     i = 0;
295     while (num > 0) {
296       BuildPathHierarchy(jcr, ppathid_cache, result[i], result[i + 1]);
297       free(result[i++]);
298       free(result[i++]);
299       num--;
300     }
301     free(result);
302 
303     FillQuery(cmd, SQL_QUERY::bvfs_unlock_tables_0);
304     if (!QUERY_DB(jcr, cmd)) { goto bail_out; }
305   }
306 
307   StartTransaction(jcr);
308 
309   FillQuery(cmd, SQL_QUERY::bvfs_update_path_visibility_3, jobid, jobid, jobid);
310 
311   do {
312     retval = QUERY_DB(jcr, cmd);
313   } while (retval && SqlAffectedRows() > 0);
314 
315   Mmsg(cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
316   UPDATE_DB(jcr, cmd);
317 
318 bail_out:
319   EndTransaction(jcr);
320   DbUnlock(this);
321 
322   return retval;
323 }
324 
BvfsUpdateCache(JobControlRecord * jcr)325 void BareosDb::BvfsUpdateCache(JobControlRecord* jcr)
326 {
327   uint32_t nb = 0;
328   db_list_ctx jobids_list;
329 
330   DbLock(this);
331 
332   Mmsg(cmd,
333        "SELECT JobId from Job "
334        "WHERE HasCache = 0 "
335        "AND Type IN ('B') AND JobStatus IN ('T', 'W', 'f', 'A') "
336        "ORDER BY JobId");
337   SqlQuery(cmd, DbListHandler, &jobids_list);
338 
339   BvfsUpdatePathHierarchyCache(jcr, jobids_list.GetAsString().c_str());
340 
341   StartTransaction(jcr);
342   Dmsg0(dbglevel, "Cleaning pathvisibility\n");
343   Mmsg(cmd,
344        "DELETE FROM PathVisibility "
345        "WHERE NOT EXISTS "
346        "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
347   nb = DELETE_DB(jcr, cmd);
348   Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
349   EndTransaction(jcr);
350 
351   DbUnlock(this);
352 }
353 
354 /*
355  * Update the bvfs cache for given jobids (1,2,3,4)
356  */
BvfsUpdatePathHierarchyCache(JobControlRecord * jcr,const char * jobids)357 bool BareosDb::BvfsUpdatePathHierarchyCache(JobControlRecord* jcr,
358                                             const char* jobids)
359 {
360   const char* p;
361   int status;
362   JobId_t JobId;
363   bool retval = true;
364   pathid_cache ppathid_cache;
365 
366   p = jobids;
367   while (1) {
368     status = GetNextJobidFromList(&p, &JobId);
369     if (status < 0) { goto bail_out; }
370 
371     if (status == 0) {
372       /*
373        * We reached the end of the list.
374        */
375       goto bail_out;
376     }
377 
378     Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
379     if (!UpdatePathHierarchyCache(jcr, ppathid_cache, JobId)) {
380       retval = false;
381     }
382   }
383 
384 bail_out:
385   return retval;
386 }
387 
BvfsLsDirs(PoolMem & query,void * ctx)388 int BareosDb::BvfsLsDirs(PoolMem& query, void* ctx)
389 {
390   int nb_record = 0;
391 
392   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
393 
394   DbLock(this);
395 
396   SqlQuery(query.c_str(), PathHandler, ctx);
397   // FIXME: SqlNumRows() is always 0 after SqlQuery.
398   // nb_record = SqlNumRows();
399 
400   DbUnlock(this);
401 
402   return nb_record;
403 }
404 
BvfsBuildLsFileQuery(PoolMem & query,DB_RESULT_HANDLER * ResultHandler,void * ctx)405 int BareosDb::BvfsBuildLsFileQuery(PoolMem& query,
406                                    DB_RESULT_HANDLER* ResultHandler,
407                                    void* ctx)
408 {
409   int nb_record = 0;
410 
411   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
412 
413   DbLock(this);
414   SqlQuery(query.c_str(), ResultHandler, ctx);
415   // FIXME: SqlNumRows() is always 0 after SqlQuery.
416   // nb_record = SqlNumRows();
417   DbUnlock(this);
418 
419   return nb_record;
420 }
421 
422 /*
423  * Generic result handler.
424  */
ResultHandler(void * ctx,int fields,char ** row)425 static int ResultHandler(void* ctx, int fields, char** row)
426 {
427   Dmsg1(100, "ResultHandler(*,%d,**)", fields);
428   if (fields == 4) {
429     Pmsg4(0, "%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3]);
430   } else if (fields == 5) {
431     Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3], row[4]);
432   } else if (fields == 6) {
433     Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3], row[4],
434           row[5]);
435   } else if (fields == 7) {
436     Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3],
437           row[4], row[5], row[6]);
438   }
439   return 0;
440 }
441 
442 /*
443  * BVFS class methods.
444  */
Bvfs(JobControlRecord * j,BareosDb * mdb)445 Bvfs::Bvfs(JobControlRecord* j, BareosDb* mdb)
446 {
447   jcr = j;
448   jcr->IncUseCount();
449   db = mdb; /* need to inc ref count */
450   jobids = GetPoolMemory(PM_NAME);
451   prev_dir = GetPoolMemory(PM_NAME);
452   pattern = GetPoolMemory(PM_NAME);
453   *jobids = *prev_dir = *pattern = 0;
454   pwd_id = 0;
455   see_copies = false;
456   see_all_versions = false;
457   limit = 1000;
458   offset = 0;
459   attr = new_attr(jcr);
460   list_entries = ResultHandler;
461   user_data = this;
462 }
463 
~Bvfs()464 Bvfs::~Bvfs()
465 {
466   FreePoolMemory(jobids);
467   FreePoolMemory(pattern);
468   FreePoolMemory(prev_dir);
469   FreeAttr(attr);
470   jcr->DecUseCount();
471 }
472 
SetJobid(JobId_t id)473 void Bvfs::SetJobid(JobId_t id) { Mmsg(jobids, "%lld", (uint64_t)id); }
474 
SetJobids(char * ids)475 void Bvfs::SetJobids(char* ids) { PmStrcpy(jobids, ids); }
476 
477 /*
478  * Return the parent_dir with the trailing "/".
479  * It updates the given string.
480  *
481  * Unix:
482  * dir=/tmp/toto/
483  * dir=/tmp/
484  * dir=/
485  * dir=
486  *
487  * Windows:
488  * dir=c:/Programs/Bareos/
489  * dir=c:/Programs/
490  * dir=c:/
491  * dir=
492  *
493  * Plugins:
494  * dir=@bpipe@/data.dat
495  * dir=@bpipe@/
496  * dir=
497  */
bvfs_parent_dir(char * path)498 char* bvfs_parent_dir(char* path)
499 {
500   char* p = path;
501   int len = strlen(path) - 1;
502 
503   /* windows directory / */
504   if (len == 2 && B_ISALPHA(path[0]) && path[1] == ':' && path[2] == '/') {
505     len = 0;
506     path[0] = '\0';
507   }
508 
509   /* if path is a directory, remove last / */
510   if ((len >= 0) && (path[len] == '/')) { path[len] = '\0'; }
511 
512   if (len > 0) {
513     p += len;
514     while (p > path && !IsPathSeparator(*p)) { p--; }
515     if (IsPathSeparator(*p) and (len >= 1)) {
516       /*
517        * Terminate the string after the "/".
518        * Do this instead of overwritting the "/"
519        * to keep the root directory "/" as a separate path.
520        */
521       p[1] = '\0';
522     } else {
523       /*
524        * path did not start with a "/".
525        * This can be the case for plugin results.
526        */
527       p[0] = '\0';
528     }
529   }
530 
531   return path;
532 }
533 
534 /* Return the basename of the path with the trailing /
535  * TODO: see in the rest of bareos if we don't have
536  * this function already (e.g. last_path_separator)
537  */
bvfs_basename_dir(char * path)538 char* bvfs_basename_dir(char* path)
539 {
540   char* p = path;
541   int len = strlen(path) - 1;
542 
543   if (path[len] == '/') { /* if directory, skip last / */
544     len -= 1;
545   }
546 
547   if (len > 0) {
548     p += len;
549     while (p > path && !IsPathSeparator(*p)) { p--; }
550     if (*p == '/') { p++; /* skip first / */ }
551   }
552   return p;
553 }
554 
555 
556 /**
557  * Update the bvfs cache for current jobids
558  */
update_cache()559 void Bvfs::update_cache() { db->BvfsUpdatePathHierarchyCache(jcr, jobids); }
560 
561 /**
562  * Change the current directory, returns true if the path exists
563  */
ChDir(const char * path)564 bool Bvfs::ChDir(const char* path)
565 {
566   DbLock(db);
567   ChDir(db->GetPathRecord(jcr, path));
568   DbUnlock(db);
569 
570   return pwd_id != 0;
571 }
572 
GetAllFileVersions(const char * path,const char * fname,const char * client)573 void Bvfs::GetAllFileVersions(const char* path,
574                               const char* fname,
575                               const char* client)
576 {
577   DBId_t pathid = 0;
578   char path_esc[MAX_ESCAPE_NAME_LENGTH];
579 
580   db->EscapeString(jcr, path_esc, path, strlen(path));
581   pathid = db->GetPathRecord(jcr, path_esc);
582   GetAllFileVersions(pathid, fname, client);
583 }
584 
585 /**
586  * Get all file versions for a specified client
587  * TODO: Handle basejobs using different client
588  */
GetAllFileVersions(DBId_t pathid,const char * fname,const char * client)589 void Bvfs::GetAllFileVersions(DBId_t pathid,
590                               const char* fname,
591                               const char* client)
592 {
593   char ed1[50];
594   char fname_esc[MAX_ESCAPE_NAME_LENGTH];
595   char client_esc[MAX_ESCAPE_NAME_LENGTH];
596   PoolMem query(PM_MESSAGE);
597   PoolMem filter(PM_MESSAGE);
598 
599   Dmsg3(dbglevel, "GetAllFileVersions(%lld, %s, %s)\n", (uint64_t)pathid, fname,
600         client);
601 
602   if (see_copies) {
603     Mmsg(filter, " AND Job.Type IN ('C', 'B') ");
604   } else {
605     Mmsg(filter, " AND Job.Type = 'B' ");
606   }
607 
608   db->EscapeString(jcr, fname_esc, fname, strlen(fname));
609   db->EscapeString(jcr, client_esc, client, strlen(client));
610 
611   db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_versions_6, fname_esc,
612                 edit_uint64(pathid, ed1), client_esc, filter.c_str(), limit,
613                 offset);
614   db->SqlQuery(query.c_str(), list_entries, user_data);
615 }
616 
get_root()617 DBId_t Bvfs::get_root()
618 {
619   int p;
620 
621   DbLock(db);
622   p = db->GetPathRecord(jcr, "");
623   DbUnlock(db);
624 
625   return p;
626 }
627 
_handlePath(void * ctx,int fields,char ** row)628 int Bvfs::_handlePath(void* ctx, int fields, char** row)
629 {
630   if (BvfsIsDir(row)) {
631     /*
632      * Can have the same path 2 times
633      */
634     if (!bstrcmp(row[BVFS_Name], prev_dir)) {
635       PmStrcpy(prev_dir, row[BVFS_Name]);
636       return list_entries(user_data, fields, row);
637     }
638   }
639   return 0;
640 }
641 
642 /* Returns true if we have dirs to read */
ls_dirs()643 bool Bvfs::ls_dirs()
644 {
645   char pathid[50];
646   PoolMem special_dirs_query(PM_MESSAGE);
647   PoolMem filter(PM_MESSAGE);
648   PoolMem sub_dirs_query(PM_MESSAGE);
649   PoolMem union_query(PM_MESSAGE);
650 
651   Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
652 
653   if (*jobids == 0) { return false; }
654 
655   /* convert pathid to string */
656   edit_uint64(pwd_id, pathid);
657 
658   /*
659    * The sql query displays same directory multiple time, take the first one
660    */
661   *prev_dir = 0;
662 
663   db->FillQuery(special_dirs_query, BareosDb::SQL_QUERY::bvfs_ls_special_dirs_3,
664                 pathid, pathid, jobids);
665 
666   if (*pattern) {
667     db->FillQuery(filter, BareosDb::SQL_QUERY::match_query, pattern);
668   }
669   db->FillQuery(sub_dirs_query, BareosDb::SQL_QUERY::bvfs_ls_sub_dirs_5, pathid,
670                 jobids, jobids, filter.c_str(), jobids);
671 
672   db->FillQuery(union_query, BareosDb::SQL_QUERY::bvfs_lsdirs_4,
673                 special_dirs_query.c_str(), sub_dirs_query.c_str(), limit,
674                 offset);
675 
676   /* FIXME: BvfsLsDirs does not return number of results */
677   nb_record = db->BvfsLsDirs(union_query, this);
678 
679   return true;
680 }
681 
build_ls_files_query(JobControlRecord * jcr,BareosDb * db,PoolMem & query,const char * JobId,const char * PathId,const char * filter,int64_t limit,int64_t offset)682 static void build_ls_files_query(JobControlRecord* jcr,
683                                  BareosDb* db,
684                                  PoolMem& query,
685                                  const char* JobId,
686                                  const char* PathId,
687                                  const char* filter,
688                                  int64_t limit,
689                                  int64_t offset)
690 {
691   if (db->GetTypeIndex() == SQL_TYPE_POSTGRESQL) {
692     db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_list_files, JobId, PathId,
693                   JobId, PathId, filter, limit, offset);
694   } else {
695     db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_list_files, JobId, PathId,
696                   JobId, PathId, limit, offset, filter, JobId, JobId);
697   }
698 }
699 
700 /*
701  * Returns true if we have files to read
702  */
ls_files()703 bool Bvfs::ls_files()
704 {
705   char pathid[50];
706   PoolMem filter(PM_MESSAGE);
707   PoolMem query(PM_MESSAGE);
708 
709   Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
710   if (*jobids == 0) { return false; }
711 
712   if (!pwd_id) { ChDir(get_root()); }
713 
714   edit_uint64(pwd_id, pathid);
715   if (*pattern) {
716     db->FillQuery(filter, BareosDb::SQL_QUERY::match_query2, pattern);
717   }
718 
719   build_ls_files_query(jcr, db, query, jobids, pathid, filter.c_str(), limit,
720                        offset);
721   nb_record = db->BvfsBuildLsFileQuery(query, list_entries, user_data);
722 
723   return nb_record == limit;
724 }
725 
726 /**
727  * Return next Id from comma separated list
728  *
729  * Returns:
730  *   1 if next Id returned
731  *   0 if no more Ids are in list
732  *  -1 there is an error
733  * TODO: merge with GetNextJobidFromList() and GetNextDbidFromList()
734  */
GetNextIdFromList(char ** p,int64_t * Id)735 static int GetNextIdFromList(char** p, int64_t* Id)
736 {
737   const int maxlen = 30;
738   char id[maxlen + 1];
739   char* q = *p;
740 
741   id[0] = 0;
742   for (int i = 0; i < maxlen; i++) {
743     if (*q == 0) {
744       break;
745     } else if (*q == ',') {
746       q++;
747       break;
748     }
749     id[i] = *q++;
750     id[i + 1] = 0;
751   }
752   if (id[0] == 0) {
753     return 0;
754   } else if (!Is_a_number(id)) {
755     return -1; /* error */
756   }
757   *p = q;
758   *Id = str_to_int64(id);
759   return 1;
760 }
761 
CheckTemp(char * output_table)762 static bool CheckTemp(char* output_table)
763 {
764   if (output_table[0] == 'b' && output_table[1] == '2'
765       && IsAnInteger(output_table + 2)) {
766     return true;
767   }
768   return false;
769 }
770 
clear_cache()771 void Bvfs::clear_cache()
772 {
773   /*
774    * FIXME:
775    * can't use predefined query,
776    * as MySQL queries do only support single SQL statements,
777    * not multiple.
778    */
779   // db->SqlQuery(BareosDb::SQL_QUERY::bvfs_clear_cache_0);
780   db->StartTransaction(jcr);
781   db->SqlQuery("UPDATE Job SET HasCache=0");
782   if (db->GetTypeIndex() == SQL_TYPE_SQLITE3) {
783     db->SqlQuery("DELETE FROM PathHierarchy;");
784     db->SqlQuery("DELETE FROM PathVisibility;");
785   } else {
786     db->SqlQuery("TRUNCATE PathHierarchy");
787     db->SqlQuery("TRUNCATE PathVisibility");
788   }
789   db->EndTransaction(jcr);
790 }
791 
DropRestoreList(char * output_table)792 bool Bvfs::DropRestoreList(char* output_table)
793 {
794   PoolMem query(PM_MESSAGE);
795   if (CheckTemp(output_table)) {
796     Mmsg(query, "DROP TABLE %s", output_table);
797     db->SqlQuery(query.c_str());
798     return true;
799   }
800   return false;
801 }
802 
compute_restore_list(char * fileid,char * dirid,char * hardlink,char * output_table)803 bool Bvfs::compute_restore_list(char* fileid,
804                                 char* dirid,
805                                 char* hardlink,
806                                 char* output_table)
807 {
808   PoolMem query(PM_MESSAGE);
809   PoolMem tmp(PM_MESSAGE), tmp2(PM_MESSAGE);
810   int64_t id, jobid, prev_jobid;
811   bool init = false;
812   bool retval = false;
813 
814   /* check args */
815   if ((*fileid && !Is_a_number_list(fileid))
816       || (*dirid && !Is_a_number_list(dirid))
817       || (*hardlink && !Is_a_number_list(hardlink))
818       || (!*hardlink && !*fileid && !*dirid && !*hardlink)) {
819     return false;
820   }
821   if (!CheckTemp(output_table)) { return false; }
822 
823   DbLock(db);
824 
825   /* Cleanup old tables first */
826   Mmsg(query, "DROP TABLE btemp%s", output_table);
827   db->SqlQuery(query.c_str());
828 
829   Mmsg(query, "DROP TABLE %s", output_table);
830   db->SqlQuery(query.c_str());
831 
832   Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
833 
834   if (*fileid) { /* Select files with their direct id */
835     init = true;
836     Mmsg(tmp,
837          "SELECT Job.JobId, JobTDate, FileIndex, File.Name, "
838          "PathId, FileId "
839          "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
840          fileid);
841     PmStrcat(query, tmp.c_str());
842   }
843 
844   /* Add a directory content */
845   while (GetNextIdFromList(&dirid, &id) == 1) {
846     Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
847 
848     if (!db->SqlQuery(tmp.c_str(), GetPathHandler, (void*)&tmp2)) {
849       Dmsg0(dbglevel, "Can't search for path\n");
850       /* print error */
851       goto bail_out;
852     }
853     if (bstrcmp(tmp2.c_str(), "")) { /* path not found */
854       Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n", id, tmp.c_str(),
855             tmp2.c_str());
856       break;
857     }
858     /* escape % and _ for LIKE search */
859     tmp.check_size((strlen(tmp2.c_str()) + 1) * 2);
860     char* p = tmp.c_str();
861     for (char* s = tmp2.c_str(); *s; s++) {
862       if (*s == '%' || *s == '_' || *s == '\\') {
863         *p = '\\';
864         p++;
865       }
866       *p = *s;
867       p++;
868     }
869     *p = '\0';
870     tmp.strcat("%");
871 
872     size_t len = strlen(tmp.c_str());
873     tmp2.check_size((len + 1) * 2);
874     db->EscapeString(jcr, tmp2.c_str(), tmp.c_str(), len);
875 
876     if (init) { query.strcat(" UNION "); }
877 
878     Mmsg(tmp,
879          "SELECT Job.JobId, JobTDate, File.FileIndex, File.Name, "
880          "File.PathId, FileId "
881          "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
882          "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
883          tmp2.c_str(), jobids);
884     query.strcat(tmp.c_str());
885     init = true;
886 
887     query.strcat(" UNION ");
888 
889     /* A directory can have files from a BaseJob */
890     Mmsg(tmp,
891          "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
892          "File.Name, File.PathId, BaseFiles.FileId "
893          "FROM BaseFiles "
894          "JOIN File USING (FileId) "
895          "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
896          "JOIN Path USING (PathId) "
897          "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
898          tmp2.c_str(), jobids);
899     query.strcat(tmp.c_str());
900   }
901 
902   /* expect jobid,fileindex */
903   prev_jobid = 0;
904   while (GetNextIdFromList(&hardlink, &jobid) == 1) {
905     if (GetNextIdFromList(&hardlink, &id) != 1) {
906       Dmsg0(dbglevel, "hardlink should be two by two\n");
907       goto bail_out;
908     }
909     if (jobid != prev_jobid) { /* new job */
910       if (prev_jobid == 0) {   /* first jobid */
911         if (init) { query.strcat(" UNION "); }
912       } else { /* end last job, start new one */
913         tmp.strcat(") UNION ");
914         query.strcat(tmp.c_str());
915       }
916       Mmsg(tmp,
917            "SELECT Job.JobId, JobTDate, FileIndex, Name, "
918            "PathId, FileId "
919            "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
920            "AND FileIndex IN (%lld",
921            jobid, id);
922       prev_jobid = jobid;
923 
924     } else { /* same job, add new findex */
925       Mmsg(tmp2, ", %lld", id);
926       tmp.strcat(tmp2.c_str());
927     }
928   }
929 
930   if (prev_jobid != 0) { /* end last job */
931     tmp.strcat(") ");
932     query.strcat(tmp.c_str());
933     init = true;
934   }
935 
936   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
937 
938   if (!db->SqlQuery(query.c_str())) {
939     Dmsg0(dbglevel, "Can't execute q\n");
940     goto bail_out;
941   }
942 
943   db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_select, output_table,
944                 output_table, output_table);
945 
946   /* TODO: handle jobid filter */
947   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
948   if (!db->SqlQuery(query.c_str())) {
949     Dmsg0(dbglevel, "Can't execute q\n");
950     goto bail_out;
951   }
952 
953   /* MySQL need it */
954   if (db->GetTypeIndex() == SQL_TYPE_MYSQL) {
955     Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)", output_table,
956          output_table);
957     Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
958     if (!db->SqlQuery(query.c_str())) {
959       Dmsg0(dbglevel, "Can't execute q\n");
960       goto bail_out;
961     }
962   }
963 
964   retval = true;
965 
966 bail_out:
967   Mmsg(query, "DROP TABLE btemp%s", output_table);
968   db->SqlQuery(query.c_str());
969   DbUnlock(db);
970   return retval;
971 }
972 
973 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || \
974           HAVE_DBI */
975