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