]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/cats/bvfs.c
bat: Use BVFS on bRestore view
[bacula/bacula] / bacula / src / cats / bvfs.c
index 57c704e035dfa0303f362e42145539fb32f60ef4..95d77d905397993607c1a0d8c8a0e5fbd054dbf1 100644 (file)
@@ -6,7 +6,7 @@
    The main author of Bacula is Kern Sibbald, with contributions from
    many others, a complete list can be found in the file AUTHORS.
    This program is Free Software; you can redistribute it and/or
-   modify it under the terms of version two of the GNU General Public
+   modify it under the terms of version three of the GNU Affero General Public
    License as published by the Free Software Foundation, which is 
    listed in the file LICENSE.
 
@@ -15,7 +15,7 @@
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    General Public License for more details.
 
-   You should have received a copy of the GNU General Public License
+   You should have received a copy of the GNU Affero General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
@@ -59,8 +59,9 @@ Bvfs::Bvfs(JCR *j, B_DB *mdb) {
    jcr->inc_use_count();
    db = mdb;                 /* need to inc ref count */
    jobids = get_pool_memory(PM_NAME);
+   prev_dir = get_pool_memory(PM_NAME);
    pattern = get_pool_memory(PM_NAME);
-   *pattern = *jobids = 0;
+   *jobids = *prev_dir = *pattern = 0;
    dir_filenameid = pwd_id = offset = 0;
    see_copies = see_all_version = false;
    limit = 1000;
@@ -72,6 +73,7 @@ Bvfs::Bvfs(JCR *j, B_DB *mdb) {
 Bvfs::~Bvfs() {
    free_pool_memory(jobids);
    free_pool_memory(pattern);
+   free_pool_memory(prev_dir);
    free_attr(attr);
    jcr->dec_use_count();
 }
@@ -93,6 +95,9 @@ private:
    hlink *nodes;
    int nb_node;
    int max_node;
+
+   alist *table_node;
+
    htable *cache_ppathid;
 
 public:
@@ -103,14 +108,17 @@ public:
       max_node = NITEMS;
       nodes = (hlink *) malloc(max_node * sizeof (hlink));
       nb_node = 0;
+      table_node = New(alist(5, owned_by_alist));
+      table_node->append(nodes);
    }
 
    hlink *get_hlink() {
-      if (nb_node >= max_node) {
-         max_node *= 2;
-         nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
+      if (++nb_node >= max_node) {
+         nb_node = 0;
+         nodes = (hlink *)malloc(max_node * sizeof(hlink));
+         table_node->append(nodes);
       }
-      return nodes + nb_node++;
+      return nodes + nb_node;
    }
 
    bool lookup(char *pathid) {
@@ -126,8 +134,11 @@ public:
    ~pathid_cache() {
       cache_ppathid->destroy();
       free(cache_ppathid);
-      free(nodes);
+      delete table_node;
    }
+private:
+   pathid_cache(const pathid_cache &); /* prohibit pass by value */
+   pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
 } ;
 
 /* Return the parent_dir with the trailing /  (update the given string)
@@ -174,7 +185,9 @@ char *bvfs_basename_dir(char *path)
       while (p > path && !IsPathSeparator(*p)) {
          p--;
       }
-      p = p+1;                  /* skip first / */
+      if (*p == '/') {
+         p++;                  /* skip first / */
+      }
    } 
    return p;
 }
@@ -191,7 +204,7 @@ static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
 
    /* Does the ppathid exist for this ? we use a memory cache...  In order to
     * avoid the full loop, we consider that if a dir is allready in the
-    * brestore_pathhierarchy table, then there is no need to calculate all the
+    * PathHierarchy table, then there is no need to calculate all the
     * hierarchy
     */
    while (path && *path)
