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