]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/bvfs.c
Add .bvfs_restore command
[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 three of the GNU Affero 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 Affero 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 static int result_handler(void *ctx, int fields, char **row)
40 {
41    if (fields == 4) {
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]);
53    }
54    return 0;
55 }
56
57 Bvfs::Bvfs(JCR *j, B_DB *mdb) {
58    jcr = j;
59    jcr->inc_use_count();
60    db = mdb;                 /* need to inc ref count */
61    jobids = get_pool_memory(PM_NAME);
62    prev_dir = get_pool_memory(PM_NAME);
63    pattern = get_pool_memory(PM_NAME);
64    *jobids = *prev_dir = *pattern = 0;
65    dir_filenameid = pwd_id = offset = 0;
66    see_copies = see_all_versions = false;
67    limit = 1000;
68    attr = new_attr(jcr);
69    list_entries = result_handler;
70    user_data = this;
71 }
72
73 Bvfs::~Bvfs() {
74    free_pool_memory(jobids);
75    free_pool_memory(pattern);
76    free_pool_memory(prev_dir);
77    free_attr(attr);
78    jcr->dec_use_count();
79 }
80
81 /* 
82  * TODO: Find a way to let the user choose how he wants to display
83  * files and directories
84  */
85
86
87 /* 
88  * Working Object to store PathId already seen (avoid
89  * database queries), equivalent to %cache_ppathid in perl
90  */
91
92 #define NITEMS 50000
93 class pathid_cache {
94 private:
95    hlink *nodes;
96    int nb_node;
97    int max_node;
98
99    alist *table_node;
100
101    htable *cache_ppathid;
102
103 public:
104    pathid_cache() {
105       hlink link;
106       cache_ppathid = (htable *)malloc(sizeof(htable));
107       cache_ppathid->init(&link, &link, NITEMS);
108       max_node = NITEMS;
109       nodes = (hlink *) malloc(max_node * sizeof (hlink));
110       nb_node = 0;
111       table_node = New(alist(5, owned_by_alist));
112       table_node->append(nodes);
113    }
114
115    hlink *get_hlink() {
116       if (++nb_node >= max_node) {
117          nb_node = 0;
118          nodes = (hlink *)malloc(max_node * sizeof(hlink));
119          table_node->append(nodes);
120       }
121       return nodes + nb_node;
122    }
123
124    bool lookup(char *pathid) {
125       bool ret = cache_ppathid->lookup(pathid) != NULL;
126       return ret;
127    }
128    
129    void insert(char *pathid) {
130       hlink *h = get_hlink();
131       cache_ppathid->insert(pathid, h);
132    }
133
134    ~pathid_cache() {
135       cache_ppathid->destroy();
136       free(cache_ppathid);
137       delete table_node;
138    }
139 private:
140    pathid_cache(const pathid_cache &); /* prohibit pass by value */
141    pathid_cache &operator= (const pathid_cache &);/* prohibit class assignment*/
142 } ;
143
144 /* Return the parent_dir with the trailing /  (update the given string)
145  * TODO: see in the rest of bacula if we don't have already this function
146  * dir=/tmp/toto/
147  * dir=/tmp/
148  * dir=/
149  * dir=
150  */
151 char *bvfs_parent_dir(char *path)
152 {
153    char *p = path;
154    int len = strlen(path) - 1;
155
156    if (len >= 0 && path[len] == '/') {      /* if directory, skip last / */
157       path[len] = '\0';
158    }
159
160    if (len > 0) {
161       p += len;
162       while (p > path && !IsPathSeparator(*p)) {
163          p--;
164       }
165       p[1] = '\0';
166    }
167    return path;
168 }
169
170 /* Return the basename of the with the trailing /
171  * TODO: see in the rest of bacula if we don't have
172  * this function already
173  */
174 char *bvfs_basename_dir(char *path)
175 {
176    char *p = path;
177    int len = strlen(path) - 1;
178
179    if (path[len] == '/') {      /* if directory, skip last / */
180       len -= 1;
181    }
182
183    if (len > 0) {
184       p += len;
185       while (p > path && !IsPathSeparator(*p)) {
186          p--;
187       }
188       if (*p == '/') {
189          p++;                  /* skip first / */
190       }
191    } 
192    return p;
193 }
194
195 static void build_path_hierarchy(JCR *jcr, B_DB *mdb, 
196                                  pathid_cache &ppathid_cache, 
197                                  char *org_pathid, char *path)
198 {
199    Dmsg1(dbglevel, "build_path_hierarchy(%s)\n", path);
200    char pathid[50];
201    ATTR_DBR parent;
202    char *bkp = mdb->path;
203    strncpy(pathid, org_pathid, sizeof(pathid));
204
205    /* Does the ppathid exist for this ? we use a memory cache...  In order to
206     * avoid the full loop, we consider that if a dir is allready in the
207     * PathHierarchy table, then there is no need to calculate all the
208     * hierarchy
209     */
210    while (path && *path)
211    {
212       if (!ppathid_cache.lookup(pathid))
213       {
214          Mmsg(mdb->cmd, 
215               "SELECT PPathId FROM PathHierarchy WHERE PathId = %s",
216               pathid);
217
218          QUERY_DB(jcr, mdb, mdb->cmd);
219          /* Do we have a result ? */
220          if (sql_num_rows(mdb) > 0) {
221             ppathid_cache.insert(pathid);
222             /* This dir was in the db ...
223              * It means we can leave, the tree has allready been built for
224              * this dir
225              */
226             goto bail_out;
227          } else {
228             /* search or create parent PathId in Path table */
229             mdb->path = bvfs_parent_dir(path);
230             mdb->pnl = strlen(mdb->path);
231             if (!db_create_path_record(jcr, mdb, &parent)) {
232                goto bail_out;
233             }
234             ppathid_cache.insert(pathid);
235             
236             Mmsg(mdb->cmd,
237                  "INSERT INTO PathHierarchy (PathId, PPathId) "
238                  "VALUES (%s,%lld)",
239                  pathid, (uint64_t) parent.PathId);
240             
241             INSERT_DB(jcr, mdb, mdb->cmd);
242
243             edit_uint64(parent.PathId, pathid);
244             path = mdb->path;   /* already done */
245          }
246       } else {
247          /* It's already in the cache.  We can leave, no time to waste here,
248           * all the parent dirs have allready been done
249           */
250          goto bail_out;
251       }
252    }   
253
254 bail_out:
255    mdb->path = bkp;
256    mdb->fnl = 0;
257 }
258
259 /* 
260  * Internal function to update path_hierarchy cache with a shared pathid cache
261  */
262 static void update_path_hierarchy_cache(JCR *jcr,
263                                         B_DB *mdb,
264                                         pathid_cache &ppathid_cache,
265                                         JobId_t JobId)
266 {
267    Dmsg0(dbglevel, "update_path_hierarchy_cache()\n");
268
269    uint32_t num;
270    char jobid[50];
271    edit_uint64(JobId, jobid);
272  
273    db_lock(mdb);
274    db_start_transaction(jcr, mdb);
275
276    Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE JobId = %s AND HasCache=1", jobid);
277    
278    if (!QUERY_DB(jcr, mdb, mdb->cmd) || sql_num_rows(mdb) > 0) {
279       Dmsg1(dbglevel, "already computed %d\n", (uint32_t)JobId );
280       goto bail_out;
281    }
282
283    /* Inserting path records for JobId */
284    Mmsg(mdb->cmd, "INSERT INTO PathVisibility (PathId, JobId) "
285                   "SELECT DISTINCT PathId, JobId FROM File WHERE JobId = %s",
286         jobid);
287    QUERY_DB(jcr, mdb, mdb->cmd);
288
289
290    /* Now we have to do the directory recursion stuff to determine missing
291     * visibility We try to avoid recursion, to be as fast as possible We also
292     * only work on not allready hierarchised directories...
293     */
294    Mmsg(mdb->cmd, 
295      "SELECT PathVisibility.PathId, Path "
296        "FROM PathVisibility "
297             "JOIN Path ON( PathVisibility.PathId = Path.PathId) "
298             "LEFT JOIN PathHierarchy "
299          "ON (PathVisibility.PathId = PathHierarchy.PathId) "
300       "WHERE PathVisibility.JobId = %s "
301         "AND PathHierarchy.PathId IS NULL "
302       "ORDER BY Path", jobid);
303    Dmsg1(dbglevel_sql, "q=%s\n", mdb->cmd);
304    QUERY_DB(jcr, mdb, mdb->cmd);
305
306    /* TODO: I need to reuse the DB connection without emptying the result 
307     * So, now i'm copying the result in memory to be able to query the
308     * catalog descriptor again.
309     */
310    num = sql_num_rows(mdb);
311    if (num > 0) {
312       char **result = (char **)malloc (num * 2 * sizeof(char *));
313       
314       SQL_ROW row;
315       int i=0;
316       while((row = sql_fetch_row(mdb))) {
317          result[i++] = bstrdup(row[0]);
318          result[i++] = bstrdup(row[1]);
319       }
320       
321       i=0;
322       while (num > 0) {
323          build_path_hierarchy(jcr, mdb, ppathid_cache, result[i], result[i+1]);
324          free(result[i++]);
325          free(result[i++]);
326          num--;
327       }
328       free(result);
329    }
330
331    /* TODO: Add Path for BaseJobs */
332    Mmsg(mdb->cmd, 
333   "INSERT INTO PathVisibility (PathId, JobId)  "
334    "SELECT a.PathId,%s "
335    "FROM ( "
336      "SELECT DISTINCT h.PPathId AS PathId "
337        "FROM PathHierarchy AS h "
338        "JOIN  PathVisibility AS p ON (h.PathId=p.PathId) "
339       "WHERE p.JobId=%s) AS a LEFT JOIN "
340        "(SELECT PathId "
341           "FROM PathVisibility "
342          "WHERE JobId=%s) AS b ON (a.PathId = b.PathId) "
343    "WHERE b.PathId IS NULL",  jobid, jobid, jobid);
344
345    do {
346       QUERY_DB(jcr, mdb, mdb->cmd);
347    } while (sql_affected_rows(mdb) > 0);
348    
349    Mmsg(mdb->cmd, "UPDATE Job SET HasCache=1 WHERE JobId=%s", jobid);
350    UPDATE_DB(jcr, mdb, mdb->cmd);
351
352 bail_out:
353    db_end_transaction(jcr, mdb);
354    db_unlock(mdb);
355 }
356
357 /* 
358  * Find an store the filename descriptor for empty directories Filename.Name=''
359  */
360 DBId_t Bvfs::get_dir_filenameid()
361 {
362    uint32_t id;
363    if (dir_filenameid) {
364       return dir_filenameid;
365    }
366    POOL_MEM q;
367    Mmsg(q, "SELECT FilenameId FROM Filename WHERE Name = ''");
368    db_sql_query(db, q.c_str(), db_int_handler, &id);
369    dir_filenameid = id;
370    return dir_filenameid;
371 }
372
373 void bvfs_update_cache(JCR *jcr, B_DB *mdb)
374 {
375    uint32_t nb=0;
376    db_list_ctx jobids_list;
377
378    db_lock(mdb);
379    db_start_transaction(jcr, mdb);
380
381 #ifdef xxx
382    /* TODO: Remove this code when updating make_bacula_table script */
383    Mmsg(mdb->cmd, "SELECT 1 FROM Job WHERE HasCache<>2 LIMIT 1");
384    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
385       Dmsg0(dbglevel, "Creating cache table\n");
386       Mmsg(mdb->cmd, "ALTER TABLE Job ADD HasCache int DEFAULT 0");
387       QUERY_DB(jcr, mdb, mdb->cmd);
388
389       Mmsg(mdb->cmd,
390            "CREATE TABLE PathHierarchy ( "
391            "PathId integer NOT NULL, "
392            "PPathId integer NOT NULL, "
393            "CONSTRAINT pathhierarchy_pkey "
394            "PRIMARY KEY (PathId))");
395       QUERY_DB(jcr, mdb, mdb->cmd); 
396
397       Mmsg(mdb->cmd,
398            "CREATE INDEX pathhierarchy_ppathid "
399            "ON PathHierarchy (PPathId)");
400       QUERY_DB(jcr, mdb, mdb->cmd);
401
402       Mmsg(mdb->cmd, 
403            "CREATE TABLE PathVisibility ("
404            "PathId integer NOT NULL, "
405            "JobId integer NOT NULL, "
406            "Size int8 DEFAULT 0, "
407            "Files int4 DEFAULT 0, "
408            "CONSTRAINT pathvisibility_pkey "
409            "PRIMARY KEY (JobId, PathId))");
410       QUERY_DB(jcr, mdb, mdb->cmd);
411
412       Mmsg(mdb->cmd, 
413            "CREATE INDEX pathvisibility_jobid "
414            "ON PathVisibility (JobId)");
415       QUERY_DB(jcr, mdb, mdb->cmd);
416
417    }
418 #endif
419
420    Mmsg(mdb->cmd, 
421  "SELECT JobId from Job "
422   "WHERE HasCache = 0 "
423     "AND Type IN ('B') AND JobStatus IN ('T', 'f', 'A') "
424   "ORDER BY JobId");
425
426    db_sql_query(mdb, mdb->cmd, db_list_handler, &jobids_list);
427
428    bvfs_update_path_hierarchy_cache(jcr, mdb, jobids_list.list);
429
430    db_end_transaction(jcr, mdb);
431    db_start_transaction(jcr, mdb);
432    Dmsg0(dbglevel, "Cleaning pathvisibility\n");
433    Mmsg(mdb->cmd, 
434         "DELETE FROM PathVisibility "
435          "WHERE NOT EXISTS "
436         "(SELECT 1 FROM Job WHERE JobId=PathVisibility.JobId)");
437    nb = DELETE_DB(jcr, mdb, mdb->cmd);
438    Dmsg1(dbglevel, "Affected row(s) = %d\n", nb);
439
440    db_end_transaction(jcr, mdb);
441    db_unlock(mdb);
442 }
443
444 /*
445  * Update the bvfs cache for given jobids (1,2,3,4)
446  */
447 void
448 bvfs_update_path_hierarchy_cache(JCR *jcr, B_DB *mdb, char *jobids)
449 {
450    pathid_cache ppathid_cache;
451    JobId_t JobId;
452    char *p;
453
454    for (p=jobids; ; ) {
455       int stat = get_next_jobid_from_list(&p, &JobId);
456       if (stat < 0) {
457          return;
458       }
459       if (stat == 0) {
460          break;
461       }
462       Dmsg1(dbglevel, "Updating cache for %lld\n", (uint64_t)JobId);
463       update_path_hierarchy_cache(jcr, mdb, ppathid_cache, JobId);
464    }
465 }
466
467 /* 
468  * Update the bvfs cache for current jobids
469  */
470 void Bvfs::update_cache()
471 {
472    bvfs_update_path_hierarchy_cache(jcr, db, jobids);
473 }
474
475 /* Change the current directory, returns true if the path exists */
476 bool Bvfs::ch_dir(const char *path)
477 {
478    pm_strcpy(db->path, path);
479    db->pnl = strlen(db->path);
480    ch_dir(db_get_path_record(jcr, db)); 
481    return pwd_id != 0;
482 }
483
484 /* 
485  * Get all file versions for a specified client
486  */
487 void Bvfs::get_all_file_versions(DBId_t pathid, DBId_t fnid, const char *client)
488 {
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];
492    POOL_MEM q;
493    if (see_copies) {
494       Mmsg(q, " AND Job.Type IN ('C', 'B') ");
495    } else {
496       Mmsg(q, " AND Job.Type = 'B' ");
497    }
498
499    POOL_MEM query;
500
501    Mmsg(query,//    1           2              3       
502 "SELECT 'V', File.PathId, File.FilenameId,  File.Md5, "
503 //         4          5           6
504         "File.JobId, File.LStat, File.FileId, "
505 //         7                    8
506        "Media.VolumeName, Media.InChanger "
507 "FROM File, Job, Client, JobMedia, Media "
508 "WHERE File.FilenameId = %s "
509   "AND File.PathId=%s "
510   "AND File.JobId = Job.JobId "
511   "AND Job.ClientId = Client.ClientId "
512   "AND Job.JobId = JobMedia.JobId "
513   "AND File.FileIndex >= JobMedia.FirstIndex "
514   "AND File.FileIndex <= JobMedia.LastIndex "
515   "AND JobMedia.MediaId = Media.MediaId "
516   "AND Client.Name = '%s' "
517   "%s ORDER BY FileId LIMIT %d OFFSET %d"
518         ,edit_uint64(fnid, ed1), edit_uint64(pathid, ed2), client, q.c_str(),
519         limit, offset);
520    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
521    db_sql_query(db, query.c_str(), list_entries, user_data);
522 }
523
524 DBId_t Bvfs::get_root()
525 {
526    *db->path = 0;
527    return db_get_path_record(jcr, db);
528 }
529
530 static int path_handler(void *ctx, int fields, char **row)
531 {
532    Bvfs *fs = (Bvfs *) ctx;
533    return fs->_handle_path(ctx, fields, row);
534 }
535
536 int Bvfs::_handle_path(void *ctx, int fields, char **row)
537 {
538    if (bvfs_is_dir(row)) {
539       /* can have the same path 2 times */
540       if (strcmp(row[BVFS_Name], prev_dir)) {
541          pm_strcpy(prev_dir, row[BVFS_Name]);
542          return list_entries(user_data, fields, row);
543       }
544    }
545    return 0;
546 }
547
548 /* 
549  * Retrieve . and .. information
550  */
551 void Bvfs::ls_special_dirs()
552 {
553    Dmsg1(dbglevel, "ls_special_dirs(%lld)\n", (uint64_t)pwd_id);
554    char ed1[50], ed2[50];
555    if (*jobids == 0) {
556       return;
557    }
558    if (!dir_filenameid) {
559       get_dir_filenameid();
560    }
561
562    /* Will fetch directories  */
563    *prev_dir = 0;
564
565    POOL_MEM query;
566    Mmsg(query, 
567 "((SELECT PPathId AS PathId, '..' AS Path "
568     "FROM  PathHierarchy "
569    "WHERE  PathId = %s) "
570 "UNION "
571  "(SELECT %s AS PathId, '.' AS Path))",
572         edit_uint64(pwd_id, ed1), ed1);
573
574    POOL_MEM query2;
575    Mmsg(query2,// 1      2     3        4     5       6
576 "SELECT 'D', tmp.PathId, 0, tmp.Path, JobId, LStat, FileId "
577   "FROM %s AS tmp  LEFT JOIN ( " // get attributes if any
578        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
579               "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
580        "WHERE File1.FilenameId = %s "
581        "AND File1.JobId IN (%s)) AS listfile1 "
582   "ON (tmp.PathId = listfile1.PathId) "
583   "ORDER BY tmp.Path, JobId DESC ",
584         query.c_str(), edit_uint64(dir_filenameid, ed2), jobids);
585
586    Dmsg1(dbglevel_sql, "q=%s\n", query2.c_str());
587    db_sql_query(db, query2.c_str(), path_handler, this);
588 }
589
590 /* Returns true if we have dirs to read */
591 bool Bvfs::ls_dirs()
592 {
593    Dmsg1(dbglevel, "ls_dirs(%lld)\n", (uint64_t)pwd_id);
594    char ed1[50], ed2[50];
595    if (*jobids == 0) {
596       return false;
597    }
598
599    POOL_MEM filter;
600    if (*pattern) {
601       Mmsg(filter, " AND Path2.Path %s '%s' ", SQL_MATCH, pattern);
602    }
603
604    if (!dir_filenameid) {
605       get_dir_filenameid();
606    }
607
608    /* the sql query displays same directory multiple time, take the first one */
609    *prev_dir = 0;
610
611    /* Let's retrieve the list of the visible dirs in this dir ...
612     * First, I need the empty filenameid to locate efficiently
613     * the dirs in the file table
614     * my $dir_filenameid = $self->get_dir_filenameid();
615     */
616    /* Then we get all the dir entries from File ... */
617    POOL_MEM query;
618    Mmsg(query,
619 //       0     1     2   3      4     5       6
620 "SELECT 'D', PathId, 0, Path, JobId, LStat, FileId FROM ( "
621     "SELECT Path1.PathId AS PathId, Path1.Path AS Path, "
622            "lower(Path1.Path) AS lpath, "
623            "listfile1.JobId AS JobId, listfile1.LStat AS LStat, "
624            "listfile1.FileId AS FileId "
625     "FROM ( "
626       "SELECT DISTINCT PathHierarchy1.PathId AS PathId "
627       "FROM PathHierarchy AS PathHierarchy1 "
628       "JOIN Path AS Path2 "
629         "ON (PathHierarchy1.PathId = Path2.PathId) "
630       "JOIN PathVisibility AS PathVisibility1 "
631         "ON (PathHierarchy1.PathId = PathVisibility1.PathId) "
632       "WHERE PathHierarchy1.PPathId = %s "
633       "AND PathVisibility1.jobid IN (%s) "
634            "%s "
635      ") AS listpath1 "
636    "JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId) "
637
638    "LEFT JOIN ( " /* get attributes if any */
639        "SELECT File1.PathId AS PathId, File1.JobId AS JobId, "
640               "File1.LStat AS LStat, File1.FileId AS FileId FROM File AS File1 "
641        "WHERE File1.FilenameId = %s "
642        "AND File1.JobId IN (%s)) AS listfile1 "
643        "ON (listpath1.PathId = listfile1.PathId) "
644     ") AS A ORDER BY 2,3 DESC LIMIT %d OFFSET %d",
645         edit_uint64(pwd_id, ed1),
646         jobids,
647         filter.c_str(),
648         edit_uint64(dir_filenameid, ed2),
649         jobids,
650         limit, offset);
651
652    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
653
654    db_lock(db);
655    db_sql_query(db, query.c_str(), path_handler, this);
656    nb_record = db->num_rows;
657    db_unlock(db);
658
659    return nb_record == limit;
660 }
661
662 /* Returns true if we have files to read */
663 bool Bvfs::ls_files()
664 {
665    Dmsg1(dbglevel, "ls_files(%lld)\n", (uint64_t)pwd_id);
666    char ed1[50];
667    if (*jobids == 0) {
668       return false;
669    }
670
671    if (!pwd_id) {
672       ch_dir(get_root());
673    }
674
675    POOL_MEM filter;
676    if (*pattern) {
677       Mmsg(filter, " AND Filename.Name %s '%s' ", SQL_MATCH, pattern);
678    }
679    /* TODO: Use JobTDate instead of FileId to determine the latest version */
680    POOL_MEM query;
681    Mmsg(query, //    1              2             3          4
682 "SELECT 'F', File.PathId, File.FilenameId, listfiles.Name, File.JobId, "
683         "File.LStat, listfiles.id "
684 "FROM File, ( "
685        "SELECT Filename.Name as Name, max(File.FileId) as id "
686          "FROM File, Filename "
687         "WHERE File.FilenameId = Filename.FilenameId "
688           "AND Filename.Name != '' "
689           "AND File.PathId = %s "
690           "AND File.JobId IN (%s) "
691           "%s "
692         "GROUP BY Filename.Name "
693         "ORDER BY Filename.Name LIMIT %d OFFSET %d "
694      ") AS listfiles "
695 "WHERE File.FileId = listfiles.id",
696         edit_uint64(pwd_id, ed1),
697         jobids,
698         filter.c_str(),
699         limit,
700         offset);
701    Dmsg1(dbglevel_sql, "q=%s\n", query.c_str());
702
703    db_lock(db);
704    db_sql_query(db, query.c_str(), list_entries, user_data);
705    nb_record = db->num_rows;
706    db_unlock(db);
707
708    return nb_record == limit;
709 }
710
711
712 /* 
713  * Return next Id from comma separated list   
714  *
715  * Returns:
716  *   1 if next Id returned
717  *   0 if no more Ids are in list
718  *  -1 there is an error
719  * TODO: merge with get_next_jobid_from_list() and get_next_dbid_from_list()
720  */
721 static int get_next_id_from_list(char **p, int64_t *Id)
722 {
723    const int maxlen = 30;
724    char id[maxlen+1];
725    char *q = *p;
726
727    id[0] = 0;
728    for (int i=0; i<maxlen; i++) {
729       if (*q == 0) {
730          break;
731       } else if (*q == ',') {
732          q++;
733          break;
734       }
735       id[i] = *q++;
736       id[i+1] = 0;
737    }
738    if (id[0] == 0) {
739       return 0;
740    } else if (!is_a_number(id)) {
741       return -1;                      /* error */
742    }
743    *p = q;
744    *Id = str_to_int64(id);
745    return 1;
746 }
747
748 static int get_path_handler(void *ctx, int fields, char **row)
749 {
750    POOL_MEM *buf = (POOL_MEM *) ctx;
751    pm_strcpy(*buf, row[0]);
752    return 0;
753 }
754
755 static bool check_temp(char *output_table)
756 {
757    if (output_table[0] == 'b' &&
758        output_table[1] == '2' &&
759        is_an_integer(output_table + 2))
760    {
761       return true;
762    }
763    return false;
764 }
765
766 bool Bvfs::drop_restore_list(char *output_table)
767 {
768    POOL_MEM query;
769    if (check_temp(output_table)) {
770       Mmsg(query, "DROP TABLE %s", output_table);
771       db_sql_query(db, query.c_str(), NULL, NULL);
772       return true;
773    }
774    return false;
775 }
776
777 bool Bvfs::compute_restore_list(char *fileid, char *dirid, char *hardlink, 
778                                 char *output_table)
779 {
780    POOL_MEM query;
781    POOL_MEM tmp, tmp2;
782    int64_t id, jobid;
783    bool init=false;
784    bool ret=false;
785    /* check args */
786    if ((*fileid   && !is_a_number_list(fileid))  ||
787        (*dirid    && !is_a_number_list(dirid))   ||
788        (*hardlink && !is_a_number_list(hardlink))||
789        (!*hardlink && !*fileid && !*dirid && !*hardlink))
790    {
791       return false;
792    }
793    if (!check_temp(output_table)) {
794       return false;
795    }
796
797    Mmsg(query, "CREATE TEMPORARY TABLE btemp%s AS ", output_table);
798
799    if (*fileid) {
800       init=true;
801       Mmsg(tmp, "(SELECT JobId, FileIndex, FilenameId, PathId, FileId "
802                    "FROM File WHERE FileId IN (%s))", fileid);
803       pm_strcat(query, tmp.c_str());
804    }
805
806    while (get_next_id_from_list(&dirid, &id) == 1) {
807       Mmsg(tmp, "SELECT Path FROM Path WHERE PathId=%lld", id);
808       
809       if (!db_sql_query(db, tmp.c_str(), get_path_handler, (void *)&tmp2)) {
810          /* print error */
811          return false;
812       }
813       if (!strcmp(tmp2.c_str(), "")) { /* path not found */
814          Dmsg3(0, "Path not found %lld q=%s s=%s\n",
815                id, tmp.c_str(), tmp2.c_str());
816          break;
817       }
818       /* escape % and _ for LIKE search */
819       tmp.check_size((strlen(tmp2.c_str())+1) * 2);
820       char *p = tmp.c_str();
821       for (char *s = tmp2.c_str(); *s ; s++) {
822          if (*s == '%' || *s == '_' || *s == '\\') {
823             *p = '\\'; 
824             p++;
825          }
826          *p = *s; 
827          p++;
828       }
829       *p = '\0';
830       tmp.strcat("%");
831
832       size_t len = strlen(tmp.c_str());
833       tmp2.check_size((len+1) * 2);
834       db_escape_string(jcr, db, tmp2.c_str(), tmp.c_str(), len);
835
836       if (init) {
837          query.strcat(" UNION ");
838       }
839       Mmsg(tmp, "(SELECT File.JobId, File.FileIndex, File.FilenameId, "
840                         "File.PathId, FileId "
841                    "FROM Path JOIN File USING (PathId) "
842                   "WHERE Path.Path LIKE '%s' AND File.JobId IN (%s)) ", 
843            tmp2.c_str(), jobids); 
844       query.strcat(tmp.c_str());
845       init = true;
846    }
847
848    /* expect jobid,fileindex */
849    int64_t prev_jobid=0;
850    while (get_next_id_from_list(&hardlink, &jobid) == 1) {
851       if (get_next_id_from_list(&hardlink, &id) != 1) {
852          return false;
853       }
854       if (jobid != prev_jobid) { /* new job */
855          if (prev_jobid == 0) {  /* first jobid */
856             if (init) {
857                query.strcat(" UNION ");
858             }
859          } else {               /* end last job, start new one */
860             tmp.strcat(")) UNION ");
861             query.strcat(tmp.c_str());
862          }
863          Mmsg(tmp, "(SELECT JobId, FileIndex, FilenameId, PathId, FileId "
864                        "FROM File WHERE JobId = %lld " 
865                         "AND FileIndex IN (%lld", jobid, id);
866          prev_jobid = jobid;
867
868       } else {                  /* same job, add new findex */
869          Mmsg(tmp2, ", %lld", id);
870          tmp.strcat(tmp2.c_str());
871       }
872    }
873
874    if (prev_jobid != 0) {       /* end last job */
875       tmp.strcat(")) ");
876       query.strcat(tmp.c_str());
877       init = true;
878    }
879
880    Dmsg1(0, "q=%s\n", query.c_str());
881
882    if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
883       goto bail_out;
884    }
885
886    Mmsg(query, "CREATE TABLE %s AS ( "
887         "SELECT JobId, FileIndex, FileId "
888           "FROM ( "
889      "SELECT DISTINCT ON (PathId, FilenameId) JobId, FileIndex, FileId "
890        "FROM btemp%s "
891       "ORDER BY PathId, FilenameId, JobId DESC "
892           ") AS T "
893           "WHERE FileIndex > 0)", output_table, output_table);
894
895    Dmsg1(0, "q=%s\n", query.c_str());
896    if (!db_sql_query(db, query.c_str(), NULL, NULL)) {
897       goto bail_out;
898    }
899    ret = true;
900
901 bail_out:
902    Mmsg(query, "DROP TABLE btemp%s", output_table);
903    db_sql_query(db, query.c_str(), NULL, NULL);
904    return ret;
905 }