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
40 * TODO: Find a way to let the user choose how he wants to display
41 * files and directories
46 * Working Object to store PathId already seen (avoid
47 * database queries), equivalent to %cache_ppathid in perl
56 htable *cache_ppathid;
61 cache_ppathid = (htable *)malloc(sizeof(htable));
62 cache_ppathid->init(&link, &link, NITEMS);
64 nodes = (hlink *) malloc(max_node * sizeof (hlink));
69 if (nb_node >= max_node) {
71 nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
73 return nodes + nb_node++;
76 bool lookup(char *pathid) {
77 bool ret = cache_ppathid->lookup(pathid) != NULL;
81 void insert(char *pathid) {
82 hlink *h = get_hlink();
83 cache_ppathid->insert(pathid, h);
87 cache_ppathid->destroy();
93 /* Return the parent_dir with the trailing / (update the given string)
94 * TODO: see in the rest of bacula if we don't have already this function
100 char *bvfs_parent_dir(char *path)
103 int len = strlen(path) - 1;
105 if (len >= 0 && path[len] == '/') { /* if directory, skip last / */
111 while (p > path && !IsPathSeparator(*p)) {
121 /* Return the basename of the with the trailing / (update the given string)
122 * TODO: see in the rest of bacula if we don't have
123 * this function already
125 char *bvfs_basename_dir(char *path)
128 int len = strlen(path) - 1;
130 if (path[len] == '/') { /* if directory, skip last / */
136 while (p > path && !IsPathSeparator(*p)) {
139 p = p+1; /* skip first / */
144 static void build_path_hierarchy(JCR *jcr, B_DB *mdb,
145 pathid_cache &ppathid_cache,
146 char *org_pathid, char *path)
148 Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
151 char *bkp = mdb->path;
152 strncpy(pathid, org_pathid, sizeof(pathid));
154 /* Does the ppathid exist for this ? we use a memory cache... In order to
155 * avoid the full loop, we consider that if a dir is allready in the
156 * brestore_pathhierarchy table, then there is no need to calculate all the
159 while (path && *path)
161 if (!ppathid_cache.lookup(pathid))
164 "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
167 QUERY_DB(jcr, mdb, mdb->cmd);
168 /* Do we have a result ? */
169 if (sql_num_rows(mdb) > 0) {
170 ppathid_cache.insert(pathid);
171 /* This dir was in the db ...
172 * It means we can leave, the tree has allready been built for
177 /* search or create parent PathId in Path table */
178 mdb->path = bvfs_parent_dir(path);
179 mdb->pnl = strlen(mdb->path);
180 if (!db_create_path_record(jcr, mdb, &parent)) {
183 ppathid_cache.insert(pathid);
186 "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
188 pathid, (uint64_t) parent.PathId);
190 INSERT_DB(jcr, mdb, mdb->cmd);
192 edit_uint64(parent.PathId, pathid);
193 path = mdb->path; /* already done */
196 /* It's allready in the cache. We can leave, no time to waste here,
197 * all the parent dirs have allready been done
209 * Internal function to update path_hierarchy cache with a shared pathid cache
211 static void update_path_hierarchy_cache(JCR *jcr,
213 pathid_cache &ppathid_cache,
216 Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
220 edit_uint64(JobId, jobid);
223 db_start_transaction(jcr, mdb);
225 Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
227 if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
228 Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
232 /* Inserting path records for JobId */
233 Mmsg(mdb->cmd, "INSERT INTO brestore_pathvisibility (PathId, JobId) "
234 "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
236 QUERY_DB(jcr, mdb, mdb->cmd);
239 /* Now we have to do the directory recursion stuff to determine missing
240 * visibility We try to avoid recursion, to be as fast as possible We also
241 * only work on not allready hierarchised directories...
244 "SELECT brestore_pathvisibility.PathId, Path "
245 "FROM brestore_pathvisibility "
246 "JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) "
247 "LEFT JOIN brestore_pathhierarchy "
248 "ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) "
249 "WHERE brestore_pathvisibility.JobId = %s "
250 "AND brestore_pathhierarchy.PathId IS NULL "
251 "ORDER BY Path", jobid);
252 Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
253 QUERY_DB(jcr, mdb, mdb->cmd);
255 /* TODO: I need to reuse the DB connection without emptying the result
256 * So, now i'm copying the result in memory to be able to query the
257 * catalog descriptor again.
259 num = sql_num_rows(mdb);
261 char **result = (char **)malloc (num * 2 * sizeof(char *));
265 while((row = sql_fetch_row(mdb))) {
266 result[i++] = bstrdup(row[0]);
267 result[i++] = bstrdup(row[1]);
272 build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
281 "INSERT INTO brestore_pathvisibility (PathId, JobId) "
282 "SELECT a.PathId,%s "
284 "SELECT DISTINCT h.PPathId AS PathId "
285 "FROM brestore_pathhierarchy AS h "
286 "JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId) "
287 "WHERE p.JobId=%s) AS a LEFT JOIN "
289 "FROM brestore_pathvisibility "
290 "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
291 "WHERE b.PathId IS NULL", jobid, jobid, jobid);
294 QUERY_DB(jcr, mdb, mdb->cmd);
295 } while (sql_affected_rows(mdb) > 0);
297 Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
298 INSERT_DB(jcr, mdb, mdb->cmd);
301 db_end_transaction(jcr, mdb);
307 * Find an store the filename descriptor for empty directories Filename.Name=''
309 DBId_t Bvfs::get_dir_filenameid()
312 if (dir_filenameid) {
313 return dir_filenameid;
316 Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
317 db_sql_query(db, q.c_str(), db_int_handler, &id);
319 return dir_filenameid;
322 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
326 db_start_transaction(jcr, mdb);
328 POOLMEM *jobids = get_pool_memory(PM_NAME);
332 "SELECT JobId from Job "
333 "WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) "
334 "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
337 db_sql_query(mdb, mdb->cmd, db_get_int_handler, jobids);
339 bvfs_update_path_hierarchy_cache(jcr, mdb, jobids);
341 db_end_transaction(jcr, mdb);
342 db_start_transaction(jcr, mdb);
344 "DELETE FROM brestore_pathvisibility "
346 "(SELECT 1 FROM Job WHERE JobId=brestore_pathvisibility.JobId)");
347 nb = DELETE_DB(jcr, mdb, mdb->cmd);
348 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
351 "DELETE FROM brestore_knownjobid "
353 "(SELECT 1 FROM Job WHERE JobId=brestore_knownjobid.JobId)");
354 nb = DELETE_DB(jcr, mdb, mdb->cmd);
355 Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
357 db_end_transaction(jcr, mdb);
361 * Update the bvfs cache for given jobids (1,2,3,4)
364 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
366 pathid_cache ppathid_cache;
371 int stat = get_next_jobid_from_list(&p, &JobId);
379 update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
384 * Update the bvfs cache for current jobids
386 void Bvfs::update_cache()
388 bvfs_update_path_hierarchy_cache(jcr, db, jobids);
391 static int result_handler(void *ctx, int fields, char **row)
394 Dmsg4(0, "%s\t%s\t%s\t%s\n",
395 row[0], row[1], row[2], row[3]);
396 } else if (fields == 5) {
397 Dmsg5(0, "%s\t%s\t%s\t%s\t%s\n",
398 row[0], row[1], row[2], row[3], row[4]);
399 } else if (fields == 6) {
400 Dmsg6(0, "%s\t%s\t%s\t%s\t%s\t%s\n",
401 row[0], row[1], row[2], row[3], row[4], row[5]);
402 } else if (fields == 7) {
403 Dmsg7(0, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
404 row[0], row[1], row[2], row[3], row[4], row[5], row[6]);
409 static int result_path_handler(void *ctx, int fields, char **row)
412 Dmsg4(0, "%s\t%s\t%s\t%s\n",
413 row[0], bvfs_basename_dir(row[1]), row[2], row[3]);
418 /* Change the current directory, returns true if the path exists */
419 bool Bvfs::ch_dir(char *path)
421 pm_strcpy(db->path, path);
422 db->pnl = strlen(db->path);
423 pwd_id = db_get_path_record(jcr, db);
428 * Get all file versions for a specified client
430 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, char *client)
432 Dmsg3(dbglevel, "get_all_file_versions(%lld, %lld, %s)\n", (uint64_t)pathid,
433 (uint64_t)fnid, client);
434 char ed1[50], ed2[50];
437 Mmsg(q, " AND Job.Type IN ('C', 'B') ");
439 Mmsg(q, " AND Job.Type = 'B' ");
445 "SELECT File.JobId, File.FileId, File.LStat, "
446 "File.Md5, Media.VolumeName, Media.InChanger "
447 "FROM File, Job, Client, JobMedia, Media "
448 "WHERE File.FilenameId = %s "
449 "AND File.PathId=%s "
450 "AND File.JobId = Job.JobId "
451 "AND Job.ClientId = Client.ClientId "
452 "AND Job.JobId = JobMedia.JobId "
453 "AND File.FileIndex >= JobMedia.FirstIndex "
454 "AND File.FileIndex <= JobMedia.LastIndex "
455 "AND JobMedia.MediaId = Media.MediaId "
456 "AND Client.Name = '%s' "
457 "%s ORDER BY FileId LIMIT %d OFFSET %d"
458 ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
461 db_sql_query(db, query.c_str(), result_handler, this);
464 DBId_t Bvfs::get_root()
467 return db_get_path_record(jcr, db);
471 * Retrieve . and .. information
473 void Bvfs::ls_special_dirs()
475 Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
476 char ed1[50], ed2[50];
480 if (!dir_filenameid) {
481 get_dir_filenameid();
486 "((SELECT PPathId AS PathId, '..' AS Path "
487 "FROM brestore_pathhierarchy "
488 "WHERE PathId = %s) "
490 "(SELECT %s AS PathId, '.' AS Path))",
491 edit_uint64(pwd_id, ed1), ed1);
495 "SELECT tmp.PathId, tmp.Path, LStat, JobId "
496 "FROM %s AS tmp LEFT JOIN ( " // get attributes if any
497 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
498 "File1.LStat AS LStat FROM File AS File1 "
499 "WHERE File1.FilenameId = %s "
500 "AND File1.JobId IN (%s)) AS listfile1 "
501 "ON (tmp.PathId = listfile1.PathId) "
502 "ORDER BY tmp.Path, JobId DESC ",
503 query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
505 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
506 db_sql_query(db, query2.c_str(), result_handler, this);
511 Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
512 char ed1[50], ed2[50];
519 Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
522 if (!dir_filenameid) {
523 get_dir_filenameid();
526 /* Let's retrieve the list of the visible dirs in this dir ...
527 * First, I need the empty filenameid to locate efficiently
528 * the dirs in the file table
529 * my $dir_filenameid = $self->get_dir_filenameid();
531 /* Then we get all the dir entries from File ... */
534 "SELECT PathId, Path, JobId, LStat FROM ( "
535 "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
536 "lower(Path1.Path) AS lpath, "
537 "listfile1.JobId AS JobId, listfile1.LStat AS LStat "
539 "SELECT DISTINCT brestore_pathhierarchy1.PathId AS PathId "
540 "FROM brestore_pathhierarchy AS brestore_pathhierarchy1 "
541 "JOIN Path AS Path2 "
542 "ON (brestore_pathhierarchy1.PathId = Path2.PathId) "
543 "JOIN brestore_pathvisibility AS brestore_pathvisibility1 "
544 "ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId) "
545 "WHERE brestore_pathhierarchy1.PPathId = %s "
546 "AND brestore_pathvisibility1.jobid IN (%s) "
549 "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
551 "LEFT JOIN ( " /* get attributes if any */
552 "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
553 "File1.LStat AS LStat FROM File AS File1 "
554 "WHERE File1.FilenameId = %s "
555 "AND File1.JobId IN (%s)) AS listfile1 "
556 "ON (listpath1.PathId = listfile1.PathId) "
557 ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
558 edit_uint64(pwd_id, ed1),
561 edit_uint64(dir_filenameid, ed2),
565 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
566 db_sql_query(db, query.c_str(), result_path_handler, this);
569 void Bvfs::ls_files()
571 Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
583 Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
587 Mmsg(query, // 0 1 2 3 4
588 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId "
590 "SELECT Filename.Name as Name, max(File.FileId) as id "
591 "FROM File, Filename "
592 "WHERE File.FilenameId = Filename.FilenameId "
593 "AND Filename.Name != '' "
594 "AND File.PathId = %s "
595 "AND File.JobId IN (%s) "
597 "GROUP BY Filename.Name "
598 "ORDER BY Filename.Name LIMIT %d OFFSET %d "
600 "WHERE File.FileId = listfiles.id",
601 edit_uint64(pwd_id, ed1),
606 Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
607 db_sql_query(db, query.c_str(), result_handler, this);