2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
21 * Bacula Director -- User Agent Commands
22 * These are "dot" commands, i.e. commands preceded
23 * by a period. These commands are meant to be used
24 * by a program, so there is no prompting, and the
25 * returned results are (supposed to be) predictable.
27 * Kern Sibbald, April MMII
33 #include "cats/bvfs.h"
34 #include "findlib/find.h"
36 /* Imported variables */
37 extern struct s_jl joblevels[];
38 extern struct s_jt jobtypes[];
40 /* Imported functions */
41 extern void do_messages(UAContext *ua, const char *cmd);
42 extern int quit_cmd(UAContext *ua, const char *cmd);
43 extern int qhelp_cmd(UAContext *ua, const char *cmd);
44 extern bool dot_status_cmd(UAContext *ua, const char *cmd);
47 /* Forward referenced functions */
48 static bool admin_cmds(UAContext *ua, const char *cmd);
49 static bool jobscmd(UAContext *ua, const char *cmd);
50 static bool filesetscmd(UAContext *ua, const char *cmd);
51 static bool clientscmd(UAContext *ua, const char *cmd);
52 static bool msgscmd(UAContext *ua, const char *cmd);
53 static bool poolscmd(UAContext *ua, const char *cmd);
54 static bool schedulescmd(UAContext *ua, const char *cmd);
55 static bool storagecmd(UAContext *ua, const char *cmd);
56 static bool defaultscmd(UAContext *ua, const char *cmd);
57 static bool typescmd(UAContext *ua, const char *cmd);
58 static bool tagscmd(UAContext *ua, const char *cmd);
59 static bool backupscmd(UAContext *ua, const char *cmd);
60 static bool levelscmd(UAContext *ua, const char *cmd);
61 static bool getmsgscmd(UAContext *ua, const char *cmd);
62 static bool volstatuscmd(UAContext *ua, const char *cmd);
63 static bool mediatypescmd(UAContext *ua, const char *cmd);
64 static bool locationscmd(UAContext *ua, const char *cmd);
65 static bool mediacmd(UAContext *ua, const char *cmd);
66 static bool aopcmd(UAContext *ua, const char *cmd);
67 static bool catalogscmd(UAContext *ua, const char *cmd);
69 static bool dot_bvfs_lsdirs(UAContext *ua, const char *cmd);
70 static bool dot_bvfs_lsfiles(UAContext *ua, const char *cmd);
71 static bool dot_bvfs_update(UAContext *ua, const char *cmd);
72 static bool dot_bvfs_get_jobids(UAContext *ua, const char *cmd);
73 static bool dot_bvfs_versions(UAContext *ua, const char *cmd);
74 static bool dot_bvfs_restore(UAContext *ua, const char *cmd);
75 static bool dot_bvfs_cleanup(UAContext *ua, const char *cmd);
76 static bool dot_bvfs_clear_cache(UAContext *ua, const char *cmd);
77 static bool dot_bvfs_decode_lstat(UAContext *ua, const char *cmd);
78 static bool dot_bvfs_update_fv(UAContext *ua, const char *cmd);
79 static bool dot_bvfs_get_volumes(UAContext *ua, const char *cmd);
80 static bool dot_bvfs_get_jobs(UAContext *ua, const char *cmd);
82 static bool putfile_cmd(UAContext *ua, const char *cmd);
83 static bool api_cmd(UAContext *ua, const char *cmd);
84 static bool sql_cmd(UAContext *ua, const char *cmd);
85 static bool dot_quit_cmd(UAContext *ua, const char *cmd);
86 static bool dot_help_cmd(UAContext *ua, const char *cmd);
87 static int one_handler(void *ctx, int num_field, char **row);
89 struct cmdstruct { const char *key; bool (*func)(UAContext *ua, const char *cmd); const char *help;const bool use_in_rs;};
90 static struct cmdstruct commands[] = { /* help */ /* can be used in runscript */
91 { NT_(".api"), api_cmd, NULL, false},
92 { NT_(".backups"), backupscmd, NULL, false},
93 { NT_(".clients"), clientscmd, NULL, true},
94 { NT_(".catalogs"), catalogscmd, NULL, false},
95 { NT_(".defaults"), defaultscmd, NULL, false},
96 { NT_(".die"), admin_cmds, NULL, false},
97 { NT_(".dump"), admin_cmds, NULL, false},
98 { NT_(".exit"), admin_cmds, NULL, false},
99 { NT_(".filesets"), filesetscmd, NULL, false},
100 { NT_(".help"), dot_help_cmd, NULL, false},
101 { NT_(".jobs"), jobscmd, NULL, true},
102 { NT_(".levels"), levelscmd, NULL, false},
103 { NT_(".messages"), getmsgscmd, NULL, false},
104 { NT_(".msgs"), msgscmd, NULL, false},
105 { NT_(".pools"), poolscmd, NULL, true},
106 { NT_(".quit"), dot_quit_cmd, NULL, false},
107 { NT_(".putfile"), putfile_cmd, NULL, false}, /* use @putfile */
108 { NT_(".schedule"), schedulescmd, NULL, false},
109 { NT_(".sql"), sql_cmd, NULL, false},
110 { NT_(".status"), dot_status_cmd, NULL, false},
111 { NT_(".storage"), storagecmd, NULL, true},
112 { NT_(".volstatus"), volstatuscmd, NULL, true},
113 { NT_(".media"), mediacmd, NULL, true},
114 { NT_(".mediatypes"), mediatypescmd, NULL, true},
115 { NT_(".locations"), locationscmd, NULL, true},
116 { NT_(".actiononpurge"),aopcmd, NULL, true},
117 { NT_(".bvfs_lsdirs"), dot_bvfs_lsdirs, NULL, true},
118 { NT_(".bvfs_lsfiles"),dot_bvfs_lsfiles, NULL, true},
119 { NT_(".bvfs_get_volumes"),dot_bvfs_get_volumes,NULL, true},
120 { NT_(".bvfs_update"), dot_bvfs_update, NULL, true},
121 { NT_(".bvfs_get_jobids"), dot_bvfs_get_jobids, NULL, true},
122 { NT_(".bvfs_get_jobs"), dot_bvfs_get_jobs, NULL, true},
123 { NT_(".bvfs_versions"), dot_bvfs_versions, NULL, true},
124 { NT_(".bvfs_restore"), dot_bvfs_restore, NULL, true},
125 { NT_(".bvfs_cleanup"), dot_bvfs_cleanup, NULL, true},
126 { NT_(".bvfs_decode_lstat"),dot_bvfs_decode_lstat,NULL, true},
127 { NT_(".bvfs_clear_cache"),dot_bvfs_clear_cache,NULL, false},
128 { NT_(".bvfs_update_fv"),dot_bvfs_update_fv, NULL, true},
129 { NT_(".types"), typescmd, NULL, false},
130 { NT_(".tags"), tagscmd, NULL, false}
132 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
135 * Execute a command from the UA
137 bool do_a_dot_command(UAContext *ua)
143 BSOCK *user = ua->UA_sock;
145 Dmsg1(1400, "Dot command: %s\n", user?user->msg:"");
146 if (ua->argc == 0 || !user) {
150 len = strlen(ua->argk[0]);
152 if (ua->api) user->signal(BNET_CMD_BEGIN);
153 if (ua->api) user->signal(BNET_CMD_OK);
154 return true; /* no op */
156 for (i=0; i<comsize; i++) { /* search for command */
157 if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) {
158 /* Check if this command is authorized in RunScript */
159 if (ua->runscript && !commands[i].use_in_rs) {
160 ua->error_msg(_("Can't use %s command in a runscript"), ua->argk[0]);
164 /* Check if command permitted, but "quit" is always OK */
165 if (strcmp(ua->argk[0], NT_(".quit")) != 0 &&
166 !acl_access_ok(ua, Command_ACL, ua->argk[0], len)) {
169 Dmsg1(100, "Cmd: %s\n", ua->cmd);
171 if (ua->api) user->signal(BNET_CMD_BEGIN);
172 ok = (*commands[i].func)(ua, ua->cmd); /* go execute command */
173 if (ua->api) user->signal(ok?BNET_CMD_OK:BNET_CMD_FAILED);
175 found = user->is_stop() ? false : true;
180 ua->error_msg("%s%s", ua->argk[0], _(": is an invalid command.\n"));
186 static void bvfs_set_acl(UAContext *ua, Bvfs *bvfs)
188 /* If no console resource => default console and all is permitted */
189 if (!ua || !ua->cons) {
192 bvfs->set_job_acl(ua->cons->ACL_lists[Job_ACL]);
193 bvfs->set_client_acl(ua->cons->ACL_lists[Client_ACL]);
194 bvfs->set_fileset_acl(ua->cons->ACL_lists[FileSet_ACL]);
195 bvfs->set_pool_acl(ua->cons->ACL_lists[Pool_ACL]);
198 static bool dot_bvfs_decode_lstat(UAContext *ua, const char *cmd)
203 int pos = find_arg_with_value(ua, "lstat");
206 for (char *p = ua->argv[pos] ; *p ; p++) {
207 if (! (B_ISALPHA(*p) || B_ISDIGIT(*p) || B_ISSPACE(*p) || *p == '/' || *p == '+' || *p == '-')) {
208 ua->error_msg("Can't accept %c in lstat\n", *p);
213 decode_stat(ua->argv[pos], &sp, sizeof(sp), &LinkFI);
214 Mmsg(q, "st_nlink=%lld\nst_mode=%lld\nst_uid=%lld\nst_gid=%lld\nst_size=%lld\n"
215 "st_blocks=%lld\nst_ino=%lld\nst_ctime=%lld\nst_mtime=%lld\nst_mtime=%lld\n"
216 "st_dev=%lld\nLinkFI=%lld\n",
217 (int64_t) sp.st_nlink,
218 (int64_t) sp.st_mode,
221 (int64_t) sp.st_size,
222 (int64_t) sp.st_blocks,
224 (int64_t) sp.st_ctime,
225 (int64_t) sp.st_mtime,
226 (int64_t) sp.st_atime,
231 ua->send_msg("%s", q.c_str());
236 static bool dot_bvfs_update(UAContext *ua, const char *cmd)
238 if (!open_new_client_db(ua)) {
242 int pos = find_arg_with_value(ua, "jobid");
243 if (pos != -1 && is_a_number_list(ua->argv[pos])) {
244 if (!bvfs_update_path_hierarchy_cache(ua->jcr, ua->db, ua->argv[pos])) {
245 ua->error_msg("ERROR: BVFS reported a problem for %s\n",
249 /* update cache for all jobids */
250 bvfs_update_cache(ua->jcr, ua->db);
256 static bool dot_bvfs_update_fv(UAContext *ua, const char *cmd)
258 int pos = find_arg_with_value(ua, "jobid");
260 if (pos == -1 || !is_a_number_list(ua->argv[pos])) {
261 ua->error_msg("Expecting to find jobid=1,2,3 argument\n");
265 if (!open_new_client_db(ua)) {
269 bvfs_update_path_hierarchy_cache(ua->jcr, ua->db, ua->argv[pos]);
270 bvfs_update_fv_cache(ua->jcr, ua->db, ua->argv[pos]);
272 ua->info_msg("OK\n");
277 static bool dot_bvfs_clear_cache(UAContext *ua, const char *cmd)
279 if (!open_client_db(ua)) {
283 int pos = find_arg(ua, "yes");
285 Bvfs fs(ua->jcr, ua->db);
287 ua->info_msg("OK\n");
289 ua->error_msg("Can't find 'yes' argument\n");
295 static int bvfs_result_handler(void *ctx, int fields, char **row)
297 UAContext *ua = (UAContext *)ctx;
300 char *fileid=row[BVFS_FileId];
301 char *lstat=row[BVFS_LStat];
302 char *jobid=row[BVFS_JobId];
304 char empty[] = "A A A A A A A A A A A A A A";
307 /* We need to deal with non existant path */
308 if (!fileid || !is_a_number(fileid)) {
314 memset(&statp, 0, sizeof(struct stat));
315 decode_stat(lstat, &statp, sizeof(statp), &LinkFI);
317 Dmsg1(100, "type=%s\n", row[0]);
318 if (bvfs_is_dir(row)) {
319 char *path = bvfs_basename_dir(row[BVFS_Name]);
320 ua->send_msg("%s\t0\t%s\t%s\t%s\t%s\n", row[BVFS_PathId], fileid,
323 } else if (bvfs_is_version(row)) {
324 ua->send_msg("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", row[BVFS_PathId],
325 row[BVFS_FilenameId], fileid, jobid,
326 lstat, row[BVFS_Md5], row[BVFS_VolName],
327 row[BVFS_VolInchanger]);
329 } else if (bvfs_is_file(row)) {
330 ua->send_msg("%s\t%s\t%s\t%s\t%s\t%s\n", row[BVFS_PathId],
331 row[BVFS_FilenameId], fileid, jobid,
332 lstat, row[BVFS_Name]);
334 } else if (bvfs_is_volume_list(row)) {
335 ua->send_msg("%s\t%s\n", row[BVFS_VolName],
336 row[BVFS_VolInchanger]);
342 static bool bvfs_parse_arg_version(UAContext *ua,
353 for (int i=1; i<ua->argc; i++) {
354 if (fnid && strcasecmp(ua->argk[i], NT_("fnid")) == 0) {
355 if (is_a_number(ua->argv[i])) {
356 *fnid = str_to_int64(ua->argv[i]);
360 if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
361 *client = ua->argv[i];
364 if (copies && strcasecmp(ua->argk[i], NT_("copies")) == 0) {
368 if (versions && strcasecmp(ua->argk[i], NT_("versions")) == 0) {
372 return (*client && *fnid > 0);
375 static bool bvfs_parse_arg(UAContext *ua,
376 DBId_t *pathid, char **path, char **jobid,
378 int *limit, int *offset)
389 for (int i=1; i<ua->argc; i++) {
390 if (strcasecmp(ua->argk[i], NT_("pathid")) == 0) {
391 if (is_a_number(ua->argv[i])) {
392 *pathid = str_to_int64(ua->argv[i]);
396 if (strcasecmp(ua->argk[i], NT_("path")) == 0) {
400 if (strcasecmp(ua->argk[i], NT_("username")) == 0) {
401 *username = ua->argv[i];
404 if (jobid && strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
405 if (is_a_number_list(ua->argv[i])) {
406 *jobid = ua->argv[i];
410 if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0) {
412 memset(&jr, 0, sizeof(jr));
413 bstrncpy(jr.Job, ua->argv[i], sizeof(jr.Job));
414 if (!open_new_client_db(ua)) {
417 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
420 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
423 /* Store the jobid after the ua->cmd, a bit kluggy */
424 int len = strlen(ua->cmd);
425 ua->cmd = check_pool_memory_size(ua->cmd, len + 1 + 50);
426 *jobid = edit_uint64(jr.JobId, ua->cmd + len + 1);
429 if (strcasecmp(ua->argk[i], NT_("limit")) == 0) {
430 if (is_a_number(ua->argv[i])) {
431 *limit = str_to_int64(ua->argv[i]);
435 if (strcasecmp(ua->argk[i], NT_("offset")) == 0) {
436 if (is_a_number(ua->argv[i])) {
437 *offset = str_to_int64(ua->argv[i]);
442 if (jobid && *jobid == NULL) {
446 if (!(*pathid || *path)) {
453 /* .bvfs_cleanup path=b2XXXXX
455 static bool dot_bvfs_cleanup(UAContext *ua, const char *cmd)
458 if ((i = find_arg_with_value(ua, "path")) >= 0) {
459 if (!open_client_db(ua)) {
462 Bvfs fs(ua->jcr, ua->db);
463 fs.drop_restore_list(ua->argv[i]);
468 /* .bvfs_restore path=b2XXXXX jobid=1,2 fileid=1,2 dirid=1,2 hardlink=1,2,3,4
470 static bool dot_bvfs_restore(UAContext *ua, const char *cmd)
473 int limit=2000, offset=0, i;
474 char *path=NULL, *jobid=NULL, *username=NULL;
475 char *empty = (char *)"";
476 char *fileid, *dirid, *hardlink;
477 fileid = dirid = hardlink = empty;
479 if (!bvfs_parse_arg(ua, &pathid, &path, &jobid, &username,
482 ua->error_msg("Can't find jobid, pathid or path argument\n");
483 return true; /* not enough param */
486 if (!open_new_client_db(ua)) {
490 Bvfs fs(ua->jcr, ua->db);
491 bvfs_set_acl(ua, &fs);
492 fs.set_username(username);
493 fs.set_jobids(jobid);
495 if ((i = find_arg_with_value(ua, "fileid")) >= 0) {
496 fileid = ua->argv[i];
498 if ((i = find_arg_with_value(ua, "dirid")) >= 0) {
501 if ((i = find_arg_with_value(ua, "hardlink")) >= 0) {
502 hardlink = ua->argv[i];
505 if (fs.compute_restore_list(fileid, dirid, hardlink, path)) {
506 ua->send_msg("OK\n");
508 ua->error_msg("Can't create restore list\n");
515 * .bvfs_get_volumes [path=/ filename=test jobid=1 | fileid=1]
520 static bool dot_bvfs_get_volumes(UAContext *ua, const char *cmd)
524 char *path=NULL, *jobid=NULL, *username=NULL;
526 int limit=2000, offset=0;
529 bvfs_parse_arg(ua, &pathid, &path, &jobid, &username, &limit, &offset);
531 if ((i = find_arg_with_value(ua, "filename")) >= 0) {
532 if (!(jobid && (path || pathid))) { /* Need JobId and Path/PathId */
533 ua->error_msg("Can't find jobid, pathid or path argument\n");
537 filename = ua->argv[i];
539 } else if ((i = find_arg_with_value(ua, "fileid")) >= 0) {
540 if (!is_a_number(ua->argv[i])) {
541 ua->error_msg("Expecting integer for FileId, got %s\n", ua->argv[i]);
544 fileid = str_to_int64(ua->argv[i]);
547 if (!open_new_client_db(ua)) {
551 Bvfs fs(ua->jcr, ua->db);
552 bvfs_set_acl(ua, &fs);
553 fs.set_username(username);
554 fs.set_handler(bvfs_result_handler, ua);
561 fs.get_volumes(fileid);
568 * .bvfs_lsfiles jobid=1,2,3,4 pathid=10
569 * .bvfs_lsfiles jobid=1,2,3,4 path=/
571 static bool dot_bvfs_lsfiles(UAContext *ua, const char *cmd)
574 int limit=2000, offset=0;
575 char *path=NULL, *jobid=NULL, *username=NULL;
576 char *pattern=NULL, *filename=NULL;
579 if (!bvfs_parse_arg(ua, &pathid, &path, &jobid, &username,
582 ua->error_msg("Can't find jobid, pathid or path argument\n");
583 return true; /* not enough param */
585 if ((i = find_arg_with_value(ua, "pattern")) >= 0) {
586 pattern = ua->argv[i];
588 if ((i = find_arg_with_value(ua, "filename")) >= 0) {
589 filename = ua->argv[i];
592 if (!open_new_client_db(ua)) {
596 Bvfs fs(ua->jcr, ua->db);
597 bvfs_set_acl(ua, &fs);
598 fs.set_username(username);
599 fs.set_jobids(jobid);
600 fs.set_handler(bvfs_result_handler, ua);
603 fs.set_pattern(pattern);
606 fs.set_filename(filename);
614 fs.set_offset(offset);
622 * .bvfs_lsdirs jobid=1,2,3,4 pathid=10
623 * .bvfs_lsdirs jobid=1,2,3,4 path=/
624 * .bvfs_lsdirs jobid=1,2,3,4 path=
626 static bool dot_bvfs_lsdirs(UAContext *ua, const char *cmd)
629 int limit=2000, offset=0;
630 char *path=NULL, *jobid=NULL, *username=NULL;
635 if (!bvfs_parse_arg(ua, &pathid, &path, &jobid, &username,
638 ua->error_msg("Can't find jobid, pathid or path argument\n");
639 return true; /* not enough param */
642 if ((i = find_arg_with_value(ua, "pattern")) >= 0) {
643 pattern = ua->argv[i];
646 dironly = find_arg(ua, "dironly");
648 if (!open_new_client_db(ua)) {
652 Bvfs fs(ua->jcr, ua->db);
653 bvfs_set_acl(ua, &fs);
654 fs.set_username(username);
655 fs.set_jobids(jobid);
657 fs.set_handler(bvfs_result_handler, ua);
660 fs.set_pattern(pattern);
669 fs.set_offset(offset);
671 fs.ls_special_dirs();
680 * .bvfs_versions fnid=10 pathid=10 client=xxx copies versions
683 static bool dot_bvfs_versions(UAContext *ua, const char *cmd)
687 int limit=2000, offset=0;
688 char *path=NULL, *client=NULL, *username=NULL;
689 bool copies=false, versions=false;
690 if (!bvfs_parse_arg(ua, &pathid, &path, NULL, &username,
693 ua->error_msg("Can't find pathid or path argument\n");
694 return true; /* not enough param */
697 if (!bvfs_parse_arg_version(ua, &client, &fnid, &versions, &copies))
699 ua->error_msg("Can't find client or fnid argument\n");
700 return true; /* not enough param */
703 if (!open_new_client_db(ua)) {
707 Bvfs fs(ua->jcr, ua->db);
708 bvfs_set_acl(ua, &fs);
710 fs.set_see_all_versions(versions);
711 fs.set_see_copies(copies);
712 fs.set_handler(bvfs_result_handler, ua);
713 fs.set_offset(offset);
714 fs.get_all_file_versions(pathid, fnid, client);
719 /* .bvfs_get_jobids jobid=1
720 * -> returns needed jobids to restore
721 * .bvfs_get_jobids ujobid=xxx only
722 * -> returns the jobid of the job
723 * .bvfs_get_jobids jobid=1 jobname
724 * -> returns the jobname
725 * .bvfs_get_jobids client=xxx
726 * -> returns all jobid for the client
727 * .bvfs_get_jobids jobid=1 all
728 * -> returns needed jobids to restore with all filesets a JobId=1 time
729 * .bvfs_get_jobids job=XXXXX
730 * -> returns needed jobids to restore with the jobname
731 * .bvfs_get_jobids ujobid=JobName
732 * -> returns needed jobids to restore
734 static bool dot_bvfs_get_jobids(UAContext *ua, const char *cmd)
737 memset(&jr, 0, sizeof(JOB_DBR));
739 db_list_ctx jobids, tempids;
743 dbid_list ids; /* Store all FileSetIds for this client */
745 if (!open_new_client_db(ua)) {
749 Bvfs fs(ua->jcr, ua->db);
750 bvfs_set_acl(ua, &fs);
752 if ((pos = find_arg_with_value(ua, "username")) >= 0) {
753 fs.set_username(ua->argv[pos]);
756 if ((pos = find_arg_with_value(ua, "ujobid")) >= 0) {
757 bstrncpy(jr.Job, ua->argv[pos], sizeof(jr.Job));
760 if ((pos = find_arg_with_value(ua, "jobid")) >= 0) {
761 jr.JobId = str_to_int64(ua->argv[pos]);
763 /* Guess JobId from Job name, take the last successful jobid */
764 } else if ((pos = find_arg_with_value(ua, "job")) >= 0) {
769 bstrncpy(jr.Name, ua->argv[pos], MAX_NAME_LENGTH);
770 /* TODO: enhance this function to take client and/or fileset as argument*/
772 job = GetJobResWithName(jr.Name);
774 ua->error_msg(_("Unable to get Job record for Job=%s\n"), jr.Name);
780 "FROM Job JOIN FileSet USING (FileSetId) JOIN Client USING (ClientId) "
781 "WHERE Client.Name = '%s' AND FileSet.FileSet = '%s' "
782 "AND Job.Type = 'B' AND Job.JobStatus IN ('T', 'W') "
783 "ORDER By JobTDate DESC LIMIT 1",
784 job->client->name(), job->fileset->name());
785 ret = db_sql_query(ua->db, ua->db->cmd, db_int_handler, &JobId);
789 ua->error_msg(_("Unable to get last Job record for Job=%s\n"),jr.Name);
794 /* Get JobId from ujobid */
795 } else if ((pos = find_arg_with_value(ua, "ujobid")) >= 0) {
796 bstrncpy(jr.Job, ua->argv[pos], MAX_NAME_LENGTH);
798 /* Return all backup jobid for a client */
799 } else if ((pos = find_arg_with_value(ua, "client")) >= 0) {
803 cli = GetClientResWithName(ua->argv[pos]);
805 ua->error_msg(_("Unable to get Client record for Client=%s\n"),
812 "FROM Job JOIN Client USING (ClientId) "
813 "WHERE Client.Name = '%s' "
814 "AND Job.Type = 'B' AND Job.JobStatus IN ('T', 'W') "
815 "ORDER By JobTDate ASC",
817 ret = db_sql_query(ua->db, ua->db->cmd, db_list_handler, &jobids);
821 ua->error_msg(_("Unable to get last Job record for Client=%s\n"),
825 /* Apply the ACL filter on JobIds */
826 fs.set_jobids(jobids.list);
827 ua->send_msg("%s\n", fs.get_jobids());
831 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
832 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
833 ua->cmd, db_strerror(ua->db));
837 /* Display only the requested jobid or
838 * When in level base, we don't rely on any Full/Incr/Diff
840 if (find_arg(ua, "only") > 0 || jr.JobLevel == L_BASE) {
841 /* Apply the ACL filter on JobIds */
842 fs.set_jobid(jr.JobId);
843 ua->send_msg("%s\n", fs.get_jobids());
847 /* Display only the requested job name
849 if (find_arg(ua, "jobname") > 0) {
850 /* Apply the ACL filter on JobIds */
851 fs.set_jobid(jr.JobId);
852 if (str_to_int64(fs.get_jobids()) == (int64_t)jr.JobId) {
853 ua->send_msg("%s\n", jr.Job);
858 /* If we have the "all" option, we do a search on all defined fileset
861 if (find_arg(ua, "all") > 0) {
862 edit_int64(jr.ClientId, ed1);
863 Mmsg(query, uar_sel_filesetid, ed1);
864 db_get_query_dbids(ua->jcr, ua->db, query, ids);
867 ids.DBId[0] = jr.FileSetId;
870 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
872 /* Foreach different FileSet, we build a restore jobid list */
873 for (int i=0; i < ids.num_ids; i++) {
874 jr.FileSetId = ids.DBId[i];
875 if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &tempids)) {
881 fs.set_jobids(jobids.list);
882 ua->send_msg("%s\n", fs.get_jobids());
886 static int jobs_handler(void *ctx, int num_field, char **row)
888 UAContext *ua = (UAContext *)ctx;
889 ua->send_msg("%s %s %s\n", row[0], row[1], row[2]);
893 /* .bvfs_get_jobs client=xxx [fileset=yyyy]
894 * 1 yyyyy Backup1_xxx_xxx_xxxx_xxx
895 * 2 yyyyy Backup1_xxx_xxx_xxxx_xxx
897 static bool dot_bvfs_get_jobs(UAContext *ua, const char *cmd)
901 char esc_cli[MAX_ESCAPE_NAME_LENGTH];
902 char esc_job[MAX_ESCAPE_NAME_LENGTH];
903 if (!open_new_client_db(ua)) {
907 if (((pos = find_arg_with_value(ua, "client")) < 0) ||
908 (strlen(ua->argv[pos]) > MAX_NAME_LENGTH))
913 if (!acl_access_ok(ua, Client_ACL, ua->argv[pos])) {
917 posj = find_arg_with_value(ua, "ujobid");
918 /* Do a little check on the size of the argument */
919 if (posj >= 0 && strlen(ua->argv[posj]) > MAX_NAME_LENGTH) {
924 db_escape_string(ua->jcr, ua->db, esc_cli,
925 ua->argv[pos], strlen(ua->argv[pos]));
927 db_escape_string(ua->jcr, ua->db, esc_job,
928 ua->argv[posj], strlen(ua->argv[pos]));
929 Mmsg(tmp, "AND Job.Job = '%s'", esc_job);
932 "SELECT JobId, JobTDate, Job "
933 "FROM Job JOIN Client USING (ClientId) "
934 "WHERE Client.Name = '%s' AND Job.Type = 'B' AND Job.JobStatus IN ('T', 'W') "
936 "ORDER By JobTDate DESC",
937 esc_cli, tmp.c_str());
938 db_sql_query(ua->db, ua->db->cmd, jobs_handler, ua);
943 static bool dot_quit_cmd(UAContext *ua, const char *cmd)
949 static bool dot_help_cmd(UAContext *ua, const char *cmd)
955 static bool getmsgscmd(UAContext *ua, const char *cmd)
957 if (console_msg_pending) {
958 do_messages(ua, cmd);
964 static void do_storage_cmd(UAContext *ua, STORE *store, const char *cmd)
970 lstore.store = store;
971 pm_strcpy(lstore.store_source, _("unknown source"));
972 set_wstorage(jcr, &lstore);
973 /* Try connecting for up to 15 seconds */
974 ua->send_msg(_("Connecting to Storage daemon %s at %s:%d\n"),
975 store->name(), store->address, store->SDport);
976 if (!connect_to_storage_daemon(jcr, 1, 15, 0)) {
977 ua->error_msg(_("Failed to connect to Storage daemon.\n"));
980 Dmsg0(120, _("Connected to storage daemon\n"));
981 sd = jcr->store_bsock;
982 sd->fsend("%s", cmd);
983 if (sd->recv() >= 0) {
984 ua->send_msg("%s", sd->msg);
986 sd->signal(BNET_TERMINATE);
987 free_bsock(ua->jcr->store_bsock);
991 static void do_client_cmd(UAContext *ua, CLIENT *client, const char *cmd)
995 /* Connect to File daemon */
997 ua->jcr->client = client;
998 /* Try to connect for 15 seconds */
999 ua->send_msg(_("Connecting to Client %s at %s:%d\n"),
1000 client->name(), client->address, client->FDport);
1001 if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
1002 ua->error_msg(_("Failed to connect to Client.\n"));
1005 Dmsg0(120, "Connected to file daemon\n");
1006 fd = ua->jcr->file_bsock;
1007 fd->fsend("%s", cmd);
1008 if (fd->recv() >= 0) {
1009 ua->send_msg("%s", fd->msg);
1011 fd->signal(BNET_TERMINATE);
1012 free_bsock(ua->jcr->file_bsock);
1019 * .exit (no arg => .quit)
1021 static bool admin_cmds(UAContext *ua, const char *cmd)
1023 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1025 CLIENT *client=NULL;
1027 bool do_deadlock=false;
1028 const char *remote_cmd;
1032 if (strncmp(ua->argk[0], ".die", 4) == 0) {
1033 if (find_arg(ua, "deadlock") > 0) {
1035 remote_cmd = ".die deadlock";
1037 remote_cmd = ".die";
1039 } else if (strncmp(ua->argk[0], ".dump", 5) == 0) {
1040 remote_cmd = "sm_dump";
1041 } else if (strncmp(ua->argk[0], ".exit", 5) == 0) {
1042 remote_cmd = "exit";
1044 ua->error_msg(_("Unknown command: %s\n"), ua->argk[0]);
1047 /* General debug? */
1048 for (i=1; i<ua->argc; i++) {
1049 if (strcasecmp(ua->argk[i], "dir") == 0 ||
1050 strcasecmp(ua->argk[i], "director") == 0) {
1053 if (strcasecmp(ua->argk[i], "client") == 0 ||
1054 strcasecmp(ua->argk[i], "fd") == 0) {
1057 client = (CLIENT *)GetResWithName(R_CLIENT, ua->argv[i]);
1060 client = select_client_resource(ua);
1064 if (strcasecmp(ua->argk[i], NT_("store")) == 0 ||
1065 strcasecmp(ua->argk[i], NT_("storage")) == 0 ||
1066 strcasecmp(ua->argk[i], NT_("sd")) == 0) {
1069 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1072 store = get_storage_resource(ua, false/*no default*/);
1077 if (!dir && !store && !client) {
1079 * We didn't find an appropriate keyword above, so
1082 start_prompt(ua, _("Available daemons are: \n"));
1083 add_prompt(ua, _("Director"));
1084 add_prompt(ua, _("Storage"));
1085 add_prompt(ua, _("Client"));
1086 switch(do_prompt(ua, "", _("Select daemon type to make die"), NULL, 0)) {
1087 case 0: /* Director */
1091 store = get_storage_resource(ua, false/*no default*/);
1094 client = select_client_resource(ua);
1102 do_storage_cmd(ua, store, remote_cmd);
1106 do_client_cmd(ua, client, remote_cmd);
1110 if (strncmp(remote_cmd, ".die", 4) == 0) {
1112 ua->send_msg(_("The Director will generate a deadlock.\n"));
1116 ua->send_msg(_("The Director will segment fault.\n"));
1117 a = jcr->JobId; /* ref NULL pointer */
1118 jcr->JobId = 1000; /* another ref NULL pointer */
1121 } else if (strncmp(remote_cmd, ".dump", 5) == 0) {
1122 sm_dump(false, true);
1123 } else if (strncmp(remote_cmd, ".exit", 5) == 0) {
1124 dot_quit_cmd(ua, cmd);
1134 * Dummy routine for non-development version
1136 static bool admin_cmds(UAContext *ua, const char *cmd)
1138 ua->error_msg(_("Unknown command: %s\n"), ua->argk[0]);
1145 * Send a file to the director from bconsole @putfile command
1146 * The .putfile can not be used directly.
1148 static bool putfile_cmd(UAContext *ua, const char *cmd)
1150 int pos, i, pnl, fnl;
1152 POOLMEM *name = get_pool_memory(PM_FNAME);
1153 POOLMEM *path = get_pool_memory(PM_FNAME);
1154 POOLMEM *fname= get_pool_memory(PM_FNAME);
1155 const char *key = "putfile";
1158 if ((pos = find_arg_with_value(ua, "key")) > 0) {
1159 /* Check the string if the string is valid */
1160 for (i=0; ua->argv[pos][i] && isalnum(ua->argv[pos][i]) && i < 16; i++);
1162 if (ua->argv[pos][i] == 0) {
1163 key = ua->argv[pos];
1166 ua->error_msg("Invalid key name for putfile command");
1172 /* the (intptr_t)ua will allow one file per console session */
1173 make_unique_filename(&name, (intptr_t)ua, (char *)key);
1175 fp = fopen(name, "w");
1178 ua->error_msg("Unable to open destination file. ERR=%s\n",
1179 be.bstrerror(errno));
1184 while (ua->UA_sock->recv() > 0) {
1185 if (fwrite(ua->UA_sock->msg, ua->UA_sock->msglen, 1, fp) != 1) {
1187 ua->error_msg("Unable to write to the destination file. ERR=%s\n",
1188 be.bstrerror(errno));
1190 /* TODO: Check if we need to quit here (data will still be in the
1195 split_path_and_filename(name, &path, &pnl, &fname, &fnl);
1199 ua->send_msg("OK\n");
1202 ua->send_msg("ERROR\n");
1205 free_pool_memory(name);
1206 free_pool_memory(path);
1207 free_pool_memory(fname);
1215 * Can use an argument to filter on JobType
1218 static bool jobscmd(UAContext *ua, const char *cmd)
1223 if ((pos = find_arg_with_value(ua, "type")) >= 0) {
1224 type = ua->argv[pos][0];
1227 foreach_res(job, R_JOB) {
1228 if (!type || type == job->JobType) {
1229 if (acl_access_ok(ua, Job_ACL, job->name())) {
1230 ua->send_msg("%s\n", job->name());
1238 static bool filesetscmd(UAContext *ua, const char *cmd)
1242 foreach_res(fs, R_FILESET) {
1243 if (acl_access_ok(ua, FileSet_ACL, fs->name())) {
1244 ua->send_msg("%s\n", fs->name());
1251 static bool catalogscmd(UAContext *ua, const char *cmd)
1255 foreach_res(cat, R_CATALOG) {
1256 if (acl_access_ok(ua, Catalog_ACL, cat->name())) {
1257 ua->send_msg("%s\n", cat->name());
1264 static bool clientscmd(UAContext *ua, const char *cmd)
1268 foreach_res(client, R_CLIENT) {
1269 if (acl_access_ok(ua, Client_ACL, client->name())) {
1270 ua->send_msg("%s\n", client->name());
1277 static bool msgscmd(UAContext *ua, const char *cmd)
1281 foreach_res(msgs, R_MSGS) {
1282 ua->send_msg("%s\n", msgs->name());
1288 static bool poolscmd(UAContext *ua, const char *cmd)
1292 foreach_res(pool, R_POOL) {
1293 if (acl_access_ok(ua, Pool_ACL, pool->name())) {
1294 ua->send_msg("%s\n", pool->name());
1301 static bool schedulescmd(UAContext *ua, const char *cmd)
1305 foreach_res(sched, R_SCHEDULE) {
1306 if (acl_access_ok(ua, Schedule_ACL, sched->name())) {
1307 ua->send_msg("%s\n", sched->name());
1314 static bool storagecmd(UAContext *ua, const char *cmd)
1318 foreach_res(store, R_STORAGE) {
1319 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1320 ua->send_msg("%s\n", store->name());
1327 static bool aopcmd(UAContext *ua, const char *cmd)
1329 ua->send_msg("None\n");
1330 ua->send_msg("Truncate\n");
1334 static bool typescmd(UAContext *ua, const char *cmd)
1336 ua->send_msg("Backup\n");
1337 ua->send_msg("Restore\n");
1338 ua->send_msg("Admin\n");
1339 ua->send_msg("Verify\n");
1340 ua->send_msg("Migrate\n");
1341 ua->send_msg("Copy\n");
1345 static bool tagscmd(UAContext *ua, const char *cmd)
1348 for (const char *p = debug_get_tag(i++, NULL) ; p ; p = debug_get_tag(i++, NULL)) {
1349 ua->send_msg("%s\n", p);
1355 * If this command is called, it tells the director that we
1356 * are a program that wants a sort of API, and hence,
1357 * we will probably suppress certain output, include more
1358 * error codes, and most of all send back a good number
1359 * of new signals that indicate whether or not the command
1362 static bool api_cmd(UAContext *ua, const char *cmd)
1365 if (ua->argc >= 2) {
1366 ua->api = atoi(ua->argk[1]);
1368 /* Get output configuration options such as time format or separator */
1369 if ((i = find_arg_with_value(ua, "api_opts")) > 0) {
1370 bstrncpy(ua->api_opts, ua->argv[i], sizeof(ua->api_opts));
1381 static int client_backups_handler(void *ctx, int num_field, char **row)
1383 UAContext *ua = (UAContext *)ctx;
1384 ua->send_msg("| %s | %s | %s | %s | %s | %s | %s | %s |\n",
1385 row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]);
1390 * Return the backups for this client
1392 * .backups client=xxx fileset=yyy
1395 static bool backupscmd(UAContext *ua, const char *cmd)
1397 if (!open_client_db(ua)) {
1400 if (ua->argc != 3 || strcmp(ua->argk[1], "client") != 0 ||
1401 strcmp(ua->argk[2], "fileset") != 0) {
1404 if (!acl_access_ok(ua, Client_ACL, ua->argv[1]) ||
1405 !acl_access_ok(ua, FileSet_ACL, ua->argv[2])) {
1406 ua->error_msg(_("Access to specified Client or FileSet not allowed.\n"));
1409 Mmsg(ua->cmd, client_backups, ua->argv[1], ua->argv[2]);
1410 if (!db_sql_query(ua->db, ua->cmd, client_backups_handler, (void *)ua)) {
1411 ua->error_msg(_("Query failed: %s. ERR=%s\n"), ua->cmd, db_strerror(ua->db));
1417 static int sql_handler(void *ctx, int num_field, char **row)
1419 UAContext *ua = (UAContext *)ctx;
1420 POOL_MEM rows(PM_MESSAGE);
1422 /* Check for nonsense */
1423 if (num_field == 0 || row == NULL || row[0] == NULL) {
1424 return 0; /* nothing returned */
1426 for (int i=0; num_field--; i++) {
1428 pm_strcpy(rows, NPRT(row[0]));
1430 pm_strcat(rows, NPRT(row[i]));
1432 pm_strcat(rows, "\t");
1434 if (!rows.c_str() || !*rows.c_str()) {
1437 ua->send_msg("%s", rows.c_str());
1442 static bool sql_cmd(UAContext *ua, const char *cmd)
1445 if (!open_new_client_db(ua)) {
1448 index = find_arg_with_value(ua, "query");
1450 ua->error_msg(_("query keyword not found.\n"));
1453 if (!db_sql_query(ua->db, ua->argv[index], sql_handler, (void *)ua)) {
1454 Dmsg1(100, "Query failed: ERR=%s\n", db_strerror(ua->db));
1455 ua->error_msg(_("Query failed: %s. ERR=%s\n"), ua->cmd, db_strerror(ua->db));
1461 static int one_handler(void *ctx, int num_field, char **row)
1463 UAContext *ua = (UAContext *)ctx;
1464 ua->send_msg("%s\n", row[0]);
1468 static bool mediatypescmd(UAContext *ua, const char *cmd)
1470 if (!open_client_db(ua)) {
1473 if (!db_sql_query(ua->db,
1474 "SELECT DISTINCT MediaType FROM MediaType ORDER BY MediaType",
1475 one_handler, (void *)ua))
1477 ua->error_msg(_("List MediaType failed: ERR=%s\n"), db_strerror(ua->db));
1482 static bool mediacmd(UAContext *ua, const char *cmd)
1484 if (!open_client_db(ua)) {
1487 if (!db_sql_query(ua->db,
1488 "SELECT DISTINCT Media.VolumeName FROM Media ORDER BY VolumeName",
1489 one_handler, (void *)ua))
1491 ua->error_msg(_("List Media failed: ERR=%s\n"), db_strerror(ua->db));
1496 static bool locationscmd(UAContext *ua, const char *cmd)
1498 if (!open_client_db(ua)) {
1501 if (!db_sql_query(ua->db,
1502 "SELECT DISTINCT Location FROM Location ORDER BY Location",
1503 one_handler, (void *)ua))
1505 ua->error_msg(_("List Location failed: ERR=%s\n"), db_strerror(ua->db));
1510 static bool levelscmd(UAContext *ua, const char *cmd)
1513 /* Note some levels are blank, which means none is needed */
1514 if (ua->argc == 1) {
1515 for (i=0; joblevels[i].level_name; i++) {
1516 if (joblevels[i].level_name[0] != ' ') {
1517 ua->send_msg("%s\n", joblevels[i].level_name);
1520 } else if (ua->argc == 2) {
1522 /* Assume that first argument is the Job Type */
1523 for (i=0; jobtypes[i].type_name; i++) {
1524 if (strcasecmp(ua->argk[1], jobtypes[i].type_name) == 0) {
1525 jobtype = jobtypes[i].job_type;
1529 for (i=0; joblevels[i].level_name; i++) {
1530 if ((joblevels[i].job_type == jobtype) && (joblevels[i].level_name[0] != ' ')) {
1531 ua->send_msg("%s\n", joblevels[i].level_name);
1539 static bool volstatuscmd(UAContext *ua, const char *cmd)
1541 ua->send_msg("Append\n");
1542 ua->send_msg("Full\n");
1543 ua->send_msg("Used\n");
1544 ua->send_msg("Recycle\n");
1545 ua->send_msg("Purged\n");
1546 ua->send_msg("Cleaning\n");
1547 ua->send_msg("Error\n");
1552 * Return default values for a job
1554 static bool defaultscmd(UAContext *ua, const char *cmd)
1557 if (ua->argc != 2 || !ua->argv[1]) {
1561 /* Send Job defaults */
1562 if (strcmp(ua->argk[1], "job") == 0) {
1563 if (!acl_access_ok(ua, Job_ACL, ua->argv[1])) {
1566 JOB *job = (JOB *)GetResWithName(R_JOB, ua->argv[1]);
1569 ua->send_msg("job=%s", job->name());
1570 ua->send_msg("pool=%s", job->pool->name());
1571 ua->send_msg("messages=%s", job->messages->name());
1572 ua->send_msg("client=%s", job->client?job->client->name():_("*None*"));
1573 get_job_storage(&store, job, NULL);
1574 ua->send_msg("storage=%s", store.store->name());
1575 ua->send_msg("where=%s", job->RestoreWhere?job->RestoreWhere:"");
1576 ua->send_msg("level=%s", level_to_str(job->JobLevel));
1577 ua->send_msg("type=%s", job_type_to_str(job->JobType));
1578 ua->send_msg("fileset=%s", job->fileset->name());
1579 ua->send_msg("enabled=%d", job->enabled);
1580 ua->send_msg("catalog=%s", job->client?job->client->catalog->name():_("*None*"));
1583 /* Send Pool defaults */
1584 else if (strcmp(ua->argk[1], "pool") == 0) {
1585 if (!acl_access_ok(ua, Pool_ACL, ua->argv[1])) {
1588 POOL *pool = (POOL *)GetResWithName(R_POOL, ua->argv[1]);
1590 ua->send_msg("pool=%s", pool->name());
1591 ua->send_msg("pool_type=%s", pool->pool_type);
1592 ua->send_msg("label_format=%s", pool->label_format?pool->label_format:"");
1593 ua->send_msg("use_volume_once=%d", pool->use_volume_once);
1594 ua->send_msg("purge_oldest_volume=%d", pool->purge_oldest_volume);
1595 ua->send_msg("recycle_oldest_volume=%d", pool->recycle_oldest_volume);
1596 ua->send_msg("recycle_current_volume=%d", pool->recycle_current_volume);
1597 ua->send_msg("max_volumes=%d", pool->max_volumes);
1598 ua->send_msg("vol_retention=%s", edit_uint64(pool->VolRetention, ed1));
1599 ua->send_msg("vol_use_duration=%s", edit_uint64(pool->VolUseDuration, ed1));
1600 ua->send_msg("max_vol_jobs=%d", pool->MaxVolJobs);
1601 ua->send_msg("max_vol_files=%d", pool->MaxVolFiles);
1602 ua->send_msg("max_vol_bytes=%s", edit_uint64(pool->MaxVolBytes, ed1));
1603 ua->send_msg("auto_prune=%d", pool->AutoPrune);
1604 ua->send_msg("recycle=%d", pool->Recycle);
1605 ua->send_msg("file_retention=%s", edit_uint64(pool->FileRetention, ed1));
1606 ua->send_msg("job_retention=%s", edit_uint64(pool->JobRetention, ed1));
1609 /* Send Storage defaults */
1610 else if (strcmp(ua->argk[1], "storage") == 0) {
1611 if (!acl_access_ok(ua, Storage_ACL, ua->argv[1])) {
1614 STORE *storage = (STORE *)GetResWithName(R_STORAGE, ua->argv[1]);
1617 ua->send_msg("storage=%s", storage->name());
1618 ua->send_msg("address=%s", storage->address);
1619 ua->send_msg("enabled=%d", storage->enabled);
1620 ua->send_msg("media_type=%s", storage->media_type);
1621 ua->send_msg("sdport=%d", storage->SDport);
1622 device = (DEVICE *)storage->device->first();
1623 ua->send_msg("device=%s", device->name());
1624 if (storage->device->size() > 1) {
1625 while ((device = (DEVICE *)storage->device->next())) {
1626 ua->send_msg(",%s", device->name());
1631 /* Send Client defaults */
1632 else if (strcmp(ua->argk[1], "client") == 0) {
1633 if (!acl_access_ok(ua, Client_ACL, ua->argv[1])) {
1636 CLIENT *client = (CLIENT *)GetResWithName(R_CLIENT, ua->argv[1]);
1638 ua->send_msg("client=%s", client->name());
1639 ua->send_msg("address=%s", client->address);
1640 ua->send_msg("fdport=%d", client->FDport);
1641 ua->send_msg("file_retention=%s", edit_uint64(client->FileRetention, ed1));
1642 ua->send_msg("job_retention=%s", edit_uint64(client->JobRetention, ed1));
1643 ua->send_msg("autoprune=%d", client->AutoPrune);
1644 ua->send_msg("catalog=%s", client->catalog->name());