]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/bvfs.c
tweak comments
[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    pattern = get_pool_memory(PM_NAME);
63    *prev_dir = *pattern = 0;
64    dir_filenameid = pwd_id = offset = 0;
65    see_copies = see_all_version = false;
66    limit = 1000;
67    attr = new_attr(jcr);
68    list_entries = result_handler;
69    user_data = this;
70 }
71
72 Bvfs::~Bvfs() {
73    free_pool_memory(pattern);
74    free_pool_memory(prev_dir);
75    free_attr(attr);
76    jcr->dec_use_count();
77 }
78
79 /* 
80  * TODO: Find a way to let the user choose how he wants to display
81  * files and directories
82  */
83
84
85 /* 
86  * Working Object to store PathId already seen (avoid
87  * database queries), equivalent to %cache_ppathid in perl
88  */
89
90 #define NITEMS 50000
91 class pathid_cache {
92 private:
93    hlink *nodes;
94    int nb_node;
95    int max_node;
96    htable *cache_ppathid;
97
98 public:
99    pathid_cache() {
100       hlink link;
101       cache_ppathid = (htable *)malloc(sizeof(htable));
102       cache_ppathid->init(&link, &link, NITEMS);
103       max_node = NITEMS;
104       nodes = (hlink *) malloc(max_node * sizeof (hlink));
105       nb_node = 0;
106    }
107
108    hlink *get_hlink() {
109       if (nb_node >= max_node) {
110          max_node *= 2;
111          nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
112       }
113       return nodes + nb_node++;
114    }
115
116    bool lookup(char *pathid) {
117       bool ret = cache_ppathid->lookup(pathid) != NULL;
118       return ret;
119    }
120    
121    void insert(char *pathid) {
122       hlink *h = get_hlink();
123       cache_ppathid->insert(pathid, h);
124    }
125
126    ~pathid_cache() {
127       cache_ppathid->destroy();
128       free(cache_ppathid);
129       free(nodes);
130    }
131 private:
132    pathid_cache(const pathid_cache &); /* prohibit pass by value */
133    pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
134 } ;
135
136 /* Return the parent_dir with the trailing /  (update the given string)
137  * TODO: see in the rest of bacula if we don't have already this function
138  * dir=/tmp/toto/
139  * dir=/tmp/
140  * dir=/
141  * dir=
142  */
143 char *bvfs_parent_dir(char *path)
144 {
145    char *p = path;
146    int len = strlen(path) - 1;
147
148    if (len >= 0 && path[len] == '/') {      /* if directory, skip last / */
149       path[len] = '\0';
150    }
151
152    if (len > 0) {
153       p += len;
154       while (p > path && !IsPathSeparator(*p)) {
155          p--;
156       }
157       p[1] = '\0';
158    }
159    return path;
160 }
161
162 /* Return the basename of the with the trailing /
163  * TODO: see in the rest of bacula if we don't have
164  * this function already
165  */
166 char *bvfs_basename_dir(char *path)
167 {
168    char *p = path;
169    int len = strlen(path) - 1;
170
171    if (path[len] == '/') {      /* if directory, skip last / */
172       len -= 1;
173    }
174
175    if (len > 0) {
176       p += len;
177       while (p > path && !IsPathSeparator(*p)) {
178          p--;
179       }
180       if (*p == '/') {
181          p++;                  /* skip first / */
182       }
183    } 
184    return p;
185 }
186
187 static void build_path_hierarchy(JCR *jcr, B_DB *mdb, 
188                                  pathid_cache &ppathid_cache, 
189                                  char *org_pathid, char *path)
190 {
191    Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
192    char pathid[50];
193    ATTR_DBR parent;
194    char *bkp = mdb->path;
195    strncpy(pathid, org_pathid, sizeof(pathid));
196
197    /* Does the ppathid exist for this ? we use a memory cache...  In order to
198     * avoid the full loop, we consider that if a dir is allready in the
199     * brestore_pathhierarchy table, then there is no need to calculate all the
200     * hierarchy
201     */
202    while (path && *path)
203    {
204       if (!ppathid_cache.lookup(pathid))
205       {
206          Mmsg(mdb->cmd, 
207               "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
208               pathid);
209
210          QUERY_DB(jcr, mdb, mdb->cmd);
211          /* Do we have a result ? */
212          if (sql_num_rows(mdb) > 0) {
213             ppathid_cache.insert(pathid);
214             /* This dir was in the db ...
215              * It means we can leave, the tree has allready been built for
216              * this dir
217              */
218             goto bail_out;
219          } else {
220             /* search or create parent PathId in Path table */
221             mdb->path = bvfs_parent_dir(path);
222             mdb->pnl = strlen(mdb->path);
223             if (!db_create_path_record(jcr, mdb, &parent)) {
224                goto bail_out;
225             }
226             ppathid_cache.insert(pathid);
227             
228             Mmsg(mdb->cmd,
229                  "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
230                  "VALUES (%s,%lld)",
231                  pathid, (uint64_t) parent.PathId);
232             
233             INSERT_DB(jcr, mdb, mdb->cmd);
234
235             edit_uint64(parent.PathId, pathid);
236             path = mdb->path;   /* already done */
237          }
238       } else {
239          /* It's already in the cache.  We can leave, no time to waste here,
240           * all the parent dirs have allready been done
241           */
242          goto bail_out;
243       }
244    }   
245
246 bail_out:
247    mdb->path = bkp;
248    mdb->fnl = 0;
249 }
250
251 /* 
252  * Internal function to update path_hierarchy cache with a shared pathid cache
253  */
254 static void update_path_hierarchy_cache(JCR *jcr,
255                                         B_DB *mdb,
256                                         pathid_cache &ppathid_cache,
257                                         JobId_t JobId)
258 {
259    Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
260
261    uint32_t num;
262    char jobid[50];
263    edit_uint64(JobId, jobid);
264  
265    db_lock(mdb);
266    db_start_transaction(jcr, mdb);
267
268    Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
269    
270    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
271       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
272       goto bail_out;
273    }
274
275    /* Inserting path records for JobId */
276    Mmsg(mdb->cmd, "INSERT INTO brestore_pathvisibility (PathId, JobId) "
277                   "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
278         jobid);
279    QUERY_DB(jcr, mdb, mdb->cmd);
280
281
282    /* Now we have to do the directory recursion stuff to determine missing
283     * visibility We try to avoid recursion, to be as fast as possible We also
284     * only work on not allready hierarchised directories...
285     */
286    Mmsg(mdb->cmd, 
287      "SELECT brestore_pathvisibility.PathId, Path "
288        "FROM brestore_pathvisibility "
289             "JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) "
290             "LEFT JOIN brestore_pathhierarchy "
291          "ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) "
292       "WHERE brestore_pathvisibility.JobId = %s "
293         "AND brestore_pathhierarchy.PathId IS NULL "
294       "ORDER BY Path", jobid);
295    Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
296    QUERY_DB(jcr, mdb, mdb->cmd);
297
298    /* TODO: I need to reuse the DB connection without emptying the result 
299     * So, now i'm copying the result in memory to be able to query the
300     * catalog descriptor again.
301     */
302    num = sql_num_rows(mdb);
303    if (num > 0) {
304       char **result = (char **)malloc (num * 2 * sizeof(char *));
305       
306       SQL_ROW row;
307       int i=0;
308       while((row = sql_fetch_row(mdb))) {
309          result[i++] = bstrdup(row[0]);
310          result[i++] = bstrdup(row[1]);
311       }
312       
313       i=0;
314       while (num > 0) {
315          build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
316          free(result[i++]);
317          free(result[i++]);
318          num--;
319       }
320       free(result);
321    }
322    
323    Mmsg(mdb->cmd, 
324   "INSERT INTO brestore_pathvisibility (PathId, JobId)  "
325    "SELECT a.PathId,%s "
326    "FROM ( "
327      "SELECT DISTINCT h.PPathId AS PathId "
328        "FROM brestore_pathhierarchy AS h "
329        "JOIN  brestore_pathvisibility AS p ON (h.PathId=p.PathId) "
330       "WHERE p.JobId=%s) AS a LEFT JOIN "
331        "(SELECT PathId "
332           "FROM brestore_pathvisibility "
333          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
334    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
335
336    do {
337       QUERY_DB(jcr, mdb, mdb->cmd);
338    } while (sql_affected_rows(mdb) > 0);
339    
340    Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
341    INSERT_DB(jcr, mdb, mdb->cmd);
342
343 bail_out:
344    db_end_transaction(jcr, mdb);
345    db_unlock(mdb);
346 }
347
348 /* 
349  * Find an store the filename descriptor for empty directories Filename.Name=''
350  */
351 DBId_t Bvfs::get_dir_filenameid()
352 {
353    uint32_t id;
354    if (dir_filenameid) {
355       return dir_filenameid;
356    }
357    POOL_MEM q;
358    Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
359    db_sql_query(db, q.c_str(), db_int_handler, &id);
360    dir_filenameid = id;
361    return dir_filenameid;
362 }
363
364 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
365 {
366    uint32_t nb=0;
367    db_list_ctx jobids;
368
369    db_lock(mdb);
370    db_start_transaction(jcr, mdb);
371
372    Mmsg(mdb->cmd, "SELECT 1 from brestore_knownjobid LIMIT 1");
373    /* TODO: Add this code in the make_bacula_table script */
374    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
375       Dmsg0(dbglevel, "Creating cache table\n");
376       Mmsg(mdb->cmd,
377            "CREATE TABLE brestore_knownjobid ("
378            "JobId integer NOT NULL, "
379            "CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId))");
380       QUERY_DB(jcr, mdb, mdb->cmd);
381
382       Mmsg(mdb->cmd,
383            "CREATE TABLE brestore_pathhierarchy ( "
384            "PathId integer NOT NULL, "
385            "PPathId integer NOT NULL, "
386            "CONSTRAINT brestore_pathhierarchy_pkey "
387            "PRIMARY KEY (PathId))");
388       QUERY_DB(jcr, mdb, mdb->cmd); 
389
390       Mmsg(mdb->cmd,
391            "CREATE INDEX brestore_pathhierarchy_ppathid "
392            "ON brestore_pathhierarchy (PPathId)");
393       QUERY_DB(jcr, mdb, mdb->cmd);
394
395       Mmsg(mdb->cmd, 
396            "CREATE TABLE brestore_pathvisibility ("
397            "PathId integer NOT NULL, "
398            "JobId integer NOT NULL, "
399            "Size int8 DEFAULT 0, "
400            "Files int4 DEFAULT 0, "
401            "CONSTRAINT brestore_pathvisibility_pkey "
402            "PRIMARY KEY (JobId, PathId))");
403       QUERY_DB(jcr, mdb, mdb->cmd);
404
405       Mmsg(mdb->cmd, 
406            "CREATE INDEX brestore_pathvisibility_jobid "
407            "ON brestore_pathvisibility (JobId)");
408       QUERY_DB(jcr, mdb, mdb->cmd);
409
410    }
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_list_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 }
442
443 /*
444  * Update the bvfs cache for given jobids (1,2,3,4)
445  */
446 void
447 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, db_list_ctx *jobids)
448 {
449    pathid_cache ppathid_cache;
450    JobId_t JobId;
451    char *p;
452
453    for (p=jobids->list; ; ) {
454       int stat = get_next_jobid_from_list(&p, &JobId);
455       Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)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.count == 0) {
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.list);
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.count == 0) {
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.list,
644         filter.c_str(),
645         edit_uint64(dir_filenameid, ed2),
646         jobids.list,
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.count == 0) {
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.list,
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 }