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