1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19
20 #include "bacula.h"
21 #include "cats.h"
22 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
23 #include "lib/htable.h"
24 #include "bvfs.h"
25
26 #define can_access(x) (true)
27
28 /* from libbacfind */
29 extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI);
30
31 #define dbglevel DT_BVFS|10
32 #define dbglevel_sql DT_SQL|15
33
result_handler(void * ctx,int fields,char ** row)34 static int result_handler(void *ctx, int fields, char **row)
35 {
36 if (fields == 4) {
37 Pmsg4(0, "%s\t%s\t%s\t%s\n",
38 row[0], row[1], row[2], row[3]);
39 } else if (fields == 5) {
40 Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
41 row[0], row[1], row[2], row[3], row[4]);
42 } else if (fields == 6) {
43 Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
44 row[0], row[1], row[2], row[3], row[4], row[5]);
45 } else if (fields == 7) {
46 Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
47 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
48 }
49 return 0;
50 }
51
Bvfs(JCR * j,BDB * mdb)52 Bvfs::Bvfs(JCR *j, BDB *mdb)
53 {
54 jcr = j;
55 jcr->inc_use_count();
56 db = mdb; /* need to inc ref count */
57 jobids = get_pool_memory(PM_NAME);
58 prev_dir = get_pool_memory(PM_NAME);
59 pattern = get_pool_memory(PM_NAME);
60 filename = get_pool_memory(PM_NAME);
61 tmp = get_pool_memory(PM_NAME);
62 escaped_list = get_pool_memory(PM_NAME);
63 *filename = *jobids = *prev_dir = *pattern = 0;
64 pwd_id = offset = 0;
65 see_copies = see_all_versions = false;
66 compute_delta = true;
67 limit = 1000;
68 attr = new_attr(jcr);
69 list_entries = result_handler;
70 user_data = this;
71 username = NULL;
72 job_acl = client_acl = pool_acl = fileset_acl = NULL;
73 last_dir_acl = NULL;
74 dir_acl = NULL;
75 use_acl = false;
76 dir_filenameid = 0; /* special FilenameId where Name='' */
77 }
78
~Bvfs()79 Bvfs::~Bvfs() {
80 free_pool_memory(jobids);
81 free_pool_memory(pattern);
82 free_pool_memory(prev_dir);
83 free_pool_memory(filename);
84 free_pool_memory(tmp);
85 free_pool_memory(escaped_list);
86 if (username) {
87 free(username);
88 }
89 free_attr(attr);
90 jcr->dec_use_count();
91 if (dir_acl) {
92 delete dir_acl;
93 }
94 }
95
escape_list(alist * lst)96 char *Bvfs::escape_list(alist *lst)
97 {
98 char *elt;
99 int len;
100
101 /* List is empty, reject everything */
102 if (!lst || lst->size() == 0) {
103 Mmsg(escaped_list, "''");
104 return escaped_list;
105 }
106
107 *tmp = 0;
108 *escaped_list = 0;
109
110 foreach_alist(elt, lst) {
111 if (elt && *elt) {
112 len = strlen(elt);
113 /* Escape + ' ' */
114 tmp = check_pool_memory_size(tmp, 2 * len + 2 + 2);
115
116 tmp[0] = '\'';
117 db->bdb_escape_string(jcr, tmp + 1 , elt, len);
118 pm_strcat(tmp, "'");
119
120 if (*escaped_list) {
121 pm_strcat(escaped_list, ",");
122 }
123
124 pm_strcat(escaped_list, tmp);
125 }
126 }
127 return escaped_list;
128 }
129
130 /* Returns the number of jobids in the result */
filter_jobid()131 int Bvfs::filter_jobid()
132 {
133 POOL_MEM query;
134 POOL_MEM sub_where;
135 POOL_MEM sub_join;
136
137 /* No ACL, no username, no check */
138 if (!job_acl && !fileset_acl && !client_acl && !pool_acl && !username) {
139 Dmsg0(dbglevel_sql, "No ACL\n");
140 /* Just count the number of items in the list */
141 int nb = (*jobids != 0) ? 1 : 0;
142 for (char *p=jobids; *p ; p++) {
143 if (*p == ',') {
144 nb++;
145 }
146 }
147 return nb;
148 }
149
150 if (job_acl) {
151 Mmsg(sub_where, " AND Job.Name IN (%s) ", escape_list(job_acl));
152 }
153
154 if (fileset_acl) {
155 Mmsg(query, " AND FileSet.FileSet IN (%s) ", escape_list(fileset_acl));
156 pm_strcat(sub_where, query.c_str());
157 pm_strcat(sub_join, " JOIN FileSet USING (FileSetId) ");
158 }
159
160 if (client_acl) {
161 Mmsg(query, " AND Client.Name IN (%s) ", escape_list(client_acl));
162 pm_strcat(sub_where, query.c_str());
163 }
164
165 if (pool_acl) {
166 Mmsg(query, " AND Pool.Name IN (%s) ", escape_list(pool_acl));
167 pm_strcat(sub_where, query.c_str());
168 pm_strcat(sub_join, " JOIN Pool USING (PoolId) ");
169 }
170
171 if (username) {
172 /* Query used by Bweb to filter clients, activated when using
173 * set_username()
174 */
175 Mmsg(query,
176 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
177 "JOIN (SELECT ClientId FROM client_group_member "
178 "JOIN client_group USING (client_group_id) "
179 "JOIN bweb_client_group_acl USING (client_group_id) "
180 "JOIN bweb_user USING (userid) "
181 "WHERE bweb_user.username = '%s' "
182 ") AS filter USING (ClientId) "
183 " WHERE JobId IN (%s) %s",
184 sub_join.c_str(), username, jobids, sub_where.c_str());
185
186 } else {
187 Mmsg(query,
188 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
189 " WHERE JobId IN (%s) %s",
190 sub_join.c_str(), jobids, sub_where.c_str());
191 }
192
193 db_list_ctx ctx;
194 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
195 db->bdb_sql_query(query.c_str(), db_list_handler, &ctx);
196 pm_strcpy(jobids, ctx.list);
197 return ctx.count;
198 }
199
200 /* Return the number of jobids after the filter */
set_jobid(JobId_t id)201 int Bvfs::set_jobid(JobId_t id)
202 {
203 Mmsg(jobids, "%lld", (uint64_t)id);
204 return filter_jobid();
205 }
206
207 /* Return the number of jobids after the filter */
set_jobids(char * ids)208 int Bvfs::set_jobids(char *ids)
209 {
210 pm_strcpy(jobids, ids);
211 return filter_jobid();
212 }
213
214 /*
215 * TODO: Find a way to let the user choose how he wants to display
216 * files and directories
217 */
218
219
220 /*
221 * Working Object to store PathId already seen (avoid
222 * database queries), equivalent to %cache_ppathid in perl
223 */
224
225 #define NITEMS 50000
226 class pathid_cache {
227 private:
228 hlink *nodes;
229 int nb_node;
230 int max_node;
231
232 alist *table_node;
233
234 htable *cache_ppathid;
235
236 public:
pathid_cache()237 pathid_cache() {
238 hlink link;
239 cache_ppathid = (htable *)malloc(sizeof(htable));
240 cache_ppathid->init(&link, &link, NITEMS);
241 max_node = NITEMS;
242 nodes = (hlink *) malloc(max_node * sizeof (hlink));
243 nb_node = 0;
244 table_node = New(alist(5, owned_by_alist));
245 table_node->append(nodes);
246 };
247
get_hlink()248 hlink *get_hlink() {
249 if (++nb_node >= max_node) {
250 nb_node = 0;
251 nodes = (hlink *)malloc(max_node * sizeof(hlink));
252 table_node->append(nodes);
253 }
254 return nodes + nb_node;
255 };
256
lookup(char * pathid)257 bool lookup(char *pathid) {
258 bool ret = cache_ppathid->lookup(pathid) != NULL;
259 return ret;
260 };
261
insert(char * pathid)262 void insert(char *pathid) {
263 hlink *h = get_hlink();
264 cache_ppathid->insert(pathid, h);
265 };
266
~pathid_cache()267 ~pathid_cache() {
268 cache_ppathid->destroy();
269 free(cache_ppathid);
270 delete table_node;
271 };
272 private:
273 pathid_cache(const pathid_cache &); /* prohibit pass by value */
274 pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
275 } ;
276
277 /* Return the parent_dir with the trailing / (update the given string)
278 * TODO: see in the rest of bacula if we don't have already this function
279 * dir=/tmp/toto/
280 * dir=/tmp/
281 * dir=/
282 * dir=
283 */
bvfs_parent_dir(char * path)284 char *bvfs_parent_dir(char *path)
285 {
286 char *p = path;
287 int len = strlen(path) - 1;
288
289 /* windows directory / */
290 if (len == 2 && B_ISALPHA(path[0])
291 && path[1] == ':'
292 && path[2] == '/')
293 {
294 len = 0;
295 path[0] = '\0';
296 }
297
298 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
299 path[len] = '\0';
300 }
301
302 if (len > 0) {
303 p += len;
304 while (p > path && !IsPathSeparator(*p)) {
305 p--;
306 }
307 p[1] = '\0';
308 }
309 return path;
310 }
311
312 /* Return the basename of the with the trailing /
313 * TODO: see in the rest of bacula if we don't have
314 * this function already
315 */
bvfs_basename_dir(char * path)316 char *bvfs_basename_dir(char *path)
317 {
318 char *p = path;
319 int len = strlen(path) - 1;
320
321 if (path[len] == '/') { /* if directory, skip last / */
322 len -= 1;
323 }
324
325 if (len > 0) {
326 p += len;
327 while (p > path && !IsPathSeparator(*p)) {
328 p--;
329 }
330 if (*p == '/') {
331 p++; /* skip first / */
332 }
333 }
334 return p;
335 }
336
build_path_hierarchy(JCR * jcr,BDB * mdb,pathid_cache & ppathid_cache,char * org_pathid,char * path)337 static void build_path_hierarchy(JCR *jcr, BDB *mdb,
338 pathid_cache &ppathid_cache,
339 char *org_pathid, char *path)
340 {
341 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
342 char pathid[50];
343 ATTR_DBR parent;
344 char *bkp = mdb->path;
345 bstrncpy(pathid, org_pathid, sizeof(pathid));
346
347 /* Does the ppathid exist for this ? we use a memory cache... In order to
348 * avoid the full loop, we consider that if a dir is allready in the
349 * PathHierarchy table, then there is no need to calculate all the
350 * hierarchy
351 */
352 while (path && *path)
353 {
354 if (!ppathid_cache.lookup(pathid))
355 {
356 Mmsg(mdb->cmd,
357 "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
358 pathid);
359
360 if (!mdb->QueryDB(jcr, mdb->cmd)) {
361 goto bail_out; /* Query failed, just leave */
362 }
363
364 /* Do we have a result ? */
365 if (mdb->sql_num_rows() > 0) {
366 ppathid_cache.insert(pathid);
367 /* This dir was in the db ...
368 * It means we can leave, the tree has allready been built for
369 * this dir
370 */
371 goto bail_out;
372 } else {
373 /* search or create parent PathId in Path table */
374 mdb->path = bvfs_parent_dir(path);
375 mdb->pnl = strlen(mdb->path);
376 if (!mdb->bdb_create_path_record(jcr, &parent)) {
377 goto bail_out;
378 }
379 ppathid_cache.insert(pathid);
380
381 Mmsg(mdb->cmd,
382 "INSERT INTO PathHierarchy (PathId, PPathId) "
383 "VALUES (%s,%lld)",
384 pathid, (uint64_t) parent.PathId);
385
386 if (!mdb->InsertDB(jcr, mdb->cmd)) {
387 goto bail_out; /* Can't insert the record, just leave */
388 }
389
390 edit_uint64(parent.PathId, pathid);
391 path = mdb->path; /* already done */
392 }
393 } else {
394 /* It's already in the cache. We can leave, no time to waste here,
395 * all the parent dirs have allready been done
396 */
397 goto bail_out;
398 }
399 }
400
401 bail_out:
402 mdb->path = bkp;
403 mdb->fnl = 0;
404 }
405
406 /*
407 * Internal function to update path_hierarchy cache with a shared pathid cache
408 * return Error 0
409 * OK 1
410 */
update_path_hierarchy_cache(JCR * jcr,BDB * mdb,pathid_cache & ppathid_cache,JobId_t JobId)411 static int update_path_hierarchy_cache(JCR *jcr,
412 BDB *mdb,
413 pathid_cache &ppathid_cache,
414 JobId_t JobId)
415 {
416 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
417 uint32_t ret=0;
418 uint32_t num;
419 char jobid[50];
420 edit_uint64(JobId, jobid);
421
422 mdb->bdb_lock();
423
424 /* We don't really want to harm users with spurious messages,
425 * everything is handled by transaction
426 */
427 mdb->set_use_fatal_jmsg(false);
428
429 mdb->bdb_start_transaction(jcr);
430
431 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
432
433 if (!mdb->QueryDB(jcr, mdb->cmd) || mdb->sql_num_rows() > 0) {
434 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
435 ret = 1;
436 goto bail_out;
437 }
438
439 /* Inserting path records for JobId */
440 Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
441 "SELECT DISTINCT PathId, JobId "
442 "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s AND FileIndex > 0 "
443 "UNION "
444 "SELECT PathId, BaseFiles.JobId "
445 "FROM BaseFiles JOIN File AS F USING (FileId) "
446 "WHERE BaseFiles.JobId = %s) AS B",
447 jobid, jobid);
448
449 if (!mdb->QueryDB(jcr, mdb->cmd)) {
450 Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId );
451 goto bail_out;
452 }
453
454 /* Now we have to do the directory recursion stuff to determine missing
455 * visibility We try to avoid recursion, to be as fast as possible We also
456 * only work on not allready hierarchised directories...
457 */
458 Mmsg(mdb->cmd,
459 "SELECT PathVisibility.PathId, Path "
460 "FROM PathVisibility "
461 "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
462 "LEFT JOIN PathHierarchy "
463 "ON (PathVisibility.PathId = PathHierarchy.PathId) "
464 "WHERE PathVisibility.JobId = %s "
465 "AND PathHierarchy.PathId IS NULL "
466 "ORDER BY Path", jobid);
467 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
468
469 if (!mdb->QueryDB(jcr, mdb->cmd)) {
470 Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId );
471 goto bail_out;
472 }
473
474 /* TODO: I need to reuse the DB connection without emptying the result
475 * So, now i'm copying the result in memory to be able to query the
476 * catalog descriptor again.
477 */
478 num = mdb->sql_num_rows();
479 if (num > 0) {
480 char **result = (char **)malloc (num * 2 * sizeof(char *));
481
482 SQL_ROW row;
483 int i=0;
484 while((row = mdb->sql_fetch_row())) {
485 result[i++] = bstrdup(row[0]);
486 result[i++] = bstrdup(row[1]);
487 }
488
489 i=0;
490 while (num > 0) {
491 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
492 free(result[i++]);
493 free(result[i++]);
494 num--;
495 }
496 free(result);
497 }
498
499 if (mdb->bdb_get_type_index() == SQL_TYPE_SQLITE3) {
500 Mmsg(mdb->cmd,
501 "INSERT INTO PathVisibility (PathId, JobId) "
502 "SELECT DISTINCT h.PPathId AS PathId, %s "
503 "FROM PathHierarchy AS h "
504 "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
505 "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
506 jobid, jobid, jobid );
507
508 } else if (mdb->bdb_get_type_index() == SQL_TYPE_MYSQL) {
509 Mmsg(mdb->cmd,
510 "INSERT INTO PathVisibility (PathId, JobId) "
511 "SELECT a.PathId,%s "
512 "FROM ( "
513 "SELECT DISTINCT h.PPathId AS PathId "
514 "FROM PathHierarchy AS h "
515 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
516 "WHERE p.JobId=%s) AS a "
517 "LEFT JOIN PathVisibility AS b ON (b.JobId=%s and a.PathId = b.PathId) "
518 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
519
520 } else {
521 Mmsg(mdb->cmd,
522 "INSERT INTO PathVisibility (PathId, JobId) "
523 "SELECT a.PathId,%s "
524 "FROM ( "
525 "SELECT DISTINCT h.PPathId AS PathId "
526 "FROM PathHierarchy AS h "
527 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
528 "WHERE p.JobId=%s) AS a LEFT JOIN "
529 "(SELECT PathId "
530 "FROM PathVisibility "
531 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
532 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
533 }
534
535 do {
536 ret = mdb->QueryDB(jcr, mdb->cmd);
537 } while (ret && mdb->sql_affected_rows() > 0);
538
539 Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
540 ret = mdb->UpdateDB(jcr, mdb->cmd, false);
541
542 bail_out:
543 mdb->bdb_end_transaction(jcr);
544
545 if (!ret) {
546 Mmsg(mdb->cmd, "SELECT HasCache FROM Job WHERE JobId=%s", jobid);
547 mdb->bdb_sql_query(mdb->cmd, db_int_handler, &ret);
548 }
549
550 /* Enable back the FATAL message if something is wrong */
551 mdb->set_use_fatal_jmsg(true);
552
553 mdb->bdb_unlock();
554 return ret;
555 }
556
557 /* Compute the cache for the bfileview compoment */
fv_update_cache()558 void Bvfs::fv_update_cache()
559 {
560 int64_t pathid;
561 int64_t size=0, count=0;
562
563 Dmsg0(dbglevel, "fv_update_cache()\n");
564
565 if (!*jobids) {
566 return; /* Nothing to build */
567 }
568
569 db->bdb_lock();
570 /* We don't really want to harm users with spurious messages,
571 * everything is handled by transaction
572 */
573 db->set_use_fatal_jmsg(false);
574
575 db->bdb_start_transaction(jcr);
576
577 pathid = get_root();
578
579 fv_compute_size_and_count(pathid, &size, &count);
580
581 db->bdb_end_transaction(jcr);
582
583 /* Enable back the FATAL message if something is wrong */
584 db->set_use_fatal_jmsg(true);
585
586 db->bdb_unlock();
587 }
588
589 /*
590 * Find an store the filename descriptor for empty directories Filename.Name=''
591 */
get_dir_filenameid()592 DBId_t Bvfs::get_dir_filenameid()
593 {
594 uint32_t id;
595 if (dir_filenameid) {
596 return dir_filenameid;
597 }
598 Mmsg(db->cmd, "SELECT FilenameId FROM Filename WHERE Name = ''");
599 db_sql_query(db, db->cmd, db_int_handler, &id);
600 dir_filenameid = id;
601 return dir_filenameid;
602 }
603
604 /* Not yet working */
fv_get_big_files(int64_t pathid,int64_t min_size,int32_t limit)605 void Bvfs::fv_get_big_files(int64_t pathid, int64_t min_size, int32_t limit)
606 {
607 Mmsg(db->cmd,
608 "SELECT FilenameId AS filenameid, Name AS name, size "
609 "FROM ( "
610 "SELECT FilenameId, base64_decode_lstat(8,LStat) AS size "
611 "FROM File "
612 "WHERE PathId = %lld "
613 "AND JobId = %s "
614 ") AS S INNER JOIN Filename USING (FilenameId) "
615 "WHERE S.size > %lld "
616 "ORDER BY S.size DESC "
617 "LIMIT %d ", pathid, jobids, min_size, limit);
618 }
619
620
621 /* Get the current path size and files count */
fv_get_current_size_and_count(int64_t pathid,int64_t * size,int64_t * count)622 void Bvfs::fv_get_current_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
623 {
624 SQL_ROW row;
625
626 *size = *count = 0;
627
628 Mmsg(db->cmd,
629 "SELECT Size AS size, Files AS files "
630 " FROM PathVisibility "
631 " WHERE PathId = %lld "
632 " AND JobId = %s ", pathid, jobids);
633
634 if (!db->QueryDB(jcr, db->cmd)) {
635 return;
636 }
637
638 if ((row = db->sql_fetch_row())) {
639 *size = str_to_int64(row[0]);
640 *count = str_to_int64(row[1]);
641 }
642 }
643
644 /* Compute for the current path the size and files count */
fv_get_size_and_count(int64_t pathid,int64_t * size,int64_t * count)645 void Bvfs::fv_get_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
646 {
647 SQL_ROW row;
648
649 *size = *count = 0;
650
651 Mmsg(db->cmd,
652 "SELECT sum(base64_decode_lstat(8,LStat)) AS size, count(1) AS files "
653 " FROM File "
654 " WHERE PathId = %lld "
655 " AND JobId = %s ", pathid, jobids);
656
657 if (!db->QueryDB(jcr, db->cmd)) {
658 return;
659 }
660
661 if ((row = db->sql_fetch_row())) {
662 *size = str_to_int64(row[0]);
663 *count = str_to_int64(row[1]);
664 }
665 }
666
fv_compute_size_and_count(int64_t pathid,int64_t * size,int64_t * count)667 void Bvfs::fv_compute_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
668 {
669 Dmsg1(dbglevel, "fv_compute_size_and_count(%lld)\n", pathid);
670
671 fv_get_current_size_and_count(pathid, size, count);
672 if (*size > 0) {
673 return;
674 }
675
676 /* Update stats for the current directory */
677 fv_get_size_and_count(pathid, size, count);
678
679 /* Update stats for all sub directories */
680 Mmsg(db->cmd,
681 " SELECT PathId "
682 " FROM PathVisibility "
683 " INNER JOIN PathHierarchy USING (PathId) "
684 " WHERE PPathId = %lld "
685 " AND JobId = %s ", pathid, jobids);
686
687 db->QueryDB(jcr, db->cmd);
688 int num = db->sql_num_rows();
689
690 if (num > 0) {
691 int64_t *result = (int64_t *)malloc (num * sizeof(int64_t));
692 SQL_ROW row;
693 int i=0;
694
695 while((row = db->sql_fetch_row())) {
696 result[i++] = str_to_int64(row[0]); /* PathId */
697 }
698
699 i=0;
700 while (num > 0) {
701 int64_t c=0, s=0;
702 fv_compute_size_and_count(result[i], &s, &c);
703 *size += s;
704 *count += c;
705
706 i++;
707 num--;
708 }
709 free(result);
710 }
711
712 fv_update_size_and_count(pathid, *size, *count);
713 }
714
fv_update_size_and_count(int64_t pathid,int64_t size,int64_t count)715 void Bvfs::fv_update_size_and_count(int64_t pathid, int64_t size, int64_t count)
716 {
717 Mmsg(db->cmd,
718 "UPDATE PathVisibility SET Files = %lld, Size = %lld "
719 " WHERE JobId = %s "
720 " AND PathId = %lld ", count, size, jobids, pathid);
721
722 db->UpdateDB(jcr, db->cmd, false);
723 }
724
bvfs_update_cache(JCR * jcr,BDB * mdb)725 void bvfs_update_cache(JCR *jcr, BDB *mdb)
726 {
727 uint32_t nb=0;
728 db_list_ctx jobids_list;
729
730 mdb->bdb_lock();
731
732 #ifdef xxx
733 /* TODO: Remove this code when updating make_bacula_table script */
734 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
735 if (!mdb->QueryDB(jcr, mdb->cmd)) {
736 Dmsg0(dbglevel, "Creating cache table\n");
737 Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
738 mdb->QueryDB(jcr, mdb->cmd);
739
740 Mmsg(mdb->cmd,
741 "CREATE TABLE PathHierarchy ( "
742 "PathId integer NOT NULL, "
743 "PPathId integer NOT NULL, "
744 "CONSTRAINT pathhierarchy_pkey "
745 "PRIMARY KEY (PathId))");
746 mdb->QueryDB(jcr, mdb->cmd);
747
748 Mmsg(mdb->cmd,
749 "CREATE INDEX pathhierarchy_ppathid "
750 "ON PathHierarchy (PPathId)");
751 mdb->QueryDB(jcr, mdb->cmd);
752
753 Mmsg(mdb->cmd,
754 "CREATE TABLE PathVisibility ("
755 "PathId integer NOT NULL, "
756 "JobId integer NOT NULL, "
757 "Size int8 DEFAULT 0, "
758 "Files int4 DEFAULT 0, "
759 "CONSTRAINT pathvisibility_pkey "
760 "PRIMARY KEY (JobId, PathId))");
761 mdb->QueryDB(jcr, mdb->cmd);
762
763 Mmsg(mdb->cmd,
764 "CREATE INDEX pathvisibility_jobid "
765 "ON PathVisibility (JobId)");
766 mdb->QueryDB(jcr, mdb->cmd);
767
768 }
769 #endif
770
771 Mmsg(mdb->cmd,
772 "SELECT JobId from Job "
773 "WHERE HasCache = 0 "
774 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
775 "ORDER BY JobId");
776
777 mdb->bdb_sql_query(mdb->cmd, db_list_handler, &jobids_list);
778
779 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
780
781 mdb->bdb_start_transaction(jcr);
782 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
783 Mmsg(mdb->cmd,
784 "DELETE FROM PathVisibility "
785 "WHERE NOT EXISTS "
786 "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
787 nb = mdb->DeleteDB(jcr, mdb->cmd);
788 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
789
790 mdb->bdb_end_transaction(jcr);
791 mdb->bdb_unlock();
792 }
793
794 /*
795 * Update the bvfs cache for given jobids (1,2,3,4)
796 */
797 int
bvfs_update_path_hierarchy_cache(JCR * jcr,BDB * mdb,char * jobids)798 bvfs_update_path_hierarchy_cache(JCR *jcr, BDB *mdb, char *jobids)
799 {
800 pathid_cache ppathid_cache;
801 JobId_t JobId;
802 char *p;
803 int ret=1;
804
805 for (p=jobids; ; ) {
806 int stat = get_next_jobid_from_list(&p, &JobId);
807 if (stat < 0) {
808 ret = 0;
809 break;
810 }
811 if (stat == 0) {
812 break;
813 }
814 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
815 if (!update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId)) {
816 ret = 0;
817 }
818 }
819 return ret;
820 }
821
822 /*
823 * Update the bvfs fileview for given jobids
824 */
825 void
bvfs_update_fv_cache(JCR * jcr,BDB * mdb,char * jobids)826 bvfs_update_fv_cache(JCR *jcr, BDB *mdb, char *jobids)
827 {
828 char *p;
829 JobId_t JobId;
830 Bvfs bvfs(jcr, mdb);
831
832 for (p=jobids; ; ) {
833 int stat = get_next_jobid_from_list(&p, &JobId);
834 if (stat < 0) {
835 return;
836 }
837 if (stat == 0) {
838 break;
839 }
840
841 Dmsg1(dbglevel, "Trying to create cache for %lld\n", (int64_t)JobId);
842
843 bvfs.set_jobid(JobId);
844 bvfs.fv_update_cache();
845 }
846 }
847
848 /*
849 * Update the bvfs cache for current jobids
850 */
update_cache()851 void Bvfs::update_cache()
852 {
853 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
854 }
855
856
ch_dir(DBId_t pathid)857 bool Bvfs::ch_dir(DBId_t pathid)
858 {
859 reset_offset();
860
861 pwd_id = pathid;
862 return pwd_id != 0;
863 }
864
865
866 /* Change the current directory, returns true if the path exists */
ch_dir(const char * path)867 bool Bvfs::ch_dir(const char *path)
868 {
869 db->bdb_lock();
870 pm_strcpy(db->path, path);
871 db->pnl = strlen(db->path);
872 ch_dir(db->bdb_get_path_record(jcr));
873 db->bdb_unlock();
874 return pwd_id != 0;
875 }
876
877 /*
878 * Get all file versions for a specified list of clients
879 * TODO: Handle basejobs using different client
880 */
get_all_file_versions(DBId_t pathid,FileId_t fnid,alist * clients)881 void Bvfs::get_all_file_versions(DBId_t pathid, FileId_t fnid, alist *clients)
882 {
883 char ed1[50], ed2[50], *eclients;
884 POOL_MEM q, query;
885
886 if (see_copies) {
887 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
888 } else {
889 Mmsg(q, " AND Job.Type = 'B' ");
890 }
891
892 eclients = escape_list(clients);
893
894 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
895 (uint64_t)fnid, eclients);
896
897 Mmsg(query,// 1 2 3 4
898 "SELECT 'V', File.PathId, File.FilenameId, 0, File.JobId, "
899 // 5 6 7
900 "File.LStat, File.FileId, File.Md5, "
901 // 8 9
902 "Media.VolumeName, Media.InChanger "
903 "FROM File, Job, Client, JobMedia, Media "
904 "WHERE File.FilenameId = %s "
905 "AND File.PathId=%s "
906 "AND File.JobId = Job.JobId "
907 "AND Job.JobId = JobMedia.JobId "
908 "AND File.FileIndex >= JobMedia.FirstIndex "
909 "AND File.FileIndex <= JobMedia.LastIndex "
910 "AND JobMedia.MediaId = Media.MediaId "
911 "AND Job.ClientId = Client.ClientId "
912 "AND Client.Name IN (%s) "
913 "%s ORDER BY FileId LIMIT %d OFFSET %d"
914 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), eclients, q.c_str(),
915 limit, offset);
916 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
917 db->bdb_sql_query(query.c_str(), list_entries, user_data);
918 }
919
920 /*
921 * Get all file versions for a specified client
922 * TODO: Handle basejobs using different client
923 */
get_delta(FileId_t fileid)924 bool Bvfs::get_delta(FileId_t fileid)
925 {
926 Dmsg1(dbglevel, "get_delta(%lld)\n", (uint64_t)fileid);
927 char ed1[50];
928 int32_t num;
929 SQL_ROW row;
930 POOL_MEM q;
931 POOL_MEM query;
932 char *fn = NULL;
933 bool ret = false;
934 db->bdb_lock();
935
936 /* Check if some FileId have DeltaSeq > 0
937 * Foreach of them we need to get the accurate_job list, and compute
938 * what are dependencies
939 */
940 Mmsg(query,
941 "SELECT F.JobId, FN.Name, F.PathId, F.DeltaSeq "
942 "FROM File AS F, Filename AS FN WHERE FileId = %lld "
943 "AND FN.FilenameId = F.FilenameId AND DeltaSeq > 0", fileid);
944
945 if (!db->QueryDB(jcr, query.c_str())) {
946 Dmsg1(dbglevel_sql, "Can't execute query=%s\n", query.c_str());
947 goto bail_out;
948 }
949
950 /* TODO: Use an other DB connection can avoid to copy the result of the
951 * previous query into a temporary buffer
952 */
953 num = db->sql_num_rows();
954 Dmsg2(dbglevel, "Found %d Delta parts q=%s\n",
955 num, query.c_str());
956
957 if (num > 0 && (row = db->sql_fetch_row())) {
958 JOB_DBR jr, jr2;
959 db_list_ctx lst;
960 memset(&jr, 0, sizeof(jr));
961 memset(&jr2, 0, sizeof(jr2));
962
963 fn = bstrdup(row[1]); /* Filename */
964 int64_t jid = str_to_int64(row[0]); /* JobId */
965 int64_t pid = str_to_int64(row[2]); /* PathId */
966
967 /* Need to limit the query to StartTime, Client/Fileset */
968 jr2.JobId = jid;
969 if (!db->bdb_get_job_record(jcr, &jr2)) {
970 Dmsg1(0, "Unable to get job record for jobid %d\n", jid);
971 goto bail_out;
972 }
973
974 jr.JobId = jid;
975 jr.ClientId = jr2.ClientId;
976 jr.FileSetId = jr2.FileSetId;
977 jr.JobLevel = L_INCREMENTAL;
978 jr.StartTime = jr2.StartTime;
979
980 /* Get accurate jobid list */
981 if (!db->bdb_get_accurate_jobids(jcr, &jr, &lst)) {
982 Dmsg1(0, "Unable to get Accurate list for jobid %d\n", jid);
983 goto bail_out;
984 }
985
986 /* Escape filename */
987 db->fnl = strlen(fn);
988 db->esc_name = check_pool_memory_size(db->esc_name, 2*db->fnl+2);
989 db->bdb_escape_string(jcr, db->esc_name, fn, db->fnl);
990
991 edit_int64(pid, ed1); /* pathid */
992
993 int id=db->bdb_get_type_index();
994 Mmsg(query, bvfs_select_delta_version_with_basejob_and_delta[id],
995 lst.list, db->esc_name, ed1,
996 lst.list, db->esc_name, ed1,
997 lst.list, lst.list);
998
999 Mmsg(db->cmd,
1000 // 0 1 2 3 4 5 6 7
1001 "SELECT 'd', PathId, 0, JobId, LStat, FileId, DeltaSeq, JobTDate"
1002 " FROM (%s) AS F1 "
1003 "ORDER BY DeltaSeq ASC",
1004 query.c_str());
1005
1006 Dmsg1(dbglevel_sql, "q=%s\n", db->cmd);
1007
1008 if (!db->bdb_sql_query(db->cmd, list_entries, user_data)) {
1009 Dmsg1(dbglevel_sql, "Can't exec q=%s\n", db->cmd);
1010 goto bail_out;
1011 }
1012 }
1013 ret = true;
1014 bail_out:
1015 if (fn) {
1016 free(fn);
1017 }
1018 db->bdb_unlock();
1019 return ret;
1020 }
1021
1022 /*
1023 * Get all volumes for a specific file
1024 */
get_volumes(FileId_t fileid)1025 void Bvfs::get_volumes(FileId_t fileid)
1026 {
1027 Dmsg1(dbglevel, "get_volumes(%lld)\n", (uint64_t)fileid);
1028
1029 char ed1[50];
1030 POOL_MEM query;
1031
1032 Mmsg(query,
1033 // 7 8
1034 "SELECT DISTINCT 'L',0,0,0,0,0,0, Media.VolumeName, Media.InChanger "
1035 "FROM File JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
1036 "WHERE File.FileId = %s "
1037 "AND File.FileIndex >= JobMedia.FirstIndex "
1038 "AND File.FileIndex <= JobMedia.LastIndex "
1039 " LIMIT %d OFFSET %d"
1040 ,edit_uint64(fileid, ed1), limit, offset);
1041 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1042 db->bdb_sql_query(query.c_str(), list_entries, user_data);
1043 }
1044
get_root()1045 DBId_t Bvfs::get_root()
1046 {
1047 int p;
1048 *db->path = 0;
1049 db->bdb_lock();
1050 p = db->bdb_get_path_record(jcr);
1051 db->bdb_unlock();
1052 return p;
1053 }
1054
path_handler(void * ctx,int fields,char ** row)1055 static int path_handler(void *ctx, int fields, char **row)
1056 {
1057 Bvfs *fs = (Bvfs *) ctx;
1058 return fs->_handle_path(ctx, fields, row);
1059 }
1060
_handle_path(void * ctx,int fields,char ** row)1061 int Bvfs::_handle_path(void *ctx, int fields, char **row)
1062 {
1063 if (bvfs_is_dir(row)) {
1064 /* can have the same path 2 times */
1065 if (strcmp(row[BVFS_PathId], prev_dir)) {
1066 pm_strcpy(prev_dir, row[BVFS_PathId]);
1067 if (strcmp(NPRTB(row[BVFS_FileIndex]), "0") == 0 &&
1068 strcmp(NPRTB(row[BVFS_FileId]), "0") != 0)
1069 {
1070 /* The directory was probably deleted */
1071 return 0;
1072 }
1073 return list_entries(user_data, fields, row);
1074 }
1075 }
1076 return 0;
1077 }
1078
1079 /*
1080 * Retrieve . and .. information
1081 */
ls_special_dirs()1082 void Bvfs::ls_special_dirs()
1083 {
1084 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
1085 char ed1[50], ed2[50];
1086 if (*jobids == 0) {
1087 return;
1088 }
1089 if (!dir_filenameid) {
1090 get_dir_filenameid();
1091 }
1092
1093 /* Will fetch directories */
1094 *prev_dir = 0;
1095
1096 POOL_MEM query;
1097 Mmsg(query,
1098 "(SELECT PathHierarchy.PPathId AS PathId, '..' AS Path "
1099 "FROM PathHierarchy JOIN PathVisibility USING (PathId) "
1100 "WHERE PathHierarchy.PathId = %s "
1101 "AND PathVisibility.JobId IN (%s) "
1102 "UNION "
1103 "SELECT %s AS PathId, '.' AS Path)",
1104 edit_uint64(pwd_id, ed1), jobids, ed1);
1105
1106 POOL_MEM query2;
1107 Mmsg(query2,// 1 2 3 4 5 6
1108 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId, FileIndex "
1109 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
1110 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
1111 "File1.LStat AS LStat, File1.FileId AS FileId, "
1112 "File1.FileIndex AS FileIndex, "
1113 "Job1.JobTDate AS JobTDate "
1114 "FROM File AS File1 JOIN Job AS Job1 USING (JobId)"
1115 "WHERE File1.FilenameId = %s "
1116 "AND File1.JobId IN (%s)) AS listfile1 "
1117 "ON (tmp.PathId = listfile1.PathId) "
1118 "ORDER BY tmp.Path, JobTDate DESC ",
1119 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
1120
1121 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
1122 db->bdb_sql_query(query2.c_str(), path_handler, this);
1123 }
1124
1125 /* Returns true if we have dirs to read */
ls_dirs()1126 bool Bvfs::ls_dirs()
1127 {
1128 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
1129 char ed1[50], ed2[50];
1130 if (*jobids == 0) {
1131 return false;
1132 }
1133
1134 POOL_MEM query;
1135 POOL_MEM filter;
1136 if (*pattern) {
1137 Mmsg(filter, " AND Path2.Path %s '%s' ",
1138 match_query[db->bdb_get_type_index()], pattern);
1139
1140 }
1141
1142 if (!dir_filenameid) {
1143 get_dir_filenameid();
1144 }
1145
1146 /* the sql query displays same directory multiple time, take the first one */
1147 *prev_dir = 0;
1148
1149 /* Let's retrieve the list of the visible dirs in this dir ...
1150 * First, I need the empty filenameid to locate efficiently
1151 * the dirs in the file table
1152 * my $dir_filenameid = $self->get_dir_filenameid();
1153 */
1154 /* Then we get all the dir entries from File ... */
1155 Mmsg(query,
1156 // 0 1 2 3 4 5 6
1157 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId, FileIndex FROM ( "
1158 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
1159 "lower(Path1.Path) AS lpath, "
1160 "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
1161 "listfile1.FileId AS FileId, "
1162 "listfile1.JobTDate AS JobTDate, "
1163 "listfile1.FileIndex AS FileIndex "
1164 "FROM ( "
1165 "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
1166 "FROM PathHierarchy AS PathHierarchy1 "
1167 "JOIN Path AS Path2 "
1168 "ON (PathHierarchy1.PathId = Path2.PathId) "
1169 "JOIN PathVisibility AS PathVisibility1 "
1170 "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
1171 "WHERE PathHierarchy1.PPathId = %s "
1172 "AND PathVisibility1.JobId IN (%s) "
1173 "%s "
1174 ") AS listpath1 "
1175 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
1176
1177 "LEFT JOIN ( " /* get attributes if any */
1178 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
1179 "File1.LStat AS LStat, File1.FileId AS FileId, "
1180 "File1.FileIndex, Job1.JobTDate AS JobTDate "
1181 "FROM File AS File1 JOIN Job AS Job1 USING (JobId) "
1182 "WHERE File1.FilenameId = %s "
1183 "AND File1.JobId IN (%s)) AS listfile1 "
1184 "ON (listpath1.PathId = listfile1.PathId) "
1185 ") AS A ORDER BY Path,JobTDate DESC LIMIT %d OFFSET %d",
1186 edit_uint64(pwd_id, ed1),
1187 jobids,
1188 filter.c_str(),
1189 edit_uint64(dir_filenameid, ed2),
1190 jobids,
1191 limit, offset);
1192
1193 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1194
1195 db->bdb_lock();
1196 db->bdb_sql_query(query.c_str(), path_handler, this);
1197 nb_record = db->sql_num_rows();
1198 db->bdb_unlock();
1199
1200 return nb_record == limit;
1201 }
1202
build_ls_files_query(BDB * db,POOL_MEM & query,const char * JobId,const char * PathId,const char * filter,int64_t limit,int64_t offset)1203 void build_ls_files_query(BDB *db, POOL_MEM &query,
1204 const char *JobId, const char *PathId,
1205 const char *filter, int64_t limit, int64_t offset)
1206 {
1207 if (db->bdb_get_type_index() == SQL_TYPE_POSTGRESQL) {
1208 Mmsg(query, sql_bvfs_list_files[db->bdb_get_type_index()],
1209 JobId, PathId, JobId, PathId,
1210 filter, limit, offset);
1211 } else {
1212 Mmsg(query, sql_bvfs_list_files[db->bdb_get_type_index()],
1213 JobId, PathId, JobId, PathId,
1214 limit, offset, filter, JobId, JobId);
1215 }
1216 }
1217
1218 /* Returns true if we have files to read */
ls_files()1219 bool Bvfs::ls_files()
1220 {
1221 POOL_MEM query;
1222 POOL_MEM filter;
1223 char pathid[50];
1224
1225 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
1226 if (*jobids == 0) {
1227 return false;
1228 }
1229
1230 if (!pwd_id) {
1231 ch_dir(get_root());
1232 }
1233
1234 edit_uint64(pwd_id, pathid);
1235 if (*pattern) {
1236 Mmsg(filter, " AND Filename.Name %s '%s' ",
1237 match_query[db_get_type_index(db)], pattern);
1238
1239 } else if (*filename) {
1240 Mmsg(filter, " AND Filename.Name = '%s' ", filename);
1241 }
1242
1243 build_ls_files_query(db, query,
1244 jobids, pathid, filter.c_str(),
1245 limit, offset);
1246
1247 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1248
1249 db->bdb_lock();
1250 db->bdb_sql_query(query.c_str(), list_entries, user_data);
1251 nb_record = db->sql_num_rows();
1252 db->bdb_unlock();
1253
1254 return nb_record == limit;
1255 }
1256
1257
1258 /*
1259 * Return next Id from comma separated list
1260 *
1261 * Returns:
1262 * 1 if next Id returned
1263 * 0 if no more Ids are in list
1264 * -1 there is an error
1265 * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
1266 */
get_next_id_from_list(char ** p,int64_t * Id)1267 static int get_next_id_from_list(char **p, int64_t *Id)
1268 {
1269 const int maxlen = 30;
1270 char id[maxlen+1];
1271 char *q = *p;
1272
1273 id[0] = 0;
1274 for (int i=0; i<maxlen; i++) {
1275 if (*q == 0) {
1276 break;
1277 } else if (*q == ',') {
1278 q++;
1279 break;
1280 }
1281 id[i] = *q++;
1282 id[i+1] = 0;
1283 }
1284 if (id[0] == 0) {
1285 return 0;
1286 } else if (!is_a_number(id)) {
1287 return -1; /* error */
1288 }
1289 *p = q;
1290 *Id = str_to_int64(id);
1291 return 1;
1292 }
1293
get_path_handler(void * ctx,int fields,char ** row)1294 static int get_path_handler(void *ctx, int fields, char **row)
1295 {
1296 POOL_MEM *buf = (POOL_MEM *) ctx;
1297 pm_strcpy(*buf, row[0]);
1298 return 0;
1299 }
1300
check_temp(char * output_table)1301 static bool check_temp(char *output_table)
1302 {
1303 if (output_table[0] == 'b' &&
1304 output_table[1] == '2' &&
1305 is_an_integer(output_table + 2))
1306 {
1307 return true;
1308 }
1309 return false;
1310 }
1311
clear_cache()1312 void Bvfs::clear_cache()
1313 {
1314 db->bdb_sql_query("BEGIN", NULL, NULL);
1315 db->bdb_sql_query("UPDATE Job SET HasCache=0", NULL, NULL);
1316 db->bdb_sql_query("TRUNCATE PathHierarchy", NULL, NULL);
1317 db->bdb_sql_query("TRUNCATE PathVisibility", NULL, NULL);
1318 db->bdb_sql_query("COMMIT", NULL, NULL);
1319 }
1320
drop_restore_list(char * output_table)1321 bool Bvfs::drop_restore_list(char *output_table)
1322 {
1323 POOL_MEM query;
1324 if (check_temp(output_table)) {
1325 Mmsg(query, "DROP TABLE %s", output_table);
1326 db->bdb_sql_query(query.c_str(), NULL, NULL);
1327 return true;
1328 }
1329 return false;
1330 }
1331
compute_restore_list(char * fileid,char * dirid,char * hardlink,char * output_table)1332 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
1333 char *output_table)
1334 {
1335 POOL_MEM query;
1336 POOL_MEM tmp, tmp2;
1337 int64_t id, jobid, prev_jobid;
1338 int num;
1339 bool init=false;
1340 bool ret=false;
1341 /* check args */
1342 if ((*fileid && !is_a_number_list(fileid)) ||
1343 (*dirid && !is_a_number_list(dirid)) ||
1344 (*hardlink && !is_a_number_list(hardlink))||
1345 (!*hardlink && !*fileid && !*dirid && !*hardlink))
1346 {
1347 Dmsg0(dbglevel, "ERROR: One or more of FileId, DirId or HardLink is not given or not a number.\n");
1348 return false;
1349 }
1350 if (!check_temp(output_table)) {
1351 Dmsg0(dbglevel, "ERROR: Wrong format for table name (in path field).\n");
1352 return false;
1353 }
1354
1355 db->bdb_lock();
1356
1357 /* Cleanup old tables first */
1358 Mmsg(query, "DROP TABLE btemp%s", output_table);
1359 db->bdb_sql_query(query.c_str());
1360
1361 Mmsg(query, "DROP TABLE %s", output_table);
1362 db->bdb_sql_query(query.c_str());
1363
1364 Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
1365
1366 if (*fileid) { /* Select files with their direct id */
1367 init=true;
1368 Mmsg(tmp,"SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1369 "PathId, FileId "
1370 "FROM File,Job WHERE Job.JobId=File.Jobid "
1371 "AND FileId IN (%s)",
1372 fileid);
1373 pm_strcat(query, tmp.c_str());
1374 }
1375
1376 /* Add a directory content */
1377 while (get_next_id_from_list(&dirid, &id) == 1) {
1378 Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
1379
1380 if (!db->bdb_sql_query(tmp.c_str(), get_path_handler, (void *)&tmp2)) {
1381 Dmsg3(dbglevel, "ERROR: Path not found %lld q=%s s=%s\n",
1382 id, tmp.c_str(), tmp2.c_str());
1383 /* print error */
1384 goto bail_out;
1385 }
1386 if (!strcmp(tmp2.c_str(), "")) { /* path not found */
1387 Dmsg3(dbglevel, "ERROR: Path not found %lld q=%s s=%s\n",
1388 id, tmp.c_str(), tmp2.c_str());
1389 break;
1390 }
1391 /* escape % and _ for LIKE search */
1392 tmp.check_size((strlen(tmp2.c_str())+1) * 2);
1393 char *p = tmp.c_str();
1394 for (char *s = tmp2.c_str(); *s ; s++) {
1395 if (*s == '%' || *s == '_' || *s == '\\') {
1396 *p = '\\';
1397 p++;
1398 }
1399 *p = *s;
1400 p++;
1401 }
1402 *p = '\0';
1403 tmp.strcat("%");
1404
1405 size_t len = strlen(tmp.c_str());
1406 tmp2.check_size((len+1) * 2);
1407 db->bdb_escape_string(jcr, tmp2.c_str(), tmp.c_str(), len);
1408
1409 if (init) {
1410 query.strcat(" UNION ");
1411 }
1412
1413 Mmsg(tmp, "SELECT Job.JobId, JobTDate, File.FileIndex, File.FilenameId, "
1414 "File.PathId, FileId "
1415 "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
1416 "WHERE Path.Path LIKE '%s' ESCAPE '%s' AND File.JobId IN (%s) ",
1417 tmp2.c_str(), escape_char_value[db->bdb_get_type_index()], jobids);
1418 query.strcat(tmp.c_str());
1419 init = true;
1420
1421 query.strcat(" UNION ");
1422
1423 /* A directory can have files from a BaseJob */
1424 Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
1425 "File.FilenameId, File.PathId, BaseFiles.FileId "
1426 "FROM BaseFiles "
1427 "JOIN File USING (FileId) "
1428 "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
1429 "JOIN Path USING (PathId) "
1430 "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
1431 tmp2.c_str(), jobids);
1432 query.strcat(tmp.c_str());
1433 }
1434
1435 /* expect jobid,fileindex */
1436 prev_jobid=0;
1437 while (get_next_id_from_list(&hardlink, &jobid) == 1) {
1438 if (get_next_id_from_list(&hardlink, &id) != 1) {
1439 Dmsg0(dbglevel, "ERROR: hardlink should be two by two\n");
1440 goto bail_out;
1441 }
1442 if (jobid != prev_jobid) { /* new job */
1443 if (prev_jobid == 0) { /* first jobid */
1444 if (init) {
1445 query.strcat(" UNION ");
1446 }
1447 } else { /* end last job, start new one */
1448 tmp.strcat(") UNION ");
1449 query.strcat(tmp.c_str());
1450 }
1451 Mmsg(tmp, "SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1452 "PathId, FileId "
1453 "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
1454 "AND FileIndex IN (%lld", jobid, id);
1455 prev_jobid = jobid;
1456
1457 } else { /* same job, add new findex */
1458 Mmsg(tmp2, ", %lld", id);
1459 tmp.strcat(tmp2.c_str());
1460 }
1461 }
1462
1463 if (prev_jobid != 0) { /* end last job */
1464 tmp.strcat(") ");
1465 query.strcat(tmp.c_str());
1466 init = true;
1467 }
1468
1469 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1470
1471 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1472 Dmsg1(dbglevel, "ERROR executing query=%s\n", query.c_str());
1473 goto bail_out;
1474 }
1475
1476 Mmsg(query, sql_bvfs_select[db->bdb_get_type_index()],
1477 output_table, output_table, output_table);
1478
1479 /* TODO: handle jobid filter */
1480 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1481 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1482 Dmsg1(dbglevel, "ERROR executing query=%s\n", query.c_str());
1483 goto bail_out;
1484 }
1485
1486 /* MySQL needs the index */
1487 if (db->bdb_get_type_index() == SQL_TYPE_MYSQL) {
1488 Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)",
1489 output_table, output_table);
1490 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1491 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1492 Dmsg1(dbglevel, "ERROR executing query=%s\n", query.c_str());
1493 goto bail_out;
1494 }
1495 }
1496
1497 /* Check if some FileId have DeltaSeq > 0
1498 * Foreach of them we need to get the accurate_job list, and compute
1499 * what are dependencies
1500 */
1501 Mmsg(query,
1502 "SELECT F.FileId, F.JobId, F.FilenameId, F.PathId, F.DeltaSeq "
1503 "FROM File AS F JOIN Job USING (JobId) JOIN %s USING (FileId) "
1504 "WHERE DeltaSeq > 0", output_table);
1505
1506 if (!db->QueryDB(jcr, query.c_str())) {
1507 Dmsg1(dbglevel_sql, "Can't execute query=%s\n", query.c_str());
1508 }
1509
1510 /* TODO: Use an other DB connection can avoid to copy the result of the
1511 * previous query into a temporary buffer
1512 */
1513 num = db->sql_num_rows();
1514 Dmsg2(dbglevel, "Found %d Delta parts in restore selection q=%s\n", num, query.c_str());
1515
1516 if (num > 0) {
1517 int64_t *result = (int64_t *)malloc (num * 4 * sizeof(int64_t));
1518 SQL_ROW row;
1519 int i=0;
1520
1521 while((row = db->sql_fetch_row())) {
1522 result[i++] = str_to_int64(row[0]); /* FileId */
1523 result[i++] = str_to_int64(row[1]); /* JobId */
1524 result[i++] = str_to_int64(row[2]); /* FilenameId */
1525 result[i++] = str_to_int64(row[3]); /* PathId */
1526 }
1527
1528 i=0;
1529 while (num > 0) {
1530 insert_missing_delta(output_table, result + i);
1531 i += 4;
1532 num--;
1533 }
1534 free(result);
1535 }
1536
1537 ret = true;
1538
1539 bail_out:
1540 Mmsg(query, "DROP TABLE btemp%s", output_table);
1541 db->bdb_sql_query(query.c_str(), NULL, NULL);
1542 db->bdb_unlock();
1543 return ret;
1544 }
1545
insert_missing_delta(char * output_table,int64_t * res)1546 void Bvfs::insert_missing_delta(char *output_table, int64_t *res)
1547 {
1548 char ed1[50];
1549 db_list_ctx lst;
1550 POOL_MEM query;
1551 JOB_DBR jr, jr2;
1552 memset(&jr, 0, sizeof(jr));
1553 memset(&jr2, 0, sizeof(jr2));
1554
1555 /* Need to limit the query to StartTime, Client/Fileset */
1556 jr2.JobId = res[1];
1557 db->bdb_get_job_record(jcr, &jr2);
1558
1559 jr.JobId = res[1];
1560 jr.ClientId = jr2.ClientId;
1561 jr.FileSetId = jr2.FileSetId;
1562 jr.JobLevel = L_INCREMENTAL;
1563 jr.StartTime = jr2.StartTime;
1564
1565 /* Get accurate jobid list */
1566 db->bdb_get_accurate_jobids(jcr, &jr, &lst);
1567
1568 Dmsg2(dbglevel_sql, "JobId list for %lld is %s\n", res[0], lst.list);
1569
1570 /* The list contains already the last DeltaSeq element, so
1571 * we don't need to select it in the next query
1572 */
1573 for (int l = strlen(lst.list); l > 0; l--) {
1574 if (lst.list[l] == ',') {
1575 lst.list[l] = '\0';
1576 break;
1577 }
1578 }
1579
1580 Dmsg1(dbglevel_sql, "JobId list after strip is %s\n", lst.list);
1581
1582 /* Escape filename */
1583 db->fnl = strlen((char *)res[2]);
1584 db->esc_name = check_pool_memory_size(db->esc_name, 2*db->fnl+2);
1585 db->bdb_escape_string(jcr, db->esc_name, (char *)res[2], db->fnl);
1586
1587 edit_int64(res[3], ed1); /* pathid */
1588
1589 int id=db->bdb_get_type_index();
1590 Mmsg(query, bvfs_select_delta_version_with_basejob_and_delta[id],
1591 lst.list, db->esc_name, ed1,
1592 lst.list, db->esc_name, ed1,
1593 lst.list, lst.list);
1594
1595 Mmsg(db->cmd, "INSERT INTO %s "
1596 "SELECT JobId, FileIndex, FileId FROM (%s) AS F1",
1597 output_table, query.c_str());
1598
1599 if (!db->bdb_sql_query(db->cmd, NULL, NULL)) {
1600 Dmsg1(dbglevel_sql, "Can't exec q=%s\n", db->cmd);
1601 }
1602 }
1603
1604 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
1605