]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/cats/sql_find.c
Another fix for bug #1311 to get the correct last_full_time
[bacula/bacula] / bacula / src / cats / sql_find.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-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 and included
11    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  * Bacula Catalog Database Find record interface routines
30  *
31  *  Note, generally, these routines are more complicated
32  *        that a simple search by name or id. Such simple
33  *        request are in get.c
34  *
35  *    Kern Sibbald, December 2000
36  *
37  *    Version $Id$
38  */
39
40
41 /* The following is necessary so that we do not include
42  * the dummy external definition of DB.
43  */
44 #define __SQL_C                       /* indicate that this is sql.c */
45
46 #include "bacula.h"
47 #include "cats.h"
48
49 #if    HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI
50
51 /* -----------------------------------------------------------------------
52  *
53  *   Generic Routines (or almost generic)
54  *
55  * -----------------------------------------------------------------------
56  */
57
58 /*
59  * Find job start time if JobId specified, otherwise
60  * find last Job start time Incremental and Differential saves.
61  *
62  *  StartTime is returned in stime
63  *
64  * Returns: 0 on failure
65  *          1 on success, jr is unchanged, but stime is set
66  */
67 bool
68 db_find_job_start_time(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM **stime)
69 {
70    SQL_ROW row;
71    char ed1[50], ed2[50];
72
73    db_lock(mdb);
74
75    pm_strcpy(stime, "0000-00-00 00:00:00");   /* default */
76    /* If no Id given, we must find corresponding job */
77    if (jr->JobId == 0) {
78       /* Differential is since last Full backup */
79       Mmsg(mdb->cmd,
80 "SELECT StartTime FROM Job WHERE JobStatus IN ('T','W') AND Type='%c' AND "
81 "Level='%c' AND Name='%s' AND ClientId=%s AND FileSetId=%s "
82 "ORDER BY StartTime DESC LIMIT 1",
83            jr->JobType, L_FULL, jr->Name, 
84            edit_int64(jr->ClientId, ed1), edit_int64(jr->FileSetId, ed2));
85
86       if (jr->JobLevel == L_DIFFERENTIAL) {
87          /* SQL cmd for Differential backup already edited above */
88
89       /* Incremental is since last Full, Incremental, or Differential */
90       } else if (jr->JobLevel == L_INCREMENTAL) {
91          /*
92           * For an Incremental job, we must first ensure
93           *  that a Full backup was done (cmd edited above)
94           *  then we do a second look to find the most recent
95           *  backup
96           */
97          if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
98             Mmsg2(&mdb->errmsg, _("Query error for start time request: ERR=%s\nCMD=%s\n"),
99                sql_strerror(mdb), mdb->cmd);
100             goto bail_out;
101          }
102          if ((row = sql_fetch_row(mdb)) == NULL) {
103             sql_free_result(mdb);
104             Mmsg(mdb->errmsg, _("No prior Full backup Job record found.\n"));
105             goto bail_out;
106          }
107          sql_free_result(mdb);
108          /* Now edit SQL command for Incremental Job */
109          Mmsg(mdb->cmd,
110 "SELECT StartTime FROM Job WHERE JobStatus IN ('T','W') AND Type='%c' AND "
111 "Level IN ('%c','%c','%c') AND Name='%s' AND ClientId=%s "
112 "AND FileSetId=%s ORDER BY StartTime DESC LIMIT 1",
113             jr->JobType, L_INCREMENTAL, L_DIFFERENTIAL, L_FULL, jr->Name,
114             edit_int64(jr->ClientId, ed1), edit_int64(jr->FileSetId, ed2));
115       } else {
116          Mmsg1(mdb->errmsg, _("Unknown level=%d\n"), jr->JobLevel);
117          goto bail_out;
118       }
119    } else {
120       Dmsg1(100, "Submitting: %s\n", mdb->cmd);
121       Mmsg(mdb->cmd, "SELECT StartTime FROM Job WHERE Job.JobId=%s", 
122            edit_int64(jr->JobId, ed1));
123    }
124
125    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
126       pm_strcpy(stime, "");                   /* set EOS */
127       Mmsg2(&mdb->errmsg, _("Query error for start time request: ERR=%s\nCMD=%s\n"),
128          sql_strerror(mdb),  mdb->cmd);
129       goto bail_out;
130    }
131
132    if ((row = sql_fetch_row(mdb)) == NULL) {
133       Mmsg2(&mdb->errmsg, _("No Job record found: ERR=%s\nCMD=%s\n"),
134          sql_strerror(mdb),  mdb->cmd);
135       sql_free_result(mdb);
136       goto bail_out;
137    }
138    Dmsg1(100, "Got start time: %s\n", row[0]);
139    pm_strcpy(stime, row[0]);
140
141    sql_free_result(mdb);
142
143    db_unlock(mdb);
144    return true;
145
146 bail_out:
147    db_unlock(mdb);
148    return false;
149 }
150
151
152 /*
153  * Find the last job start time for the specified JobLevel
154  *
155  *  StartTime is returned in stime
156  *
157  * Returns: false on failure
158  *          true  on success, jr is unchanged, but stime is set
159  */
160 bool
161 db_find_last_job_start_time(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM **stime, int JobLevel)
162 {
163    SQL_ROW row;
164    char ed1[50], ed2[50];
165
166    db_lock(mdb);
167
168    pm_strcpy(stime, "0000-00-00 00:00:00");   /* default */
169
170    Mmsg(mdb->cmd,
171 "SELECT StartTime FROM Job WHERE JobStatus IN ('T','W') AND Type='%c' AND "
172 "Level='%c' AND Name='%s' AND ClientId=%s AND FileSetId=%s "
173 "ORDER BY StartTime DESC LIMIT 1",
174       jr->JobType, JobLevel, jr->Name, 
175       edit_int64(jr->ClientId, ed1), edit_int64(jr->FileSetId, ed2));
176    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
177       Mmsg2(&mdb->errmsg, _("Query error for start time request: ERR=%s\nCMD=%s\n"),
178          sql_strerror(mdb), mdb->cmd);
179       goto bail_out;
180    }
181    if ((row = sql_fetch_row(mdb)) == NULL) {
182       sql_free_result(mdb);
183       Mmsg(mdb->errmsg, _("No prior Full backup Job record found.\n"));
184       goto bail_out;
185    }
186    Dmsg1(100, "Got start time: %s\n", row[0]);
187    pm_strcpy(stime, row[0]);
188    sql_free_result(mdb);
189    db_unlock(mdb);
190    return true;
191
192 bail_out:
193    db_unlock(mdb);
194    return false;
195 }
196
197 /*
198  * Find last failed job since given start-time
199  *   it must be either Full or Diff.
200  *
201  * Returns: false on failure
202  *          true  on success, jr is unchanged and stime unchanged
203  *                level returned in JobLevel
204  */
205 bool
206 db_find_failed_job_since(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *stime, int &JobLevel)
207 {
208    SQL_ROW row;
209    char ed1[50], ed2[50];
210
211    db_lock(mdb);
212    /* Differential is since last Full backup */
213    Mmsg(mdb->cmd,
214 "SELECT Level FROM Job WHERE JobStatus!='T' AND Type='%c' AND "
215 "Level IN ('%c','%c') AND Name='%s' AND ClientId=%s "
216 "AND FileSetId=%s AND StartTime>'%s' "
217 "ORDER BY StartTime DESC LIMIT 1",
218          jr->JobType, L_FULL, L_DIFFERENTIAL, jr->Name,
219          edit_int64(jr->ClientId, ed1), edit_int64(jr->FileSetId, ed2),
220          stime);
221
222    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
223       db_unlock(mdb);
224       return false;
225    }
226
227    if ((row = sql_fetch_row(mdb)) == NULL) {
228       sql_free_result(mdb);
229       db_unlock(mdb);
230       return false;
231    }
232    JobLevel = (int)*row[0];
233    sql_free_result(mdb);
234
235    db_unlock(mdb);
236    return true;
237 }
238
239
240 /*
241  * Find JobId of last job that ran.  E.g. for
242  *   VERIFY_CATALOG we want the JobId of the last INIT.
243  *   For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the last Job.
244  *
245  * Returns: true  on success
246  *          false on failure
247  */
248 bool
249 db_find_last_jobid(JCR *jcr, B_DB *mdb, const char *Name, JOB_DBR *jr)
250 {
251    SQL_ROW row;
252    char ed1[50];
253
254    /* Find last full */
255    db_lock(mdb);
256    Dmsg2(100, "JobLevel=%d JobType=%d\n", jr->JobLevel, jr->JobType);
257    if (jr->JobLevel == L_VERIFY_CATALOG) {
258       Mmsg(mdb->cmd,
259 "SELECT JobId FROM Job WHERE Type='V' AND Level='%c' AND "
260 " JobStatus IN ('T','W') AND Name='%s' AND "
261 "ClientId=%s ORDER BY StartTime DESC LIMIT 1",
262            L_VERIFY_INIT, jr->Name, 
263            edit_int64(jr->ClientId, ed1));
264    } else if (jr->JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
265               jr->JobLevel == L_VERIFY_DISK_TO_CATALOG ||
266               jr->JobType == JT_BACKUP) {
267       if (Name) {
268          Mmsg(mdb->cmd,
269 "SELECT JobId FROM Job WHERE Type='B' AND JobStatus IN ('T','W') AND "
270 "Name='%s' ORDER BY StartTime DESC LIMIT 1", Name);
271       } else {
272          Mmsg(mdb->cmd,
273 "SELECT JobId FROM Job WHERE Type='B' AND JobStatus IN ('T','W') AND "
274 "ClientId=%s ORDER BY StartTime DESC LIMIT 1", 
275            edit_int64(jr->ClientId, ed1));
276       }
277    } else {
278       Mmsg1(&mdb->errmsg, _("Unknown Job level=%d\n"), jr->JobLevel);
279       db_unlock(mdb);
280       return false;
281    }
282    Dmsg1(100, "Query: %s\n", mdb->cmd);
283    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
284       db_unlock(mdb);
285       return false;
286    }
287    if ((row = sql_fetch_row(mdb)) == NULL) {
288       Mmsg1(&mdb->errmsg, _("No Job found for: %s.\n"), mdb->cmd);
289       sql_free_result(mdb);
290       db_unlock(mdb);
291       return false;
292    }
293
294    jr->JobId = str_to_int64(row[0]);
295    sql_free_result(mdb);
296
297    Dmsg1(100, "db_get_last_jobid: got JobId=%d\n", jr->JobId);
298    if (jr->JobId <= 0) {
299       Mmsg1(&mdb->errmsg, _("No Job found for: %s\n"), mdb->cmd);
300       db_unlock(mdb);
301       return false;
302    }
303
304    db_unlock(mdb);
305    return true;
306 }
307
308 /*
309  * Find Available Media (Volume) for Pool
310  *
311  * Find a Volume for a given PoolId, MediaType, and Status.
312  *
313  * Returns: 0 on failure
314  *          numrows on success
315  */
316 int
317 db_find_next_volume(JCR *jcr, B_DB *mdb, int item, bool InChanger, MEDIA_DBR *mr)
318 {
319    SQL_ROW row = NULL;
320    int numrows;
321    const char *order;
322
323    char ed1[50];
324
325    db_lock(mdb);
326    if (item == -1) {       /* find oldest volume */
327       /* Find oldest volume */
328       Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
329          "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
330          "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
331          "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
332          "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
333          "Enabled,LocationId,RecycleCount,InitialWrite,"
334          "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime "
335          "FROM Media WHERE PoolId=%s AND MediaType='%s' AND VolStatus IN ('Full',"
336          "'Recycle','Purged','Used','Append') AND Enabled=1 "
337          "ORDER BY LastWritten LIMIT 1", 
338          edit_int64(mr->PoolId, ed1), mr->MediaType);
339      item = 1;
340    } else {
341       POOL_MEM changer(PM_FNAME);
342       /* Find next available volume */
343       if (InChanger) {
344          Mmsg(changer, "AND InChanger=1 AND StorageId=%s",
345               edit_int64(mr->StorageId, ed1));
346       }
347       if (strcmp(mr->VolStatus, "Recycle") == 0 ||
348           strcmp(mr->VolStatus, "Purged") == 0) {
349          order = "AND Recycle=1 ORDER BY LastWritten ASC,MediaId";  /* take oldest that can be recycled */
350       } else {
351          order = "ORDER BY LastWritten IS NULL,LastWritten DESC,MediaId";   /* take most recently written */
352       }
353       Mmsg(mdb->cmd, "SELECT MediaId,VolumeName,VolJobs,VolFiles,VolBlocks,"
354          "VolBytes,VolMounts,VolErrors,VolWrites,MaxVolBytes,VolCapacityBytes,"
355          "MediaType,VolStatus,PoolId,VolRetention,VolUseDuration,MaxVolJobs,"
356          "MaxVolFiles,Recycle,Slot,FirstWritten,LastWritten,InChanger,"
357          "EndFile,EndBlock,VolParts,LabelType,LabelDate,StorageId,"
358          "Enabled,LocationId,RecycleCount,InitialWrite,"
359          "ScratchPoolId,RecyclePoolId,VolReadTime,VolWriteTime "
360          "FROM Media WHERE PoolId=%s AND MediaType='%s' AND Enabled=1 "
361          "AND VolStatus='%s' "
362          "%s "
363          "%s LIMIT %d",
364          edit_int64(mr->PoolId, ed1), mr->MediaType,
365          mr->VolStatus, changer.c_str(), order, item);
366    }
367    Dmsg1(050, "fnextvol=%s\n", mdb->cmd);
368    if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
369       db_unlock(mdb);
370       return 0;
371    }
372
373    numrows = sql_num_rows(mdb);
374    if (item > numrows || item < 1) {
375       Dmsg2(050, "item=%d got=%d\n", item, numrows);
376       Mmsg2(&mdb->errmsg, _("Request for Volume item %d greater than max %d or less than 1\n"),
377          item, numrows);
378       db_unlock(mdb);
379       return 0;
380    }
381
382    /* Note, we previously seeked to the row using:
383     *  sql_data_seek(mdb, item-1);
384     * but this failed on PostgreSQL, so now we loop
385     * over all the records.  This should not be too horrible since
386     * the maximum Volumes we look at in any case is 20.
387     */
388    while (item-- > 0) {
389       if ((row = sql_fetch_row(mdb)) == NULL) {
390          Dmsg1(050, "Fail fetch item=%d\n", item+1);
391          Mmsg1(&mdb->errmsg, _("No Volume record found for item %d.\n"), item);
392          sql_free_result(mdb);
393          db_unlock(mdb);
394          return 0;
395       }
396    }
397
398    /* Return fields in Media Record */
399    mr->MediaId = str_to_int64(row[0]);
400    bstrncpy(mr->VolumeName, row[1]!=NULL?row[1]:"", sizeof(mr->VolumeName));
401    mr->VolJobs = str_to_int64(row[2]);
402    mr->VolFiles = str_to_int64(row[3]);
403    mr->VolBlocks = str_to_int64(row[4]);
404    mr->VolBytes = str_to_uint64(row[5]);
405    mr->VolMounts = str_to_int64(row[6]);
406    mr->VolErrors = str_to_int64(row[7]);
407    mr->VolWrites = str_to_int64(row[8]);
408    mr->MaxVolBytes = str_to_uint64(row[9]);
409    mr->VolCapacityBytes = str_to_uint64(row[10]);
410    bstrncpy(mr->MediaType, row[11]!=NULL?row[11]:"", sizeof(mr->MediaType));
411    bstrncpy(mr->VolStatus, row[12]!=NULL?row[12]:"", sizeof(mr->VolStatus));
412    mr->PoolId = str_to_int64(row[13]);
413    mr->VolRetention = str_to_uint64(row[14]);
414    mr->VolUseDuration = str_to_uint64(row[15]);
415    mr->MaxVolJobs = str_to_int64(row[16]);
416    mr->MaxVolFiles = str_to_int64(row[17]);
417    mr->Recycle = str_to_int64(row[18]);
418    mr->Slot = str_to_int64(row[19]);
419    bstrncpy(mr->cFirstWritten, row[20]!=NULL?row[20]:"", sizeof(mr->cFirstWritten));
420    mr->FirstWritten = (time_t)str_to_utime(mr->cFirstWritten);
421    bstrncpy(mr->cLastWritten, row[21]!=NULL?row[21]:"", sizeof(mr->cLastWritten));
422    mr->LastWritten = (time_t)str_to_utime(mr->cLastWritten);
423    mr->InChanger = str_to_uint64(row[22]);
424    mr->EndFile = str_to_uint64(row[23]);
425    mr->EndBlock = str_to_uint64(row[24]);
426    mr->VolParts = str_to_int64(row[25]);
427    mr->LabelType = str_to_int64(row[26]);
428    bstrncpy(mr->cLabelDate, row[27]!=NULL?row[27]:"", sizeof(mr->cLabelDate));
429    mr->LabelDate = (time_t)str_to_utime(mr->cLabelDate);
430    mr->StorageId = str_to_int64(row[28]);
431    mr->Enabled = str_to_int64(row[29]);
432    mr->LocationId = str_to_int64(row[30]);
433    mr->RecycleCount = str_to_int64(row[31]);
434    bstrncpy(mr->cInitialWrite, row[32]!=NULL?row[32]:"", sizeof(mr->cInitialWrite));
435    mr->InitialWrite = (time_t)str_to_utime(mr->cInitialWrite);
436    mr->ScratchPoolId = str_to_int64(row[33]);
437    mr->RecyclePoolId = str_to_int64(row[34]);
438    mr->VolReadTime = str_to_int64(row[35]);
439    mr->VolWriteTime = str_to_int64(row[36]);
440
441    sql_free_result(mdb);
442
443    db_unlock(mdb);
444    Dmsg1(050, "Rtn numrows=%d\n", numrows);
445    return numrows;
446 }
447
448
449 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL*/