]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/bvfs.c
Backport from BEE
[bacula/bacula] / bacula / src / cats / bvfs.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2009-2014 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from many
7    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    Bacula® is a registered trademark of Kern Sibbald.
15 */
16
17 #include "bacula.h"
18
19 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
20
21 #include "cats.h"
22 #include "bdb_priv.h"
23 #include "sql_glue.h"
24 #include "lib/htable.h"
25 #include "bvfs.h"
26
27 #define dbglevel 10
28 #define dbglevel_sql 15
29
30 static int result_handler(void *ctx, int fields, char **row)
31 {
32    if (fields == 4) {
33       Pmsg4(0, "%s\t%s\t%s\t%s\n",
34             row[0], row[1], row[2], row[3]);
35    } else if (fields == 5) {
36       Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
37             row[0], row[1], row[2], row[3], row[4]);
38    } else if (fields == 6) {
39       Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
40             row[0], row[1], row[2], row[3], row[4], row[5]);
41    } else if (fields == 7) {
42       Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
43             row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
44    }
45    return 0;
46 }
47
48 Bvfs::Bvfs(JCR *j, B_DB *mdb) {
49    jcr = j;
50    jcr->inc_use_count();
51    db = mdb;                 /* need to inc ref count */
52    jobids = get_pool_memory(PM_NAME);
53    prev_dir = get_pool_memory(PM_NAME);
54    pattern = get_pool_memory(PM_NAME);
55    filename = get_pool_memory(PM_NAME);
56    tmp = get_pool_memory(PM_NAME);
57    escaped_list = get_pool_memory(PM_NAME);
58    *filename = *jobids = *prev_dir = *pattern = 0;
59    dir_filenameid = pwd_id = offset = 0;
60    see_copies = see_all_versions = false;
61    limit = 1000;
62    attr = new_attr(jcr);
63    list_entries = result_handler;
64    user_data = this;
65    username = NULL;
66    job_acl = client_acl = pool_acl = fileset_acl = NULL;
67 }
68
69 Bvfs::~Bvfs() {
70    free_pool_memory(jobids);
71    free_pool_memory(pattern);
72    free_pool_memory(prev_dir);
73    free_pool_memory(filename);
74    free_pool_memory(tmp);
75    free_pool_memory(escaped_list);
76    if (username) {
77       free(username);
78    }
79    free_attr(attr);
80    jcr->dec_use_count();
81 }
82
83 char *Bvfs::escape_list(alist *lst)
84 {
85    char *elt;
86    int len;
87
88    /* List is empty, reject everything */
89    if (!lst || lst->size() == 0) {
90       Mmsg(escaped_list, "''");
91       return escaped_list;
92    }
93
94    *tmp = 0;
95    *escaped_list = 0;
96
97    foreach_alist(elt, lst) {
98       if (elt && *elt) {
99          len = strlen(elt);
100          /* Escape + ' ' */
101          tmp = check_pool_memory_size(tmp, 2 * len + 2 + 2);
102
103          tmp[0] = '\'';
104          db_escape_string(jcr, db, tmp + 1 , elt, len);
105          pm_strcat(tmp, "'");
106
107          if (*escaped_list) {
108             pm_strcat(escaped_list, ",");
109          }
110
111          pm_strcat(escaped_list, tmp);
112       }
113    }
114    return escaped_list;
115 }
116
117 void Bvfs::filter_jobid()
118 {
119    POOL_MEM query;
120    POOL_MEM sub_where;
121    POOL_MEM sub_join;
122
123    /* No ACL, no username, no check */
124    if (!job_acl && !fileset_acl && !client_acl && !pool_acl && !username) {
125       Dmsg0(dbglevel_sql, "No ACL\n");
126       return;
127    }
128
129    if (job_acl) {
130       Mmsg(sub_where, " AND Job.Name IN (%s) ", escape_list(job_acl));
131    }
132
133    if (fileset_acl) {
134       Mmsg(query, " AND FileSet.FileSet IN (%s) ", escape_list(fileset_acl));
135       pm_strcat(sub_where, query.c_str());
136       pm_strcat(sub_join, " JOIN FileSet USING (FileSetId) ");
137    }
138
139    if (client_acl) {
140       Mmsg(query, " AND Client.Name IN (%s) ", escape_list(client_acl));
141       pm_strcat(sub_where, query.c_str());
142    }
143
144    if (pool_acl) {
145       Mmsg(query, " AND Pool.Name IN (%s) ", escape_list(pool_acl));
146       pm_strcat(sub_where, query.c_str());
147       pm_strcat(sub_join, " JOIN Pool USING (PoolId) ");
148    }
149
150    if (username) {
151       /* Query used by Bweb to filter clients, activated when using
152        * set_username()
153        */
154       Mmsg(query,
155       "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
156         "JOIN (SELECT ClientId FROM client_group_member "
157         "JOIN client_group USING (client_group_id) "
158         "JOIN bweb_client_group_acl USING (client_group_id) "
159         "JOIN bweb_user USING (userid) "
160        "WHERE bweb_user.username = '%s' "
161       ") AS filter USING (ClientId) "
162         " WHERE JobId IN (%s) %s",
163            sub_join.c_str(), username, jobids, sub_where.c_str());
164
165    } else {
166       Mmsg(query,
167       "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
168       " WHERE JobId IN (%s) %s",
169            sub_join.c_str(), jobids, sub_where.c_str());
170    }
171
172    db_list_ctx ctx;
173    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
174    db_sql_query(db, query.c_str(), db_list_handler, &ctx);
175    pm_strcpy(jobids, ctx.list);
176 }
177
178 void Bvfs::set_jobid(JobId_t id)
179 {
180    Mmsg(jobids, "%lld", (uint64_t)id);
181    filter_jobid();
182 }
183
184 void Bvfs::set_jobids(char *ids)
185 {
186    pm_strcpy(jobids, ids);
187    filter_jobid();
188 }
189
190 /*
191  * TODO: Find a way to let the user choose how he wants to display
192  * files and directories
193  */
194
195
196 /*
197  * Working Object to store PathId already seen (avoid
198  * database queries), equivalent to %cache_ppathid in perl
199  */
200
201 #define NITEMS 50000
202 class pathid_cache {
203 private:
204    hlink *nodes;
205    int nb_node;
206    int max_node;
207
208    alist *table_node;
209
210    htable *cache_ppathid;
211
212 public:
213    pathid_cache() {
214       hlink link;
215       cache_ppathid = (htable *)malloc(sizeof(htable));
216       cache_ppathid->init(&link, &link, NITEMS);
217       max_node = NITEMS;
218       nodes = (hlink *) malloc(max_node * sizeof (hlink));
219       nb_node = 0;
220       table_node = New(alist(5, owned_by_alist));
221       table_node->append(nodes);
222    }
223
224    hlink *get_hlink() {
225       if (++nb_node >= max_node) {
226          nb_node = 0;
227          nodes = (hlink *)malloc(max_node * sizeof(hlink));
228          table_node->append(nodes);
229       }
230       return nodes + nb_node;
231    }
232
233    bool lookup(char *pathid) {
234       bool ret = cache_ppathid->lookup(pathid) != NULL;
235       return ret;
236    }
237
238    void insert(char *pathid) {
239       hlink *h = get_hlink();
240       cache_ppathid->insert(pathid, h);
241    }
242
243    ~pathid_cache() {
244       cache_ppathid->destroy();
245       free(cache_ppathid);
246       delete table_node;
247    }
248 private:
249    pathid_cache(const pathid_cache &); /* prohibit pass by value */
250    pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
251 } ;
252
253 /* Return the parent_dir with the trailing /  (update the given string)
254  * TODO: see in the rest of bacula if we don't have already this function
255  * dir=/tmp/toto/
256  * dir=/tmp/
257  * dir=/
258  * dir=
259  */
260 char *bvfs_parent_dir(char *path)
261 {
262    char *p = path;
263    int len = strlen(path) - 1;
264
265    /* windows directory / */
266    if (len == 2 && B_ISALPHA(path[0])
267                 && path[1] == ':'
268                 && path[2] == '/')
269    {
270       len = 0;
271       path[0] = '\0';
272    }
273
274    if (len >= 0 && path[len] == '/') {      /* if directory, skip last / */
275       path[len] = '\0';
276    }
277
278    if (len > 0) {
279       p += len;
280       while (p > path && !IsPathSeparator(*p)) {
281          p--;
282       }
283       p[1] = '\0';
284    }
285    return path;
286 }
287
288 /* Return the basename of the with the trailing /
289  * TODO: see in the rest of bacula if we don't have
290  * this function already
291  */
292 char *bvfs_basename_dir(char *path)
293 {
294    char *p = path;
295    int len = strlen(path) - 1;
296
297    if (path[len] == '/') {      /* if directory, skip last / */
298       len -= 1;
299    }
300
301    if (len > 0) {
302       p += len;
303       while (p > path && !IsPathSeparator(*p)) {
304          p--;
305       }
306       if (*p == '/') {
307          p++;                  /* skip first / */
308       }
309    }
310    return p;
311 }
312
313 static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
314                                  pathid_cache &ppathid_cache,
315                                  char *org_pathid, char *path)
316 {
317    Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
318    char pathid[50];
319    ATTR_DBR parent;
320    char *bkp = mdb->path;
321    strncpy(pathid, org_pathid, sizeof(pathid));
322
323    /* Does the ppathid exist for this ? we use a memory cache...  In order to
324     * avoid the full loop, we consider that if a dir is allready in the
325     * PathHierarchy table, then there is no need to calculate all the
326     * hierarchy
327     */
328    while (path && *path)
329    {
330       if (!ppathid_cache.lookup(pathid))
331       {
332          Mmsg(mdb->cmd,
333               "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
334               pathid);
335
336          if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
337             goto bail_out;      /* Query failed, just leave */
338          }
339
340          /* Do we have a result ? */
341          if (sql_num_rows(mdb) > 0) {
342             ppathid_cache.insert(pathid);
343             /* This dir was in the db ...
344              * It means we can leave, the tree has allready been built for
345              * this dir
346              */
347             goto bail_out;
348          } else {
349             /* search or create parent PathId in Path table */
350             mdb->path = bvfs_parent_dir(path);
351             mdb->pnl = strlen(mdb->path);
352             if (!db_create_path_record(jcr, mdb, &parent)) {
353                goto bail_out;
354             }
355             ppathid_cache.insert(pathid);
356
357             Mmsg(mdb->cmd,
358                  "INSERT INTO PathHierarchy (PathId, PPathId) "
359                  "VALUES (%s,%lld)",
360                  pathid, (uint64_t) parent.PathId);
361
362             if (!INSERT_DB(jcr, mdb, mdb->cmd)) {
363                goto bail_out;   /* Can't insert the record, just leave */
364             }
365
366             edit_uint64(parent.PathId, pathid);
367             path = mdb->path;   /* already done */
368          }
369       } else {
370          /* It's already in the cache.  We can leave, no time to waste here,
371           * all the parent dirs have allready been done
372           */
373          goto bail_out;
374       }
375    }
376
377 bail_out:
378    mdb->path = bkp;
379    mdb->fnl = 0;
380 }
381
382 /*
383  * Internal function to update path_hierarchy cache with a shared pathid cache
384  * return Error 0
385  *        OK    1
386  */
387 static int update_path_hierarchy_cache(JCR *jcr,
388                                         B_DB *mdb,
389                                         pathid_cache &ppathid_cache,
390                                         JobId_t JobId)
391 {
392    Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
393    int ret=0;
394    uint32_t num;
395    char jobid[50];
396    edit_uint64(JobId, jobid);
397
398    db_lock(mdb);
399    db_start_transaction(jcr, mdb);
400
401    Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
402
403    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
404       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
405       ret = 1;
406       goto bail_out;
407    }
408
409    /* Inserting path records for JobId */
410    Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
411                    "SELECT DISTINCT PathId, JobId "
412                      "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
413                            "UNION "
414                            "SELECT PathId, BaseFiles.JobId "
415                              "FROM BaseFiles JOIN File AS F USING (FileId) "
416                             "WHERE BaseFiles.JobId = %s) AS B",
417         jobid, jobid);
418
419    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
420       Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId );
421       goto bail_out;
422    }
423
424    /* Now we have to do the directory recursion stuff to determine missing
425     * visibility We try to avoid recursion, to be as fast as possible We also
426     * only work on not allready hierarchised directories...
427     */
428    Mmsg(mdb->cmd,
429      "SELECT PathVisibility.PathId, Path "
430        "FROM PathVisibility "
431             "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
432             "LEFT JOIN PathHierarchy "
433          "ON (PathVisibility.PathId = PathHierarchy.PathId) "
434       "WHERE PathVisibility.JobId = %s "
435         "AND PathHierarchy.PathId IS NULL "
436       "ORDER BY Path", jobid);
437    Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
438
439    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
440       Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId );
441       goto bail_out;
442    }
443
444    /* TODO: I need to reuse the DB connection without emptying the result
445     * So, now i'm copying the result in memory to be able to query the
446     * catalog descriptor again.
447     */
448    num = sql_num_rows(mdb);
449    if (num > 0) {
450       char **result = (char **)malloc (num * 2 * sizeof(char *));
451
452       SQL_ROW row;
453       int i=0;
454       while((row = sql_fetch_row(mdb))) {
455          result[i++] = bstrdup(row[0]);
456          result[i++] = bstrdup(row[1]);
457       }
458
459       i=0;
460       while (num > 0) {
461          build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
462          free(result[i++]);
463          free(result[i++]);
464          num--;
465       }
466       free(result);
467    }
468
469    if (mdb->db_get_type_index() == SQL_TYPE_SQLITE3) {
470       Mmsg(mdb->cmd,
471  "INSERT INTO PathVisibility (PathId, JobId) "
472    "SELECT DISTINCT h.PPathId AS PathId, %s "
473      "FROM PathHierarchy AS h "
474     "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
475       "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
476            jobid, jobid, jobid );
477
478    } else {
479       Mmsg(mdb->cmd,
480   "INSERT INTO PathVisibility (PathId, JobId)  "
481    "SELECT a.PathId,%s "
482    "FROM ( "
483      "SELECT DISTINCT h.PPathId AS PathId "
484        "FROM PathHierarchy AS h "
485        "JOIN  PathVisibility AS p ON (h.PathId=p.PathId) "
486       "WHERE p.JobId=%s) AS a LEFT JOIN "
487        "(SELECT PathId "
488           "FROM PathVisibility "
489          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
490    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
491    }
492
493    do {
494       ret = QUERY_DB(jcr, mdb, mdb->cmd);
495    } while (ret && sql_affected_rows(mdb) > 0);
496
497    Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
498    UPDATE_DB(jcr, mdb, mdb->cmd);
499
500 bail_out:
501    db_end_transaction(jcr, mdb);
502    db_unlock(mdb);
503    return ret;
504 }
505
506 /*
507  * Find an store the filename descriptor for empty directories Filename.Name=''
508  */
509 DBId_t Bvfs::get_dir_filenameid()
510 {
511    uint32_t id;
512    if (dir_filenameid) {
513       return dir_filenameid;
514    }
515    Mmsg(db->cmd, "SELECT FilenameId FROM Filename WHERE Name = ''");
516    db_sql_query(db, db->cmd, db_int_handler, &id);
517    dir_filenameid = id;
518    return dir_filenameid;
519 }
520
521 /* Compute the cache for the bfileview compoment */
522 void Bvfs::fv_update_cache()
523 {
524    int64_t pathid;
525    int64_t size=0, count=0;
526
527    Dmsg0(dbglevel, "fv_update_cache()\n");
528
529    if (!*jobids) {
530       return;                   /* Nothing to build */
531    }
532
533    db_lock(db);
534    db_start_transaction(jcr, db);
535
536    pathid = get_root();
537
538    fv_compute_size_and_count(pathid, &size, &count);
539
540    db_end_transaction(jcr, db);
541    db_unlock(db);
542 }
543
544 /* Not yet working */
545 void Bvfs::fv_get_big_files(int64_t pathid, int64_t min_size, int32_t limit)
546 {
547    Mmsg(db->cmd,
548         "SELECT FilenameId AS filenameid, Name AS name, size "
549           "FROM ( "
550          "SELECT FilenameId, base64_decode_lstat(8,LStat) AS size "
551            "FROM File "
552           "WHERE PathId  = %lld "
553             "AND JobId = %s "
554         ") AS S INNER JOIN Filename USING (FilenameId) "
555      "WHERE S.size > %lld "
556      "ORDER BY S.size DESC "
557      "LIMIT %d ", pathid, jobids, min_size, limit);
558 }
559
560 /* Get the current path size and files count */
561 void Bvfs::fv_get_current_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
562 {
563    SQL_ROW row;
564
565    *size = *count = 0;
566
567    Mmsg(db->cmd,
568  "SELECT Size AS size, Files AS files "
569   " FROM PathVisibility "
570  " WHERE PathId = %lld "
571    " AND JobId = %s ", pathid, jobids);
572
573    if (!QUERY_DB(jcr, db, db->cmd)) {
574       return;
575    }
576
577    if ((row = sql_fetch_row(db))) {
578       *size = str_to_int64(row[0]);
579       *count =  str_to_int64(row[1]);
580    }
581 }
582
583 /* Compute for the current path the size and files count */
584 void Bvfs::fv_get_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
585 {
586    SQL_ROW row;
587
588    *size = *count = 0;
589
590    Mmsg(db->cmd,
591  "SELECT sum(base64_decode_lstat(8,LStat)) AS size, count(1) AS files "
592   " FROM File "
593  " WHERE PathId = %lld "
594    " AND JobId = %s ", pathid, jobids);
595
596    if (!QUERY_DB(jcr, db, db->cmd)) {
597       return;
598    }
599
600    if ((row = sql_fetch_row(db))) {
601       *size = str_to_int64(row[0]);
602       *count =  str_to_int64(row[1]);
603    }
604 }
605
606 void Bvfs::fv_compute_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
607 {
608    Dmsg1(dbglevel, "fv_compute_size_and_count(%lld)\n", pathid);
609
610    fv_get_current_size_and_count(pathid, size, count);
611    if (*size > 0) {
612       return;
613    }
614
615    /* Update stats for the current directory */
616    fv_get_size_and_count(pathid, size, count);
617
618    /* Update stats for all sub directories */
619    Mmsg(db->cmd,
620         " SELECT PathId "
621           " FROM PathVisibility "
622                " INNER JOIN PathHierarchy USING (PathId) "
623          " WHERE PPathId  = %lld "
624            " AND JobId = %s ", pathid, jobids);
625
626    QUERY_DB(jcr, db, db->cmd);
627    int num = sql_num_rows(db);
628
629    if (num > 0) {
630       int64_t *result = (int64_t *)malloc (num * sizeof(int64_t));
631       SQL_ROW row;
632       int i=0;
633
634       while((row = sql_fetch_row(db))) {
635          result[i++] = str_to_int64(row[0]); /* PathId */
636       }
637
638       i=0;
639       while (num > 0) {
640          int64_t c=0, s=0;
641          fv_compute_size_and_count(result[i], &s, &c);
642          *size += s;
643          *count += c;
644
645          i++;
646          num--;
647       }
648       free(result);
649    }
650
651    fv_update_size_and_count(pathid, *size, *count);
652 }
653
654 void Bvfs::fv_update_size_and_count(int64_t pathid, int64_t size, int64_t count)
655 {
656    Mmsg(db->cmd,
657         "UPDATE PathVisibility SET Files = %lld, Size = %lld "
658         " WHERE JobId = %s "
659         " AND PathId = %lld ", count, size, jobids, pathid);
660
661    UPDATE_DB(jcr, db, db->cmd);
662 }
663
664 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
665 {
666    uint32_t nb=0;
667    db_list_ctx jobids_list;
668
669    db_lock(mdb);
670
671 #ifdef xxx
672    /* TODO: Remove this code when updating make_bacula_table script */
673    Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
674    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
675       Dmsg0(dbglevel, "Creating cache table\n");
676       Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
677       QUERY_DB(jcr, mdb, mdb->cmd);
678
679       Mmsg(mdb->cmd,
680            "CREATE TABLE PathHierarchy ( "
681            "PathId integer NOT NULL, "
682            "PPathId integer NOT NULL, "
683            "CONSTRAINT pathhierarchy_pkey "
684            "PRIMARY KEY (PathId))");
685       QUERY_DB(jcr, mdb, mdb->cmd);
686
687       Mmsg(mdb->cmd,
688            "CREATE INDEX pathhierarchy_ppathid "
689            "ON PathHierarchy (PPathId)");
690       QUERY_DB(jcr, mdb, mdb->cmd);
691
692       Mmsg(mdb->cmd,
693            "CREATE TABLE PathVisibility ("
694            "PathId integer NOT NULL, "
695            "JobId integer NOT NULL, "
696            "Size int8 DEFAULT 0, "
697            "Files int4 DEFAULT 0, "
698            "CONSTRAINT pathvisibility_pkey "
699            "PRIMARY KEY (JobId, PathId))");
700       QUERY_DB(jcr, mdb, mdb->cmd);
701
702       Mmsg(mdb->cmd,
703            "CREATE INDEX pathvisibility_jobid "
704            "ON PathVisibility (JobId)");
705       QUERY_DB(jcr, mdb, mdb->cmd);
706
707    }
708 #endif
709
710    Mmsg(mdb->cmd,
711  "SELECT JobId from Job "
712   "WHERE HasCache = 0 "
713     "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
714   "ORDER BY JobId");
715
716    db_sql_query(mdb, mdb->cmd, db_list_handler, &jobids_list);
717
718    bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
719
720    db_start_transaction(jcr, mdb);
721    Dmsg0(dbglevel, "Cleaning pathvisibility\n");
722    Mmsg(mdb->cmd,
723         "DELETE FROM PathVisibility "
724          "WHERE NOT EXISTS "
725         "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
726    nb = DELETE_DB(jcr, mdb, mdb->cmd);
727    Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
728
729    db_end_transaction(jcr, mdb);
730    db_unlock(mdb);
731 }
732
733 /*
734  * Update the bvfs cache for given jobids (1,2,3,4)
735  */
736 int
737 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
738 {
739    pathid_cache ppathid_cache;
740    JobId_t JobId;
741    char *p;
742    int ret=1;
743
744    for (p=jobids; ; ) {
745       int stat = get_next_jobid_from_list(&p, &JobId);
746       if (stat < 0) {
747          return 0;
748       }
749       if (stat == 0) {
750          break;
751       }
752       Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
753       if (!update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId)) {
754          ret = 0;
755       }
756    }
757    return ret;
758 }
759
760 /*
761  * Update the bvfs fileview for given jobids
762  */
763 void
764 bvfs_update_fv_cache(JCR *jcr, B_DB *mdb, char *jobids)
765 {
766    char *p;
767    JobId_t JobId;
768    Bvfs bvfs(jcr, mdb);
769
770    for (p=jobids; ; ) {
771       int stat = get_next_jobid_from_list(&p, &JobId);
772       if (stat < 0) {
773          return;
774       }
775       if (stat == 0) {
776          break;
777       }
778
779       Dmsg1(dbglevel, "Trying to create cache for %lld\n", (int64_t)JobId);
780
781       bvfs.set_jobid(JobId);
782       bvfs.fv_update_cache();
783    }
784 }
785
786 /*
787  * Update the bvfs cache for current jobids
788  */
789 void Bvfs::update_cache()
790 {
791    bvfs_update_path_hierarchy_cache(jcr, db, jobids);
792 }
793
794 /* Change the current directory, returns true if the path exists */
795 bool Bvfs::ch_dir(const char *path)
796 {
797    pm_strcpy(db->path, path);
798    db->pnl = strlen(db->path);
799    db_lock(db);
800    ch_dir(db_get_path_record(jcr, db));
801    db_unlock(db);
802    return pwd_id != 0;
803 }
804
805 /*
806  * Get all file versions for a specified client
807  * TODO: Handle basejobs using different client
808  */
809 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
810 {
811    Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
812          (uint64_t)fnid, client);
813    char ed1[50], ed2[50];
814    POOL_MEM q;
815    if (see_copies) {
816       Mmsg(q, " AND Job.Type IN ('C', 'B') ");
817    } else {
818       Mmsg(q, " AND Job.Type = 'B' ");
819    }
820
821    POOL_MEM query;
822
823    Mmsg(query,//    1           2              3
824 "SELECT 'V', File.PathId, File.FilenameId,  File.Md5, "
825 //         4          5           6
826         "File.JobId, File.LStat, File.FileId, "
827 //         7                    8
828        "Media.VolumeName, Media.InChanger "
829 "FROM File, Job, Client, JobMedia, Media "
830 "WHERE File.FilenameId = %s "
831   "AND File.PathId=%s "
832   "AND File.JobId = Job.JobId "
833   "AND Job.JobId = JobMedia.JobId "
834   "AND File.FileIndex >= JobMedia.FirstIndex "
835   "AND File.FileIndex <= JobMedia.LastIndex "
836   "AND JobMedia.MediaId = Media.MediaId "
837   "AND Job.ClientId = Client.ClientId "
838   "AND Client.Name = '%s' "
839   "%s ORDER BY FileId LIMIT %d OFFSET %d"
840         ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
841         limit, offset);
842    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
843    db_sql_query(db, query.c_str(), list_entries, user_data);
844 }
845
846 /*
847  * Get all volumes for a specific file
848  */
849 void Bvfs::get_volumes(DBId_t  fileid)
850 {
851    Dmsg1(dbglevel, "get_volumes(%lld)\n", (uint64_t)fileid);
852
853    char ed1[50];
854    POOL_MEM query;
855
856    Mmsg(query,
857 //                          7                8
858 "SELECT 'L',0,0,0,0,0,0, Media.VolumeName, Media.InChanger "
859 "FROM File JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
860 "WHERE File.FileId = %s "
861   "AND File.FileIndex >= JobMedia.FirstIndex "
862   "AND File.FileIndex <= JobMedia.LastIndex "
863   " ORDER BY JobMediaId LIMIT %d OFFSET %d"
864         ,edit_uint64(fileid, ed1), limit, offset);
865    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
866    db_sql_query(db, query.c_str(), list_entries, user_data);
867 }
868
869 DBId_t Bvfs::get_root()
870 {
871    int p;
872    *db->path = 0;
873    db_lock(db);
874    p = db_get_path_record(jcr, db);
875    db_unlock(db);
876    return p;
877 }
878
879 static int path_handler(void *ctx, int fields, char **row)
880 {
881    Bvfs *fs = (Bvfs *) ctx;
882    return fs->_handle_path(ctx, fields, row);
883 }
884
885 int Bvfs::_handle_path(void *ctx, int fields, char **row)
886 {
887    if (bvfs_is_dir(row)) {
888       /* can have the same path 2 times */
889       if (strcmp(row[BVFS_PathId], prev_dir)) {
890          pm_strcpy(prev_dir, row[BVFS_PathId]);
891          return list_entries(user_data, fields, row);
892       }
893    }
894    return 0;
895 }
896
897 /*
898  * Retrieve . and .. information
899  */
900 void Bvfs::ls_special_dirs()
901 {
902    Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
903    char ed1[50], ed2[50];
904    if (*jobids == 0) {
905       return;
906    }
907    if (!dir_filenameid) {
908       get_dir_filenameid();
909    }
910
911    /* Will fetch directories  */
912    *prev_dir = 0;
913
914    POOL_MEM query;
915    Mmsg(query,
916 "(SELECT PPathId AS PathId, '..' AS Path "
917     "FROM  PathHierarchy "
918    "WHERE  PathId = %s "
919 "UNION "
920  "SELECT %s AS PathId, '.' AS Path)",
921         edit_uint64(pwd_id, ed1), ed1);
922
923    POOL_MEM query2;
924    Mmsg(query2,// 1      2     3        4     5       6
925 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
926   "FROM %s AS tmp  LEFT JOIN ( " // get attributes if any
927        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
928               "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
929        "WHERE File1.FilenameId = %s "
930        "AND File1.JobId IN (%s)) AS listfile1 "
931   "ON (tmp.PathId = listfile1.PathId) "
932   "ORDER BY tmp.Path, JobId DESC ",
933         query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
934
935    Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
936    db_sql_query(db, query2.c_str(), path_handler, this);
937 }
938
939 /* Returns true if we have dirs to read */
940 bool Bvfs::ls_dirs()
941 {
942    Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
943    char ed1[50], ed2[50];
944    if (*jobids == 0) {
945       return false;
946    }
947
948    POOL_MEM query;
949    POOL_MEM filter;
950    if (*pattern) {
951       Mmsg(filter, " AND Path2.Path %s '%s' ",
952            match_query[db_get_type_index(db)], pattern);
953
954    }
955
956    if (!dir_filenameid) {
957       get_dir_filenameid();
958    }
959
960    /* the sql query displays same directory multiple time, take the first one */
961    *prev_dir = 0;
962
963    /* Let's retrieve the list of the visible dirs in this dir ...
964     * First, I need the empty filenameid to locate efficiently
965     * the dirs in the file table
966     * my $dir_filenameid = $self->get_dir_filenameid();
967     */
968    /* Then we get all the dir entries from File ... */
969    Mmsg(query,
970 //       0     1     2   3      4     5       6
971 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
972     "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
973            "lower(Path1.Path) AS lpath, "
974            "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
975            "listfile1.FileId AS FileId "
976     "FROM ( "
977       "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
978       "FROM PathHierarchy AS PathHierarchy1 "
979       "JOIN Path AS Path2 "
980         "ON (PathHierarchy1.PathId = Path2.PathId) "
981       "JOIN PathVisibility AS PathVisibility1 "
982         "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
983       "WHERE PathHierarchy1.PPathId = %s "
984       "AND PathVisibility1.JobId IN (%s) "
985            "%s "
986      ") AS listpath1 "
987    "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
988
989    "LEFT JOIN ( " /* get attributes if any */
990        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
991               "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
992        "WHERE File1.FilenameId = %s "
993        "AND File1.JobId IN (%s)) AS listfile1 "
994        "ON (listpath1.PathId = listfile1.PathId) "
995     ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
996         edit_uint64(pwd_id, ed1),
997         jobids,
998         filter.c_str(),
999         edit_uint64(dir_filenameid, ed2),
1000         jobids,
1001         limit, offset);
1002
1003    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1004
1005    db_lock(db);
1006    db_sql_query(db, query.c_str(), path_handler, this);
1007    nb_record = sql_num_rows(db);
1008    db_unlock(db);
1009
1010    return nb_record == limit;
1011 }
1012
1013 void build_ls_files_query(B_DB *db, POOL_MEM &query,
1014                           const char *JobId, const char *PathId,
1015                           const char *filter, int64_t limit, int64_t offset)
1016 {
1017    if (db_get_type_index(db) == SQL_TYPE_POSTGRESQL) {
1018       Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
1019            JobId, PathId, JobId, PathId,
1020            filter, limit, offset);
1021    } else {
1022       Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
1023            JobId, PathId, JobId, PathId,
1024            limit, offset, filter, JobId, JobId);
1025    }
1026 }
1027
1028 /* Returns true if we have files to read */
1029 bool Bvfs::ls_files()
1030 {
1031    POOL_MEM query;
1032    POOL_MEM filter;
1033    char pathid[50];
1034
1035    Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
1036    if (*jobids == 0) {
1037       return false;
1038    }
1039
1040    if (!pwd_id) {
1041       ch_dir(get_root());
1042    }
1043
1044    edit_uint64(pwd_id, pathid);
1045    if (*pattern) {
1046       Mmsg(filter, " AND Filename.Name %s '%s' ",
1047            match_query[db_get_type_index(db)], pattern);
1048
1049    } else if (*filename) {
1050       Mmsg(filter, " AND Filename.Name = '%s' ", filename);
1051    }
1052
1053    build_ls_files_query(db, query,
1054                         jobids, pathid, filter.c_str(),
1055                         limit, offset);
1056
1057    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1058
1059    db_lock(db);
1060    db_sql_query(db, query.c_str(), list_entries, user_data);
1061    nb_record = sql_num_rows(db);
1062    db_unlock(db);
1063
1064    return nb_record == limit;
1065 }
1066
1067
1068 /*
1069  * Return next Id from comma separated list
1070  *
1071  * Returns:
1072  *   1 if next Id returned
1073  *   0 if no more Ids are in list
1074  *  -1 there is an error
1075  * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
1076  */
1077 static int get_next_id_from_list(char **p, int64_t *Id)
1078 {
1079    const int maxlen = 30;
1080    char id[maxlen+1];
1081    char *q = *p;
1082
1083    id[0] = 0;
1084    for (int i=0; i<maxlen; i++) {
1085       if (*q == 0) {
1086          break;
1087       } else if (*q == ',') {
1088          q++;
1089          break;
1090       }
1091       id[i] = *q++;
1092       id[i+1] = 0;
1093    }
1094    if (id[0] == 0) {
1095       return 0;
1096    } else if (!is_a_number(id)) {
1097       return -1;                      /* error */
1098    }
1099    *p = q;
1100    *Id = str_to_int64(id);
1101    return 1;
1102 }
1103
1104 static int get_path_handler(void *ctx, int fields, char **row)
1105 {
1106    POOL_MEM *buf = (POOL_MEM *) ctx;
1107    pm_strcpy(*buf, row[0]);
1108    return 0;
1109 }
1110
1111 static bool check_temp(char *output_table)
1112 {
1113    if (output_table[0] == 'b' &&
1114        output_table[1] == '2' &&
1115        is_an_integer(output_table + 2))
1116    {
1117       return true;
1118    }
1119    return false;
1120 }
1121
1122 void Bvfs::clear_cache()
1123 {
1124    db_sql_query(db, "BEGIN",                     NULL, NULL);
1125    db_sql_query(db, "UPDATE Job SET HasCache=0", NULL, NULL);
1126    db_sql_query(db, "TRUNCATE PathHierarchy",    NULL, NULL);
1127    db_sql_query(db, "TRUNCATE PathVisibility",   NULL, NULL);
1128    db_sql_query(db, "COMMIT",                    NULL, NULL);
1129 }
1130
1131 bool Bvfs::drop_restore_list(char *output_table)
1132 {
1133    POOL_MEM query;
1134    if (check_temp(output_table)) {
1135       Mmsg(query, "DROP TABLE %s", output_table);
1136       db_sql_query(db, query.c_str(), NULL, NULL);
1137       return true;
1138    }
1139    return false;
1140 }
1141
1142 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
1143                                 char *output_table)
1144 {
1145    POOL_MEM query;
1146    POOL_MEM tmp, tmp2;
1147    int64_t id, jobid, prev_jobid;
1148    bool init=false;
1149    bool ret=false;
1150    /* check args */
1151    if ((*fileid   && !is_a_number_list(fileid))  ||
1152        (*dirid    && !is_a_number_list(dirid))   ||
1153        (*hardlink && !is_a_number_list(hardlink))||
1154        (!*hardlink && !*fileid && !*dirid && !*hardlink))
1155    {
1156       return false;
1157    }
1158    if (!check_temp(output_table)) {
1159       return false;
1160    }
1161
1162    db_lock(db);
1163
1164    /* Cleanup old tables first */
1165    Mmsg(query, "DROP TABLE btemp%s", output_table);
1166    db_sql_query(db, query.c_str());
1167
1168    Mmsg(query, "DROP TABLE %s", output_table);
1169    db_sql_query(db, query.c_str());
1170
1171    Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
1172
1173    if (*fileid) {               /* Select files with their direct id */
1174       init=true;
1175       Mmsg(tmp,"SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1176                       "PathId, FileId "
1177                  "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
1178            fileid);
1179       pm_strcat(query, tmp.c_str());
1180    }
1181
1182    /* Add a directory content */
1183    while (get_next_id_from_list(&dirid, &id) == 1) {
1184       Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
1185
1186       if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
1187          Dmsg0(dbglevel, "Can't search for path\n");
1188          /* print error */
1189          goto bail_out;
1190       }
1191       if (!strcmp(tmp2.c_str(), "")) { /* path not found */
1192          Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
1193                id, tmp.c_str(), tmp2.c_str());
1194          break;
1195       }
1196       /* escape % and _ for LIKE search */
1197       tmp.check_size((strlen(tmp2.c_str())+1) * 2);
1198       char *p = tmp.c_str();
1199       for (char *s = tmp2.c_str(); *s ; s++) {
1200          if (*s == '%' || *s == '_' || *s == '\\') {
1201             *p = '\\';
1202             p++;
1203          }
1204          *p = *s;
1205          p++;
1206       }
1207       *p = '\0';
1208       tmp.strcat("%");
1209
1210       size_t len = strlen(tmp.c_str());
1211       tmp2.check_size((len+1) * 2);
1212       db_escape_string(jcr, db, tmp2.c_str(), tmp.c_str(), len);
1213
1214       if (init) {
1215          query.strcat(" UNION ");
1216       }
1217
1218       Mmsg(tmp, "SELECT Job.JobId, JobTDate, File.FileIndex, File.FilenameId, "
1219                         "File.PathId, FileId "
1220                    "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
1221                   "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
1222            tmp2.c_str(), jobids);
1223       query.strcat(tmp.c_str());
1224       init = true;
1225
1226       query.strcat(" UNION ");
1227
1228       /* A directory can have files from a BaseJob */
1229       Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
1230                         "File.FilenameId, File.PathId, BaseFiles.FileId "
1231                    "FROM BaseFiles "
1232                         "JOIN File USING (FileId) "
1233                         "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
1234                         "JOIN Path USING (PathId) "
1235                   "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
1236            tmp2.c_str(), jobids);
1237       query.strcat(tmp.c_str());
1238    }
1239
1240    /* expect jobid,fileindex */
1241    prev_jobid=0;
1242    while (get_next_id_from_list(&hardlink, &jobid) == 1) {
1243       if (get_next_id_from_list(&hardlink, &id) != 1) {
1244          Dmsg0(dbglevel, "hardlink should be two by two\n");
1245          goto bail_out;
1246       }
1247       if (jobid != prev_jobid) { /* new job */
1248          if (prev_jobid == 0) {  /* first jobid */
1249             if (init) {
1250                query.strcat(" UNION ");
1251             }
1252          } else {               /* end last job, start new one */
1253             tmp.strcat(") UNION ");
1254             query.strcat(tmp.c_str());
1255          }
1256          Mmsg(tmp,   "SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1257                             "PathId, FileId "
1258                        "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
1259                         "AND FileIndex IN (%lld", jobid, id);
1260          prev_jobid = jobid;
1261
1262       } else {                  /* same job, add new findex */
1263          Mmsg(tmp2, ", %lld", id);
1264          tmp.strcat(tmp2.c_str());
1265       }
1266    }
1267
1268    if (prev_jobid != 0) {       /* end last job */
1269       tmp.strcat(") ");
1270       query.strcat(tmp.c_str());
1271       init = true;
1272    }
1273
1274    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1275
1276    if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1277       Dmsg0(dbglevel, "Can't execute q\n");
1278       goto bail_out;
1279    }
1280
1281    Mmsg(query, sql_bvfs_select[db_get_type_index(db)],
1282         output_table, output_table, output_table);
1283
1284    /* TODO: handle jobid filter */
1285    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1286    if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1287       Dmsg0(dbglevel, "Can't execute q\n");
1288       goto bail_out;
1289    }
1290
1291    /* MySQL need it */
1292    if (db_get_type_index(db) == SQL_TYPE_MYSQL) {
1293       Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)",
1294            output_table, output_table);
1295       Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1296       if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1297          Dmsg0(dbglevel, "Can't execute q\n");
1298          goto bail_out;
1299       }
1300    }
1301
1302    ret = true;
1303
1304 bail_out:
1305    Mmsg(query, "DROP TABLE btemp%s", output_table);
1306    db_sql_query(db, query.c_str(), NULL, NULL);
1307    db_unlock(db);
1308    return ret;
1309 }
1310
1311 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */