]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_prune.c
Implement a new .sql command for bat
[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 s_count_ctx cnt;
388    struct del_ctx del;
389    int i;          
390    bool ok = false;
391    JOB_DBR jr;
392    utime_t now, period;
393    char ed1[50], ed2[50];
394
395    if (mr->Enabled == 2) {
396       return false;                   /* Cannot prune archived volumes */
397    }
398
399    db_lock(ua->db);
400    memset(&jr, 0, sizeof(jr));
401    memset(&del, 0, sizeof(del));
402
403    /*
404     * Find out how many Jobs remain on this Volume by
405     *  counting the JobMedia records.
406     */
407    cnt.count = 0;
408    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
409    Dmsg1(150, "Query=%s\n", query.c_str());
410    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
411       bsendmsg(ua, "%s", db_strerror(ua->db));
412       Dmsg0(050, "Count failed\n");
413       goto bail_out;
414    }
415
416    if (cnt.count == 0) {
417       /* Don't mark appendable volume as purged */
418       if (strcmp(mr->VolStatus, "Append") == 0 && verbose) {
419          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Prune not needed.\n"),
420             mr->VolumeName);
421          ok = true;
422          goto bail_out;
423       }
424       /* If volume not already purged, do so */
425       if (strcmp(mr->VolStatus, "Purged") != 0 && verbose) {
426          bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
427             mr->VolumeName);
428       }
429       ok = mark_media_purged(ua, mr);
430       goto bail_out;
431    }
432
433    if (cnt.count < MAX_DEL_LIST_LEN) {
434       del.max_ids = cnt.count + 1;
435    } else {
436       del.max_ids = MAX_DEL_LIST_LEN;
437    }
438
439    /*
440     * Now get a list of JobIds for Jobs written to this Volume
441     */
442    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
443
444    /* Use Volume Retention to prune Jobs and their Files */
445    period = mr->VolRetention;
446    now = (utime_t)time(NULL);
447    Mmsg(query, sel_JobMedia, edit_int64(mr->MediaId, ed1), 
448         edit_uint64(now-period, ed2));
449    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
450       (int)(now-period));
451
452    Dmsg1(150, "Query=%s\n", query.c_str());
453    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del)) {
454       if (ua->verbose) {
455          bsendmsg(ua, "%s", db_strerror(ua->db));
456       }
457       Dmsg0(050, "Count failed\n");
458       goto bail_out;
459    }
460
461
462    cnt.count = 0;
463    for (i=0; i < del.num_ids; i++) {
464       if (ua->jcr->JobId == del.JobId[i]) {
465          Dmsg2(250, "skip same job JobId[%d]=%d\n", i, (int)del.JobId[i]);
466          del.JobId[i] = 0;
467          continue;
468       }
469       Dmsg2(250, "accept JobId[%d]=%d\n", i, (int)del.JobId[i]);
470       cnt.count++;
471    }
472    if (cnt.count != 0) {
473       purge_job_list_from_catalog(ua, del);
474    } else {
475       Dmsg0(050, "No jobs to prune.\n");
476       goto bail_out;
477    }
478
479    if (ua->verbose && del.num_del != 0) {
480       bsendmsg(ua, _("Pruned %d %s on Volume \"%s\" from catalog.\n"), del.num_del,
481          del.num_del == 1 ? "Job" : "Jobs", mr->VolumeName);
482    }
483
484    /*
485     * Find out how many Jobs remain on this Volume by
486     *  counting the JobMedia records.
487     */
488    cnt.count = 0;
489    Mmsg(query, cnt_JobMedia, edit_int64(mr->MediaId, ed1));
490    Dmsg1(150, "Query=%s\n", query.c_str());
491    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
492       bsendmsg(ua, "%s", db_strerror(ua->db));
493       Dmsg0(050, "Count failed\n");
494       goto bail_out;
495    }
496    if (cnt.count == 0) {
497       Dmsg0(200, "Volume is purged.\n");
498       ok = mark_media_purged(ua, mr);
499    }
500
501 bail_out:
502    db_unlock(ua->db);
503    if (del.JobId) {
504       free(del.JobId);
505    }
506    return ok;
507 }
508
509 /*
510  * Get prune list for a volume
511  */
512 int get_prune_list_for_volume(UAContext *ua, MEDIA_DBR *mr, del_ctx *del)
513 {
514    POOL_MEM query(PM_MESSAGE);
515    int count = 0;
516    int i;          
517    utime_t now, period;
518    char ed1[50], ed2[50];
519
520    if (mr->Enabled == 2) {
521       return 0;                    /* cannot prune Archived volumes */
522    }
523
524    db_lock(ua->db);
525
526    /*
527     * Now add to the  list of JobIds for Jobs written to this Volume
528     */
529    edit_int64(mr->MediaId, ed1); 
530    period = mr->VolRetention;
531    now = (utime_t)time(NULL);
532    edit_uint64(now-period, ed2);
533    Mmsg(query, sel_JobMedia, ed1, ed2);
534    Dmsg3(250, "Now=%d period=%d now-period=%d\n", (int)now, (int)period,
535       (int)(now-period));
536
537    Dmsg1(050, "Query=%s\n", query.c_str());
538    if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)del)) {
539       if (ua->verbose) {
540          bsendmsg(ua, "%s", db_strerror(ua->db));
541       }
542       Dmsg0(050, "Count failed\n");
543       goto bail_out;
544    }
545
546    for (i=0; i < del->num_ids; i++) {
547       if (ua->jcr->JobId == del->JobId[i]) {
548          Dmsg2(150, "skip same job JobId[%d]=%d\n", i, (int)del->JobId[i]);
549          del->JobId[i] = 0;
550          continue;
551       }
552       Dmsg2(150, "accept JobId[%d]=%d\n", i, (int)del->JobId[i]);
553       count++;
554    }
555
556 bail_out:
557    db_unlock(ua->db);
558    return count;
559 }