2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2009-2014 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
23 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
24 #include "lib/htable.h"
27 #define dbglevel DT_BVFS|10
28 #define dbglevel_sql DT_SQL|15
30 static int result_handler(void *ctx, int fields, char **row)
33 Pmsg4(0, "%s\t%s\t%s\t%s\n",
34 row[0], row[1], row[2], row[3]);
35 } else if (fields == 5) {
36 Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
37 row[0], row[1], row[2], row[3], row[4]);
38 } else if (fields == 6) {
39 Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
40 row[0], row[1], row[2], row[3], row[4], row[5]);
41 } else if (fields == 7) {
42 Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
43 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
48 Bvfs::Bvfs(JCR *j, BDB *mdb) {
51 db = mdb; /* need to inc ref count */
52 jobids = get_pool_memory(PM_NAME);
53 prev_dir = get_pool_memory(PM_NAME);
54 pattern = get_pool_memory(PM_NAME);
55 filename = get_pool_memory(PM_NAME);
56 tmp = get_pool_memory(PM_NAME);
57 escaped_list = get_pool_memory(PM_NAME);
58 *filename = *jobids = *prev_dir = *pattern = 0;
59 dir_filenameid = pwd_id = offset = 0;
60 see_copies = see_all_versions = false;
63 list_entries = result_handler;
66 job_acl = client_acl = pool_acl = fileset_acl = NULL;
70 free_pool_memory(jobids);
71 free_pool_memory(pattern);
72 free_pool_memory(prev_dir);
73 free_pool_memory(filename);
74 free_pool_memory(tmp);
75 free_pool_memory(escaped_list);
83 char *Bvfs::escape_list(alist *lst)
88 /* List is empty, reject everything */
89 if (!lst || lst->size() == 0) {
90 Mmsg(escaped_list, "''");
97 foreach_alist(elt, lst) {
101 tmp = check_pool_memory_size(tmp, 2 * len + 2 + 2);
104 db->bdb_escape_string(jcr, tmp + 1 , elt, len);
108 pm_strcat(escaped_list, ",");
111 pm_strcat(escaped_list, tmp);
117 void Bvfs::filter_jobid()
123 /* No ACL, no username, no check */
124 if (!job_acl && !fileset_acl && !client_acl && !pool_acl && !username) {
125 Dmsg0(dbglevel_sql, "No ACL\n");
130 Mmsg(sub_where, " AND Job.Name IN (%s) ", escape_list(job_acl));
134 Mmsg(query, " AND FileSet.FileSet IN (%s) ", escape_list(fileset_acl));
135 pm_strcat(sub_where, query.c_str());
136 pm_strcat(sub_join, " JOIN FileSet USING (FileSetId) ");
140 Mmsg(query, " AND Client.Name IN (%s) ", escape_list(client_acl));
141 pm_strcat(sub_where, query.c_str());
145 Mmsg(query, " AND Pool.Name IN (%s) ", escape_list(pool_acl));
146 pm_strcat(sub_where, query.c_str());
147 pm_strcat(sub_join, " JOIN Pool USING (PoolId) ");
151 /* Query used by Bweb to filter clients, activated when using
155 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
156 "JOIN (SELECT ClientId FROM client_group_member "
157 "JOIN client_group USING (client_group_id) "
158 "JOIN bweb_client_group_acl USING (client_group_id) "
159 "JOIN bweb_user USING (userid) "
160 "WHERE bweb_user.username = '%s' "
161 ") AS filter USING (ClientId) "
162 " WHERE JobId IN (%s) %s",
163 sub_join.c_str(), username, jobids, sub_where.c_str());
167 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) %s "
168 " WHERE JobId IN (%s) %s",
169 sub_join.c_str(), jobids, sub_where.c_str());
173 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
174 db->bdb_sql_query(query.c_str(), db_list_handler, &ctx);
175 pm_strcpy(jobids, ctx.list);
178 void Bvfs::set_jobid(JobId_t id)
180 Mmsg(jobids, "%lld", (uint64_t)id);
184 void Bvfs::set_jobids(char *ids)
186 pm_strcpy(jobids, ids);
191 * TODO: Find a way to let the user choose how he wants to display
192 * files and directories
197 * Working Object to store PathId already seen (avoid
198 * database queries), equivalent to %cache_ppathid in perl
210 htable *cache_ppathid;
215 cache_ppathid = (htable *)malloc(sizeof(htable));
216 cache_ppathid->init(&link, &link, NITEMS);
218 nodes = (hlink *) malloc(max_node * sizeof (hlink));
220 table_node = New(alist(5, owned_by_alist));
221 table_node->append(nodes);
225 if (++nb_node >= max_node) {
227 nodes = (hlink *)malloc(max_node * sizeof(hlink));
228 table_node->append(nodes);
230 return nodes + nb_node;
233 bool lookup(char *pathid) {
234 bool ret = cache_ppathid->lookup(pathid) != NULL;
238 void insert(char *pathid) {
239 hlink *h = get_hlink();
240 cache_ppathid->insert(pathid, h);
244 cache_ppathid->destroy();
249 pathid_cache(const pathid_cache &); /* prohibit pass by value */
250 pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
253 /* Return the parent_dir with the trailing / (update the given string)
254 * TODO: see in the rest of bacula if we don't have already this function
260 char *bvfs_parent_dir(char *path)
263 int len = strlen(path) - 1;
265 /* windows directory / */
266 if (len == 2 && B_ISALPHA(path[0])
274 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
280 while (p > path && !IsPathSeparator(*p)) {
288 /* Return the basename of the with the trailing /
289 * TODO: see in the rest of bacula if we don't have
290 * this function already
292 char *bvfs_basename_dir(char *path)
295 int len = strlen(path) - 1;
297 if (path[len] == '/') { /* if directory, skip last / */
303 while (p > path && !IsPathSeparator(*p)) {
307 p++; /* skip first / */
313 static void build_path_hierarchy(JCR *jcr, BDB *mdb,
314 pathid_cache &ppathid_cache,
315 char *org_pathid, char *path)
317 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
320 char *bkp = mdb->path;
321 strncpy(pathid, org_pathid, sizeof(pathid));
323 /* Does the ppathid exist for this ? we use a memory cache... In order to
324 * avoid the full loop, we consider that if a dir is allready in the
325 * PathHierarchy table, then there is no need to calculate all the
328 while (path && *path)
330 if (!ppathid_cache.lookup(pathid))
333 "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
336 if (!mdb->QueryDB(jcr, mdb->cmd)) {
337 goto bail_out; /* Query failed, just leave */
340 /* Do we have a result ? */
341 if (mdb->sql_num_rows() > 0) {
342 ppathid_cache.insert(pathid);
343 /* This dir was in the db ...
344 * It means we can leave, the tree has allready been built for
349 /* search or create parent PathId in Path table */
350 mdb->path = bvfs_parent_dir(path);
351 mdb->pnl = strlen(mdb->path);
352 if (!mdb->bdb_create_path_record(jcr, &parent)) {
355 ppathid_cache.insert(pathid);
358 "INSERT INTO PathHierarchy (PathId, PPathId) "
360 pathid, (uint64_t) parent.PathId);
362 if (!mdb->InsertDB(jcr, mdb->cmd)) {
363 goto bail_out; /* Can't insert the record, just leave */
366 edit_uint64(parent.PathId, pathid);
367 path = mdb->path; /* already done */
370 /* It's already in the cache. We can leave, no time to waste here,
371 * all the parent dirs have allready been done
383 * Internal function to update path_hierarchy cache with a shared pathid cache
387 static int update_path_hierarchy_cache(JCR *jcr,
389 pathid_cache &ppathid_cache,
392 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
396 edit_uint64(JobId, jobid);
400 /* We don't really want to harm users with spurious messages,
401 * everything is handled by transaction
403 mdb->set_use_fatal_jmsg(false);
405 mdb->bdb_start_transaction(jcr);
407 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
409 if (!mdb->QueryDB(jcr, mdb->cmd) || mdb->sql_num_rows() > 0) {
410 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
415 /* Inserting path records for JobId */
416 Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
417 "SELECT DISTINCT PathId, JobId "
418 "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
420 "SELECT PathId, BaseFiles.JobId "
421 "FROM BaseFiles JOIN File AS F USING (FileId) "
422 "WHERE BaseFiles.JobId = %s) AS B",
425 if (!mdb->QueryDB(jcr, mdb->cmd)) {
426 Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId );
430 /* Now we have to do the directory recursion stuff to determine missing
431 * visibility We try to avoid recursion, to be as fast as possible We also
432 * only work on not allready hierarchised directories...
435 "SELECT PathVisibility.PathId, Path "
436 "FROM PathVisibility "
437 "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
438 "LEFT JOIN PathHierarchy "
439 "ON (PathVisibility.PathId = PathHierarchy.PathId) "
440 "WHERE PathVisibility.JobId = %s "
441 "AND PathHierarchy.PathId IS NULL "
442 "ORDER BY Path", jobid);
443 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
445 if (!mdb->QueryDB(jcr, mdb->cmd)) {
446 Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId );
450 /* TODO: I need to reuse the DB connection without emptying the result
451 * So, now i'm copying the result in memory to be able to query the
452 * catalog descriptor again.
454 num = mdb->sql_num_rows();
456 char **result = (char **)malloc (num * 2 * sizeof(char *));
460 while((row = mdb->sql_fetch_row())) {
461 result[i++] = bstrdup(row[0]);
462 result[i++] = bstrdup(row[1]);
467 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
476 if (mdb->bdb_get_type_index() == SQL_TYPE_SQLITE3) {
478 "INSERT INTO PathVisibility (PathId, JobId) "
479 "SELECT DISTINCT h.PPathId AS PathId, %s "
480 "FROM PathHierarchy AS h "
481 "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
482 "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
483 jobid, jobid, jobid );
485 } else if (mdb->bdb_get_type_index() == SQL_TYPE_MYSQL) {
487 "INSERT INTO PathVisibility (PathId, JobId) "
488 "SELECT a.PathId,%s "
490 "SELECT DISTINCT h.PPathId AS PathId "
491 "FROM PathHierarchy AS h "
492 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
493 "WHERE p.JobId=%s) AS a "
494 "LEFT JOIN PathVisibility AS b ON (b.JobId=%s and a.PathId = b.PathId) "
495 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
499 "INSERT INTO PathVisibility (PathId, JobId) "
500 "SELECT a.PathId,%s "
502 "SELECT DISTINCT h.PPathId AS PathId "
503 "FROM PathHierarchy AS h "
504 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
505 "WHERE p.JobId=%s) AS a LEFT JOIN "
507 "FROM PathVisibility "
508 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
509 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
513 ret = mdb->QueryDB(jcr, mdb->cmd);
514 } while (ret && mdb->sql_affected_rows() > 0);
516 Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
517 ret = mdb->UpdateDB(jcr, mdb->cmd);
520 mdb->bdb_end_transaction(jcr);
523 Mmsg(mdb->cmd, "SELECT HasCache FROM Job WHERE JobId=%s", jobid);
524 mdb->bdb_sql_query(mdb->cmd, db_int_handler, &ret);
527 /* Enable back the FATAL message if something is wrong */
528 mdb->set_use_fatal_jmsg(true);
535 * Find an store the filename descriptor for empty directories Filename.Name=''
537 DBId_t Bvfs::get_dir_filenameid()
540 if (dir_filenameid) {
541 return dir_filenameid;
543 Mmsg(db->cmd, "SELECT FilenameId FROM Filename WHERE Name = ''");
544 db_sql_query(db, db->cmd, db_int_handler, &id);
546 return dir_filenameid;
549 /* Compute the cache for the bfileview compoment */
550 void Bvfs::fv_update_cache()
553 int64_t size=0, count=0;
555 Dmsg0(dbglevel, "fv_update_cache()\n");
558 return; /* Nothing to build */
562 /* We don't really want to harm users with spurious messages,
563 * everything is handled by transaction
565 db->set_use_fatal_jmsg(false);
567 db->bdb_start_transaction(jcr);
571 fv_compute_size_and_count(pathid, &size, &count);
573 db->bdb_end_transaction(jcr);
575 /* Enable back the FATAL message if something is wrong */
576 db->set_use_fatal_jmsg(true);
581 /* Not yet working */
582 void Bvfs::fv_get_big_files(int64_t pathid, int64_t min_size, int32_t limit)
585 "SELECT FilenameId AS filenameid, Name AS name, size "
587 "SELECT FilenameId, base64_decode_lstat(8,LStat) AS size "
589 "WHERE PathId = %lld "
591 ") AS S INNER JOIN Filename USING (FilenameId) "
592 "WHERE S.size > %lld "
593 "ORDER BY S.size DESC "
594 "LIMIT %d ", pathid, jobids, min_size, limit);
597 /* Get the current path size and files count */
598 void Bvfs::fv_get_current_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
605 "SELECT Size AS size, Files AS files "
606 " FROM PathVisibility "
607 " WHERE PathId = %lld "
608 " AND JobId = %s ", pathid, jobids);
610 if (!db->QueryDB(jcr, db->cmd)) {
614 if ((row = db->sql_fetch_row())) {
615 *size = str_to_int64(row[0]);
616 *count = str_to_int64(row[1]);
620 /* Compute for the current path the size and files count */
621 void Bvfs::fv_get_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
628 "SELECT sum(base64_decode_lstat(8,LStat)) AS size, count(1) AS files "
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 void Bvfs::fv_compute_size_and_count(int64_t pathid, int64_t *size, int64_t *count)
645 Dmsg1(dbglevel, "fv_compute_size_and_count(%lld)\n", pathid);
647 fv_get_current_size_and_count(pathid, size, count);
652 /* Update stats for the current directory */
653 fv_get_size_and_count(pathid, size, count);
655 /* Update stats for all sub directories */
658 " FROM PathVisibility "
659 " INNER JOIN PathHierarchy USING (PathId) "
660 " WHERE PPathId = %lld "
661 " AND JobId = %s ", pathid, jobids);
663 db->QueryDB(jcr, db->cmd);
664 int num = db->sql_num_rows();
667 int64_t *result = (int64_t *)malloc (num * sizeof(int64_t));
671 while((row = db->sql_fetch_row())) {
672 result[i++] = str_to_int64(row[0]); /* PathId */
678 fv_compute_size_and_count(result[i], &s, &c);
688 fv_update_size_and_count(pathid, *size, *count);
691 void Bvfs::fv_update_size_and_count(int64_t pathid, int64_t size, int64_t count)
694 "UPDATE PathVisibility SET Files = %lld, Size = %lld "
696 " AND PathId = %lld ", count, size, jobids, pathid);
698 db->UpdateDB(jcr, db->cmd);
701 void bvfs_update_cache(JCR *jcr, BDB *mdb)
704 db_list_ctx jobids_list;
709 /* TODO: Remove this code when updating make_bacula_table script */
710 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
711 if (!mdb->QueryDB(jcr, mdb->cmd)) {
712 Dmsg0(dbglevel, "Creating cache table\n");
713 Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
714 mdb->QueryDB(jcr, mdb->cmd);
717 "CREATE TABLE PathHierarchy ( "
718 "PathId integer NOT NULL, "
719 "PPathId integer NOT NULL, "
720 "CONSTRAINT pathhierarchy_pkey "
721 "PRIMARY KEY (PathId))");
722 mdb->QueryDB(jcr, mdb->cmd);
725 "CREATE INDEX pathhierarchy_ppathid "
726 "ON PathHierarchy (PPathId)");
727 mdb->QueryDB(jcr, mdb->cmd);
730 "CREATE TABLE PathVisibility ("
731 "PathId integer NOT NULL, "
732 "JobId integer NOT NULL, "
733 "Size int8 DEFAULT 0, "
734 "Files int4 DEFAULT 0, "
735 "CONSTRAINT pathvisibility_pkey "
736 "PRIMARY KEY (JobId, PathId))");
737 mdb->QueryDB(jcr, mdb->cmd);
740 "CREATE INDEX pathvisibility_jobid "
741 "ON PathVisibility (JobId)");
742 mdb->QueryDB(jcr, mdb->cmd);
748 "SELECT JobId from Job "
749 "WHERE HasCache = 0 "
750 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
753 mdb->bdb_sql_query(mdb->cmd, db_list_handler, &jobids_list);
755 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
757 mdb->bdb_start_transaction(jcr);
758 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
760 "DELETE FROM PathVisibility "
762 "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
763 nb = mdb->DeleteDB(jcr, mdb->cmd);
764 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
766 mdb->bdb_end_transaction(jcr);
771 * Update the bvfs cache for given jobids (1,2,3,4)
774 bvfs_update_path_hierarchy_cache(JCR *jcr, BDB *mdb, char *jobids)
776 pathid_cache ppathid_cache;
782 int stat = get_next_jobid_from_list(&p, &JobId);
790 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
791 if (!update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId)) {
799 * Update the bvfs fileview for given jobids
802 bvfs_update_fv_cache(JCR *jcr, BDB *mdb, char *jobids)
809 int stat = get_next_jobid_from_list(&p, &JobId);
817 Dmsg1(dbglevel, "Trying to create cache for %lld\n", (int64_t)JobId);
819 bvfs.set_jobid(JobId);
820 bvfs.fv_update_cache();
825 * Update the bvfs cache for current jobids
827 void Bvfs::update_cache()
829 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
832 /* Change the current directory, returns true if the path exists */
833 bool Bvfs::ch_dir(const char *path)
835 pm_strcpy(db->path, path);
836 db->pnl = strlen(db->path);
838 ch_dir(db->bdb_get_path_record(jcr));
844 * Get all file versions for a specified client
845 * TODO: Handle basejobs using different client
847 void Bvfs::get_all_file_versions(DBId_t pathid, FileId_t fnid, const char *client)
849 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
850 (uint64_t)fnid, client);
851 char ed1[50], ed2[50];
854 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
856 Mmsg(q, " AND Job.Type = 'B' ");
862 "SELECT 'V', File.PathId, File.FilenameId, File.Md5, "
864 "File.JobId, File.LStat, File.FileId, "
866 "Media.VolumeName, Media.InChanger "
867 "FROM File, Job, Client, JobMedia, Media "
868 "WHERE File.FilenameId = %s "
869 "AND File.PathId=%s "
870 "AND File.JobId = Job.JobId "
871 "AND Job.JobId = JobMedia.JobId "
872 "AND File.FileIndex >= JobMedia.FirstIndex "
873 "AND File.FileIndex <= JobMedia.LastIndex "
874 "AND JobMedia.MediaId = Media.MediaId "
875 "AND Job.ClientId = Client.ClientId "
876 "AND Client.Name = '%s' "
877 "%s ORDER BY FileId LIMIT %d OFFSET %d"
878 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
880 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
881 db->bdb_sql_query(query.c_str(), list_entries, user_data);
885 * Get all volumes for a specific file
887 void Bvfs::get_volumes(FileId_t fileid)
889 Dmsg1(dbglevel, "get_volumes(%lld)\n", (uint64_t)fileid);
896 "SELECT DISTINCT 'L',0,0,0,0,0,0, Media.VolumeName, Media.InChanger "
897 "FROM File JOIN JobMedia USING (JobId) JOIN Media USING (MediaId) "
898 "WHERE File.FileId = %s "
899 "AND File.FileIndex >= JobMedia.FirstIndex "
900 "AND File.FileIndex <= JobMedia.LastIndex "
901 " LIMIT %d OFFSET %d"
902 ,edit_uint64(fileid, ed1), limit, offset);
903 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
904 db->bdb_sql_query(query.c_str(), list_entries, user_data);
907 DBId_t Bvfs::get_root()
912 p = db->bdb_get_path_record(jcr);
917 static int path_handler(void *ctx, int fields, char **row)
919 Bvfs *fs = (Bvfs *) ctx;
920 return fs->_handle_path(ctx, fields, row);
923 int Bvfs::_handle_path(void *ctx, int fields, char **row)
925 if (bvfs_is_dir(row)) {
926 /* can have the same path 2 times */
927 if (strcmp(row[BVFS_PathId], prev_dir)) {
928 pm_strcpy(prev_dir, row[BVFS_PathId]);
929 return list_entries(user_data, fields, row);
936 * Retrieve . and .. information
938 void Bvfs::ls_special_dirs()
940 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
941 char ed1[50], ed2[50];
945 if (!dir_filenameid) {
946 get_dir_filenameid();
949 /* Will fetch directories */
954 "(SELECT PPathId AS PathId, '..' AS Path "
955 "FROM PathHierarchy "
958 "SELECT %s AS PathId, '.' AS Path)",
959 edit_uint64(pwd_id, ed1), ed1);
962 Mmsg(query2,// 1 2 3 4 5 6
963 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
964 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
965 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
966 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
967 "WHERE File1.FilenameId = %s "
968 "AND File1.JobId IN (%s)) AS listfile1 "
969 "ON (tmp.PathId = listfile1.PathId) "
970 "ORDER BY tmp.Path, JobId DESC ",
971 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
973 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
974 db->bdb_sql_query(query2.c_str(), path_handler, this);
977 /* Returns true if we have dirs to read */
980 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
981 char ed1[50], ed2[50];
989 Mmsg(filter, " AND Path2.Path %s '%s' ",
990 match_query[db->bdb_get_type_index()], pattern);
994 if (!dir_filenameid) {
995 get_dir_filenameid();
998 /* the sql query displays same directory multiple time, take the first one */
1001 /* Let's retrieve the list of the visible dirs in this dir ...
1002 * First, I need the empty filenameid to locate efficiently
1003 * the dirs in the file table
1004 * my $dir_filenameid = $self->get_dir_filenameid();
1006 /* Then we get all the dir entries from File ... */
1009 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
1010 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
1011 "lower(Path1.Path) AS lpath, "
1012 "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
1013 "listfile1.FileId AS FileId "
1015 "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
1016 "FROM PathHierarchy AS PathHierarchy1 "
1017 "JOIN Path AS Path2 "
1018 "ON (PathHierarchy1.PathId = Path2.PathId) "
1019 "JOIN PathVisibility AS PathVisibility1 "
1020 "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
1021 "WHERE PathHierarchy1.PPathId = %s "
1022 "AND PathVisibility1.JobId IN (%s) "
1025 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
1027 "LEFT JOIN ( " /* get attributes if any */
1028 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
1029 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
1030 "WHERE File1.FilenameId = %s "
1031 "AND File1.JobId IN (%s)) AS listfile1 "
1032 "ON (listpath1.PathId = listfile1.PathId) "
1033 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
1034 edit_uint64(pwd_id, ed1),
1037 edit_uint64(dir_filenameid, ed2),
1041 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1044 db->bdb_sql_query(query.c_str(), path_handler, this);
1045 nb_record = db->sql_num_rows();
1048 return nb_record == limit;
1051 void build_ls_files_query(BDB *db, POOL_MEM &query,
1052 const char *JobId, const char *PathId,
1053 const char *filter, int64_t limit, int64_t offset)
1055 if (db->bdb_get_type_index() == SQL_TYPE_POSTGRESQL) {
1056 Mmsg(query, sql_bvfs_list_files[db->bdb_get_type_index()],
1057 JobId, PathId, JobId, PathId,
1058 filter, limit, offset);
1060 Mmsg(query, sql_bvfs_list_files[db->bdb_get_type_index()],
1061 JobId, PathId, JobId, PathId,
1062 limit, offset, filter, JobId, JobId);
1066 /* Returns true if we have files to read */
1067 bool Bvfs::ls_files()
1073 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
1082 edit_uint64(pwd_id, pathid);
1084 Mmsg(filter, " AND Filename.Name %s '%s' ",
1085 match_query[db_get_type_index(db)], pattern);
1087 } else if (*filename) {
1088 Mmsg(filter, " AND Filename.Name = '%s' ", filename);
1091 build_ls_files_query(db, query,
1092 jobids, pathid, filter.c_str(),
1095 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1098 db->bdb_sql_query(query.c_str(), list_entries, user_data);
1099 nb_record = db->sql_num_rows();
1102 return nb_record == limit;
1107 * Return next Id from comma separated list
1110 * 1 if next Id returned
1111 * 0 if no more Ids are in list
1112 * -1 there is an error
1113 * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
1115 static int get_next_id_from_list(char **p, int64_t *Id)
1117 const int maxlen = 30;
1122 for (int i=0; i<maxlen; i++) {
1125 } else if (*q == ',') {
1134 } else if (!is_a_number(id)) {
1135 return -1; /* error */
1138 *Id = str_to_int64(id);
1142 static int get_path_handler(void *ctx, int fields, char **row)
1144 POOL_MEM *buf = (POOL_MEM *) ctx;
1145 pm_strcpy(*buf, row[0]);
1149 static bool check_temp(char *output_table)
1151 if (output_table[0] == 'b' &&
1152 output_table[1] == '2' &&
1153 is_an_integer(output_table + 2))
1160 void Bvfs::clear_cache()
1162 db->bdb_sql_query("BEGIN", NULL, NULL);
1163 db->bdb_sql_query("UPDATE Job SET HasCache=0", NULL, NULL);
1164 db->bdb_sql_query("TRUNCATE PathHierarchy", NULL, NULL);
1165 db->bdb_sql_query("TRUNCATE PathVisibility", NULL, NULL);
1166 db->bdb_sql_query("COMMIT", NULL, NULL);
1169 bool Bvfs::drop_restore_list(char *output_table)
1172 if (check_temp(output_table)) {
1173 Mmsg(query, "DROP TABLE %s", output_table);
1174 db->bdb_sql_query(query.c_str(), NULL, NULL);
1180 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
1185 int64_t id, jobid, prev_jobid;
1190 if ((*fileid && !is_a_number_list(fileid)) ||
1191 (*dirid && !is_a_number_list(dirid)) ||
1192 (*hardlink && !is_a_number_list(hardlink))||
1193 (!*hardlink && !*fileid && !*dirid && !*hardlink))
1197 if (!check_temp(output_table)) {
1203 /* Cleanup old tables first */
1204 Mmsg(query, "DROP TABLE btemp%s", output_table);
1205 db->bdb_sql_query(query.c_str());
1207 Mmsg(query, "DROP TABLE %s", output_table);
1208 db->bdb_sql_query(query.c_str());
1210 Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
1212 if (*fileid) { /* Select files with their direct id */
1214 Mmsg(tmp,"SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1216 "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
1218 pm_strcat(query, tmp.c_str());
1221 /* Add a directory content */
1222 while (get_next_id_from_list(&dirid, &id) == 1) {
1223 Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
1225 if (!db->bdb_sql_query(tmp.c_str(), get_path_handler, (void *)&tmp2)) {
1226 Dmsg0(dbglevel, "Can't search for path\n");
1230 if (!strcmp(tmp2.c_str(), "")) { /* path not found */
1231 Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
1232 id, tmp.c_str(), tmp2.c_str());
1235 /* escape % and _ for LIKE search */
1236 tmp.check_size((strlen(tmp2.c_str())+1) * 2);
1237 char *p = tmp.c_str();
1238 for (char *s = tmp2.c_str(); *s ; s++) {
1239 if (*s == '%' || *s == '_' || *s == '\\') {
1249 size_t len = strlen(tmp.c_str());
1250 tmp2.check_size((len+1) * 2);
1251 db->bdb_escape_string(jcr, tmp2.c_str(), tmp.c_str(), len);
1254 query.strcat(" UNION ");
1257 Mmsg(tmp, "SELECT Job.JobId, JobTDate, File.FileIndex, File.FilenameId, "
1258 "File.PathId, FileId "
1259 "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
1260 "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
1261 tmp2.c_str(), jobids);
1262 query.strcat(tmp.c_str());
1265 query.strcat(" UNION ");
1267 /* A directory can have files from a BaseJob */
1268 Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
1269 "File.FilenameId, File.PathId, BaseFiles.FileId "
1271 "JOIN File USING (FileId) "
1272 "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
1273 "JOIN Path USING (PathId) "
1274 "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
1275 tmp2.c_str(), jobids);
1276 query.strcat(tmp.c_str());
1279 /* expect jobid,fileindex */
1281 while (get_next_id_from_list(&hardlink, &jobid) == 1) {
1282 if (get_next_id_from_list(&hardlink, &id) != 1) {
1283 Dmsg0(dbglevel, "hardlink should be two by two\n");
1286 if (jobid != prev_jobid) { /* new job */
1287 if (prev_jobid == 0) { /* first jobid */
1289 query.strcat(" UNION ");
1291 } else { /* end last job, start new one */
1292 tmp.strcat(") UNION ");
1293 query.strcat(tmp.c_str());
1295 Mmsg(tmp, "SELECT Job.JobId, JobTDate, FileIndex, FilenameId, "
1297 "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
1298 "AND FileIndex IN (%lld", jobid, id);
1301 } else { /* same job, add new findex */
1302 Mmsg(tmp2, ", %lld", id);
1303 tmp.strcat(tmp2.c_str());
1307 if (prev_jobid != 0) { /* end last job */
1309 query.strcat(tmp.c_str());
1313 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1315 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1316 Dmsg1(dbglevel, "Can't execute query=%s\n", query.c_str());
1320 Mmsg(query, sql_bvfs_select[db->bdb_get_type_index()],
1321 output_table, output_table, output_table);
1323 /* TODO: handle jobid filter */
1324 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1325 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1326 Dmsg1(dbglevel, "Can't execute query=%s\n", query.c_str());
1330 /* MySQL needs the index */
1331 if (db->bdb_get_type_index() == SQL_TYPE_MYSQL) {
1332 Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)",
1333 output_table, output_table);
1334 Dmsg1(dbglevel_sql, "query=%s\n", query.c_str());
1335 if (!db->bdb_sql_query(query.c_str(), NULL, NULL)) {
1336 Dmsg1(dbglevel, "Can't execute query=%s\n", query.c_str());
1341 /* Check if some FileId have DeltaSeq > 0
1342 * Foreach of them we need to get the accurate_job list, and compute
1343 * what are dependencies
1346 "SELECT F.FileId, F.JobId, F.FilenameId, F.PathId, F.DeltaSeq "
1347 "FROM File AS F JOIN Job USING (JobId) JOIN %s USING (FileId) "
1348 "WHERE DeltaSeq > 0", output_table);
1350 if (!db->QueryDB(jcr, query.c_str())) {
1351 Dmsg1(dbglevel_sql, "Can't execute query=%s\n", query.c_str());
1354 /* TODO: Use an other DB connection can avoid to copy the result of the
1355 * previous query into a temporary buffer
1357 num = db->sql_num_rows();
1358 Dmsg2(dbglevel, "Found %d Delta parts in restore selection q=%s\n", num, query.c_str());
1361 int64_t *result = (int64_t *)malloc (num * 4 * sizeof(int64_t));
1365 while((row = db->sql_fetch_row())) {
1366 result[i++] = str_to_int64(row[0]); /* FileId */
1367 result[i++] = str_to_int64(row[1]); /* JobId */
1368 result[i++] = str_to_int64(row[2]); /* FilenameId */
1369 result[i++] = str_to_int64(row[3]); /* PathId */
1374 insert_missing_delta(output_table, result + i);
1384 Mmsg(query, "DROP TABLE btemp%s", output_table);
1385 db->bdb_sql_query(query.c_str(), NULL, NULL);
1390 void Bvfs::insert_missing_delta(char *output_table, int64_t *res)
1392 char ed1[50], ed2[50];
1396 memset(&jr, 0, sizeof(jr));
1397 memset(&jr2, 0, sizeof(jr2));
1399 /* Need to limit the query to StartTime, Client/Fileset */
1401 db->bdb_get_job_record(jcr, &jr2);
1404 jr.ClientId = jr2.ClientId;
1405 jr.FileSetId = jr2.FileSetId;
1406 jr.JobLevel = L_INCREMENTAL;
1407 jr.StartTime = jr2.StartTime;
1409 /* Get accurate jobid list */
1410 db->bdb_get_accurate_jobids(jcr, &jr, &lst);
1412 Dmsg2(dbglevel_sql, "JobId list for %lld is %s\n", res[0], lst.list);
1414 /* The list contains already the last DeltaSeq element, so
1415 * we don't need to select it in the next query
1417 for (int l = strlen(lst.list); l > 0; l--) {
1418 if (lst.list[l] == ',') {
1424 Dmsg1(dbglevel_sql, "JobId list after strip is %s\n", lst.list);
1426 edit_int64(res[2], ed1); /* fnid */
1427 edit_int64(res[3], ed2); /* pathid */
1429 int id=db->bdb_get_type_index();
1430 Mmsg(query, bvfs_select_delta_version_with_basejob_and_delta[id],
1433 lst.list, lst.list);
1435 Mmsg(db->cmd, "INSERT INTO %s "
1436 "SELECT JobId, FileIndex, FileId FROM (%s) AS F1",
1437 output_table, query.c_str());
1439 if (!db->bdb_sql_query(db->cmd, NULL, NULL)) {
1440 Dmsg1(dbglevel_sql, "Can't exec q=%s\n", db->cmd);
1444 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */