]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
kes Add new version of upgrade-win32-client.txt to examples directory.
[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[1]);
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       NT_("files"),
160       NT_("jobs"),
161       NT_("volume"),
162       NULL};
163
164    static const char *files_keywords[] = {
165       NT_("Job"),
166       NT_("JobId"),
167       NT_("Client"),
168       NT_("Volume"),
169       NULL};
170
171    static const char *jobs_keywords[] = {
172       NT_("Client"),
173       NT_("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.JobId);
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, NT_("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       purge_files_from_job(ua, del.JobId[i]);
312    }
313    bsendmsg(ua, _("%d Files for client \"%s\" purged from %s catalog.\n"), del.num_ids,
314       client->hdr.name, client->catalog->hdr.name);
315
316 bail_out:
317    if (del.JobId) {
318       free(del.JobId);
319    }
320    free_pool_memory(query);
321    return 1;
322 }
323
324
325
326 /*
327  * Purge Job records from the database. For any Job which
328  * is older than the retention period, we unconditionally delete
329  * it and all File records for that Job.  This is simple enough that no
330  * temporary tables are needed. We simply make an in memory list of
331  * the JobIds then delete the Job, Files, and JobMedia records in that list.
332  */
333 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
334 {
335    struct s_job_del_ctx del;
336    POOLMEM *query = get_pool_memory(PM_MESSAGE);
337    int i;
338    CLIENT_DBR cr;
339    char ed1[50];
340
341    memset(&cr, 0, sizeof(cr));
342    memset(&del, 0, sizeof(del));
343
344    bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name));
345    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
346       return 0;
347    }
348
349    bsendmsg(ua, _("Begin purging jobs from Client \"%s\"\n"), cr.Name);
350    Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
351
352    Dmsg1(050, "select sql=%s\n", query);
353
354    if (!db_sql_query(ua->db, query, job_count_handler, (void *)&del)) {
355       bsendmsg(ua, "%s", db_strerror(ua->db));
356       Dmsg0(050, "Count failed\n");
357       goto bail_out;
358    }
359    if (del.tot_ids == 0) {
360       bsendmsg(ua, _("No Jobs found for client %s to purge from %s catalog.\n"),
361          client->hdr.name, client->catalog->hdr.name);
362       goto bail_out;
363    }
364
365    if (del.tot_ids < MAX_DEL_LIST_LEN) {
366       del.max_ids = del.tot_ids + 1;
367    } else {
368       del.max_ids = MAX_DEL_LIST_LEN;
369    }
370
371    del.tot_ids = 0;
372
373    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
374    del.PurgedFiles = (char *)malloc(del.max_ids);
375
376    db_sql_query(ua->db, query, job_delete_handler, (void *)&del);
377
378    /*
379     * OK, now we have the list of JobId's to be purged, first check
380     * if the Files have been purged, if not, purge (delete) them.
381     * Then delete the Job entry, and finally and JobMedia records.
382     */
383    for (i=0; i < del.num_ids; i++) {
384       Dmsg1(050, "Delete Files JobId=%s\n", ed1); 
385       if (!del.PurgedFiles[i]) {
386          purge_files_from_job(ua, del.JobId[i]);
387       }
388       purge_job_from_catalog(ua, del.JobId[i]);
389    }
390    bsendmsg(ua, _("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
391       client->hdr.name, client->catalog->hdr.name);
392
393 bail_out:
394    if (del.JobId) {
395       free(del.JobId);
396    }
397    if (del.PurgedFiles) {
398       free(del.PurgedFiles);
399    }
400    free_pool_memory(query);
401    return 1;
402 }
403
404 void purge_job_from_catalog(UAContext *ua, JobId_t JobId)
405 {
406    POOL_MEM query(PM_MESSAGE);
407    char ed1[50];
408
409    edit_int64(JobId, ed1);
410    Mmsg(query, "DELETE FROM Job WHERE JobId=%s", ed1);
411    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
412    Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
413
414    Mmsg(query, "DELETE FROM JobMedia WHERE JobId=%s", ed1);
415    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
416    Dmsg1(050, "Delete JobMedia sql=%s\n", query.c_str());
417
418    Mmsg(query, "DELETE FROM Log WHERE JobId=%s", ed1);
419    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
420    Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
421
422 }
423
424 void purge_files_from_job(UAContext *ua, JobId_t JobId)
425 {
426    POOL_MEM query(PM_MESSAGE);
427    char ed1[50];
428
429    edit_int64(JobId, ed1);
430    Mmsg(query, del_File, ed1);
431    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
432
433    /*
434     * Now mark Job as having files purged. This is necessary to
435     * avoid having too many Jobs to process in future prunings. If
436     * we don't do this, the number of JobId's in our in memory list
437     * could grow very large.
438     */
439    Mmsg(query, upd_Purged, ed1);
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    POOLMEM *query = 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       purge_files_from_job(ua, del.JobId[i]);
522       purge_job_from_catalog(ua, del.JobId[i]);
523       del.num_del++;
524    }
525    if (del.JobId) {
526       free(del.JobId);
527    }
528    bsendmsg(ua, _("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
529       del.num_del==1?"":"s", mr->VolumeName);
530
531    /* If purged, mark it so */
532    cnt.count = 0;
533    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
534         edit_int64(mr->MediaId, ed1));
535    if (!db_sql_query(ua->db, query, count_handler, (void *)&cnt)) {
536       bsendmsg(ua, "%s", db_strerror(ua->db));
537       Dmsg0(050, "Count failed\n");
538       goto bail_out;
539    }
540
541    if (cnt.count == 0) {
542       bsendmsg(ua, _("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
543          mr->VolumeName);
544       if (!(stat = mark_media_purged(ua, mr))) {
545          bsendmsg(ua, "%s", db_strerror(ua->db));
546          goto bail_out;
547       }
548    }
549
550 bail_out:
551    free_pool_memory(query);
552    return stat;
553 }
554
555 /*
556  * IF volume status is Append, Full, Used, or Error, mark it Purged
557  *   Purged volumes can then be recycled (if enabled).
558  */
559 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
560 {
561    JCR *jcr = ua->jcr;
562    if (strcmp(mr->VolStatus, "Append") == 0 ||
563        strcmp(mr->VolStatus, "Full")   == 0 ||
564        strcmp(mr->VolStatus, "Used")   == 0 ||
565        strcmp(mr->VolStatus, "Error")  == 0) {
566       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
567       if (!db_update_media_record(jcr, ua->db, mr)) {
568          return false;
569       }
570       pm_strcpy(jcr->VolumeName, mr->VolumeName);
571       generate_job_event(jcr, "VolumePurged");
572       if (ua->jcr) {
573          Jmsg1(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"),
574             mr->VolumeName); 
575       }
576       return true;
577    } else {
578       bsendmsg(ua, _("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
579    }
580    return strcmp(mr->VolStatus, "Purged") == 0;
581 }