@@ -199,7 +212,7 @@ static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
       if (!ppathid_cache.lookup(pathid))
       {
          Mmsg(mdb->cmd, 
-              "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
+              "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
               pathid);
 
          QUERY_DB(jcr, mdb, mdb->cmd);
@@ -221,7 +234,7 @@ static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
             ppathid_cache.insert(pathid);
             
             Mmsg(mdb->cmd,
-                 "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
+                 "INSERT INTO PathHierarchy (PathId, PPathId) "
                  "VALUES (%s,%lld)",
                  pathid, (uint64_t) parent.PathId);
             
@@ -231,7 +244,7 @@ static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
             path = mdb->path;   /* already done */
          }
       } else {
-         /* It's allready in the cache.  We can leave, no time to waste here,
+         /* It's already in the cache.  We can leave, no time to waste here,
           * all the parent dirs have allready been done
           */
          goto bail_out;
@@ -260,7 +273,7 @@ static void update_path_hierarchy_cache(JCR *jcr,
    db_lock(mdb);
    db_start_transaction(jcr, mdb);
 
-   Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
+   Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
    
    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
@@ -268,7 +281,7 @@ static void update_path_hierarchy_cache(JCR *jcr,
    }
 
    /* Inserting path records for JobId */
-   Mmsg(mdb->cmd, "INSERT INTO brestore_pathvisibility (PathId, JobId) "
+   Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
                   "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
         jobid);
    QUERY_DB(jcr, mdb, mdb->cmd);
@@ -279,13 +292,13 @@ static void update_path_hierarchy_cache(JCR *jcr,
     * only work on not allready hierarchised directories...
     */
    Mmsg(mdb->cmd, 
-     "SELECT brestore_pathvisibility.PathId, Path "
-       "FROM brestore_pathvisibility "
-            "JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) "
-            "LEFT JOIN brestore_pathhierarchy "
-         "ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) "
-      "WHERE brestore_pathvisibility.JobId = %s "
-        "AND brestore_pathhierarchy.PathId IS NULL "
+     "SELECT PathVisibility.PathId, Path "
+       "FROM PathVisibility "
+            "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
+            "LEFT JOIN PathHierarchy "
+         "ON (PathVisibility.PathId = PathHierarchy.PathId) "
+      "WHERE PathVisibility.JobId = %s "
+        "AND PathHierarchy.PathId IS NULL "
       "ORDER BY Path", jobid);
    Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
    QUERY_DB(jcr, mdb, mdb->cmd);
@@ -316,15 +329,15 @@ static void update_path_hierarchy_cache(JCR *jcr,
    }
    
    Mmsg(mdb->cmd, 
-  "INSERT INTO brestore_pathvisibility (PathId, JobId)  "
+  "INSERT INTO PathVisibility (PathId, JobId)  "
    "SELECT a.PathId,%s "
    "FROM ( "
      "SELECT DISTINCT h.PPathId AS PathId "
-       "FROM brestore_pathhierarchy AS h "
-       "JOIN  brestore_pathvisibility AS p ON (h.PathId=p.PathId) "
+       "FROM PathHierarchy AS h "
+       "JOIN  PathVisibility AS p ON (h.PathId=p.PathId) "
       "WHERE p.JobId=%s) AS a LEFT JOIN "
        "(SELECT PathId "
-          "FROM brestore_pathvisibility "
+          "FROM PathVisibility "
          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
 
@@ -332,8 +345,8 @@ static void update_path_hierarchy_cache(JCR *jcr,
       QUERY_DB(jcr, mdb, mdb->cmd);
    } while (sql_affected_rows(mdb) > 0);
    
-   Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
-   INSERT_DB(jcr, mdb, mdb->cmd);
+   Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
+   UPDATE_DB(jcr, mdb, mdb->cmd);
 
 bail_out:
    db_end_transaction(jcr, mdb);
@@ -359,81 +372,72 @@ DBId_t Bvfs::get_dir_filenameid()
 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
 {
    uint32_t nb=0;
+   db_list_ctx jobids_list;
+
    db_lock(mdb);
    db_start_transaction(jcr, mdb);
 
-   Mmsg(mdb->cmd, "SELECT 1 from brestore_knownjobid LIMIT 1");
-   /* TODO: Add this code in the make_bacula_table script */
+#ifdef xxx
+   /* TODO: Remove this code when updating make_bacula_table script */
+   Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
       Dmsg0(dbglevel, "Creating cache table\n");
-      Mmsg(mdb->cmd,
-           "CREATE TABLE brestore_knownjobid ("
-           "JobId integer NOT NULL, "
-           "CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId))");
+      Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
       QUERY_DB(jcr, mdb, mdb->cmd);
 
       Mmsg(mdb->cmd,
-           "CREATE TABLE brestore_pathhierarchy ( "
+           "CREATE TABLE PathHierarchy ( "
            "PathId integer NOT NULL, "
            "PPathId integer NOT NULL, "
-           "CONSTRAINT brestore_pathhierarchy_pkey "
+           "CONSTRAINT pathhierarchy_pkey "
            "PRIMARY KEY (PathId))");
       QUERY_DB(jcr, mdb, mdb->cmd); 
 
       Mmsg(mdb->cmd,
-           "CREATE INDEX brestore_pathhierarchy_ppathid "
-           "ON brestore_pathhierarchy (PPathId)");
+           "CREATE INDEX pathhierarchy_ppathid "
+           "ON PathHierarchy (PPathId)");
       QUERY_DB(jcr, mdb, mdb->cmd);
 
       Mmsg(mdb->cmd, 
-           "CREATE TABLE brestore_pathvisibility ("
+           "CREATE TABLE PathVisibility ("
            "PathId integer NOT NULL, "
            "JobId integer NOT NULL, "
            "Size int8 DEFAULT 0, "
            "Files int4 DEFAULT 0, "
-           "CONSTRAINT brestore_pathvisibility_pkey "
+           "CONSTRAINT pathvisibility_pkey "
            "PRIMARY KEY (JobId, PathId))");
       QUERY_DB(jcr, mdb, mdb->cmd);
 
       Mmsg(mdb->cmd, 
-           "CREATE INDEX brestore_pathvisibility_jobid "
-           "ON brestore_pathvisibility (JobId)");
+           "CREATE INDEX pathvisibility_jobid "
+           "ON PathVisibility (JobId)");
       QUERY_DB(jcr, mdb, mdb->cmd);
 
    }
-
-   POOLMEM *jobids = get_pool_memory(PM_NAME);
-   *jobids = 0;
+#endif
 
    Mmsg(mdb->cmd, 
  "SELECT JobId from Job "
-  "WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) "
+  "WHERE HasCache = 0 "
     "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
   "ORDER BY JobId");
 
-   db_sql_query(mdb, mdb->cmd, db_get_int_handler, jobids);
+   db_sql_query(mdb, mdb->cmd, db_list_handler, &jobids_list);
 
-   bvfs_update_path_hierarchy_cache(jcr, mdb, jobids);
+   bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
 
    db_end_transaction(jcr, mdb);
    db_start_transaction(jcr, mdb);
    Dmsg0(dbglevel, "Cleaning pathvisibility\n");
    Mmsg(mdb->cmd, 
-        "DELETE FROM brestore_pathvisibility "
-         "WHERE NOT EXISTS "
-        "(SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
-   nb = DELETE_DB(jcr, mdb, mdb->cmd);
-   Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
-
-   Dmsg0(dbglevel, "Cleaning knownjobid\n");
-   Mmsg(mdb->cmd,         
-        "DELETE FROM brestore_knownjobid "
+        "DELETE FROM PathVisibility "
          "WHERE NOT EXISTS "
-        "(SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
+        "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
    nb = DELETE_DB(jcr, mdb, mdb->cmd);
    Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
 
    db_end_transaction(jcr, mdb);
+   db_unlock(mdb);
 }
 
 /*
@@ -454,7 +458,7 @@ bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
       if (stat == 0) {
          break;
       }
-      Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t) JobId);
+      Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
       update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
    }
 }
@@ -468,18 +472,18 @@ void Bvfs::update_cache()
 }
 
 /* Change the current directory, returns true if the path exists */
-bool Bvfs::ch_dir(char *path)
+bool Bvfs::ch_dir(const char *path)
 {
    pm_strcpy(db->path, path);
    db->pnl = strlen(db->path);
-   pwd_id = db_get_path_record(jcr, db); 
+   ch_dir(db_get_path_record(jcr, db)); 
    return pwd_id != 0;
 }
 
 /* 
  * Get all file versions for a specified client
  */
-void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, char *client)
+void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
 {
    Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
          (uint64_t)fnid, client);
@@ -493,9 +497,10 @@ void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, char *client)
 
    POOL_MEM query;
 
-   Mmsg(query, 
-"SELECT File.JobId, File.FileId, File.LStat, "
-       "File.Md5, Media.VolumeName, Media.InChanger "
+   Mmsg(query,//    1           2          3       4
+"SELECT 'V', File.FileId, File.Md5, File.JobId, File.LStat, "
+//         5                6
+       "Media.VolumeName, Media.InChanger "
 "FROM File, Job, Client, JobMedia, Media "
 "WHERE File.FilenameId = %s "
   "AND File.PathId=%s "
@@ -509,7 +514,7 @@ void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, char *client)
   "%s ORDER BY FileId LIMIT %d OFFSET %d"
         ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
         limit, offset);
-
+   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
    db_sql_query(db, query.c_str(), list_entries, user_data);
 }
 
