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