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