2 Bacula® - The Network Backup Solution
4 Copyright (C) 2009-2009 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 two of the GNU 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 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.
29 #define __SQL_C /* indicate that this is sql.c */
32 #include "cats/cats.h"
33 #include "lib/htable.h"
37 #define dbglevel_sql 15
39 static int result_handler(void *ctx, int fields, char **row)
42 Pmsg4(0, "%s\t%s\t%s\t%s\n",
43 row[0], row[1], row[2], row[3]);
44 } else if (fields == 5) {
45 Pmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
46 row[0], row[1], row[2], row[3], row[4]);
47 } else if (fields == 6) {
48 Pmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
49 row[0], row[1], row[2], row[3], row[4], row[5]);
50 } else if (fields == 7) {
51 Pmsg7(0, "%s\t%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], row[6]);
57 Bvfs::Bvfs(JCR *j, B_DB *mdb) {
60 db = mdb; /* need to inc ref count */
61 jobids = get_pool_memory(PM_NAME);
62 pattern = get_pool_memory(PM_NAME);
63 *pattern = *jobids = 0;
64 dir_filenameid = pwd_id = offset = 0;
65 see_copies = see_all_version = false;
68 list_entries = result_handler;
73 free_pool_memory(jobids);
74 free_pool_memory(pattern);
80 * TODO: Find a way to let the user choose how he wants to display
81 * files and directories
86 * Working Object to store PathId already seen (avoid
87 * database queries), equivalent to %cache_ppathid in perl
96 htable *cache_ppathid;
101 cache_ppathid = (htable *)malloc(sizeof(htable));
102 cache_ppathid->init(&link, &link, NITEMS);
104 nodes = (hlink *) malloc(max_node * sizeof (hlink));
109 if (nb_node >= max_node) {
111 nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
113 return nodes + nb_node++;
116 bool lookup(char *pathid) {
117 bool ret = cache_ppathid->lookup(pathid) != NULL;
121 void insert(char *pathid) {
122 hlink *h = get_hlink();
123 cache_ppathid->insert(pathid, h);
127 cache_ppathid->destroy();
133 /* Return the parent_dir with the trailing / (update the given string)
134 * TODO: see in the rest of bacula if we don't have already this function
140 char *bvfs_parent_dir(char *path)
143 int len = strlen(path) - 1;
145 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
151 while (p > path && !IsPathSeparator(*p)) {
159 /* Return the basename of the with the trailing /
160 * TODO: see in the rest of bacula if we don't have
161 * this function already
163 char *bvfs_basename_dir(char *path)
166 int len = strlen(path) - 1;
168 if (path[len] == '/') { /* if directory, skip last / */
174 while (p > path && !IsPathSeparator(*p)) {
177 p = p+1; /* skip first / */
182 static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
183 pathid_cache &ppathid_cache,
184 char *org_pathid, char *path)
186 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
189 char *bkp = mdb->path;
190 strncpy(pathid, org_pathid, sizeof(pathid));
192 /* Does the ppathid exist for this ? we use a memory cache... In order to
193 * avoid the full loop, we consider that if a dir is allready in the
194 * brestore_pathhierarchy table, then there is no need to calculate all the
197 while (path && *path)
199 if (!ppathid_cache.lookup(pathid))
202 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
205 QUERY_DB(jcr, mdb, mdb->cmd);
206 /* Do we have a result ? */
207 if (sql_num_rows(mdb) > 0) {
208 ppathid_cache.insert(pathid);
209 /* This dir was in the db ...
210 * It means we can leave, the tree has allready been built for
215 /* search or create parent PathId in Path table */
216 mdb->path = bvfs_parent_dir(path);
217 mdb->pnl = strlen(mdb->path);
218 if (!db_create_path_record(jcr, mdb, &parent)) {
221 ppathid_cache.insert(pathid);
224 "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
226 pathid, (uint64_t) parent.PathId);
228 INSERT_DB(jcr, mdb, mdb->cmd);
230 edit_uint64(parent.PathId, pathid);
231 path = mdb->path; /* already done */
234 /* It's allready in the cache. We can leave, no time to waste here,
235 * all the parent dirs have allready been done
247 * Internal function to update path_hierarchy cache with a shared pathid cache
249 static void update_path_hierarchy_cache(JCR *jcr,
251 pathid_cache &ppathid_cache,
254 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
258 edit_uint64(JobId, jobid);
261 db_start_transaction(jcr, mdb);
263 Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
265 if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
266 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
270 /* Inserting path records for JobId */
271 Mmsg(mdb->cmd, "INSERT INTO brestore_pathvisibility (PathId, JobId) "
272 "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
274 QUERY_DB(jcr, mdb, mdb->cmd);
277 /* Now we have to do the directory recursion stuff to determine missing
278 * visibility We try to avoid recursion, to be as fast as possible We also
279 * only work on not allready hierarchised directories...
282 "SELECT brestore_pathvisibility.PathId, Path "
283 "FROM brestore_pathvisibility "
284 "JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) "
285 "LEFT JOIN brestore_pathhierarchy "
286 "ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) "
287 "WHERE brestore_pathvisibility.JobId = %s "
288 "AND brestore_pathhierarchy.PathId IS NULL "
289 "ORDER BY Path", jobid);
290 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
291 QUERY_DB(jcr, mdb, mdb->cmd);
293 /* TODO: I need to reuse the DB connection without emptying the result
294 * So, now i'm copying the result in memory to be able to query the
295 * catalog descriptor again.
297 num = sql_num_rows(mdb);
299 char **result = (char **)malloc (num * 2 * sizeof(char *));
303 while((row = sql_fetch_row(mdb))) {
304 result[i++] = bstrdup(row[0]);
305 result[i++] = bstrdup(row[1]);
310 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
319 "INSERT INTO brestore_pathvisibility (PathId, JobId) "
320 "SELECT a.PathId,%s "
322 "SELECT DISTINCT h.PPathId AS PathId "
323 "FROM brestore_pathhierarchy AS h "
324 "JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId) "
325 "WHERE p.JobId=%s) AS a LEFT JOIN "
327 "FROM brestore_pathvisibility "
328 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
329 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
332 QUERY_DB(jcr, mdb, mdb->cmd);
333 } while (sql_affected_rows(mdb) > 0);
335 Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
336 INSERT_DB(jcr, mdb, mdb->cmd);
339 db_end_transaction(jcr, mdb);
344 * Find an store the filename descriptor for empty directories Filename.Name=''
346 DBId_t Bvfs::get_dir_filenameid()
349 if (dir_filenameid) {
350 return dir_filenameid;
353 Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
354 db_sql_query(db, q.c_str(), db_int_handler, &id);
356 return dir_filenameid;
359 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
363 db_start_transaction(jcr, mdb);
365 Mmsg(mdb->cmd, "SELECT 1 from brestore_knownjobid LIMIT 1");
366 /* TODO: Add this code in the make_bacula_table script */
367 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
368 Dmsg0(dbglevel, "Creating cache table\n");
370 "CREATE TABLE brestore_knownjobid ("
371 "JobId integer NOT NULL, "
372 "CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId))");
373 QUERY_DB(jcr, mdb, mdb->cmd);
376 "CREATE TABLE brestore_pathhierarchy ( "
377 "PathId integer NOT NULL, "
378 "PPathId integer NOT NULL, "
379 "CONSTRAINT brestore_pathhierarchy_pkey "
380 "PRIMARY KEY (PathId))");
381 QUERY_DB(jcr, mdb, mdb->cmd);
384 "CREATE INDEX brestore_pathhierarchy_ppathid "
385 "ON brestore_pathhierarchy (PPathId)");
386 QUERY_DB(jcr, mdb, mdb->cmd);
389 "CREATE TABLE brestore_pathvisibility ("
390 "PathId integer NOT NULL, "
391 "JobId integer NOT NULL, "
392 "Size int8 DEFAULT 0, "
393 "Files int4 DEFAULT 0, "
394 "CONSTRAINT brestore_pathvisibility_pkey "
395 "PRIMARY KEY (JobId, PathId))");
396 QUERY_DB(jcr, mdb, mdb->cmd);
399 "CREATE INDEX brestore_pathvisibility_jobid "
400 "ON brestore_pathvisibility (JobId)");
401 QUERY_DB(jcr, mdb, mdb->cmd);
405 POOLMEM *jobids = get_pool_memory(PM_NAME);
409 "SELECT JobId from Job "
410 "WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) "
411 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
414 db_sql_query(mdb, mdb->cmd, db_get_int_handler, jobids);
416 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids);
418 db_end_transaction(jcr, mdb);
419 db_start_transaction(jcr, mdb);
420 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
422 "DELETE FROM brestore_pathvisibility "
424 "(SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
425 nb = DELETE_DB(jcr, mdb, mdb->cmd);
426 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
428 Dmsg0(dbglevel, "Cleaning knownjobid\n");
430 "DELETE FROM brestore_knownjobid "
432 "(SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
433 nb = DELETE_DB(jcr, mdb, mdb->cmd);
434 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
436 db_end_transaction(jcr, mdb);
440 * Update the bvfs cache for given jobids (1,2,3,4)
443 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
445 pathid_cache ppathid_cache;
450 int stat = get_next_jobid_from_list(&p, &JobId);
457 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t) JobId);
458 update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
463 * Update the bvfs cache for current jobids
465 void Bvfs::update_cache()
467 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
470 /* Change the current directory, returns true if the path exists */
471 bool Bvfs::ch_dir(char *path)
473 pm_strcpy(db->path, path);
474 db->pnl = strlen(db->path);
475 pwd_id = db_get_path_record(jcr, db);
480 * Get all file versions for a specified client
482 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, char *client)
484 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
485 (uint64_t)fnid, client);
486 char ed1[50], ed2[50];
489 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
491 Mmsg(q, " AND Job.Type = 'B' ");
497 "SELECT File.JobId, File.FileId, File.LStat, "
498 "File.Md5, Media.VolumeName, Media.InChanger "
499 "FROM File, Job, Client, JobMedia, Media "
500 "WHERE File.FilenameId = %s "
501 "AND File.PathId=%s "
502 "AND File.JobId = Job.JobId "
503 "AND Job.ClientId = Client.ClientId "
504 "AND Job.JobId = JobMedia.JobId "
505 "AND File.FileIndex >= JobMedia.FirstIndex "
506 "AND File.FileIndex <= JobMedia.LastIndex "
507 "AND JobMedia.MediaId = Media.MediaId "
508 "AND Client.Name = '%s' "
509 "%s ORDER BY FileId LIMIT %d OFFSET %d"
510 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
513 db_sql_query(db, query.c_str(), list_entries, user_data);
516 DBId_t Bvfs::get_root()
519 return db_get_path_record(jcr, db);
523 * Retrieve . and .. information
525 void Bvfs::ls_special_dirs()
527 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
528 char ed1[50], ed2[50];
532 if (!dir_filenameid) {
533 get_dir_filenameid();
538 "((SELECT PPathId AS PathId, '..' AS Path "
539 "FROM brestore_pathhierarchy "
540 "WHERE PathId = %s) "
542 "(SELECT %s AS PathId, '.' AS Path))",
543 edit_uint64(pwd_id, ed1), ed1);
547 "SELECT tmp.PathId, tmp.Path, JobId, LStat "
548 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
549 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
550 "File1.LStat AS LStat FROM File AS File1 "
551 "WHERE File1.FilenameId = %s "
552 "AND File1.JobId IN (%s)) AS listfile1 "
553 "ON (tmp.PathId = listfile1.PathId) "
554 "ORDER BY tmp.Path, JobId DESC ",
555 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
557 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
558 db_sql_query(db, query2.c_str(), list_entries, user_data);
563 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
564 char ed1[50], ed2[50];
571 Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
574 if (!dir_filenameid) {
575 get_dir_filenameid();
578 /* Let's retrieve the list of the visible dirs in this dir ...
579 * First, I need the empty filenameid to locate efficiently
580 * the dirs in the file table
581 * my $dir_filenameid = $self->get_dir_filenameid();
583 /* Then we get all the dir entries from File ... */
587 "SELECT PathId, Path, JobId, LStat FROM ( "
588 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
589 "lower(Path1.Path) AS lpath, "
590 "listfile1.JobId AS JobId, listfile1.LStat AS LStat "
592 "SELECT DISTINCT brestore_pathhierarchy1.PathId AS PathId "
593 "FROM brestore_pathhierarchy AS brestore_pathhierarchy1 "
594 "JOIN Path AS Path2 "
595 "ON (brestore_pathhierarchy1.PathId = Path2.PathId) "
596 "JOIN brestore_pathvisibility AS brestore_pathvisibility1 "
597 "ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId) "
598 "WHERE brestore_pathhierarchy1.PPathId = %s "
599 "AND brestore_pathvisibility1.jobid IN (%s) "
602 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
604 "LEFT JOIN ( " /* get attributes if any */
605 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
606 "File1.LStat AS LStat FROM File AS File1 "
607 "WHERE File1.FilenameId = %s "
608 "AND File1.JobId IN (%s)) AS listfile1 "
609 "ON (listpath1.PathId = listfile1.PathId) "
610 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
611 edit_uint64(pwd_id, ed1),
614 edit_uint64(dir_filenameid, ed2),
618 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
619 db_sql_query(db, query.c_str(), list_entries, user_data);
622 void Bvfs::ls_files()
624 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
636 Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
640 Mmsg(query, // 0 1 2 3 4
641 "SELECT File.FilenameId, listfiles.Name, File.JobId, File.LStat, listfiles.id "
643 "SELECT Filename.Name as Name, max(File.FileId) as id "
644 "FROM File, Filename "
645 "WHERE File.FilenameId = Filename.FilenameId "
646 "AND Filename.Name != '' "
647 "AND File.PathId = %s "
648 "AND File.JobId IN (%s) "
650 "GROUP BY Filename.Name "
651 "ORDER BY Filename.Name LIMIT %d OFFSET %d "
653 "WHERE File.FileId = listfiles.id",
654 edit_uint64(pwd_id, ed1),
659 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
660 db_sql_query(db, query.c_str(), list_entries, user_data);