]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
This commit was manufactured by cvs2svn to create tag
[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 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    char *query = (char *)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 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, "Del 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 meeting the prune conditions, then delete the Job,
344  * Files, and JobMedia records in that list.
345  */
346 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
347 {
348    struct s_job_del_ctx del;
349    char *query = (char *)get_pool_memory(PM_MESSAGE);
350    int i;
351    CLIENT_DBR cr;
352    char ed1[50];
353
354    memset(&cr, 0, sizeof(cr));
355    memset(&del, 0, sizeof(del));
356
357    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
358    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
359       return 0;
360    }
361
362    bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
363    Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
364
365    Dmsg1(050, "select sql=%s\n", query);
366
367    if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
368       bsendmsg(ua, "%s", db_strerror(ua->db));
369       Dmsg0(050, "Count failed\n");
370       goto bail_out;
371    }
372    if (del.tot_ids == 0) {
373       bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
374          client->hdr.name, client->catalog->hdr.name);
375       goto bail_out;
376    }
377
378    if (del.tot_ids < MAX_DEL_LIST_LEN) {
379       del.max_ids = del.tot_ids + 1;
380    } else {
381       del.max_ids = MAX_DEL_LIST_LEN;
382    }
383
384    del.tot_ids = 0;
385
386    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
387    del.PurgedFiles = (char *)malloc(del.max_ids);
388
389    db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
390
391    /*
392     * OK, now we have the list of JobId's to be purged, first check
393     * if the Files have been purged, if not, purge (delete) them.
394     * Then delete the Job entry, and finally and JobMedia records.
395     */
396    for (i=0; i < del.num_ids; i++) {
397       edit_int64(del.JobId[i], ed1);
398       Dmsg1(050, "Delete JobId=%s\n", ed1); 
399       if (!del.PurgedFiles[i]) {
400          Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
401          db_sql_query(ua->db, query, NULL, (void *)NULL);
402          Dmsg1(050, "Del sql=%s\n", query);
403       }
404
405       Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
406       db_sql_query(ua->db, query, NULL, (void *)NULL);
407       Dmsg1(050, "Del sql=%s\n", query);
408
409       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
410       db_sql_query(ua->db, query, NULL, (void *)NULL);
411       Dmsg1(050, "Del sql=%s\n", query);
412    }
413    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
414       client->hdr.name, client->catalog->hdr.name);
415
416 bail_out:
417    if (del.JobId) {
418       free(del.JobId);
419    }
420    if (del.PurgedFiles) {
421       free(del.PurgedFiles);
422    }
423    free_pool_memory(query);
424    return 1;
425 }
426
427 void purge_files_from_job(UAContext *ua, JOB_DBR *jr)
428 {
429    char *query = (char *)get_pool_memory(PM_MESSAGE);
430    char ed1[50];
431
432    edit_int64(jr->JobId,ed1);
433    Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
434    db_sql_query(ua->db, query, NULL, (void *)NULL);
435
436    Mmsg(query, "UPDATE Job Set PurgedFiles=1 WHERE JobId=%s", ed1);
437    db_sql_query(ua->db, query, NULL, (void *)NULL);
438
439    free_pool_memory(query);
440 }
441
442 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
443 {} /* ***FIXME*** implement */
444
445 /*
446  * Returns: 1 if Volume purged
447  *          0 if Volume not purged
448  */
449 int purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr)
450 {
451    char *query = (char *)get_pool_memory(PM_MESSAGE);
452    struct s_count_ctx cnt;
453    struct s_file_del_ctx del;
454    int i, stat = 0;
455    JOB_DBR jr;
456    char ed1[50];
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=%s", 
474         edit_int64(mr->MediaId, ed1));
475    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
476       bsendmsg(ua, "%s", db_strerror(ua->db));
477       Dmsg0(050, "Count failed\n");
478       goto bail_out;
479    }
480
481    if (cnt.count == 0) {
482       bsendmsg(ua, _("There are no Jobs associated with Volume \"%s\". Marking it purged.\n"),
483          mr->VolumeName);
484       if (!mark_media_purged(ua, mr)) {
485          bsendmsg(ua, "%s", db_strerror(ua->db));
486          goto bail_out;
487       }
488       goto bail_out;
489    }
490
491    if (cnt.count < MAX_DEL_LIST_LEN) {
492       del.max_ids = cnt.count + 1;
493    } else {
494       del.max_ids = MAX_DEL_LIST_LEN;
495    }
496
497    /*
498     * Check if he wants to purge a single jobid
499     */
500    i = find_arg_with_value(ua, "jobid");
501    if (i >= 0) {
502       del.JobId = (JobId_t *)malloc(sizeof(JobId_t));
503       del.num_ids = 1;
504       del.JobId[0] = str_to_int64(ua->argv[i]);
505    } else {
506       /*
507        * Purge ALL JobIds
508        */
509       del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
510
511       Mmsg(query, "SELECT JobId FROM JobMedia WHERE MediaId=%s", 
512            edit_int64(mr->MediaId, ed1));
513       if (!db_sql_query(ua->db, query, file_delete_handler, (void *)&del)) {
514          bsendmsg(ua, "%s", db_strerror(ua->db));
515          Dmsg0(050, "Count failed\n");
516          goto bail_out;
517       }
518    }
519
520    for (i=0; i < del.num_ids; i++) {
521       edit_int64(del.JobId[i], ed1);
522       Dmsg1(050, "Delete JobId=%s\n", ed1);
523       Mmsg(query, "DELETE FROM File WHERE JobId=%s", ed1);
524       db_sql_query(ua->db, query, NULL, (void *)NULL);
525       Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
526       db_sql_query(ua->db, query, NULL, (void *)NULL);
527       Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
528       db_sql_query(ua->db, query, NULL, (void *)NULL);
529       Dmsg1(050, "Del sql=%s\n", query);
530       del.num_del++;
531    }
532    if (del.JobId) {
533       free(del.JobId);
534    }
535    bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
536       del.num_del==1?"":"s", mr->VolumeName);
537
538    /* If purged, mark it so */
539    cnt.count = 0;
540    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
541         edit_int64(mr->MediaId, ed1));
542    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
543       bsendmsg(ua, "%s", db_strerror(ua->db));
544       Dmsg0(050, "Count failed\n");
545       goto bail_out;
546    }
547
548    if (cnt.count == 0) {
549       bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
550          mr->VolumeName);
551       if (!(stat = mark_media_purged(ua, mr))) {
552          bsendmsg(ua, "%s", db_strerror(ua->db));
553          goto bail_out;
554       }
555    }
556
557 bail_out:
558    free_pool_memory(query);
559    return stat;
560 }
561
562 /*
563  * IF volume status is Append, Full, Used, or Error, mark it Purged
564  *   Purged volumes can then be recycled (if enabled).
565  */
566 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
567 {
568    JCR *jcr = ua->jcr;
569    if (strcmp(mr->VolStatus, "Append") == 0 ||
570        strcmp(mr->VolStatus, "Full")   == 0 ||
571        strcmp(mr->VolStatus, "Used")   == 0 ||
572        strcmp(mr->VolStatus, "Error")  == 0) {
573       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
574       if (!db_update_media_record(jcr, ua->db, mr)) {
575          return false;
576       }
577       pm_strcpy(jcr->VolumeName, mr->VolumeName);
578       generate_job_event(jcr, "VolumePurged");
579       return true;
580    } else {
581       bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
582    }
583    return strcpy(mr->VolStatus, "Purged") == 0;
584 }