@@ -519,6 +524,24 @@ DBId_t Bvfs::get_root()
    return db_get_path_record(jcr, db);
 }
 
+static int path_handler(void *ctx, int fields, char **row)
+{
+   Bvfs *fs = (Bvfs *) ctx;
+   return fs->_handle_path(ctx, fields, row);
+}
+
+int Bvfs::_handle_path(void *ctx, int fields, char **row)
+{
+   if (bvfs_is_dir(row)) {
+      /* can have the same path 2 times */
+      if (strcmp(row[BVFS_Name], prev_dir)) {
+         pm_strcpy(prev_dir, row[BVFS_Name]);
+         return list_entries(user_data, fields, row);
+      }
+   }
+   return 0;
+}
+
 /* 
  * Retrieve . and .. information
  */
@@ -526,44 +549,48 @@ void Bvfs::ls_special_dirs()
 {
    Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
    char ed1[50], ed2[50];
-   if (!*jobids) {
+   if (*jobids == 0) {
       return;
    }
    if (!dir_filenameid) {
       get_dir_filenameid();
    }
 
+   /* Will fetch directories  */
+   *prev_dir = 0;
+
    POOL_MEM query;
    Mmsg(query, 
 "((SELECT PPathId AS PathId, '..' AS Path "
-    "FROM  brestore_pathhierarchy "
+    "FROM  PathHierarchy "
    "WHERE  PathId = %s) "
 "UNION "
  "(SELECT %s AS PathId, '.' AS Path))",
         edit_uint64(pwd_id, ed1), ed1);
 
    POOL_MEM query2;
-   Mmsg(query2, 
-"SELECT tmp.PathId, tmp.Path, JobId, LStat "
+   Mmsg(query2,// 1      2     3        4     5       6
+"SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
   "FROM %s AS tmp  LEFT JOIN ( " // get attributes if any
        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
-              "File1.LStat AS LStat FROM File AS File1 "
+              "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
        "WHERE File1.FilenameId = %s "
        "AND File1.JobId IN (%s)) AS listfile1 "
   "ON (tmp.PathId = listfile1.PathId) "
   "ORDER BY tmp.Path, JobId DESC ",
         query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
 
-   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
-   db_sql_query(db, query2.c_str(), list_entries, user_data);
+   Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
+   db_sql_query(db, query2.c_str(), path_handler, this);
 }
 
-void Bvfs::ls_dirs()
+/* Returns true if we have dirs to read */
+bool Bvfs::ls_dirs()
 {
    Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
    char ed1[50], ed2[50];
-   if (!*jobids) {
-      return;
+   if (*jobids == 0) {
+      return false;
    }
 
    POOL_MEM filter;
@@ -575,6 +602,9 @@ void Bvfs::ls_dirs()
       get_dir_filenameid();
    }
 
+   /* the sql query displays same directory multiple time, take the first one */
+   *prev_dir = 0;
+
    /* Let's retrieve the list of the visible dirs in this dir ...
     * First, I need the empty filenameid to locate efficiently
     * the dirs in the file table
@@ -583,27 +613,28 @@ void Bvfs::ls_dirs()
    /* Then we get all the dir entries from File ... */
    POOL_MEM query;
    Mmsg(query,
-//        0     1      2      3
-"SELECT PathId, Path, JobId, LStat FROM ( "
+//       0     1     2   3      4     5       6
+"SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
     "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
            "lower(Path1.Path) AS lpath, "
-           "listfile1.JobId AS JobId, listfile1.LStat AS LStat "
+           "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
+           "listfile1.FileId AS FileId "
     "FROM ( "
-      "SELECT DISTINCT brestore_pathhierarchy1.PathId AS PathId "
-      "FROM brestore_pathhierarchy AS brestore_pathhierarchy1 "
+      "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
+      "FROM PathHierarchy AS PathHierarchy1 "
       "JOIN Path AS Path2 "
-        "ON (brestore_pathhierarchy1.PathId = Path2.PathId) "
-      "JOIN brestore_pathvisibility AS brestore_pathvisibility1 "
-        "ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId) "
-      "WHERE brestore_pathhierarchy1.PPathId = %s "
-      "AND brestore_pathvisibility1.jobid IN (%s) "
+        "ON (PathHierarchy1.PathId = Path2.PathId) "
+      "JOIN PathVisibility AS PathVisibility1 "
+        "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
+      "WHERE PathHierarchy1.PPathId = %s "
+      "AND PathVisibility1.jobid IN (%s) "
            "%s "
      ") AS listpath1 "
    "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
 
    "LEFT JOIN ( " /* get attributes if any */
        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
-              "File1.LStat AS LStat FROM File AS File1 "
+              "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
        "WHERE File1.FilenameId = %s "
        "AND File1.JobId IN (%s)) AS listfile1 "
        "ON (listpath1.PathId = listfile1.PathId) "
@@ -616,15 +647,22 @@ void Bvfs::ls_dirs()
         limit, offset);
 
    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
-   db_sql_query(db, query.c_str(), list_entries, user_data);
+
+   db_lock(db);
+   db_sql_query(db, query.c_str(), path_handler, this);
+   nb_record = db->num_rows;
+   db_unlock(db);
+
+   return nb_record == limit;
 }
 
-void Bvfs::ls_files()
+/* Returns true if we have files to read */
+bool Bvfs::ls_files()
 {
    Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
    char ed1[50];
-   if (!*jobids) {
-      return ;
+   if (*jobids == 0) {
+      return false;
    }
 
    if (!pwd_id) {
@@ -635,14 +673,15 @@ void Bvfs::ls_files()
    if (*pattern) {
       Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
    }
-
+   /* TODO: Use JobTDate instead of FileId to determine the latest version */
    POOL_MEM query;
-   Mmsg(query, // 0         1              2             3          4
-"SELECT File.FilenameId, listfiles.Name, File.JobId, File.LStat, listfiles.id "
+   Mmsg(query, //    1              2             3          4
+"SELECT 'F', File.PathId, File.FilenameId, listfiles.Name, File.JobId, "
+        "File.LStat, listfiles.id "
 "FROM File, ( "
        "SELECT Filename.Name as Name, max(File.FileId) as id "
-        "FROM File, Filename "
-       "WHERE File.FilenameId = Filename.FilenameId "
+         "FROM File, Filename "
+        "WHERE File.FilenameId = Filename.FilenameId "
           "AND Filename.Name != '' "
           "AND File.PathId = %s "
           "AND File.JobId IN (%s) "
@@ -657,5 +696,11 @@ void Bvfs::ls_files()
         limit,
         offset);
    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+
+   db_lock(db);
    db_sql_query(db, query.c_str(), list_entries, user_data);
+   nb_record = db->num_rows;
+   db_unlock(db);
+
+   return nb_record == limit;
 }