/*
Bacula® - The Network Backup Solution
- Copyright (C) 2009-2009 Free Software Foundation Europe e.V.
+ Copyright (C) 2009-2010 Free Software Foundation Europe e.V.
The main author of Bacula is Kern Sibbald, with contributions from
many others, a complete list can be found in the file AUTHORS.
This program is Free Software; you can redistribute it and/or
- modify it under the terms of version two of the GNU General Public
+ modify it under the terms of version three of the GNU Affero General Public
License as published by the Free Software Foundation, which is
listed in the file LICENSE.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
- You should have received a copy of the GNU General Public License
+ You should have received a copy of the GNU Affero General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
Switzerland, email:ftf@fsfeurope.org.
*/
-#define __SQL_C /* indicate that this is sql.c */
-
#include "bacula.h"
-#include "cats/cats.h"
+
+#if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI
+
+#include "cats.h"
+#include "bdb_priv.h"
+#include "sql_glue.h"
#include "lib/htable.h"
#include "bvfs.h"
pattern = get_pool_memory(PM_NAME);
*jobids = *prev_dir = *pattern = 0;
dir_filenameid = pwd_id = offset = 0;
- see_copies = see_all_version = false;
+ see_copies = see_all_versions = false;
limit = 1000;
attr = new_attr(jcr);
list_entries = result_handler;
user_data = this;
+ username = NULL;
}
Bvfs::~Bvfs() {
free_pool_memory(jobids);
free_pool_memory(pattern);
free_pool_memory(prev_dir);
+ if (username) {
+ free(username);
+ }
free_attr(attr);
jcr->dec_use_count();
}
+void Bvfs::filter_jobid()
+{
+ if (!username) {
+ return;
+ }
+
+ /* Query used by Bweb to filter clients, activated when using
+ * set_username()
+ */
+ POOL_MEM query;
+ Mmsg(query,
+ "SELECT DISTINCT JobId FROM Job JOIN Client USING (ClientId) "
+ "JOIN (SELECT ClientId FROM client_group_member "
+ "JOIN client_group USING (client_group_id) "
+ "JOIN bweb_client_group_acl USING (client_group_id) "
+ "JOIN bweb_user USING (userid) "
+ "WHERE bweb_user.username = '%s' "
+ ") AS filter USING (ClientId) "
+ " WHERE JobId IN (%s)",
+ username, jobids);
+
+ db_list_ctx ctx;
+ Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+ db_sql_query(db, query.c_str(), db_list_handler, &ctx);
+ pm_strcpy(jobids, ctx.list);
+}
+
+void Bvfs::set_jobid(JobId_t id)
+{
+ Mmsg(jobids, "%lld", (uint64_t)id);
+ filter_jobid();
+}
+
+void Bvfs::set_jobids(char *ids)
+{
+ pm_strcpy(jobids, ids);
+ filter_jobid();
+}
+
/*
* TODO: Find a way to let the user choose how he wants to display
* files and directories
hlink *nodes;
int nb_node;
int max_node;
+
+ alist *table_node;
+
htable *cache_ppathid;
public:
max_node = NITEMS;
nodes = (hlink *) malloc(max_node * sizeof (hlink));
nb_node = 0;
+ table_node = New(alist(5, owned_by_alist));
+ table_node->append(nodes);
}
hlink *get_hlink() {
- if (nb_node >= max_node) {
- max_node *= 2;
- nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
+ if (++nb_node >= max_node) {
+ nb_node = 0;
+ nodes = (hlink *)malloc(max_node * sizeof(hlink));
+ table_node->append(nodes);
}
- return nodes + nb_node++;
+ return nodes + nb_node;
}
bool lookup(char *pathid) {
~pathid_cache() {
cache_ppathid->destroy();
free(cache_ppathid);
- free(nodes);
+ delete table_node;
}
private:
pathid_cache(const pathid_cache &); /* prohibit pass by value */
char *p = path;
int len = strlen(path) - 1;
+ /* windows directory / */
+ if (len == 2 && B_ISALPHA(path[0])
+ && path[1] == ':'
+ && path[2] == '/')
+ {
+ len = 0;
+ path[0] = '\0';
+ }
+
if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
path[len] = '\0';
}
/* Inserting path records for JobId */
Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
- "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
- jobid);
+ "SELECT DISTINCT PathId, JobId "
+ "FROM (SELECT PathId, JobId FROM File WHERE JobId = %s "
+ "UNION "
+ "SELECT PathId, BaseFiles.JobId "
+ "FROM BaseFiles JOIN File AS F USING (FileId) "
+ "WHERE BaseFiles.JobId = %s) AS B",
+ jobid, jobid);
QUERY_DB(jcr, mdb, mdb->cmd);
-
/* Now we have to do the directory recursion stuff to determine missing
* visibility We try to avoid recursion, to be as fast as possible We also
* only work on not allready hierarchised directories...
}
free(result);
}
-
- Mmsg(mdb->cmd,
+
+ if (mdb->db_get_type_index() == SQL_TYPE_SQLITE3) {
+ Mmsg(mdb->cmd,
+ "INSERT INTO PathVisibility (PathId, JobId) "
+ "SELECT DISTINCT h.PPathId AS PathId, %s "
+ "FROM PathHierarchy AS h "
+ "WHERE h.PathId IN (SELECT PathId FROM PathVisibility WHERE JobId=%s) "
+ "AND h.PPathId NOT IN (SELECT PathId FROM PathVisibility WHERE JobId=%s)",
+ jobid, jobid, jobid );
+
+ } else {
+ Mmsg(mdb->cmd,
"INSERT INTO PathVisibility (PathId, JobId) "
"SELECT a.PathId,%s "
"FROM ( "
"FROM PathVisibility "
"WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
"WHERE b.PathId IS NULL", jobid, jobid, jobid);
+ }
do {
QUERY_DB(jcr, mdb, mdb->cmd);
db_list_ctx jobids_list;
db_lock(mdb);
- db_start_transaction(jcr, mdb);
+#ifdef xxx
/* TODO: Remove this code when updating make_bacula_table script */
Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
QUERY_DB(jcr, mdb, mdb->cmd);
}
+#endif
Mmsg(mdb->cmd,
"SELECT JobId from Job "
- "WHERE HashCache = 0 "
+ "WHERE HasCache = 0 "
"AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
"ORDER BY JobId");
bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
- db_end_transaction(jcr, mdb);
db_start_transaction(jcr, mdb);
Dmsg0(dbglevel, "Cleaning pathvisibility\n");
Mmsg(mdb->cmd,
Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
db_end_transaction(jcr, mdb);
+ db_unlock(mdb);
}
/*
/*
* Get all file versions for a specified client
+ * TODO: Handle basejobs using different client
*/
void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
{
POOL_MEM query;
- Mmsg(query,// 1 2 3 4
-"SELECT 'V', File.FileId, File.Md5, File.JobId, File.LStat, "
-// 5 6
+ Mmsg(query,// 1 2 3
+"SELECT 'V', File.PathId, File.FilenameId, File.Md5, "
+// 4 5 6
+ "File.JobId, File.LStat, File.FileId, "
+// 7 8
"Media.VolumeName, Media.InChanger "
"FROM File, Job, Client, JobMedia, Media "
"WHERE File.FilenameId = %s "
"AND File.PathId=%s "
"AND File.JobId = Job.JobId "
- "AND Job.ClientId = Client.ClientId "
"AND Job.JobId = JobMedia.JobId "
"AND File.FileIndex >= JobMedia.FirstIndex "
"AND File.FileIndex <= JobMedia.LastIndex "
"AND JobMedia.MediaId = Media.MediaId "
+ "AND Job.ClientId = Client.ClientId "
"AND Client.Name = '%s' "
"%s ORDER BY FileId LIMIT %d OFFSET %d"
,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
POOL_MEM query;
Mmsg(query,
-"((SELECT PPathId AS PathId, '..' AS Path "
+"(SELECT PPathId AS PathId, '..' AS Path "
"FROM PathHierarchy "
- "WHERE PathId = %s) "
+ "WHERE PathId = %s "
"UNION "
- "(SELECT %s AS PathId, '.' AS Path))",
+ "SELECT %s AS PathId, '.' AS Path)",
edit_uint64(pwd_id, ed1), ed1);
POOL_MEM query2;
return false;
}
+ POOL_MEM query;
POOL_MEM filter;
if (*pattern) {
- Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
+ Mmsg(filter, " AND Path2.Path %s '%s' ",
+ match_query[db_get_type_index(db)], pattern);
}
if (!dir_filenameid) {
* my $dir_filenameid = $self->get_dir_filenameid();
*/
/* Then we get all the dir entries from File ... */
- POOL_MEM query;
Mmsg(query,
// 0 1 2 3 4 5 6
"SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
"JOIN PathVisibility AS PathVisibility1 "
"ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
"WHERE PathHierarchy1.PPathId = %s "
- "AND PathVisibility1.jobid IN (%s) "
+ "AND PathVisibility1.JobId IN (%s) "
"%s "
") AS listpath1 "
"JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
db_lock(db);
db_sql_query(db, query.c_str(), path_handler, this);
- nb_record = db->num_rows;
+ nb_record = sql_num_rows(db);
db_unlock(db);
return nb_record == limit;
}
+void build_ls_files_query(B_DB *db, POOL_MEM &query,
+ const char *JobId, const char *PathId,
+ const char *filter, int64_t limit, int64_t offset)
+{
+ if (db_get_type_index(db) == SQL_TYPE_POSTGRESQL) {
+ Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
+ JobId, PathId, JobId, PathId,
+ filter, limit, offset);
+ } else {
+ Mmsg(query, sql_bvfs_list_files[db_get_type_index(db)],
+ JobId, PathId, JobId, PathId,
+ limit, offset, filter, JobId, JobId);
+ }
+}
+
/* Returns true if we have files to read */
bool Bvfs::ls_files()
{
+ POOL_MEM query;
+ POOL_MEM filter;
+ char pathid[50];
+
Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
- char ed1[50];
if (*jobids == 0) {
return false;
}
ch_dir(get_root());
}
- POOL_MEM filter;
+ edit_uint64(pwd_id, pathid);
if (*pattern) {
- Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
+ Mmsg(filter, " AND Filename.Name %s '%s' ",
+ match_query[db_get_type_index(db)], pattern);
}
- POOL_MEM query;
- Mmsg(query, // 1 2 3 4
-"SELECT 'F', File.PathId, File.FilenameId, listfiles.Name, File.JobId, "
- "File.LStat, listfiles.id "
-"FROM File, ( "
- "SELECT Filename.Name as Name, max(File.FileId) as id "
- "FROM File, Filename "
- "WHERE File.FilenameId = Filename.FilenameId "
- "AND Filename.Name != '' "
- "AND File.PathId = %s "
- "AND File.JobId IN (%s) "
- "%s "
- "GROUP BY Filename.Name "
- "ORDER BY Filename.Name LIMIT %d OFFSET %d "
- ") AS listfiles "
-"WHERE File.FileId = listfiles.id",
- edit_uint64(pwd_id, ed1),
- jobids,
- filter.c_str(),
- limit,
- offset);
+ build_ls_files_query(db, query,
+ jobids, pathid, filter.c_str(),
+ limit, offset);
+
Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
db_lock(db);
db_sql_query(db, query.c_str(), list_entries, user_data);
- nb_record = db->num_rows;
+ nb_record = sql_num_rows(db);
db_unlock(db);
return nb_record == limit;
}
+
+
+/*
+ * Return next Id from comma separated list
+ *
+ * Returns:
+ * 1 if next Id returned
+ * 0 if no more Ids are in list
+ * -1 there is an error
+ * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
+ */
+static int get_next_id_from_list(char **p, int64_t *Id)
+{
+ const int maxlen = 30;
+ char id[maxlen+1];
+ char *q = *p;
+
+ id[0] = 0;
+ for (int i=0; i<maxlen; i++) {
+ if (*q == 0) {
+ break;
+ } else if (*q == ',') {
+ q++;
+ break;
+ }
+ id[i] = *q++;
+ id[i+1] = 0;
+ }
+ if (id[0] == 0) {
+ return 0;
+ } else if (!is_a_number(id)) {
+ return -1; /* error */
+ }
+ *p = q;
+ *Id = str_to_int64(id);
+ return 1;
+}
+
+static int get_path_handler(void *ctx, int fields, char **row)
+{
+ POOL_MEM *buf = (POOL_MEM *) ctx;
+ pm_strcpy(*buf, row[0]);
+ return 0;
+}
+
+static bool check_temp(char *output_table)
+{
+ if (output_table[0] == 'b' &&
+ output_table[1] == '2' &&
+ is_an_integer(output_table + 2))
+ {
+ return true;
+ }
+ return false;
+}
+
+void Bvfs::clear_cache()
+{
+ db_sql_query(db, "BEGIN", NULL, NULL);
+ db_sql_query(db, "UPDATE Job SET HasCache=0", NULL, NULL);
+ db_sql_query(db, "TRUNCATE PathHierarchy", NULL, NULL);
+ db_sql_query(db, "TRUNCATE PathVisibility", NULL, NULL);
+ db_sql_query(db, "COMMIT", NULL, NULL);
+}
+
+bool Bvfs::drop_restore_list(char *output_table)
+{
+ POOL_MEM query;
+ if (check_temp(output_table)) {
+ Mmsg(query, "DROP TABLE %s", output_table);
+ db_sql_query(db, query.c_str(), NULL, NULL);
+ return true;
+ }
+ return false;
+}
+
+bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink,
+ char *output_table)
+{
+ POOL_MEM query;
+ POOL_MEM tmp, tmp2;
+ int64_t id, jobid;
+ bool init=false;
+ bool ret=false;
+ /* check args */
+ if ((*fileid && !is_a_number_list(fileid)) ||
+ (*dirid && !is_a_number_list(dirid)) ||
+ (*hardlink && !is_a_number_list(hardlink))||
+ (!*hardlink && !*fileid && !*dirid && !*hardlink))
+ {
+ return false;
+ }
+ if (!check_temp(output_table)) {
+ return false;
+ }
+
+ Mmsg(query, "CREATE TABLE btemp%s AS ", output_table);
+
+ if (*fileid) { /* Select files with their direct id */
+ init=true;
+ Mmsg(tmp,"SELECT JobId, JobTDate, FileIndex, FilenameId, PathId, FileId "
+ "FROM File JOIN Job USING (JobId) WHERE FileId IN (%s)",
+ fileid);
+ pm_strcat(query, tmp.c_str());
+ }
+
+ /* Add a directory content */
+ while (get_next_id_from_list(&dirid, &id) == 1) {
+ Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
+
+ if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
+ Dmsg0(dbglevel, "Can't search for path\n");
+ /* print error */
+ return false;
+ }
+ if (!strcmp(tmp2.c_str(), "")) { /* path not found */
+ Dmsg3(dbglevel, "Path not found %lld q=%s s=%s\n",
+ id, tmp.c_str(), tmp2.c_str());
+ break;
+ }
+ /* escape % and _ for LIKE search */
+ tmp.check_size((strlen(tmp2.c_str())+1) * 2);
+ char *p = tmp.c_str();
+ for (char *s = tmp2.c_str(); *s ; s++) {
+ if (*s == '%' || *s == '_' || *s == '\\') {
+ *p = '\\';
+ p++;
+ }
+ *p = *s;
+ p++;
+ }
+ *p = '\0';
+ tmp.strcat("%");
+
+ size_t len = strlen(tmp.c_str());
+ tmp2.check_size((len+1) * 2);
+ db_escape_string(jcr, db, tmp2.c_str(), tmp.c_str(), len);
+
+ if (init) {
+ query.strcat(" UNION ");
+ }
+
+ Mmsg(tmp, "SELECT JobId, JobTDate, File.FileIndex, File.FilenameId, "
+ "File.PathId, FileId "
+ "FROM Path JOIN File USING (PathId) JOIN Job USING (JobId) "
+ "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s) ",
+ tmp2.c_str(), jobids);
+ query.strcat(tmp.c_str());
+ init = true;
+
+ query.strcat(" UNION ");
+
+ /* A directory can have files from a BaseJob */
+ Mmsg(tmp, "SELECT File.JobId, JobTDate, BaseFiles.FileIndex, "
+ "File.FilenameId, File.PathId, BaseFiles.FileId "
+ "FROM BaseFiles "
+ "JOIN File USING (FileId) "
+ "JOIN Job ON (BaseFiles.JobId = Job.JobId) "
+ "JOIN Path USING (PathId) "
+ "WHERE Path.Path LIKE '%s' AND BaseFiles.JobId IN (%s) ",
+ tmp2.c_str(), jobids);
+ query.strcat(tmp.c_str());
+ }
+
+ /* expect jobid,fileindex */
+ int64_t prev_jobid=0;
+ while (get_next_id_from_list(&hardlink, &jobid) == 1) {
+ if (get_next_id_from_list(&hardlink, &id) != 1) {
+ Dmsg0(dbglevel, "hardlink should be two by two\n");
+ return false;
+ }
+ if (jobid != prev_jobid) { /* new job */
+ if (prev_jobid == 0) { /* first jobid */
+ if (init) {
+ query.strcat(" UNION ");
+ }
+ } else { /* end last job, start new one */
+ tmp.strcat(") UNION ");
+ query.strcat(tmp.c_str());
+ }
+ Mmsg(tmp, "SELECT JobId, JobTDate, FileIndex, FilenameId, "
+ "PathId, FileId "
+ "FROM File JOIN Job USING (JobId) WHERE JobId = %lld "
+ "AND FileIndex IN (%lld", jobid, id);
+ prev_jobid = jobid;
+
+ } else { /* same job, add new findex */
+ Mmsg(tmp2, ", %lld", id);
+ tmp.strcat(tmp2.c_str());
+ }
+ }
+
+ if (prev_jobid != 0) { /* end last job */
+ tmp.strcat(") ");
+ query.strcat(tmp.c_str());
+ init = true;
+ }
+
+ Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+
+ if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
+ Dmsg0(dbglevel, "Can't execute q\n");
+ goto bail_out;
+ }
+
+ /* TODO: handle basejob and SQLite3 */
+ Mmsg(query, sql_bvfs_select[db_get_type_index(db)], output_table, output_table);
+
+ /* TODO: handle jobid filter */
+ Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+ if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
+ Dmsg0(dbglevel, "Can't execute q\n");
+ goto bail_out;
+ }
+
+ /* MySQL need it */
+ if (db_get_type_index(db) == SQL_TYPE_MYSQL) {
+ Mmsg(query, "CREATE INDEX idx_%s ON b2%s (JobId)",
+ output_table, output_table);
+ Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
+ if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
+ Dmsg0(dbglevel, "Can't execute q\n");
+ goto bail_out;
+ }
+ }
+
+ ret = true;
+
+bail_out:
+ Mmsg(query, "DROP TABLE btemp%s", output_table);
+ db_sql_query(db, query.c_str(), NULL, NULL);
+ return ret;
+}
+
+#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL || HAVE_INGRES || HAVE_DBI */