2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
22 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
23 #include "lib/htable.h"
26 #define can_access(x) (true)
29 extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI);
31 #define dbglevel DT_BVFS|10
32 #define dbglevel_sql DT_SQL|15
34 static int result_handler(void *ctx, int fields, char **row)
37 Pmsg4(0, "%s\t%s\t%s\t%s\n",
38 row[0], row[1], row[2], row[3]);
39 } else if (fields == 5) {
40 Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
41 row[0], row[1], row[2], row[3], row[4]);
42 } else if (fields == 6) {
43 Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
44 row[0], row[1], row[2], row[3], row[4], row[5]);
45 } else if (fields == 7) {
46 Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
47 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
52 Bvfs::Bvfs(JCR *j, BDB *mdb)
56 db = mdb; /* need to inc ref count */
57 jobids = get_pool_memory(PM_NAME);
58 prev_dir = get_pool_memory(PM_NAME);
59 pattern = get_pool_memory(PM_NAME);
60 filename = get_pool_memory(PM_NAME);
61 tmp = get_pool_memory(PM_NAME);
62 escaped_list = get_pool_memory(PM_NAME);
63 *filename = *jobids = *prev_dir = *pattern = 0;
65 see_copies = see_all_versions = false;
69 list_entries = result_handler;
72 job_acl = client_acl = pool_acl = fileset_acl = NULL;
79 free_pool_memory(jobids);
80 free_pool_memory(pattern);
81 free_pool_memory(prev_dir);
82 free_pool_memory(filename);
83 free_pool_memory(tmp);
84 free_pool_memory(escaped_list);
95 char *Bvfs::escape_list(alist *lst)
100 /* List is empty, reject everything */
101 if (!lst || lst->size() == 0) {
102 Mmsg(escaped_list, "''");
109 foreach_alist(elt, lst) {
113 tmp = check_pool_memory_size(tmp, 2 * len + 2 + 2);
116 db->bdb_escape_string(jcr, tmp + 1 , elt, len);
120 pm_strcat(escaped_list, ",");
123 pm_strcat(escaped_list, tmp);
129 /* Returns the number of jobids in the result */
130 int Bvfs::filter_jobid()
136 /* No ACL, no username, no check */
137 if (!job_acl && !fileset_acl && !client_acl && !pool_acl && !username) {
138 Dmsg0(dbglevel_sql, "No ACL\n");
139 /* Just count the number of items in the list */
140 int nb = (*jobids != 0) ? 1 : 0;
141 for (char *p=jobids; *p ; p++) {
150 Mmsg(sub_where, " AND Job.Name IN (%s) ", escape_list(job_acl));
154 Mmsg(query, " AND FileSet.FileSet IN (%s) ", escape_list(fileset_acl));
155 pm_strcat(sub_where, query.c_str());
156 pm_strcat(sub_join, " JOIN FileSet USING (FileSetId) ");
160 Mmsg(query, " AND Client.Name IN (%s) ", escape_list(client_acl));
161 pm_strcat(sub_where, query.c_str());
165 Mmsg(query, " AND Pool.Name IN (%s) ", escape_list(pool_acl));
166 pm_strcat(sub_where, query.c_str());
167 pm_strcat(sub_join, " JOIN Pool USING (PoolId) ");
171 /* Query used by Bweb to filter clients, activated when using
175 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
176 "JOIN (SELECT ClientId FROM client_group_member "
177 "JOIN client_group USING (client_group_id) "
178 "JOIN bweb_client_group_acl USING (client_group_id) "
179 "JOIN bweb_user USING (userid) "
180 "WHERE bweb_user.username = '%s' "
181 ") AS filter USING (ClientId) "
182 " WHERE JobId IN (%s) %s",
183 sub_join.c_str(), username, jobids, sub_where.c_str());
187 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
188 " WHERE JobId IN (%s) %s",
189 sub_join.c_str(), jobids, sub_where.c_str());
193 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
194 db->bdb_sql_query(query.c_str(), db_list_handler, &ctx);
195 pm_strcpy(jobids, ctx.list);
199 /* Return the number of jobids after the filter */
200 int Bvfs::set_jobid(JobId_t id)
202 Mmsg(jobids, "%lld", (uint64_t)id);
203 return filter_jobid();
206 /* Return the number of jobids after the filter */
207 int Bvfs::set_jobids(char *ids)
209 pm_strcpy(jobids, ids);
210 return filter_jobid();
214 * TODO: Find a way to let the user choose how he wants to display
215 * files and directories
220 * Working Object to store PathId already seen (avoid
221 * database queries), equivalent to %cache_ppathid in perl
233 htable *cache_ppathid;
238 cache_ppathid = (htable *)malloc(sizeof(htable));
239 cache_ppathid->init(&link, &link, NITEMS);
241 nodes = (hlink *) malloc(max_node * sizeof (hlink));
243 table_node = New(alist(5, owned_by_alist));
244 table_node->append(nodes);
248 if (++nb_node >= max_node) {
250 nodes = (hlink *)malloc(max_node * sizeof(hlink));
251 table_node->append(nodes);
253 return nodes + nb_node;
256 bool lookup(char *pathid) {
257 bool ret = cache_ppathid->lookup(pathid) != NULL;
261 void insert(char *pathid) {
262 hlink *h = get_hlink();
263 cache_ppathid->insert(pathid, h);
267 cache_ppathid->destroy();
272 pathid_cache(const pathid_cache &); /* prohibit pass by value */
273 pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
276 /* Return the parent_dir with the trailing / (update the given string)
277 * TODO: see in the rest of bacula if we don't have already this function
283 char *bvfs_parent_dir(char *path)
286 int len = strlen(path) - 1;
288 /* windows directory / */
289 if (len == 2 && B_ISALPHA(path[0])
297 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
303 while (p > path && !IsPathSeparator(*p)) {
311 /* Return the basename of the with the trailing /
312 * TODO: see in the rest of bacula if we don't have
313 * this function already
315 char *bvfs_basename_dir(char *path)
318 int len = strlen(path) - 1;
320 if (path[len] == '/') { /* if directory, skip last / */
326 while (p > path && !IsPathSeparator(*p)) {
330 p++; /* skip first / */
336 static void build_path_hierarchy(JCR *jcr, BDB *mdb,
337 pathid_cache &ppathid_cache,
338 char *org_pathid, char *path)
340 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
343 char *bkp = mdb->path;
344 strncpy(pathid, org_pathid, sizeof(pathid));
346 /* Does the ppathid exist for this ? we use a memory cache... In order to
347 * avoid the full loop, we consider that if a dir is allready in the
348 * PathHierarchy table, then there is no need to calculate all the
351 while (path && *path)
353 if (!ppathid_cache.lookup(pathid))
356 "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
359 if (!mdb->QueryDB(jcr, mdb->cmd)) {
360 goto bail_out; /* Query failed, just leave */
363 /* Do we have a result ? */
364 if (mdb->sql_num_rows() > 0) {
365 ppathid_cache.insert(pathid);
366 /* This dir was in the db ...
367 * It means we can leave, the tree has allready been built for
372 /* search or create parent PathId in Path table */
373 mdb->path = bvfs_parent_dir(path);
374 mdb->pnl = strlen(mdb->path);
375 if (!mdb->bdb_create_path_record(jcr, &parent)) {
378 ppathid_cache.insert(pathid);
381 "INSERT INTO PathHierarchy (PathId, PPathId) "
383 pathid, (uint64_t) parent.PathId);
385 if (!mdb->InsertDB(jcr, mdb->cmd)) {
386 goto bail_out; /* Can't insert the record, just leave */
389 edit_uint64(parent.PathId, pathid);
390 path = mdb->path; /* already done */
393 /* It's already in the cache. We can leave, no time to waste here,
394 * all the parent dirs have allready been done
406 * Internal function to update path_hierarchy cache with a shared pathid cache
410 static int update_path_hierarchy_cache(JCR *jcr,
412 pathid_cache &ppathid_cache,
415 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
419 edit_uint64(JobId, jobid);
423 /* We don't really want to harm users with spurious messages,
424 * everything is handled by transaction
426 mdb->set_use_fatal_jmsg(false);
428 mdb->bdb_start_transaction(jcr);
430 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
432 if (!mdb->QueryDB(jcr, mdb->cmd) || mdb->sql_num_rows() > 0) {
433 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
438 /* Inserting path records for JobId */
439 Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
440 "SELECT DISTINCT PathId, JobId "
441 "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
443 "SELECT PathId, BaseFiles.JobId "
444 "FROM BaseFiles JOIN File AS F USING (FileId) "
445 "WHERE BaseFiles.JobId = %s) AS B",
448 if (!mdb->QueryDB(jcr, mdb->cmd)) {
449 Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId );
453 /* Now we have to do the directory recursion stuff to determine missing
454 * visibility We try to avoid recursion, to be as fast as possible We also
455 * only work on not allready hierarchised directories...
458 "SELECT PathVisibility.PathId, Path "
459 "FROM PathVisibility "
460 "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
461 "LEFT JOIN PathHierarchy "
462 "ON (PathVisibility.PathId = PathHierarchy.PathId) "
463 "WHERE PathVisibility.JobId = %s "
464 "AND PathHierarchy.PathId IS NULL "
465 "ORDER BY Path", jobid);
466 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
468 if (!mdb->QueryDB(jcr, mdb->cmd)) {
469 Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId );
473 /* TODO: I need to reuse the DB connection without emptying the result
474 * So, now i'm copying the result in memory to be able to query the
475 * catalog descriptor again.
477 num = mdb->sql_num_rows();
479 char **result = (char **)malloc (num * 2 * sizeof(char *));
483 while((row = mdb->sql_fetch_row())) {
484 result[i++] = bstrdup(row[0]);
485 result[i++] = bstrdup(row[1]);
490 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
498 if (mdb->bdb_get_type_index() == SQL_TYPE_SQLITE3) {
500 "INSERT INTO PathVisibility (PathId, JobId) "
501 "SELECT DISTINCT h.PPathId AS PathId, %s "
502 "FROM PathHierarchy AS h "
503 "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
504 "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
505 jobid, jobid, jobid );
507 } else if (mdb->bdb_get_type_index() == SQL_TYPE_MYSQL) {
509 "INSERT INTO PathVisibility (PathId, JobId) "
510 "SELECT a.PathId,%s "
512 "SELECT DISTINCT h.PPathId AS PathId "
513 "FROM PathHierarchy AS h "
514 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
515 "WHERE p.JobId=%s) AS a "
516 "LEFT JOIN PathVisibility AS b ON (b.JobId=%s and a.PathId = b.PathId) "
517 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
521 "INSERT INTO PathVisibility (PathId, JobId) "
522 "SELECT a.PathId,%s "
524 "SELECT DISTINCT h.PPathId AS PathId "
525 "FROM PathHierarchy AS h "
526 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
527 "WHERE p.JobId=%s) AS a LEFT JOIN "
529 "FROM PathVisibility "
530 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
531 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
535 ret = mdb->QueryDB(jcr, mdb->cmd);
536 } while (ret && mdb->sql_affected_rows() > 0);
538 Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
539 ret = mdb->UpdateDB(jcr, mdb->cmd, false);
542 mdb->bdb_end_transaction(jcr);
545 Mmsg(mdb->cmd, "SELECT HasCache FROM Job WHERE JobId=%s", jobid);
546 mdb->bdb_sql_query(mdb->cmd, db_int_handler, &ret);
549 /* Enable back the FATAL message if something is wrong */
550 mdb->set_use_fatal_jmsg(true);
556 /* Compute the cache for the bfileview compoment */
557 void Bvfs::fv_update_cache()
560 int64_t size=0, count=0;
562 Dmsg0(dbglevel, "fv_update_cache()\n");
565 return; /* Nothing to build */
569 /* We don't really want to harm users with spurious messages,
570 * everything is handled by transaction
572 db->set_use_fatal_jmsg(false);
574 db->bdb_start_transaction(jcr);
578 fv_compute_size_and_count(pathid, &size, &count);
580 db->bdb_end_transaction(jcr);
582 /* Enable back the FATAL message if something is wrong */
583 db->set_use_fatal_jmsg(true);
589 * Find an store the filename descriptor for empty directories Filename.Name=''
591 DBId_t Bvfs::get_dir_filenameid()
594 if (dir_filenameid) {
595 return dir_filenameid;
597 Mmsg(db->cmd, "SELECT FilenameId FROM Filename WHERE Name = ''");
598 db_sql_query(db, db->cmd, db_int_handler, &id);
600 return dir_filenameid;
603 /* Not yet working */
604 void Bvfs::fv_get_big_files(int64_t pathid, int64_t min_size, int32_t limit)
607 "SELECT FilenameId AS filenameid, Name AS name, size "
609 "SELECT FilenameId, base64_decode_lstat(8,LStat) AS size "
611 "WHERE PathId = %lld "
613 ") AS S INNER JOIN Filename USING (FilenameId) "
614 "WHERE S.size > %lld "
615 "ORDER BY S.size DESC "
616 "LIMIT %d ", pathid, jobids, min_size, limit);
620 /* Get the current path size and files count */
621 void Bvfs::fv_get_current_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
628 "SELECT Size AS size, Files AS files "
629 " FROM PathVisibility "
630 " WHERE PathId = %lld "
631 " AND JobId = %s ", pathid, jobids);
633 if (!db->QueryDB(jcr, db->cmd)) {
637 if ((row = db->sql_fetch_row())) {
638 *size = str_to_int64(row[0]);
639 *count = str_to_int64(row[1]);
643 /* Compute for the current path the size and files count */
644 void Bvfs::fv_get_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
651 "SELECT sum(base64_decode_lstat(8,LStat)) AS size, count(1) AS files "
653 " WHERE PathId = %lld "
654 " AND JobId = %s ", pathid, jobids);
656 if (!db->QueryDB(jcr, db->cmd)) {
660 if ((row = db->sql_fetch_row())) {
661 *size = str_to_int64(row[0]);
662 *count = str_to_int64(row[1]);
666 void Bvfs::fv_compute_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
668 Dmsg1(dbglevel, "fv_compute_size_and_count(%lld)\n", pathid);
670 fv_get_current_size_and_count(pathid, size, count);
675 /* Update stats for the current directory */
676 fv_get_size_and_count(pathid, size, count);
678 /* Update stats for all sub directories */
681 " FROM PathVisibility "
682 " INNER JOIN PathHierarchy USING (PathId) "
683 " WHERE PPathId = %lld "
684 " AND JobId = %s ", pathid, jobids);
686 db->QueryDB(jcr, db->cmd);
687 int num = db->sql_num_rows();
690 int64_t *result = (int64_t *)malloc (num * sizeof(int64_t));
694 while((row = db->sql_fetch_row())) {
695 result[i++] = str_to_int64(row[0]); /* PathId */
701 fv_compute_size_and_count(result[i], &s, &c);
711 fv_update_size_and_count(pathid, *size, *count);
714 void Bvfs::fv_update_size_and_count(int64_t pathid, int64_t size, int64_t count)
717 "UPDATE PathVisibility SET Files = %lld, Size = %lld "
719 " AND PathId = %lld ", count, size, jobids, pathid);
721 db->UpdateDB(jcr, db->cmd, false);
724 void bvfs_update_cache(JCR *jcr, BDB *mdb)
727 db_list_ctx jobids_list;
732 /* TODO: Remove this code when updating make_bacula_table script */
733 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
734 if (!mdb->QueryDB(jcr, mdb->cmd)) {
735 Dmsg0(dbglevel, "Creating cache table\n");
736 Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
737 mdb->QueryDB(jcr, mdb->cmd);
740 "CREATE TABLE PathHierarchy ( "
741 "PathId integer NOT NULL, "
742 "PPathId integer NOT NULL, "
743 "CONSTRAINT pathhierarchy_pkey "
744 "PRIMARY KEY (PathId))");
745 mdb->QueryDB(jcr, mdb->cmd);
748 "CREATE INDEX pathhierarchy_ppathid "
749 "ON PathHierarchy (PPathId)");
750 mdb->QueryDB(jcr, mdb->cmd);
753 "CREATE TABLE PathVisibility ("
754 "PathId integer NOT NULL, "
755 "JobId integer NOT NULL, "
756 "Size int8 DEFAULT 0, "
757 "Files int4 DEFAULT 0, "
758 "CONSTRAINT pathvisibility_pkey "
759 "PRIMARY KEY (JobId, PathId))");
760 mdb->QueryDB(jcr, mdb->cmd);
763 "CREATE INDEX pathvisibility_jobid "
764 "ON PathVisibility (JobId)");
765 mdb->QueryDB(jcr, mdb->cmd);
771 "SELECT JobId from Job "
772 "WHERE HasCache = 0 "
773 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
776 mdb->bdb_sql_query(mdb->cmd, db_list_handler, &jobids_list);
778 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
780 mdb->bdb_start_transaction(jcr);
781 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
783 "DELETE FROM PathVisibility "
785 "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
786 nb = mdb->DeleteDB(jcr, mdb->cmd);
787 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
789 mdb->bdb_end_transaction(jcr);
794 * Update the bvfs cache for given jobids (1,2,3,4)
797 bvfs_update_path_hierarchy_cache(JCR *jcr, BDB *mdb, char *jobids)
799 pathid_cache ppathid_cache;
805 int stat = get_next_jobid_from_list(&p, &JobId);
813 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
814 if (!update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId)) {
822 * Update the bvfs fileview for given jobids
825 bvfs_update_fv_cache(JCR *jcr, BDB *mdb, char *jobids)
832 int stat = get_next_jobid_from_list(&p, &JobId);
840 Dmsg1(dbglevel, "Trying to create cache for %lld\n", (int64_t)JobId);
842 bvfs.set_jobid(JobId);
843 bvfs.fv_update_cache();
848 * Update the bvfs cache for current jobids
850 void Bvfs::update_cache()
852 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
856 bool Bvfs::ch_dir(DBId_t pathid)
865 /* Change the current directory, returns true if the path exists */
866 bool Bvfs::ch_dir(const char *path)
869 pm_strcpy(db->path, path);
870 db->pnl = strlen(db->path);
871 ch_dir(db->bdb_get_path_record(jcr));
877 * Get all file versions for a specified client
878 * TODO: Handle basejobs using different client
880 void Bvfs::get_all_file_versions(DBId_t pathid, FileId_t fnid, const char *client)
882 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
883 (uint64_t)fnid, client);
884 char ed1[50], ed2[50];
887 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
889 Mmsg(q, " AND Job.Type = 'B' ");
895 "SELECT 'V', File.PathId, File.FilenameId, File.Md5, "
897 "File.JobId, File.LStat, File.FileId, "
899 "Media.VolumeName, Media.InChanger "
900 "FROM File, Job, Client, JobMedia, Media "
901 "WHERE File.FilenameId = %s "
902 "AND File.PathId=%s "
903 "AND File.JobId = Job.JobId "
904 "AND Job.JobId = JobMedia.JobId "
905 "AND File.FileIndex >= JobMedia.FirstIndex "
906 "AND File.FileIndex <= JobMedia.LastIndex "
907 "AND JobMedia.MediaId = Media.MediaId "
908 "AND Job.ClientId = Client.ClientId "
909 "AND Client.Name = '%s' "
910 "%s ORDER BY FileId LIMIT %d OFFSET %d"
911 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
913 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
914 db->bdb_sql_query(query.c_str(), list_entries, user_data);
918 * Get all file versions for a specified client
919 * TODO: Handle basejobs using different client
921 bool Bvfs::get_delta(FileId_t fileid)
923 Dmsg1(dbglevel, "get_delta(%lld)\n", (uint64_t)fileid);
933 /* Check if some FileId have DeltaSeq > 0
934 * Foreach of them we need to get the accurate_job list, and compute
935 * what are dependencies
938 "SELECT F.JobId, FN.Name, F.PathId, F.DeltaSeq "
939 "FROM File AS F, Filename AS FN WHERE FileId = %lld "
940 "AND FN.FilenameId = F.FilenameId AND DeltaSeq > 0", fileid);
942 if (!db->QueryDB(jcr, query.c_str())) {
943 Dmsg1(dbglevel_sql, "Can't execute query=%s\n", query.c_str());
947 /* TODO: Use an other DB connection can avoid to copy the result of the
948 * previous query into a temporary buffer
950 num = db->sql_num_rows();
951 Dmsg2(dbglevel, "Found %d Delta parts q=%s\n",
954 if (num > 0 && (row = db->sql_fetch_row())) {
957 memset(&jr, 0, sizeof(jr));
958 memset(&jr2, 0, sizeof(jr2));
960 fn = bstrdup(row[1]); /* Filename */
961 int64_t jid = str_to_int64(row[0]); /* JobId */
962 int64_t pid = str_to_int64(row[2]); /* PathId */
964 /* Need to limit the query to StartTime, Client/Fileset */
966 if (!db->bdb_get_job_record(jcr, &jr2)) {
967 Dmsg1(0, "Unable to get job record for jobid %d\n", jid);
972 jr.ClientId = jr2.ClientId;
973 jr.FileSetId = jr2.FileSetId;
974 jr.JobLevel = L_INCREMENTAL;
975 jr.StartTime = jr2.StartTime;
977 /* Get accurate jobid list */
978 if (!db->bdb_get_accurate_jobids(jcr, &jr, &lst)) {
979 Dmsg1(0, "Unable to get Accurate list for jobid %d\n", jid);
983 /* Escape filename */
984 db->fnl = strlen(fn);
985 db->esc_name = check_pool_memory_size(db->esc_name, 2*db->fnl+2);
986 db->bdb_escape_string(jcr, db->esc_name, fn, db->fnl);
988 edit_int64(pid, ed1); /* pathid */
990 int id=db->bdb_get_type_index();
991 Mmsg(query, bvfs_select_delta_version_with_basejob_and_delta[id],
992 lst.list, db->esc_name, ed1,
993 lst.list, db->esc_name, ed1,
998 "SELECT 'd', PathId, 0, JobId, LStat, FileId, DeltaSeq, JobTDate"
1000 "ORDER BY DeltaSeq ASC",
1003 Dmsg1(dbglevel_sql, "q=%s\n", db->cmd);
1005 if (!db->bdb_sql_query(db->cmd, list_entries, user_data)) {
1006 Dmsg1(dbglevel_sql, "Can't exec q=%s\n", db->cmd);
1020 * Get all volumes for a specific file
1022 void Bvfs::get_volumes(FileId_t fileid)
1024 Dmsg1(dbglevel, "get_volumes(%lld)\n", (uint64_t)fileid);
1031 "SELECT DISTINCT 'L',0,0,0,0,0,0, Media.VolumeName, Media.InChanger "
1032 "FROM File JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
1033 "WHERE File.FileId = %s "
1034 "AND File.FileIndex >= JobMedia.FirstIndex "
1035 "AND File.FileIndex <= JobMedia.LastIndex "
1036 " LIMIT %d OFFSET %d"
1037 ,edit_uint64(fileid, ed1), limit, offset);
1038 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1039 db->bdb_sql_query(query.c_str(), list_entries, user_data);
1042 DBId_t Bvfs::get_root()
1047 p = db->bdb_get_path_record(jcr);
1052 static int path_handler(void *ctx, int fields, char **row)
1054 Bvfs *fs = (Bvfs *) ctx;
1055 return fs->_handle_path(ctx, fields, row);
1058 int Bvfs::_handle_path(void *ctx, int fields, char **row)
1060 if (bvfs_is_dir(row)) {
1061 /* can have the same path 2 times */
1062 if (strcmp(row[BVFS_PathId], prev_dir)) {
1063 pm_strcpy(prev_dir, row[BVFS_PathId]);
1064 if (strcmp(NPRTB(row[BVFS_FileIndex]), "0") == 0 &&
1065 strcmp(NPRTB(row[BVFS_FileId]), "0") != 0)
1067 /* The directory was probably deleted */
1070 return list_entries(user_data, fields, row);
1077 * Retrieve . and .. information
1079 void Bvfs::ls_special_dirs()
1081 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
1082 char ed1[50], ed2[50];
1086 if (!dir_filenameid) {
1087 get_dir_filenameid();
1090 /* Will fetch directories */
1095 "(SELECT PPathId AS PathId, '..' AS Path "
1096 "FROM PathHierarchy "
1097 "WHERE PathId = %s "
1099 "SELECT %s AS PathId, '.' AS Path)",
1100 edit_uint64(pwd_id, ed1), ed1);
1103 Mmsg(query2,// 1 2 3 4 5 6
1104 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
1105 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
1106 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
1107 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
1108 "WHERE File1.FilenameId = %s "
1109 "AND File1.JobId IN (%s)) AS listfile1 "
1110 "ON (tmp.PathId = listfile1.PathId) "
1111 "ORDER BY tmp.Path, JobId DESC ",
1112 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
1114 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
1115 db->bdb_sql_query(query2.c_str(), path_handler, this);
1118 /* Returns true if we have dirs to read */
1119 bool Bvfs::ls_dirs()
1121 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
1122 char ed1[50], ed2[50];
1130 Mmsg(filter, " AND Path2.Path %s '%s' ",
1131 match_query[db->bdb_get_type_index()], pattern);
1135 if (!dir_filenameid) {
1136 get_dir_filenameid();
1139 /* the sql query displays same directory multiple time, take the first one */
1142 /* Let's retrieve the list of the visible dirs in this dir ...
1143 * First, I need the empty filenameid to locate efficiently
1144 * the dirs in the file table
1145 * my $dir_filenameid = $self->get_dir_filenameid();
1147 /* Then we get all the dir entries from File ... */
1150 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
1151 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
1152 "lower(Path1.Path) AS lpath, "
1153 "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
1154 "listfile1.FileId AS FileId "
1156 "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
1157 "FROM PathHierarchy AS PathHierarchy1 "
1158 "JOIN Path AS Path2 "
1159 "ON (PathHierarchy1.PathId = Path2.PathId) "
1160 "JOIN PathVisibility AS PathVisibility1 "
1161 "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
1162 "WHERE PathHierarchy1.PPathId = %s "
1163 "AND PathVisibility1.JobId IN (%s) "
1166 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
1168 "LEFT JOIN ( " /* get attributes if any */
1169 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
1170 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
1171 "WHERE File1.FilenameId = %s "
1172 "AND File1.JobId IN (%s)) AS listfile1 "
1173 "ON (listpath1.PathId = listfile1.PathId) "
1174 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
1175 edit_uint64(pwd_id, ed1),
1178 edit_uint64(dir_filenameid, ed2),
1182 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1185 db->bdb_sql_query(query.c_str(), path_handler, this);
1186 nb_record = db->sql_num_rows();
1189 return nb_record == limit;
1192 void build_ls_files_query(BDB *db, POOL_MEM &query,
1193 const char *JobId, const char *PathId,
1194 const char *filter, int64_t limit, int64_t offset)
1196 if (db->bdb_get_type_index() == SQL_TYPE_POSTGRESQL) {
1197 Mmsg(query, sql_bvfs_list_files[db->bdb_get_type_index()],
1198 JobId, PathId, JobId, PathId,
1199 filter, limit, offset);
1201 Mmsg(query, sql_bvfs_list_files[db->bdb_get_type_index()],
1202 JobId, PathId, JobId, PathId,
1203 limit, offset, filter, JobId, JobId);
1207 /* Returns true if we have files to read */
1208 bool Bvfs::ls_files()
1214 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
1223 edit_uint64(pwd_id, pathid);
1225 Mmsg(filter, " AND Filename.Name %s '%s' ",
1226 match_query[db_get_type_index(db)], pattern);
1228 } else if (*filename) {
1229 Mmsg(filter, " AND Filename.Name = '%s' ", filename);
1232 build_ls_files_query(db, query,
1233 jobids, pathid, filter.c_str(),
1236 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1239 db->bdb_sql_query(query.c_str(), list_entries, user_data);
1240 nb_record = db->sql_num_rows();
1243 return nb_record == limit;
1248 * Return next Id from comma separated list
1251 * 1 if next Id returned
1252 * 0 if no more Ids are in list
1253 * -1 there is an error
1254 * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
1256 static int get_next_id_from_list(char **p, int64_t *Id)
1258 const int maxlen = 30;
1263 for (int i=0; i<maxlen; i++) {
1266 } else if (*q == ',') {
1275 } else if (!is_a_number(id)) {
1276 return -1; /* error */
1279 *Id = str_to_int64(id);
1283 static int get_path_handler(void *ctx, int fields, char **row)
1285 POOL_MEM *buf = (POOL_MEM *) ctx;
1286 pm_strcpy(*buf, row[0]);
1290 static bool check_temp(char *output_table)
1292 if (output_table[0] == 'b' &&
1293 output_table[1] == '2' &&
1294 is_an_integer(output_table + 2))
1301 void Bvfs::clear_cache()
1303 db->bdb_sql_query("BEGIN", NULL, NULL);
1304 db->bdb_sql_query("UPDATE Job SET HasCache=0", NULL, NULL);
1305 db->bdb_sql_query("TRUNCATE PathHierarchy", NULL, NULL);
1306 db->bdb_sql_query("TRUNCATE PathVisibility", NULL, NULL);
1307 db->bdb_sql_query("COMMIT", NULL, NULL);
1310 bool Bvfs::drop_restore_list(char *output_table)
1313 if (check_temp(output_table)) {
1314 Mmsg(query, "DROP TABLE %s", output_table);
1315 db->bdb_sql_query(query.c_str(), NULL, NULL);
1321 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
1326 int64_t id, jobid, prev_jobid;
1331 if ((*fileid && !is_a_number_list(fileid)) ||
1332 (*dirid && !is_a_number_list(dirid)) ||
1333 (*hardlink && !is_a_number_list(hardlink))||
1334 (!*hardlink && !*fileid && !*dirid && !*hardlink))
1338 if (!check_temp(output_table)) {
1344 /* Cleanup old tables first */
1345 Mmsg(query, "DROP TABLE btemp%s", output_table);
1346 db->bdb_sql_query(query.c_str());
1348 Mmsg(query, "DROP TABLE %s", output_table);
1349 db->bdb_sql_query(query.c_str());
1351 Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
1353 if (*fileid) { /* Select files with their direct id */
1355 Mmsg(tmp,"SELECT Job.JobId, JobTDate, FileIndex, Filename.Name, "
1357 "FROM File,Job,Filename WHERE Job.JobId=File.Jobid "
1358 "AND File.FilenameId=Filename.FilenameId AND FileId IN (%s)",
1360 pm_strcat(query, tmp.c_str());
1363 /* Add a directory content */
1364 while (get_next_id_from_list(&dirid, &id) == 1) {
1365 Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
1367 if (!db->bdb_sql_query(tmp.c_str(), get_path_handler, (void *)&tmp2)) {
1368 Dmsg0(dbglevel, "Can't search for path\n");
1372 if (!strcmp(tmp2.c_str(), "")) { /* path not found */
1373 Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
1374 id, tmp.c_str(), tmp2.c_str());
1377 /* escape % and _ for LIKE search */
1378 tmp.check_size((strlen(tmp2.c_str())+1) * 2);
1379 char *p = tmp.c_str();
1380 for (char *s = tmp2.c_str(); *s ; s++) {
1381 if (*s == '%' || *s == '_' || *s == '\\') {
1391 size_t len = strlen(tmp.c_str());
1392 tmp2.check_size((len+1) * 2);
1393 db->bdb_escape_string(jcr, tmp2.c_str(), tmp.c_str(), len);
1396 query.strcat(" UNION ");
1399 Mmsg(tmp, "SELECT Job.JobId, JobTDate, File.FileIndex, File.FilenameId, "
1400 "File.PathId, FileId "
1401 "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
1402 "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
1403 tmp2.c_str(), jobids);
1404 query.strcat(tmp.c_str());
1407 query.strcat(" UNION ");
1409 /* A directory can have files from a BaseJob */
1410 Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
1411 "File.FilenameId, File.PathId, BaseFiles.FileId "
1413 "JOIN File USING (FileId) "
1414 "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
1415 "JOIN Path USING (PathId) "
1416 "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
1417 tmp2.c_str(), jobids);
1418 query.strcat(tmp.c_str());
1421 /* expect jobid,fileindex */
1423 while (get_next_id_from_list(&hardlink, &jobid) == 1) {
1424 if (get_next_id_from_list(&hardlink, &id) != 1) {
1425 Dmsg0(dbglevel, "hardlink should be two by two\n");
1428 if (jobid != prev_jobid) { /* new job */
1429 if (prev_jobid == 0) { /* first jobid */
1431 query.strcat(" UNION ");
1433 } else { /* end last job, start new one */
1434 tmp.strcat(") UNION ");
1435 query.strcat(tmp.c_str());
1437 Mmsg(tmp, "SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1439 "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
1440 "AND FileIndex IN (%lld", jobid, id);
1443 } else { /* same job, add new findex */
1444 Mmsg(tmp2, ", %lld", id);
1445 tmp.strcat(tmp2.c_str());
1449 if (prev_jobid != 0) { /* end last job */
1451 query.strcat(tmp.c_str());
1455 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1457 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1458 Dmsg1(dbglevel, "Can't execute query=%s\n", query.c_str());
1462 Mmsg(query, sql_bvfs_select[db->bdb_get_type_index()],
1463 output_table, output_table, output_table);
1465 /* TODO: handle jobid filter */
1466 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1467 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1468 Dmsg1(dbglevel, "Can't execute query=%s\n", query.c_str());
1472 /* MySQL needs the index */
1473 if (db->bdb_get_type_index() == SQL_TYPE_MYSQL) {
1474 Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)",
1475 output_table, output_table);
1476 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1477 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1478 Dmsg1(dbglevel, "Can't execute query=%s\n", query.c_str());
1483 /* Check if some FileId have DeltaSeq > 0
1484 * Foreach of them we need to get the accurate_job list, and compute
1485 * what are dependencies
1488 "SELECT F.FileId, F.JobId, F.FilenameId, F.PathId, F.DeltaSeq "
1489 "FROM File AS F JOIN Job USING (JobId) JOIN %s USING (FileId) "
1490 "WHERE DeltaSeq > 0", output_table);
1492 if (!db->QueryDB(jcr, query.c_str())) {
1493 Dmsg1(dbglevel_sql, "Can't execute query=%s\n", query.c_str());
1496 /* TODO: Use an other DB connection can avoid to copy the result of the
1497 * previous query into a temporary buffer
1499 num = db->sql_num_rows();
1500 Dmsg2(dbglevel, "Found %d Delta parts in restore selection q=%s\n", num, query.c_str());
1503 int64_t *result = (int64_t *)malloc (num * 4 * sizeof(int64_t));
1507 while((row = db->sql_fetch_row())) {
1508 result[i++] = str_to_int64(row[0]); /* FileId */
1509 result[i++] = str_to_int64(row[1]); /* JobId */
1510 result[i++] = str_to_int64(row[2]); /* FilenameId */
1511 result[i++] = str_to_int64(row[3]); /* PathId */
1516 insert_missing_delta(output_table, result + i);
1526 Mmsg(query, "DROP TABLE btemp%s", output_table);
1527 db->bdb_sql_query(query.c_str(), NULL, NULL);
1532 void Bvfs::insert_missing_delta(char *output_table, int64_t *res)
1538 memset(&jr, 0, sizeof(jr));
1539 memset(&jr2, 0, sizeof(jr2));
1541 /* Need to limit the query to StartTime, Client/Fileset */
1543 db->bdb_get_job_record(jcr, &jr2);
1546 jr.ClientId = jr2.ClientId;
1547 jr.FileSetId = jr2.FileSetId;
1548 jr.JobLevel = L_INCREMENTAL;
1549 jr.StartTime = jr2.StartTime;
1551 /* Get accurate jobid list */
1552 db->bdb_get_accurate_jobids(jcr, &jr, &lst);
1554 Dmsg2(dbglevel_sql, "JobId list for %lld is %s\n", res[0], lst.list);
1556 /* The list contains already the last DeltaSeq element, so
1557 * we don't need to select it in the next query
1559 for (int l = strlen(lst.list); l > 0; l--) {
1560 if (lst.list[l] == ',') {
1566 Dmsg1(dbglevel_sql, "JobId list after strip is %s\n", lst.list);
1568 /* Escape filename */
1569 db->fnl = strlen((char *)res[2]);
1570 db->esc_name = check_pool_memory_size(db->esc_name, 2*db->fnl+2);
1571 db->bdb_escape_string(jcr, db->esc_name, (char *)res[2], db->fnl);
1573 edit_int64(res[3], ed1); /* pathid */
1575 int id=db->bdb_get_type_index();
1576 Mmsg(query, bvfs_select_delta_version_with_basejob_and_delta[id],
1577 lst.list, db->esc_name, ed1,
1578 lst.list, db->esc_name, ed1,
1579 lst.list, lst.list);
1581 Mmsg(db->cmd, "INSERT INTO %s "
1582 "SELECT JobId, FileIndex, FileId FROM (%s) AS F1",
1583 output_table, query.c_str());
1585 if (!db->bdb_sql_query(db->cmd, NULL, NULL)) {
1586 Dmsg1(dbglevel_sql, "Can't exec q=%s\n", db->cmd);
1590 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */