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