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 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
208 while (p > path && !IsPathSeparator(*p)) {
216 /* Return the basename of the with the trailing /
217 * TODO: see in the rest of bacula if we don't have
218 * this function already
220 char *bvfs_basename_dir(char *path)
223 int len = strlen(path) - 1;
225 if (path[len] == '/') { /* if directory, skip last / */
231 while (p > path && !IsPathSeparator(*p)) {
235 p++; /* skip first / */
241 static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
242 pathid_cache &ppathid_cache,
243 char *org_pathid, char *path)
245 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
248 char *bkp = mdb->path;
249 strncpy(pathid, org_pathid, sizeof(pathid));
251 /* Does the ppathid exist for this ? we use a memory cache... In order to
252 * avoid the full loop, we consider that if a dir is allready in the
253 * PathHierarchy table, then there is no need to calculate all the
256 while (path && *path)
258 if (!ppathid_cache.lookup(pathid))
261 "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
264 QUERY_DB(jcr, mdb, mdb->cmd);
265 /* Do we have a result ? */
266 if (sql_num_rows(mdb) > 0) {
267 ppathid_cache.insert(pathid);
268 /* This dir was in the db ...
269 * It means we can leave, the tree has allready been built for
274 /* search or create parent PathId in Path table */
275 mdb->path = bvfs_parent_dir(path);
276 mdb->pnl = strlen(mdb->path);
277 if (!db_create_path_record(jcr, mdb, &parent)) {
280 ppathid_cache.insert(pathid);
283 "INSERT INTO PathHierarchy (PathId, PPathId) "
285 pathid, (uint64_t) parent.PathId);
287 INSERT_DB(jcr, mdb, mdb->cmd);
289 edit_uint64(parent.PathId, pathid);
290 path = mdb->path; /* already done */
293 /* It's already in the cache. We can leave, no time to waste here,
294 * all the parent dirs have allready been done
306 * Internal function to update path_hierarchy cache with a shared pathid cache
308 static void update_path_hierarchy_cache(JCR *jcr,
310 pathid_cache &ppathid_cache,
313 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
317 edit_uint64(JobId, jobid);
320 db_start_transaction(jcr, mdb);
322 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
324 if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
325 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
329 /* Inserting path records for JobId */
330 Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
331 "SELECT DISTINCT PathId, JobId "
332 "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
334 "SELECT PathId, BaseFiles.JobId "
335 "FROM BaseFiles JOIN File AS F USING (FileId) "
336 "WHERE BaseFiles.JobId = %s) AS B",
338 QUERY_DB(jcr, mdb, mdb->cmd);
340 /* Now we have to do the directory recursion stuff to determine missing
341 * visibility We try to avoid recursion, to be as fast as possible We also
342 * only work on not allready hierarchised directories...
345 "SELECT PathVisibility.PathId, Path "
346 "FROM PathVisibility "
347 "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
348 "LEFT JOIN PathHierarchy "
349 "ON (PathVisibility.PathId = PathHierarchy.PathId) "
350 "WHERE PathVisibility.JobId = %s "
351 "AND PathHierarchy.PathId IS NULL "
352 "ORDER BY Path", jobid);
353 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
354 QUERY_DB(jcr, mdb, mdb->cmd);
356 /* TODO: I need to reuse the DB connection without emptying the result
357 * So, now i'm copying the result in memory to be able to query the
358 * catalog descriptor again.
360 num = sql_num_rows(mdb);
362 char **result = (char **)malloc (num * 2 * sizeof(char *));
366 while((row = sql_fetch_row(mdb))) {
367 result[i++] = bstrdup(row[0]);
368 result[i++] = bstrdup(row[1]);
373 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
381 if (mdb->db_get_type_index() == SQL_TYPE_SQLITE3) {
383 "INSERT INTO PathVisibility (PathId, JobId) "
384 "SELECT DISTINCT h.PPathId AS PathId, %s "
385 "FROM PathHierarchy AS h "
386 "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
387 "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
388 jobid, jobid, jobid );
392 "INSERT INTO PathVisibility (PathId, JobId) "
393 "SELECT a.PathId,%s "
395 "SELECT DISTINCT h.PPathId AS PathId "
396 "FROM PathHierarchy AS h "
397 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
398 "WHERE p.JobId=%s) AS a LEFT JOIN "
400 "FROM PathVisibility "
401 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
402 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
406 QUERY_DB(jcr, mdb, mdb->cmd);
407 } while (sql_affected_rows(mdb) > 0);
409 Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
410 UPDATE_DB(jcr, mdb, mdb->cmd);
413 db_end_transaction(jcr, mdb);
418 * Find an store the filename descriptor for empty directories Filename.Name=''
420 DBId_t Bvfs::get_dir_filenameid()
423 if (dir_filenameid) {
424 return dir_filenameid;
427 Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
428 db_sql_query(db, q.c_str(), db_int_handler, &id);
430 return dir_filenameid;
433 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
436 db_list_ctx jobids_list;
439 db_start_transaction(jcr, mdb);
442 /* TODO: Remove this code when updating make_bacula_table script */
443 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
444 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
445 Dmsg0(dbglevel, "Creating cache table\n");
446 Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
447 QUERY_DB(jcr, mdb, mdb->cmd);
450 "CREATE TABLE PathHierarchy ( "
451 "PathId integer NOT NULL, "
452 "PPathId integer NOT NULL, "
453 "CONSTRAINT pathhierarchy_pkey "
454 "PRIMARY KEY (PathId))");
455 QUERY_DB(jcr, mdb, mdb->cmd);
458 "CREATE INDEX pathhierarchy_ppathid "
459 "ON PathHierarchy (PPathId)");
460 QUERY_DB(jcr, mdb, mdb->cmd);
463 "CREATE TABLE PathVisibility ("
464 "PathId integer NOT NULL, "
465 "JobId integer NOT NULL, "
466 "Size int8 DEFAULT 0, "
467 "Files int4 DEFAULT 0, "
468 "CONSTRAINT pathvisibility_pkey "
469 "PRIMARY KEY (JobId, PathId))");
470 QUERY_DB(jcr, mdb, mdb->cmd);
473 "CREATE INDEX pathvisibility_jobid "
474 "ON PathVisibility (JobId)");
475 QUERY_DB(jcr, mdb, mdb->cmd);
481 "SELECT JobId from Job "
482 "WHERE HasCache = 0 "
483 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
486 db_sql_query(mdb, mdb->cmd, db_list_handler, &jobids_list);
488 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
490 db_end_transaction(jcr, mdb);
491 db_start_transaction(jcr, mdb);
492 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
494 "DELETE FROM PathVisibility "
496 "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
497 nb = DELETE_DB(jcr, mdb, mdb->cmd);
498 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
500 db_end_transaction(jcr, mdb);
505 * Update the bvfs cache for given jobids (1,2,3,4)
508 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
510 pathid_cache ppathid_cache;
515 int stat = get_next_jobid_from_list(&p, &JobId);
522 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
523 update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
528 * Update the bvfs cache for current jobids
530 void Bvfs::update_cache()
532 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
535 /* Change the current directory, returns true if the path exists */
536 bool Bvfs::ch_dir(const char *path)
538 pm_strcpy(db->path, path);
539 db->pnl = strlen(db->path);
540 ch_dir(db_get_path_record(jcr, db));
545 * Get all file versions for a specified client
546 * TODO: Handle basejobs using different client
548 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
550 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
551 (uint64_t)fnid, client);
552 char ed1[50], ed2[50];
555 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
557 Mmsg(q, " AND Job.Type = 'B' ");
563 "SELECT 'V', File.PathId, File.FilenameId, File.Md5, "
565 "File.JobId, File.LStat, File.FileId, "
567 "Media.VolumeName, Media.InChanger "
568 "FROM File, Job, Client, JobMedia, Media "
569 "WHERE File.FilenameId = %s "
570 "AND File.PathId=%s "
571 "AND File.JobId = Job.JobId "
572 "AND Job.JobId = JobMedia.JobId "
573 "AND File.FileIndex >= JobMedia.FirstIndex "
574 "AND File.FileIndex <= JobMedia.LastIndex "
575 "AND JobMedia.MediaId = Media.MediaId "
576 "AND Job.ClientId = Client.ClientId "
577 "AND Client.Name = '%s' "
578 "%s ORDER BY FileId LIMIT %d OFFSET %d"
579 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
581 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
582 db_sql_query(db, query.c_str(), list_entries, user_data);
585 DBId_t Bvfs::get_root()
588 return db_get_path_record(jcr, db);
591 static int path_handler(void *ctx, int fields, char **row)
593 Bvfs *fs = (Bvfs *) ctx;
594 return fs->_handle_path(ctx, fields, row);
597 int Bvfs::_handle_path(void *ctx, int fields, char **row)
599 if (bvfs_is_dir(row)) {
600 /* can have the same path 2 times */
601 if (strcmp(row[BVFS_Name], prev_dir)) {
602 pm_strcpy(prev_dir, row[BVFS_Name]);
603 return list_entries(user_data, fields, row);
610 * Retrieve . and .. information
612 void Bvfs::ls_special_dirs()
614 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
615 char ed1[50], ed2[50];
619 if (!dir_filenameid) {
620 get_dir_filenameid();
623 /* Will fetch directories */
628 "(SELECT PPathId AS PathId, '..' AS Path "
629 "FROM PathHierarchy "
632 "SELECT %s AS PathId, '.' AS Path)",
633 edit_uint64(pwd_id, ed1), ed1);
636 Mmsg(query2,// 1 2 3 4 5 6
637 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
638 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
639 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
640 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
641 "WHERE File1.FilenameId = %s "
642 "AND File1.JobId IN (%s)) AS listfile1 "
643 "ON (tmp.PathId = listfile1.PathId) "
644 "ORDER BY tmp.Path, JobId DESC ",
645 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
647 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
648 db_sql_query(db, query2.c_str(), path_handler, this);
651 /* Returns true if we have dirs to read */
654 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
655 char ed1[50], ed2[50];
663 Mmsg(filter, " AND Path2.Path %s '%s' ",
664 match_query[db_get_type_index(db)], pattern);
667 if (!dir_filenameid) {
668 get_dir_filenameid();
671 /* the sql query displays same directory multiple time, take the first one */
674 /* Let's retrieve the list of the visible dirs in this dir ...
675 * First, I need the empty filenameid to locate efficiently
676 * the dirs in the file table
677 * my $dir_filenameid = $self->get_dir_filenameid();
679 /* Then we get all the dir entries from File ... */
682 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
683 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
684 "lower(Path1.Path) AS lpath, "
685 "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
686 "listfile1.FileId AS FileId "
688 "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
689 "FROM PathHierarchy AS PathHierarchy1 "
690 "JOIN Path AS Path2 "
691 "ON (PathHierarchy1.PathId = Path2.PathId) "
692 "JOIN PathVisibility AS PathVisibility1 "
693 "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
694 "WHERE PathHierarchy1.PPathId = %s "
695 "AND PathVisibility1.JobId IN (%s) "
698 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
700 "LEFT JOIN ( " /* get attributes if any */
701 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
702 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
703 "WHERE File1.FilenameId = %s "
704 "AND File1.JobId IN (%s)) AS listfile1 "
705 "ON (listpath1.PathId = listfile1.PathId) "
706 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
707 edit_uint64(pwd_id, ed1),
710 edit_uint64(dir_filenameid, ed2),
714 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
717 db_sql_query(db, query.c_str(), path_handler, this);
718 nb_record = sql_num_rows(db);
721 return nb_record == limit;
724 void build_ls_files_query(B_DB *db, POOL_MEM &query,
725 const char *JobId, const char *PathId,
726 const char *filter, int64_t limit, int64_t offset)
728 if (db_get_type_index(db) == SQL_TYPE_POSTGRESQL) {
729 Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
730 JobId, PathId, JobId, PathId,
731 filter, limit, offset);
733 Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
734 JobId, PathId, JobId, PathId,
735 limit, offset, filter, JobId, JobId);
739 /* Returns true if we have files to read */
740 bool Bvfs::ls_files()
746 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
755 edit_uint64(pwd_id, pathid);
757 Mmsg(filter, " AND Filename.Name %s '%s' ",
758 match_query[db_get_type_index(db)], pattern);
761 build_ls_files_query(db, query,
762 jobids, pathid, filter.c_str(),
765 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
768 db_sql_query(db, query.c_str(), list_entries, user_data);
769 nb_record = sql_num_rows(db);
772 return nb_record == limit;
777 * Return next Id from comma separated list
780 * 1 if next Id returned
781 * 0 if no more Ids are in list
782 * -1 there is an error
783 * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
785 static int get_next_id_from_list(char **p, int64_t *Id)
787 const int maxlen = 30;
792 for (int i=0; i<maxlen; i++) {
795 } else if (*q == ',') {
804 } else if (!is_a_number(id)) {
805 return -1; /* error */
808 *Id = str_to_int64(id);
812 static int get_path_handler(void *ctx, int fields, char **row)
814 POOL_MEM *buf = (POOL_MEM *) ctx;
815 pm_strcpy(*buf, row[0]);
819 static bool check_temp(char *output_table)
821 if (output_table[0] == 'b' &&
822 output_table[1] == '2' &&
823 is_an_integer(output_table + 2))
830 bool Bvfs::drop_restore_list(char *output_table)
833 if (check_temp(output_table)) {
834 Mmsg(query, "DROP TABLE %s", output_table);
835 db_sql_query(db, query.c_str(), NULL, NULL);
841 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
850 if ((*fileid && !is_a_number_list(fileid)) ||
851 (*dirid && !is_a_number_list(dirid)) ||
852 (*hardlink && !is_a_number_list(hardlink))||
853 (!*hardlink && !*fileid && !*dirid && !*hardlink))
857 if (!check_temp(output_table)) {
861 Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
863 if (*fileid) { /* Select files with their direct id */
865 Mmsg(tmp,"SELECT JobId, JobTDate, FileIndex, FilenameId, PathId, FileId "
866 "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
868 pm_strcat(query, tmp.c_str());
871 /* Add a directory content */
872 while (get_next_id_from_list(&dirid, &id) == 1) {
873 Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
875 if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
876 Dmsg0(dbglevel, "Can't search for path\n");
880 if (!strcmp(tmp2.c_str(), "")) { /* path not found */
881 Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
882 id, tmp.c_str(), tmp2.c_str());
885 /* escape % and _ for LIKE search */
886 tmp.check_size((strlen(tmp2.c_str())+1) * 2);
887 char *p = tmp.c_str();
888 for (char *s = tmp2.c_str(); *s ; s++) {
889 if (*s == '%' || *s == '_' || *s == '\\') {
899 size_t len = strlen(tmp.c_str());
900 tmp2.check_size((len+1) * 2);
901 db_escape_string(jcr, db, tmp2.c_str(), tmp.c_str(), len);
904 query.strcat(" UNION ");
907 Mmsg(tmp, "SELECT JobId, JobTDate, File.FileIndex, File.FilenameId, "
908 "File.PathId, FileId "
909 "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
910 "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
911 tmp2.c_str(), jobids);
912 query.strcat(tmp.c_str());
915 query.strcat(" UNION ");
917 /* A directory can have files from a BaseJob */
918 Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
919 "File.FilenameId, File.PathId, BaseFiles.FileId "
921 "JOIN File USING (FileId) "
922 "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
923 "JOIN Path USING (PathId) "
924 "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
925 tmp2.c_str(), jobids);
926 query.strcat(tmp.c_str());
929 /* expect jobid,fileindex */
930 int64_t prev_jobid=0;
931 while (get_next_id_from_list(&hardlink, &jobid) == 1) {
932 if (get_next_id_from_list(&hardlink, &id) != 1) {
933 Dmsg0(dbglevel, "hardlink should be two by two\n");
936 if (jobid != prev_jobid) { /* new job */
937 if (prev_jobid == 0) { /* first jobid */
939 query.strcat(" UNION ");
941 } else { /* end last job, start new one */
942 tmp.strcat(") UNION ");
943 query.strcat(tmp.c_str());
945 Mmsg(tmp, "SELECT JobId, JobTDate, FileIndex, FilenameId, "
947 "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
948 "AND FileIndex IN (%lld", jobid, id);
951 } else { /* same job, add new findex */
952 Mmsg(tmp2, ", %lld", id);
953 tmp.strcat(tmp2.c_str());
957 if (prev_jobid != 0) { /* end last job */
959 query.strcat(tmp.c_str());
963 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
965 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
966 Dmsg0(dbglevel, "Can't execute q\n");
970 /* TODO: handle basejob and SQLite3 */
971 Mmsg(query, sql_bvfs_select[db_get_type_index(db)], output_table, output_table);
973 /* TODO: handle jobid filter */
974 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
975 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
976 Dmsg0(dbglevel, "Can't execute q\n");
981 if (db_get_type_index(db) == SQL_TYPE_MYSQL) {
982 Mmsg(query, "CREATE INDEX idx_%s ON b2%s (JobId)",
983 output_table, output_table);
984 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
985 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
986 Dmsg0(dbglevel, "Can't execute q\n");
994 Mmsg(query, "DROP TABLE btemp%s", output_table);
995 db_sql_query(db, query.c_str(), NULL, NULL);
999 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */