]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
kes Modify job report to include director name and Build OS.
[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 plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of 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  */
111 int prunecmd(UAContext *ua, const char *cmd)
112 {
113    CLIENT *client;
114    POOL_DBR pr;
115    MEDIA_DBR mr;
116    int kw;
117
118    static const char *keywords[] = {
119       NT_("Files"),
120       NT_("Jobs"),
121       NT_("Volume"),
122       NULL};
123
124    if (!open_client_db(ua)) {
125       return false;
126    }
127
128    /* First search args */
129    kw = find_arg_keyword(ua, keywords);
130    if (kw < 0 || kw > 2) {
131       /* no args, so ask user */
132       kw = do_keyword_prompt(ua, _("Choose item to prune"), keywords);
133    }
134
135    switch (kw) {
136    case 0:  /* prune files */
137       client = get_client_resource(ua);
138       if (!client || !confirm_retention(ua, &client->FileRetention, "File")) {
139          return false;
140       }
141       prune_files(ua, client);
142       return true;
143    case 1:  /* prune jobs */
144       client = get_client_resource(ua);
145       if (!client || !confirm_retention(ua, &client->JobRetention, "Job")) {
146          return false;
147       }
148       /* ****FIXME**** allow user to select JobType */
149       prune_jobs(ua, client, JT_BACKUP);
150       return 1;
151    case 2:  /* prune volume */
152       if (!select_pool_and_media_dbr(ua, &pr, &mr)) {
153          return false;
154       }
155       if (mr.Enabled == 2) {
156          bsendmsg(ua, _("Cannot prune Volume \"%s\" because it is archived.\n"),
157             mr.VolumeName);
158       }
159       if (!confirm_retention(ua, &mr.VolRetention, "Volume")) {
160          return false;
161       }
162       prune_volume(ua, &mr);
163       return true;
164    default:
165       break;
166    }
167
168    return true;
169 }
170
171 /*
172  * Prune File records from the database. For any Job which
173  * is older than the retention period, we unconditionally delete
174  * all File records for that Job.  This is simple enough that no
175  * temporary tables are needed. We simply make an in memory list of
176  * the JobIds meeting the prune conditions, then delete all File records
177  * pointing to each of those JobIds.
178  *
179  * This routine assumes you want the pruning to be done. All checking
180  *  must be done before calling this routine.
181  */
182 int prune_files(UAContext *ua, CLIENT *client)
183 {
184    struct del_ctx del;
185    struct s_count_ctx cnt;
186    POOL_MEM query(PM_MESSAGE);
187    utime_t now, period;
188    CLIENT_DBR cr;
189    char ed1[50], ed2[50];
190
191    db_lock(ua->db);
192    memset(&cr, 0, sizeof(cr));
193    memset(&del, 0, sizeof(del));
194    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
195    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
196       db_unlock(ua->db);
197       return 0;
198    }
199
200    period = client->FileRetention;
201    now = (utime_t)time(NULL);
202
203    /* Select Jobs -- for counting */
204    Mmsg(query, count_select_job, edit_uint64(now - period, ed1), 
205         edit_int64(cr.ClientId, ed2));
206    Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, 
207                (uint32_t)period, query.c_str());
208    cnt.count = 0;
209    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
210       bsendmsg(ua, "%s", db_strerror(ua->db));
211       Dmsg0(050, "Count failed\n");
212       goto bail_out;
213    }
214
215    if (cnt.count == 0) {
216       if (ua->verbose) {
217          bsendmsg(ua, _("No Files found to prune.\n"));
218       }
219       goto bail_out;
220    }
221
222    if (cnt.count < MAX_DEL_LIST_LEN) {
223       del.max_ids = cnt.count + 1;
224    } else {
225       del.max_ids = MAX_DEL_LIST_LEN;
226    }
227    del.tot_ids = 0;
228
229    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
230
231    /* Now process same set but making a delete list */
232    Mmsg(query, select_job, edit_uint64(now - period, ed1), 
233         edit_int64(cr.ClientId, ed2));
234    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
235
236    purge_files_from_job_list(ua, del);
237
238    edit_uint64_with_commas(del.num_del, ed1);
239    bsendmsg(ua, _("Pruned Files from %s Jobs for client %s from catalog.\n"),
240       ed1, client->name());
241
242 bail_out:
243    db_unlock(ua->db);
244    if (del.JobId) {
245       free(del.JobId);
246    }
247    return 1;
248 }
249
250
251 static void drop_temp_tables(UAContext *ua)
252 {
253    int i;
254    for (i=0; drop_deltabs[i]; i++) {
255       db_sql_query(ua->db, drop_deltabs[i], NULL, (void *)NULL);
256    }
257 }
258
259 static bool create_temp_tables(UAContext *ua)
260 {
261    int i;
262    /* Create temp tables and indicies */
263    for (i=0; create_deltabs[i]; i++) {
264       if (!db_sql_query(ua->db, create_deltabs[i], NULL, (void *)NULL)) {
265          bsendmsg(ua, "%s", db_strerror(ua->db));
266          Dmsg0(050, "create DelTables table failed\n");
267          return false;
268       }
269    }
270    return true;
271 }
272
273
274
275 /*
276  * Pruning Jobs is a bit more complicated than purging Files
277  * because we delete Job records only if there is a more current
278  * backup of the FileSet. Otherwise, we keep the Job record.
279  * In other words, we never delete the only Job record that
280  * contains a current backup of a FileSet. This prevents the
281  * Volume from being recycled and destroying a current backup.
282  *
283  * For Verify Jobs, we do not delete the last InitCatalog.
284  *
285  * For Restore Jobs there are no restrictions.
286  */
287 int prune_jobs(UAContext *ua, CLIENT *client, int JobType)
288 {
289    struct del_ctx del;
290    POOL_MEM query(PM_MESSAGE);
291    utime_t now, period;
292    CLIENT_DBR cr;
293    char ed1[50], ed2[50];
294
295    db_lock(ua->db);
296    memset(&cr, 0, sizeof(cr));
297    memset(&del, 0, sizeof(del));
298
299    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
300    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
301       db_unlock(ua->db);
302       return 0;
303    }
304
305    period = client->JobRetention;
306    now = (utime_t)time(NULL);
307
308    /* Drop any previous temporary tables still there */
309    drop_temp_tables(ua);
310
311    /* Create temp tables and indicies */
312    if (!create_temp_tables(ua)) {
313       goto bail_out;
314    }
315
316    /*
317     * Select all files that are older than the JobRetention period
318     *  and stuff them into the "DeletionCandidates" table.
319     */
320    edit_uint64(now - period, ed1);
321    Mmsg(query, insert_delcand, (char)JobType, ed1, 
322         edit_int64(cr.ClientId, ed2));
323    if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) {
324       if (ua->verbose) {
325          bsendmsg(ua, "%s", db_strerror(ua->db));
326       }
327       Dmsg0(050, "insert delcand failed\n");
328       goto bail_out;
329    }
330
331    del.max_ids = 100;
332    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
333    del.PurgedFiles = (char *)malloc(del.max_ids);
334
335    /* ed1 = JobTDate */
336    edit_int64(cr.ClientId, ed2);
337    switch (JobType) {
338    case JT_BACKUP:
339       Mmsg(query, select_backup_del, ed1, ed2);
340       break;
341    case JT_RESTORE:
342       Mmsg(query, select_restore_del, ed1, ed2);
343       break;
344    case JT_VERIFY:
345       Mmsg(query, select_verify_del, ed1, ed2);
346       break;
347    case JT_ADMIN:
348       Mmsg(query, select_admin_del, ed1, ed2);
349       break;
350    case JT_MIGRATE:
351       Mmsg(query, select_migrate_del, ed1, ed2);
352       break;
353    }
354
355    Dmsg1(150, "Query=%s\n", query.c_str());
356    if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) {
357       bsendmsg(ua, "%s", db_strerror(ua->db));
358    }
359
360    purge_job_list_from_catalog(ua, del);
361
362    if (del.num_del > 0) {
363       bsendmsg(ua, _("Pruned %d %s for client %s from catalog.\n"), del.num_del,
364          del.num_del==1?_("Job"):_("Jobs"), client->name());
365     } else if (ua->verbose) {
366        bsendmsg(ua, _("No Jobs found to prune.\n"));
367     }
368
369 bail_out:
370    drop_temp_tables(ua);
371    db_unlock(ua->db);
372    if (del.JobId) {
373       free(del.JobId);
374    }
375    if (del.PurgedFiles) {
376       free(del.PurgedFiles);
377    }
378    return 1;
379 }
380
381 /*
382  * Prune a given Volume
383  */
384 bool prune_volume(UAContext *ua, MEDIA_DBR *mr)
385 {
386    POOL_MEM query(PM_MESSAGE);
387    struct del_ctx del;
388    bool ok = false;
389    int count;
390
391    if (mr->Enabled == 2) {
392       return false;                   /* Cannot prune archived volumes */
393    }
394
395    memset(&del, 0, sizeof(del));
396    del.max_ids = 1000;
397    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
398
399    db_lock(ua->db);
400
401    /* Prune only Volumes with status "Full", or "Used" */
402    if (strcmp(mr->VolStatus, "Full")   == 0 ||
403        strcmp(mr->VolStatus, "Used")   == 0) {
404       Dmsg2(050, "get prune list MediaId=%d Volume %s\n", (int)mr->MediaId, mr->VolumeName);
405       count = get_prune_list_for_volume(ua, mr, &del);
406       Dmsg1(050, "Num pruned = %d\n", count);
407       if (count != 0) {
408          purge_job_list_from_catalog(ua, del);
409       }
410       ok = is_volume_purged(ua, mr);
411    }
412
413    db_unlock(ua->db);
414    if (del.JobId) {
415       free(del.JobId);
416    }
417    return ok;
418 }
419
420 /*
421  * Get prune list for a volume
422  */
423 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
424 {
425    POOL_MEM query(PM_MESSAGE);
426    int count = 0;
427    int i;          
428    utime_t now, period;
429    char ed1[50], ed2[50];
430
431    if (mr->Enabled == 2) {
432       return 0;                    /* cannot prune Archived volumes */
433    }
434
435    db_lock(ua->db);
436
437    /*
438     * Now add to the  list of JobIds for Jobs written to this Volume
439     */
440    edit_int64(mr->MediaId, ed1); 
441    period = mr->VolRetention;
442    now = (utime_t)time(NULL);
443    edit_uint64(now-period, ed2);
444    Mmsg(query, sel_JobMedia, ed1, ed2);
445    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
446       (int)(now-period));
447
448    Dmsg1(050, "Query=%s\n", query.c_str());
449    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
450       if (ua->verbose) {
451          bsendmsg(ua, "%s", db_strerror(ua->db));
452       }
453       Dmsg0(050, "Count failed\n");
454       goto bail_out;
455    }
456
457    for (i=0; i < del->num_ids; i++) {
458       if (ua->jcr->JobId == del->JobId[i]) {
459          Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
460          del->JobId[i] = 0;
461          continue;
462       }
463       Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);
464       count++;
465    }
466
467 bail_out:
468    db_unlock(ua->db);
469    return count;
470 }