2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2008 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 and included
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 * Bacula Catalog Database Get record interface routines
30 * Note, these routines generally get a record by id or
31 * by name. If more logic is involved, the routine
34 * Kern Sibbald, March 2000
40 /* The following is necessary so that we do not include
41 * the dummy external definition of DB.
43 #define __SQL_C /* indicate that this is sql.c */
48 #if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI
50 /* -----------------------------------------------------------------------
52 * Generic Routines (or almost generic)
54 * -----------------------------------------------------------------------
57 /* Forward referenced functions */
58 static int db_get_file_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr, FILE_DBR *fdbr);
59 static int db_get_filename_record(JCR *jcr, B_DB *mdb);
60 static int db_get_path_record(JCR *jcr, B_DB *mdb);
64 * Given a full filename (with path), look up the File record
65 * (with attributes) in the database.
67 * Returns: 0 on failure
68 * 1 on success with the File record in FILE_DBR
70 int db_get_file_attributes_record(JCR *jcr, B_DB *mdb, char *fname, JOB_DBR *jr, FILE_DBR *fdbr)
73 Dmsg1(100, "db_get_file_att_record fname=%s \n", fname);
76 split_path_and_file(jcr, mdb, fname);
78 fdbr->FilenameId = db_get_filename_record(jcr, mdb);
80 fdbr->PathId = db_get_path_record(jcr, mdb);
82 stat = db_get_file_record(jcr, mdb, jr, fdbr);
92 * Returns: 0 on failure
95 * DO NOT use Jmsg in this routine.
97 * Note in this routine, we do not use Jmsg because it may be
98 * called to get attributes of a non-existent file, which is
99 * "normal" if a new file is found during Verify.
101 * The following is a bit of a kludge: because we always backup a
102 * directory entry, we can end up with two copies of the directory
103 * in the backup. One is when we encounter the directory and find
104 * we cannot recurse into it, and the other is when we find an
105 * explicit mention of the directory. This can also happen if the
106 * use includes the directory twice. In this case, Verify
107 * VolumeToCatalog fails because we have two copies in the catalog,
108 * and only the first one is marked (twice). So, when calling from Verify,
109 * jr is not NULL and we know jr->FileIndex is the fileindex
110 * of the version of the directory/file we actually want and do
111 * a more explicit SQL search.
114 int db_get_file_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr, FILE_DBR *fdbr)
118 char ed1[50], ed2[50], ed3[50];
120 if (jcr->get_JobLevel() == L_VERIFY_DISK_TO_CATALOG) {
122 "SELECT FileId, LStat, MD5 FROM File,Job WHERE "
123 "File.JobId=Job.JobId AND File.PathId=%s AND "
124 "File.FilenameId=%s AND Job.Type='B' AND Job.JobStatus='T' AND "
125 "ClientId=%s ORDER BY StartTime DESC LIMIT 1",
126 edit_int64(fdbr->PathId, ed1),
127 edit_int64(fdbr->FilenameId, ed2),
128 edit_int64(jr->ClientId,ed3));
130 } else if (jr != NULL) {
131 /* Called from Verify so jr->FileIndex is valid */
133 "SELECT FileId, LStat, MD5 FROM File WHERE File.JobId=%s AND File.PathId=%s AND "
134 "File.FilenameId=%s AND FileIndex=%u",
135 edit_int64(fdbr->JobId, ed1),
136 edit_int64(fdbr->PathId, ed2),
137 edit_int64(fdbr->FilenameId,ed3),
141 "SELECT FileId, LStat, MD5 FROM File WHERE File.JobId=%s AND File.PathId=%s AND "
142 "File.FilenameId=%s",
143 edit_int64(fdbr->JobId, ed1),
144 edit_int64(fdbr->PathId, ed2),
145 edit_int64(fdbr->FilenameId,ed3));
147 Dmsg3(450, "Get_file_record JobId=%u FilenameId=%u PathId=%u\n",
148 fdbr->JobId, fdbr->FilenameId, fdbr->PathId);
150 Dmsg1(100, "Query=%s\n", mdb->cmd);
152 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
153 mdb->num_rows = sql_num_rows(mdb);
154 Dmsg1(050, "get_file_record num_rows=%d\n", (int)mdb->num_rows);
155 if (mdb->num_rows > 1) {
156 Mmsg1(mdb->errmsg, _("get_file_record want 1 got rows=%d\n"),
158 Dmsg1(000, "=== Problem! %s", mdb->errmsg);
160 if (mdb->num_rows >= 1) {
161 if ((row = sql_fetch_row(mdb)) == NULL) {
162 Mmsg1(mdb->errmsg, _("Error fetching row: %s\n"), sql_strerror(mdb));
164 fdbr->FileId = (FileId_t)str_to_int64(row[0]);
165 bstrncpy(fdbr->LStat, row[1], sizeof(fdbr->LStat));
166 bstrncpy(fdbr->Digest, row[2], sizeof(fdbr->Digest));
170 Mmsg2(mdb->errmsg, _("File record for PathId=%s FilenameId=%s not found.\n"),
171 edit_int64(fdbr->PathId, ed1),
172 edit_int64(fdbr->FilenameId, ed2));
174 sql_free_result(mdb);
176 Mmsg(mdb->errmsg, _("File record not found in Catalog.\n"));
182 /* Get Filename record
183 * Returns: 0 on failure
184 * FilenameId on success
186 * DO NOT use Jmsg in this routine (see notes for get_file_record)
188 static int db_get_filename_record(JCR *jcr, B_DB *mdb)
193 mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->fnl+2);
194 db_escape_string(jcr, mdb, mdb->esc_name, mdb->fname, mdb->fnl);
196 Mmsg(mdb->cmd, "SELECT FilenameId FROM Filename WHERE Name='%s'", mdb->esc_name);
197 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
199 mdb->num_rows = sql_num_rows(mdb);
200 if (mdb->num_rows > 1) {
201 Mmsg2(mdb->errmsg, _("More than one Filename!: %s for file: %s\n"),
202 edit_uint64(mdb->num_rows, ed1), mdb->fname);
203 Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
205 if (mdb->num_rows >= 1) {
206 if ((row = sql_fetch_row(mdb)) == NULL) {
207 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
209 FilenameId = str_to_int64(row[0]);
210 if (FilenameId <= 0) {
211 Mmsg2(mdb->errmsg, _("Get DB Filename record %s found bad record: %d\n"),
212 mdb->cmd, FilenameId);
217 Mmsg1(mdb->errmsg, _("Filename record: %s not found.\n"), mdb->fname);
219 sql_free_result(mdb);
221 Mmsg(mdb->errmsg, _("Filename record: %s not found in Catalog.\n"), mdb->fname);
227 * Returns: 0 on failure
230 * DO NOT use Jmsg in this routine (see notes for get_file_record)
232 static int db_get_path_record(JCR *jcr, B_DB *mdb)
237 mdb->esc_name = check_pool_memory_size(mdb->esc_name, 2*mdb->pnl+2);
238 db_escape_string(jcr, mdb, mdb->esc_name, mdb->path, mdb->pnl);
240 if (mdb->cached_path_id != 0 && mdb->cached_path_len == mdb->pnl &&
241 strcmp(mdb->cached_path, mdb->path) == 0) {
242 return mdb->cached_path_id;
245 Mmsg(mdb->cmd, "SELECT PathId FROM Path WHERE Path='%s'", mdb->esc_name);
247 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
249 mdb->num_rows = sql_num_rows(mdb);
250 if (mdb->num_rows > 1) {
251 Mmsg2(mdb->errmsg, _("More than one Path!: %s for path: %s\n"),
252 edit_uint64(mdb->num_rows, ed1), mdb->path);
253 Jmsg(jcr, M_WARNING, 0, "%s", mdb->errmsg);
255 /* Even if there are multiple paths, take the first one */
256 if (mdb->num_rows >= 1) {
257 if ((row = sql_fetch_row(mdb)) == NULL) {
258 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
260 PathId = str_to_int64(row[0]);
262 Mmsg2(mdb->errmsg, _("Get DB path record %s found bad record: %s\n"),
263 mdb->cmd, edit_int64(PathId, ed1));
267 if (PathId != mdb->cached_path_id) {
268 mdb->cached_path_id = PathId;
269 mdb->cached_path_len = mdb->pnl;
270 pm_strcpy(mdb->cached_path, mdb->path);
275 Mmsg1(mdb->errmsg, _("Path record: %s not found.\n"), mdb->path);
277 sql_free_result(mdb);
279 Mmsg(mdb->errmsg, _("Path record: %s not found in Catalog.\n"), mdb->path);
286 * Get Job record for given JobId or Job name
287 * Returns: false on failure
290 bool db_get_job_record(JCR *jcr, B_DB *mdb, JOB_DBR *jr)
296 if (jr->JobId == 0) {
297 Mmsg(mdb->cmd, "SELECT VolSessionId,VolSessionTime,"
298 "PoolId,StartTime,EndTime,JobFiles,JobBytes,JobTDate,Job,JobStatus,"
299 "Type,Level,ClientId,Name,PriorJobId,RealEndTime,JobId,FileSetId,"
300 "SchedTime,RealEndTime "
301 "FROM Job WHERE Job='%s'", jr->Job);
303 Mmsg(mdb->cmd, "SELECT VolSessionId,VolSessionTime,"
304 "PoolId,StartTime,EndTime,JobFiles,JobBytes,JobTDate,Job,JobStatus,"
305 "Type,Level,ClientId,Name,PriorJobId,RealEndTime,JobId,FileSetId,"
306 "SchedTime,RealEndTime "
307 "FROM Job WHERE JobId=%s",
308 edit_int64(jr->JobId, ed1));
311 if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
313 return false; /* failed */
315 if ((row = sql_fetch_row(mdb)) == NULL) {
316 Mmsg1(mdb->errmsg, _("No Job found for JobId %s\n"), edit_int64(jr->JobId, ed1));
317 sql_free_result(mdb);
319 return false; /* failed */
322 jr->VolSessionId = str_to_uint64(row[0]);
323 jr->VolSessionTime = str_to_uint64(row[1]);
324 jr->PoolId = str_to_int64(row[2]);
325 bstrncpy(jr->cStartTime, row[3]!=NULL?row[3]:"", sizeof(jr->cStartTime));
326 bstrncpy(jr->cEndTime, row[4]!=NULL?row[4]:"", sizeof(jr->cEndTime));
327 jr->JobFiles = str_to_int64(row[5]);
328 jr->JobBytes = str_to_int64(row[6]);
329 jr->JobTDate = str_to_int64(row[7]);
330 bstrncpy(jr->Job, row[8]!=NULL?row[8]:"", sizeof(jr->Job));
331 jr->JobStatus = row[9]!=NULL?(int)*row[9]:JS_FatalError;
332 jr->JobType = row[10]!=NULL?(int)*row[10]:JT_BACKUP;
333 jr->JobLevel = row[11]!=NULL?(int)*row[11]:L_NONE;
334 jr->ClientId = str_to_uint64(row[12]!=NULL?row[12]:(char *)"");
335 bstrncpy(jr->Name, row[13]!=NULL?row[13]:"", sizeof(jr->Name));
336 jr->PriorJobId = str_to_uint64(row[14]!=NULL?row[14]:(char *)"");
337 bstrncpy(jr->cRealEndTime, row[15]!=NULL?row[15]:"", sizeof(jr->cRealEndTime));
338 if (jr->JobId == 0) {
339 jr->JobId = str_to_int64(row[16]);
341 jr->FileSetId = str_to_int64(row[17]);
342 bstrncpy(jr->cSchedTime, row[3]!=NULL?row[18]:"", sizeof(jr->cSchedTime));
343 bstrncpy(jr->cRealEndTime, row[3]!=NULL?row[19]:"", sizeof(jr->cRealEndTime));
344 jr->StartTime = str_to_utime(jr->cStartTime);
345 jr->SchedTime = str_to_utime(jr->cSchedTime);
346 jr->EndTime = str_to_utime(jr->cEndTime);
347 jr->RealEndTime = str_to_utime(jr->cRealEndTime);
348 sql_free_result(mdb);
355 * Find VolumeNames for a given JobId
356 * Returns: 0 on error or no Volumes found
357 * number of volumes on success
358 * Volumes are concatenated in VolumeNames
359 * separated by a vertical bar (|) in the order
360 * that they were written.
362 * Returns: number of volumes on success
364 int db_get_job_volume_names(JCR *jcr, B_DB *mdb, JobId_t JobId, POOLMEM **VolumeNames)
372 /* Get one entry per VolumeName, but "sort" by VolIndex */
374 "SELECT VolumeName,MAX(VolIndex) FROM JobMedia,Media WHERE "
375 "JobMedia.JobId=%s AND JobMedia.MediaId=Media.MediaId "
376 "GROUP BY VolumeName "
377 "ORDER BY 2 ASC", edit_int64(JobId,ed1));
379 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
381 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
382 mdb->num_rows = sql_num_rows(mdb);
383 Dmsg1(130, "Num rows=%d\n", mdb->num_rows);
384 if (mdb->num_rows <= 0) {
385 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
388 stat = mdb->num_rows;
389 for (i=0; i < stat; i++) {
390 if ((row = sql_fetch_row(mdb)) == NULL) {
391 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
392 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
396 if (*VolumeNames[0] != 0) {
397 pm_strcat(VolumeNames, "|");
399 pm_strcat(VolumeNames, row[0]);
403 sql_free_result(mdb);
405 Mmsg(mdb->errmsg, _("No Volume for JobId %d found in Catalog.\n"), JobId);
412 * Find Volume parameters for a give JobId
413 * Returns: 0 on error or no Volumes found
414 * number of volumes on success
415 * List of Volumes and start/end file/blocks (malloced structure!)
417 * Returns: number of volumes on success
419 int db_get_job_volume_parameters(JCR *jcr, B_DB *mdb, JobId_t JobId, VOL_PARAMS **VolParams)
425 VOL_PARAMS *Vols = NULL;
429 "SELECT VolumeName,MediaType,FirstIndex,LastIndex,StartFile,"
430 "JobMedia.EndFile,StartBlock,JobMedia.EndBlock,Copy,"
432 " FROM JobMedia,Media WHERE JobMedia.JobId=%s"
433 " AND JobMedia.MediaId=Media.MediaId ORDER BY VolIndex,JobMediaId",
434 edit_int64(JobId, ed1));
436 Dmsg1(130, "VolNam=%s\n", mdb->cmd);
437 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
438 mdb->num_rows = sql_num_rows(mdb);
439 Dmsg1(200, "Num rows=%d\n", mdb->num_rows);
440 if (mdb->num_rows <= 0) {
441 Mmsg1(mdb->errmsg, _("No volumes found for JobId=%d\n"), JobId);
444 stat = mdb->num_rows;
447 *VolParams = Vols = (VOL_PARAMS *)malloc(stat * sizeof(VOL_PARAMS));
448 SId = (DBId_t *)malloc(stat * sizeof(DBId_t));
450 for (i=0; i < stat; i++) {
451 if ((row = sql_fetch_row(mdb)) == NULL) {
452 Mmsg2(mdb->errmsg, _("Error fetching row %d: ERR=%s\n"), i, sql_strerror(mdb));
453 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
458 uint32_t StartBlock, EndBlock, StartFile, EndFile;
459 bstrncpy(Vols[i].VolumeName, row[0], MAX_NAME_LENGTH);
460 bstrncpy(Vols[i].MediaType, row[1], MAX_NAME_LENGTH);
461 Vols[i].FirstIndex = str_to_uint64(row[2]);
462 Vols[i].LastIndex = str_to_uint64(row[3]);
463 StartFile = str_to_uint64(row[4]);
464 EndFile = str_to_uint64(row[5]);
465 StartBlock = str_to_uint64(row[6]);
466 EndBlock = str_to_uint64(row[7]);
467 Vols[i].StartAddr = (((uint64_t)StartFile)<<32) | StartBlock;
468 Vols[i].EndAddr = (((uint64_t)EndFile)<<32) | EndBlock;
469 // Vols[i].Copy = str_to_uint64(row[8]);
470 Vols[i].Slot = str_to_uint64(row[9]);
471 StorageId = str_to_uint64(row[10]);
472 Vols[i].Storage[0] = 0;
476 for (i=0; i < stat; i++) {
478 Mmsg(mdb->cmd, "SELECT Name from Storage WHERE StorageId=%s",
479 edit_int64(SId[i], ed1));
480 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
481 if ((row = sql_fetch_row(mdb)) && row[0]) {
482 bstrncpy(Vols[i].Storage, row[0], MAX_NAME_LENGTH);
491 sql_free_result(mdb);
500 * Get the number of pool records
502 * Returns: -1 on failure
505 int db_get_num_pool_records(JCR *jcr, B_DB *mdb)
510 Mmsg(mdb->cmd, "SELECT count(*) from Pool");
511 stat = get_sql_record_max(jcr, mdb);
517 * This function returns a list of all the Pool record ids.
518 * The caller must free ids if non-NULL.
520 * Returns 0: on failure
523 int db_get_pool_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
532 Mmsg(mdb->cmd, "SELECT PoolId FROM Pool");
533 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
534 *num_ids = sql_num_rows(mdb);
536 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
537 while ((row = sql_fetch_row(mdb)) != NULL) {
538 id[i++] = str_to_uint64(row[0]);
542 sql_free_result(mdb);
545 Mmsg(mdb->errmsg, _("Pool id select failed: ERR=%s\n"), sql_strerror(mdb));
546 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
554 * This function returns a list of all the Client record ids.
555 * The caller must free ids if non-NULL.
557 * Returns 0: on failure
560 int db_get_client_ids(JCR *jcr, B_DB *mdb, int *num_ids, uint32_t *ids[])
569 Mmsg(mdb->cmd, "SELECT ClientId FROM Client");
570 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
571 *num_ids = sql_num_rows(mdb);
573 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
574 while ((row = sql_fetch_row(mdb)) != NULL) {
575 id[i++] = str_to_uint64(row[0]);
579 sql_free_result(mdb);
582 Mmsg(mdb->errmsg, _("Client id select failed: ERR=%s\n"), sql_strerror(mdb));
583 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
593 * If the PoolId is non-zero, we get its record,
594 * otherwise, we search on the PoolName
596 * Returns: false on failure
599 bool db_get_pool_record(JCR *jcr, B_DB *mdb, POOL_DBR *pdbr)
606 if (pdbr->PoolId != 0) { /* find by id */
608 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
609 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
610 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId FROM Pool WHERE Pool.PoolId=%s",
611 edit_int64(pdbr->PoolId, ed1));
612 } else { /* find by name */
614 "SELECT PoolId,Name,NumVols,MaxVols,UseOnce,UseCatalog,AcceptAnyVolume,"
615 "AutoPrune,Recycle,VolRetention,VolUseDuration,MaxVolJobs,MaxVolFiles,"
616 "MaxVolBytes,PoolType,LabelType,LabelFormat,RecyclePoolId,ScratchPoolId FROM Pool WHERE Pool.Name='%s'",
619 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
620 mdb->num_rows = sql_num_rows(mdb);
621 if (mdb->num_rows > 1) {
623 Mmsg1(mdb->errmsg, _("More than one Pool!: %s\n"),
624 edit_uint64(mdb->num_rows, ed1));
625 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
626 } else if (mdb->num_rows == 1) {
627 if ((row = sql_fetch_row(mdb)) == NULL) {
628 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
629 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
631 pdbr->PoolId = str_to_int64(row[0]);
632 bstrncpy(pdbr->Name, row[1]!=NULL?row[1]:"", sizeof(pdbr->Name));
633 pdbr->NumVols = str_to_int64(row[2]);
634 pdbr->MaxVols = str_to_int64(row[3]);
635 pdbr->UseOnce = str_to_int64(row[4]);
636 pdbr->UseCatalog = str_to_int64(row[5]);
637 pdbr->AcceptAnyVolume = str_to_int64(row[6]);
638 pdbr->AutoPrune = str_to_int64(row[7]);
639 pdbr->Recycle = str_to_int64(row[8]);
640 pdbr->VolRetention = str_to_int64(row[9]);
641 pdbr->VolUseDuration = str_to_int64(row[10]);
642 pdbr->MaxVolJobs = str_to_int64(row[11]);
643 pdbr->MaxVolFiles = str_to_int64(row[12]);
644 pdbr->MaxVolBytes = str_to_uint64(row[13]);
645 bstrncpy(pdbr->PoolType, row[14]!=NULL?row[14]:"", sizeof(pdbr->PoolType));
646 pdbr->LabelType = str_to_int64(row[15]);
647 bstrncpy(pdbr->LabelFormat, row[16]!=NULL?row[16]:"", sizeof(pdbr->LabelFormat));
648 pdbr->RecyclePoolId = str_to_int64(row[17]);
649 pdbr->ScratchPoolId = str_to_int64(row[18]);
653 sql_free_result(mdb);
657 Mmsg(mdb->cmd, "SELECT count(*) from Media WHERE PoolId=%s",
658 edit_int64(pdbr->PoolId, ed1));
659 NumVols = get_sql_record_max(jcr, mdb);
660 Dmsg2(400, "Actual NumVols=%d Pool NumVols=%d\n", NumVols, pdbr->NumVols);
661 if (NumVols != pdbr->NumVols) {
662 pdbr->NumVols = NumVols;
663 db_update_pool_record(jcr, mdb, pdbr);
666 Mmsg(mdb->errmsg, _("Pool record not found in Catalog.\n"));
673 * If the ClientId is non-zero, we get its record,
674 * otherwise, we search on the Client Name
676 * Returns: 0 on failure
679 int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr)
686 if (cdbr->ClientId != 0) { /* find by id */
688 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
689 "FROM Client WHERE Client.ClientId=%s",
690 edit_int64(cdbr->ClientId, ed1));
691 } else { /* find by name */
693 "SELECT ClientId,Name,Uname,AutoPrune,FileRetention,JobRetention "
694 "FROM Client WHERE Client.Name='%s'", cdbr->Name);
697 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
698 mdb->num_rows = sql_num_rows(mdb);
699 if (mdb->num_rows > 1) {
700 Mmsg1(mdb->errmsg, _("More than one Client!: %s\n"),
701 edit_uint64(mdb->num_rows, ed1));
702 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
703 } else if (mdb->num_rows == 1) {
704 if ((row = sql_fetch_row(mdb)) == NULL) {
705 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
706 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
708 cdbr->ClientId = str_to_int64(row[0]);
709 bstrncpy(cdbr->Name, row[1]!=NULL?row[1]:"", sizeof(cdbr->Name));
710 bstrncpy(cdbr->Uname, row[2]!=NULL?row[2]:"", sizeof(cdbr->Uname));
711 cdbr->AutoPrune = str_to_int64(row[3]);
712 cdbr->FileRetention = str_to_int64(row[4]);
713 cdbr->JobRetention = str_to_int64(row[5]);
717 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
719 sql_free_result(mdb);
721 Mmsg(mdb->errmsg, _("Client record not found in Catalog.\n"));
730 * Returns: 0 on failure
733 int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr)
738 Mmsg(mdb->cmd, "SELECT MinValue,MaxValue,CurrentValue,WrapCounter "
739 "FROM Counters WHERE Counter='%s'", cr->Counter);
741 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
742 mdb->num_rows = sql_num_rows(mdb);
744 /* If more than one, report error, but return first row */
745 if (mdb->num_rows > 1) {
746 Mmsg1(mdb->errmsg, _("More than one Counter!: %d\n"), (int)(mdb->num_rows));
747 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
749 if (mdb->num_rows >= 1) {
750 if ((row = sql_fetch_row(mdb)) == NULL) {
751 Mmsg1(mdb->errmsg, _("error fetching Counter row: %s\n"), sql_strerror(mdb));
752 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
753 sql_free_result(mdb);
757 cr->MinValue = str_to_int64(row[0]);
758 cr->MaxValue = str_to_int64(row[1]);
759 cr->CurrentValue = str_to_int64(row[2]);
761 bstrncpy(cr->WrapCounter, row[3], sizeof(cr->WrapCounter));
763 cr->WrapCounter[0] = 0;
765 sql_free_result(mdb);
769 sql_free_result(mdb);
771 Mmsg(mdb->errmsg, _("Counter record: %s not found in Catalog.\n"), cr->Counter);
778 /* Get FileSet Record
779 * If the FileSetId is non-zero, we get its record,
780 * otherwise, we search on the name
782 * Returns: 0 on failure
785 int db_get_fileset_record(JCR *jcr, B_DB *mdb, FILESET_DBR *fsr)
792 if (fsr->FileSetId != 0) { /* find by id */
794 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
795 "WHERE FileSetId=%s",
796 edit_int64(fsr->FileSetId, ed1));
797 } else { /* find by name */
799 "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet "
800 "WHERE FileSet='%s' ORDER BY CreateTime DESC LIMIT 1", fsr->FileSet);
803 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
804 mdb->num_rows = sql_num_rows(mdb);
805 if (mdb->num_rows > 1) {
807 Mmsg1(mdb->errmsg, _("Error got %s FileSets but expected only one!\n"),
808 edit_uint64(mdb->num_rows, ed1));
809 sql_data_seek(mdb, mdb->num_rows-1);
811 if ((row = sql_fetch_row(mdb)) == NULL) {
812 Mmsg1(mdb->errmsg, _("FileSet record \"%s\" not found.\n"), fsr->FileSet);
814 fsr->FileSetId = str_to_int64(row[0]);
815 bstrncpy(fsr->FileSet, row[1]!=NULL?row[1]:"", sizeof(fsr->FileSet));
816 bstrncpy(fsr->MD5, row[2]!=NULL?row[2]:"", sizeof(fsr->MD5));
817 bstrncpy(fsr->cCreateTime, row[3]!=NULL?row[3]:"", sizeof(fsr->cCreateTime));
818 stat = fsr->FileSetId;
820 sql_free_result(mdb);
822 Mmsg(mdb->errmsg, _("FileSet record not found in Catalog.\n"));
830 * Get the number of Media records
832 * Returns: -1 on failure
835 int db_get_num_media_records(JCR *jcr, B_DB *mdb)
840 Mmsg(mdb->cmd, "SELECT count(*) from Media");
841 stat = get_sql_record_max(jcr, mdb);
848 * This function returns a list of all the Media record ids for
849 * the current Pool with the correct Media Type.
850 * The caller must free ids if non-NULL.
852 * Returns false: on failure
855 bool db_get_media_ids(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr, int *num_ids, uint32_t *ids[])
865 Mmsg(mdb->cmd, "SELECT DISTINCT MediaId FROM Media WHERE PoolId=%s "
866 " AND MediaType='%s'",
867 edit_int64(mr->PoolId, ed1), mr->MediaType);
868 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
869 *num_ids = sql_num_rows(mdb);
871 id = (uint32_t *)malloc(*num_ids * sizeof(uint32_t));
872 while ((row = sql_fetch_row(mdb)) != NULL) {
873 id[i++] = str_to_uint64(row[0]);
877 sql_free_result(mdb);
880 Mmsg(mdb->errmsg, _("Media id select failed: ERR=%s\n"), sql_strerror(mdb));
881 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
890 * This function returns a list of all the DBIds that are returned
893 * Returns false: on failure
896 bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids)
904 if (QUERY_DB(jcr, mdb, query.c_str())) {
905 ids.num_ids = sql_num_rows(mdb);
906 if (ids.num_ids > 0) {
907 if (ids.max_ids < ids.num_ids) {
909 ids.DBId = (DBId_t *)malloc(ids.num_ids * sizeof(DBId_t));
911 while ((row = sql_fetch_row(mdb)) != NULL) {
912 ids.DBId[i++] = str_to_uint64(row[0]);
915 sql_free_result(mdb);
918 Mmsg(mdb->errmsg, _("query dbids failed: ERR=%s\n"), sql_strerror(mdb));
919 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
928 * Returns: false: on failure
931 bool db_get_media_record(JCR *jcr, B_DB *mdb, MEDIA_DBR *mr)
938 if (mr->MediaId == 0 && mr->VolumeName[0] == 0) {
939 Mmsg(mdb->cmd, "SELECT count(*) from Media");
940 mr->MediaId = get_sql_record_max(jcr, mdb);
944 if (mr->MediaId != 0) { /* find by id */
945 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
946 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
947 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
948 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
949 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
950 "Enabled,LocationId,RecycleCount,InitialWrite,"
951 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime "
952 "FROM Media WHERE MediaId=%s",
953 edit_int64(mr->MediaId, ed1));
954 } else { /* find by name */
955 Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
956 "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
957 "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
958 "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
959 "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
960 "Enabled,LocationId,RecycleCount,InitialWrite,"
961 "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime "
962 "FROM Media WHERE VolumeName='%s'", mr->VolumeName);
965 if (QUERY_DB(jcr, mdb, mdb->cmd)) {
967 mdb->num_rows = sql_num_rows(mdb);
968 if (mdb->num_rows > 1) {
969 Mmsg1(mdb->errmsg, _("More than one Volume!: %s\n"),
970 edit_uint64(mdb->num_rows, ed1));
971 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
972 } else if (mdb->num_rows == 1) {
973 if ((row = sql_fetch_row(mdb)) == NULL) {
974 Mmsg1(mdb->errmsg, _("error fetching row: %s\n"), sql_strerror(mdb));
975 Jmsg(jcr, M_ERROR, 0, "%s", mdb->errmsg);
978 mr->MediaId = str_to_int64(row[0]);
979 bstrncpy(mr->VolumeName, row[1]!=NULL?row[1]:"", sizeof(mr->VolumeName));
980 mr->VolJobs = str_to_int64(row[2]);
981 mr->VolFiles = str_to_int64(row[3]);
982 mr->VolBlocks = str_to_int64(row[4]);
983 mr->VolBytes = str_to_uint64(row[5]);
984 mr->VolMounts = str_to_int64(row[6]);
985 mr->VolErrors = str_to_int64(row[7]);
986 mr->VolWrites = str_to_int64(row[8]);
987 mr->MaxVolBytes = str_to_uint64(row[9]);
988 mr->VolCapacityBytes = str_to_uint64(row[10]);
989 bstrncpy(mr->MediaType, row[11]!=NULL?row[11]:"", sizeof(mr->MediaType));
990 bstrncpy(mr->VolStatus, row[12]!=NULL?row[12]:"", sizeof(mr->VolStatus));
991 mr->PoolId = str_to_int64(row[13]);
992 mr->VolRetention = str_to_uint64(row[14]);
993 mr->VolUseDuration = str_to_uint64(row[15]);
994 mr->MaxVolJobs = str_to_int64(row[16]);
995 mr->MaxVolFiles = str_to_int64(row[17]);
996 mr->Recycle = str_to_int64(row[18]);
997 mr->Slot = str_to_int64(row[19]);
998 bstrncpy(mr->cFirstWritten, row[20]!=NULL?row[20]:"", sizeof(mr->cFirstWritten));
999 mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten);
1000 bstrncpy(mr->cLastWritten, row[21]!=NULL?row[21]:"", sizeof(mr->cLastWritten));
1001 mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten);
1002 mr->InChanger = str_to_uint64(row[22]);
1003 mr->EndFile = str_to_uint64(row[23]);
1004 mr->EndBlock = str_to_uint64(row[24]);
1005 mr->VolParts = str_to_int64(row[25]);
1006 mr->LabelType = str_to_int64(row[26]);
1007 bstrncpy(mr->cLabelDate, row[27]!=NULL?row[27]:"", sizeof(mr->cLabelDate));
1008 mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate);
1009 mr->StorageId = str_to_int64(row[28]);
1010 mr->Enabled = str_to_int64(row[29]);
1011 mr->LocationId = str_to_int64(row[30]);
1012 mr->RecycleCount = str_to_int64(row[31]);
1013 bstrncpy(mr->cInitialWrite, row[32]!=NULL?row[32]:"", sizeof(mr->cInitialWrite));
1014 mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite);
1015 mr->ScratchPoolId = str_to_int64(row[33]);
1016 mr->RecyclePoolId = str_to_int64(row[34]);
1017 mr->VolReadTime = str_to_int64(row[35]);
1018 mr->VolWriteTime = str_to_int64(row[36]);
1023 if (mr->MediaId != 0) {
1024 Mmsg1(mdb->errmsg, _("Media record MediaId=%s not found.\n"),
1025 edit_int64(mr->MediaId, ed1));
1027 Mmsg1(mdb->errmsg, _("Media record for Volume \"%s\" not found.\n"),
1031 sql_free_result(mdb);
1033 if (mr->MediaId != 0) {
1034 Mmsg(mdb->errmsg, _("Media record for MediaId=%u not found in Catalog.\n"),
1037 Mmsg(mdb->errmsg, _("Media record for Vol=%s not found in Catalog.\n"),
1045 * Find the last "accurate" backup state (that can take deleted files in account)
1046 * 1) Get all files with jobid in list (F subquery)
1047 * 2) Take only the last version of each file (Temp subquery) => accurate list is ok
1048 * 3) Join the result to file table to get fileindex, jobid and lstat information
1050 * TODO: See if we can do the SORT only if needed (as an argument)
1052 bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
1053 DB_RESULT_HANDLER *result_handler, void *ctx)
1057 Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
1061 POOL_MEM buf(PM_MESSAGE);
1063 #define new_db_get_file_list
1064 #ifdef new_db_get_file_list
1065 /* This is broken, at least if called from ua_restore.c */
1067 "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
1069 "SELECT max(FileId) as FileId, PathId, FilenameId "
1070 "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
1071 "GROUP BY PathId, FilenameId "
1073 "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
1074 "JOIN Path ON (Path.PathId = Temp.PathId) "
1075 "JOIN File ON (File.FileId = Temp.FileId) "
1076 "WHERE File.FileIndex > 0 ORDER BY JobId, FileIndex ASC", /* Return sorted by JobId, */
1077 /* FileIndex for restore code */
1081 * I am not sure that this works the same as the code in ua_restore.c
1082 * but it is very similar. The accurate-test fails in a restore. Bad file count.
1084 Mmsg(buf, uar_sel_files, jobids);
1087 return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
1090 /* The decision do change an incr/diff was done before
1092 * Differential : get the last full id
1093 * Incremental : get the last full + last diff + last incr(s) ids
1095 * TODO: look and merge from ua_restore.c
1097 bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
1098 JOB_DBR *jr, POOLMEM *jobids)
1101 char clientid[50], jobid[50], filesetid[50];
1102 char date[MAX_TIME_LENGTH];
1103 POOL_MEM query(PM_FNAME);
1104 bstrutime(date, sizeof(date), time(NULL) + 1);
1107 /* First, find the last good Full backup for this job/client/fileset */
1109 "CREATE TABLE btemp3%s AS "
1110 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1111 "FROM Job JOIN FileSet USING (FileSetId) "
1112 "WHERE ClientId = %s "
1113 "AND Level='F' AND JobStatus='T' AND Type='B' "
1114 "AND StartTime<'%s' "
1115 "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1116 "ORDER BY Job.JobTDate DESC LIMIT 1",
1117 edit_uint64(jcr->JobId, jobid),
1118 edit_uint64(jr->ClientId, clientid),
1120 edit_uint64(jr->FileSetId, filesetid));
1122 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1126 if (jr->JobLevel == L_INCREMENTAL || jr->JobLevel == L_VIRTUAL_FULL) {
1127 /* Now, find the last differential backup after the last full */
1129 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1130 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1131 "FROM Job JOIN FileSet USING (FileSetId) "
1132 "WHERE ClientId = %s "
1133 "AND Level='D' AND JobStatus='T' AND Type='B' "
1134 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1135 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1136 "ORDER BY Job.JobTDate DESC LIMIT 1 ",
1142 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1146 /* We just have to take all incremental after the last Full/Diff */
1148 "INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
1149 "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
1150 "FROM Job JOIN FileSet USING (FileSetId) "
1151 "WHERE ClientId = %s "
1152 "AND Level='I' AND JobStatus='T' AND Type='B' "
1153 "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
1154 "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
1155 "ORDER BY Job.JobTDate DESC ",
1160 if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
1165 /* build a jobid list ie: 1,2,3,4 */
1166 Mmsg(query, "SELECT JobId FROM btemp3%s ORDER by JobTDate", jobid);
1167 db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
1168 Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
1172 Mmsg(query, "DROP TABLE btemp3%s", jobid);
1173 db_sql_query(mdb, query.c_str(), NULL, NULL);
1179 * Use to build a string of int list from a query. "10,20,30"
1181 int db_get_int_handler(void *ctx, int num_fields, char **row)
1183 POOLMEM *ret = (POOLMEM *)ctx;
1184 if (num_fields == 1) {
1186 pm_strcat(ret, ",");
1188 pm_strcat(ret, row[0]);
1193 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */