]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_purge.c
Tweak copyright dates + subroutine name
[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    /* Do it in two times for mysql */
391    Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
392                   "SELECT MIN(JobId) AS JobId FROM Job "     /* Choose the oldest job */
393                    "WHERE Type='%c' "
394                      "AND ( PriorJobId IN (%s) "
395                          "OR "
396                           " PriorJobId IN ( "
397                              "SELECT PriorJobId "
398                                "FROM Job "
399                               "WHERE JobId IN (%s) "
400                                " AND Type='B' "
401                             ") "
402                          ") "
403                    "GROUP BY PriorJobId ",           /* one result per copy */
404         JT_JOB_COPY, jobs, jobs);
405    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
406    Dmsg1(050, "Upgrade copies Log sql=%s\n", query.c_str());
407
408    /* Now upgrade first copy to Backup */
409    Mmsg(query, "UPDATE Job SET Type='B' "           /* JT_JOB_COPY => JT_BACKUP  */
410                 "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
411
412    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
413
414    Mmsg(query, "DROP TABLE cpy_tmp");
415    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
416
417    db_unlock(ua->db);
418 }
419
420 /*
421  * Remove all records from catalog for a list of JobIds
422  */
423 void purge_jobs_from_catalog(UAContext *ua, char *jobs)
424 {
425    POOL_MEM query(PM_MESSAGE);
426
427    /* Delete (or purge) records associated with the job */
428    purge_files_from_jobs(ua, jobs);
429
430    Mmsg(query, "DELETE FROM JobMedia WHERE JobId IN (%s)", jobs);
431    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
432    Dmsg1(050, "Delete JobMedia sql=%s\n", query.c_str());
433
434    Mmsg(query, "DELETE FROM Log WHERE JobId IN (%s)", jobs);
435    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
436    Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
437
438    upgrade_copies(ua, jobs);
439
440    /* Now remove the Job record itself */
441    Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
442    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
443
444    Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
445 }
446
447 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
448 {} /* ***FIXME*** implement */
449
450 /*
451  * Returns: 1 if Volume purged
452  *          0 if Volume not purged
453  */
454 bool purge_jobs_from_volume(UAContext *ua, MEDIA_DBR *mr, bool force)
455 {
456    POOL_MEM query(PM_MESSAGE);
457    struct del_ctx del;
458    int i;
459    bool purged = false;
460    bool stat;
461    JOB_DBR jr;
462    char ed1[50];
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       ua->error_msg(_("\nVolume \"%s\" has VolStatus \"%s\" and cannot be purged.\n"
470                      "The VolStatus must be: Append, Full, Used, or Error to be purged.\n"),
471                      mr->VolumeName, mr->VolStatus);
472       return 0;
473    }
474
475    memset(&jr, 0, sizeof(jr));
476    memset(&del, 0, sizeof(del));
477    del.max_ids = 1000;
478    del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids);
479
480    /*
481     * Check if he wants to purge a single jobid
482     */
483    i = find_arg_with_value(ua, "jobid");
484    if (i >= 0) {
485       del.num_ids = 1;
486       del.JobId[0] = str_to_int64(ua->argv[i]);
487    } else {
488       /*
489        * Purge ALL JobIds
490        */
491       Mmsg(query, "SELECT DISTINCT JobId FROM JobMedia WHERE MediaId=%s", 
492            edit_int64(mr->MediaId, ed1));
493       if (!db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del)) {
494          ua->error_msg("%s", db_strerror(ua->db));
495          Dmsg0(050, "Count failed\n");
496          goto bail_out;
497       }
498    }
499
500    purge_job_list_from_catalog(ua, del);
501
502    ua->info_msg(_("%d File%s on Volume \"%s\" purged from catalog.\n"), del.num_del,
503       del.num_del==1?"":"s", mr->VolumeName);
504
505    purged = is_volume_purged(ua, mr, force); 
506
507 bail_out:
508    if (del.JobId) {
509       free(del.JobId);
510    }
511    return purged;
512 }
513
514 /*
515  * This routine will check the JobMedia records to see if the
516  *   Volume has been purged. If so, it marks it as such and
517  *
518  * Returns: true if volume purged
519  *          false if not
520  *
521  * Note, we normally will not purge a volume that has Firstor LastWritten
522  *   zero, because it means the volume is most likely being written
523  *   however, if the user manually purges using the purge command in
524  *   the console, he has been warned, and we go ahead and purge
525  *   the volume anyway, if possible).
526  */
527 bool is_volume_purged(UAContext *ua, MEDIA_DBR *mr, bool force)
528 {
529    POOL_MEM query(PM_MESSAGE);
530    struct s_count_ctx cnt;
531    bool purged = false;
532    char ed1[50];
533
534    if (!force && (mr->FirstWritten == 0 || mr->LastWritten == 0)) {
535       goto bail_out;               /* not written cannot purge */
536    }
537
538    if (strcmp(mr->VolStatus, "Purged") == 0) {
539       purged = true;
540       goto bail_out;
541    }
542
543    /* If purged, mark it so */
544    cnt.count = 0;
545    Mmsg(query, "SELECT count(*) FROM JobMedia WHERE MediaId=%s", 
546         edit_int64(mr->MediaId, ed1));
547    if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) {
548       ua->error_msg("%s", db_strerror(ua->db));
549       Dmsg0(050, "Count failed\n");
550       goto bail_out;
551    }
552
553    if (cnt.count == 0) {
554       ua->warning_msg(_("There are no more Jobs associated with Volume \"%s\". Marking it purged.\n"),
555          mr->VolumeName);
556       if (!(purged = mark_media_purged(ua, mr))) {
557          ua->error_msg("%s", db_strerror(ua->db));
558       }
559    }
560 bail_out:
561    return purged;
562 }
563
564 static BSOCK *open_sd_bsock(UAContext *ua)
565 {
566    STORE *store = ua->jcr->wstore;
567
568    if (!ua->jcr->store_bsock) {
569       ua->send_msg(_("Connecting to Storage daemon %s at %s:%d ...\n"),
570          store->name(), store->address, store->SDport);
571       if (!connect_to_storage_daemon(ua->jcr, 10, SDConnectTimeout, 1)) {
572          ua->error_msg(_("Failed to connect to Storage daemon.\n"));
573          return NULL;
574       }
575    }
576    return ua->jcr->store_bsock;
577 }
578
579 static void do_truncate_on_purge(UAContext *ua, MEDIA_DBR *mr, 
580                                  char *pool, char *storage,
581                                  int drive, BSOCK *sd)
582 {
583    int dvd;
584    bool ok=false;
585    uint64_t VolBytes = 0;
586    
587    /* TODO: Return if not mr->Recyle ? */
588    if (!mr->Recycle) {
589       return;
590    }
591
592    if (mr->ActionOnPurge & AOP_TRUNCATE) {
593       /* Send the command to truncate the volume after purge. If this feature
594        * is disabled for the specific device, this will be a no-op.
595        */
596
597       /* Protect us from spaces */
598       bash_spaces(mr->VolumeName);
599       bash_spaces(mr->MediaType);
600       bash_spaces(pool);
601       bash_spaces(storage);
602          
603       sd->fsend("relabel %s OldName=%s NewName=%s PoolName=%s "
604                 "MediaType=%s Slot=%d drive=%d\n",
605                    storage,
606                    mr->VolumeName, mr->VolumeName,
607                    pool, mr->MediaType, mr->Slot, drive);
608          
609       unbash_spaces(mr->VolumeName);
610       unbash_spaces(mr->MediaType);
611       unbash_spaces(pool);
612       unbash_spaces(storage);
613
614       while (sd->recv() >= 0) {
615          ua->send_msg("%s", sd->msg);
616          if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu DVD=%d ",
617                     &VolBytes, &dvd) == 2) 
618          {
619             ok = true;
620          }
621       }
622
623       if (ok) {
624          mr->VolBytes = VolBytes;
625          mr->VolFiles = 0;
626          if (!db_update_media_record(ua->jcr, ua->db, mr)) {
627             ua->error_msg(_("Can't update volume size in the catalog\n"));
628          }
629          ua->send_msg(_("The volume \"%s\" has been truncated\n"), mr->VolumeName);
630       } else {
631          ua->warning_msg(_("Unable to truncate volume \"%s\"\n"), mr->VolumeName);
632       }
633    }
634 }
635
636 /* purge action= pool= volume= storage= devicetype= */
637 static int action_on_purge_cmd(UAContext *ua, const char *cmd)
638 {
639    bool allpools=false;
640    int drive=-1;
641    int nb=0;
642
643    uint32_t *results=NULL;
644    const char *action="all";
645    STORE *store=NULL;
646    POOL *pool=NULL;
647    MEDIA_DBR mr;
648    POOL_DBR pr;
649
650    BSOCK *sd=NULL;
651    
652    memset(&pr, 0, sizeof(pr));
653    memset(&mr, 0, sizeof(mr));
654
655    /* Look arguments */
656    for (int i=1; i<ua->argc; i++) {
657       if (strcasecmp(ua->argk[i], NT_("allpools")) == 0) {
658          allpools = true;
659             
660       } else if (strcasecmp(ua->argk[i], NT_("volume")) == 0 && ua->argv[i]) {
661          bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
662
663       } else if (strcasecmp(ua->argk[i], NT_("devicetype")) == 0 && ua->argv[i]) {
664          bstrncpy(mr.MediaType, ua->argv[i], sizeof(mr.MediaType));
665          
666       } else if (strcasecmp(ua->argk[i], NT_("drive")) == 0 && ua->argv[i]) {
667          drive = atoi(ua->argv[i]);
668
669       } else if (strcasecmp(ua->argk[i], NT_("action")) == 0 && ua->argv[i]) {
670          action=ua->argv[i];
671       }
672    }
673
674    /* Choose storage */
675    ua->jcr->wstore = store =  get_storage_resource(ua, false);
676    if (!store) {
677       goto bail_out;
678    }
679    mr.StorageId = store->StorageId;
680
681    if (!open_db(ua)) {
682       Dmsg0(100, "Can't open db\n");
683       goto bail_out;
684    }
685
686    if (!allpools) {
687       /* force pool selection */
688       pool = get_pool_resource(ua);
689       if (!pool) {
690          Dmsg0(100, "Can't get pool resource\n");
691          goto bail_out;
692       }
693       bstrncpy(pr.Name, pool->name(), sizeof(pr.Name));
694       if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
695          Dmsg0(100, "Can't get pool record\n");
696          goto bail_out;
697       }
698       mr.PoolId = pr.PoolId;
699    }
700
701    mr.Recycle = 1;
702    mr.Enabled = 1;
703    mr.VolBytes = 10000;
704    bstrncpy(mr.VolStatus, "Purged", sizeof(mr.VolStatus));
705
706    if (!db_get_media_ids(ua->jcr, ua->db, &mr, &nb, &results)) {
707       Dmsg0(100, "No results from db_get_media_ids\n");
708       goto bail_out;
709    }
710    
711    if (!nb) {
712       ua->send_msg(_("No volume founds to perform %s action(s)\n"), action);
713       goto bail_out;
714    }
715
716    if ((sd=open_sd_bsock(ua)) == NULL) {
717       Dmsg0(100, "Can't open connection to sd\n");
718       goto bail_out;
719    }
720
721    for (int i=0; i < nb; i++) {
722       memset(&mr, 0, sizeof(mr));
723       mr.MediaId = results[i];
724       if (db_get_media_record(ua->jcr, ua->db, &mr)) {         
725          /* TODO: ask for drive and change Pool */
726          if (!strcasecmp("truncate", action) || !strcasecmp("all", action)) {
727             do_truncate_on_purge(ua, &mr, pr.Name, store->dev_name(), drive, sd);
728          }
729       } else {
730          Dmsg1(0, "Can't find MediaId=%lld\n", (uint64_t) mr.MediaId);
731       }
732    }
733
734 bail_out:
735    close_db(ua);
736    if (sd) {
737       sd->signal(BNET_TERMINATE);
738       sd->close();
739       ua->jcr->store_bsock = NULL;
740    }
741    ua->jcr->wstore = NULL;
742    if (results) {
743       free(results);
744    }
745
746    return 1;
747 }
748
749 /*
750  * IF volume status is Append, Full, Used, or Error, mark it Purged
751  *   Purged volumes can then be recycled (if enabled).
752  */
753 bool mark_media_purged(UAContext *ua, MEDIA_DBR *mr)
754 {
755    JCR *jcr = ua->jcr;
756    if (strcmp(mr->VolStatus, "Append") == 0 ||
757        strcmp(mr->VolStatus, "Full")   == 0 ||
758        strcmp(mr->VolStatus, "Used")   == 0 ||
759        strcmp(mr->VolStatus, "Error")  == 0) {
760       bstrncpy(mr->VolStatus, "Purged", sizeof(mr->VolStatus));
761       if (!db_update_media_record(jcr, ua->db, mr)) {
762          return false;
763       }
764       pm_strcpy(jcr->VolumeName, mr->VolumeName);
765       generate_job_event(jcr, "VolumePurged");
766       generate_plugin_event(jcr, bEventVolumePurged);
767       /*
768        * If the RecyclePool is defined, move the volume there
769        */
770       if (mr->RecyclePoolId && mr->RecyclePoolId != mr->PoolId) {
771          POOL_DBR oldpr, newpr;
772          memset(&oldpr, 0, sizeof(POOL_DBR));
773          memset(&newpr, 0, sizeof(POOL_DBR));
774          newpr.PoolId = mr->RecyclePoolId;
775          oldpr.PoolId = mr->PoolId;
776          if (   db_get_pool_record(jcr, ua->db, &oldpr) 
777              && db_get_pool_record(jcr, ua->db, &newpr)) 
778          {
779             /* check if destination pool size is ok */
780             if (newpr.MaxVols > 0 && newpr.NumVols >= newpr.MaxVols) {
781                ua->error_msg(_("Unable move recycled Volume in full " 
782                               "Pool \"%s\" MaxVols=%d\n"),
783                         newpr.Name, newpr.MaxVols);
784
785             } else {            /* move media */
786                update_vol_pool(ua, newpr.Name, mr, &oldpr);
787             }
788          } else {
789             ua->error_msg("%s", db_strerror(ua->db));
790          }
791       }
792
793       /* Send message to Job report, if it is a *real* job */           
794       if (jcr && jcr->JobId > 0) {
795          Jmsg(jcr, M_INFO, 0, _("All records pruned from Volume \"%s\"; marking it \"Purged\"\n"),
796             mr->VolumeName); 
797       }
798       return true;
799    } else {
800       ua->error_msg(_("Cannot purge Volume with VolStatus=%s\n"), mr->VolStatus);
801    }
802    return strcmp(mr->VolStatus, "Purged") == 0;
803 }