]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
20Feb06
[bacula/bacula] / bacula / src / dird / ua_purge.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database Purge Command
4  *
5  *      Purges Files from specific JobIds
6  * or
7  *      Purges Jobs from Volumes
8  *
9  *     Kern Sibbald, February MMII
10  *
11  *   Version $Id$
12  */
13 /*
14    Copyright (C) 2002-2006 Kern Sibbald
15
16    This program is free software; you can redistribute it and/or
17    modify it under the terms of the GNU General Public License
18    version 2 as amended with additional clauses defined in the
19    file LICENSE in the main source directory.
20
21    This program is distributed in the hope that it will be useful,
22    but WITHOUT ANY WARRANTY; without even the implied warranty of
23    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
24    the file LICENSE for additional details.
25
26  */
27
28 #include "bacula.h"
29 #include "dird.h"
30
31 /* Forward referenced functions */
32 static int purge_files_from_client(UAContext *ua, CLIENT *client);
33 static int purge_jobs_from_client(UAContext *ua, CLIENT *client);
34
35 #define MAX_DEL_LIST_LEN 1000000
36
37 static const char *select_jobsfiles_from_client =
38    "SELECT JobId FROM Job "
39    "WHERE ClientId=%s "
40    "AND PurgedFiles=0";
41
42 static const char *select_jobs_from_client =
43    "SELECT JobId, PurgedFiles FROM Job "
44    "WHERE ClientId=%s";
45
46
47 /* In memory list of JobIds */
48 struct s_file_del_ctx {
49    JobId_t *JobId;
50    int num_ids;                       /* ids stored */
51    int max_ids;                       /* size of array */
52    int num_del;                       /* number deleted */
53    int tot_ids;                       /* total to process */
54 };
55
56 struct s_job_del_ctx {
57    JobId_t *JobId;                    /* array of JobIds */
58    char *PurgedFiles;                 /* Array of PurgedFile flags */
59    int num_ids;                       /* ids stored */
60    int max_ids;                       /* size of array */
61    int num_del;                       /* number deleted */
62    int tot_ids;                       /* total to process */
63 };
64
65 struct s_count_ctx {
66    int count;
67 };
68
69 /*
70  * Called here to count entries to be deleted
71  */
72 static int count_handler(void *ctx, int num_fields, char **row)
73 {
74    struct s_count_ctx *cnt = (struct s_count_ctx *)ctx;
75
76    if (row[0]) {
77       cnt->count = str_to_int64(row[0]);
78    } else {
79       cnt->count = 0;
80    }
81    return 0;
82 }
83
84 /*
85  * Called here to count entries to be deleted
86  */
87 static int file_count_handler(void *ctx, int num_fields, char **row)
88 {
89    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
90    del->tot_ids++;
91    return 0;
92 }
93
94
95 static int job_count_handler(void *ctx, int num_fields, char **row)
96 {
97    struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
98    del->tot_ids++;
99    return 0;
100 }
101
102
103 /*
104  * Called here to make in memory list of JobIds to be
105  *  deleted and the associated PurgedFiles flag.
106  *  The in memory list will then be transversed
107  *  to issue the SQL DELETE commands.  Note, the list
108  *  is allowed to get to MAX_DEL_LIST_LEN to limit the
109  *  maximum malloc'ed memory.
110  */
111 static int job_delete_handler(void *ctx, int num_fields, char **row)
112 {
113    struct s_job_del_ctx *del = (struct s_job_del_ctx *)ctx;
114
115    if (del->num_ids == MAX_DEL_LIST_LEN) {
116       return 1;
117    }
118    if (del->num_ids == del->max_ids) {
119       del->max_ids = (del->max_ids * 3) / 2;
120       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) * del->max_ids);
121       del->PurgedFiles = (char *)brealloc(del->PurgedFiles, del->max_ids);
122    }
123    del->JobId[del->num_ids] = (JobId_t)str_to_int64(row[0]);
124    del->PurgedFiles[del->num_ids++] = (char)str_to_int64(row[0]);
125    return 0;
126 }
127
128 static int file_delete_handler(void *ctx, int num_fields, char **row)
129 {
130    struct s_file_del_ctx *del = (struct s_file_del_ctx *)ctx;
131
132    if (del->num_ids == MAX_DEL_LIST_LEN) {
133       return 1;
134    }
135    if (del->num_ids == del->max_ids) {
136       del->max_ids = (del->max_ids * 3) / 2;
137       del->JobId = (JobId_t *)brealloc(del->JobId, sizeof(JobId_t) *
138          del->max_ids);
139    }
140    del->JobId[del->num_ids++] = (JobId_t)str_to_int64(row[0]);
141    return 0;
142 }
143
144 /*
145  *   Purge records from database
146  *
147  *     Purge Files (from) [Job|JobId|Client|Volume]
148  *     Purge Jobs  (from) [Client|Volume]
149  *
150  *  N.B. Not all above is implemented yet.
151  */
152 int purgecmd(UAContext *ua, const char *cmd)
153 {
154    int i;
155    CLIENT *client;
156    MEDIA_DBR mr;
157    JOB_DBR  jr;
158    static const char *keywords[] = {
159       N_("files"),
160       N_("jobs"),
161       N_("volume"),
162       NULL};
163
164    static const char *files_keywords[] = {
165       N_("Job"),
166       N_("JobId"),
167       N_("Client"),
168       N_("Volume"),
169       NULL};
170
171    static const char *jobs_keywords[] = {
172       N_("Client"),
173       N_("Volume"),
174       NULL};
175
176    bsendmsg(ua, _(
177       "\nThis command is can be DANGEROUS!!!\n\n"
178       "It purges (deletes) all Files from a Job,\n"
179       "JobId, Client or Volume; or it purges (deletes)\n"
180       "all Jobs from a Client or Volume without regard\n"
181       "for retention periods. Normally you should use the\n"
182       "PRUNE command, which respects retention periods.\n"));
183
184    if (!open_db(ua)) {
185       return 1;
186    }
187    switch (find_arg_keyword(ua, keywords)) {
188    /* Files */
189    case 0:
190       switch(find_arg_keyword(ua, files_keywords)) {
191       case 0:                         /* Job */
192       case 1:                         /* JobId */
193          if (get_job_dbr(ua, &jr)) {
194             purge_files_from_job(ua, &jr);
195          }
196          return 1;
197       case 2:                         /* client */
198          client = get_client_resource(ua);
199          if (client) {
200             purge_files_from_client(ua, client);
201          }
202          return 1;
203       case 3:                         /* Volume */
204          if (select_media_dbr(ua, &mr)) {
205             purge_files_from_volume(ua, &mr);
206          }
207          return 1;
208       }
209    /* Jobs */
210    case 1:
211       switch(find_arg_keyword(ua, jobs_keywords)) {
212       case 0:                         /* client */
213          client = get_client_resource(ua);
214          if (client) {
215             purge_jobs_from_client(ua, client);
216          }
217          return 1;
218       case 1:                         /* Volume */
219          if (select_media_dbr(ua, &mr)) {
220             purge_jobs_from_volume(ua, &mr);
221          }
222          return 1;
223       }
224    /* Volume */
225    case 2:
226       while ((i=find_arg(ua, N_("volume"))) >= 0) {
227          if (select_media_dbr(ua, &mr)) {
228             purge_jobs_from_volume(ua, &mr);
229          }
230          *ua->argk[i] = 0;            /* zap keyword already seen */
231          bsendmsg(ua, "\n");
232       }
233       return 1;
234    default:
235       break;
236    }
237    switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
238    case 0:                            /* files */
239       client = get_client_resource(ua);
240       if (client) {
241          purge_files_from_client(ua, client);
242       }
243       break;
244    case 1:                            /* jobs */
245       client = get_client_resource(ua);
246       if (client) {
247          purge_jobs_from_client(ua, client);
248       }
249       break;
250    case 2:                            /* Volume */
251       if (select_media_dbr(ua, &mr)) {
252          purge_jobs_from_volume(ua, &mr);
253       }
254       break;
255    }
256    return 1;
257 }
258
259 /*
260  * Purge File records from the database. For any Job which
261  * is older than the retention period, we unconditionally delete
262  * all File records for that Job.  This is simple enough that no
263  * temporary tables are needed. We simply make an in memory list of
264  * the JobIds meeting the prune conditions, then delete all File records
265  * pointing to each of those JobIds.
266  */
267 static int purge_files_from_client(UAContext *ua, CLIENT *client)
268 {
269    struct s_file_del_ctx del;
270    POOLMEM *query = get_pool_memory(PM_MESSAGE);
271    int i;
272    CLIENT_DBR cr;
273    char ed1[50];
274
275    memset(&cr, 0, sizeof(cr));
276    memset(&del, 0, sizeof(del));
277
278    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
279    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
280       return 0;
281    }
282    bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
283    Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
284
285    Dmsg1(050, "select sql=%s\n", query);
286
287    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
288       bsendmsg(ua, "%s", db_strerror(ua->db));
289       Dmsg0(050, "Count failed\n");
290       goto bail_out;
291    }
292
293    if (del.tot_ids == 0) {
294       bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
295          client->hdr.name, client->catalog->hdr.name);
296       goto bail_out;
297    }
298
299    if (del.tot_ids < MAX_DEL_LIST_LEN) {
300       del.max_ids = del.tot_ids + 1;
301    } else {
302       del.max_ids = MAX_DEL_LIST_LEN;
303    }
304    del.tot_ids = 0;
305
306    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
307
308    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
309
310    for (i=0; i < del.num_ids; i++) {
311       edit_int64(del.JobId[i], ed1);
312       Dmsg1(050, "Delete Files JobId=%s\n", ed1);
313       Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
314       db_sql_query(ua->db, query, NULL, (void *)NULL);
315       /*
316        * Now mark Job as having files purged. This is necessary to
317        * avoid having too many Jobs to process in future prunings. If
318        * we don't do this, the number of JobId's in our in memory list
319        * will grow very large.
320        */
321       Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
322       db_sql_query(ua->db, query, NULL, (void *)NULL);
323       Dmsg1(050, "Update Purged sql=%s\n", query);
324    }
325    bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
326       client->hdr.name, client->catalog->hdr.name);
327
328 bail_out:
329    if (del.JobId) {
330       free(del.JobId);
331    }
332    free_pool_memory(query);
333    return 1;
334 }
335
336
337
338 /*
339  * Purge Job records from the database. For any Job which
340  * is older than the retention period, we unconditionally delete
341  * it and all File records for that Job.  This is simple enough that no
342  * temporary tables are needed. We simply make an in memory list of
343  * the JobIds then delete the Job, Files, and JobMedia records in that list.
344  */
345 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
346 {
347    struct s_job_del_ctx del;
348    POOLMEM *query = get_pool_memory(PM_MESSAGE);
349    int i;
350    CLIENT_DBR cr;
351    char ed1[50];
352
353    memset(&cr, 0, sizeof(cr));
354    memset(&del, 0, sizeof(del));
355
356    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
357    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
358       return 0;
359    }
360
361    bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
362    Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
363
364    Dmsg1(050, "select sql=%s\n", query);
365
366    if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
367       bsendmsg(ua, "%s", db_strerror(ua->db));
368       Dmsg0(050, "Count failed\n");
369       goto bail_out;
370    }
371    if (del.tot_ids == 0) {
372       bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
373          client->hdr.name, client->catalog->hdr.name);
374       goto bail_out;
375    }
376
377    if (del.tot_ids < MAX_DEL_LIST_LEN) {
378       del.max_ids = del.tot_ids + 1;
379    } else {
380       del.max_ids = MAX_DEL_LIST_LEN;
381    }
382
383    del.tot_ids = 0;
384
385    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
386    del.PurgedFiles = (char *)malloc(del.max_ids);
387
388    db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
389
390    /*
391     * OK, now we have the list of JobId's to be purged, first check
392     * if the Files have been purged, if not, purge (delete) them.
393     * Then delete the Job entry, and finally and JobMedia records.
394     */
395    for (i=0; i < del.num_ids; i++) {
396       edit_int64(del.JobId[i], ed1);
397       Dmsg1(050, "Delete Files JobId=%s\n", ed1); 
398       if (!del.PurgedFiles[i]) {
399          Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
400          db_sql_query(ua->db, query, NULL, (void *)NULL);
401          Dmsg1(050, "Del sql=%s\n", query);
402       }
403
404       Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
405       db_sql_query(ua->db, query, NULL, (void *)NULL);
406       Dmsg1(050, "Delete Job sql=%s\n", query);
407
408       Mmsg(query, "DELETE FROM MAC WHERE JobId=%s", ed1);
409       db_sql_query(ua->db, query, NULL, (void *)NULL);
410       Dmsg1(050, "Delete MAC sql=%s\n", query);
411
412       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
413       db_sql_query(ua->db, query, NULL, (void *)NULL);
414       Dmsg1(050, "Delete JobMedia sql=%s\n", query);
415    }
416    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
417       client->hdr.name, client->catalog->hdr.name);
418
419 bail_out:
420    if (del.JobId) {
421       free(del.JobId);
422    }
423    if (del.PurgedFiles) {
424       free(del.PurgedFiles);
425    }
426    free_pool_memory(query);
427    return 1;
428 }
429
430 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
431 {
432    POOLMEM *query = get_pool_memory(PM_MESSAGE);
433    char ed1[50];
434
435    edit_int64(jr->JobId, ed1);
436    Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
437    db_sql_query(ua->db, query, NULL, (void *)NULL);
438
439    Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
440    db_sql_query(ua->db, query, NULL, (void *)NULL);
441
442    free_pool_memory(query);
443 }
444
445 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
446 {} /* ***FIXME*** implement */
447
448 /*
449  * Returns: 1 if Volume purged
450  *          0 if Volume not purged
451  */
452 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
453 {
454    POOLMEM *query = get_pool_memory(PM_MESSAGE);
455    struct s_count_ctx cnt;
456    struct s_file_del_ctx del;
457    int i, stat = 0;
458    JOB_DBR jr;
459    char ed1[50];
460
461    stat = strcmp(mr->VolStatus, "Append") == 0 ||
462           strcmp(mr->VolStatus, "Full")   == 0 ||
463           strcmp(mr->VolStatus, "Used")   == 0 ||
464           strcmp(mr->VolStatus, "Error")  == 0;
465    if (!stat) {
466       bsendmsg(ua, "\n");
467       bsendmsg(ua, _("Volume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
468                      "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
469                      mr->VolumeName, mr->VolStatus);
470       goto bail_out;
471    }
472
473    memset(&jr, 0, sizeof(jr));
474    memset(&del, 0, sizeof(del));
475    cnt.count = 0;
476    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
477         edit_int64(mr->MediaId, ed1));
478    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
479       bsendmsg(ua, "%s", db_strerror(ua->db));
480       Dmsg0(050, "Count failed\n");
481       goto bail_out;
482    }
483
484    if (cnt.count == 0) {
485       bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
486          mr->VolumeName);
487       if (!mark_media_purged(ua, mr)) {
488          bsendmsg(ua, "%s", db_strerror(ua->db));
489          goto bail_out;
490       }
491       goto bail_out;
492    }
493
494    if (cnt.count < MAX_DEL_LIST_LEN) {
495       del.max_ids = cnt.count + 1;
496    } else {
497       del.max_ids = MAX_DEL_LIST_LEN;
498    }
499
500    /*
501     * Check if he wants to purge a single jobid
502     */
503    i = find_arg_with_value(ua, "jobid");
504    if (i >= 0) {
505       del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
506       del.num_ids = 1;
507       del.JobId[0] = str_to_int64(ua->argv[i]);
508    } else {
509       /*
510        * Purge ALL JobIds
511        */
512       del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
513
514       Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%s", 
515            edit_int64(mr->MediaId, ed1));
516       if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
517          bsendmsg(ua, "%s", db_strerror(ua->db));
518          Dmsg0(050, "Count failed\n");
519          goto bail_out;
520       }
521    }
522
523    for (i=0; i < del.num_ids; i++) {
524       edit_int64(del.JobId[i], ed1);
525       Dmsg1(050, "Delete JobId=%s\n", ed1);
526       Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
527       db_sql_query(ua->db, query, NULL, (void *)NULL);
528       Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
529       db_sql_query(ua->db, query, NULL, (void *)NULL);
530       Mmsg(query, "DELETE FROM MAC WHERE JobId=%s", ed1);
531       db_sql_query(ua->db, query, NULL, (void *)NULL);
532       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
533       db_sql_query(ua->db, query, NULL, (void *)NULL);
534       Dmsg1(050, "Del sql=%s\n", query);
535       del.num_del++;
536    }
537    if (del.JobId) {
538       free(del.JobId);
539    }
540    bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
541       del.num_del==1?"":"s", mr->VolumeName);
542
543    /* If purged, mark it so */
544    cnt.count = 0;
545    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
546         edit_int64(mr->MediaId, ed1));
547    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
548       bsendmsg(ua, "%s", db_strerror(ua->db));
549       Dmsg0(050, "Count failed\n");
550       goto bail_out;
551    }
552
553    if (cnt.count == 0) {
554       bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
555          mr->VolumeName);
556       if (!(stat = mark_media_purged(ua, mr))) {
557          bsendmsg(ua, "%s", db_strerror(ua->db));
558          goto bail_out;
559       }
560    }
561
562 bail_out:
563    free_pool_memory(query);
564    return stat;
565 }
566
567 /*
568  * IF volume status is Append, Full, Used, or Error, mark it Purged
569  *   Purged volumes can then be recycled (if enabled).
570  */
571 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
572 {
573    JCR *jcr = ua->jcr;
574    if (strcmp(mr->VolStatus, "Append") == 0 ||
575        strcmp(mr->VolStatus, "Full")   == 0 ||
576        strcmp(mr->VolStatus, "Used")   == 0 ||
577        strcmp(mr->VolStatus, "Error")  == 0) {
578       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
579       if (!db_update_media_record(jcr, ua->db, mr)) {
580          return false;
581       }
582       pm_strcpy(jcr->VolumeName, mr->VolumeName);
583       generate_job_event(jcr, "VolumePurged");
584       return true;
585    } else {
586       bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
587    }
588    return strcmp(mr->VolStatus, "Purged") == 0;
589 }