]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
added VSS toggling by enable_vss
[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-2005 Kern Sibbald
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 const char *select_jobsfiles_from_client =
50    "SELECT JobId FROM Job "
51    "WHERE ClientId=%d "
52    "AND PurgedFiles=0";
53
54 static const 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 = str_to_int64(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, const char *cmd)
165 {
166    int i;
167    CLIENT *client;
168    MEDIA_DBR mr;
169    JOB_DBR  jr;
170    static const char *keywords[] = {
171       N_("files"),
172       N_("jobs"),
173       N_("volume"),
174       NULL};
175
176    static const char *files_keywords[] = {
177       N_("Job"),
178       N_("JobId"),
179       N_("Client"),
180       N_("Volume"),
181       NULL};
182
183    static const char *jobs_keywords[] = {
184       N_("Client"),
185       N_("Volume"),
186       NULL};
187
188    bsendmsg(ua, _(
189       "\nThis command is can be DANGEROUS!!!\n\n"
190       "It purges (deletes) all Files from a Job,\n"
191       "JobId, Client or Volume; or it purges (deletes)\n"
192       "all Jobs from a Client or Volume without regard\n"
193       "for retention periods. Normally you should use the\n"
194       "PRUNE command, which respects retention periods.\n"));
195
196    if (!open_db(ua)) {
197       return 1;
198    }
199    switch (find_arg_keyword(ua, keywords)) {
200    /* Files */
201    case 0:
202       switch(find_arg_keyword(ua, files_keywords)) {
203       case 0:                         /* Job */
204       case 1:                         /* JobId */
205          if (get_job_dbr(ua, &jr)) {
206             purge_files_from_job(ua, &jr);
207          }
208          return 1;
209       case 2:                         /* client */
210          client = get_client_resource(ua);
211          if (client) {
212             purge_files_from_client(ua, client);
213          }
214          return 1;
215       case 3:                         /* Volume */
216          if (select_media_dbr(ua, &mr)) {
217             purge_files_from_volume(ua, &mr);
218          }
219          return 1;
220       }
221    /* Jobs */
222    case 1:
223       switch(find_arg_keyword(ua, jobs_keywords)) {
224       case 0:                         /* client */
225          client = get_client_resource(ua);
226          if (client) {
227             purge_jobs_from_client(ua, client);
228          }
229          return 1;
230       case 1:                         /* Volume */
231          if (select_media_dbr(ua, &mr)) {
232             purge_jobs_from_volume(ua, &mr);
233          }
234          return 1;
235       }
236    /* Volume */
237    case 2:
238       while ((i=find_arg(ua, _("volume"))) >= 0) {
239          if (select_media_dbr(ua, &mr)) {
240             purge_jobs_from_volume(ua, &mr);
241          }
242          *ua->argk[i] = 0;            /* zap keyword already seen */
243          bsendmsg(ua, "\n");
244       }
245       return 1;
246    default:
247       break;
248    }
249    switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
250    case 0:                            /* files */
251       client = get_client_resource(ua);
252       if (client) {
253          purge_files_from_client(ua, client);
254       }
255       break;
256    case 1:                            /* jobs */
257       client = get_client_resource(ua);
258       if (client) {
259          purge_jobs_from_client(ua, client);
260       }
261       break;
262    case 2:                            /* Volume */
263       if (select_media_dbr(ua, &mr)) {
264          purge_jobs_from_volume(ua, &mr);
265       }
266       break;
267    }
268    return 1;
269 }
270
271 /*
272  * Purge File records from the database. For any Job which
273  * is older than the retention period, we unconditionally delete
274  * all File records for that Job.  This is simple enough that no
275  * temporary tables are needed. We simply make an in memory list of
276  * the JobIds meeting the prune conditions, then delete all File records
277  * pointing to each of those JobIds.
278  */
279 static int purge_files_from_client(UAContext *ua, CLIENT *client)
280 {
281    struct s_file_del_ctx del;
282    char *query = (char *)get_pool_memory(PM_MESSAGE);
283    int i;
284    CLIENT_DBR cr;
285
286    memset(&cr, 0, sizeof(cr));
287    memset(&del, 0, sizeof(del));
288
289    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
290    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
291       return 0;
292    }
293    bsendmsg(ua, _("Begin purging files for Client \"%s\"\n"), cr.Name);
294    Mmsg(query, select_jobsfiles_from_client, cr.ClientId);
295
296    Dmsg1(050, "select sql=%s\n", query);
297
298    if (!db_sql_query(ua->db, query, file_count_handler, (void *)&del)) {
299       bsendmsg(ua, "%s", db_strerror(ua->db));
300       Dmsg0(050, "Count failed\n");
301       goto bail_out;
302    }
303
304    if (del.tot_ids == 0) {
305       bsendmsg(ua, _("No Files found for client %s to purge from %s catalog.\n"),
306          client->hdr.name, client->catalog->hdr.name);
307       goto bail_out;
308    }
309
310    if (del.tot_ids < MAX_DEL_LIST_LEN) {
311       del.max_ids = del.tot_ids + 1;
312    } else {
313       del.max_ids = MAX_DEL_LIST_LEN;
314    }
315    del.tot_ids = 0;
316
317    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
318
319    db_sql_query(ua->db, query, file_delete_handler, (void *)&del);
320
321    for (i=0; i < del.num_ids; i++) {
322       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
323       Mmsg(query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]);
324       db_sql_query(ua->db, query, NULL, (void *)NULL);
325       /*
326        * Now mark Job as having files purged. This is necessary to
327        * avoid having too many Jobs to process in future prunings. If
328        * we don't do this, the number of JobId's in our in memory list
329        * will grow very large.
330        */
331       Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%d", del.JobId[i]);
332       db_sql_query(ua->db, query, NULL, (void *)NULL);
333       Dmsg1(050, "Del sql=%s\n", query);
334    }
335    bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
336       client->hdr.name, client->catalog->hdr.name);
337
338 bail_out:
339    if (del.JobId) {
340       free(del.JobId);
341    }
342    free_pool_memory(query);
343    return 1;
344 }
345
346
347
348 /*
349  * Purge Job records from the database. For any Job which
350  * is older than the retention period, we unconditionally delete
351  * it and all File records for that Job.  This is simple enough that no
352  * temporary tables are needed. We simply make an in memory list of
353  * the JobIds meeting the prune conditions, then delete the Job,
354  * Files, and JobMedia records in that list.
355  */
356 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
357 {
358    struct s_job_del_ctx del;
359    char *query = (char *)get_pool_memory(PM_MESSAGE);
360    int i;
361    CLIENT_DBR cr;
362
363    memset(&cr, 0, sizeof(cr));
364    memset(&del, 0, sizeof(del));
365
366    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
367    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
368       return 0;
369    }
370
371    bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
372    Mmsg(query, select_jobs_from_client, cr.ClientId);
373
374    Dmsg1(050, "select sql=%s\n", query);
375
376    if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
377       bsendmsg(ua, "%s", db_strerror(ua->db));
378       Dmsg0(050, "Count failed\n");
379       goto bail_out;
380    }
381    if (del.tot_ids == 0) {
382       bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
383          client->hdr.name, client->catalog->hdr.name);
384       goto bail_out;
385    }
386
387    if (del.tot_ids < MAX_DEL_LIST_LEN) {
388       del.max_ids = del.tot_ids + 1;
389    } else {
390       del.max_ids = MAX_DEL_LIST_LEN;
391    }
392
393    del.tot_ids = 0;
394
395    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
396    del.PurgedFiles = (char *)malloc(del.max_ids);
397
398    db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
399
400    /*
401     * OK, now we have the list of JobId's to be purged, first check
402     * if the Files have been purged, if not, purge (delete) them.
403     * Then delete the Job entry, and finally and JobMedia records.
404     */
405    for (i=0; i < del.num_ids; i++) {
406       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
407       if (!del.PurgedFiles[i]) {
408          Mmsg(query, "DELETE FROM File 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
413       Mmsg(query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]);
414       db_sql_query(ua->db, query, NULL, (void *)NULL);
415       Dmsg1(050, "Del sql=%s\n", query);
416
417       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]);
418       db_sql_query(ua->db, query, NULL, (void *)NULL);
419       Dmsg1(050, "Del sql=%s\n", query);
420    }
421    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
422       client->hdr.name, client->catalog->hdr.name);
423
424 bail_out:
425    if (del.JobId) {
426       free(del.JobId);
427    }
428    if (del.PurgedFiles) {
429       free(del.PurgedFiles);
430    }
431    free_pool_memory(query);
432    return 1;
433 }
434
435 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
436 {
437    char *query = (char *)get_pool_memory(PM_MESSAGE);
438    char ed1[50];
439
440    Mmsg(query, "DELETE FROM File WHERE JobId=%s", edit_int64(jr->JobId,ed1));
441    db_sql_query(ua->db, query, NULL, (void *)NULL);
442
443    Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", edit_int64(jr->JobId,ed1));
444    db_sql_query(ua->db, query, NULL, (void *)NULL);
445
446    free_pool_memory(query);
447 }
448
449 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
450 {} /* ***FIXME*** implement */
451
452 /*
453  * Returns: 1 if Volume purged
454  *          0 if Volume not purged
455  */
456 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
457 {
458    char *query = (char *)get_pool_memory(PM_MESSAGE);
459    struct s_count_ctx cnt;
460    struct s_file_del_ctx del;
461    int i, stat = 0;
462    JOB_DBR jr;
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=%d", mr->MediaId);
480    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
481       bsendmsg(ua, "%s", db_strerror(ua->db));
482       Dmsg0(050, "Count failed\n");
483       goto bail_out;
484    }
485
486    if (cnt.count == 0) {
487       bsendmsg(ua, "There are no Jobs associated with Volume \"%s\". Marking it purged.\n",
488          mr->VolumeName);
489       if (!mark_media_purged(ua, mr)) {
490          bsendmsg(ua, "%s", db_strerror(ua->db));
491          goto bail_out;
492       }
493       goto bail_out;
494    }
495
496    if (cnt.count < MAX_DEL_LIST_LEN) {
497       del.max_ids = cnt.count + 1;
498    } else {
499       del.max_ids = MAX_DEL_LIST_LEN;
500    }
501
502    /*
503     * Check if he wants to purge a single jobid
504     */
505    i = find_arg_with_value(ua, "jobid");
506    if (i >= 0) {
507       del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
508       del.num_ids = 1;
509       del.JobId[0] = str_to_int64(ua->argv[i]);
510    } else {
511       /*
512        * Purge ALL JobIds
513        */
514       del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
515
516       Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%d", mr->MediaId);
517       if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
518          bsendmsg(ua, "%s", db_strerror(ua->db));
519          Dmsg0(050, "Count failed\n");
520          goto bail_out;
521       }
522    }
523
524    for (i=0; i < del.num_ids; i++) {
525       Dmsg1(050, "Delete JobId=%d\n", del.JobId[i]);
526       Mmsg(query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]);
527       db_sql_query(ua->db, query, NULL, (void *)NULL);
528       Mmsg(query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]);
529       db_sql_query(ua->db, query, NULL, (void *)NULL);
530       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]);
531       db_sql_query(ua->db, query, NULL, (void *)NULL);
532       Dmsg1(050, "Del sql=%s\n", query);
533       del.num_del++;
534    }
535    if (del.JobId) {
536       free(del.JobId);
537    }
538    bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
539       del.num_del==1?"":"s", mr->VolumeName);
540
541    /* If purged, mark it so */
542    cnt.count = 0;
543    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%d", mr->MediaId);
544    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
545       bsendmsg(ua, "%s", db_strerror(ua->db));
546       Dmsg0(050, "Count failed\n");
547       goto bail_out;
548    }
549
550    if (cnt.count == 0) {
551       bsendmsg(ua, "There are no more Jobs associated with Volume \"%s\". Marking it purged.\n",
552          mr->VolumeName);
553       if (!(stat = mark_media_purged(ua, mr))) {
554          bsendmsg(ua, "%s", db_strerror(ua->db));
555          goto bail_out;
556       }
557    }
558
559 bail_out:
560    free_pool_memory(query);
561    return stat;
562 }
563
564 /*
565  * IF volume status is Append, Full, Used, or Error, mark it Purged
566  *   Purged volumes can then be recycled (if enabled).
567  */
568 int mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
569 {
570    if (strcmp(mr->VolStatus, "Append") == 0 ||
571        strcmp(mr->VolStatus, "Full")   == 0 ||
572        strcmp(mr->VolStatus, "Used")   == 0 ||
573        strcmp(mr->VolStatus, "Error")  == 0) {
574       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
575       if (!db_update_media_record(ua->jcr, ua->db, mr)) {
576          return 0;
577       }
578       return 1;
579    } else {
580       bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
581    }
582    return strcpy(mr->VolStatus, "Purged") == 0;
583 }