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 prev_dir = get_pool_memory(PM_NAME);
62 jobids = get_pool_memory(PM_NAME);
63 pattern = get_pool_memory(PM_NAME);
64 *prev_dir = *pattern = *jobids = 0;
65 dir_filenameid = pwd_id = offset = 0;
66 see_copies = see_all_version = false;
69 list_entries = result_handler;
74 free_pool_memory(jobids);
75 free_pool_memory(pattern);
76 free_pool_memory(prev_dir);
82 * TODO: Find a way to let the user choose how he wants to display
83 * files and directories
88 * Working Object to store PathId already seen (avoid
89 * database queries), equivalent to %cache_ppathid in perl
98 htable *cache_ppathid;
103 cache_ppathid = (htable *)malloc(sizeof(htable));
104 cache_ppathid->init(&link, &link, NITEMS);
106 nodes = (hlink *) malloc(max_node * sizeof (hlink));
111 if (nb_node >= max_node) {
113 nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
115 return nodes + nb_node++;
118 bool lookup(char *pathid) {
119 bool ret = cache_ppathid->lookup(pathid) != NULL;
123 void insert(char *pathid) {
124 hlink *h = get_hlink();
125 cache_ppathid->insert(pathid, h);
129 cache_ppathid->destroy();
135 /* Return the parent_dir with the trailing / (update the given string)
136 * TODO: see in the rest of bacula if we don't have already this function
142 char *bvfs_parent_dir(char *path)
145 int len = strlen(path) - 1;
147 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
153 while (p > path && !IsPathSeparator(*p)) {
161 /* Return the basename of the with the trailing /
162 * TODO: see in the rest of bacula if we don't have
163 * this function already
165 char *bvfs_basename_dir(char *path)
168 int len = strlen(path) - 1;
170 if (path[len] == '/') { /* if directory, skip last / */
176 while (p > path && !IsPathSeparator(*p)) {
180 p++; /* skip first / */
186 static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
187 pathid_cache &ppathid_cache,
188 char *org_pathid, char *path)
190 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
193 char *bkp = mdb->path;
194 strncpy(pathid, org_pathid, sizeof(pathid));
196 /* Does the ppathid exist for this ? we use a memory cache... In order to
197 * avoid the full loop, we consider that if a dir is allready in the
198 * brestore_pathhierarchy table, then there is no need to calculate all the
201 while (path && *path)
203 if (!ppathid_cache.lookup(pathid))
206 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
209 QUERY_DB(jcr, mdb, mdb->cmd);
210 /* Do we have a result ? */
211 if (sql_num_rows(mdb) > 0) {
212 ppathid_cache.insert(pathid);
213 /* This dir was in the db ...
214 * It means we can leave, the tree has allready been built for
219 /* search or create parent PathId in Path table */
220 mdb->path = bvfs_parent_dir(path);
221 mdb->pnl = strlen(mdb->path);
222 if (!db_create_path_record(jcr, mdb, &parent)) {
225 ppathid_cache.insert(pathid);
228 "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
230 pathid, (uint64_t) parent.PathId);
232 INSERT_DB(jcr, mdb, mdb->cmd);
234 edit_uint64(parent.PathId, pathid);
235 path = mdb->path; /* already done */
238 /* It's allready in the cache. We can leave, no time to waste here,
239 * all the parent dirs have allready been done
251 * Internal function to update path_hierarchy cache with a shared pathid cache
253 static void update_path_hierarchy_cache(JCR *jcr,
255 pathid_cache &ppathid_cache,
258 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
262 edit_uint64(JobId, jobid);
265 db_start_transaction(jcr, mdb);
267 Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
269 if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
270 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
274 /* Inserting path records for JobId */
275 Mmsg(mdb->cmd, "INSERT INTO brestore_pathvisibility (PathId, JobId) "
276 "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
278 QUERY_DB(jcr, mdb, mdb->cmd);
281 /* Now we have to do the directory recursion stuff to determine missing
282 * visibility We try to avoid recursion, to be as fast as possible We also
283 * only work on not allready hierarchised directories...
286 "SELECT brestore_pathvisibility.PathId, Path "
287 "FROM brestore_pathvisibility "
288 "JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) "
289 "LEFT JOIN brestore_pathhierarchy "
290 "ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) "
291 "WHERE brestore_pathvisibility.JobId = %s "
292 "AND brestore_pathhierarchy.PathId IS NULL "
293 "ORDER BY Path", jobid);
294 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
295 QUERY_DB(jcr, mdb, mdb->cmd);
297 /* TODO: I need to reuse the DB connection without emptying the result
298 * So, now i'm copying the result in memory to be able to query the
299 * catalog descriptor again.
301 num = sql_num_rows(mdb);
303 char **result = (char **)malloc (num * 2 * sizeof(char *));
307 while((row = sql_fetch_row(mdb))) {
308 result[i++] = bstrdup(row[0]);
309 result[i++] = bstrdup(row[1]);
314 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
323 "INSERT INTO brestore_pathvisibility (PathId, JobId) "
324 "SELECT a.PathId,%s "
326 "SELECT DISTINCT h.PPathId AS PathId "
327 "FROM brestore_pathhierarchy AS h "
328 "JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId) "
329 "WHERE p.JobId=%s) AS a LEFT JOIN "
331 "FROM brestore_pathvisibility "
332 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
333 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
336 QUERY_DB(jcr, mdb, mdb->cmd);
337 } while (sql_affected_rows(mdb) > 0);
339 Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
340 INSERT_DB(jcr, mdb, mdb->cmd);
343 db_end_transaction(jcr, mdb);
348 * Find an store the filename descriptor for empty directories Filename.Name=''
350 DBId_t Bvfs::get_dir_filenameid()
353 if (dir_filenameid) {
354 return dir_filenameid;
357 Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
358 db_sql_query(db, q.c_str(), db_int_handler, &id);
360 return dir_filenameid;
363 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
367 db_start_transaction(jcr, mdb);
369 Mmsg(mdb->cmd, "SELECT 1 from brestore_knownjobid LIMIT 1");
370 /* TODO: Add this code in the make_bacula_table script */
371 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
372 Dmsg0(dbglevel, "Creating cache table\n");
374 "CREATE TABLE brestore_knownjobid ("
375 "JobId integer NOT NULL, "
376 "CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId))");
377 QUERY_DB(jcr, mdb, mdb->cmd);
380 "CREATE TABLE brestore_pathhierarchy ( "
381 "PathId integer NOT NULL, "
382 "PPathId integer NOT NULL, "
383 "CONSTRAINT brestore_pathhierarchy_pkey "
384 "PRIMARY KEY (PathId))");
385 QUERY_DB(jcr, mdb, mdb->cmd);
388 "CREATE INDEX brestore_pathhierarchy_ppathid "
389 "ON brestore_pathhierarchy (PPathId)");
390 QUERY_DB(jcr, mdb, mdb->cmd);
393 "CREATE TABLE brestore_pathvisibility ("
394 "PathId integer NOT NULL, "
395 "JobId integer NOT NULL, "
396 "Size int8 DEFAULT 0, "
397 "Files int4 DEFAULT 0, "
398 "CONSTRAINT brestore_pathvisibility_pkey "
399 "PRIMARY KEY (JobId, PathId))");
400 QUERY_DB(jcr, mdb, mdb->cmd);
403 "CREATE INDEX brestore_pathvisibility_jobid "
404 "ON brestore_pathvisibility (JobId)");
405 QUERY_DB(jcr, mdb, mdb->cmd);
409 POOLMEM *jobids = get_pool_memory(PM_NAME);
413 "SELECT JobId from Job "
414 "WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) "
415 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
418 db_sql_query(mdb, mdb->cmd, db_get_int_handler, jobids);
420 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids);
422 db_end_transaction(jcr, mdb);
423 db_start_transaction(jcr, mdb);
424 Dmsg0(dbglevel, "Cleaning pathvisibility\n");
426 "DELETE FROM brestore_pathvisibility "
428 "(SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
429 nb = DELETE_DB(jcr, mdb, mdb->cmd);
430 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
432 Dmsg0(dbglevel, "Cleaning knownjobid\n");
434 "DELETE FROM brestore_knownjobid "
436 "(SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
437 nb = DELETE_DB(jcr, mdb, mdb->cmd);
438 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
440 db_end_transaction(jcr, mdb);
441 free_pool_memory(jobids);
445 * Update the bvfs cache for given jobids (1,2,3,4)
448 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
450 pathid_cache ppathid_cache;
455 int stat = get_next_jobid_from_list(&p, &JobId);
462 Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t) JobId);
463 update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
468 * Update the bvfs cache for current jobids
470 void Bvfs::update_cache()
472 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
475 /* Change the current directory, returns true if the path exists */
476 bool Bvfs::ch_dir(const char *path)
478 pm_strcpy(db->path, path);
479 db->pnl = strlen(db->path);
480 ch_dir(db_get_path_record(jcr, db));
485 * Get all file versions for a specified client
487 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
489 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
490 (uint64_t)fnid, client);
491 char ed1[50], ed2[50];
494 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
496 Mmsg(q, " AND Job.Type = 'B' ");
502 "SELECT File.FileId, File.Md5, File.JobId, File.LStat, "
504 "Media.VolumeName, Media.InChanger "
505 "FROM File, Job, Client, JobMedia, Media "
506 "WHERE File.FilenameId = %s "
507 "AND File.PathId=%s "
508 "AND File.JobId = Job.JobId "
509 "AND Job.ClientId = Client.ClientId "
510 "AND Job.JobId = JobMedia.JobId "
511 "AND File.FileIndex >= JobMedia.FirstIndex "
512 "AND File.FileIndex <= JobMedia.LastIndex "
513 "AND JobMedia.MediaId = Media.MediaId "
514 "AND Client.Name = '%s' "
515 "%s ORDER BY FileId LIMIT %d OFFSET %d"
516 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
518 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
519 db_sql_query(db, query.c_str(), list_entries, user_data);
522 DBId_t Bvfs::get_root()
525 return db_get_path_record(jcr, db);
528 static int path_handler(void *ctx, int fields, char **row)
530 Bvfs *fs = (Bvfs *) ctx;
531 return fs->_handle_path(ctx, fields, row);
534 int Bvfs::_handle_path(void *ctx, int fields, char **row)
536 if (fields == BVFS_DIR_RECORD) {
537 /* can have the same path 2 times */
538 if (strcmp(row[BVFS_Name], prev_dir)) {
539 pm_strcpy(prev_dir, row[BVFS_Name]);
540 return list_entries(user_data, fields, row);
547 * Retrieve . and .. information
549 void Bvfs::ls_special_dirs()
551 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
552 char ed1[50], ed2[50];
556 if (!dir_filenameid) {
557 get_dir_filenameid();
560 /* Will fetch directories */
565 "((SELECT PPathId AS PathId, '..' AS Path "
566 "FROM brestore_pathhierarchy "
567 "WHERE PathId = %s) "
569 "(SELECT %s AS PathId, '.' AS Path))",
570 edit_uint64(pwd_id, ed1), ed1);
574 "SELECT tmp.PathId, tmp.Path, JobId, LStat "
575 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
576 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
577 "File1.LStat AS LStat FROM File AS File1 "
578 "WHERE File1.FilenameId = %s "
579 "AND File1.JobId IN (%s)) AS listfile1 "
580 "ON (tmp.PathId = listfile1.PathId) "
581 "ORDER BY tmp.Path, JobId DESC ",
582 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
584 Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
585 db_sql_query(db, query2.c_str(), path_handler, this);
588 /* Returns true if we have dirs to read */
591 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
592 char ed1[50], ed2[50];
599 Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
602 if (!dir_filenameid) {
603 get_dir_filenameid();
606 /* the sql query displays same directory multiple time, take the first one */
609 /* Let's retrieve the list of the visible dirs in this dir ...
610 * First, I need the empty filenameid to locate efficiently
611 * the dirs in the file table
612 * my $dir_filenameid = $self->get_dir_filenameid();
614 /* Then we get all the dir entries from File ... */
618 "SELECT PathId, Path, JobId, LStat FROM ( "
619 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
620 "lower(Path1.Path) AS lpath, "
621 "listfile1.JobId AS JobId, listfile1.LStat AS LStat "
623 "SELECT DISTINCT brestore_pathhierarchy1.PathId AS PathId "
624 "FROM brestore_pathhierarchy AS brestore_pathhierarchy1 "
625 "JOIN Path AS Path2 "
626 "ON (brestore_pathhierarchy1.PathId = Path2.PathId) "
627 "JOIN brestore_pathvisibility AS brestore_pathvisibility1 "
628 "ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId) "
629 "WHERE brestore_pathhierarchy1.PPathId = %s "
630 "AND brestore_pathvisibility1.jobid IN (%s) "
633 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
635 "LEFT JOIN ( " /* get attributes if any */
636 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
637 "File1.LStat AS LStat FROM File AS File1 "
638 "WHERE File1.FilenameId = %s "
639 "AND File1.JobId IN (%s)) AS listfile1 "
640 "ON (listpath1.PathId = listfile1.PathId) "
641 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
642 edit_uint64(pwd_id, ed1),
645 edit_uint64(dir_filenameid, ed2),
649 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
652 db_sql_query(db, query.c_str(), path_handler, this);
653 nb_record = db->num_rows;
656 return nb_record == limit;
659 /* Returns true if we have files to read */
660 bool Bvfs::ls_files()
662 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
674 Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
678 Mmsg(query, // 0 1 2 3 4
679 "SELECT File.FilenameId, listfiles.Name, File.JobId, File.LStat, listfiles.id "
681 "SELECT Filename.Name as Name, max(File.FileId) as id "
682 "FROM File, Filename "
683 "WHERE File.FilenameId = Filename.FilenameId "
684 "AND Filename.Name != '' "
685 "AND File.PathId = %s "
686 "AND File.JobId IN (%s) "
688 "GROUP BY Filename.Name "
689 "ORDER BY Filename.Name LIMIT %d OFFSET %d "
691 "WHERE File.FileId = listfiles.id",
692 edit_uint64(pwd_id, ed1),
697 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
700 db_sql_query(db, query.c_str(), list_entries, user_data);
701 nb_record = db->num_rows;
704 return nb_record == limit;