]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
Fix bug pointed out by Peter Much that causes the StorageId to
[bacula/bacula] / bacula / src / dird / ua_prune.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2007 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 John Walker.
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  *
30  *   Bacula Director -- User Agent Database prune Command
31  *      Applies retention periods
32  *
33  *     Kern Sibbald, February MMII
34  *
35  *   Version $Id$
36  */
37
38 #include "bacula.h"
39 #include "dird.h"
40
41 /* Imported functions */
42
43 /* Forward referenced functions */
44
45 /*
46  * Called here to count entries to be deleted
47  */
48 int del_count_handler(void *ctx, int num_fields, char **row)
49 {
50    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
51
52    if (row[0]) {
53       cnt->count = str_to_int64(row[0]);
54    } else {
55       cnt->count = 0;
56    }
57    return 0;
58 }
59
60
61 /*
62  * Called here to make in memory list of JobIds to be
63  *  deleted and the associated PurgedFiles flag.
64  *  The in memory list will then be transversed
65  *  to issue the SQL DELETE commands.  Note, the list
66  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
67  *  maximum malloc'ed memory.
68  */
69 int job_delete_handler(void *ctx, int num_fields, char **row)
70 {
71    struct del_ctx *del = (struct del_ctx *)ctx;
72
73    if (del->num_ids == MAX_DEL_LIST_LEN) {
74       return 1;
75    }
76    if (del->num_ids == del->max_ids) {
77       del->max_ids = (del->max_ids * 3) / 2;
78       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
79       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
80    }
81    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
82 // Dmsg2(60, "row=%d val=%d\n", del->num_ids, del->JobId[del->num_ids]);
83    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[1]);
84    return 0;
85 }
86
87 int file_delete_handler(void *ctx, int num_fields, char **row)
88 {
89    struct del_ctx *del = (struct del_ctx *)ctx;
90
91    if (del->num_ids == MAX_DEL_LIST_LEN) {
92       return 1;
93    }
94    if (del->num_ids == del->max_ids) {
95       del->max_ids = (del->max_ids * 3) / 2;
96       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
97          del->max_ids);
98    }
99    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
100 // Dmsg2(150, "row=%d val=%d\n", del->num_ids-1, del->JobId[del->num_ids-1]);
101    return 0;
102 }
103
104 /*
105  *   Prune records from database
106  *
107  *    prune files (from) client=xxx
108  *    prune jobs (from) client=xxx
109  *    prune volume=xxx
110  *    prune stats
111  */
112 int prunecmd(UAContext *ua, const char *cmd)
113 {
114    CLIENT *client;
115    POOL_DBR pr;
116    MEDIA_DBR mr;
117    int kw;
118
119    static const char *keywords[] = {
120       NT_("Files"),
121       NT_("Jobs"),
122       NT_("Volume"),
123       NT_("Stats"),
124       NULL};
125
126    if (!open_client_db(ua)) {
127       return false;
128    }
129
130    /* First search args */
131    kw = find_arg_keyword(ua, keywords);
132    if (kw < 0 || kw > 3) {
133       /* no args, so ask user */
134       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
135    }
136
137    switch (kw) {
138    case 0:  /* prune files */
139       client = get_client_resource(ua);
140       if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
141          return false;
142       }
143       prune_files(ua, client);
144       return true;
145    case 1:  /* prune jobs */
146       client = get_client_resource(ua);
147       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
148          return false;
149       }
150       /* ****FIXME**** allow user to select JobType */
151       prune_jobs(ua, client, JT_BACKUP);
152       return 1;
153    case 2:  /* prune volume */
154       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
155          return false;
156       }
157       if (mr.Enabled == 2) {
158          ua->error_msg(_("Cannot prune Volume \"%s\" because it is archived.\n"),
159             mr.VolumeName);
160          return false;
161       }
162       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
163          return false;
164       }
165       prune_volume(ua, &mr);
166       return true;
167    case 3:  /* prune stats */
168       /* TODO: prune JobStat table */
169       return true;
170    default:
171       break;
172    }
173
174    return true;
175 }
176
177 /*
178  * Prune File records from the database. For any Job which
179  * is older than the retention period, we unconditionally delete
180  * all File records for that Job.  This is simple enough that no
181  * temporary tables are needed. We simply make an in memory list of
182  * the JobIds meeting the prune conditions, then delete all File records
183  * pointing to each of those JobIds.
184  *
185  * This routine assumes you want the pruning to be done. All checking
186  *  must be done before calling this routine.
187  */
188 int prune_files(UAContext *ua, CLIENT *client)
189 {
190    struct del_ctx del;
191    struct s_count_ctx cnt;
192    POOL_MEM query(PM_MESSAGE);
193    utime_t now, period;
194    CLIENT_DBR cr;
195    char ed1[50], ed2[50];
196
197    db_lock(ua->db);
198    memset(&cr, 0, sizeof(cr));
199    memset(&del, 0, sizeof(del));
200    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
201    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
202       db_unlock(ua->db);
203       return 0;
204    }
205
206    period = client->FileRetention;
207    now = (utime_t)time(NULL);
208
209    /* Select Jobs -- for counting */
210    Mmsg(query, count_select_job, edit_uint64(now - period, ed1), 
211         edit_int64(cr.ClientId, ed2));
212    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
213                (uint32_t)period, query.c_str());
214    cnt.count = 0;
215    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
216       ua->error_msg("%s", db_strerror(ua->db));
217       Dmsg0(050, "Count failed\n");
218       goto bail_out;
219    }
220
221    if (cnt.count == 0) {
222       if (ua->verbose) {
223          ua->warning_msg(_("No Files found to prune.\n"));
224       }
225       goto bail_out;
226    }
227
228    if (cnt.count < MAX_DEL_LIST_LEN) {
229       del.max_ids = cnt.count + 1;
230    } else {
231       del.max_ids = MAX_DEL_LIST_LEN;
232    }
233    del.tot_ids = 0;
234
235    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
236
237    /* Now process same set but making a delete list */
238    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
239         edit_int64(cr.ClientId, ed2));
240    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
241
242    purge_files_from_job_list(ua, del);
243
244    edit_uint64_with_commas(del.num_del, ed1);
245    ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"),
246       ed1, client->name());
247
248 bail_out:
249    db_unlock(ua->db);
250    if (del.JobId) {
251       free(del.JobId);
252    }
253    return 1;
254 }
255
256
257 static void drop_temp_tables(UAContext *ua)
258 {
259    int i;
260    for (i=0; drop_deltabs[i]; i++) {
261       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
262    }
263 }
264
265 static bool create_temp_tables(UAContext *ua)
266 {
267    /* Create temp tables and indicies */
268    if (!db_sql_query(ua->db, create_deltabs[db_type], NULL, (void *)NULL)) {
269       ua->error_msg("%s", db_strerror(ua->db));
270       Dmsg0(050, "create DelTables table failed\n");
271       return false;
272    }
273    if (!db_sql_query(ua->db, create_delindex, NULL, (void *)NULL)) {
274        ua->error_msg("%s", db_strerror(ua->db));
275        Dmsg0(050, "create DelInx1 index failed\n");
276        return false;
277    }
278    return true;
279 }
280
281
282
283 /*
284  * Pruning Jobs is a bit more complicated than purging Files
285  * because we delete Job records only if there is a more current
286  * backup of the FileSet. Otherwise, we keep the Job record.
287  * In other words, we never delete the only Job record that
288  * contains a current backup of a FileSet. This prevents the
289  * Volume from being recycled and destroying a current backup.
290  *
291  * For Verify Jobs, we do not delete the last InitCatalog.
292  *
293  * For Restore Jobs there are no restrictions.
294  */
295 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
296 {
297    struct del_ctx del;
298    POOL_MEM query(PM_MESSAGE);
299    utime_t now, period;
300    CLIENT_DBR cr;
301    char ed1[50], ed2[50];
302
303    db_lock(ua->db);
304    memset(&cr, 0, sizeof(cr));
305    memset(&del, 0, sizeof(del));
306
307    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
308    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
309       db_unlock(ua->db);
310       return 0;
311    }
312
313    period = client->JobRetention;
314    now = (utime_t)time(NULL);
315
316    /* Drop any previous temporary tables still there */
317    drop_temp_tables(ua);
318
319    /* Create temp tables and indicies */
320    if (!create_temp_tables(ua)) {
321       goto bail_out;
322    }
323
324    /*
325     * Select all files that are older than the JobRetention period
326     *  and stuff them into the "DeletionCandidates" table.
327     */
328    edit_uint64(now - period, ed1);
329    Mmsg(query, insert_delcand, (char)JobType, ed1, 
330         edit_int64(cr.ClientId, ed2));
331    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
332       if (ua->verbose) {
333          ua->error_msg("%s", db_strerror(ua->db));
334       }
335       Dmsg0(050, "insert delcand failed\n");
336       goto bail_out;
337    }
338
339    del.max_ids = 100;
340    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
341    del.PurgedFiles = (char *)malloc(del.max_ids);
342
343    /* ed1 = JobTDate */
344    edit_int64(cr.ClientId, ed2);
345    switch (JobType) {
346    case JT_BACKUP:
347       Mmsg(query, select_backup_del, ed1, ed2);
348       break;
349    case JT_RESTORE:
350       Mmsg(query, select_restore_del, ed1, ed2);
351       break;
352    case JT_VERIFY:
353       Mmsg(query, select_verify_del, ed1, ed2);
354       break;
355    case JT_ADMIN:
356       Mmsg(query, select_admin_del, ed1, ed2);
357       break;
358    case JT_COPY:
359       Mmsg(query, select_copy_del, ed1, ed2);
360       break;
361    case JT_MIGRATE:
362       Mmsg(query, select_migrate_del, ed1, ed2);
363       break;
364    }
365
366    Dmsg1(150, "Query=%s\n", query.c_str());
367    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
368       ua->error_msg("%s", db_strerror(ua->db));
369    }
370
371    purge_job_list_from_catalog(ua, del);
372
373    if (del.num_del > 0) {
374       ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del,
375          del.num_del==1?_("Job"):_("Jobs"), client->name());
376     } else if (ua->verbose) {
377        ua->info_msg(_("No Jobs found to prune.\n"));
378     }
379
380 bail_out:
381    drop_temp_tables(ua);
382    db_unlock(ua->db);
383    if (del.JobId) {
384       free(del.JobId);
385    }
386    if (del.PurgedFiles) {
387       free(del.PurgedFiles);
388    }
389    return 1;
390 }
391
392 /*
393  * Prune a given Volume
394  */
395 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
396 {
397    POOL_MEM query(PM_MESSAGE);
398    struct del_ctx del;
399    bool ok = false;
400    int count;
401
402    if (mr->Enabled == 2) {
403       return false;                   /* Cannot prune archived volumes */
404    }
405
406    memset(&del, 0, sizeof(del));
407    del.max_ids = 10000;
408    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
409
410    db_lock(ua->db);
411
412    /* Prune only Volumes with status "Full", or "Used" */
413    if (strcmp(mr->VolStatus, "Full")   == 0 ||
414        strcmp(mr->VolStatus, "Used")   == 0) {
415       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
416       count = get_prune_list_for_volume(ua, mr, &del);
417       Dmsg1(050, "Num pruned = %d\n", count);
418       if (count != 0) {
419          purge_job_list_from_catalog(ua, del);
420       }
421       ok = is_volume_purged(ua, mr);
422    }
423
424    db_unlock(ua->db);
425    if (del.JobId) {
426       free(del.JobId);
427    }
428    return ok;
429 }
430
431 /*
432  * Get prune list for a volume
433  */
434 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
435 {
436    POOL_MEM query(PM_MESSAGE);
437    int count = 0;
438    int i;          
439    utime_t now, period;
440    char ed1[50], ed2[50];
441
442    if (mr->Enabled == 2) {
443       return 0;                    /* cannot prune Archived volumes */
444    }
445
446    db_lock(ua->db);
447
448    /*
449     * Now add to the  list of JobIds for Jobs written to this Volume
450     */
451    edit_int64(mr->MediaId, ed1); 
452    period = mr->VolRetention;
453    now = (utime_t)time(NULL);
454    edit_uint64(now-period, ed2);
455    Mmsg(query, sel_JobMedia, ed1, ed2);
456    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
457       (int)(now-period));
458
459    Dmsg1(050, "Query=%s\n", query.c_str());
460    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
461       if (ua->verbose) {
462          ua->error_msg("%s", db_strerror(ua->db));
463       }
464       Dmsg0(050, "Count failed\n");
465       goto bail_out;
466    }
467
468    for (i=0; i < del->num_ids; i++) {
469       if (ua->jcr->JobId == del->JobId[i]) {
470          Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
471          del->JobId[i] = 0;
472          continue;
473       }
474       Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);
475       count++;
476    }
477
478 bail_out:
479    db_unlock(ua->db);
480    return count;
481 }