]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/cats/bvfs.c
Fix #5346 .bvfs_lsfiles and .bvfs_restore to handle deleted files
[bacula/bacula] / bacula / src / cats / bvfs.c
index c3b8bbd8a28ba94d589990bebc64b3f27931a1c7..27f76d54b7c0c764c0a54de47faec267d2a98847 100644 (file)
    Switzerland, email:ftf@fsfeurope.org.
 */
 
-#define __SQL_C                       /* indicate that this is sql.c */
-
 #include "bacula.h"
-#include "cats/cats.h"
+
+#if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI
+
+#include "cats.h"
+#include "bdb_priv.h"
+#include "sql_glue.h"
 #include "lib/htable.h"
 #include "bvfs.h"
 
@@ -68,16 +71,59 @@ Bvfs::Bvfs(JCR *j, B_DB *mdb) {
    attr = new_attr(jcr);
    list_entries = result_handler;
    user_data = this;
+   username = NULL;
 }
 
 Bvfs::~Bvfs() {
    free_pool_memory(jobids);
    free_pool_memory(pattern);
    free_pool_memory(prev_dir);
+   if (username) {
+      free(username);
+   }
    free_attr(attr);
    jcr->dec_use_count();
 }
 
+void Bvfs::filter_jobid()
+{
+   if (!username) {
+      return;
+   }
+
+   /* Query used by Bweb to filter clients, activated when using
+    * set_username() 
+    */
+   POOL_MEM query;
+   Mmsg(query,
+      "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) "
+        "JOIN (SELECT ClientId FROM client_group_member "
+        "JOIN client_group USING (client_group_id) "
+        "JOIN bweb_client_group_acl USING (client_group_id) "
+        "JOIN bweb_user USING (userid) "
+       "WHERE bweb_user.username = '%s' "
+      ") AS filter USING (ClientId) "
+        " WHERE JobId IN (%s)", 
+        username, jobids);
+
+   db_list_ctx ctx;
+   Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+   db_sql_query(db, query.c_str(), db_list_handler, &ctx);
+   pm_strcpy(jobids, ctx.list);
+}
+
+void Bvfs::set_jobid(JobId_t id)
+{
+   Mmsg(jobids, "%lld", (uint64_t)id);
+   filter_jobid();
+}
+
+void Bvfs::set_jobids(char *ids)
+{
+   pm_strcpy(jobids, ids);
+   filter_jobid();
+}
+
 /* 
  * TODO: Find a way to let the user choose how he wants to display
  * files and directories
@@ -153,6 +199,15 @@ char *bvfs_parent_dir(char *path)
    char *p = path;
    int len = strlen(path) - 1;
 
+   /* windows directory / */
+   if (len == 2 && B_ISALPHA(path[0]) 
+                && path[1] == ':' 
+                && path[2] == '/')
+   {
+      len = 0;
+      path[0] = '\0';
+   }
+
    if (len >= 0 && path[len] == '/') {      /* if directory, skip last / */
       path[len] = '\0';
    }
@@ -215,7 +270,10 @@ static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
               "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
               pathid);
 
-         QUERY_DB(jcr, mdb, mdb->cmd);
+         if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+            goto bail_out;      /* Query failed, just leave */
+         }
+
          /* Do we have a result ? */
          if (sql_num_rows(mdb) > 0) {
             ppathid_cache.insert(pathid);
@@ -238,7 +296,9 @@ static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
                  "VALUES (%s,%lld)",
                  pathid, (uint64_t) parent.PathId);
             
-            INSERT_DB(jcr, mdb, mdb->cmd);
+            if (!INSERT_DB(jcr, mdb, mdb->cmd)) {
+               goto bail_out;   /* Can't insert the record, just leave */
+            }
 
             edit_uint64(parent.PathId, pathid);
             path = mdb->path;   /* already done */
@@ -258,14 +318,16 @@ bail_out:
 
 /* 
  * Internal function to update path_hierarchy cache with a shared pathid cache
+ * return Error 0
+ *        OK    1
  */
-static void update_path_hierarchy_cache(JCR *jcr,
+static int update_path_hierarchy_cache(JCR *jcr,
                                         B_DB *mdb,
                                         pathid_cache &ppathid_cache,
                                         JobId_t JobId)
 {
    Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
-
+   int ret=0;
    uint32_t num;
    char jobid[50];
    edit_uint64(JobId, jobid);
@@ -277,6 +339,7 @@ static void update_path_hierarchy_cache(JCR *jcr,
    
    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
+      ret = 1;
       goto bail_out;
    }
 
@@ -285,10 +348,15 @@ static void update_path_hierarchy_cache(JCR *jcr,
                    "SELECT DISTINCT PathId, JobId "
                      "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
                            "UNION "
-                           "SELECT PathId, BaseFiles.JobId FROM BaseFiles JOIN File AS F USING (FileId) "
+                           "SELECT PathId, BaseFiles.JobId "
+                             "FROM BaseFiles JOIN File AS F USING (FileId) "
                             "WHERE BaseFiles.JobId = %s) AS B",
         jobid, jobid);
-   QUERY_DB(jcr, mdb, mdb->cmd);
+
+   if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+      Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId );
+      goto bail_out;
+   }
 
    /* Now we have to do the directory recursion stuff to determine missing
     * visibility We try to avoid recursion, to be as fast as possible We also
@@ -304,7 +372,11 @@ static void update_path_hierarchy_cache(JCR *jcr,
         "AND PathHierarchy.PathId IS NULL "
       "ORDER BY Path", jobid);
    Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
-   QUERY_DB(jcr, mdb, mdb->cmd);
+
+   if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+      Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId );
+      goto bail_out;
+   }
 
    /* TODO: I need to reuse the DB connection without emptying the result 
     * So, now i'm copying the result in memory to be able to query the
@@ -331,7 +403,17 @@ static void update_path_hierarchy_cache(JCR *jcr,
       free(result);
    }
 
-   Mmsg(mdb->cmd, 
+   if (mdb->db_get_type_index() == SQL_TYPE_SQLITE3) {
+      Mmsg(mdb->cmd, 
+ "INSERT INTO PathVisibility (PathId, JobId) "
+   "SELECT DISTINCT h.PPathId AS PathId, %s "
+     "FROM PathHierarchy AS h "
+    "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
+      "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
+           jobid, jobid, jobid );
+
+   } else {
+      Mmsg(mdb->cmd, 
   "INSERT INTO PathVisibility (PathId, JobId)  "
    "SELECT a.PathId,%s "
    "FROM ( "
@@ -343,10 +425,11 @@ static void update_path_hierarchy_cache(JCR *jcr,
           "FROM PathVisibility "
          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
+   }
 
    do {
-      QUERY_DB(jcr, mdb, mdb->cmd);
-   } while (sql_affected_rows(mdb) > 0);
+      ret = QUERY_DB(jcr, mdb, mdb->cmd);
+   } while (ret && sql_affected_rows(mdb) > 0);
    
    Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
    UPDATE_DB(jcr, mdb, mdb->cmd);
@@ -354,6 +437,7 @@ static void update_path_hierarchy_cache(JCR *jcr,
 bail_out:
    db_end_transaction(jcr, mdb);
    db_unlock(mdb);
+   return ret;
 }
 
 /* 
@@ -378,7 +462,6 @@ void bvfs_update_cache(JCR *jcr, B_DB *mdb)
    db_list_ctx jobids_list;
 
    db_lock(mdb);
-   db_start_transaction(jcr, mdb);
 
 #ifdef xxx
    /* TODO: Remove this code when updating make_bacula_table script */
@@ -429,7 +512,6 @@ void bvfs_update_cache(JCR *jcr, B_DB *mdb)
 
    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, 
@@ -446,24 +528,28 @@ void bvfs_update_cache(JCR *jcr, B_DB *mdb)
 /*
  * Update the bvfs cache for given jobids (1,2,3,4)
  */
-void
+int
 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
 {
    pathid_cache ppathid_cache;
    JobId_t JobId;
    char *p;
+   int ret=1;
 
    for (p=jobids; ; ) {
       int stat = get_next_jobid_from_list(&p, &JobId);
       if (stat < 0) {
-         return;
+         return 0;
       }
       if (stat == 0) {
          break;
       }
       Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
-      update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
+      if (!update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId)) {
+         ret = 0;
+      }
    }
+   return ret;
 }
 
 /* 
@@ -479,7 +565,9 @@ bool Bvfs::ch_dir(const char *path)
 {
    pm_strcpy(db->path, path);
    db->pnl = strlen(db->path);
+   db_lock(db);
    ch_dir(db_get_path_record(jcr, db)); 
+   db_unlock(db);
    return pwd_id != 0;
 }
 
@@ -526,8 +614,12 @@ void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
 
 DBId_t Bvfs::get_root()
 {
+   int p;
    *db->path = 0;
-   return db_get_path_record(jcr, db);
+   db_lock(db);
+   p = db_get_path_record(jcr, db);
+   db_unlock(db);
+   return p;
 }
 
 static int path_handler(void *ctx, int fields, char **row)
@@ -567,11 +659,11 @@ void Bvfs::ls_special_dirs()
 
    POOL_MEM query;
    Mmsg(query, 
-"((SELECT PPathId AS PathId, '..' AS Path "
+"(SELECT PPathId AS PathId, '..' AS Path "
     "FROM  PathHierarchy "
-   "WHERE  PathId = %s) "
+   "WHERE  PathId = %s "
 "UNION "
- "(SELECT %s AS PathId, '.' AS Path))",
+ "SELECT %s AS PathId, '.' AS Path)",
         edit_uint64(pwd_id, ed1), ed1);
 
    POOL_MEM query2;
@@ -599,9 +691,11 @@ bool Bvfs::ls_dirs()
       return false;
    }
 
+   POOL_MEM query;
    POOL_MEM filter;
    if (*pattern) {
-      Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
+      Mmsg(filter, " AND Path2.Path %s '%s' ", 
+           match_query[db_get_type_index(db)], pattern);
    }
 
    if (!dir_filenameid) {
@@ -617,7 +711,6 @@ bool Bvfs::ls_dirs()
     * my $dir_filenameid = $self->get_dir_filenameid();
     */
    /* Then we get all the dir entries from File ... */
-   POOL_MEM query;
    Mmsg(query,
 //       0     1     2   3      4     5       6
 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
@@ -633,7 +726,7 @@ bool Bvfs::ls_dirs()
       "JOIN PathVisibility AS PathVisibility1 "
         "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
       "WHERE PathHierarchy1.PPathId = %s "
-      "AND PathVisibility1.jobid IN (%s) "
+      "AND PathVisibility1.JobId IN (%s) "
            "%s "
      ") AS listpath1 "
    "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
@@ -656,17 +749,35 @@ bool Bvfs::ls_dirs()
 
    db_lock(db);
    db_sql_query(db, query.c_str(), path_handler, this);
-   nb_record = db->num_rows;
+   nb_record = sql_num_rows(db);
    db_unlock(db);
 
    return nb_record == limit;
 }
 
+void build_ls_files_query(B_DB *db, POOL_MEM &query, 
+                          const char *JobId, const char *PathId,  
+                          const char *filter, int64_t limit, int64_t offset)
+{
+   if (db_get_type_index(db) == SQL_TYPE_POSTGRESQL) {
+      Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)], 
+           JobId, PathId, JobId, PathId, 
+           filter, limit, offset);
+   } else {
+      Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)], 
+           JobId, PathId, JobId, PathId, 
+           limit, offset, filter, JobId, JobId);
+   }
+}
+
 /* Returns true if we have files to read */
 bool Bvfs::ls_files()
 {
+   POOL_MEM query;
+   POOL_MEM filter;
+   char pathid[50];
+
    Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
-   char ed1[50];
    if (*jobids == 0) {
       return false;
    }
@@ -675,37 +786,21 @@ bool Bvfs::ls_files()
       ch_dir(get_root());
    }
 
-   POOL_MEM filter;
+   edit_uint64(pwd_id, pathid);
    if (*pattern) {
-      Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
+      Mmsg(filter, " AND Filename.Name %s '%s' ", 
+           match_query[db_get_type_index(db)], pattern);
    }
-   /* TODO: Use JobTDate instead of FileId to determine the latest version */
-   POOL_MEM query;
-   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 "
-          "AND Filename.Name != '' "
-          "AND File.PathId = %s "
-          "AND File.JobId IN (%s) "
-          "%s "
-        "GROUP BY Filename.Name "
-        "ORDER BY Filename.Name LIMIT %d OFFSET %d "
-     ") AS listfiles "
-"WHERE File.FileId = listfiles.id",
-        edit_uint64(pwd_id, ed1),
-        jobids,
-        filter.c_str(),
-        limit,
-        offset);
+
+   build_ls_files_query(db, query, 
+                        jobids, pathid, filter.c_str(),
+                        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;
+   nb_record = sql_num_rows(db);
    db_unlock(db);
 
    return nb_record == limit;
@@ -766,6 +861,15 @@ static bool check_temp(char *output_table)
    return false;
 }
 
+void Bvfs::clear_cache()
+{
+   db_sql_query(db, "BEGIN",                     NULL, NULL);
+   db_sql_query(db, "UPDATE Job SET HasCache=0", NULL, NULL);
+   db_sql_query(db, "TRUNCATE PathHierarchy",    NULL, NULL);
+   db_sql_query(db, "TRUNCATE PathVisibility",   NULL, NULL);
+   db_sql_query(db, "COMMIT",                    NULL, NULL);
+}
+
 bool Bvfs::drop_restore_list(char *output_table)
 {
    POOL_MEM query;
@@ -782,7 +886,7 @@ bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
 {
    POOL_MEM query;
    POOL_MEM tmp, tmp2;
-   int64_t id, jobid;
+   int64_t id, jobid, prev_jobid;
    bool init=false;
    bool ret=false;
    /* check args */
@@ -797,22 +901,34 @@ bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
       return false;
    }
 
-   Mmsg(query, "CREATE TEMPORARY TABLE btemp%s AS ", output_table);
+   db_lock(db);
+
+   /* Cleanup old tables first */
+   Mmsg(query, "DROP TABLE btemp%s", output_table);
+   db_sql_query(db, query.c_str());
+
+   Mmsg(query, "DROP TABLE %s", output_table);
+   db_sql_query(db, query.c_str());
+
+   Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
 
-   if (*fileid) {
+   if (*fileid) {               /* Select files with their direct id */
       init=true;
-      Mmsg(tmp, "(SELECT JobId, FileIndex, FilenameId, PathId, FileId "
-                   "FROM File WHERE FileId IN (%s))", fileid);
+      Mmsg(tmp,"SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
+                      "PathId, FileId "
+                 "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
+           fileid);
       pm_strcat(query, tmp.c_str());
    }
 
+   /* Add a directory content */
    while (get_next_id_from_list(&dirid, &id) == 1) {
       Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
       
       if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
          Dmsg0(dbglevel, "Can't search for path\n");
          /* print error */
-         return false;
+         goto bail_out;
       }
       if (!strcmp(tmp2.c_str(), "")) { /* path not found */
          Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
@@ -840,21 +956,35 @@ bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
       if (init) {
          query.strcat(" UNION ");
       }
-      Mmsg(tmp, "(SELECT File.JobId, File.FileIndex, File.FilenameId, "
+
+      Mmsg(tmp, "SELECT Job.JobId, JobTDate, File.FileIndex, File.FilenameId, "
                         "File.PathId, FileId "
-                   "FROM Path JOIN File USING (PathId) "
-                  "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s)) ", 
+                   "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
+                  "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ", 
            tmp2.c_str(), jobids); 
       query.strcat(tmp.c_str());
       init = true;
+
+      query.strcat(" UNION ");
+
+      /* A directory can have files from a BaseJob */
+      Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
+                        "File.FilenameId, File.PathId, BaseFiles.FileId "
+                   "FROM BaseFiles "
+                        "JOIN File USING (FileId) "
+                        "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
+                        "JOIN Path USING (PathId) "
+                  "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ", 
+           tmp2.c_str(), jobids); 
+      query.strcat(tmp.c_str());
    }
 
    /* expect jobid,fileindex */
-   int64_t prev_jobid=0;
+   prev_jobid=0;
    while (get_next_id_from_list(&hardlink, &jobid) == 1) {
       if (get_next_id_from_list(&hardlink, &id) != 1) {
          Dmsg0(dbglevel, "hardlink should be two by two\n");
-         return false;
+         goto bail_out;
       }
       if (jobid != prev_jobid) { /* new job */
          if (prev_jobid == 0) {  /* first jobid */
@@ -862,11 +992,12 @@ bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
                query.strcat(" UNION ");
             }
          } else {               /* end last job, start new one */
-            tmp.strcat(")) UNION ");
+            tmp.strcat(") UNION ");
             query.strcat(tmp.c_str());
          }
-         Mmsg(tmp, "(SELECT JobId, FileIndex, FilenameId, PathId, FileId "
-                       "FROM File WHERE JobId = %lld " 
+         Mmsg(tmp,   "SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
+                            "PathId, FileId "
+                       "FROM File JOIN Job USING (JobId) WHERE JobId = %lld " 
                         "AND FileIndex IN (%lld", jobid, id);
          prev_jobid = jobid;
 
@@ -877,7 +1008,7 @@ bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
    }
 
    if (prev_jobid != 0) {       /* end last job */
-      tmp.strcat(")) ");
+      tmp.strcat(") ");
       query.strcat(tmp.c_str());
       init = true;
    }
@@ -889,18 +1020,34 @@ bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
       goto bail_out;
    }
 
-   /* TODO: handle basejob and SQLite3 */
-   Mmsg(query, sql_bvfs_select[db_type], output_table, output_table);
+   Mmsg(query, sql_bvfs_select[db_get_type_index(db)], 
+        output_table, output_table, output_table);
 
+   /* TODO: handle jobid filter */
    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
    if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
       Dmsg0(dbglevel, "Can't execute q\n");
       goto bail_out;
    }
+
+   /* MySQL need it */
+   if (db_get_type_index(db) == SQL_TYPE_MYSQL) {
+      Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)", 
+           output_table, output_table);
+      Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+      if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
+         Dmsg0(dbglevel, "Can't execute q\n");
+         goto bail_out;
+      }
+   }
+
    ret = true;
 
 bail_out:
    Mmsg(query, "DROP TABLE btemp%s", output_table);
    db_sql_query(db, query.c_str(), NULL, NULL);
+   db_unlock(db);
    return ret;
 }
+
+#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */