File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / rsync / match.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Fri Feb 17 15:09:30 2012 UTC (12 years, 4 months ago) by misho
Branches: rsync, MAIN
CVS tags: rsync3_0_9p0, RSYNC3_0_9, HEAD
rsync

    1: /*
    2:  * Block matching used by the file-transfer code.
    3:  *
    4:  * Copyright (C) 1996 Andrew Tridgell
    5:  * Copyright (C) 1996 Paul Mackerras
    6:  * Copyright (C) 2003-2009 Wayne Davison
    7:  *
    8:  * This program is free software; you can redistribute it and/or modify
    9:  * it under the terms of the GNU General Public License as published by
   10:  * the Free Software Foundation; either version 3 of the License, or
   11:  * (at your option) any later version.
   12:  *
   13:  * This program is distributed in the hope that it will be useful,
   14:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   15:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16:  * GNU General Public License for more details.
   17:  *
   18:  * You should have received a copy of the GNU General Public License along
   19:  * with this program; if not, visit the http://fsf.org website.
   20:  */
   21: 
   22: #include "rsync.h"
   23: 
   24: extern int verbose;
   25: extern int do_progress;
   26: extern int checksum_seed;
   27: extern int append_mode;
   28: 
   29: int updating_basis_file;
   30: 
   31: static int false_alarms;
   32: static int hash_hits;
   33: static int matches;
   34: static int64 data_transfer;
   35: 
   36: static int total_false_alarms;
   37: static int total_hash_hits;
   38: static int total_matches;
   39: 
   40: extern struct stats stats;
   41: 
   42: #define TRADITIONAL_TABLESIZE (1<<16)
   43: 
   44: static uint32 tablesize;
   45: static int32 *hash_table;
   46: 
   47: #define SUM2HASH2(s1,s2) (((s1) + (s2)) & 0xFFFF)
   48: #define SUM2HASH(sum) SUM2HASH2((sum)&0xFFFF,(sum)>>16)
   49: 
   50: #define BIG_SUM2HASH(sum) ((sum)%tablesize)
   51: 
   52: static void build_hash_table(struct sum_struct *s)
   53: {
   54: 	static uint32 alloc_size;
   55: 	int32 i;
   56: 
   57: 	/* Dynamically calculate the hash table size so that the hash load
   58: 	 * for big files is about 80%.  A number greater than the traditional
   59: 	 * size must be odd or s2 will not be able to span the entire set. */
   60: 	tablesize = (uint32)(s->count/8) * 10 + 11;
   61: 	if (tablesize < TRADITIONAL_TABLESIZE)
   62: 		tablesize = TRADITIONAL_TABLESIZE;
   63: 	if (tablesize > alloc_size || tablesize < alloc_size - 16*1024) {
   64: 		if (hash_table)
   65: 			free(hash_table);
   66: 		hash_table = new_array(int32, tablesize);
   67: 		if (!hash_table)
   68: 			out_of_memory("build_hash_table");
   69: 		alloc_size = tablesize;
   70: 	}
   71: 
   72: 	memset(hash_table, 0xFF, tablesize * sizeof hash_table[0]);
   73: 
   74: 	if (tablesize == TRADITIONAL_TABLESIZE) {
   75: 		for (i = 0; i < s->count; i++) {
   76: 			uint32 t = SUM2HASH(s->sums[i].sum1);
   77: 			s->sums[i].chain = hash_table[t];
   78: 			hash_table[t] = i;
   79: 		}
   80: 	} else {
   81: 		for (i = 0; i < s->count; i++) {
   82: 			uint32 t = BIG_SUM2HASH(s->sums[i].sum1);
   83: 			s->sums[i].chain = hash_table[t];
   84: 			hash_table[t] = i;
   85: 		}
   86: 	}
   87: }
   88: 
   89: 
   90: static OFF_T last_match;
   91: 
   92: 
   93: /* Transmit a literal and/or match token.
   94:  *
   95:  * This delightfully-named function is called either when we find a
   96:  * match and need to transmit all the unmatched data leading up to it,
   97:  * or when we get bored of accumulating literal data and just need to
   98:  * transmit it.  As a result of this second case, it is called even if
   99:  * we have not matched at all!
  100:  *
  101:  * If i >= 0, the number of a matched token.  If < 0, indicates we have
  102:  * only literal data.  A -1 will send a 0-token-int too, and a -2 sends
  103:  * only literal data, w/o any token-int. */
  104: static void matched(int f, struct sum_struct *s, struct map_struct *buf,
  105: 		    OFF_T offset, int32 i)
  106: {
  107: 	int32 n = (int32)(offset - last_match); /* max value: block_size (int32) */
  108: 	int32 j;
  109: 
  110: 	if (verbose > 2 && i >= 0) {
  111: 		rprintf(FINFO,
  112: 			"match at %.0f last_match=%.0f j=%d len=%ld n=%ld\n",
  113: 			(double)offset, (double)last_match, i,
  114: 			(long)s->sums[i].len, (long)n);
  115: 	}
  116: 
  117: 	send_token(f, i, buf, last_match, n, i < 0 ? 0 : s->sums[i].len);
  118: 	data_transfer += n;
  119: 
  120: 	if (i >= 0) {
  121: 		stats.matched_data += s->sums[i].len;
  122: 		n += s->sums[i].len;
  123: 	}
  124: 
  125: 	for (j = 0; j < n; j += CHUNK_SIZE) {
  126: 		int32 n1 = MIN(CHUNK_SIZE, n - j);
  127: 		sum_update(map_ptr(buf, last_match + j, n1), n1);
  128: 	}
  129: 
  130: 	if (i >= 0)
  131: 		last_match = offset + s->sums[i].len;
  132: 	else
  133: 		last_match = offset;
  134: 
  135: 	if (buf && do_progress)
  136: 		show_progress(last_match, buf->file_size);
  137: }
  138: 
  139: 
  140: static void hash_search(int f,struct sum_struct *s,
  141: 			struct map_struct *buf, OFF_T len)
  142: {
  143: 	OFF_T offset, aligned_offset, end;
  144: 	int32 k, want_i, aligned_i, backup;
  145: 	char sum2[SUM_LENGTH];
  146: 	uint32 s1, s2, sum;
  147: 	int more;
  148: 	schar *map;
  149: 
  150: 	/* want_i is used to encourage adjacent matches, allowing the RLL
  151: 	 * coding of the output to work more efficiently. */
  152: 	want_i = 0;
  153: 
  154: 	if (verbose > 2) {
  155: 		rprintf(FINFO, "hash search b=%ld len=%.0f\n",
  156: 			(long)s->blength, (double)len);
  157: 	}
  158: 
  159: 	k = (int32)MIN(len, (OFF_T)s->blength);
  160: 
  161: 	map = (schar *)map_ptr(buf, 0, k);
  162: 
  163: 	sum = get_checksum1((char *)map, k);
  164: 	s1 = sum & 0xFFFF;
  165: 	s2 = sum >> 16;
  166: 	if (verbose > 3)
  167: 		rprintf(FINFO, "sum=%.8x k=%ld\n", sum, (long)k);
  168: 
  169: 	offset = aligned_offset = aligned_i = 0;
  170: 
  171: 	end = len + 1 - s->sums[s->count-1].len;
  172: 
  173: 	if (verbose > 3) {
  174: 		rprintf(FINFO, "hash search s->blength=%ld len=%.0f count=%.0f\n",
  175: 			(long)s->blength, (double)len, (double)s->count);
  176: 	}
  177: 
  178: 	do {
  179: 		int done_csum2 = 0;
  180: 		int32 i;
  181: 
  182: 		if (verbose > 4) {
  183: 			rprintf(FINFO, "offset=%.0f sum=%04x%04x\n",
  184: 				(double)offset, s2 & 0xFFFF, s1 & 0xFFFF);
  185: 		}
  186: 
  187: 		if (tablesize == TRADITIONAL_TABLESIZE) {
  188: 			if ((i = hash_table[SUM2HASH2(s1,s2)]) < 0)
  189: 				goto null_hash;
  190: 			sum = (s1 & 0xffff) | (s2 << 16);
  191: 		} else {
  192: 			sum = (s1 & 0xffff) | (s2 << 16);
  193: 			if ((i = hash_table[BIG_SUM2HASH(sum)]) < 0)
  194: 				goto null_hash;
  195: 		}
  196: 
  197: 		hash_hits++;
  198: 		do {
  199: 			int32 l;
  200: 
  201: 			if (sum != s->sums[i].sum1)
  202: 				continue;
  203: 
  204: 			/* also make sure the two blocks are the same length */
  205: 			l = (int32)MIN((OFF_T)s->blength, len-offset);
  206: 			if (l != s->sums[i].len)
  207: 				continue;
  208: 
  209: 			/* in-place: ensure chunk's offset is either >= our
  210: 			 * offset or that the data didn't move. */
  211: 			if (updating_basis_file && s->sums[i].offset < offset
  212: 			    && !(s->sums[i].flags & SUMFLG_SAME_OFFSET))
  213: 				continue;
  214: 
  215: 			if (verbose > 3) {
  216: 				rprintf(FINFO,
  217: 					"potential match at %.0f i=%ld sum=%08x\n",
  218: 					(double)offset, (long)i, sum);
  219: 			}
  220: 
  221: 			if (!done_csum2) {
  222: 				map = (schar *)map_ptr(buf,offset,l);
  223: 				get_checksum2((char *)map,l,sum2);
  224: 				done_csum2 = 1;
  225: 			}
  226: 
  227: 			if (memcmp(sum2,s->sums[i].sum2,s->s2length) != 0) {
  228: 				false_alarms++;
  229: 				continue;
  230: 			}
  231: 
  232: 			/* When updating in-place, the best possible match is
  233: 			 * one with an identical offset, so we prefer that over
  234: 			 * the adjacent want_i optimization. */
  235: 			if (updating_basis_file) {
  236: 				/* All the generator's chunks start at blength boundaries. */
  237: 				while (aligned_offset < offset) {
  238: 					aligned_offset += s->blength;
  239: 					aligned_i++;
  240: 				}
  241: 				if (offset == aligned_offset && aligned_i < s->count) {
  242: 					if (i != aligned_i) {
  243: 						if (sum != s->sums[aligned_i].sum1
  244: 						 || l != s->sums[aligned_i].len
  245: 						 || memcmp(sum2, s->sums[aligned_i].sum2, s->s2length) != 0)
  246: 							goto check_want_i;
  247: 						i = aligned_i;
  248: 					}
  249: 					/* This identical chunk is in the same spot in the old and new file. */
  250: 					s->sums[i].flags |= SUMFLG_SAME_OFFSET;
  251: 					want_i = i;
  252: 				}
  253: 			}
  254: 
  255: 		  check_want_i:
  256: 			/* we've found a match, but now check to see
  257: 			 * if want_i can hint at a better match. */
  258: 			if (i != want_i && want_i < s->count
  259: 			    && (!updating_basis_file || s->sums[want_i].offset >= offset
  260: 			     || s->sums[want_i].flags & SUMFLG_SAME_OFFSET)
  261: 			    && sum == s->sums[want_i].sum1
  262: 			    && memcmp(sum2, s->sums[want_i].sum2, s->s2length) == 0) {
  263: 				/* we've found an adjacent match - the RLL coder
  264: 				 * will be happy */
  265: 				i = want_i;
  266: 			}
  267: 			want_i = i + 1;
  268: 
  269: 			matched(f,s,buf,offset,i);
  270: 			offset += s->sums[i].len - 1;
  271: 			k = (int32)MIN((OFF_T)s->blength, len-offset);
  272: 			map = (schar *)map_ptr(buf, offset, k);
  273: 			sum = get_checksum1((char *)map, k);
  274: 			s1 = sum & 0xFFFF;
  275: 			s2 = sum >> 16;
  276: 			matches++;
  277: 			break;
  278: 		} while ((i = s->sums[i].chain) >= 0);
  279: 
  280: 	  null_hash:
  281: 		backup = (int32)(offset - last_match);
  282: 		/* We sometimes read 1 byte prior to last_match... */
  283: 		if (backup < 0)
  284: 			backup = 0;
  285: 
  286: 		/* Trim off the first byte from the checksum */
  287: 		more = offset + k < len;
  288: 		map = (schar *)map_ptr(buf, offset - backup, k + more + backup)
  289: 		    + backup;
  290: 		s1 -= map[0] + CHAR_OFFSET;
  291: 		s2 -= k * (map[0]+CHAR_OFFSET);
  292: 
  293: 		/* Add on the next byte (if there is one) to the checksum */
  294: 		if (more) {
  295: 			s1 += map[k] + CHAR_OFFSET;
  296: 			s2 += s1;
  297: 		} else
  298: 			--k;
  299: 
  300: 		/* By matching early we avoid re-reading the
  301: 		   data 3 times in the case where a token
  302: 		   match comes a long way after last
  303: 		   match. The 3 reads are caused by the
  304: 		   running match, the checksum update and the
  305: 		   literal send. */
  306: 		if (backup >= s->blength+CHUNK_SIZE && end-offset > CHUNK_SIZE)
  307: 			matched(f, s, buf, offset - s->blength, -2);
  308: 	} while (++offset < end);
  309: 
  310: 	matched(f, s, buf, len, -1);
  311: 	map_ptr(buf, len-1, 1);
  312: }
  313: 
  314: 
  315: /**
  316:  * Scan through a origin file, looking for sections that match
  317:  * checksums from the generator, and transmit either literal or token
  318:  * data.
  319:  *
  320:  * Also calculates the MD4 checksum of the whole file, using the md
  321:  * accumulator.  This is transmitted with the file as protection
  322:  * against corruption on the wire.
  323:  *
  324:  * @param s Checksums received from the generator.  If <tt>s->count ==
  325:  * 0</tt>, then there are actually no checksums for this file.
  326:  *
  327:  * @param len Length of the file to send.
  328:  **/
  329: void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
  330: {
  331: 	char file_sum[MAX_DIGEST_LEN];
  332: 	int sum_len;
  333: 
  334: 	last_match = 0;
  335: 	false_alarms = 0;
  336: 	hash_hits = 0;
  337: 	matches = 0;
  338: 	data_transfer = 0;
  339: 
  340: 	sum_init(checksum_seed);
  341: 
  342: 	if (append_mode > 0) {
  343: 		if (append_mode == 2) {
  344: 			OFF_T j = 0;
  345: 			for (j = CHUNK_SIZE; j < s->flength; j += CHUNK_SIZE) {
  346: 				if (buf && do_progress)
  347: 					show_progress(last_match, buf->file_size);
  348: 				sum_update(map_ptr(buf, last_match, CHUNK_SIZE),
  349: 					   CHUNK_SIZE);
  350: 				last_match = j;
  351: 			}
  352: 			if (last_match < s->flength) {
  353: 				int32 n = (int32)(s->flength - last_match);
  354: 				if (buf && do_progress)
  355: 					show_progress(last_match, buf->file_size);
  356: 				sum_update(map_ptr(buf, last_match, n), n);
  357: 			}
  358: 		}
  359: 		last_match = s->flength;
  360: 		s->count = 0;
  361: 	}
  362: 
  363: 	if (len > 0 && s->count > 0) {
  364: 		build_hash_table(s);
  365: 
  366: 		if (verbose > 2)
  367: 			rprintf(FINFO,"built hash table\n");
  368: 
  369: 		hash_search(f, s, buf, len);
  370: 
  371: 		if (verbose > 2)
  372: 			rprintf(FINFO,"done hash search\n");
  373: 	} else {
  374: 		OFF_T j;
  375: 		/* by doing this in pieces we avoid too many seeks */
  376: 		for (j = last_match + CHUNK_SIZE; j < len; j += CHUNK_SIZE)
  377: 			matched(f, s, buf, j, -2);
  378: 		matched(f, s, buf, len, -1);
  379: 	}
  380: 
  381: 	sum_len = sum_end(file_sum);
  382: 	/* If we had a read error, send a bad checksum. */
  383: 	if (buf && buf->status != 0)
  384: 		file_sum[0]++;
  385: 
  386: 	if (verbose > 2)
  387: 		rprintf(FINFO,"sending file_sum\n");
  388: 	write_buf(f, file_sum, sum_len);
  389: 
  390: 	if (verbose > 2)
  391: 		rprintf(FINFO, "false_alarms=%d hash_hits=%d matches=%d\n",
  392: 			false_alarms, hash_hits, matches);
  393: 
  394: 	total_hash_hits += hash_hits;
  395: 	total_false_alarms += false_alarms;
  396: 	total_matches += matches;
  397: 	stats.literal_data += data_transfer;
  398: }
  399: 
  400: void match_report(void)
  401: {
  402: 	if (verbose <= 1)
  403: 		return;
  404: 
  405: 	rprintf(FINFO,
  406: 		"total: matches=%d  hash_hits=%d  false_alarms=%d data=%.0f\n",
  407: 		total_matches, total_hash_hits, total_false_alarms,
  408: 		(double)stats.literal_data);
  409: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>