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