]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
Rework sql queries for update copies
[bacula/bacula] / bacula / src / dird / ua_purge.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2010 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *   Bacula Director -- User Agent Database Purge Command
31  *
32  *      Purges Files from specific JobIds
33  * or
34  *      Purges Jobs from Volumes
35  *
36  *     Kern Sibbald, February MMII
37  *
38  */
39
40 #include "bacula.h"
41 #include "dird.h"
42
43 /* Forward referenced functions */
44 static int purge_files_from_client(UAContext *ua, CLIENT *client);
45 static int purge_jobs_from_client(UAContext *ua, CLIENT *client);
46 static int action_on_purge_cmd(UAContext *ua, const char *cmd);
47
48 static const char *select_jobsfiles_from_client =
49    "SELECT JobId FROM Job "
50    "WHERE ClientId=%s "
51    "AND PurgedFiles=0";
52
53 static const char *select_jobs_from_client =
54    "SELECT JobId, PurgedFiles FROM Job "
55    "WHERE ClientId=%s";
56
57 /*
58  *   Purge records from database
59  *
60  *     Purge Files (from) [Job|JobId|Client|Volume]
61  *     Purge Jobs  (from) [Client|Volume]
62  *
63  *  N.B. Not all above is implemented yet.
64  */
65 int purgecmd(UAContext *ua, const char *cmd)
66 {
67    int i;
68    CLIENT *client;
69    MEDIA_DBR mr;
70    JOB_DBR  jr;
71    static const char *keywords[] = {
72       NT_("files"),
73       NT_("jobs"),
74       NT_("volume"),
75       NULL};
76
77    static const char *files_keywords[] = {
78       NT_("Job"),
79       NT_("JobId"),
80       NT_("Client"),
81       NT_("Volume"),
82       NULL};
83
84    static const char *jobs_keywords[] = {
85       NT_("Client"),
86       NT_("Volume"),
87       NULL};
88
89    ua->warning_msg(_(
90       "\nThis command can be DANGEROUS!!!\n\n"
91       "It purges (deletes) all Files from a Job,\n"
92       "JobId, Client or Volume; or it purges (deletes)\n"
93       "all Jobs from a Client or Volume without regard\n"
94       "to retention periods. Normally you should use the\n"
95       "PRUNE command, which respects retention periods.\n"));
96
97    if (!open_db(ua)) {
98       return 1;
99    }
100    switch (find_arg_keyword(ua, keywords)) {
101    /* Files */
102    case 0:
103       switch(find_arg_keyword(ua, files_keywords)) {
104       case 0:                         /* Job */
105       case 1:                         /* JobId */
106          if (get_job_dbr(ua, &jr)) {
107             char jobid[50];
108             edit_int64(jr.JobId, jobid);
109             purge_files_from_jobs(ua, jobid);
110          }
111          return 1;
112       case 2:                         /* client */
113          client = get_client_resource(ua);
114          if (client) {
115             purge_files_from_client(ua, client);
116          }
117          return 1;
118       case 3:                         /* Volume */
119          if (select_media_dbr(ua, &mr)) {
120             purge_files_from_volume(ua, &mr);
121          }
122          return 1;
123       }
124    /* Jobs */
125    case 1:
126       switch(find_arg_keyword(ua, jobs_keywords)) {
127       case 0:                         /* client */
128          client = get_client_resource(ua);
129          if (client) {
130             purge_jobs_from_client(ua, client);
131          }
132          return 1;
133       case 1:                         /* Volume */
134          if (select_media_dbr(ua, &mr)) {
135             purge_jobs_from_volume(ua, &mr, /*force*/true);
136          }
137          return 1;
138       }
139    /* Volume */
140    case 2:
141       /* Perform ActionOnPurge (action=truncate) */
142       if (find_arg(ua, "action") >= 0) {
143          return action_on_purge_cmd(ua, ua->cmd);
144       }
145
146       while ((i=find_arg(ua, NT_("volume"))) >= 0) {
147          if (select_media_dbr(ua, &mr)) {
148             purge_jobs_from_volume(ua, &mr, /*force*/true);
149          }
150          *ua->argk[i] = 0;            /* zap keyword already seen */
151          ua->send_msg("\n");
152       }
153       return 1;
154    default:
155       break;
156    }
157    switch (do_keyword_prompt(ua, _("Choose item to purge"), keywords)) {
158    case 0:                            /* files */
159       client = get_client_resource(ua);
160       if (client) {
161          purge_files_from_client(ua, client);
162       }
163       break;
164    case 1:                            /* jobs */
165       client = get_client_resource(ua);
166       if (client) {
167          purge_jobs_from_client(ua, client);
168       }
169       break;
170    case 2:                            /* Volume */
171       if (select_media_dbr(ua, &mr)) {
172          purge_jobs_from_volume(ua, &mr, /*force*/true);
173       }
174       break;
175    }
176    return 1;
177 }
178
179 /*
180  * Purge File records from the database. For any Job which
181  * is older than the retention period, we unconditionally delete
182  * all File records for that Job.  This is simple enough that no
183  * temporary tables are needed. We simply make an in memory list of
184  * the JobIds meeting the prune conditions, then delete all File records
185  * pointing to each of those JobIds.
186  */
187 static int purge_files_from_client(UAContext *ua, CLIENT *client)
188 {
189    struct del_ctx del;
190    POOL_MEM query(PM_MESSAGE);
191    CLIENT_DBR cr;
192    char ed1[50];
193
194    memset(&cr, 0, sizeof(cr));
195    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
196    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
197       return 0;
198    }
199
200    memset(&del, 0, sizeof(del));
201    del.max_ids = 1000;
202    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
203
204    ua->info_msg(_("Begin purging files for Client \"%s\"\n"), cr.Name);
205
206    Mmsg(query, select_jobsfiles_from_client, edit_int64(cr.ClientId, ed1));
207    Dmsg1(050, "select sql=%s\n", query.c_str());
208    db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del);
209
210    purge_files_from_job_list(ua, del);
211
212    if (del.num_ids == 0) {
213       ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"),
214          client->name(), client->catalog->name());
215    } else {
216       ua->info_msg(_("Files for %d Jobs for client \"%s\" purged from %s catalog.\n"), del.num_ids,
217          client->name(), client->catalog->name());
218    }
219
220    if (del.JobId) {
221       free(del.JobId);
222    }
223    return 1;
224 }
225
226
227
228 /*
229  * Purge Job records from the database. For any Job which
230  * is older than the retention period, we unconditionally delete
231  * it and all File records for that Job.  This is simple enough that no
232  * temporary tables are needed. We simply make an in memory list of
233  * the JobIds then delete the Job, Files, and JobMedia records in that list.
234  */
235 static int purge_jobs_from_client(UAContext *ua, CLIENT *client)
236 {
237    struct del_ctx del;
238    POOL_MEM query(PM_MESSAGE);
239    CLIENT_DBR cr;
240    char ed1[50];
241
242    memset(&cr, 0, sizeof(cr));
243
244    bstrncpy(cr.Name, client->name(), sizeof(cr.Name));
245    if (!db_create_client_record(ua->jcr, ua->db, &cr)) {
246       return 0;
247    }
248
249    memset(&del, 0, sizeof(del));
250    del.max_ids = 1000;
251    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
252    del.PurgedFiles = (char *)malloc(del.max_ids);
253    
254    ua->info_msg(_("Begin purging jobs from Client \"%s\"\n"), cr.Name);
255
256    Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1));
257    Dmsg1(150, "select sql=%s\n", query.c_str());
258    db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del);
259
260    purge_job_list_from_catalog(ua, del);
261
262    if (del.num_ids == 0) {
263       ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"),
264          client->name(), client->catalog->name());
265    } else {
266       ua->info_msg(_("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids,
267          client->name(), client->catalog->name());
268    }
269
270    if (del.JobId) {
271       free(del.JobId);
272    }
273    if (del.PurgedFiles) {
274       free(del.PurgedFiles);
275    }
276    return 1;
277 }
278
279
280 /*
281  * Remove File records from a list of JobIds
282  */
283 void purge_files_from_jobs(UAContext *ua, char *jobs)
284 {
285    POOL_MEM query(PM_MESSAGE);
286
287    Mmsg(query, "DELETE FROM File WHERE JobId IN (%s)", jobs);
288    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
289    Dmsg1(050, "Delete File sql=%s\n", query.c_str());
290
291    Mmsg(query, "DELETE FROM BaseFiles WHERE JobId IN (%s)", jobs);
292    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
293    Dmsg1(050, "Delete BaseFiles sql=%s\n", query.c_str());
294
295    /*
296     * Now mark Job as having files purged. This is necessary to
297     * avoid having too many Jobs to process in future prunings. If
298     * we don't do this, the number of JobId's in our in memory list
299     * could grow very large.
300     */
301    Mmsg(query, "UPDATE Job SET PurgedFiles=1 WHERE JobId IN (%s)", jobs);
302    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
303    Dmsg1(050, "Mark purged sql=%s\n", query.c_str());
304 }
305
306 /*
307  * Delete jobs (all records) from the catalog in groups of 1000
308  *  at a time.
309  */
310 void purge_job_list_from_catalog(UAContext *ua, del_ctx &del)
311 {
312    POOL_MEM jobids(PM_MESSAGE);
313    char ed1[50];
314
315    for (int i=0; del.num_ids; ) {
316       Dmsg1(150, "num_ids=%d\n", del.num_ids);
317       pm_strcat(jobids, "");
318       for (int j=0; j<1000 && del.num_ids>0; j++) {
319          del.num_ids--;
320          if (del.JobId[i] == 0 || ua->jcr->JobId == del.JobId[i]) {
321             Dmsg2(150, "skip JobId[%d]=%d\n", i, (int)del.JobId[i]);
322             i++;
323             continue;
324          }
325          if (*jobids.c_str() != 0) {
326             pm_strcat(jobids, ",");
327          }
328          pm_strcat(jobids, edit_int64(del.JobId[i++], ed1));
329          Dmsg1(150, "Add id=%s\n", ed1);
330          del.num_del++;
331       }
332       Dmsg1(150, "num_ids=%d\n", del.num_ids);
333       purge_jobs_from_catalog(ua, jobids.c_str());
334    }
335 }
336
337 /*
338  * Delete files from a list of jobs in groups of 1000
339  *  at a time.
340  */
341 void purge_files_from_job_list(UAContext *ua, del_ctx &del)
342 {
343    POOL_MEM jobids(PM_MESSAGE);
344    char ed1[50];
345    /*
346     * OK, now we have the list of JobId's to be pruned, send them
347     *   off to be deleted batched 1000 at a time.
348     */
349    for (int i=0; del.num_ids; ) {
350       pm_strcat(jobids, "");
351       for (int j=0; j<1000 && del.num_ids>0; j++) {
352          del.num_ids--;
353          if (del.JobId[i] == 0 || ua->jcr->JobId == del.JobId[i]) {
354             Dmsg2(150, "skip JobId[%d]=%d\n", i, (int)del.JobId[i]);
355             i++;
356             continue;
357          }
358          if (*jobids.c_str() != 0) {
359             pm_strcat(jobids, ",");
360          }
361          pm_strcat(jobids, edit_int64(del.JobId[i++], ed1));
362          Dmsg1(150, "Add id=%s\n", ed1);
363          del.num_del++;
364       }
365       purge_files_from_jobs(ua, jobids.c_str());
366    }
367 }
368
369 /*
370  * Change the type of the next copy job to backup.
371  * We need to upgrade the next copy of a normal job,
372  * and also upgrade the next copy when the normal job
373  * already have been purged.
374  *
375  *   JobId: 1   PriorJobId: 0    (original)
376  *   JobId: 2   PriorJobId: 1    (first copy)
377  *   JobId: 3   PriorJobId: 1    (second copy)
378  *
379  *   JobId: 2   PriorJobId: 1    (first copy, now regular backup)
380  *   JobId: 3   PriorJobId: 1    (second copy)
381  *
382  *  => Search through PriorJobId in jobid and
383  *                    PriorJobId in PriorJobId (jobid)
384  */
385 void upgrade_copies(UAContext *ua, char *jobs)
386 {
387    POOL_MEM query(PM_MESSAGE);
388    
389    db_lock(ua->db);
390
391    /* Do it in two times for mysql */
392    Mmsg(query, uap_upgrade_copies_oldest_job[db_type], JT_JOB_COPY, jobs, jobs);
393    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
394    Dmsg1(050, "Upgrade copies Log sql=%s\n", query.c_str());
395
396    /* Now upgrade first copy to Backup */
397    Mmsg(query, "UPDATE Job SET Type='B' "      /* JT_JOB_COPY => JT_BACKUP  */
398                 "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
399
400    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
401
402    Mmsg(query, "DROP TABLE cpy_tmp");
403    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
404
405    db_unlock(ua->db);
406 }
407
408 /*
409  * Remove all records from catalog for a list of JobIds
410  */
411 void purge_jobs_from_catalog(UAContext *ua, char *jobs)
412 {
413    POOL_MEM query(PM_MESSAGE);
414
415    /* Delete (or purge) records associated with the job */
416    purge_files_from_jobs(ua, jobs);
417
418    Mmsg(query, "DELETE FROM JobMedia WHERE JobId IN (%s)", jobs);
419    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
420    Dmsg1(050, "Delete JobMedia sql=%s\n", query.c_str());
421
422    Mmsg(query, "DELETE FROM Log WHERE JobId IN (%s)", jobs);
423    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
424    Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
425
426    upgrade_copies(ua, jobs);
427
428    /* Now remove the Job record itself */
429    Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
430    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
431
432    Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
433 }
434
435 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
436 {} /* ***FIXME*** implement */
437
438 /*
439  * Returns: 1 if Volume purged
440  *          0 if Volume not purged
441  */
442 bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr, bool force)
443 {
444    POOL_MEM query(PM_MESSAGE);
445    struct del_ctx del;
446    int i;
447    bool purged = false;
448    bool stat;
449    JOB_DBR jr;
450    char ed1[50];
451
452    stat = strcmp(mr->VolStatus, "Append") == 0 ||
453           strcmp(mr->VolStatus, "Full")   == 0 ||
454           strcmp(mr->VolStatus, "Used")   == 0 ||
455           strcmp(mr->VolStatus, "Error")  == 0;
456    if (!stat) {
457       ua->error_msg(_("\nVolume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
458                      "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
459                      mr->VolumeName, mr->VolStatus);
460       return 0;
461    }
462
463    memset(&jr, 0, sizeof(jr));
464    memset(&del, 0, sizeof(del));
465    del.max_ids = 1000;
466    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
467
468    /*
469     * Check if he wants to purge a single jobid
470     */
471    i = find_arg_with_value(ua, "jobid");
472    if (i >= 0) {
473       del.num_ids = 1;
474       del.JobId[0] = str_to_int64(ua->argv[i]);
475    } else {
476       /*
477        * Purge ALL JobIds
478        */
479       Mmsg(query, "SELECT DISTINCT JobId FROM JobMedia WHERE MediaId=%s", 
480            edit_int64(mr->MediaId, ed1));
481       if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del)) {
482          ua->error_msg("%s", db_strerror(ua->db));
483          Dmsg0(050, "Count failed\n");
484          goto bail_out;
485       }
486    }
487
488    purge_job_list_from_catalog(ua, del);
489
490    ua->info_msg(_("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
491       del.num_del==1?"":"s", mr->VolumeName);
492
493    purged = is_volume_purged(ua, mr, force); 
494
495 bail_out:
496    if (del.JobId) {
497       free(del.JobId);
498    }
499    return purged;
500 }
501
502 /*
503  * This routine will check the JobMedia records to see if the
504  *   Volume has been purged. If so, it marks it as such and
505  *
506  * Returns: true if volume purged
507  *          false if not
508  *
509  * Note, we normally will not purge a volume that has Firstor LastWritten
510  *   zero, because it means the volume is most likely being written
511  *   however, if the user manually purges using the purge command in
512  *   the console, he has been warned, and we go ahead and purge
513  *   the volume anyway, if possible).
514  */
515 bool is_volume_purged(UAContext *ua, MEDIA_DBR *mr, bool force)
516 {
517    POOL_MEM query(PM_MESSAGE);
518    struct s_count_ctx cnt;
519    bool purged = false;
520    char ed1[50];
521
522    if (!force && (mr->FirstWritten == 0 || mr->LastWritten == 0)) {
523       goto bail_out;               /* not written cannot purge */
524    }
525
526    if (strcmp(mr->VolStatus, "Purged") == 0) {
527       purged = true;
528       goto bail_out;
529    }
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.c_str(), del_count_handler, (void *)&cnt)) {
536       ua->error_msg("%s", db_strerror(ua->db));
537       Dmsg0(050, "Count failed\n");
538       goto bail_out;
539    }
540
541    if (cnt.count == 0) {
542       ua->warning_msg(_("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
543          mr->VolumeName);
544       if (!(purged = mark_media_purged(ua, mr))) {
545          ua->error_msg("%s", db_strerror(ua->db));
546       }
547    }
548 bail_out:
549    return purged;
550 }
551
552 static BSOCK *open_sd_bsock(UAContext *ua)
553 {
554    STORE *store = ua->jcr->wstore;
555
556    if (!ua->jcr->store_bsock) {
557       ua->send_msg(_("Connecting to Storage daemon %s at %s:%d ...\n"),
558          store->name(), store->address, store->SDport);
559       if (!connect_to_storage_daemon(ua->jcr, 10, SDConnectTimeout, 1)) {
560          ua->error_msg(_("Failed to connect to Storage daemon.\n"));
561          return NULL;
562       }
563    }
564    return ua->jcr->store_bsock;
565 }
566
567 static void do_truncate_on_purge(UAContext *ua, MEDIA_DBR *mr, 
568                                  char *pool, char *storage,
569                                  int drive, BSOCK *sd)
570 {
571    int dvd;
572    bool ok=false;
573    uint64_t VolBytes = 0;
574    
575    /* TODO: Return if not mr->Recyle ? */
576    if (!mr->Recycle) {
577       return;
578    }
579
580    if (mr->ActionOnPurge & ON_PURGE_TRUNCATE) {
581       /* Send the command to truncate the volume after purge. If this feature
582        * is disabled for the specific device, this will be a no-op.
583        */
584
585       /* Protect us from spaces */
586       bash_spaces(mr->VolumeName);
587       bash_spaces(mr->MediaType);
588       bash_spaces(pool);
589       bash_spaces(storage);
590          
591       sd->fsend("relabel %s OldName=%s NewName=%s PoolName=%s "
592                 "MediaType=%s Slot=%d drive=%d\n",
593                    storage,
594                    mr->VolumeName, mr->VolumeName,
595                    pool, mr->MediaType, mr->Slot, drive);
596          
597       unbash_spaces(mr->VolumeName);
598       unbash_spaces(mr->MediaType);
599       unbash_spaces(pool);
600       unbash_spaces(storage);
601
602       while (sd->recv() >= 0) {
603          ua->send_msg("%s", sd->msg);
604          if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu DVD=%d ",
605                     &VolBytes, &dvd) == 2) 
606          {
607             ok = true;
608          }
609       }
610
611       if (ok) {
612          mr->VolBytes = VolBytes;
613          mr->VolFiles = 0;
614          if (!db_update_media_record(ua->jcr, ua->db, mr)) {
615             ua->error_msg(_("Can't update volume size in the catalog\n"));
616          }
617          ua->send_msg(_("The volume \"%s\" has been truncated\n"), mr->VolumeName);
618       } else {
619          ua->warning_msg(_("Unable to truncate volume \"%s\"\n"), mr->VolumeName);
620       }
621    }
622 }
623
624 /* purge action= pool= volume= storage= devicetype= */
625 static int action_on_purge_cmd(UAContext *ua, const char *cmd)
626 {
627    bool allpools=false;
628    int drive=-1;
629    int nb=0;
630
631    uint32_t *results=NULL;
632    const char *action="all";
633    STORE *store=NULL;
634    POOL *pool=NULL;
635    MEDIA_DBR mr;
636    POOL_DBR pr;
637
638    BSOCK *sd=NULL;
639    
640    memset(&pr, 0, sizeof(pr));
641    memset(&mr, 0, sizeof(mr));
642
643    /* Look arguments */
644    for (int i=1; i<ua->argc; i++) {
645       if (strcasecmp(ua->argk[i], NT_("allpools")) == 0) {
646          allpools = true;
647             
648       } else if (strcasecmp(ua->argk[i], NT_("volume")) == 0 && ua->argv[i]) {
649          bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
650
651       } else if (strcasecmp(ua->argk[i], NT_("devicetype")) == 0 && ua->argv[i]) {
652          bstrncpy(mr.MediaType, ua->argv[i], sizeof(mr.MediaType));
653          
654       } else if (strcasecmp(ua->argk[i], NT_("drive")) == 0 && ua->argv[i]) {
655          drive = atoi(ua->argv[i]);
656
657       } else if (strcasecmp(ua->argk[i], NT_("action")) == 0 && ua->argv[i]) {
658          action=ua->argv[i];
659       }
660    }
661
662    /* Choose storage */
663    ua->jcr->wstore = store =  get_storage_resource(ua, false);
664    if (!store) {
665       goto bail_out;
666    }
667    mr.StorageId = store->StorageId;
668
669    if (!open_db(ua)) {
670       Dmsg0(100, "Can't open db\n");
671       goto bail_out;
672    }
673
674    if (!allpools) {
675       /* force pool selection */
676       pool = get_pool_resource(ua);
677       if (!pool) {
678          Dmsg0(100, "Can't get pool resource\n");
679          goto bail_out;
680       }
681       bstrncpy(pr.Name, pool->name(), sizeof(pr.Name));
682       if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
683          Dmsg0(100, "Can't get pool record\n");
684          goto bail_out;
685       }
686       mr.PoolId = pr.PoolId;
687    }
688
689    mr.Recycle = 1;
690    mr.Enabled = 1;
691    mr.VolBytes = 10000;
692    bstrncpy(mr.VolStatus, "Purged", sizeof(mr.VolStatus));
693
694    if (!db_get_media_ids(ua->jcr, ua->db, &mr, &nb, &results)) {
695       Dmsg0(100, "No results from db_get_media_ids\n");
696       goto bail_out;
697    }
698    
699    if (!nb) {
700       ua->send_msg(_("No volume founds to perform %s action(s)\n"), action);
701       goto bail_out;
702    }
703
704    if ((sd=open_sd_bsock(ua)) == NULL) {
705       Dmsg0(100, "Can't open connection to sd\n");
706       goto bail_out;
707    }
708
709    for (int i=0; i < nb; i++) {
710       memset(&mr, 0, sizeof(mr));
711       mr.MediaId = results[i];
712       if (db_get_media_record(ua->jcr, ua->db, &mr)) {         
713          /* TODO: ask for drive and change Pool */
714          if (!strcasecmp("truncate", action) || !strcasecmp("all", action)) {
715             do_truncate_on_purge(ua, &mr, pr.Name, store->dev_name(), drive, sd);
716          }
717       } else {
718          Dmsg1(0, "Can't find MediaId=%lld\n", (uint64_t) mr.MediaId);
719       }
720    }
721
722 bail_out:
723    close_db(ua);
724    if (sd) {
725       sd->signal(BNET_TERMINATE);
726       sd->close();
727       ua->jcr->store_bsock = NULL;
728    }
729    ua->jcr->wstore = NULL;
730    if (results) {
731       free(results);
732    }
733
734    return 1;
735 }
736
737 /*
738  * IF volume status is Append, Full, Used, or Error, mark it Purged
739  *   Purged volumes can then be recycled (if enabled).
740  */
741 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
742 {
743    JCR *jcr = ua->jcr;
744    if (strcmp(mr->VolStatus, "Append") == 0 ||
745        strcmp(mr->VolStatus, "Full")   == 0 ||
746        strcmp(mr->VolStatus, "Used")   == 0 ||
747        strcmp(mr->VolStatus, "Error")  == 0) {
748       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
749       if (!db_update_media_record(jcr, ua->db, mr)) {
750          return false;
751       }
752       pm_strcpy(jcr->VolumeName, mr->VolumeName);
753       generate_job_event(jcr, "VolumePurged");
754       generate_plugin_event(jcr, bEventVolumePurged);
755       /*
756        * If the RecyclePool is defined, move the volume there
757        */
758       if (mr->RecyclePoolId && mr->RecyclePoolId != mr->PoolId) {
759          POOL_DBR oldpr, newpr;
760          memset(&oldpr, 0, sizeof(POOL_DBR));
761          memset(&newpr, 0, sizeof(POOL_DBR));
762          newpr.PoolId = mr->RecyclePoolId;
763          oldpr.PoolId = mr->PoolId;
764          if (   db_get_pool_record(jcr, ua->db, &oldpr) 
765              && db_get_pool_record(jcr, ua->db, &newpr)) 
766          {
767             /* check if destination pool size is ok */
768             if (newpr.MaxVols > 0 && newpr.NumVols >= newpr.MaxVols) {
769                ua->error_msg(_("Unable move recycled Volume in full " 
770                               "Pool \"%s\" MaxVols=%d\n"),
771                         newpr.Name, newpr.MaxVols);
772
773             } else {            /* move media */
774                update_vol_pool(ua, newpr.Name, mr, &oldpr);
775             }
776          } else {
777             ua->error_msg("%s", db_strerror(ua->db));
778          }
779       }
780
781       /* Send message to Job report, if it is a *real* job */           
782       if (jcr && jcr->JobId > 0) {
783          Jmsg(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"),
784             mr->VolumeName); 
785       }
786       return true;
787    } else {
788       ua->error_msg(_("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
789    }
790    return strcmp(mr->VolStatus, "Purged") == 0;
791 }