2 Bacula® - The Network Backup Solution
4 Copyright (C) 2009-2010 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation, which is
11 listed in the file LICENSE.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
31 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI
36 #include "lib/htable.h"
40 #define dbglevel_sql 15
42 static int result_handler(void *ctx, int fields, char **row)
45 Pmsg4(0, "%s\t%s\t%s\t%s\n",
46 row[0], row[1], row[2], row[3]);
47 } else if (fields == 5) {
48 Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
49 row[0], row[1], row[2], row[3], row[4]);
50 } else if (fields == 6) {
51 Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
52 row[0], row[1], row[2], row[3], row[4], row[5]);
53 } else if (fields == 7) {
54 Pmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
55 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
60 Bvfs::Bvfs(JCR *j, B_DB *mdb) {
63 db = mdb; /* need to inc ref count */
64 jobids = get_pool_memory(PM_NAME);
65 prev_dir = get_pool_memory(PM_NAME);
66 pattern = get_pool_memory(PM_NAME);
67 *jobids = *prev_dir = *pattern = 0;
68 dir_filenameid = pwd_id = offset = 0;
69 see_copies = see_all_versions = false;
72 list_entries = result_handler;
78 free_pool_memory(jobids);
79 free_pool_memory(pattern);
80 free_pool_memory(prev_dir);
88 void Bvfs::filter_jobid()
94 /* Query used by Bweb to filter clients, activated when using
99 "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) "
100 "JOIN (SELECT ClientId FROM client_group_member "
101 "JOIN client_group USING (client_group_id) "
102 "JOIN bweb_client_group_acl USING (client_group_id) "
103 "JOIN bweb_user USING (userid) "
104 "WHERE bweb_user.username = '%s' "
105 ") AS filter USING (ClientId) "
106 " WHERE JobId IN (%s)",
110 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
111 db_sql_query(db, query.c_str(), db_list_handler, &ctx);
112 pm_strcpy(jobids, ctx.list);
115 void Bvfs::set_jobid(JobId_t id)
117 Mmsg(jobids, "%lld", (uint64_t)id);
121 void Bvfs::set_jobids(char *ids)
123 pm_strcpy(jobids, ids);
128 * TODO: Find a way to let the user choose how he wants to display
129 * files and directories
134 * Working Object to store PathId already seen (avoid
135 * database queries), equivalent to %cache_ppathid in perl
147 htable *cache_ppathid;
152 cache_ppathid = (htable *)malloc(sizeof(htable));
153 cache_ppathid->init(&link, &link, NITEMS);
155 nodes = (hlink *) malloc(max_node * sizeof (hlink));
157 table_node = New(alist(5, owned_by_alist));
158 table_node->append(nodes);
162 if (++nb_node >= max_node) {
164 nodes = (hlink *)malloc(max_node * sizeof(hlink));
165 table_node->append(nodes);
167 return nodes + nb_node;
170 bool lookup(char *pathid) {
171 bool ret = cache_ppathid->lookup(pathid) != NULL;
175 void insert(char *pathid) {
176 hlink *h = get_hlink();
177 cache_ppathid->insert(pathid, h);
181 cache_ppathid->destroy();
186 pathid_cache(const pathid_cache &); /* prohibit pass by value */
187 pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
190 /* Return the parent_dir with the trailing / (update the given string)
191 * TODO: see in the rest of bacula if we don't have already this function
197 char *bvfs_parent_dir(char *path)
200 int len = strlen(path) - 1;
202 /* windows directory / */
203 if (len == 2 && B_ISALPHA(path[0])
211 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
217 while (p > path && !IsPathSeparator(*p)) {
225 /* Return the basename of the with the trailing /
226 * TODO: see in the rest of bacula if we don't have
227 * this function already
229 char *bvfs_basename_dir(char *path)
232 int len = strlen(path) - 1;
234 if (path[len] == '/') { /* if directory, skip last / */
240 while (p > path && !IsPathSeparator(*p)) {
244 p++; /* skip first / */
250 static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
251 pathid_cache &ppathid_cache,
252 char *org_pathid, char *path)
254 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
257 char *bkp = mdb->path;
258 strncpy(pathid, org_pathid, sizeof(pathid));
260 /* Does the ppathid exist for this ? we use a memory cache... In order to
261 * avoid the full loop, we consider that if a dir is allready in the
262 * PathHierarchy table, then there is no need to calculate all the
265 while (path && *path)
267 if (!ppathid_cache.lookup(pathid))
270 "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
273 QUERY_DB(jcr, mdb, mdb->cmd);
274 /* Do we have a result ? */
275 if (sql_num_rows(mdb) > 0) {
276 ppathid_cache.insert(pathid);
277 /* This dir was in the db ...
278 * It means we can leave, the tree has allready been built for
283 /* search or create parent PathId in Path table */
284 mdb->path = bvfs_parent_dir(path);
285 mdb->pnl = strlen(mdb->path);
286 if (!db_create_path_record(jcr, mdb, &parent)) {
289 ppathid_cache.insert(pathid);
292 "INSERT INTO PathHierarchy (PathId, PPathId) "
294 pathid, (uint64_t) parent.PathId);
296 INSERT_DB(jcr, mdb, mdb->cmd);
298 edit_uint64(parent.PathId, pathid);
299 path = mdb->path; /* already done */
302 /* It's already in the cache. We can leave, no time to waste here,
303 * all the parent dirs have allready been done
315 * Internal function to update path_hierarchy cache with a shared pathid cache
317 static void update_path_hierarchy_cache(JCR *jcr,
319 pathid_cache &ppathid_cache,
322 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
326 edit_uint64(JobId, jobid);
329 db_start_transaction(jcr, mdb);
331 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
333 if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
334 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
338 /* Inserting path records for JobId */
339 Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
340 "SELECT DISTINCT PathId, JobId "
341 "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
343 "SELECT PathId, BaseFiles.JobId "
344 "FROM BaseFiles JOIN File AS F USING (FileId) "
345 "WHERE BaseFiles.JobId = %s) AS B",
347 QUERY_DB(jcr, mdb, mdb->cmd);
349 /* Now we have to do the directory recursion stuff to determine missing
350 * visibility We try to avoid recursion, to be as fast as possible We also
351 * only work on not allready hierarchised directories...
354 "SELECT PathVisibility.PathId, Path "
355 "FROM PathVisibility "
356 "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
357 "LEFT JOIN PathHierarchy "
358 "ON (PathVisibility.PathId = PathHierarchy.PathId) "
359 "WHERE PathVisibility.JobId = %s "
360 "AND PathHierarchy.PathId IS NULL "
361 "ORDER BY Path", jobid);
362 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
363 QUERY_DB(jcr, mdb, mdb->cmd);
365 /* TODO: I need to reuse the DB connection without emptying the result
366 * So, now i'm copying the result in memory to be able to query the
367 * catalog descriptor again.
369 num = sql_num_rows(mdb);
371 char **result = (char **)malloc (num * 2 * sizeof(char *));
375 while((row = sql_fetch_row(mdb))) {
376 result[i++] = bstrdup(row[0]);
377 result[i++] = bstrdup(row[1]);
382 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
390 if (mdb->db_get_type_index() == SQL_TYPE_SQLITE3) {
392 "INSERT INTO PathVisibility (PathId, JobId) "
393 "SELECT DISTINCT h.PPathId AS PathId, %s "
394 "FROM PathHierarchy AS h "
395 "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
396 "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
397 jobid, jobid, jobid );
401 "INSERT INTO PathVisibility (PathId, JobId) "
402 "SELECT a.PathId,%s "
404 "SELECT DISTINCT h.PPathId AS PathId "
405 "FROM PathHierarchy AS h "
406 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
407 "WHERE p.JobId=%s) AS a LEFT JOIN "
409 "FROM PathVisibility "
410 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
411 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
415 QUERY_DB(jcr, mdb, mdb->cmd);
416 } while (sql_affected_rows(mdb) > 0);
418 Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
419 UPDATE_DB(jcr, mdb, mdb->cmd);
422 db_end_transaction(jcr, mdb);
427 * Find an store the filename descriptor for empty directories Filename.Name=''
429 DBId_t Bvfs::get_dir_filenameid()
432 if (dir_filenameid) {
433 return dir_filenameid;
436 Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
437 db_sql_query(db, q.c_str(), db_int_handler, &id);
439 return dir_filenameid;
442 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
445 db_list_ctx jobids_list;
450 /* TODO: Remove this code when updating make_bacula_table script */
451 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
452 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
453 Dmsg0(dbglevel, "Creating cache table\n");
454 Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
455 QUERY_DB(jcr, mdb, mdb->cmd);
458 "CREATE TABLE PathHierarchy ( "
459 "PathId integer NOT NULL, "
460 "PPathId integer NOT NULL, "
461 "CONSTRAINT pathhierarchy_pkey "
462 "PRIMARY KEY (PathId))");
463 QUERY_DB(jcr, mdb, mdb->cmd);
466 "CREATE INDEX pathhierarchy_ppathid "
467 "ON PathHierarchy (PPathId)");
468 QUERY_DB(jcr, mdb, mdb->cmd);
471 "CREATE TABLE PathVisibility ("
472 "PathId integer NOT NULL, "
473 "JobId integer NOT NULL, "
474 "Size int8 DEFAULT 0, "
475 "Files int4 DEFAULT 0, "
476 "CONSTRAINT pathvisibility_pkey "
477 "PRIMARY KEY (JobId, PathId))");
478 QUERY_DB(jcr, mdb, mdb->cmd);
481 "CREATE INDEX pathvisibility_jobid "
482 "ON PathVisibility (JobId)");
483 QUERY_DB(jcr, mdb, mdb->cmd);
489 "SELECT JobId from Job "
490 "WHERE HasCache = 0 "
491 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
494 db_sql_query(mdb, mdb->cmd, db_list_handler, &jobids_list);
496 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
498 db_start_transaction(jcr, mdb);
499 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
501 "DELETE FROM PathVisibility "
503 "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
504 nb = DELETE_DB(jcr, mdb, mdb->cmd);
505 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
507 db_end_transaction(jcr, mdb);
512 * Update the bvfs cache for given jobids (1,2,3,4)
515 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
517 pathid_cache ppathid_cache;
522 int stat = get_next_jobid_from_list(&p, &JobId);
529 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
530 update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
535 * Update the bvfs cache for current jobids
537 void Bvfs::update_cache()
539 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
542 /* Change the current directory, returns true if the path exists */
543 bool Bvfs::ch_dir(const char *path)
545 pm_strcpy(db->path, path);
546 db->pnl = strlen(db->path);
547 ch_dir(db_get_path_record(jcr, db));
552 * Get all file versions for a specified client
553 * TODO: Handle basejobs using different client
555 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
557 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
558 (uint64_t)fnid, client);
559 char ed1[50], ed2[50];
562 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
564 Mmsg(q, " AND Job.Type = 'B' ");
570 "SELECT 'V', File.PathId, File.FilenameId, File.Md5, "
572 "File.JobId, File.LStat, File.FileId, "
574 "Media.VolumeName, Media.InChanger "
575 "FROM File, Job, Client, JobMedia, Media "
576 "WHERE File.FilenameId = %s "
577 "AND File.PathId=%s "
578 "AND File.JobId = Job.JobId "
579 "AND Job.JobId = JobMedia.JobId "
580 "AND File.FileIndex >= JobMedia.FirstIndex "
581 "AND File.FileIndex <= JobMedia.LastIndex "
582 "AND JobMedia.MediaId = Media.MediaId "
583 "AND Job.ClientId = Client.ClientId "
584 "AND Client.Name = '%s' "
585 "%s ORDER BY FileId LIMIT %d OFFSET %d"
586 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
588 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
589 db_sql_query(db, query.c_str(), list_entries, user_data);
592 DBId_t Bvfs::get_root()
595 return db_get_path_record(jcr, db);
598 static int path_handler(void *ctx, int fields, char **row)
600 Bvfs *fs = (Bvfs *) ctx;
601 return fs->_handle_path(ctx, fields, row);
604 int Bvfs::_handle_path(void *ctx, int fields, char **row)
606 if (bvfs_is_dir(row)) {
607 /* can have the same path 2 times */
608 if (strcmp(row[BVFS_Name], prev_dir)) {
609 pm_strcpy(prev_dir, row[BVFS_Name]);
610 return list_entries(user_data, fields, row);
617 * Retrieve . and .. information
619 void Bvfs::ls_special_dirs()
621 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
622 char ed1[50], ed2[50];
626 if (!dir_filenameid) {
627 get_dir_filenameid();
630 /* Will fetch directories */
635 "(SELECT PPathId AS PathId, '..' AS Path "
636 "FROM PathHierarchy "
639 "SELECT %s AS PathId, '.' AS Path)",
640 edit_uint64(pwd_id, ed1), ed1);
643 Mmsg(query2,// 1 2 3 4 5 6
644 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
645 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
646 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
647 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
648 "WHERE File1.FilenameId = %s "
649 "AND File1.JobId IN (%s)) AS listfile1 "
650 "ON (tmp.PathId = listfile1.PathId) "
651 "ORDER BY tmp.Path, JobId DESC ",
652 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
654 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
655 db_sql_query(db, query2.c_str(), path_handler, this);
658 /* Returns true if we have dirs to read */
661 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
662 char ed1[50], ed2[50];
670 Mmsg(filter, " AND Path2.Path %s '%s' ",
671 match_query[db_get_type_index(db)], pattern);
674 if (!dir_filenameid) {
675 get_dir_filenameid();
678 /* the sql query displays same directory multiple time, take the first one */
681 /* Let's retrieve the list of the visible dirs in this dir ...
682 * First, I need the empty filenameid to locate efficiently
683 * the dirs in the file table
684 * my $dir_filenameid = $self->get_dir_filenameid();
686 /* Then we get all the dir entries from File ... */
689 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
690 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
691 "lower(Path1.Path) AS lpath, "
692 "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
693 "listfile1.FileId AS FileId "
695 "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
696 "FROM PathHierarchy AS PathHierarchy1 "
697 "JOIN Path AS Path2 "
698 "ON (PathHierarchy1.PathId = Path2.PathId) "
699 "JOIN PathVisibility AS PathVisibility1 "
700 "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
701 "WHERE PathHierarchy1.PPathId = %s "
702 "AND PathVisibility1.JobId IN (%s) "
705 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
707 "LEFT JOIN ( " /* get attributes if any */
708 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
709 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
710 "WHERE File1.FilenameId = %s "
711 "AND File1.JobId IN (%s)) AS listfile1 "
712 "ON (listpath1.PathId = listfile1.PathId) "
713 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
714 edit_uint64(pwd_id, ed1),
717 edit_uint64(dir_filenameid, ed2),
721 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
724 db_sql_query(db, query.c_str(), path_handler, this);
725 nb_record = sql_num_rows(db);
728 return nb_record == limit;
731 void build_ls_files_query(B_DB *db, POOL_MEM &query,
732 const char *JobId, const char *PathId,
733 const char *filter, int64_t limit, int64_t offset)
735 if (db_get_type_index(db) == SQL_TYPE_POSTGRESQL) {
736 Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
737 JobId, PathId, JobId, PathId,
738 filter, limit, offset);
740 Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
741 JobId, PathId, JobId, PathId,
742 limit, offset, filter, JobId, JobId);
746 /* Returns true if we have files to read */
747 bool Bvfs::ls_files()
753 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
762 edit_uint64(pwd_id, pathid);
764 Mmsg(filter, " AND Filename.Name %s '%s' ",
765 match_query[db_get_type_index(db)], pattern);
768 build_ls_files_query(db, query,
769 jobids, pathid, filter.c_str(),
772 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
775 db_sql_query(db, query.c_str(), list_entries, user_data);
776 nb_record = sql_num_rows(db);
779 return nb_record == limit;
784 * Return next Id from comma separated list
787 * 1 if next Id returned
788 * 0 if no more Ids are in list
789 * -1 there is an error
790 * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
792 static int get_next_id_from_list(char **p, int64_t *Id)
794 const int maxlen = 30;
799 for (int i=0; i<maxlen; i++) {
802 } else if (*q == ',') {
811 } else if (!is_a_number(id)) {
812 return -1; /* error */
815 *Id = str_to_int64(id);
819 static int get_path_handler(void *ctx, int fields, char **row)
821 POOL_MEM *buf = (POOL_MEM *) ctx;
822 pm_strcpy(*buf, row[0]);
826 static bool check_temp(char *output_table)
828 if (output_table[0] == 'b' &&
829 output_table[1] == '2' &&
830 is_an_integer(output_table + 2))
837 void Bvfs::clear_cache()
839 db_sql_query(db, "BEGIN", NULL, NULL);
840 db_sql_query(db, "UPDATE Job SET HasCache=0", NULL, NULL);
841 db_sql_query(db, "TRUNCATE PathHierarchy", NULL, NULL);
842 db_sql_query(db, "TRUNCATE PathVisibility", NULL, NULL);
843 db_sql_query(db, "COMMIT", NULL, NULL);
846 bool Bvfs::drop_restore_list(char *output_table)
849 if (check_temp(output_table)) {
850 Mmsg(query, "DROP TABLE %s", output_table);
851 db_sql_query(db, query.c_str(), NULL, NULL);
857 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
866 if ((*fileid && !is_a_number_list(fileid)) ||
867 (*dirid && !is_a_number_list(dirid)) ||
868 (*hardlink && !is_a_number_list(hardlink))||
869 (!*hardlink && !*fileid && !*dirid && !*hardlink))
873 if (!check_temp(output_table)) {
877 Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
879 if (*fileid) { /* Select files with their direct id */
881 Mmsg(tmp,"SELECT JobId, JobTDate, FileIndex, FilenameId, PathId, FileId "
882 "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
884 pm_strcat(query, tmp.c_str());
887 /* Add a directory content */
888 while (get_next_id_from_list(&dirid, &id) == 1) {
889 Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
891 if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
892 Dmsg0(dbglevel, "Can't search for path\n");
896 if (!strcmp(tmp2.c_str(), "")) { /* path not found */
897 Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
898 id, tmp.c_str(), tmp2.c_str());
901 /* escape % and _ for LIKE search */
902 tmp.check_size((strlen(tmp2.c_str())+1) * 2);
903 char *p = tmp.c_str();
904 for (char *s = tmp2.c_str(); *s ; s++) {
905 if (*s == '%' || *s == '_' || *s == '\\') {
915 size_t len = strlen(tmp.c_str());
916 tmp2.check_size((len+1) * 2);
917 db_escape_string(jcr, db, tmp2.c_str(), tmp.c_str(), len);
920 query.strcat(" UNION ");
923 Mmsg(tmp, "SELECT JobId, JobTDate, File.FileIndex, File.FilenameId, "
924 "File.PathId, FileId "
925 "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
926 "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
927 tmp2.c_str(), jobids);
928 query.strcat(tmp.c_str());
931 query.strcat(" UNION ");
933 /* A directory can have files from a BaseJob */
934 Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
935 "File.FilenameId, File.PathId, BaseFiles.FileId "
937 "JOIN File USING (FileId) "
938 "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
939 "JOIN Path USING (PathId) "
940 "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
941 tmp2.c_str(), jobids);
942 query.strcat(tmp.c_str());
945 /* expect jobid,fileindex */
946 int64_t prev_jobid=0;
947 while (get_next_id_from_list(&hardlink, &jobid) == 1) {
948 if (get_next_id_from_list(&hardlink, &id) != 1) {
949 Dmsg0(dbglevel, "hardlink should be two by two\n");
952 if (jobid != prev_jobid) { /* new job */
953 if (prev_jobid == 0) { /* first jobid */
955 query.strcat(" UNION ");
957 } else { /* end last job, start new one */
958 tmp.strcat(") UNION ");
959 query.strcat(tmp.c_str());
961 Mmsg(tmp, "SELECT JobId, JobTDate, FileIndex, FilenameId, "
963 "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
964 "AND FileIndex IN (%lld", jobid, id);
967 } else { /* same job, add new findex */
968 Mmsg(tmp2, ", %lld", id);
969 tmp.strcat(tmp2.c_str());
973 if (prev_jobid != 0) { /* end last job */
975 query.strcat(tmp.c_str());
979 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
981 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
982 Dmsg0(dbglevel, "Can't execute q\n");
986 /* TODO: handle basejob and SQLite3 */
987 Mmsg(query, sql_bvfs_select[db_get_type_index(db)], output_table, output_table);
989 /* TODO: handle jobid filter */
990 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
991 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
992 Dmsg0(dbglevel, "Can't execute q\n");
997 if (db_get_type_index(db) == SQL_TYPE_MYSQL) {
998 Mmsg(query, "CREATE INDEX idx_%s ON b2%s (JobId)",
999 output_table, output_table);
1000 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1001 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1002 Dmsg0(dbglevel, "Can't execute q\n");
1010 Mmsg(query, "DROP TABLE btemp%s", output_table);
1011 db_sql_query(db, query.c_str(), NULL, NULL);
1015 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */