git-core-*/?*
test-date
test-delta
+test-gsimm
common-cmds.h
*.tar.gz
*.dsc
test-delta$X: test-delta.c diff-delta.o patch-delta.o
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ -lz
+test-gsimm$X: test-gsimm.c gsimm.o rabinpoly.o
+ $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
+
check:
for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
$(LIB_FILE) $(XDIFF_LIB)
rm -f $(ALL_PROGRAMS) git$X
+ rm -f test-date$X test-delta$X test-gsimm$X
rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
rm -rf $(GIT_TARNAME)
rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
#define LOGSIZE (65536)
-static int cmd_log(int argc, const char **argv, char **envp)
+static int cmd_log_wc(int argc, const char **argv, char **envp,
+ struct whatchanged_opt *wcopt)
{
- struct rev_info rev;
struct commit *commit;
char *buf = xmalloc(LOGSIZE);
- static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
- int abbrev = DEFAULT_ABBREV;
- int abbrev_commit = 0;
const char *commit_prefix = "commit ";
- struct log_tree_opt opt;
int shown = 0;
- int do_diff = 0;
- int full_diff = 0;
-
- init_log_tree_opt(&opt);
- argc = setup_revisions(argc, argv, &rev, "HEAD");
- while (1 < argc) {
- const char *arg = argv[1];
- if (!strncmp(arg, "--pretty", 8)) {
- commit_format = get_commit_format(arg + 8);
- if (commit_format == CMIT_FMT_ONELINE)
- commit_prefix = "";
- }
- else if (!strcmp(arg, "--no-abbrev")) {
- abbrev = 0;
- }
- else if (!strcmp(arg, "--abbrev")) {
- abbrev = DEFAULT_ABBREV;
- }
- else if (!strcmp(arg, "--abbrev-commit")) {
- abbrev_commit = 1;
- }
- else if (!strncmp(arg, "--abbrev=", 9)) {
- abbrev = strtoul(arg + 9, NULL, 10);
- if (abbrev && abbrev < MINIMUM_ABBREV)
- abbrev = MINIMUM_ABBREV;
- else if (40 < abbrev)
- abbrev = 40;
- }
- else if (!strcmp(arg, "--full-diff")) {
- do_diff = 1;
- full_diff = 1;
- }
- else {
- int cnt = log_tree_opt_parse(&opt, argv+1, argc-1);
- if (0 < cnt) {
- do_diff = 1;
- argv += cnt;
- argc -= cnt;
- continue;
- }
- die("unrecognized argument: %s", arg);
- }
+ struct rev_info *rev = &wcopt->revopt;
+ struct log_tree_opt *opt = &wcopt->logopt;
- argc--; argv++;
- }
+ opt->commit_format = CMIT_FMT_DEFAULT;
+ wcopt->abbrev = DEFAULT_ABBREV;
+ argc = parse_whatchanged_opt(argc, argv, wcopt);
- if (do_diff) {
- opt.diffopt.abbrev = abbrev;
- opt.verbose_header = 0;
- opt.always_show_header = 0;
- opt.no_commit_id = 1;
- if (opt.combine_merges)
- opt.ignore_merges = 0;
- if (opt.dense_combined_merges)
- opt.diffopt.output_format = DIFF_FORMAT_PATCH;
- if (opt.diffopt.output_format == DIFF_FORMAT_PATCH)
- opt.diffopt.recursive = 1;
- if (!full_diff && rev.prune_data)
- diff_tree_setup_paths(rev.prune_data, &opt.diffopt);
- diff_setup_done(&opt.diffopt);
- }
+ if (opt->commit_format == CMIT_FMT_ONELINE)
+ commit_prefix = "";
- prepare_revision_walk(&rev);
+ prepare_revision_walk(rev);
setup_pager();
- while ((commit = get_revision(&rev)) != NULL) {
- if (shown && do_diff && commit_format != CMIT_FMT_ONELINE)
+ while ((commit = get_revision(rev)) != NULL) {
+ if (shown && wcopt->do_diff &&
+ opt->commit_format != CMIT_FMT_ONELINE)
putchar('\n');
fputs(commit_prefix, stdout);
- if (abbrev_commit && abbrev)
- fputs(find_unique_abbrev(commit->object.sha1, abbrev),
+ if (wcopt->abbrev_commit && wcopt->abbrev)
+ fputs(find_unique_abbrev(commit->object.sha1, wcopt->abbrev),
stdout);
else
fputs(sha1_to_hex(commit->object.sha1), stdout);
- if (rev.parents) {
+ if (rev->parents) {
struct commit_list *parents = commit->parents;
while (parents) {
struct object *o = &(parents->item->object);
parents = parents->next)
parents->item->object.flags &= ~TMP_MARK;
}
- if (commit_format == CMIT_FMT_ONELINE)
+ if (opt->commit_format == CMIT_FMT_ONELINE)
putchar(' ');
else
putchar('\n');
- pretty_print_commit(commit_format, commit, ~0, buf,
- LOGSIZE, abbrev);
+ pretty_print_commit(opt->commit_format, commit, ~0, buf,
+ LOGSIZE, wcopt->abbrev);
printf("%s\n", buf);
- if (do_diff) {
+ if (wcopt->do_diff) {
printf("---\n");
- log_tree_commit(&opt, commit);
+ log_tree_commit(opt, commit);
}
shown = 1;
free(commit->buffer);
return 0;
}
+static int cmd_log(int ac, const char **av, char **ep)
+{
+ struct whatchanged_opt wcopt;
+
+ memset(&wcopt, 0, sizeof(wcopt));
+ init_log_tree_opt(&wcopt.logopt);
+ return cmd_log_wc(ac, av, ep, &wcopt);
+}
+
+static int cmd_whatchanged(int ac, const char **av, char **ep)
+{
+ struct whatchanged_opt wcopt;
+
+ memset(&wcopt, 0, sizeof(wcopt));
+ wcopt.do_diff = 1;
+ init_log_tree_opt(&wcopt.logopt);
+ wcopt.logopt.diffopt.recursive = 1;
+ return cmd_log_wc(ac, av, ep, &wcopt);
+}
+
+static int cmd_show(int ac, const char **av, char **ep)
+{
+ struct whatchanged_opt wcopt;
+
+ memset(&wcopt, 0, sizeof(wcopt));
+ wcopt.do_diff = 1;
+ init_log_tree_opt(&wcopt.logopt);
+ wcopt.logopt.ignore_merges = 0;
+ wcopt.logopt.combine_merges = 1;
+ wcopt.logopt.dense_combined_merges = 1;
+ wcopt.logopt.diffopt.recursive = 1;
+ return cmd_log_wc(ac, av, ep, &wcopt);
+}
+
static void handle_internal_command(int argc, const char **argv, char **envp)
{
const char *cmd = argv[0];
{ "version", cmd_version },
{ "help", cmd_help },
{ "log", cmd_log },
+ { "show", cmd_show },
+ { "whatchanged", cmd_whatchanged },
};
int i;
--- /dev/null
+#include "rabinpoly.h"
+#include "gsimm.h"
+
+/* Has to be power of two. Since the Rabin hash only has 63
+ usable bits, the number of hashes is limited to 32.
+ Lower powers of two could be used for speeding up processing
+ of very large files. */
+#define NUM_HASHES_PER_CHAR 32
+
+/* Size of cache used to eliminate duplicate substrings.
+ Make small enough to comfortably fit in L1 cache. */
+#define DUP_CACHE_SIZE 256
+
+/* For the final counting, do not count each bit individually, but
+ group them. Must be power of two, at most NUM_HASHES_PER_CHAR.
+ However, larger sizes result in higher cache usage. Use 8 bits
+ per group for efficient processing of large files on fast machines
+ with decent caches, or 4 bits for faster processing of small files
+ and for machines with small caches. */
+#define GROUP_BITS 4
+#define GROUP_COUNTERS (1<<GROUP_BITS)
+
+static void freq_to_md(u_char *md, int *freq)
+{ int j, k;
+
+ for (j = 0; j < MD_LENGTH; j++)
+ { u_char ch = 0;
+
+ for (k = 0; k < 8; k++) ch = 2*ch + (freq[8*j+k] > 0);
+ md[j] = ch;
+ }
+ bzero (freq, sizeof(freq[0]) * MD_BITS);
+}
+
+void gb_simm_process(u_char *data, unsigned len, u_char *md)
+{ size_t j = 0;
+ u_int32_t ofs;
+ u_int32_t dup_cache[DUP_CACHE_SIZE];
+ u_int32_t count [MD_BITS * (GROUP_COUNTERS/GROUP_BITS)];
+ int freq[MD_BITS];
+
+ bzero (freq, sizeof(freq[0]) * MD_BITS);
+ bzero (dup_cache, DUP_CACHE_SIZE * sizeof (u_int32_t));
+ bzero (count, (MD_BITS * (GROUP_COUNTERS/GROUP_BITS) * sizeof (u_int32_t)));
+
+ /* Ignore incomplete substrings */
+ while (j < len && j < RABIN_WINDOW_SIZE) rabin_slide8 (data[j++]);
+
+ while (j < len)
+ { u_int64_t hash;
+ u_int32_t ofs, sum;
+ u_char idx;
+ int k;
+
+ hash = rabin_slide8 (data[j++]);
+
+ /* In order to update a much larger frequency table
+ with only 32 bits of checksum, randomly select a
+ part of the table to update. The selection should
+ only depend on the content of the represented data,
+ and be independent of the bits used for the update.
+
+ Instead of updating 32 individual counters, process
+ the checksum in MD_BITS / GROUP_BITS groups of
+ GROUP_BITS bits, and count the frequency of each bit pattern.
+ */
+
+ idx = (hash >> 32);
+ sum = (u_int32_t) hash;
+ ofs = idx % (MD_BITS / NUM_HASHES_PER_CHAR) * NUM_HASHES_PER_CHAR;
+ idx %= DUP_CACHE_SIZE;
+ if (dup_cache[idx] != sum)
+ { dup_cache[idx] = sum;
+ for (k = 0; k < NUM_HASHES_PER_CHAR / GROUP_BITS; k++)
+ { count[ofs * GROUP_COUNTERS / GROUP_BITS + (sum % GROUP_COUNTERS)]++;
+ ofs += GROUP_BITS;
+ sum >>= GROUP_BITS;
+ } } }
+
+ /* Distribute the occurrences of each bit group over the frequency table. */
+ for (ofs = 0; ofs < MD_BITS; ofs += GROUP_BITS)
+ { int j;
+ for (j = 0; j < GROUP_COUNTERS; j++)
+ { int k;
+ for (k = 0; k < GROUP_BITS; k++)
+ { freq[ofs + k] += ((1<<k) & j)
+ ? count[ofs * GROUP_COUNTERS / GROUP_BITS + j]
+ : -count[ofs * GROUP_COUNTERS / GROUP_BITS + j];
+ } } }
+
+ if (md)
+ { rabin_reset();
+ freq_to_md (md, freq);
+} }
--- /dev/null
+#ifndef GSIMM_H
+#define GSIMM_H
+
+/* Length of file message digest (MD) in bytes. Longer MD's are
+ better, but increase processing time for diminishing returns.
+ Must be multiple of NUM_HASHES_PER_CHAR / 8, and at least 24
+ for good results
+*/
+#define MD_LENGTH 32
+#define MD_BITS (MD_LENGTH * 8)
+
+/* The MIN_FILE_SIZE indicates the absolute minimal file size that
+ can be processed. As indicated above, the first and last
+ RABIN_WINDOW_SIZE - 1 bytes are skipped.
+ In order to get at least an average of 12 samples
+ per bit in the final message digest, require at least 3 * MD_LENGTH
+ complete windows in the file. */
+#define MIN_FILE_SIZE (3 * MD_LENGTH + 2 * (RABIN_WINDOW_SIZE - 1))
+
+/* Limit matching algorithm to files less than 256 MB, so we can use
+ 32 bit integers everywhere without fear of overflow. For larger
+ files we should add logic to mmap the file by piece and accumulate
+ the frequency counts. */
+#define MAX_FILE_SIZE (256*1024*1024 - 1)
+
+void gb_simm_process(u_char *data, unsigned len, u_char *md);
+
+#endif
}
return 0;
}
+
+int parse_whatchanged_opt(int ac, const char **av, struct whatchanged_opt *wcopt)
+{
+ struct rev_info *rev = &wcopt->revopt;
+ struct log_tree_opt *opt = &wcopt->logopt;
+ const char **unrecognized = av+1;
+ int left = 1;
+
+ ac = setup_revisions(ac, av, rev, "HEAD");
+ if (!strcmp(av[0], "show"))
+ rev->no_walk = 1;
+ while (1 < ac) {
+ const char *arg = av[1];
+ if (!strncmp(arg, "--pretty", 8)) {
+ opt->commit_format = get_commit_format(arg + 8);
+ }
+ else if (!strcmp(arg, "--no-abbrev")) {
+ wcopt->abbrev = 0;
+ }
+ else if (!strcmp(arg, "--abbrev")) {
+ wcopt->abbrev = DEFAULT_ABBREV;
+ }
+ else if (!strcmp(arg, "--abbrev-commit")) {
+ wcopt->abbrev_commit = 1;
+ }
+ else if (!strncmp(arg, "--abbrev=", 9)) {
+ wcopt->abbrev = strtoul(arg + 9, NULL, 10);
+ if (wcopt->abbrev && wcopt->abbrev < MINIMUM_ABBREV)
+ wcopt->abbrev = MINIMUM_ABBREV;
+ else if (40 < wcopt->abbrev)
+ wcopt->abbrev = 40;
+ }
+ else if (!strcmp(arg, "--full-diff")) {
+ wcopt->do_diff = 1;
+ wcopt->full_diff = 1;
+ }
+ else {
+ int cnt = log_tree_opt_parse(opt, av+1, ac-1);
+ if (0 < cnt) {
+ wcopt->do_diff = 1;
+ av += cnt;
+ ac -= cnt;
+ continue;
+ }
+ *unrecognized++ = arg;
+ left++;
+ }
+ ac--; av++;
+ }
+
+ if (wcopt->do_diff) {
+ opt->diffopt.abbrev = wcopt->abbrev;
+ opt->verbose_header = 0;
+ opt->always_show_header = 0;
+ opt->no_commit_id = 1;
+ if (opt->combine_merges)
+ opt->ignore_merges = 0;
+ if (opt->dense_combined_merges)
+ opt->diffopt.output_format = DIFF_FORMAT_PATCH;
+ if (opt->diffopt.output_format == DIFF_FORMAT_PATCH)
+ opt->diffopt.recursive = 1;
+ if (!wcopt->full_diff && rev->prune_data)
+ diff_tree_setup_paths(rev->prune_data, &opt->diffopt);
+ diff_setup_done(&opt->diffopt);
+ }
+ return left;
+}
#ifndef LOG_TREE_H
#define LOG_TREE_H
+#include "revision.h"
+
struct log_tree_opt {
struct diff_options diffopt;
int show_root_diff;
int log_tree_commit(struct log_tree_opt *, struct commit *);
int log_tree_opt_parse(struct log_tree_opt *, const char **, int);
+struct whatchanged_opt {
+ struct rev_info revopt;
+ struct log_tree_opt logopt;
+ int abbrev;
+ int abbrev_commit;
+ int do_diff;
+ int full_diff;
+};
+
+int parse_whatchanged_opt(int, const char **, struct whatchanged_opt *);
+
#endif
--- /dev/null
+/*
+ *
+ * Copyright (C) 1999 David Mazieres (dm@uun.org)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ */
+
+ /* Faster generic_fls */
+ /* (c) 2002, D.Phillips and Sistina Software */
+
+#include "rabinpoly.h"
+#define MSB64 0x8000000000000000ULL
+
+static inline unsigned fls8(unsigned n)
+{
+ return n & 0xf0?
+ n & 0xc0? (n >> 7) + 7: (n >> 5) + 5:
+ n & 0x0c? (n >> 3) + 3: n - ((n + 1) >> 2);
+}
+
+static inline unsigned fls16(unsigned n)
+{
+ return n & 0xff00? fls8(n >> 8) + 8: fls8(n);
+}
+
+static inline unsigned fls32(unsigned n)
+{
+ return n & 0xffff0000? fls16(n >> 16) + 16: fls16(n);
+}
+
+static inline unsigned fls64(unsigned long long n) /* should be u64 */
+{
+ return n & 0xffffffff00000000ULL? fls32(n >> 32) + 32: fls32(n);
+}
+
+
+static u_int64_t polymod (u_int64_t nh, u_int64_t nl, u_int64_t d);
+static void polymult (u_int64_t *php, u_int64_t *plp,
+ u_int64_t x, u_int64_t y);
+static u_int64_t polymmult (u_int64_t x, u_int64_t y, u_int64_t d);
+
+static u_int64_t poly = 0xb15e234bd3792f63ull; // Actual polynomial
+static u_int64_t T[256]; // Lookup table for mod
+static int shift;
+
+u_int64_t append8 (u_int64_t p, u_char m)
+{ return ((p << 8) | m) ^ T[p >> shift];
+}
+
+static u_int64_t
+polymod (u_int64_t nh, u_int64_t nl, u_int64_t d)
+{ int i, k;
+ assert (d);
+ k = fls64 (d) - 1;
+ d <<= 63 - k;
+
+ if (nh) {
+ if (nh & MSB64)
+ nh ^= d;
+ for (i = 62; i >= 0; i--)
+ if (nh & 1ULL << i) {
+ nh ^= d >> (63 - i);
+ nl ^= d << (i + 1);
+ }
+ }
+ for (i = 63; i >= k; i--)
+ if (nl & 1ULL << i)
+ nl ^= d >> (63 - i);
+ return nl;
+}
+
+static void
+polymult (u_int64_t *php, u_int64_t *plp, u_int64_t x, u_int64_t y)
+{ int i;
+ u_int64_t ph = 0, pl = 0;
+ if (x & 1)
+ pl = y;
+ for (i = 1; i < 64; i++)
+ if (x & (1ULL << i)) {
+ ph ^= y >> (64 - i);
+ pl ^= y << i;
+ }
+ if (php)
+ *php = ph;
+ if (plp)
+ *plp = pl;
+}
+
+static u_int64_t
+polymmult (u_int64_t x, u_int64_t y, u_int64_t d)
+{
+ u_int64_t h, l;
+ polymult (&h, &l, x, y);
+ return polymod (h, l, d);
+}
+
+static int size = RABIN_WINDOW_SIZE;
+static u_int64_t fingerprint = 0;
+static int bufpos = -1;
+static u_int64_t U[256];
+static u_char buf[RABIN_WINDOW_SIZE];
+
+void rabin_init ()
+{ u_int64_t sizeshift = 1;
+ u_int64_t T1;
+ int xshift;
+ int i, j;
+ assert (poly >= 0x100);
+ xshift = fls64 (poly) - 1;
+ shift = xshift - 8;
+ T1 = polymod (0, 1ULL << xshift, poly);
+ for (j = 0; j < 256; j++)
+ T[j] = polymmult (j, T1, poly) | ((u_int64_t) j << xshift);
+
+ for (i = 1; i < size; i++)
+ sizeshift = append8 (sizeshift, 0);
+ for (i = 0; i < 256; i++)
+ U[i] = polymmult (i, sizeshift, poly);
+ bzero (buf, sizeof (buf));
+}
+
+void
+rabin_reset ()
+{ rabin_init();
+ fingerprint = 0;
+ bzero (buf, sizeof (buf));
+}
+
+u_int64_t
+rabin_slide8 (u_char m)
+{ u_char om;
+ if (++bufpos >= size) bufpos = 0;
+
+ om = buf[bufpos];
+ buf[bufpos] = m;
+ fingerprint = append8 (fingerprint ^ U[om], m);
+
+ return fingerprint;
+}
+
--- /dev/null
+/*
+ *
+ * Copyright (C) 2000 David Mazieres (dm@uun.org)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Translated to C and simplified by Geert Bosch (bosch@gnat.com)
+ */
+
+#include <assert.h>
+#include <strings.h>
+#include <sys/types.h>
+
+#ifndef RABIN_WINDOW_SIZE
+#define RABIN_WINDOW_SIZE 48
+#endif
+void rabin_reset();
+u_int64_t rabin_slide8(u_char c);
save_commit_buffer = verbose_header;
track_object_refs = 0;
+ if (bisect_list)
+ revs.limited = 1;
prepare_revision_walk(&revs);
if (revs.tree_objects)
if (revs->prune_fn)
revs->prune_fn(revs, commit);
+ if (revs->no_walk)
+ return;
+
parent = commit->parents;
while (parent) {
struct commit *p = parent->item;
void prepare_revision_walk(struct rev_info *revs)
{
+ if (revs->no_walk)
+ return;
sort_by_date(&revs->commits);
if (revs->limited)
limit_list(revs);
/* Traversal flags */
unsigned int dense:1,
no_merges:1,
+ no_walk:1,
remove_empty_trees:1,
lifo:1,
topo_order:1,
--- /dev/null
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "rabinpoly.h"
+#include "gsimm.h"
+
+#define MIN(x,y) ((y)<(x) ? (y) : (x))
+#define MAX(x,y) ((y)>(x) ? (y) : (x))
+
+/* The RABIN_WINDOW_SIZE is the size of fingerprint window used by
+ Rabin algorithm. This is not a modifiable parameter.
+
+ The first RABIN_WINDOW_SIZE - 1 bytes are skipped, in order to ensure
+ fingerprints are good hashes. This does somewhat reduce the
+ influence of the first few bytes in the file (they're part of
+ fewer windows, like the last few bytes), but that actually isn't
+ so bad as files often start with fixed content that may bias comparisons.
+*/
+
+typedef struct fileinfo
+{ char *name;
+ size_t length;
+ u_char md[MD_LENGTH];
+ int match;
+} File;
+
+int flag_verbose = 0;
+int flag_debug = 0;
+char *flag_relative = 0;
+
+char cmd[12] = " ...";
+char md_strbuf[MD_LENGTH * 2 + 1];
+u_char relative_md [MD_LENGTH];
+
+File *file;
+int file_count;
+size_t file_bytes;
+
+char hex[17] = "0123456789abcdef";
+
+void usage()
+{ fprintf (stderr, "usage: %s [-dhvw] [-r fingerprint] file ...\n", cmd);
+ fprintf (stderr, " -d\tdebug output, repeate for more verbosity\n");
+ fprintf (stderr, " -h\tshow this usage information\n");
+ fprintf (stderr, " -r\tshow distance relative to fingerprint "
+ "(%u hex digits)\n", MD_LENGTH * 2);
+ fprintf (stderr, " -v\tverbose output, repeat for even more verbosity\n");
+ fprintf (stderr, " -w\tenable warnings for suspect statistics\n");
+ exit (1);
+}
+
+int dist (u_char *l, u_char *r)
+{ int j, k;
+ int d = 0;
+
+ for (j = 0; j < MD_LENGTH; j++)
+ { u_char ch = l[j] ^ r[j];
+
+ for (k = 0; k < 8; k++) d += ((ch & (1<<k)) > 0);
+ }
+
+ return d;
+}
+
+char *md_to_str(u_char *md)
+{ int j;
+
+ for (j = 0; j < MD_LENGTH; j++)
+ { u_char ch = md[j];
+
+ md_strbuf[j*2] = hex[ch >> 4];
+ md_strbuf[j*2+1] = hex[ch & 0xF];
+ }
+
+ md_strbuf[j*2] = 0;
+ return md_strbuf;
+}
+
+void process_file (char *name)
+{ int fd;
+ struct stat fs;
+ u_char *data;
+ File *fi = file+file_count;;
+
+ fd = open (name, O_RDONLY, 0);
+ if (fd < 0)
+ { perror (name);
+ exit (2);
+ }
+
+ if (fstat (fd, &fs))
+ { perror (name);
+ exit (2);
+ }
+
+ if (fs.st_size >= MIN_FILE_SIZE
+ && fs.st_size <= MAX_FILE_SIZE)
+ { fi->length = fs.st_size;
+ fi->name = name;
+
+ data = (u_char *) mmap (0, fs.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+ if (data == (u_char *) -1)
+ { perror (name);
+ exit (2);
+ }
+
+ gb_simm_process (data, fs.st_size, fi->md);
+ if (flag_relative)
+ { int d = dist (fi->md, relative_md);
+ double sim = 1.0 - MIN (1.0, (double) (d) / (MD_LENGTH * 4 - 1));
+ fprintf (stdout, "%s %llu %u %s %u %3.1f\n",
+ md_to_str (fi->md), (long long unsigned) 0,
+ (unsigned) fs.st_size, name,
+ d, 100.0 * sim);
+ }
+ else
+ {
+ fprintf (stdout, "%s %llu %u %s\n",
+ md_to_str (fi->md), (long long unsigned) 0,
+ (unsigned) fs.st_size, name);
+ }
+ munmap (data, fs.st_size);
+ file_bytes += fs.st_size;
+ file_count++;
+ } else if (flag_verbose)
+ { fprintf (stdout, "skipping %s (size %llu)\n", name, (long long unsigned) fs.st_size); }
+
+ close (fd);
+}
+
+u_char *str_to_md(char *str, u_char *md)
+{ int j;
+
+ if (!md || !str) return 0;
+
+ bzero (md, MD_LENGTH);
+
+ for (j = 0; j < MD_LENGTH * 2; j++)
+ { char ch = str[j];
+
+ if (ch >= '0' && ch <= '9')
+ { md [j/2] = (md [j/2] << 4) + (ch - '0');
+ }
+ else
+ { ch |= 32;
+
+ if (ch < 'a' || ch > 'f') break;
+ md [j/2] = (md[j/2] << 4) + (ch - 'a' + 10);
+ } }
+
+ return (j != MD_LENGTH * 2 || str[j] != 0) ? 0 : md;
+}
+
+int main (int argc, char *argv[])
+{ int ch, j;
+
+ strncpy (cmd, basename (argv[0]), 8);
+
+ while ((ch = getopt(argc, argv, "dhr:vw")) != -1)
+ { switch (ch)
+ { case 'd': flag_debug++;
+ break;
+ case 'r': if (!optarg)
+ { fprintf (stderr, "%s: missing argument for -r\n", cmd);
+ return 1;
+ }
+ if (str_to_md (optarg, relative_md)) flag_relative = optarg;
+ else
+ { fprintf (stderr, "%s: not a valid fingerprint\n", optarg);
+ return 1;
+ }
+ break;
+ case 'v': flag_verbose++;
+ break;
+ case 'w': break;
+ default : usage();
+ return (ch != 'h');
+ } }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) usage();
+
+ rabin_reset ();
+ if (flag_verbose && flag_relative)
+ { fprintf (stdout, "distances are relative to %s\n", flag_relative);
+ }
+
+ file = (File *) calloc (argc, sizeof (File));
+
+ for (j = 0; j < argc; j++) process_file (argv[j]);
+
+ if (flag_verbose)
+ { fprintf (stdout, "%li bytes in %i files\n", (long) file_bytes, file_count);
+ }
+
+ return 0;
+}