all: Makefile
@for I in ${subdirs}; \
- do (cd $$I; echo "==>Entering directory `pwd`"; $(MAKE) $@ || exit 1); done
+ do (cd $$I; echo "==>Entering directory `pwd`"; \
+ $(MAKE) $@ || (echo ""; echo ""; echo " ====== Error in `pwd` ======"; \
+ echo ""; echo "";)); \
+ done
depend:
@for I in ${subdirs}; \
bacula-fd: Makefile
@for I in ${FDsubdirs}; \
- do (cd $$I; echo "==>Entering directory `pwd`"; $(MAKE) all || exit 1); done
+ do (cd $$I; echo "==>Entering directory `pwd`"; \
+ $(MAKE) all || (echo ""; echo ""; echo " ====== Error in `pwd` ======"; \
+ echo ""; echo "";)); \
+ done
#-------------------------------------------------------------------------
configure: autoconf/configure.in autoconf/aclocal.m4 autoconf/acconfig.h autoconf/config.h.in
VolWrites INTEGER UNSIGNED NOT NULL,
VolMaxBytes BIGINT UNSIGNED NOT NULL,
VolCapacityBytes BIGINT UNSIGNED NOT NULL,
- VolStatus ENUM('Full', 'Archive', 'Append', 'Recycle',
+ VolStatus ENUM('Full', 'Archive', 'Append', 'Recycle', 'Purged',
'Read-Only', 'Disabled', 'Error', 'Busy') NOT NULL,
Recycle ENUM('No', 'Yes') NOT NULL,
PRIMARY KEY(MediaId),
return 1;
}
+#define MAX_DEL_LIST_LEN 1000000
-/* Delete Media record */
-int db_delete_media_record(B_DB *mdb, MEDIA_DBR *mr)
+struct s_del_ctx {
+ uint32_t *JobId;
+ int num_ids; /* ids stored */
+ int max_ids; /* size of array */
+ int num_del; /* number deleted */
+ int tot_ids; /* total to process */
+};
+
+/*
+ * Called here to make in memory list of JobIds to be
+ * deleted. The in memory list will then be transversed
+ * to issue the SQL DELETE commands. Note, the list
+ * is allowed to get to MAX_DEL_LIST_LEN to limit the
+ * maximum malloc'ed memory.
+ */
+static int delete_handler(void *ctx, int num_fields, char **row)
{
+ struct s_del_ctx *del = (struct s_del_ctx *)ctx;
- P(mdb->mutex);
- if (mr->MediaId == 0) {
- Mmsg(&mdb->cmd, "DELETE FROM Media WHERE VolumeName=\"%s\"",
- mr->VolumeName);
- } else {
- Mmsg(&mdb->cmd, "DELETE FROM Media WHERE MediaId=%d",
- mr->MediaId);
+ if (del->num_ids == MAX_DEL_LIST_LEN) {
+ return 1;
+ }
+ if (del->num_ids == del->max_ids) {
+ del->max_ids = (del->max_ids * 3) / 2;
+ del->JobId = (uint32_t *)brealloc(del->JobId, sizeof(uint32_t) *
+ del->max_ids);
}
+ del->JobId[del->num_ids++] = (uint32_t)strtod(row[0], NULL);
+ return 0;
+}
- mr->MediaId = DELETE_DB(mdb, mdb->cmd);
- V(mdb->mutex);
+/*
+ * This routine will purge (delete) all records
+ * associated with a particular Volume. It will
+ * not delete the media record itself.
+ */
+static int do_media_purge(B_DB *mdb, MEDIA_DBR *mr)
+{
+ char *query = (char *)get_pool_memory(PM_MESSAGE);
+ struct s_del_ctx del;
+ int i;
+
+ del.num_ids = 0;
+ del.tot_ids = 0;
+ del.num_del = 0;
+ del.max_ids = 0;
+ Mmsg(&mdb->cmd, "SELECT JobId from JobMedia WHERE MediaId=%d", mr->MediaId);
+ del.max_ids = mr->VolJobs;
+ if (del.max_ids < 100) {
+ del.max_ids = 100;
+ } else if (del.max_ids > MAX_DEL_LIST_LEN) {
+ del.max_ids = MAX_DEL_LIST_LEN;
+ }
+ del.JobId = (uint32_t *)malloc(sizeof(uint32_t) * del.max_ids);
+ db_sql_query(mdb, mdb->cmd, delete_handler, (void *)&del);
+
+ for (i=0; i < del.num_ids; i++) {
+ Dmsg1(400, "Delete JobId=%d\n", del.JobId[i]);
+ Mmsg(&query, "DELETE FROM Job WHERE JobId=%d", del.JobId[i]);
+ db_sql_query(mdb, query, NULL, (void *)NULL);
+ Mmsg(&query, "DELETE FROM File WHERE JobId=%d", del.JobId[i]);
+ db_sql_query(mdb, query, NULL, (void *)NULL);
+ Mmsg(&query, "DELETE FROM JobMedia WHERE JobId=%d", del.JobId[i]);
+ db_sql_query(mdb, query, NULL, (void *)NULL);
+ }
+ free(del.JobId);
+ free_pool_memory(query);
return 1;
}
+/* Delete Media record and all records that
+ * are associated with it.
+ */
+int db_delete_media_record(B_DB *mdb, MEDIA_DBR *mr)
+{
+ if (mr->MediaId == 0 && !db_get_media_record(mdb, mr)) {
+ return 0;
+ }
+ /* Delete associated records */
+ do_media_purge(mdb, mr);
+
+ Mmsg(&mdb->cmd, "DELETE FROM Media WHERE MediaId=%d", mr->MediaId);
+ db_sql_query(mdb, mdb->cmd, NULL, (void *)NULL);
+ return 1;
+}
+
+/*
+ * Purge all records associated with a
+ * media record. This does not delete the
+ * media record itself. But the media status
+ * is changed to "Purged".
+ */
+int db_purge_media_record(B_DB *mdb, MEDIA_DBR *mr)
+{
+ if (mr->MediaId == 0 && !db_get_media_record(mdb, mr)) {
+ return 0;
+ }
+ /* Delete associated records */
+ do_media_purge(mdb, mr);
+
+ /* Mark Volume as purged */
+ strcpy(mr->VolStatus, "Purged");
+ if (!db_update_media_record(mdb, mr)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
#endif /* HAVE_MYSQL || HAVE_SQLITE */
static char *configfile = NULL;
static BSOCK *UA_sock = NULL;
static DIRRES *dir;
+static FILE *output = stdout;
+
#define CONFIG_FILE "./console.conf" /* default configuration file */
}
UnlockRes();
if (ndir == 0) {
- Emsg1(M_ABORT, 0, "No director resource defined in %s\n\
+ Emsg1(M_ABORT, 0, "No Director resource defined in %s\n\
Without that I don't how to speak to the Director :-(\n", configfile);
}
if (ndir > 1) {
UA_sock = init_bsock(0, "", "", 0);
try_again:
- printf("Available Directors:\n");
+ fprintf(output, "Available Directors:\n");
LockRes();
ndir = 0;
for (dir = NULL; (dir = (DIRRES *)GetNextRes(R_DIRECTOR, (RES *)dir)); ) {
- printf("%d %s at %s:%d\n", 1+ndir++, dir->hdr.name, dir->address,
+ fprintf(output, "%d %s at %s:%d\n", 1+ndir++, dir->hdr.name, dir->address,
dir->DIRport);
}
UnlockRes();
}
item = atoi(UA_sock->msg);
if (item < 0 || item > ndir) {
- printf("You must enter a number between 1 and %d\n", ndir);
+ fprintf(output, "You must enter a number between 1 and %d\n", ndir);
goto try_again;
}
LockRes();
}
jcr.dir_bsock = UA_sock;
if (!authenticate_director(&jcr, dir)) {
- printf("ERR: %s", UA_sock->msg);
+ fprintf(stderr, "ERR: %s", UA_sock->msg);
terminate_console(0);
return 1;
}
int
get_cmd(char *prompt, BSOCK *sock, int sec)
{
- fprintf(stdout, prompt);
- fflush(stdout);
+ fprintf(output, prompt);
+ fflush(output);
switch (wait_for_data(fileno(stdin), sec)) {
case 0:
return 0; /* timeout */
* resource with the routine to process the record
* information.
*/
+
+/* Console "globals" */
+static struct res_items cons_items[] = {
+ {"name", store_name, ITEM(res_cons.hdr.name), 0, ITEM_REQUIRED, 0},
+ {"description", store_str, ITEM(res_cons.hdr.desc), 0, 0, 0},
+ {"rcfile", store_dir, ITEM(res_cons.rc_file), 0, 0, 0},
+ {"historyfile", store_dir, ITEM(res_cons.hist_file), 0, 0, 0},
+ {NULL, NULL, NULL, 0, 0, 0}
+};
+
+
+/* Director's that we can contact */
static struct res_items dir_items[] = {
{"name", store_name, ITEM(res_dir.hdr.name), 0, ITEM_REQUIRED, 0},
{"description", store_str, ITEM(res_dir.hdr.desc), 0, 0, 0},
* It must have one item for each of the resources.
*/
struct s_res resources[] = {
+ {"console", cons_items, R_CONSOLE, NULL},
{"director", dir_items, R_DIRECTOR, NULL},
{NULL, NULL, 0, NULL}
};
recurse = 0;
}
switch (type) {
+ case R_CONSOLE:
+ printf("Console: name=%s rcfile=%s histfile=%s\n", reshdr->name,
+ res->res_cons.rc_file, res->res_cons.hist_file);
+ break;
case R_DIRECTOR:
printf("Director: name=%s address=%s DIRport=%d\n", reshdr->name,
res->res_dir.address, res->res_dir.DIRport);
free(res->res_dir.hdr.desc);
switch (type) {
+ case R_CONSOLE:
+ if (res->res_cons.rc_file) {
+ free(res->res_cons.rc_file);
+ }
+ if (res->res_cons.hist_file) {
+ free(res->res_cons.hist_file);
+ }
case R_DIRECTOR:
if (res->res_dir.address)
free(res->res_dir.address);
if (pass == 2) {
switch (type) {
/* Resources not containing a resource */
+ case R_CONSOLE:
case R_DIRECTOR:
break;
}
switch (type) {
+ case R_CONSOLE:
+ size = sizeof(CONSRES);
+ break;
case R_DIRECTOR:
size = sizeof(DIRRES);
break;
/*
* Resource codes -- they must be sequential for indexing
*/
-#define R_FIRST 1001
+#define R_FIRST 1001
-#define R_DIRECTOR 1001
+#define R_CONSOLE 1001
+#define R_DIRECTOR 1002
-#define R_LAST R_DIRECTOR
+#define R_LAST R_DIRECTOR
/*
* Some resource attributes
*/
-#define R_NAME 1020
-#define R_ADDRESS 1021
-#define R_PASSWORD 1022
-#define R_TYPE 1023
-#define R_BACKUP 1024
+#define R_NAME 1020
+#define R_ADDRESS 1021
+#define R_PASSWORD 1022
+#define R_TYPE 1023
+#define R_BACKUP 1024
/* Definition of the contents of each Resource */
+
+/* Console "globals" */
+struct s_res_cons {
+ RES hdr;
+ char *rc_file; /* startup file */
+ char *hist_file; /* command history file */
+};
+typedef struct s_res_cons CONSRES;
+
+/* Director */
struct s_res_dir {
- RES hdr;
- int DIRport; /* UA server port */
- char *address; /* UA server address */
- char *password; /* UA server password */
+ RES hdr;
+ int DIRport; /* UA server port */
+ char *address; /* UA server address */
+ char *password; /* UA server password */
};
typedef struct s_res_dir DIRRES;
* resource structure definitions.
*/
union u_res {
- struct s_res_dir res_dir;
+ struct s_res_dir res_dir;
+ struct s_res_cons res_cons;
RES hdr;
};
/* Requests from the Storage daemon */
static char Find_media[] = "CatReq Job=%127s FindMedia=%d\n";
-static char Find_Vol_Info[] = "CatReq Job=%127s GetVolInfo VolName=%127s\n";
+static char Get_Vol_Info[] = "CatReq Job=%127s GetVolInfo VolName=%127s\n";
static char Update_media[] = "CatReq Job=%127s UpdateMedia VolName=%s\
VolJobs=%d VolFiles=%d VolBlocks=%d VolBytes=%" lld " VolMounts=%d\
/* Responses sent to Storage daemon */
static char OK_media[] = "1000 OK VolName=%s VolJobs=%d VolFiles=%d\
VolBlocks=%d VolBytes=%" lld " VolMounts=%d VolErrors=%d VolWrites=%d\
- VolMaxBytes=%" lld " VolCapacityBytes=%" lld "\n";
+ VolMaxBytes=%" lld " VolCapacityBytes=%" lld " VolStatus=%s\n";
static char OK_update[] = "1000 OK UpdateMedia\n";
jcr->MediaId = mr.MediaId;
Dmsg1(20, "Find_next_vol MediaId=%d\n", jcr->MediaId);
strcpy(jcr->VolumeName, mr.VolumeName);
-
ok = TRUE;
} else {
- /* See if we can create a new Volume */
- ok = newVolume(jcr);
+ /* Well, try finding recycled tapes */
+ strcpy(mr.VolStatus, "Recycle");
+ if (db_find_next_volume(jcr->db, index, &mr)) {
+ jcr->MediaId = mr.MediaId;
+ Dmsg1(20, "Find_next_vol MediaId=%d\n", jcr->MediaId);
+ strcpy(jcr->VolumeName, mr.VolumeName);
+ ok = TRUE;
+ } else {
+ /* See if we can create a new Volume */
+ ok = newVolume(jcr);
+ }
}
/*
* Send Find Media response to Storage daemon
bash_spaces(mr.VolumeName);
bnet_fsend(bs, OK_media, mr.VolumeName, mr.VolJobs,
mr.VolFiles, mr.VolBlocks, mr.VolBytes, mr.VolMounts, mr.VolErrors,
- mr.VolWrites, mr.VolMaxBytes, mr.VolCapacityBytes);
+ mr.VolWrites, mr.VolMaxBytes, mr.VolCapacityBytes,
+ mr.VolStatus);
} else {
bnet_fsend(bs, "1999 No Media\n");
}
/*
* Request to find specific volume information
*/
- } else if (sscanf(bs->msg, Find_Vol_Info, &Job, &mr.VolumeName) == 2) {
+ } else if (sscanf(bs->msg, Get_Vol_Info, &Job, &mr.VolumeName) == 2) {
Dmsg1(120, "CatReq GetVolInfo Vol=%s\n", mr.VolumeName);
/*
* Find the Volume
Dmsg1(20, "VolumeInfo MediaId=%d\n", jcr->MediaId);
strcpy(jcr->VolumeName, mr.VolumeName);
/*
- * Make sure this volume is suitable for this job
+ * Make sure this volume is suitable for this job, i.e.
+ * it is either Append or Recycle and Media Type matches.
*/
if (mr.PoolId == jcr->PoolId &&
- strcmp(mr.VolStatus, "Append") == 0 &&
+ (strcmp(mr.VolStatus, "Append") == 0 ||
+ strcmp(mr.VolStatus, "Recycle") == 0) &&
strcmp(mr.MediaType, jcr->store->media_type) == 0) {
/*
* Send Find Media response to Storage daemon
bash_spaces(mr.VolumeName);
bnet_fsend(bs, OK_media, mr.VolumeName, mr.VolJobs,
mr.VolFiles, mr.VolBlocks, mr.VolBytes, mr.VolMounts, mr.VolErrors,
- mr.VolWrites, mr.VolMaxBytes, mr.VolCapacityBytes);
+ mr.VolWrites, mr.VolMaxBytes, mr.VolCapacityBytes,
+ mr.VolStatus);
} else {
Dmsg4(000, "get_media_record PoolId=%d wanted %d, Status=%s, \
MediaType=%s\n", mr.PoolId, jcr->PoolId, mr.VolStatus, mr.MediaType);
} else {
lcase(lc->str);
for (i=0; joblevels[i].level_name; i++) {
- if (strcmp(lc->str, joblevels[i].level_name) == 0) {
+ if (strcasecmp(lc->str, joblevels[i].level_name) == 0) {
lrun.level = joblevels[i].level;
lrun.job_class = joblevels[i].job_class;
i = 0;
}
mr.MediaId = 0;
strcpy(mr.VolumeName, ua->cmd);
+ bsendmsg(ua, _("\nThis command will delete volume %s\n"
+ "and all Jobs saved on that volume from the Catalog\n"));
+
if (!get_cmd(ua, _("If you want to continue enter pretty please: "))) {
return 1;
}
bnet_fsend(sd, _("label %s VolumeName=%s PoolName=%s MediaType=%s"),
dev_name, mr.VolumeName, pr.Name, mr.MediaType);
bsendmsg(ua, "Sending label command ...\n");
- while (bnet_recv(sd) > 0) {
+ while (bget_msg(sd, 0) > 0) {
bsendmsg(ua, "%s", sd->msg);
if (strncmp(sd->msg, "3000 OK label.", 14) == 0) {
ok = TRUE;
close_db(ua);
return 0;
}
+ ua->jcr->db = ua->db;
Dmsg1(50, "DB %s opened\n", ua->catalog->db_name);
return 1;
}
db_close_database(ua->db);
}
ua->db = NULL;
+ ua->jcr->db = NULL;
}
extern void run_job(JCR *jcr);
/* Imported variables */
-extern struct s_jl joblevels[];
extern int r_first;
extern int r_last;
extern struct s_res resources[];
ua.UA_sock = NULL;
}
+ close_db(&ua); /* do this before freeing JCR */
+
if (ua.jcr) {
free_jcr(ua.jcr);
ua.jcr = NULL;
}
- close_db(&ua);
if (ua.prompt) {
free(ua.prompt);
}
struct stat statc; /* catalog stat */
int stat = JS_Terminated;
char buf[MAXSTRING];
+ char *fname = (char *)get_pool_memory(PM_MESSAGE);
memset(&fdbr, 0, sizeof(FILE_DBR));
fd = jcr->file_bsock;
char Opts_MD5[MAXSTRING]; /* Verify Opts or MD5 signature */
int do_MD5;
+ fname = (char *)check_pool_memory_size(fname, fd->msglen);
+ jcr->fname = (char *)check_pool_memory_size(fname, fd->msglen);
Dmsg1(50, "Atts+MD5=%s\n", fd->msg);
- if ((len = sscanf(fd->msg, "%ld %d %s %s", &file_index, &stream,
- Opts_MD5, jcr->fname)) != 4) {
+ if ((len = sscanf(fd->msg, "%ld %d %100s %s", &file_index, &stream,
+ Opts_MD5, fname)) != 4) {
Jmsg3(jcr, M_FATAL, 0, _("bird<filed: bad attributes, expected 4 fields got %d\n\
msglen=%d msg=%s\n"), len, fd->msglen, fd->msg);
jcr->JobStatus = JS_ErrorTerminated;
decode_stat(attr, &statf); /* decode file stat packet */
do_MD5 = FALSE;
jcr->fn_printed = FALSE;
+ strcpy(jcr->fname, fname); /* move filename into JCR */
Dmsg2(11, "dird<filed: stream=%d %s\n", stream, jcr->fname);
Dmsg1(20, "dird<filed: attr=%s\n", attr);
} else if (do_MD5) {
db_escape_string(buf, Opts_MD5, strlen(Opts_MD5));
if (strcmp(buf, fdbr.MD5) != 0) {
- /***FIXME**** fname may not be valid */
prt_fname(jcr);
if (debug_level >= 10) {
Jmsg(jcr, M_INFO, 0, _(" MD5 not same. File=%s Cat=%s\n"), buf, fdbr.MD5);
"AND File.FileIndex!=%d AND File.PathId=Path.PathId "
"AND File.FilenameId=Filename.FilenameId",
last_full_id, jcr->JobId);
+ /* missing_handler is called for each file found */
db_sql_query(jcr->db, buf, missing_handler, (void *)jcr);
if (jcr->fn_printed) {
stat = JS_Differences;
int token;
token = lex_get_token(lc);
- if (token != T_NUMBER) {
+ if (token != T_NUMBER || !is_a_number(lc->str)) {
scan_err1(lc, "expected an integer number, got: %s", lc->str);
} else {
errno = 0;
- *(int *)(item->value) = strtol(lc->str, NULL, 0);
+ *(int *)(item->value) = (int)strtod(lc->str, NULL);
if (errno != 0) {
scan_err1(lc, "expected an integer number, got: %s", lc->str);
}
int token;
token = lex_get_token(lc);
- if (token != T_NUMBER) {
- scan_err1(lc, "expected an integer number, got: %s", lc->str);
+ if (token != T_NUMBER || !is_a_number(lc->str)) {
+ scan_err1(lc, "expected a positive integer number, got: %s", lc->str);
} else {
errno = 0;
- token = strtol(lc->str, NULL, 0);
+ token = (int)strtod(lc->str, NULL);
if (errno != 0 || token < 0) {
scan_err1(lc, "expected a postive integer number, got: %s", lc->str);
}
int token;
token = lex_get_token(lc);
- if (token != T_NUMBER) {
+ Dmsg2(400, "int64=:%s: %f\n", lc->str, strtod(lc->str, NULL));
+ if (token != T_NUMBER || !is_a_number(lc->str)) {
scan_err1(lc, "expected an integer number, got: %s", lc->str);
} else {
errno = 0;
{
int token, i, ch;
uint64_t value;
- int mod[] = {'k', 'm', 'g'};
- uint64_t mult[] = {1024, /* kilobyte */
+ int mod[] = {'*', 'k', 'm', 'g', 0}; /* first item * not used */
+ uint64_t mult[] = {1, /* byte */
+ 1024, /* kilobyte */
1048576, /* megabyte */
1073741824}; /* gigabyte */
#ifdef we_have_a_compiler_that_works
- int mod[] = {'k', 'm', 'g', 't'};
- uint64_t mult[] = {1024, /* kilobyte */
+ int mod[] = {'*', 'k', 'm', 'g', 't', 0};
+ uint64_t mult[] = {1, /* byte */
+ 1024, /* kilobyte */
1048576, /* megabyte */
1073741824, /* gigabyte */
1099511627776};/* terabyte */
errno = 0;
switch (token) {
case T_NUMBER:
+ Dmsg2(400, "size num=:%s: %f\n", lc->str, strtod(lc->str, NULL));
value = (uint64_t)strtod(lc->str, NULL);
if (errno != 0 || token < 0) {
- scan_err1(lc, "expected a size, got: %s", lc->str);
+ scan_err1(lc, "expected a size number, got: %s", lc->str);
}
*(uint64_t *)(item->value) = value;
break;
if (ISUPPER(ch)) {
ch = tolower(ch);
}
- while (i < (int)sizeof(mod)) {
+ while (mod[++i] != 0) {
if (ch == mod[i]) {
lc->str_len--;
lc->str[lc->str_len] = 0; /* strip modifier */
break;
}
- i++;
}
}
- if (i >= (int)sizeof(mod)) {
- scan_err1(lc, "expected a size, got: %s", lc->str);
+ if (mod[i] == 0 || !is_a_number(lc->str)) {
+ scan_err1(lc, "expected a size number, got: %s", lc->str);
}
+ Dmsg3(400, "size str=:%s: %f i=%d\n", lc->str, strtod(lc->str, NULL), i);
+
value = (uint64_t)strtod(lc->str, NULL);
Dmsg1(400, "Int value = %d\n", (int)value);
if (errno != 0 || value < 0) {
- scan_err1(lc, "expected a size, got: %s", lc->str);
+ scan_err1(lc, "expected a size number, got: %s", lc->str);
}
- *(uint64_t *)(item->value) = (uint64_t)(strtod(lc->str, NULL) * mult[i]);
- Dmsg1(400, "Full value = %f\n", strtod(lc->str, NULL) * mult[i]);
+ *(uint64_t *)(item->value) = value * mult[i];
+ Dmsg2(400, "Full value = %f %" lld "\n", strtod(lc->str, NULL) * mult[i],
+ value *mult[i]);
break;
default:
scan_err1(lc, "expected a size, got: %s", lc->str);
void store_time(LEX *lc, struct res_items *item, int index, int pass)
{
int token, i, ch, value;
- int mod[] = {'s', 'm', 'h', 'd', 'w', 'o', 'q', 'y'};
+ int mod[] = {'*', 's', 'm', 'h', 'd', 'w', 'o', 'q', 'y', 0};
int mult[] = {1, 60, 60*60, 60*60*24, 60*60*24*7, 60*60*24*30,
60*60*24*91, 60*60*24*365};
errno = 0;
switch (token) {
case T_NUMBER:
- token = strtol(lc->str, NULL, 0);
+ token = (int)strtod(lc->str, NULL);
if (errno != 0 || token < 0) {
scan_err1(lc, "expected a time period, got: %s", lc->str);
}
if (ISUPPER(ch)) {
ch = tolower(ch);
}
- while (i < (int)sizeof(mod)) {
+ while (mod[++i] != 0) {
if (ch == mod[i]) {
+ lc->str_len--;
+ lc->str[lc->str_len] = 0; /* strip modifier */
break;
}
- i++;
}
}
- if (i >= (int)sizeof(mod)) {
+ if (mod[i] == 0 || !is_a_number(lc->str)) {
scan_err1(lc, "expected a time period, got: %s", lc->str);
}
- value = strtol(lc->str, NULL, 0);
+ value = (int)strtod(lc->str, NULL);
if (errno != 0 || value < 0) {
scan_err1(lc, "expected a time period, got: %s", lc->str);
}
char * edit_uint_with_commas __PROTO((uint64_t val, char *buf));
char * add_commas __PROTO((char *val, char *buf));
int do_shell_expansion(char *name);
+int is_a_number(const char *num);
+
+
/*
*void print_ls_output __PROTO((char *fname, char *lname, int type, struct stat *statp));
*/
*/
/*
- * Edit a number with commas, the supplied buffer
- * must be at least 27 bytes long.
+ * Check if specified string is a number or not.
+ * Taken from SQLite, cool, thanks.
+ */
+int is_a_number(const char *n)
+{
+ int digit_seen = 0;
+
+ if( *n == '-' || *n == '+' ) {
+ n++;
+ }
+ while (ISDIGIT(*n)) {
+ digit_seen = 1;
+ n++;
+ }
+ if (digit_seen && *n == '.') {
+ n++;
+ while (ISDIGIT(*n)) { n++; }
+ }
+ if (digit_seen && (*n == 'e' || *n == 'E')
+ && (ISDIGIT(n[1]) || ((n[1]=='-' || n[1] == '+') && ISDIGIT(n[2])))) {
+ n += 2; /* skip e- or e+ */
+ while (ISDIGIT(*n)) { n++; }
+ }
+ return digit_seen && *n==0;
+}
+
+
+/*
+ * Edit an integer number with commas, the supplied buffer
+ * must be at least 27 bytes long. The incoming number
+ * is always widened to 64 bits.
*/
char *edit_uint_with_commas(uint64_t val, char *buf)
{
return add_commas(buf, buf);
}
+/*
+ * Add commas to a string, which is presumably
+ * a number.
+ */
char *add_commas(char *val, char *buf)
{
int len, nc;
/* Convert a string in place to lower case */
-void
-lcase(char *str)
+void lcase(char *str)
{
while (*str) {
if (ISUPPER(*str))
/* Requests sent to the Director */
static char Find_media[] = "CatReq Job=%s FindMedia=%d\n";
-static char Find_Vol_Info[] = "CatReq Job=%s GetVolInfo VolName=%s\n";
+static char Get_Vol_Info[] = "CatReq Job=%s GetVolInfo VolName=%s\n";
static char Update_media[] = "CatReq Job=%s UpdateMedia VolName=%s\
VolJobs=%d VolFiles=%d VolBlocks=%d VolBytes=%" lld " VolMounts=%d\
/* Responses received from the Director */
static char OK_media[] = "1000 OK VolName=%127s VolJobs=%d VolFiles=%d\
VolBlocks=%d VolBytes=%" lld " VolMounts=%d VolErrors=%d VolWrites=%d\
- VolMaxBytes=%" lld " VolCapacityBytes=%" lld "\n";
+ VolMaxBytes=%" lld " VolCapacityBytes=%" lld " VolStatus=%20s\n";
static char OK_update[] = "1000 OK UpdateMedia\n";
&vol->VolCatBlocks, &vol->VolCatBytes,
&vol->VolCatMounts, &vol->VolCatErrors,
&vol->VolCatWrites, &vol->VolCatMaxBytes,
- &vol->VolCatCapacityBytes) != 10) {
+ &vol->VolCatCapacityBytes, vol->VolCatStatus) != 11) {
Dmsg1(30, "Bad response from Dir: %s\n", dir->msg);
return 0;
}
strcpy(jcr->VolumeName, vol->VolCatName); /* set desired VolumeName */
Dmsg1(200, "Got Volume=%s\n", vol->VolCatName);
- strcpy(vol->VolCatStatus, "Append");
return 1;
}
* Get Volume info for a specific volume from the Director's Database
*
* Returns: 1 on success (not Director guarantees that Pool and MediaType
- * are correct and VolStatus==Append)
+ * are correct and VolStatus==Append or
+ * VolStatus==Recycle)
* 0 on failure
*
* Volume information returned in jcr
strcpy(jcr->VolCatInfo.VolCatName, jcr->VolumeName);
Dmsg1(200, "dir_get_volume_info=%s\n", jcr->VolCatInfo.VolCatName);
bash_spaces(jcr->VolCatInfo.VolCatName);
- bnet_fsend(dir, Find_Vol_Info, jcr->Job, jcr->VolCatInfo.VolCatName);
+ bnet_fsend(dir, Get_Vol_Info, jcr->Job, jcr->VolCatInfo.VolCatName);
return do_request_volume_info(jcr);
}
uint32_t VolCatErrors; /* Number of errors this volume */
uint32_t VolCatWrites; /* Number of writes this volume */
uint32_t VolCatReads; /* Number of reads this volume */
+ uint32_t VolCatRecycles; /* Number of recycles this volume */
uint64_t VolCatMaxBytes; /* max bytes to write */
uint64_t VolCatCapacityBytes; /* capacity estimate */
char VolCatStatus[20]; /* Volume status */
static int ready_dev_for_append(JCR *jcr, DEVICE *dev, DEV_BLOCK *block)
{
int mounted = 0;
+ int recycle = 0;
Dmsg0(100, "Enter ready_dev_for_append\n");
case VOL_OK:
Dmsg1(200, "Vol OK name=%s\n", jcr->VolumeName);
memcpy(&dev->VolCatInfo, &jcr->VolCatInfo, sizeof(jcr->VolCatInfo));
+ if (strcmp(dev->VolCatInfo.VolCatStatus, "Recycle") == 0) {
+ recycle = 1;
+ }
break; /* got it */
case VOL_NAME_ERROR:
/* Check if we can accept this as an anonymous volume */
* be appended just after the block label. If we are writing
* an second volume, the calling routine will write the label
* before writing the overflow block.
+ *
+ * If the tape is marked as Recycle, we rewrite the label.
*/
- if (dev->VolHdr.LabelType == PRE_LABEL) { /* fresh tape */
+ if (dev->VolHdr.LabelType == PRE_LABEL || recycle) {
Dmsg1(90, "ready_for_append found freshly labeled volume. dev=%x\n", dev);
dev->VolHdr.LabelType = VOL_LABEL; /* set Volume label */
write_volume_label_to_block(jcr, dev, block);
write_volume_label_to_block(jcr, dev, block);
dev->VolCatInfo.VolCatJobs = 1;
dev->VolCatInfo.VolCatFiles = 1;
- dev->VolCatInfo.VolCatMounts = 1;
dev->VolCatInfo.VolCatErrors = 0;
- dev->VolCatInfo.VolCatWrites = 1;
dev->VolCatInfo.VolCatBlocks = 1;
+ if (recycle) {
+ dev->VolCatInfo.VolCatMounts++;
+ dev->VolCatInfo.VolCatRecycles++;
+ } else {
+ dev->VolCatInfo.VolCatMounts = 1;
+ dev->VolCatInfo.VolCatRecycles = 0;
+ dev->VolCatInfo.VolCatWrites = 1;
+ dev->VolCatInfo.VolCatReads = 1;
+ }
+ strcpy(dev->VolCatInfo.VolCatStatus, "Append");
dir_update_volume_info(jcr, &dev->VolCatInfo);
- Jmsg(jcr, M_INFO, 0, _("Wrote label to prelabeled Volume %s on device %s\n"),
- jcr->VolumeName, dev_name(dev));
+ if (recycle) {
+ Jmsg(jcr, M_INFO, 0, _("Recycled volume %s on device %s, all previous data lost.\n"),
+ jcr->VolumeName, dev_name(dev));
+ } else {
+ Jmsg(jcr, M_INFO, 0, _("Wrote label to prelabeled Volume %s on device %s\n"),
+ jcr->VolumeName, dev_name(dev));
+ }
} else {
/* OK, at this point, we have a valid Bacula label, but
static int mount_cmd(JCR *jcr);
static int unmount_cmd(JCR *jcr);
static int status_cmd(JCR *sjcr);
-static void label_device_if_ok(JCR *jcr, DEVICE *dev, char *vname, char *poolname);
+static void label_volume_if_ok(JCR *jcr, DEVICE *dev, char *vname, char *poolname);
struct s_cmds {
char *cmd;
if (open_dev(dev, volname, READ_WRITE) < 0) {
bnet_fsend(dir, _("3994 Connot open device: %s\n"), strerror_dev(dev));
} else {
- label_device_if_ok(jcr, dev, volname, poolname);
+ label_volume_if_ok(jcr, dev, volname, poolname);
force_close_dev(dev);
}
} else if (dev->dev_blocked &&
dev->dev_blocked != BST_DOING_ACQUIRE) { /* device blocked? */
- label_device_if_ok(jcr, dev, volname, poolname);
+ label_volume_if_ok(jcr, dev, volname, poolname);
} else if (dev->state & ST_READ || dev->num_writers) {
if (dev->state & ST_READ) {
bnet_fsend(dir, _("3901 Device %s is busy with 1 reader.\n"),
dev_name(dev), dev->num_writers);
}
} else { /* device not being used */
- label_device_if_ok(jcr, dev, volname, poolname);
+ label_volume_if_ok(jcr, dev, volname, poolname);
}
V(dev->mutex);
} else {
*
* Enter with the mutex set
*/
-static void label_device_if_ok(JCR *jcr, DEVICE *dev, char *vname, char *poolname)
+static void label_volume_if_ok(JCR *jcr, DEVICE *dev, char *vname, char *poolname)
{
BSOCK *dir = jcr->dir_bsock;
DEV_BLOCK *block;
/* */
#define VERSION "1.19"
#define VSTRING "1"
-#define DATE "24 April 2002"
-#define LSMDATE "24Apr02"
+#define DATE "25 April 2002"
+#define LSMDATE "25Apr02"
/* Debug flags */
#define DEBUG 1