]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
- Add disk-changer to scripts directory + configure/Makefile
[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 JobMedia WHERE JobId=%s", ed1);
409       db_sql_query(ua->db, query, NULL, (void *)NULL);
410       Dmsg1(050, "Delete JobMedia sql=%s\n", query);
411    }
412    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
413       client->hdr.name, client->catalog->hdr.name);
414
415 bail_out:
416    if (del.JobId) {
417       free(del.JobId);
418    }
419    if (del.PurgedFiles) {
420       free(del.PurgedFiles);
421    }
422    free_pool_memory(query);
423    return 1;
424 }
425
426 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
427 {
428    POOLMEM *query = get_pool_memory(PM_MESSAGE);
429    char ed1[50];
430
431    edit_int64(jr->JobId, ed1);
432    Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
433    db_sql_query(ua->db, query, NULL, (void *)NULL);
434
435    Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
436    db_sql_query(ua->db, query, NULL, (void *)NULL);
437
438    free_pool_memory(query);
439 }
440
441 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
442 {} /* ***FIXME*** implement */
443
444 /*
445  * Returns: 1 if Volume purged
446  *          0 if Volume not purged
447  */
448 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
449 {
450    POOLMEM *query = get_pool_memory(PM_MESSAGE);
451    struct s_count_ctx cnt;
452    struct s_file_del_ctx del;
453    int i, stat = 0;
454    JOB_DBR jr;
455    char ed1[50];
456
457    stat = strcmp(mr->VolStatus, "Append") == 0 ||
458           strcmp(mr->VolStatus, "Full")   == 0 ||
459           strcmp(mr->VolStatus, "Used")   == 0 ||
460           strcmp(mr->VolStatus, "Error")  == 0;
461    if (!stat) {
462       bsendmsg(ua, "\n");
463       bsendmsg(ua, _("Volume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
464                      "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
465                      mr->VolumeName, mr->VolStatus);
466       goto bail_out;
467    }
468
469    memset(&jr, 0, sizeof(jr));
470    memset(&del, 0, sizeof(del));
471    cnt.count = 0;
472    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
473         edit_int64(mr->MediaId, ed1));
474    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
475       bsendmsg(ua, "%s", db_strerror(ua->db));
476       Dmsg0(050, "Count failed\n");
477       goto bail_out;
478    }
479
480    if (cnt.count == 0) {
481       bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
482          mr->VolumeName);
483       if (!mark_media_purged(ua, mr)) {
484          bsendmsg(ua, "%s", db_strerror(ua->db));
485          goto bail_out;
486       }
487       goto bail_out;
488    }
489
490    if (cnt.count < MAX_DEL_LIST_LEN) {
491       del.max_ids = cnt.count + 1;
492    } else {
493       del.max_ids = MAX_DEL_LIST_LEN;
494    }
495
496    /*
497     * Check if he wants to purge a single jobid
498     */
499    i = find_arg_with_value(ua, "jobid");
500    if (i >= 0) {
501       del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
502       del.num_ids = 1;
503       del.JobId[0] = str_to_int64(ua->argv[i]);
504    } else {
505       /*
506        * Purge ALL JobIds
507        */
508       del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
509
510       Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%s", 
511            edit_int64(mr->MediaId, ed1));
512       if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
513          bsendmsg(ua, "%s", db_strerror(ua->db));
514          Dmsg0(050, "Count failed\n");
515          goto bail_out;
516       }
517    }
518
519    for (i=0; i < del.num_ids; i++) {
520       edit_int64(del.JobId[i], ed1);
521       Dmsg1(050, "Delete JobId=%s\n", ed1);
522       Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
523       db_sql_query(ua->db, query, NULL, (void *)NULL);
524       Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
525       db_sql_query(ua->db, query, NULL, (void *)NULL);
526       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
527       db_sql_query(ua->db, query, NULL, (void *)NULL);
528       Dmsg1(050, "Del sql=%s\n", query);
529       del.num_del++;
530    }
531    if (del.JobId) {
532       free(del.JobId);
533    }
534    bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
535       del.num_del==1?"":"s", mr->VolumeName);
536
537    /* If purged, mark it so */
538    cnt.count = 0;
539    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
540         edit_int64(mr->MediaId, ed1));
541    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
542       bsendmsg(ua, "%s", db_strerror(ua->db));
543       Dmsg0(050, "Count failed\n");
544       goto bail_out;
545    }
546
547    if (cnt.count == 0) {
548       bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
549          mr->VolumeName);
550       if (!(stat = mark_media_purged(ua, mr))) {
551          bsendmsg(ua, "%s", db_strerror(ua->db));
552          goto bail_out;
553       }
554    }
555
556 bail_out:
557    free_pool_memory(query);
558    return stat;
559 }
560
561 /*
562  * IF volume status is Append, Full, Used, or Error, mark it Purged
563  *   Purged volumes can then be recycled (if enabled).
564  */
565 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
566 {
567    JCR *jcr = ua->jcr;
568    if (strcmp(mr->VolStatus, "Append") == 0 ||
569        strcmp(mr->VolStatus, "Full")   == 0 ||
570        strcmp(mr->VolStatus, "Used")   == 0 ||
571        strcmp(mr->VolStatus, "Error")  == 0) {
572       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
573       if (!db_update_media_record(jcr, ua->db, mr)) {
574          return false;
575       }
576       pm_strcpy(jcr->VolumeName, mr->VolumeName);
577       generate_job_event(jcr, "VolumePurged");
578       return true;
579    } else {
580       bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
581    }
582    return strcmp(mr->VolStatus, "Purged") == 0;
583 }