From f73bf7171544dcc169859ca86a4a6abfb5a55bfc Mon Sep 17 00:00:00 2001 From: Kern Sibbald Date: Sun, 3 Dec 2006 09:00:00 +0000 Subject: [PATCH] kes Move unserial code in restore.c to a subroutine. Add a bit of debug code. kes Rework a bit of code in backup.c to handle sparse blocks correctly. The main problem was that signatures were being generated on blocks of zeros, which is unnecessary. 02Dec06 kes Fix scanner (next_arg) to handle leading double quote correctly. kes Modify cd command in restore tree to look at full argument without keywords. This fixes bug #716. 01Dec06 kes Do not update Migrated Job type if migration does not terminate normally. This fixes bug #719. git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@3734 91ce42f0-d328-0410-95d8-f526ca767f89 --- bacula/projects | 2 +- bacula/src/baconfig.h | 3 ++ bacula/src/dird/migrate.c | 6 +-- bacula/src/dird/ua_tree.c | 5 +- bacula/src/filed/backup.c | 97 +++++++++++++++++++-------------- bacula/src/filed/restore.c | 44 ++++++++------- bacula/src/lib/crypto.c | 3 +- bacula/src/lib/protos.h | 4 +- bacula/src/lib/scan.c | 107 +++++++++++++++++++++++-------------- bacula/src/lib/util.c | 11 ++-- bacula/src/version.h | 4 +- bacula/technotes-1.39 | 10 ++++ 12 files changed, 180 insertions(+), 116 deletions(-) diff --git a/bacula/projects b/bacula/projects index 070f416723..4dd5f76505 100644 --- a/bacula/projects +++ b/bacula/projects @@ -476,7 +476,7 @@ Item 13: Multiple threads in file daemon for the same job enable or disable this feature. The configuration option could specify the maximum number of threads in the file daemon. - If the theads could spool the data to separate spool files + If the threads could spool the data to separate spool files the restore process will not be much slower. Why: Multiple concurrent backups of a large fileserver with many diff --git a/bacula/src/baconfig.h b/bacula/src/baconfig.h index 67f88f498f..8b46f79e0b 100644 --- a/bacula/src/baconfig.h +++ b/bacula/src/baconfig.h @@ -277,6 +277,9 @@ void InitWinAPIWrapper(); /* Size of File Address stored in STREAM_SPARSE_DATA. Do NOT change! */ #define SPARSE_FADDR_SIZE (sizeof(uint64_t)) +/* Size of crypto length stored at head of crypto buffer. Do NOT change! */ +#define CRYPTO_LEN_SIZE ((int)sizeof(uint32_t)) + /* This is for dumb compilers/libraries like Solaris. Linux GCC * does it correctly, so it might be worthwhile diff --git a/bacula/src/dird/migrate.c b/bacula/src/dird/migrate.c index aa534382e0..9b1432ebc5 100644 --- a/bacula/src/dird/migrate.c +++ b/bacula/src/dird/migrate.c @@ -344,6 +344,7 @@ bool do_migration(JCR *jcr) if (jcr->JobStatus != JS_Terminated) { return false; } + migration_cleanup(jcr, jcr->JobStatus); if (mig_jcr) { UAContext *ua = new_ua_context(jcr); @@ -987,8 +988,6 @@ void migration_cleanup(JCR *jcr, int TermCode) mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId; set_jcr_job_status(mig_jcr, TermCode); - - update_job_end_record(mig_jcr); /* Update final items to set them to the previous job's values */ @@ -1066,7 +1065,8 @@ void migration_cleanup(JCR *jcr, int TermCode) break; } } else { - term_msg = _("%s -- no files to migrate"); + msg_type = M_ERROR; /* Generate error message */ + term_msg = _("*** %s Error ***"); } bsnprintf(term_code, sizeof(term_code), term_msg, "Migration"); diff --git a/bacula/src/dird/ua_tree.c b/bacula/src/dird/ua_tree.c index f1b4330309..8741537643 100644 --- a/bacula/src/dird/ua_tree.c +++ b/bacula/src/dird/ua_tree.c @@ -120,7 +120,7 @@ bool user_select_files_from_tree(TREE_CTX *tree) if (!get_cmd(ua, "$ ")) { break; } - parse_ua_args(ua); + parse_args_only(ua->cmd, &ua->args, &ua->argc, ua->argk, ua->argv, MAX_CMD_ARGS); if (ua->argc == 0) { bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n")); continue; @@ -661,10 +661,11 @@ static int cdcmd(UAContext *ua, TREE_CTX *tree) TREE_NODE *node; char cwd[2000]; + if (ua->argc != 2) { + bsendmsg(ua, _("Too many arguments. Try using double quotes.\n")); return 1; } - strip_leading_space(ua->argk[1]); node = tree_cwd(ua->argk[1], tree->root, tree->node); if (!node) { /* Try once more if Win32 drive -- make absolute */ diff --git a/bacula/src/filed/backup.c b/bacula/src/filed/backup.c index 13fe72799a..62ebcf6fcf 100644 --- a/bacula/src/filed/backup.c +++ b/bacula/src/filed/backup.c @@ -720,7 +720,7 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, * Read the file data */ while ((sd->msglen=(uint32_t)bread(&ff_pkt->bfd, rbuf, rsize)) > 0) { - int sparseBlock = 0; + bool sparseBlock = false; /* Check for sparse blocks */ if (ff_pkt->flags & FO_SPARSE) { @@ -735,10 +735,13 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, ser_begin(wbuf, SPARSE_FADDR_SIZE); ser_uint64(fileAddr); /* store fileAddr in begin of buffer */ } + fileAddr += sd->msglen; /* update file address */ + if (sparseBlock) { + continue; /* skip block of zeros */ + } } jcr->ReadBytes += sd->msglen; /* count bytes read */ - fileAddr += sd->msglen; /* Uncompressed cipher input length */ cipher_input_len = sd->msglen; @@ -755,7 +758,7 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, #ifdef HAVE_LIBZ /* Do compression if turned on */ - if (!sparseBlock && (ff_pkt->flags & FO_GZIP) && jcr->pZLIB_compress_workset) { + if (ff_pkt->flags & FO_GZIP && jcr->pZLIB_compress_workset) { Dmsg3(400, "cbuf=0x%x rbuf=0x%x len=%u\n", cbuf, rbuf, sd->msglen); ((z_stream*)jcr->pZLIB_compress_workset)->next_in = (Bytef *)rbuf; @@ -783,8 +786,20 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, cipher_input_len = compress_len; } #endif - - if (!sparseBlock && (ff_pkt->flags & FO_ENCRYPT)) { + /* + * Note, here we prepend the current record length to the beginning + * of the encrypted data. This is because both sparse and compression + * restore handling want records returned to them with exactly the + * same number of bytes that were processed in the backup handling. + * That is, both are block filters rather than a stream. When doing + * compression, the compression routines may buffer data, so that for + * any one record compressed, when it is decompressed the same size + * will not be obtained. Of course, the buffered data eventually comes + * out in subsequent crypto_cipher_update() calls or at least + * when crypto_cipher_finalize() is called. Unfortunately, this + * "feature" of encryption enormously complicates the restore code. + */ + if (ff_pkt->flags & FO_ENCRYPT) { uint32_t initial_len = 0; ser_declare; @@ -796,7 +811,8 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, uint8_t packet_len[sizeof(uint32_t)]; ser_begin(packet_len, sizeof(uint32_t)); - ser_uint32(cipher_input_len); /* store fileAddr in begin of buffer */ + ser_uint32(cipher_input_len); /* store data len in begin of buffer */ + Dmsg1(20, "Encrypt len=%d\n", cipher_input_len); if (!crypto_cipher_update(cipher_ctx, packet_len, sizeof(packet_len), (u_int8_t *)jcr->crypto_buf, &initial_len)) { @@ -823,16 +839,14 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, } /* Send the buffer to the Storage daemon */ - if (!sparseBlock) { - if (ff_pkt->flags & FO_SPARSE) { - sd->msglen += SPARSE_FADDR_SIZE; /* include fileAddr in size */ - } - sd->msg = wbuf; /* set correct write buffer */ - if (!bnet_send(sd)) { - Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), - bnet_strerror(sd)); - goto err; - } + if (ff_pkt->flags & FO_SPARSE) { + sd->msglen += SPARSE_FADDR_SIZE; /* include fileAddr in size */ + } + sd->msg = wbuf; /* set correct write buffer */ + if (!bnet_send(sd)) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + bnet_strerror(sd)); + goto err; } Dmsg1(130, "Send data to SD len=%d\n", sd->msglen); /* #endif */ @@ -841,37 +855,38 @@ int send_data(JCR *jcr, int stream, FF_PKT *ff_pkt, DIGEST *digest, } /* end while read file data */ - /* Send any remaining encrypted data + padding */ - if (sd->msglen >= 0) { - if (ff_pkt->flags & FO_ENCRYPT) { - if (!crypto_cipher_finalize(cipher_ctx, (uint8_t *)jcr->crypto_buf, - &encrypted_len)) { - /* Padding failed. Shouldn't happen. */ - Jmsg(jcr, M_FATAL, 0, _("Encryption padding error\n")); - goto err; - } - - if (encrypted_len > 0) { - sd->msglen = encrypted_len; /* set encrypted length */ - - sd->msg = jcr->crypto_buf; /* set correct write buffer */ - if (!bnet_send(sd)) { - Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), - bnet_strerror(sd)); - goto err; - } - Dmsg1(130, "Send data to SD len=%d\n", sd->msglen); - jcr->JobBytes += sd->msglen; /* count bytes saved possibly compressed/encrypted */ - sd->msg = msgsave; /* restore bnet buffer */ - } - } - } else { + if (sd->msglen < 0) { /* error */ berrno be; Jmsg(jcr, M_ERROR, 0, _("Read error on file %s. ERR=%s\n"), ff_pkt->fname, be.strerror(ff_pkt->bfd.berrno)); if (jcr->Errors++ > 1000) { /* insanity check */ Jmsg(jcr, M_FATAL, 0, _("Too many errors.\n")); } + } else if (ff_pkt->flags & FO_ENCRYPT) { + /* + * For encryption, we must call finalize to push out any + * buffered data. + */ + if (!crypto_cipher_finalize(cipher_ctx, (uint8_t *)jcr->crypto_buf, + &encrypted_len)) { + /* Padding failed. Shouldn't happen. */ + Jmsg(jcr, M_FATAL, 0, _("Encryption padding error\n")); + goto err; + } + + /* Note, on SSL pre-0.9.7, there is always some output */ + if (encrypted_len > 0) { + sd->msglen = encrypted_len; /* set encrypted length */ + sd->msg = jcr->crypto_buf; /* set correct write buffer */ + if (!bnet_send(sd)) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + bnet_strerror(sd)); + goto err; + } + Dmsg1(130, "Send data to SD len=%d\n", sd->msglen); + jcr->JobBytes += sd->msglen; /* count bytes saved possibly compressed/encrypted */ + sd->msg = msgsave; /* restore bnet buffer */ + } } if (!bnet_sig(sd, BNET_EOD)) { /* indicate end of file data */ diff --git a/bacula/src/filed/restore.c b/bacula/src/filed/restore.c index 880890ad36..97383d6325 100644 --- a/bacula/src/filed/restore.c +++ b/bacula/src/filed/restore.c @@ -781,6 +781,16 @@ bool decompress_data(JCR *jcr, char **data, uint32_t *length) #endif } +static void unser_crypto_size(JCR *jcr) +{ + unser_declare; + if (jcr->crypto_size == 0 && jcr->crypto_count >= CRYPTO_LEN_SIZE) { + unser_begin(&jcr->crypto_buf[0], CRYPTO_LEN_SIZE); + unser_uint32(jcr->crypto_size); + jcr->crypto_size += CRYPTO_LEN_SIZE; + } +} + bool store_data(JCR *jcr, BFILE *bfd, char *data, const int32_t length, bool win32_decomp) { if (win32_decomp) { @@ -822,7 +832,6 @@ int32_t extract_data(JCR *jcr, BFILE *bfd, POOLMEM *buf, int32_t buflen, if (flags & FO_ENCRYPT) { ASSERT(cipher); - unser_declare; while (jcr->crypto_size > 0 && jcr->crypto_count > 0 && wsize > 0) { uint32_t chunk_size = 16; @@ -854,9 +863,8 @@ int32_t extract_data(JCR *jcr, BFILE *bfd, POOLMEM *buf, int32_t buflen, wsize -= chunk_size; if (jcr->crypto_count >= jcr->crypto_size) { - - char *packet = &jcr->crypto_buf[4]; /* Decrypted, possibly decompressed output here. */ - uint32_t packet_size = jcr->crypto_size - 4; + char *packet = &jcr->crypto_buf[CRYPTO_LEN_SIZE]; /* Decrypted, possibly decompressed output here. */ + uint32_t packet_size = jcr->crypto_size - CRYPTO_LEN_SIZE; if (flags & FO_GZIP) { if (!decompress_data(jcr, &packet, &packet_size)) { @@ -906,18 +914,15 @@ int32_t extract_data(JCR *jcr, BFILE *bfd, POOLMEM *buf, int32_t buflen, jcr->crypto_count += decrypted_len; - if (jcr->crypto_size == 0 && jcr->crypto_count >= 4) { - unser_begin(&jcr->crypto_buf[0], sizeof(uint32_t)); - unser_uint32(jcr->crypto_size); - jcr->crypto_size += 4; - } + unser_crypto_size(jcr); + wsize = jcr->crypto_size - CRYPTO_LEN_SIZE; + Dmsg1(10, "Decrypt size=%d\n", wsize); + wbuf = &jcr->crypto_buf[CRYPTO_LEN_SIZE]; /* Decrypted, possibly decompressed output here. */ if (jcr->crypto_size == 0 || jcr->crypto_count < jcr->crypto_size) { return 0; } - wsize = jcr->crypto_size - 4; - wbuf = &jcr->crypto_buf[4]; /* Decrypted, possibly decompressed output here. */ } if (flags & FO_SPARSE) { @@ -971,9 +976,11 @@ bool flush_cipher(JCR *jcr, BFILE *bfd, int flags, CIPHER_CONTEXT *cipher, uint3 char ec1[50]; /* Buffer printing huge values */ /* Write out the remaining block and free the cipher context */ - jcr->crypto_buf = check_pool_memory_size(jcr->crypto_buf, jcr->crypto_count + cipher_block_size); + jcr->crypto_buf = check_pool_memory_size(jcr->crypto_buf, jcr->crypto_count + + cipher_block_size); - if (!crypto_cipher_finalize(cipher, (uint8_t *)&jcr->crypto_buf[jcr->crypto_count], &decrypted_len)) { + if (!crypto_cipher_finalize(cipher, (uint8_t *)&jcr->crypto_buf[jcr->crypto_count], + &decrypted_len)) { /* Writing out the final, buffered block failed. Shouldn't happen. */ Jmsg1(jcr, M_FATAL, 0, _("Decryption error for %s\n"), jcr->last_fname); } @@ -985,16 +992,13 @@ bool flush_cipher(JCR *jcr, BFILE *bfd, int flags, CIPHER_CONTEXT *cipher, uint3 jcr->crypto_count += decrypted_len; - if (jcr->crypto_size == 0) { - ASSERT(jcr->crypto_count >= 4); - jcr->crypto_size = ntohl(*(uint32_t *)&jcr->crypto_buf[0]) + 4; - } + unser_crypto_size(jcr); + wsize = jcr->crypto_size - CRYPTO_LEN_SIZE; + Dmsg1(10, "Unser size=%d\n", wsize); + wbuf = &jcr->crypto_buf[CRYPTO_LEN_SIZE]; /* Decrypted, possibly decompressed output here. */ ASSERT(jcr->crypto_count == jcr->crypto_size); - wbuf = &jcr->crypto_buf[4]; - wsize = jcr->crypto_size - 4; - if (flags & FO_GZIP) { decompress_data(jcr, &wbuf, &wsize); } else { diff --git a/bacula/src/lib/crypto.c b/bacula/src/lib/crypto.c index f1ad9c816f..5ae8c2e1df 100644 --- a/bacula/src/lib/crypto.c +++ b/bacula/src/lib/crypto.c @@ -653,7 +653,8 @@ bool crypto_digest_update(DIGEST *digest, const uint8_t *data, uint32_t length) * Returns: true on success * false on failure */ -bool crypto_digest_finalize (DIGEST *digest, uint8_t *dest, uint32_t *length) { +bool crypto_digest_finalize (DIGEST *digest, uint8_t *dest, uint32_t *length) +{ if (!EVP_DigestFinal(&digest->ctx, dest, (unsigned int *)length)) { return false; } else { diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index 5b2d8d4e0a..863b8539e0 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -264,6 +264,8 @@ int fstrsch (const char *a, const char *b); char *next_arg(char **s); int parse_args(POOLMEM *cmd, POOLMEM **args, int *argc, char **argk, char **argv, int max_args); +int parse_args_only(POOLMEM *cmd, POOLMEM **args, int *argc, + char **argk, char **argv, int max_args); void split_path_and_filename(const char *fname, POOLMEM **path, int *pnl, POOLMEM **file, int *fnl); int bsscanf(const char *buf, const char *fmt, ...); @@ -295,7 +297,7 @@ int tls_bsock_readn (BSOCK *bsock, char *ptr, int32_t nbyte /* util.c */ -int is_buf_zero (char *buf, int len); +bool is_buf_zero (char *buf, int len); void lcase (char *str); void bash_spaces (char *str); void bash_spaces (POOL_MEM &pm); diff --git a/bacula/src/lib/scan.c b/bacula/src/lib/scan.c index bbbe9913b3..3199fd14b7 100644 --- a/bacula/src/lib/scan.c +++ b/bacula/src/lib/scan.c @@ -154,7 +154,18 @@ fstrsch(const char *a, const char *b) /* folded case search */ /* * Return next argument from command line. Note, this - * routine is destructive. + * routine is destructive because it stored 0 at the end + * of each argument. + * Called with pointer to pointer to command line. This + * pointer is updated to point to the remainder of the + * command line. + * + * Returns pointer to next argument -- don't store the result + * in the pointer you passed as an argument ... + * The next argument is terminated by a space unless within + * quotes. Double quote characters (unless preceded by a \) are + * stripped. + * */ char *next_arg(char **s) { @@ -167,8 +178,8 @@ char *next_arg(char **s) } Dmsg1(900, "Next arg=%s\n", p); for (n = q = p; *p ; ) { - if (*p == '\\') { - p++; + if (*p == '\\') { /* slash? */ + p++; /* yes, skip it */ if (*p) { *q++ = *p++; } else { @@ -177,16 +188,11 @@ char *next_arg(char **s) continue; } if (*p == '"') { /* start or end of quote */ - if (in_quote) { - p++; /* skip quote */ - in_quote = false; - continue; - } - in_quote = true; p++; + in_quote = !in_quote; /* change state */ continue; } - if (!in_quote && B_ISSPACE(*p)) { /* end of field */ + if (!in_quote && B_ISSPACE(*p)) { /* end of field */ p++; break; } @@ -201,7 +207,7 @@ char *next_arg(char **s) /* * This routine parses the input command line. * It makes a copy in args, then builds an - * argc, argv like list where + * argc, argk, argv list where: * * argc = count of arguments * argk[i] = argument keyword (part preceding =) @@ -217,49 +223,25 @@ char *next_arg(char **s) * argk[2] = arg3 * argv[2] = */ - int parse_args(POOLMEM *cmd, POOLMEM **args, int *argc, char **argk, char **argv, int max_args) { - char *p, *q, *n; + char *p; + + parse_args_only(cmd, args, argc, argk, argv, max_args); - pm_strcpy(args, cmd); - strip_trailing_junk(*args); - p = *args; - *argc = 0; - /* Pick up all arguments */ - while (*argc < max_args) { - n = next_arg(&p); - if (*n) { - argk[*argc] = n; - argv[(*argc)++] = NULL; - } else { - break; - } - } /* Separate keyword and value */ for (int i=0; i < *argc; i++) { p = strchr(argk[i], '='); if (p) { *p++ = 0; /* terminate keyword and point to value */ - /* Unquote quoted values */ - if (*p == '"') { - for (n = q = ++p; *p && *p != '"'; ) { - if (*p == '\\') { - p++; - } - *q++ = *p++; - } - *q = 0; /* terminate string */ - p = n; /* point to string */ - } if (strlen(p) > MAX_NAME_LENGTH-1) { p[MAX_NAME_LENGTH-1] = 0; /* truncate to max len */ } } argv[i] = p; /* save ptr to value or NULL */ } -#ifdef xxxx +#ifdef xxx_debug for (int i=0; i < *argc; i++) { Pmsg3(000, "Arg %d: kw=%s val=%s\n", i, argk[i], argv[i]?argv[i]:"NULL"); } @@ -267,6 +249,53 @@ int parse_args(POOLMEM *cmd, POOLMEM **args, int *argc, return 1; } + +/* + * This routine parses the input command line. + * It makes a copy in args, then builds an + * argc, argk, but no argv (values). + * This routine is useful for scanning command lines where the data + * is a filename and no keywords are expected. If we scan a filename + * for keywords, any = in the filename will be interpreted as the + * end of a keyword, and this is not good. + * + * argc = count of arguments + * argk[i] = argument keyword (part preceding =) + * argv[i] = NULL + * + * example: arg1 arg2=abc arg3= + * + * argc = c + * argk[0] = arg1 + * argv[0] = NULL + * argk[1] = arg2=abc + * argv[1] = NULL + * argk[2] = arg3 + * argv[2] = + */ +int parse_args_only(POOLMEM *cmd, POOLMEM **args, int *argc, + char **argk, char **argv, int max_args) +{ + char *p, *n; + + pm_strcpy(args, cmd); + strip_trailing_junk(*args); + p = *args; + *argc = 0; + /* Pick up all arguments */ + while (*argc < max_args) { + n = next_arg(&p); + if (*n) { + argk[*argc] = n; + argv[(*argc)++] = NULL; + } else { + break; + } + } + return 1; +} + + /* * Given a full filename, split it into its path * and filename parts. They are returned in pool memory diff --git a/bacula/src/lib/util.c b/bacula/src/lib/util.c index 0519cb5d53..b457f0a396 100644 --- a/bacula/src/lib/util.c +++ b/bacula/src/lib/util.c @@ -43,21 +43,21 @@ */ /* Return true of buffer has all zero bytes */ -int is_buf_zero(char *buf, int len) +bool is_buf_zero(char *buf, int len) { uint64_t *ip; char *p; int i, len64, done, rem; if (buf[0] != 0) { - return 0; + return false; } ip = (uint64_t *)buf; /* Optimize by checking uint64_t for zero */ len64 = len / sizeof(uint64_t); for (i=0; i < len64; i++) { if (ip[i] != 0) { - return 0; + return false; } } done = len64 * sizeof(uint64_t); /* bytes already checked */ @@ -65,10 +65,10 @@ int is_buf_zero(char *buf, int len) rem = len - done; for (i = 0; i < rem; i++) { if (p[i] != 0) { - return 0; + return false; } } - return 1; + return true; } @@ -712,4 +712,3 @@ const char *last_path_separator(const char *str) } return NULL; } - diff --git a/bacula/src/version.h b/bacula/src/version.h index 172bb9748a..5c25a18f12 100644 --- a/bacula/src/version.h +++ b/bacula/src/version.h @@ -4,8 +4,8 @@ #undef VERSION #define VERSION "1.39.29" -#define BDATE "01 December 2006" -#define LSMDATE "01Dec06" +#define BDATE "02 December 2006" +#define LSMDATE "02Dec06" #define PROG_COPYRIGHT "Copyright (C) %d-2006 Free Software Foundation Europe e.V.\n" #define BYEAR "2006" /* year for copyright messages in progs */ diff --git a/bacula/technotes-1.39 b/bacula/technotes-1.39 index a03e20e789..3389cd0e6a 100644 --- a/bacula/technotes-1.39 +++ b/bacula/technotes-1.39 @@ -1,6 +1,16 @@ Technical notes on version 1.39 General: +03Dec06 +kes Move unserial code in restore.c to a subroutine. Add a bit of debug + code. +kes Rework a bit of code in backup.c to handle sparse blocks correctly. + The main problem was that signatures were being generated on blocks + of zeros, which is unnecessary. +02Dec06 +kes Fix scanner (next_arg) to handle leading double quote correctly. +kes Modify cd command in restore tree to look at full argument without + keywords. This fixes bug #716. 01Dec06 kes Do not update Migrated Job type if migration does not terminate normally. This fixes bug #719. -- 2.39.5