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-2019 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     i = 0;
287     while (num > 0) {
288       BuildPathHierarchy(jcr, ppathid_cache, result[i], result[i + 1]);
289       free(result[i++]);
290       free(result[i++]);
291       num--;
292     }
293     free(result);
294   }
295 
296   StartTransaction(jcr);
297 
298   FillQuery(cmd, SQL_QUERY::bvfs_update_path_visibility_3, jobid, jobid, jobid);
299 
300   do {
301     retval = QUERY_DB(jcr, cmd);
302   } while (retval && SqlAffectedRows() > 0);
303 
304   Mmsg(cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
305   UPDATE_DB(jcr, cmd);
306 
307 bail_out:
308   EndTransaction(jcr);
309   DbUnlock(this);
310 
311   return retval;
312 }
313 
BvfsUpdateCache(JobControlRecord * jcr)314 void BareosDb::BvfsUpdateCache(JobControlRecord* jcr)
315 {
316   uint32_t nb = 0;
317   db_list_ctx jobids_list;
318 
319   DbLock(this);
320 
321   Mmsg(cmd,
322        "SELECT JobId from Job "
323        "WHERE HasCache = 0 "
324        "AND Type IN ('B') AND JobStatus IN ('T', 'W', 'f', 'A') "
325        "ORDER BY JobId");
326   SqlQuery(cmd, DbListHandler, &jobids_list);
327 
328   BvfsUpdatePathHierarchyCache(jcr, jobids_list.list);
329 
330   StartTransaction(jcr);
331   Dmsg0(dbglevel, "Cleaning pathvisibility\n");
332   Mmsg(cmd,
333        "DELETE FROM PathVisibility "
334        "WHERE NOT EXISTS "
335        "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
336   nb = DELETE_DB(jcr, cmd);
337   Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
338   EndTransaction(jcr);
339 
340   DbUnlock(this);
341 }
342 
343 /*
344  * Update the bvfs cache for given jobids (1,2,3,4)
345  */
BvfsUpdatePathHierarchyCache(JobControlRecord * jcr,char * jobids)346 bool BareosDb::BvfsUpdatePathHierarchyCache(JobControlRecord* jcr, char* jobids)
347 {
348   char* p;
349   int status;
350   JobId_t JobId;
351   bool retval = true;
352   pathid_cache ppathid_cache;
353 
354   p = jobids;
355   while (1) {
356     status = GetNextJobidFromList(&p, &JobId);
357     if (status < 0) { goto bail_out; }
358 
359     if (status == 0) {
360       /*
361        * We reached the end of the list.
362        */
363       goto bail_out;
364     }
365 
366     Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
367     if (!UpdatePathHierarchyCache(jcr, ppathid_cache, JobId)) {
368       retval = false;
369     }
370   }
371 
372 bail_out:
373   return retval;
374 }
375 
BvfsLsDirs(PoolMem & query,void * ctx)376 int BareosDb::BvfsLsDirs(PoolMem& query, void* ctx)
377 {
378   int nb_record = 0;
379 
380   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
381 
382   DbLock(this);
383 
384   SqlQuery(query.c_str(), PathHandler, ctx);
385   // FIXME: SqlNumRows() is always 0 after SqlQuery.
386   // nb_record = SqlNumRows();
387 
388   DbUnlock(this);
389 
390   return nb_record;
391 }
392 
BvfsBuildLsFileQuery(PoolMem & query,DB_RESULT_HANDLER * ResultHandler,void * ctx)393 int BareosDb::BvfsBuildLsFileQuery(PoolMem& query,
394                                    DB_RESULT_HANDLER* ResultHandler,
395                                    void* ctx)
396 {
397   int nb_record = 0;
398 
399   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
400 
401   DbLock(this);
402   SqlQuery(query.c_str(), ResultHandler, ctx);
403   // FIXME: SqlNumRows() is always 0 after SqlQuery.
404   // nb_record = SqlNumRows();
405   DbUnlock(this);
406 
407   return nb_record;
408 }
409 
410 /*
411  * Generic result handler.
412  */
ResultHandler(void * ctx,int fields,char ** row)413 static int ResultHandler(void* ctx, int fields, char** row)
414 {
415   Dmsg1(100, "ResultHandler(*,%d,**)", fields);
416   if (fields == 4) {
417     Pmsg4(0, "%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3]);
418   } else if (fields == 5) {
419     Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3], row[4]);
420   } else if (fields == 6) {
421     Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3], row[4],
422           row[5]);
423   } else if (fields == 7) {
424     Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", row[0], row[1], row[2], row[3],
425           row[4], row[5], row[6]);
426   }
427   return 0;
428 }
429 
430 /*
431  * BVFS class methods.
432  */
Bvfs(JobControlRecord * j,BareosDb * mdb)433 Bvfs::Bvfs(JobControlRecord* j, BareosDb* mdb)
434 {
435   jcr = j;
436   jcr->IncUseCount();
437   db = mdb; /* need to inc ref count */
438   jobids = GetPoolMemory(PM_NAME);
439   prev_dir = GetPoolMemory(PM_NAME);
440   pattern = GetPoolMemory(PM_NAME);
441   *jobids = *prev_dir = *pattern = 0;
442   pwd_id = 0;
443   see_copies = false;
444   see_all_versions = false;
445   limit = 1000;
446   offset = 0;
447   attr = new_attr(jcr);
448   list_entries = ResultHandler;
449   user_data = this;
450 }
451 
~Bvfs()452 Bvfs::~Bvfs()
453 {
454   FreePoolMemory(jobids);
455   FreePoolMemory(pattern);
456   FreePoolMemory(prev_dir);
457   FreeAttr(attr);
458   jcr->DecUseCount();
459 }
460 
SetJobid(JobId_t id)461 void Bvfs::SetJobid(JobId_t id) { Mmsg(jobids, "%lld", (uint64_t)id); }
462 
SetJobids(char * ids)463 void Bvfs::SetJobids(char* ids) { PmStrcpy(jobids, ids); }
464 
465 /*
466  * Return the parent_dir with the trailing "/".
467  * It updates the given string.
468  *
469  * Unix:
470  * dir=/tmp/toto/
471  * dir=/tmp/
472  * dir=/
473  * dir=
474  *
475  * Windows:
476  * dir=c:/Programs/Bareos/
477  * dir=c:/Programs/
478  * dir=c:/
479  * dir=
480  *
481  * Plugins:
482  * dir=@bpipe@/data.dat
483  * dir=@bpipe@/
484  * dir=
485  */
bvfs_parent_dir(char * path)486 char* bvfs_parent_dir(char* path)
487 {
488   char* p = path;
489   int len = strlen(path) - 1;
490 
491   /* windows directory / */
492   if (len == 2 && B_ISALPHA(path[0]) && path[1] == ':' && path[2] == '/') {
493     len = 0;
494     path[0] = '\0';
495   }
496 
497   /* if path is a directory, remove last / */
498   if ((len >= 0) && (path[len] == '/')) { path[len] = '\0'; }
499 
500   if (len > 0) {
501     p += len;
502     while (p > path && !IsPathSeparator(*p)) { p--; }
503     if (IsPathSeparator(*p) and (len >= 1)) {
504       /*
505        * Terminate the string after the "/".
506        * Do this instead of overwritting the "/"
507        * to keep the root directory "/" as a separate path.
508        */
509       p[1] = '\0';
510     } else {
511       /*
512        * path did not start with a "/".
513        * This can be the case for plugin results.
514        */
515       p[0] = '\0';
516     }
517   }
518 
519   return path;
520 }
521 
522 /* Return the basename of the path with the trailing /
523  * TODO: see in the rest of bareos if we don't have
524  * this function already (e.g. last_path_separator)
525  */
bvfs_basename_dir(char * path)526 char* bvfs_basename_dir(char* path)
527 {
528   char* p = path;
529   int len = strlen(path) - 1;
530 
531   if (path[len] == '/') { /* if directory, skip last / */
532     len -= 1;
533   }
534 
535   if (len > 0) {
536     p += len;
537     while (p > path && !IsPathSeparator(*p)) { p--; }
538     if (*p == '/') { p++; /* skip first / */ }
539   }
540   return p;
541 }
542 
543 
544 /**
545  * Update the bvfs cache for current jobids
546  */
update_cache()547 void Bvfs::update_cache() { db->BvfsUpdatePathHierarchyCache(jcr, jobids); }
548 
549 /**
550  * Change the current directory, returns true if the path exists
551  */
ChDir(const char * path)552 bool Bvfs::ChDir(const char* path)
553 {
554   DbLock(db);
555   ChDir(db->GetPathRecord(jcr, path));
556   DbUnlock(db);
557 
558   return pwd_id != 0;
559 }
560 
GetAllFileVersions(const char * path,const char * fname,const char * client)561 void Bvfs::GetAllFileVersions(const char* path,
562                               const char* fname,
563                               const char* client)
564 {
565   DBId_t pathid = 0;
566   char path_esc[MAX_ESCAPE_NAME_LENGTH];
567 
568   db->EscapeString(jcr, path_esc, path, strlen(path));
569   pathid = db->GetPathRecord(jcr, path_esc);
570   GetAllFileVersions(pathid, fname, client);
571 }
572 
573 /**
574  * Get all file versions for a specified client
575  * TODO: Handle basejobs using different client
576  */
GetAllFileVersions(DBId_t pathid,const char * fname,const char * client)577 void Bvfs::GetAllFileVersions(DBId_t pathid,
578                               const char* fname,
579                               const char* client)
580 {
581   char ed1[50];
582   char fname_esc[MAX_ESCAPE_NAME_LENGTH];
583   char client_esc[MAX_ESCAPE_NAME_LENGTH];
584   PoolMem query(PM_MESSAGE);
585   PoolMem filter(PM_MESSAGE);
586 
587   Dmsg3(dbglevel, "GetAllFileVersions(%lld, %s, %s)\n", (uint64_t)pathid, fname,
588         client);
589 
590   if (see_copies) {
591     Mmsg(filter, " AND Job.Type IN ('C', 'B') ");
592   } else {
593     Mmsg(filter, " AND Job.Type = 'B' ");
594   }
595 
596   db->EscapeString(jcr, fname_esc, fname, strlen(fname));
597   db->EscapeString(jcr, client_esc, client, strlen(client));
598 
599   db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_versions_6, fname_esc,
600                 edit_uint64(pathid, ed1), client_esc, filter.c_str(), limit,
601                 offset);
602   db->SqlQuery(query.c_str(), list_entries, user_data);
603 }
604 
get_root()605 DBId_t Bvfs::get_root()
606 {
607   int p;
608 
609   DbLock(db);
610   p = db->GetPathRecord(jcr, "");
611   DbUnlock(db);
612 
613   return p;
614 }
615 
_handlePath(void * ctx,int fields,char ** row)616 int Bvfs::_handlePath(void* ctx, int fields, char** row)
617 {
618   if (BvfsIsDir(row)) {
619     /*
620      * Can have the same path 2 times
621      */
622     if (!bstrcmp(row[BVFS_Name], prev_dir)) {
623       PmStrcpy(prev_dir, row[BVFS_Name]);
624       return list_entries(user_data, fields, row);
625     }
626   }
627   return 0;
628 }
629 
630 /* Returns true if we have dirs to read */
ls_dirs()631 bool Bvfs::ls_dirs()
632 {
633   char pathid[50];
634   PoolMem special_dirs_query(PM_MESSAGE);
635   PoolMem filter(PM_MESSAGE);
636   PoolMem sub_dirs_query(PM_MESSAGE);
637   PoolMem union_query(PM_MESSAGE);
638 
639   Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
640 
641   if (*jobids == 0) { return false; }
642 
643   /* convert pathid to string */
644   edit_uint64(pwd_id, pathid);
645 
646   /*
647    * The sql query displays same directory multiple time, take the first one
648    */
649   *prev_dir = 0;
650 
651   db->FillQuery(special_dirs_query, BareosDb::SQL_QUERY::bvfs_ls_special_dirs_3,
652                 pathid, pathid, jobids);
653 
654   if (*pattern) {
655     db->FillQuery(filter, BareosDb::SQL_QUERY::match_query, pattern);
656   }
657   db->FillQuery(sub_dirs_query, BareosDb::SQL_QUERY::bvfs_ls_sub_dirs_5, pathid,
658                 jobids, jobids, filter.c_str(), jobids);
659 
660   db->FillQuery(union_query, BareosDb::SQL_QUERY::bvfs_lsdirs_4,
661                 special_dirs_query.c_str(), sub_dirs_query.c_str(), limit,
662                 offset);
663 
664   /* FIXME: BvfsLsDirs does not return number of results */
665   nb_record = db->BvfsLsDirs(union_query, this);
666 
667   return true;
668 }
669 
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)670 static void build_ls_files_query(JobControlRecord* jcr,
671                                  BareosDb* db,
672                                  PoolMem& query,
673                                  const char* JobId,
674                                  const char* PathId,
675                                  const char* filter,
676                                  int64_t limit,
677                                  int64_t offset)
678 {
679   if (db->GetTypeIndex() == SQL_TYPE_POSTGRESQL) {
680     db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_list_files, JobId, PathId,
681                   JobId, PathId, filter, limit, offset);
682   } else {
683     db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_list_files, JobId, PathId,
684                   JobId, PathId, limit, offset, filter, JobId, JobId);
685   }
686 }
687 
688 /*
689  * Returns true if we have files to read
690  */
ls_files()691 bool Bvfs::ls_files()
692 {
693   char pathid[50];
694   PoolMem filter(PM_MESSAGE);
695   PoolMem query(PM_MESSAGE);
696 
697   Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
698   if (*jobids == 0) { return false; }
699 
700   if (!pwd_id) { ChDir(get_root()); }
701 
702   edit_uint64(pwd_id, pathid);
703   if (*pattern) {
704     db->FillQuery(filter, BareosDb::SQL_QUERY::match_query2, pattern);
705   }
706 
707   build_ls_files_query(jcr, db, query, jobids, pathid, filter.c_str(), limit,
708                        offset);
709   nb_record = db->BvfsBuildLsFileQuery(query, list_entries, user_data);
710 
711   return nb_record == limit;
712 }
713 
714 /**
715  * Return next Id from comma separated list
716  *
717  * Returns:
718  *   1 if next Id returned
719  *   0 if no more Ids are in list
720  *  -1 there is an error
721  * TODO: merge with GetNextJobidFromList() and GetNextDbidFromList()
722  */
GetNextIdFromList(char ** p,int64_t * Id)723 static int GetNextIdFromList(char** p, int64_t* Id)
724 {
725   const int maxlen = 30;
726   char id[maxlen + 1];
727   char* q = *p;
728 
729   id[0] = 0;
730   for (int i = 0; i < maxlen; i++) {
731     if (*q == 0) {
732       break;
733     } else if (*q == ',') {
734       q++;
735       break;
736     }
737     id[i] = *q++;
738     id[i + 1] = 0;
739   }
740   if (id[0] == 0) {
741     return 0;
742   } else if (!Is_a_number(id)) {
743     return -1; /* error */
744   }
745   *p = q;
746   *Id = str_to_int64(id);
747   return 1;
748 }
749 
CheckTemp(char * output_table)750 static bool CheckTemp(char* output_table)
751 {
752   if (output_table[0] == 'b' && output_table[1] == '2' &&
753       IsAnInteger(output_table + 2)) {
754     return true;
755   }
756   return false;
757 }
758 
clear_cache()759 void Bvfs::clear_cache()
760 {
761   /*
762    * FIXME:
763    * can't use predefined query,
764    * as MySQL queries do only support single SQL statements,
765    * not multiple.
766    */
767   // db->SqlQuery(BareosDb::SQL_QUERY::bvfs_clear_cache_0);
768   db->StartTransaction(jcr);
769   db->SqlQuery("UPDATE Job SET HasCache=0");
770   if (db->GetTypeIndex() == SQL_TYPE_SQLITE3) {
771     db->SqlQuery("DELETE FROM PathHierarchy;");
772     db->SqlQuery("DELETE FROM PathVisibility;");
773   } else {
774     db->SqlQuery("TRUNCATE PathHierarchy");
775     db->SqlQuery("TRUNCATE PathVisibility");
776   }
777   db->EndTransaction(jcr);
778 }
779 
DropRestoreList(char * output_table)780 bool Bvfs::DropRestoreList(char* output_table)
781 {
782   PoolMem query(PM_MESSAGE);
783   if (CheckTemp(output_table)) {
784     Mmsg(query, "DROP TABLE %s", output_table);
785     db->SqlQuery(query.c_str());
786     return true;
787   }
788   return false;
789 }
790 
compute_restore_list(char * fileid,char * dirid,char * hardlink,char * output_table)791 bool Bvfs::compute_restore_list(char* fileid,
792                                 char* dirid,
793                                 char* hardlink,
794                                 char* output_table)
795 {
796   PoolMem query(PM_MESSAGE);
797   PoolMem tmp(PM_MESSAGE), tmp2(PM_MESSAGE);
798   int64_t id, jobid, prev_jobid;
799   bool init = false;
800   bool retval = false;
801 
802   /* check args */
803   if ((*fileid && !Is_a_number_list(fileid)) ||
804       (*dirid && !Is_a_number_list(dirid)) ||
805       (*hardlink && !Is_a_number_list(hardlink)) ||
806       (!*hardlink && !*fileid && !*dirid && !*hardlink)) {
807     return false;
808   }
809   if (!CheckTemp(output_table)) { return false; }
810 
811   DbLock(db);
812 
813   /* Cleanup old tables first */
814   Mmsg(query, "DROP TABLE btemp%s", output_table);
815   db->SqlQuery(query.c_str());
816 
817   Mmsg(query, "DROP TABLE %s", output_table);
818   db->SqlQuery(query.c_str());
819 
820   Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
821 
822   if (*fileid) { /* Select files with their direct id */
823     init = true;
824     Mmsg(tmp,
825          "SELECT Job.JobId, JobTDate, FileIndex, File.Name, "
826          "PathId, FileId "
827          "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
828          fileid);
829     PmStrcat(query, tmp.c_str());
830   }
831 
832   /* Add a directory content */
833   while (GetNextIdFromList(&dirid, &id) == 1) {
834     Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
835 
836     if (!db->SqlQuery(tmp.c_str(), GetPathHandler, (void*)&tmp2)) {
837       Dmsg0(dbglevel, "Can't search for path\n");
838       /* print error */
839       goto bail_out;
840     }
841     if (bstrcmp(tmp2.c_str(), "")) { /* path not found */
842       Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n", id, tmp.c_str(),
843             tmp2.c_str());
844       break;
845     }
846     /* escape % and _ for LIKE search */
847     tmp.check_size((strlen(tmp2.c_str()) + 1) * 2);
848     char* p = tmp.c_str();
849     for (char* s = tmp2.c_str(); *s; s++) {
850       if (*s == '%' || *s == '_' || *s == '\\') {
851         *p = '\\';
852         p++;
853       }
854       *p = *s;
855       p++;
856     }
857     *p = '\0';
858     tmp.strcat("%");
859 
860     size_t len = strlen(tmp.c_str());
861     tmp2.check_size((len + 1) * 2);
862     db->EscapeString(jcr, tmp2.c_str(), tmp.c_str(), len);
863 
864     if (init) { query.strcat(" UNION "); }
865 
866     Mmsg(tmp,
867          "SELECT Job.JobId, JobTDate, File.FileIndex, File.Name, "
868          "File.PathId, FileId "
869          "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
870          "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
871          tmp2.c_str(), jobids);
872     query.strcat(tmp.c_str());
873     init = true;
874 
875     query.strcat(" UNION ");
876 
877     /* A directory can have files from a BaseJob */
878     Mmsg(tmp,
879          "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
880          "File.Name, File.PathId, BaseFiles.FileId "
881          "FROM BaseFiles "
882          "JOIN File USING (FileId) "
883          "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
884          "JOIN Path USING (PathId) "
885          "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
886          tmp2.c_str(), jobids);
887     query.strcat(tmp.c_str());
888   }
889 
890   /* expect jobid,fileindex */
891   prev_jobid = 0;
892   while (GetNextIdFromList(&hardlink, &jobid) == 1) {
893     if (GetNextIdFromList(&hardlink, &id) != 1) {
894       Dmsg0(dbglevel, "hardlink should be two by two\n");
895       goto bail_out;
896     }
897     if (jobid != prev_jobid) { /* new job */
898       if (prev_jobid == 0) {   /* first jobid */
899         if (init) { query.strcat(" UNION "); }
900       } else { /* end last job, start new one */
901         tmp.strcat(") UNION ");
902         query.strcat(tmp.c_str());
903       }
904       Mmsg(tmp,
905            "SELECT Job.JobId, JobTDate, FileIndex, Name, "
906            "PathId, FileId "
907            "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
908            "AND FileIndex IN (%lld",
909            jobid, id);
910       prev_jobid = jobid;
911 
912     } else { /* same job, add new findex */
913       Mmsg(tmp2, ", %lld", id);
914       tmp.strcat(tmp2.c_str());
915     }
916   }
917 
918   if (prev_jobid != 0) { /* end last job */
919     tmp.strcat(") ");
920     query.strcat(tmp.c_str());
921     init = true;
922   }
923 
924   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
925 
926   if (!db->SqlQuery(query.c_str())) {
927     Dmsg0(dbglevel, "Can't execute q\n");
928     goto bail_out;
929   }
930 
931   db->FillQuery(query, BareosDb::SQL_QUERY::bvfs_select, output_table,
932                 output_table, output_table);
933 
934   /* TODO: handle jobid filter */
935   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
936   if (!db->SqlQuery(query.c_str())) {
937     Dmsg0(dbglevel, "Can't execute q\n");
938     goto bail_out;
939   }
940 
941   /* MySQL need it */
942   if (db->GetTypeIndex() == SQL_TYPE_MYSQL) {
943     Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)", output_table,
944          output_table);
945     Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
946     if (!db->SqlQuery(query.c_str())) {
947       Dmsg0(dbglevel, "Can't execute q\n");
948       goto bail_out;
949     }
950   }
951 
952   retval = true;
953 
954 bail_out:
955   Mmsg(query, "DROP TABLE btemp%s", output_table);
956   db->SqlQuery(query.c_str());
957   DbUnlock(db);
958   return retval;
959 }
960 
961 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || \
962           HAVE_DBI */
963