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 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
274 goto bail_out; /* Query failed, just leave */
277 /* Do we have a result ? */
278 if (sql_num_rows(mdb) > 0) {
279 ppathid_cache.insert(pathid);
280 /* This dir was in the db ...
281 * It means we can leave, the tree has allready been built for
286 /* search or create parent PathId in Path table */
287 mdb->path = bvfs_parent_dir(path);
288 mdb->pnl = strlen(mdb->path);
289 if (!db_create_path_record(jcr, mdb, &parent)) {
292 ppathid_cache.insert(pathid);
295 "INSERT INTO PathHierarchy (PathId, PPathId) "
297 pathid, (uint64_t) parent.PathId);
299 if (!INSERT_DB(jcr, mdb, mdb->cmd)) {
300 goto bail_out; /* Can't insert the record, just leave */
303 edit_uint64(parent.PathId, pathid);
304 path = mdb->path; /* already done */
307 /* It's already in the cache. We can leave, no time to waste here,
308 * all the parent dirs have allready been done
320 * Internal function to update path_hierarchy cache with a shared pathid cache
324 static int update_path_hierarchy_cache(JCR *jcr,
326 pathid_cache &ppathid_cache,
329 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
333 edit_uint64(JobId, jobid);
336 db_start_transaction(jcr, mdb);
338 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
340 if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
341 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
346 /* Inserting path records for JobId */
347 Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
348 "SELECT DISTINCT PathId, JobId "
349 "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
351 "SELECT PathId, BaseFiles.JobId "
352 "FROM BaseFiles JOIN File AS F USING (FileId) "
353 "WHERE BaseFiles.JobId = %s) AS B",
356 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
357 Dmsg1(dbglevel, "Can't fill PathVisibility %d\n", (uint32_t)JobId );
361 /* Now we have to do the directory recursion stuff to determine missing
362 * visibility We try to avoid recursion, to be as fast as possible We also
363 * only work on not allready hierarchised directories...
366 "SELECT PathVisibility.PathId, Path "
367 "FROM PathVisibility "
368 "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
369 "LEFT JOIN PathHierarchy "
370 "ON (PathVisibility.PathId = PathHierarchy.PathId) "
371 "WHERE PathVisibility.JobId = %s "
372 "AND PathHierarchy.PathId IS NULL "
373 "ORDER BY Path", jobid);
374 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
376 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
377 Dmsg1(dbglevel, "Can't get new Path %d\n", (uint32_t)JobId );
381 /* TODO: I need to reuse the DB connection without emptying the result
382 * So, now i'm copying the result in memory to be able to query the
383 * catalog descriptor again.
385 num = sql_num_rows(mdb);
387 char **result = (char **)malloc (num * 2 * sizeof(char *));
391 while((row = sql_fetch_row(mdb))) {
392 result[i++] = bstrdup(row[0]);
393 result[i++] = bstrdup(row[1]);
398 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
406 if (mdb->db_get_type_index() == SQL_TYPE_SQLITE3) {
408 "INSERT INTO PathVisibility (PathId, JobId) "
409 "SELECT DISTINCT h.PPathId AS PathId, %s "
410 "FROM PathHierarchy AS h "
411 "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
412 "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
413 jobid, jobid, jobid );
417 "INSERT INTO PathVisibility (PathId, JobId) "
418 "SELECT a.PathId,%s "
420 "SELECT DISTINCT h.PPathId AS PathId "
421 "FROM PathHierarchy AS h "
422 "JOIN PathVisibility AS p ON (h.PathId=p.PathId) "
423 "WHERE p.JobId=%s) AS a LEFT JOIN "
425 "FROM PathVisibility "
426 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
427 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
431 ret = QUERY_DB(jcr, mdb, mdb->cmd);
432 } while (ret && sql_affected_rows(mdb) > 0);
434 Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
435 UPDATE_DB(jcr, mdb, mdb->cmd);
438 db_end_transaction(jcr, mdb);
444 * Find an store the filename descriptor for empty directories Filename.Name=''
446 DBId_t Bvfs::get_dir_filenameid()
449 if (dir_filenameid) {
450 return dir_filenameid;
453 Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
454 db_sql_query(db, q.c_str(), db_int_handler, &id);
456 return dir_filenameid;
459 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
462 db_list_ctx jobids_list;
467 /* TODO: Remove this code when updating make_bacula_table script */
468 Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
469 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
470 Dmsg0(dbglevel, "Creating cache table\n");
471 Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
472 QUERY_DB(jcr, mdb, mdb->cmd);
475 "CREATE TABLE PathHierarchy ( "
476 "PathId integer NOT NULL, "
477 "PPathId integer NOT NULL, "
478 "CONSTRAINT pathhierarchy_pkey "
479 "PRIMARY KEY (PathId))");
480 QUERY_DB(jcr, mdb, mdb->cmd);
483 "CREATE INDEX pathhierarchy_ppathid "
484 "ON PathHierarchy (PPathId)");
485 QUERY_DB(jcr, mdb, mdb->cmd);
488 "CREATE TABLE PathVisibility ("
489 "PathId integer NOT NULL, "
490 "JobId integer NOT NULL, "
491 "Size int8 DEFAULT 0, "
492 "Files int4 DEFAULT 0, "
493 "CONSTRAINT pathvisibility_pkey "
494 "PRIMARY KEY (JobId, PathId))");
495 QUERY_DB(jcr, mdb, mdb->cmd);
498 "CREATE INDEX pathvisibility_jobid "
499 "ON PathVisibility (JobId)");
500 QUERY_DB(jcr, mdb, mdb->cmd);
506 "SELECT JobId from Job "
507 "WHERE HasCache = 0 "
508 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
511 db_sql_query(mdb, mdb->cmd, db_list_handler, &jobids_list);
513 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
515 db_start_transaction(jcr, mdb);
516 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
518 "DELETE FROM PathVisibility "
520 "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
521 nb = DELETE_DB(jcr, mdb, mdb->cmd);
522 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
524 db_end_transaction(jcr, mdb);
529 * Update the bvfs cache for given jobids (1,2,3,4)
532 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
534 pathid_cache ppathid_cache;
540 int stat = get_next_jobid_from_list(&p, &JobId);
547 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
548 if (!update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId)) {
556 * Update the bvfs cache for current jobids
558 void Bvfs::update_cache()
560 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
563 /* Change the current directory, returns true if the path exists */
564 bool Bvfs::ch_dir(const char *path)
566 pm_strcpy(db->path, path);
567 db->pnl = strlen(db->path);
569 ch_dir(db_get_path_record(jcr, db));
575 * Get all file versions for a specified client
576 * TODO: Handle basejobs using different client
578 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
580 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
581 (uint64_t)fnid, client);
582 char ed1[50], ed2[50];
585 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
587 Mmsg(q, " AND Job.Type = 'B' ");
593 "SELECT 'V', File.PathId, File.FilenameId, File.Md5, "
595 "File.JobId, File.LStat, File.FileId, "
597 "Media.VolumeName, Media.InChanger "
598 "FROM File, Job, Client, JobMedia, Media "
599 "WHERE File.FilenameId = %s "
600 "AND File.PathId=%s "
601 "AND File.JobId = Job.JobId "
602 "AND Job.JobId = JobMedia.JobId "
603 "AND File.FileIndex >= JobMedia.FirstIndex "
604 "AND File.FileIndex <= JobMedia.LastIndex "
605 "AND JobMedia.MediaId = Media.MediaId "
606 "AND Job.ClientId = Client.ClientId "
607 "AND Client.Name = '%s' "
608 "%s ORDER BY FileId LIMIT %d OFFSET %d"
609 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
611 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
612 db_sql_query(db, query.c_str(), list_entries, user_data);
615 DBId_t Bvfs::get_root()
620 p = db_get_path_record(jcr, db);
625 static int path_handler(void *ctx, int fields, char **row)
627 Bvfs *fs = (Bvfs *) ctx;
628 return fs->_handle_path(ctx, fields, row);
631 int Bvfs::_handle_path(void *ctx, int fields, char **row)
633 if (bvfs_is_dir(row)) {
634 /* can have the same path 2 times */
635 if (strcmp(row[BVFS_Name], prev_dir)) {
636 pm_strcpy(prev_dir, row[BVFS_Name]);
637 return list_entries(user_data, fields, row);
644 * Retrieve . and .. information
646 void Bvfs::ls_special_dirs()
648 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
649 char ed1[50], ed2[50];
653 if (!dir_filenameid) {
654 get_dir_filenameid();
657 /* Will fetch directories */
662 "(SELECT PPathId AS PathId, '..' AS Path "
663 "FROM PathHierarchy "
666 "SELECT %s AS PathId, '.' AS Path)",
667 edit_uint64(pwd_id, ed1), ed1);
670 Mmsg(query2,// 1 2 3 4 5 6
671 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
672 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
673 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
674 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
675 "WHERE File1.FilenameId = %s "
676 "AND File1.JobId IN (%s)) AS listfile1 "
677 "ON (tmp.PathId = listfile1.PathId) "
678 "ORDER BY tmp.Path, JobId DESC ",
679 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
681 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
682 db_sql_query(db, query2.c_str(), path_handler, this);
685 /* Returns true if we have dirs to read */
688 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
689 char ed1[50], ed2[50];
697 Mmsg(filter, " AND Path2.Path %s '%s' ",
698 match_query[db_get_type_index(db)], pattern);
701 if (!dir_filenameid) {
702 get_dir_filenameid();
705 /* the sql query displays same directory multiple time, take the first one */
708 /* Let's retrieve the list of the visible dirs in this dir ...
709 * First, I need the empty filenameid to locate efficiently
710 * the dirs in the file table
711 * my $dir_filenameid = $self->get_dir_filenameid();
713 /* Then we get all the dir entries from File ... */
716 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
717 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
718 "lower(Path1.Path) AS lpath, "
719 "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
720 "listfile1.FileId AS FileId "
722 "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
723 "FROM PathHierarchy AS PathHierarchy1 "
724 "JOIN Path AS Path2 "
725 "ON (PathHierarchy1.PathId = Path2.PathId) "
726 "JOIN PathVisibility AS PathVisibility1 "
727 "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
728 "WHERE PathHierarchy1.PPathId = %s "
729 "AND PathVisibility1.JobId IN (%s) "
732 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
734 "LEFT JOIN ( " /* get attributes if any */
735 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
736 "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
737 "WHERE File1.FilenameId = %s "
738 "AND File1.JobId IN (%s)) AS listfile1 "
739 "ON (listpath1.PathId = listfile1.PathId) "
740 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
741 edit_uint64(pwd_id, ed1),
744 edit_uint64(dir_filenameid, ed2),
748 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
751 db_sql_query(db, query.c_str(), path_handler, this);
752 nb_record = sql_num_rows(db);
755 return nb_record == limit;
758 void build_ls_files_query(B_DB *db, POOL_MEM &query,
759 const char *JobId, const char *PathId,
760 const char *filter, int64_t limit, int64_t offset)
762 if (db_get_type_index(db) == SQL_TYPE_POSTGRESQL) {
763 Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
764 JobId, PathId, JobId, PathId,
765 filter, limit, offset);
767 Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
768 JobId, PathId, JobId, PathId,
769 limit, offset, filter, JobId, JobId);
773 /* Returns true if we have files to read */
774 bool Bvfs::ls_files()
780 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
789 edit_uint64(pwd_id, pathid);
791 Mmsg(filter, " AND Filename.Name %s '%s' ",
792 match_query[db_get_type_index(db)], pattern);
795 build_ls_files_query(db, query,
796 jobids, pathid, filter.c_str(),
799 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
802 db_sql_query(db, query.c_str(), list_entries, user_data);
803 nb_record = sql_num_rows(db);
806 return nb_record == limit;
811 * Return next Id from comma separated list
814 * 1 if next Id returned
815 * 0 if no more Ids are in list
816 * -1 there is an error
817 * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
819 static int get_next_id_from_list(char **p, int64_t *Id)
821 const int maxlen = 30;
826 for (int i=0; i<maxlen; i++) {
829 } else if (*q == ',') {
838 } else if (!is_a_number(id)) {
839 return -1; /* error */
842 *Id = str_to_int64(id);
846 static int get_path_handler(void *ctx, int fields, char **row)
848 POOL_MEM *buf = (POOL_MEM *) ctx;
849 pm_strcpy(*buf, row[0]);
853 static bool check_temp(char *output_table)
855 if (output_table[0] == 'b' &&
856 output_table[1] == '2' &&
857 is_an_integer(output_table + 2))
864 void Bvfs::clear_cache()
866 db_sql_query(db, "BEGIN", NULL, NULL);
867 db_sql_query(db, "UPDATE Job SET HasCache=0", NULL, NULL);
868 db_sql_query(db, "TRUNCATE PathHierarchy", NULL, NULL);
869 db_sql_query(db, "TRUNCATE PathVisibility", NULL, NULL);
870 db_sql_query(db, "COMMIT", NULL, NULL);
873 bool Bvfs::drop_restore_list(char *output_table)
876 if (check_temp(output_table)) {
877 Mmsg(query, "DROP TABLE %s", output_table);
878 db_sql_query(db, query.c_str(), NULL, NULL);
884 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
889 int64_t id, jobid, prev_jobid;
893 if ((*fileid && !is_a_number_list(fileid)) ||
894 (*dirid && !is_a_number_list(dirid)) ||
895 (*hardlink && !is_a_number_list(hardlink))||
896 (!*hardlink && !*fileid && !*dirid && !*hardlink))
900 if (!check_temp(output_table)) {
906 /* Cleanup old tables first */
907 Mmsg(query, "DROP TABLE btemp%s", output_table);
908 db_sql_query(db, query.c_str());
910 Mmsg(query, "DROP TABLE %s", output_table);
911 db_sql_query(db, query.c_str());
913 Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
915 if (*fileid) { /* Select files with their direct id */
917 Mmsg(tmp,"SELECT JobId, JobTDate, FileIndex, FilenameId, PathId, FileId "
918 "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
920 pm_strcat(query, tmp.c_str());
923 /* Add a directory content */
924 while (get_next_id_from_list(&dirid, &id) == 1) {
925 Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
927 if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
928 Dmsg0(dbglevel, "Can't search for path\n");
932 if (!strcmp(tmp2.c_str(), "")) { /* path not found */
933 Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
934 id, tmp.c_str(), tmp2.c_str());
937 /* escape % and _ for LIKE search */
938 tmp.check_size((strlen(tmp2.c_str())+1) * 2);
939 char *p = tmp.c_str();
940 for (char *s = tmp2.c_str(); *s ; s++) {
941 if (*s == '%' || *s == '_' || *s == '\\') {
951 size_t len = strlen(tmp.c_str());
952 tmp2.check_size((len+1) * 2);
953 db_escape_string(jcr, db, tmp2.c_str(), tmp.c_str(), len);
956 query.strcat(" UNION ");
959 Mmsg(tmp, "SELECT JobId, JobTDate, File.FileIndex, File.FilenameId, "
960 "File.PathId, FileId "
961 "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
962 "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
963 tmp2.c_str(), jobids);
964 query.strcat(tmp.c_str());
967 query.strcat(" UNION ");
969 /* A directory can have files from a BaseJob */
970 Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
971 "File.FilenameId, File.PathId, BaseFiles.FileId "
973 "JOIN File USING (FileId) "
974 "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
975 "JOIN Path USING (PathId) "
976 "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
977 tmp2.c_str(), jobids);
978 query.strcat(tmp.c_str());
981 /* expect jobid,fileindex */
983 while (get_next_id_from_list(&hardlink, &jobid) == 1) {
984 if (get_next_id_from_list(&hardlink, &id) != 1) {
985 Dmsg0(dbglevel, "hardlink should be two by two\n");
988 if (jobid != prev_jobid) { /* new job */
989 if (prev_jobid == 0) { /* first jobid */
991 query.strcat(" UNION ");
993 } else { /* end last job, start new one */
994 tmp.strcat(") UNION ");
995 query.strcat(tmp.c_str());
997 Mmsg(tmp, "SELECT JobId, JobTDate, FileIndex, FilenameId, "
999 "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
1000 "AND FileIndex IN (%lld", jobid, id);
1003 } else { /* same job, add new findex */
1004 Mmsg(tmp2, ", %lld", id);
1005 tmp.strcat(tmp2.c_str());
1009 if (prev_jobid != 0) { /* end last job */
1011 query.strcat(tmp.c_str());
1015 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1017 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1018 Dmsg0(dbglevel, "Can't execute q\n");
1022 /* TODO: handle basejob and SQLite3 */
1023 Mmsg(query, sql_bvfs_select[db_get_type_index(db)], output_table, output_table);
1025 /* TODO: handle jobid filter */
1026 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1027 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1028 Dmsg0(dbglevel, "Can't execute q\n");
1033 if (db_get_type_index(db) == SQL_TYPE_MYSQL) {
1034 Mmsg(query, "CREATE INDEX idx_%s ON %s (JobId)",
1035 output_table, output_table);
1036 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
1037 if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
1038 Dmsg0(dbglevel, "Can't execute q\n");
1046 Mmsg(query, "DROP TABLE btemp%s", output_table);
1047 db_sql_query(db, query.c_str(), NULL, NULL);
1052 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */