]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/bvfs.c
Add a new Bvfs class that implements brestore instant navigation
[bacula/bacula] / bacula / src / cats / bvfs.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2009-2009 Free Software Foundation Europe e.V.
5
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.
12
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.
17
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
21    02110-1301, USA.
22
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.
27 */
28
29 #define __SQL_C                       /* indicate that this is sql.c */
30
31 #include "bacula.h"
32 #include "cats/cats.h"
33 #include "lib/htable.h"
34 #include "bvfs.h"
35
36 #define dbglevel 10
37 #define dbglevel_sql 15
38
39 /* 
40  * TODO: Find a way to let the user choose how he wants to display
41  * files and directories
42  */
43
44
45 /* 
46  * Working Object to store PathId already seen (avoid
47  * database queries), equivalent to %cache_ppathid in perl
48  */
49
50 #define NITEMS 50000
51 class pathid_cache {
52 private:
53    hlink *nodes;
54    int nb_node;
55    int max_node;
56    htable *cache_ppathid;
57
58 public:
59    pathid_cache() {
60       hlink link;
61       cache_ppathid = (htable *)malloc(sizeof(htable));
62       cache_ppathid->init(&link, &link, NITEMS);
63       max_node = NITEMS;
64       nodes = (hlink *) malloc(max_node * sizeof (hlink));
65       nb_node = 0;
66    }
67
68    hlink *get_hlink() {
69       if (nb_node >= max_node) {
70          max_node *= 2;
71          nodes = (hlink *)brealloc(nodes, sizeof(hlink) * max_node);
72       }
73       return nodes + nb_node++;
74    }
75
76    bool lookup(char *pathid) {
77       bool ret = cache_ppathid->lookup(pathid) != NULL;
78       return ret;
79    }
80    
81    void insert(char *pathid) {
82       hlink *h = get_hlink();
83       cache_ppathid->insert(pathid, h);
84    }
85
86    ~pathid_cache() {
87       cache_ppathid->destroy();
88       free(cache_ppathid);
89       free(nodes);
90    }
91 } ;
92
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
95  * dir=/tmp/toto/
96  * dir=/tmp/
97  * dir=/
98  * dir=
99  */
100 char *bvfs_parent_dir(char *path)
101 {
102    char *p = path;
103    int len = strlen(path) - 1;
104
105    if (len >= 0 && path[len] == '/') {      /* if directory, skip last / */
106       path[len] = '\0';
107    }
108
109    if (len > 0) {
110       p += len;
111       while (p > path && !IsPathSeparator(*p)) {
112          p--;
113       }
114       p[1] = '\0';
115    }
116    return path;
117 }
118
119
120
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
124  */
125 char *bvfs_basename_dir(char *path)
126 {
127    char *p = path;
128    int len = strlen(path) - 1;
129
130    if (path[len] == '/') {      /* if directory, skip last / */
131       len -= 1;
132    }
133
134    if (len > 0) {
135       p += len;
136       while (p > path && !IsPathSeparator(*p)) {
137          p--;
138       }
139       p = p+1;                  /* skip first / */
140    } 
141    return p;
142 }
143
144 static void build_path_hierarchy(JCR *jcr, B_DB *mdb, 
145                                  pathid_cache &ppathid_cache, 
146                                  char *org_pathid, char *path)
147 {
148    Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
149    char pathid[50];
150    ATTR_DBR parent;
151    char *bkp = mdb->path;
152    strncpy(pathid, org_pathid, sizeof(pathid));
153
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
157     * hierarchy
158     */
159    while (path && *path)
160    {
161       if (!ppathid_cache.lookup(pathid))
162       {
163          Mmsg(mdb->cmd, 
164               "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = %s",
165               pathid);
166
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
173              * this dir
174              */
175             goto bail_out;
176          } else {
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)) {
181                goto bail_out;
182             }
183             ppathid_cache.insert(pathid);
184             
185             Mmsg(mdb->cmd,
186                  "INSERT INTO brestore_pathhierarchy (PathId, PPathId) "
187                  "VALUES (%s,%lld)",
188                  pathid, (uint64_t) parent.PathId);
189             
190             INSERT_DB(jcr, mdb, mdb->cmd);
191
192             edit_uint64(parent.PathId, pathid);
193             path = mdb->path;   /* already done */
194          }
195       } else {
196          /* It's allready in the cache.  We can leave, no time to waste here,
197           * all the parent dirs have allready been done
198           */
199          goto bail_out;
200       }
201    }   
202
203 bail_out:
204    mdb->path = bkp;
205    mdb->fnl = 0;
206 }
207
208 /* 
209  * Internal function to update path_hierarchy cache with a shared pathid cache
210  */
211 static void update_path_hierarchy_cache(JCR *jcr,
212                                         B_DB *mdb,
213                                         pathid_cache &ppathid_cache,
214                                         JobId_t JobId)
215 {
216    Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
217
218    uint32_t num;
219    char jobid[50];
220    edit_uint64(JobId, jobid);
221  
222    db_lock(mdb);
223    db_start_transaction(jcr, mdb);
224
225    Mmsg(mdb->cmd, "SELECT 1 FROM brestore_knownjobid WHERE JobId = %s", jobid);
226    
227    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
228       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
229       goto bail_out;
230    }
231
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",
235         jobid);
236    QUERY_DB(jcr, mdb, mdb->cmd);
237
238
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...
242     */
243    Mmsg(mdb->cmd, 
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);
254
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.
258     */
259    num = sql_num_rows(mdb);
260    if (num > 0) {
261       char **result = (char **)malloc (num * 2 * sizeof(char *));
262       
263       SQL_ROW row;
264       int i=0;
265       while((row = sql_fetch_row(mdb))) {
266          result[i++] = bstrdup(row[0]);
267          result[i++] = bstrdup(row[1]);
268       }
269       
270       i=0;
271       while (num > 0) {
272          build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
273          free(result[i++]);
274          free(result[i++]);
275          num--;
276       }
277       free(result);
278    }
279    
280    Mmsg(mdb->cmd, 
281   "INSERT INTO brestore_pathvisibility (PathId, JobId)  "
282    "SELECT a.PathId,%s "
283    "FROM ( "
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 "
288        "(SELECT PathId "
289           "FROM brestore_pathvisibility "
290          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
291    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
292
293    do {
294       QUERY_DB(jcr, mdb, mdb->cmd);
295    } while (sql_affected_rows(mdb) > 0);
296    
297    Mmsg(mdb->cmd, "INSERT INTO brestore_knownjobid (JobId) VALUES (%s)", jobid);
298    INSERT_DB(jcr, mdb, mdb->cmd);
299
300 bail_out:
301    db_end_transaction(jcr, mdb);
302    db_unlock(mdb);
303 }
304
305
306 /* 
307  * Find an store the filename descriptor for empty directories Filename.Name=''
308  */
309 DBId_t Bvfs::get_dir_filenameid()
310 {
311    uint32_t id;
312    if (dir_filenameid) {
313       return dir_filenameid;
314    }
315    POOL_MEM q;
316    Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
317    db_sql_query(db, q.c_str(), db_int_handler, &id);
318    dir_filenameid = id;
319    return dir_filenameid;
320 }
321
322 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
323 {
324    uint32_t nb;
325    db_lock(mdb);
326    db_start_transaction(jcr, mdb);
327
328    POOLMEM *jobids = get_pool_memory(PM_NAME);
329    *jobids = 0;
330
331    Mmsg(mdb->cmd, 
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') "
335   "ORDER BY JobId");
336
337    db_sql_query(mdb, mdb->cmd, db_get_int_handler, jobids);
338
339    bvfs_update_path_hierarchy_cache(jcr, mdb, jobids);
340
341    db_end_transaction(jcr, mdb);
342    db_start_transaction(jcr, mdb);
343    Mmsg(mdb->cmd, 
344         "DELETE FROM brestore_pathvisibility "
345          "WHERE NOT EXISTS "
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);
349
350    Mmsg(mdb->cmd,         
351         "DELETE FROM brestore_knownjobid "
352          "WHERE NOT EXISTS "
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);
356
357    db_end_transaction(jcr, mdb);
358 }
359
360 /*
361  * Update the bvfs cache for given jobids (1,2,3,4)
362  */
363 void
364 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
365 {
366    pathid_cache ppathid_cache;
367    JobId_t JobId;
368    char *p;
369
370    for (p=jobids; ; ) {
371       int stat = get_next_jobid_from_list(&p, &JobId);
372       if (stat < 0) {
373          return;
374       }
375       if (stat == 0) {
376          break;
377       }
378
379       update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
380    }
381 }
382
383 /* 
384  * Update the bvfs cache for current jobids
385  */
386 void Bvfs::update_cache()
387 {
388    bvfs_update_path_hierarchy_cache(jcr, db, jobids);
389 }
390
391 static int result_handler(void *ctx, int fields, char **row)
392 {
393    if (fields == 4) {
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]);
405    }
406    return 0;
407 }
408
409 static int result_path_handler(void *ctx, int fields, char **row)
410 {
411    if (fields == 4) {
412       Dmsg4(0, "%s\t%s\t%s\t%s\n", 
413             row[0], bvfs_basename_dir(row[1]), row[2], row[3]);
414    }
415    return 0;
416 }
417
418 /* Change the current directory, returns true if the path exists */
419 bool Bvfs::ch_dir(char *path)
420 {
421    pm_strcpy(db->path, path);
422    db->pnl = strlen(db->path);
423    pwd_id = db_get_path_record(jcr, db); 
424    return pwd_id != 0;
425 }
426
427 /* 
428  * Get all file versions for a specified client
429  */
430 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, char *client)
431 {
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];
435    POOL_MEM q;
436    if (see_copies) {
437       Mmsg(q, " AND Job.Type IN ('C', 'B') ");
438    } else {
439       Mmsg(q, " AND Job.Type = 'B' ");
440    }
441
442    POOL_MEM query;
443
444    Mmsg(query, 
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(),
459         limit, offset);
460
461    db_sql_query(db, query.c_str(), result_handler, this);
462 }
463
464 DBId_t Bvfs::get_root()
465 {
466    *db->path = 0;
467    return db_get_path_record(jcr, db);
468 }
469
470 /* 
471  * Retrieve . and .. information
472  */
473 void Bvfs::ls_special_dirs()
474 {
475    Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
476    char ed1[50], ed2[50];
477    if (!*jobids) {
478       return;
479    }
480    if (!dir_filenameid) {
481       get_dir_filenameid();
482    }
483
484    POOL_MEM query;
485    Mmsg(query, 
486 "((SELECT PPathId AS PathId, '..' AS Path "
487     "FROM  brestore_pathhierarchy "
488    "WHERE  PathId = %s) "
489 "UNION "
490  "(SELECT %s AS PathId, '.' AS Path))",
491         edit_uint64(pwd_id, ed1), ed1);
492
493    POOL_MEM query2;
494    Mmsg(query2, 
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);
504
505    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
506    db_sql_query(db, query2.c_str(), result_handler, this);
507 }
508
509 void Bvfs::ls_dirs()
510 {
511    Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
512    char ed1[50], ed2[50];
513    if (!*jobids) {
514       return;
515    }
516
517    POOL_MEM filter;
518    if (*pattern) {
519       Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
520    }
521
522    if (!dir_filenameid) {
523       get_dir_filenameid();
524    }
525
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();
530     */
531    /* Then we get all the dir entries from File ... */
532    POOL_MEM query;
533    Mmsg(query, 
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 "
538     "FROM ( "
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) "
547            "%s "
548      ") AS listpath1 "
549    "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
550
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),
559         jobids,
560         filter.c_str(),
561         edit_uint64(dir_filenameid, ed2),
562         jobids,
563         limit, offset);
564
565    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
566    db_sql_query(db, query.c_str(), result_path_handler, this);
567 }
568
569 void Bvfs::ls_files()
570 {
571    Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
572    char ed1[50];
573    if (!*jobids) {
574       return ;
575    }
576
577    if (!pwd_id) {
578       ch_dir(get_root());
579    }
580
581    POOL_MEM filter;
582    if (*pattern) {
583       Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
584    }
585
586    POOL_MEM query;
587    Mmsg(query, // 0         1            2                3            4
588 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId "
589 "FROM File, ( "
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) "
596           "%s "
597         "GROUP BY Filename.Name "
598         "ORDER BY Filename.Name LIMIT %d OFFSET %d "
599      ") AS listfiles "
600 "WHERE File.FileId = listfiles.id",
601         edit_uint64(pwd_id, ed1),
602         jobids,
603         filter.c_str(),
604         limit,
605         offset);
606    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
607    db_sql_query(db, query.c_str(), result_handler, this);
608 }