]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/bvfs.c
Merge branch 'master' into basejobv3
[bacula/bacula] / bacula / src / cats / bvfs.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2009-2009 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation, which is 
11    listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28
29 #define __SQL_C                       /* indicate that this is sql.c */
30
31 #include "bacula.h"
32 #include "cats/cats.h"
33 #include "lib/htable.h"
34 #include "bvfs.h"
35
36 #define dbglevel 10
37 #define dbglevel_sql 15
38
39 static int result_handler(void *ctx, int fields, char **row)
40 {
41    if (fields == 4) {
42       Pmsg4(0, "%s\t%s\t%s\t%s\n", 
43             row[0], row[1], row[2], row[3]);
44    } else if (fields == 5) {
45       Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n", 
46             row[0], row[1], row[2], row[3], row[4]);
47    } else if (fields == 6) {
48       Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n", 
49             row[0], row[1], row[2], row[3], row[4], row[5]);
50    } else if (fields == 7) {
51       Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 
52             row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
53    }
54    return 0;
55 }
56
57 Bvfs::Bvfs(JCR *j, B_DB *mdb) {
58    jcr = j;
59    jcr->inc_use_count();
60    db = mdb;                 /* need to inc ref count */
61    prev_dir = get_pool_memory(PM_NAME);
62    jobids = get_pool_memory(PM_NAME);
63    pattern = get_pool_memory(PM_NAME);
64    *prev_dir = *pattern = *jobids = 0;
65    dir_filenameid = pwd_id = offset = 0;
66    see_copies = see_all_version = false;
67    limit = 1000;
68    attr = new_attr(jcr);
69    list_entries = result_handler;
70    user_data = this;
71 }
72
73 Bvfs::~Bvfs() {
74    free_pool_memory(jobids);
75    free_pool_memory(pattern);
76    free_pool_memory(prev_dir);
77    free_attr(attr);
78    jcr->dec_use_count();
79 }
80
81 /* 
82  * TODO: Find a way to let the user choose how he wants to display
83  * files and directories
84  */
85
86
87 /* 
88  * Working Object to store PathId already seen (avoid
89  * database queries), equivalent to %cache_ppathid in perl
90  */
91
92 #define NITEMS 50000
93 class pathid_cache {
94 private:
95    hlink *nodes;
96    int nb_node;
97    int max_node;
98    htable *cache_ppathid;
99
100 public:
101    pathid_cache() {
102       hlink link;
103       cache_ppathid = (htable *)malloc(sizeof(htable));
104       cache_ppathid->init(&link, &link, NITEMS);
105       max_node = NITEMS;
106       nodes = (hlink *) malloc(max_node * sizeof (hlink));
107       nb_node = 0;
108    }
109
110    hlink *get_hlink() {
111       if (nb_node >= max_node) {
112          max_node *= 2;
113          nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
114       }
115       return nodes + nb_node++;
116    }
117
118    bool lookup(char *pathid) {
119       bool ret = cache_ppathid->lookup(pathid) != NULL;
120       return ret;
121    }
122    
123    void insert(char *pathid) {
124       hlink *h = get_hlink();
125       cache_ppathid->insert(pathid, h);
126    }
127
128    ~pathid_cache() {
129       cache_ppathid->destroy();
130       free(cache_ppathid);
131       free(nodes);
132    }
133 } ;
134
135 /* Return the parent_dir with the trailing /  (update the given string)
136  * TODO: see in the rest of bacula if we don't have already this function
137  * dir=/tmp/toto/
138  * dir=/tmp/
139  * dir=/
140  * dir=
141  */
142 char *bvfs_parent_dir(char *path)
143 {
144    char *p = path;
145    int len = strlen(path) - 1;
146
147    if (len >= 0 && path[len] == '/') {      /* if directory, skip last / */
148       path[len] = '\0';
149    }
150
151    if (len > 0) {
152       p += len;
153       while (p > path && !IsPathSeparator(*p)) {
154          p--;
155       }
156       p[1] = '\0';
157    }
158    return path;
159 }
160
161 /* Return the basename of the with the trailing /
162  * TODO: see in the rest of bacula if we don't have
163  * this function already
164  */
165 char *bvfs_basename_dir(char *path)
166 {
167    char *p = path;
168    int len = strlen(path) - 1;
169
170    if (path[len] == '/') {      /* if directory, skip last / */
171       len -= 1;
172    }
173
174    if (len > 0) {
175       p += len;
176       while (p > path && !IsPathSeparator(*p)) {
177          p--;
178       }
179       if (*p == '/') {
180          p++;                  /* skip first / */
181       }
182    } 
183    return p;
184 }
185
186 static void build_path_hierarchy(JCR *jcr, B_DB *mdb, 
187                                  pathid_cache &ppathid_cache, 
188                                  char *org_pathid, char *path)
189 {
190    Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
191    char pathid[50];
192    ATTR_DBR parent;
193    char *bkp = mdb->path;
194    strncpy(pathid, org_pathid, sizeof(pathid));
195
196    /* Does the ppathid exist for this ? we use a memory cache...  In order to
197     * avoid the full loop, we consider that if a dir is allready in the
198     * brestore_pathhierarchy table, then there is no need to calculate all the
199     * hierarchy
200     */
201    while (path && *path)
202    {
203       if (!ppathid_cache.lookup(pathid))
204       {
205          Mmsg(mdb->cmd, 
206               "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
207               pathid);
208
209          QUERY_DB(jcr, mdb, mdb->cmd);
210          /* Do we have a result ? */
211          if (sql_num_rows(mdb) > 0) {
212             ppathid_cache.insert(pathid);
213             /* This dir was in the db ...
214              * It means we can leave, the tree has allready been built for
215              * this dir
216              */
217             goto bail_out;
218          } else {
219             /* search or create parent PathId in Path table */
220             mdb->path = bvfs_parent_dir(path);
221             mdb->pnl = strlen(mdb->path);
222             if (!db_create_path_record(jcr, mdb, &parent)) {
223                goto bail_out;
224             }
225             ppathid_cache.insert(pathid);
226             
227             Mmsg(mdb->cmd,
228                  "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
229                  "VALUES (%s,%lld)",
230                  pathid, (uint64_t) parent.PathId);
231             
232             INSERT_DB(jcr, mdb, mdb->cmd);
233
234             edit_uint64(parent.PathId, pathid);
235             path = mdb->path;   /* already done */
236          }
237       } else {
238          /* It's allready in the cache.  We can leave, no time to waste here,
239           * all the parent dirs have allready been done
240           */
241          goto bail_out;
242       }
243    }   
244
245 bail_out:
246    mdb->path = bkp;
247    mdb->fnl = 0;
248 }
249
250 /* 
251  * Internal function to update path_hierarchy cache with a shared pathid cache
252  */
253 static void update_path_hierarchy_cache(JCR *jcr,
254                                         B_DB *mdb,
255                                         pathid_cache &ppathid_cache,
256                                         JobId_t JobId)
257 {
258    Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
259
260    uint32_t num;
261    char jobid[50];
262    edit_uint64(JobId, jobid);
263  
264    db_lock(mdb);
265    db_start_transaction(jcr, mdb);
266
267    Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
268    
269    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
270       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
271       goto bail_out;
272    }
273
274    /* Inserting path records for JobId */
275    Mmsg(mdb->cmd, "INSERT INTO brestore_pathvisibility (PathId, JobId) "
276                   "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
277         jobid);
278    QUERY_DB(jcr, mdb, mdb->cmd);
279
280
281    /* Now we have to do the directory recursion stuff to determine missing
282     * visibility We try to avoid recursion, to be as fast as possible We also
283     * only work on not allready hierarchised directories...
284     */
285    Mmsg(mdb->cmd, 
286      "SELECT brestore_pathvisibility.PathId, Path "
287        "FROM brestore_pathvisibility "
288             "JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) "
289             "LEFT JOIN brestore_pathhierarchy "
290          "ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) "
291       "WHERE brestore_pathvisibility.JobId = %s "
292         "AND brestore_pathhierarchy.PathId IS NULL "
293       "ORDER BY Path", jobid);
294    Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
295    QUERY_DB(jcr, mdb, mdb->cmd);
296
297    /* TODO: I need to reuse the DB connection without emptying the result 
298     * So, now i'm copying the result in memory to be able to query the
299     * catalog descriptor again.
300     */
301    num = sql_num_rows(mdb);
302    if (num > 0) {
303       char **result = (char **)malloc (num * 2 * sizeof(char *));
304       
305       SQL_ROW row;
306       int i=0;
307       while((row = sql_fetch_row(mdb))) {
308          result[i++] = bstrdup(row[0]);
309          result[i++] = bstrdup(row[1]);
310       }
311       
312       i=0;
313       while (num > 0) {
314          build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
315          free(result[i++]);
316          free(result[i++]);
317          num--;
318       }
319       free(result);
320    }
321    
322    Mmsg(mdb->cmd, 
323   "INSERT INTO brestore_pathvisibility (PathId, JobId)  "
324    "SELECT a.PathId,%s "
325    "FROM ( "
326      "SELECT DISTINCT h.PPathId AS PathId "
327        "FROM brestore_pathhierarchy AS h "
328        "JOIN  brestore_pathvisibility AS p ON (h.PathId=p.PathId) "
329       "WHERE p.JobId=%s) AS a LEFT JOIN "
330        "(SELECT PathId "
331           "FROM brestore_pathvisibility "
332          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
333    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
334
335    do {
336       QUERY_DB(jcr, mdb, mdb->cmd);
337    } while (sql_affected_rows(mdb) > 0);
338    
339    Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
340    INSERT_DB(jcr, mdb, mdb->cmd);
341
342 bail_out:
343    db_end_transaction(jcr, mdb);
344    db_unlock(mdb);
345 }
346
347 /* 
348  * Find an store the filename descriptor for empty directories Filename.Name=''
349  */
350 DBId_t Bvfs::get_dir_filenameid()
351 {
352    uint32_t id;
353    if (dir_filenameid) {
354       return dir_filenameid;
355    }
356    POOL_MEM q;
357    Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
358    db_sql_query(db, q.c_str(), db_int_handler, &id);
359    dir_filenameid = id;
360    return dir_filenameid;
361 }
362
363 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
364 {
365    uint32_t nb=0;
366    db_lock(mdb);
367    db_start_transaction(jcr, mdb);
368
369    Mmsg(mdb->cmd, "SELECT 1 from brestore_knownjobid LIMIT 1");
370    /* TODO: Add this code in the make_bacula_table script */
371    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
372       Dmsg0(dbglevel, "Creating cache table\n");
373       Mmsg(mdb->cmd,
374            "CREATE TABLE brestore_knownjobid ("
375            "JobId integer NOT NULL, "
376            "CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId))");
377       QUERY_DB(jcr, mdb, mdb->cmd);
378
379       Mmsg(mdb->cmd,
380            "CREATE TABLE brestore_pathhierarchy ( "
381            "PathId integer NOT NULL, "
382            "PPathId integer NOT NULL, "
383            "CONSTRAINT brestore_pathhierarchy_pkey "
384            "PRIMARY KEY (PathId))");
385       QUERY_DB(jcr, mdb, mdb->cmd); 
386
387       Mmsg(mdb->cmd,
388            "CREATE INDEX brestore_pathhierarchy_ppathid "
389            "ON brestore_pathhierarchy (PPathId)");
390       QUERY_DB(jcr, mdb, mdb->cmd);
391
392       Mmsg(mdb->cmd, 
393            "CREATE TABLE brestore_pathvisibility ("
394            "PathId integer NOT NULL, "
395            "JobId integer NOT NULL, "
396            "Size int8 DEFAULT 0, "
397            "Files int4 DEFAULT 0, "
398            "CONSTRAINT brestore_pathvisibility_pkey "
399            "PRIMARY KEY (JobId, PathId))");
400       QUERY_DB(jcr, mdb, mdb->cmd);
401
402       Mmsg(mdb->cmd, 
403            "CREATE INDEX brestore_pathvisibility_jobid "
404            "ON brestore_pathvisibility (JobId)");
405       QUERY_DB(jcr, mdb, mdb->cmd);
406
407    }
408
409    POOLMEM *jobids = get_pool_memory(PM_NAME);
410    *jobids = 0;
411
412    Mmsg(mdb->cmd, 
413  "SELECT JobId from Job "
414   "WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) "
415     "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
416   "ORDER BY JobId");
417
418    db_sql_query(mdb, mdb->cmd, db_get_int_handler, jobids);
419
420    bvfs_update_path_hierarchy_cache(jcr, mdb, jobids);
421
422    db_end_transaction(jcr, mdb);
423    db_start_transaction(jcr, mdb);
424    Dmsg0(dbglevel, "Cleaning pathvisibility\n");
425    Mmsg(mdb->cmd, 
426         "DELETE FROM brestore_pathvisibility "
427          "WHERE NOT EXISTS "
428         "(SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
429    nb = DELETE_DB(jcr, mdb, mdb->cmd);
430    Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
431
432    Dmsg0(dbglevel, "Cleaning knownjobid\n");
433    Mmsg(mdb->cmd,         
434         "DELETE FROM brestore_knownjobid "
435          "WHERE NOT EXISTS "
436         "(SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
437    nb = DELETE_DB(jcr, mdb, mdb->cmd);
438    Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
439
440    db_end_transaction(jcr, mdb);
441    free_pool_memory(jobids);
442 }
443
444 /*
445  * Update the bvfs cache for given jobids (1,2,3,4)
446  */
447 void
448 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
449 {
450    pathid_cache ppathid_cache;
451    JobId_t JobId;
452    char *p;
453
454    for (p=jobids; ; ) {
455       int stat = get_next_jobid_from_list(&p, &JobId);
456       if (stat < 0) {
457          return;
458       }
459       if (stat == 0) {
460          break;
461       }
462       Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t) JobId);
463       update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
464    }
465 }
466
467 /* 
468  * Update the bvfs cache for current jobids
469  */
470 void Bvfs::update_cache()
471 {
472    bvfs_update_path_hierarchy_cache(jcr, db, jobids);
473 }
474
475 /* Change the current directory, returns true if the path exists */
476 bool Bvfs::ch_dir(const char *path)
477 {
478    pm_strcpy(db->path, path);
479    db->pnl = strlen(db->path);
480    ch_dir(db_get_path_record(jcr, db)); 
481    return pwd_id != 0;
482 }
483
484 /* 
485  * Get all file versions for a specified client
486  */
487 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
488 {
489    Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
490          (uint64_t)fnid, client);
491    char ed1[50], ed2[50];
492    POOL_MEM q;
493    if (see_copies) {
494       Mmsg(q, " AND Job.Type IN ('C', 'B') ");
495    } else {
496       Mmsg(q, " AND Job.Type = 'B' ");
497    }
498
499    POOL_MEM query;
500
501    Mmsg(query,//0       1           2          3
502 "SELECT File.FileId, File.Md5, File.JobId, File.LStat, "
503 //         4                5          
504        "Media.VolumeName, Media.InChanger "
505 "FROM File, Job, Client, JobMedia, Media "
506 "WHERE File.FilenameId = %s "
507   "AND File.PathId=%s "
508   "AND File.JobId = Job.JobId "
509   "AND Job.ClientId = Client.ClientId "
510   "AND Job.JobId = JobMedia.JobId "
511   "AND File.FileIndex >= JobMedia.FirstIndex "
512   "AND File.FileIndex <= JobMedia.LastIndex "
513   "AND JobMedia.MediaId = Media.MediaId "
514   "AND Client.Name = '%s' "
515   "%s ORDER BY FileId LIMIT %d OFFSET %d"
516         ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
517         limit, offset);
518    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
519    db_sql_query(db, query.c_str(), list_entries, user_data);
520 }
521
522 DBId_t Bvfs::get_root()
523 {
524    *db->path = 0;
525    return db_get_path_record(jcr, db);
526 }
527
528 static int path_handler(void *ctx, int fields, char **row)
529 {
530    Bvfs *fs = (Bvfs *) ctx;
531    return fs->_handle_path(ctx, fields, row);
532 }
533
534 int Bvfs::_handle_path(void *ctx, int fields, char **row)
535 {
536    if (fields == BVFS_DIR_RECORD) {
537       /* can have the same path 2 times */
538       if (strcmp(row[BVFS_Name], prev_dir)) {
539          pm_strcpy(prev_dir, row[BVFS_Name]);
540          return list_entries(user_data, fields, row);
541       }
542    }
543    return 0;
544 }
545
546 /* 
547  * Retrieve . and .. information
548  */
549 void Bvfs::ls_special_dirs()
550 {
551    Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
552    char ed1[50], ed2[50];
553    if (!*jobids) {
554       return;
555    }
556    if (!dir_filenameid) {
557       get_dir_filenameid();
558    }
559
560    /* Will fetch directories  */
561    *prev_dir = 0;
562
563    POOL_MEM query;
564    Mmsg(query, 
565 "((SELECT PPathId AS PathId, '..' AS Path "
566     "FROM  brestore_pathhierarchy "
567    "WHERE  PathId = %s) "
568 "UNION "
569  "(SELECT %s AS PathId, '.' AS Path))",
570         edit_uint64(pwd_id, ed1), ed1);
571
572    POOL_MEM query2;
573    Mmsg(query2, 
574 "SELECT tmp.PathId, tmp.Path, JobId, LStat "
575   "FROM %s AS tmp  LEFT JOIN ( " // get attributes if any
576        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
577               "File1.LStat AS LStat FROM File AS File1 "
578        "WHERE File1.FilenameId = %s "
579        "AND File1.JobId IN (%s)) AS listfile1 "
580   "ON (tmp.PathId = listfile1.PathId) "
581   "ORDER BY tmp.Path, JobId DESC ",
582         query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
583
584    Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
585    db_sql_query(db, query2.c_str(), path_handler, this);
586 }
587
588 /* Returns true if we have dirs to read */
589 bool Bvfs::ls_dirs()
590 {
591    Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
592    char ed1[50], ed2[50];
593    if (!*jobids) {
594       return false;
595    }
596
597    POOL_MEM filter;
598    if (*pattern) {
599       Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
600    }
601
602    if (!dir_filenameid) {
603       get_dir_filenameid();
604    }
605
606    /* the sql query displays same directory multiple time, take the first one */
607    *prev_dir = 0;
608
609    /* Let's retrieve the list of the visible dirs in this dir ...
610     * First, I need the empty filenameid to locate efficiently
611     * the dirs in the file table
612     * my $dir_filenameid = $self->get_dir_filenameid();
613     */
614    /* Then we get all the dir entries from File ... */
615    POOL_MEM query;
616    Mmsg(query,
617 //        0     1      2      3
618 "SELECT PathId, Path, JobId, LStat FROM ( "
619     "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
620            "lower(Path1.Path) AS lpath, "
621            "listfile1.JobId AS JobId, listfile1.LStat AS LStat "
622     "FROM ( "
623       "SELECT DISTINCT brestore_pathhierarchy1.PathId AS PathId "
624       "FROM brestore_pathhierarchy AS brestore_pathhierarchy1 "
625       "JOIN Path AS Path2 "
626         "ON (brestore_pathhierarchy1.PathId = Path2.PathId) "
627       "JOIN brestore_pathvisibility AS brestore_pathvisibility1 "
628         "ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId) "
629       "WHERE brestore_pathhierarchy1.PPathId = %s "
630       "AND brestore_pathvisibility1.jobid IN (%s) "
631            "%s "
632      ") AS listpath1 "
633    "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
634
635    "LEFT JOIN ( " /* get attributes if any */
636        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
637               "File1.LStat AS LStat FROM File AS File1 "
638        "WHERE File1.FilenameId = %s "
639        "AND File1.JobId IN (%s)) AS listfile1 "
640        "ON (listpath1.PathId = listfile1.PathId) "
641     ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
642         edit_uint64(pwd_id, ed1),
643         jobids,
644         filter.c_str(),
645         edit_uint64(dir_filenameid, ed2),
646         jobids,
647         limit, offset);
648
649    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
650
651    db_lock(db);
652    db_sql_query(db, query.c_str(), path_handler, this);
653    nb_record = db->num_rows;
654    db_unlock(db);
655
656    return nb_record == limit;
657 }
658
659 /* Returns true if we have files to read */
660 bool Bvfs::ls_files()
661 {
662    Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
663    char ed1[50];
664    if (!*jobids) {
665       return false;
666    }
667
668    if (!pwd_id) {
669       ch_dir(get_root());
670    }
671
672    POOL_MEM filter;
673    if (*pattern) {
674       Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
675    }
676
677    POOL_MEM query;
678    Mmsg(query, // 0         1              2             3          4
679 "SELECT File.FilenameId, listfiles.Name, File.JobId, File.LStat, listfiles.id "
680 "FROM File, ( "
681        "SELECT Filename.Name as Name, max(File.FileId) as id "
682          "FROM File, Filename "
683         "WHERE File.FilenameId = Filename.FilenameId "
684           "AND Filename.Name != '' "
685           "AND File.PathId = %s "
686           "AND File.JobId IN (%s) "
687           "%s "
688         "GROUP BY Filename.Name "
689         "ORDER BY Filename.Name LIMIT %d OFFSET %d "
690      ") AS listfiles "
691 "WHERE File.FileId = listfiles.id",
692         edit_uint64(pwd_id, ed1),
693         jobids,
694         filter.c_str(),
695         limit,
696         offset);
697    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
698
699    db_lock(db);
700    db_sql_query(db, query.c_str(), list_entries, user_data);
701    nb_record = db->num_rows;
702    db_unlock(db);
703
704    return nb_record == limit;
705 }