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