amiga-rhash/calc_sums.c

818 lines
22 KiB
C

/* calc_sums.c - crc calculating and printing functions */
#include "common_func.h" /* should be included before the C library files */
#include <stdio.h>
#include <string.h>
#include <stdlib.h> /* free() */
#include <unistd.h> /* read() */
#include <fcntl.h> /* open() */
#include <time.h> /* localtime(), time() */
#include <sys/stat.h> /* stat() */
#include <errno.h>
#include <assert.h>
#include "librhash/rhash.h"
#include "librhash/rhash_torrent.h"
#include "parse_cmdline.h"
#include "rhash_main.h"
#include "hash_print.h"
#include "output.h"
#include "win_utils.h"
#include "calc_sums.h"
/**
* Initialize BTIH hash function. Unlike other algorithms BTIH
* requires more data for correct computation.
*
* @param info the file data
*/
static void init_btih_data(struct file_info *info)
{
assert((info->rctx->hash_id & RHASH_BTIH) != 0);
rhash_torrent_add_file(info->rctx, file_info_get_utf8_print_path(info), info->size);
rhash_torrent_set_program_name(info->rctx, get_bt_program_name());
if (opt.flags & OPT_BT_PRIVATE) {
rhash_torrent_set_options(info->rctx, RHASH_TORRENT_OPT_PRIVATE);
}
if (opt.bt_announce) {
size_t i;
for (i = 0; i < opt.bt_announce->size; i++) {
rhash_torrent_add_announce(info->rctx, (const char*)opt.bt_announce->array[i]);
}
}
if (opt.bt_piece_length) {
rhash_torrent_set_piece_length(info->rctx, opt.bt_piece_length);
}
else if (opt.bt_batch_file && rhash_data.batch_size) {
rhash_torrent_set_batch_size(info->rctx, rhash_data.batch_size);
}
}
/**
* (Re)-initialize RHash context, to calculate hash sums.
*
* @param info the file data
*/
static void re_init_rhash_context(struct file_info *info)
{
if (rhash_data.rctx != 0) {
if (opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED)) {
/* a set of hash sums can change from file to file */
rhash_free(rhash_data.rctx);
rhash_data.rctx = 0;
} else {
info->rctx = rhash_data.rctx;
if (opt.bt_batch_file) {
/* add another file to the torrent batch */
rhash_torrent_add_file(info->rctx, file_info_get_utf8_print_path(info), info->size);
return;
} else {
rhash_reset(rhash_data.rctx);
}
}
}
if (rhash_data.rctx == 0) {
rhash_data.rctx = rhash_init(info->sums_flags);
info->rctx = rhash_data.rctx;
}
if (info->sums_flags & RHASH_BTIH) {
/* re-initialize BitTorrent data */
init_btih_data(info);
}
}
/**
* Calculate hash sums simultaneously, according to the info->sums_flags.
* Calculated hashes are stored in info->rctx.
*
* @param info file data. The info->full_path can be "-" to denote stdin
* @return 0 on success, -1 on fail with error code stored in errno
*/
static int calc_sums(struct file_info *info)
{
FILE* fd = stdin; /* stdin */
int res;
assert(info->file);
if (info->file->mode & FILE_IFSTDIN) {
info->print_path = "(stdin)";
#ifdef _WIN32
/* using 0 instead of _fileno(stdin). _fileno() is undefined under 'gcc -ansi' */
if (setmode(0, _O_BINARY) < 0) {
return -1;
}
#endif
} else {
if ((opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED)) && FILE_ISDIR(info->file)) {
errno = EISDIR;
return -1;
}
info->size = info->file->size; /* total size, in bytes */
if (!info->sums_flags) return 0;
/* skip without reporting an error the files
* opened exclusively by another process */
fd = rsh_fopen_bin(info->full_path, "rb");
if (!fd) {
return -1;
}
}
re_init_rhash_context(info);
/* save initial msg_size, for correct calculation of percents */
info->msg_offset = info->rctx->msg_size;
if (percents_output->update != 0) {
rhash_set_callback(info->rctx, (rhash_callback_t)percents_output->update, info);
}
/* read and hash file content */
if ((res = rhash_file_update(info->rctx, fd)) != -1) {
if (!opt.bt_batch_file) {
rhash_final(info->rctx, 0); /* finalize hashing */
}
}
/* calculate real file size */
info->size = info->rctx->msg_size - info->msg_offset;
rhash_data.total_size += info->size;
if (fd != stdin) fclose(fd);
return res;
}
/**
* Free memory allocated by given file_info structure.
*
* @param info pointer the structure to de-initialize
*/
void file_info_destroy(struct file_info* info)
{
free(info->utf8_print_path);
free(info->allocated_ptr);
}
/**
* Store print_path in a file_info struct, replacing if needed
* system path separators with specified by user command line option.
*
* @param info pointer to the the file_info structure to change
* @param print_path the print path to store
*/
static void file_info_set_print_path(struct file_info* info, const char* print_path)
{
char *p;
char wrong_sep;
/* check if path separator was specified by command line options */
if (opt.path_separator) {
wrong_sep = (opt.path_separator == '/' ? '\\' : '/');
if ((p = (char*)strchr(print_path, wrong_sep)) != NULL) {
info->allocated_ptr = rsh_strdup(print_path);
info->print_path = info->allocated_ptr;
p = info->allocated_ptr + (p - print_path);
/* replace wrong_sep in the print_path with separator defined by options */
for (; *p; p++) {
if (*p == wrong_sep) *p = opt.path_separator;
}
return;
}
}
/* if path was not replaces, than just store the value */
info->print_path = print_path;
}
/**
* Return utf8 version of print_path.
*
* @param info file information
* @return utf8 string on success, NULL if couldn't convert.
*/
const char* file_info_get_utf8_print_path(struct file_info* info)
{
if (info->utf8_print_path == NULL) {
if (is_utf8()) return info->print_path;
info->utf8_print_path = to_utf8(info->print_path);
}
return info->utf8_print_path;
}
/* functions to calculate and print file sums */
/**
* Search for a crc32 hash sum in the given file name.
*
* @param filepath the path to the file.
* @param crc32 pointer to integer to receive parsed hash sum.
* @return non zero if crc32 was found, zero otherwise.
*/
static int find_embedded_crc32(const char* filepath, unsigned* crc32_be)
{
const char* e = filepath + strlen(filepath) - 10;
/* search for the sum enclosed in brackets */
for (; e >= filepath && !IS_PATH_SEPARATOR(*e); e--) {
if ((*e == '[' && e[9] == ']') || (*e == '(' && e[9] == ')')) {
const char *p = e + 8;
for (; p > e && IS_HEX(*p); p--);
if (p == e) {
rhash_hex_to_byte(e + 1, (char unsigned*)crc32_be, 8);
return 1;
}
e -= 9;
}
}
return 0;
}
/**
* Rename given file inserting its crc32 sum enclosed into square braces
* and placing it right before the file extension.
*
* @param info pointer to the data of the file to rename.
* @return 0 on success, -1 on fail with error code in errno
*/
int rename_file_by_embeding_crc32(struct file_info *info)
{
size_t len = strlen(info->full_path);
const char* p = info->full_path + len;
const char* c = p - 1;
char* new_path;
char* insertion_point;
unsigned crc32_be;
assert((info->rctx->hash_id & RHASH_CRC32) != 0);
/* check if the filename contains a CRC32 hash sum */
if (find_embedded_crc32(info->print_path, &crc32_be)) {
unsigned char* c =
(unsigned char*)rhash_get_context_ptr(info->rctx, RHASH_CRC32);
unsigned actual_crc32 = ((unsigned)c[0] << 24) |
((unsigned)c[1] << 16) | ((unsigned)c[2] << 8) | (unsigned)c[3];
/* compare with calculated CRC32 */
if (crc32_be != actual_crc32) {
char crc32_str[9];
rhash_print(crc32_str, info->rctx, RHASH_CRC32, RHPR_UPPERCASE);
/* TRANSLATORS: sample filename with embedded CRC32: file_[A1B2C3D4].mkv */
log_warning(_("wrong embedded CRC32, should be %s\n"), crc32_str);
} else return 0;
}
/* find file extension (as the place to insert the hash sum) */
for (; c >= info->full_path && !IS_PATH_SEPARATOR(*c); c--) {
if (*c == '.') {
p = c;
break;
}
}
/* now p is the point to insert delimiter + hash string in brackets */
new_path = (char*)rsh_malloc(len + 12);
insertion_point = new_path + (p - info->full_path);
memcpy(new_path, info->full_path, p - info->full_path);
if (opt.embed_crc_delimiter && *opt.embed_crc_delimiter) *(insertion_point++) = *opt.embed_crc_delimiter;
rhash_print(insertion_point+1, info->rctx, RHASH_CRC32, RHPR_UPPERCASE);
insertion_point[0] = '[';
insertion_point[9] = ']'; /* ']' overrides '\0' inserted by rhash_print_sum() */
strcpy(insertion_point + 10, p); /* append file extension */
/* rename the file */
if (rename(info->full_path, new_path) < 0) {
log_error(_("can't move %s to %s: %s\n"), info->full_path, new_path,
strerror(errno));
free(new_path);
return -1;
}
/* change file name in the file info structure */
if (info->print_path >= info->full_path && info->print_path < p) {
file_info_set_print_path(info, new_path + len - strlen(info->print_path));
} else {
file_info_set_print_path(info, new_path);
}
free(info->full_path);
info->full_path = new_path;
return 0;
}
/**
* Save torrent file to the given path.
*
* @param path the path to save torrent file to
* @param rctx the context containing torrent data
* @return 0 on success, -1 on fail with error code in errno
*/
int save_torrent_to(const char* path, rhash_context* rctx)
{
FILE* fd;
int res = 0;
const rhash_str* text = rhash_torrent_generate_content(rctx);
if (!text) {
errno = ENOMEM;
log_file_error(path);
return -1;
}
if (if_file_exists(path)) {
/* make backup copy of the existing torrent file */
char *bak_path = str_append(path, ".bak");
unlink(bak_path);
rename(path, bak_path);
free(bak_path);
}
/* write the torrent file */
fd = rsh_fopen_bin(path, "wb");
if (fd && text->length == fwrite(text->str, 1, text->length, fd) &&
!ferror(fd) && !fflush(fd))
{
log_msg(_("%s saved\n"), path);
} else {
log_file_error(path);
res = -1;
}
if (fd) fclose(fd);
return res;
}
/**
* Save torrent file.
*
* @param info information about the hashed file
*/
static void save_torrent(struct file_info* info)
{
/* append .torrent extension to the file path */
char* path = str_append(info->full_path, ".torrent");
save_torrent_to(path, info->rctx);
free(path);
}
/**
* Calculate and print file hash sums using printf format.
*
* @param out a stream to print to
* @param file the file to calculate sums for
* @param print_path the path to print
* @return 0 on success, -1 on fail
*/
int calculate_and_print_sums(FILE* out, file_t* file, const char *print_path)
{
struct file_info info;
timedelta_t timer;
int res = 0;
memset(&info, 0, sizeof(info));
info.file = file;
info.full_path = rsh_strdup(file->path);
file_info_set_print_path(&info, print_path);
info.size = 0;
info.sums_flags = opt.sum_flags;
if (file->mode & FILE_IFSTDIN) {
print_path = "(stdin)";
} else {
if (file->mode & FILE_IFDIR) return 0; /* don't handle directories */
info.size = file->size; /* total size, in bytes */
}
/* initialize percents output */
init_percents(&info);
rsh_timer_start(&timer);
if (info.sums_flags) {
/* calculate sums */
if (calc_sums(&info) < 0) {
/* print i/o error */
log_file_error(file->path);
res = -1;
}
if (rhash_data.interrupted) {
report_interrupted();
return 0;
}
}
info.time = rsh_timer_stop(&timer);
finish_percents(&info, res);
if (opt.flags & OPT_EMBED_CRC) {
/* rename the file */
rename_file_by_embeding_crc32(&info);
}
if ((opt.mode & MODE_TORRENT) && !opt.bt_batch_file) {
save_torrent(&info);
}
if ((opt.mode & MODE_UPDATE) && opt.fmt == FMT_SFV) {
/* updating SFV file: print SFV header line */
print_sfv_header_line(rhash_data.upd_fd, file, 0);
if (opt.flags & OPT_VERBOSE) {
print_sfv_header_line(rhash_data.log, file, 0);
fflush(rhash_data.log);
}
file_cleanup(file);
}
if (rhash_data.print_list && res >= 0) {
if (!opt.bt_batch_file) {
print_line(out, rhash_data.print_list, &info);
fflush(out);
/* print calculated line to stderr or log-file if verbose */
if ((opt.mode & MODE_UPDATE) && (opt.flags & OPT_VERBOSE)) {
print_line(rhash_data.log, rhash_data.print_list, &info);
fflush(rhash_data.log);
}
}
if ((opt.flags & OPT_SPEED) && info.sums_flags) {
print_file_time_stats(&info);
}
}
free(info.full_path);
file_info_destroy(&info);
return res;
}
/**
* Verify hash sums of the file.
*
* @param info structure file path to process
* @return zero on success, -1 on file error, -2 if hash sums are different
*/
static int verify_sums(struct file_info *info)
{
timedelta_t timer;
int res = 0;
errno = 0;
/* initialize percents output */
init_percents(info);
rsh_timer_start(&timer);
if (calc_sums(info) < 0) {
finish_percents(info, -1);
return -1;
}
info->time = rsh_timer_stop(&timer);
if (rhash_data.interrupted) {
report_interrupted();
return 0;
}
if ((opt.flags & OPT_EMBED_CRC) && find_embedded_crc32(
info->print_path, &info->hc.embedded_crc32_be)) {
info->hc.flags |= HC_HAS_EMBCRC32;
assert(info->hc.hash_mask & RHASH_CRC32);
}
if (!hash_check_verify(&info->hc, info->rctx)) {
res = -2;
}
finish_percents(info, res);
if ((opt.flags & OPT_SPEED) && info->sums_flags) {
print_file_time_stats(info);
}
return res;
}
/**
* Check hash sums in a hash file.
* Lines beginning with ';' and '#' are ignored.
*
* @param hash_file_path - the path of the file with hash sums to verify.
* @param chdir - true if function should emulate chdir to directory of filepath before checking it.
* @return zero on success, -1 on fail
*/
int check_hash_file(file_t* file, int chdir)
{
FILE *fd;
char buf[2048];
size_t pos;
const char *ralign;
timedelta_t timer;
struct file_info info;
const char* hash_file_path = file->path;
int res = 0, line_num = 0;
double time;
/* process --check-embedded option */
if (opt.mode & MODE_CHECK_EMBEDDED) {
unsigned crc32_be;
if (find_embedded_crc32(hash_file_path, &crc32_be)) {
/* initialize file_info structure */
memset(&info, 0, sizeof(info));
info.full_path = rsh_strdup(hash_file_path);
info.file = file;
file_info_set_print_path(&info, info.full_path);
info.sums_flags = info.hc.hash_mask = RHASH_CRC32;
info.hc.flags = HC_HAS_EMBCRC32;
info.hc.embedded_crc32_be = crc32_be;
res = verify_sums(&info);
fflush(rhash_data.out);
if (!rhash_data.interrupted) {
if (res == 0) rhash_data.ok++;
else if (res == -1 && errno == ENOENT) rhash_data.miss++;
rhash_data.processed++;
}
free(info.full_path);
file_info_destroy(&info);
} else {
log_warning(_("file name doesn't contain a CRC32: %s\n"), hash_file_path);
return -1;
}
return 0;
}
/* initialize statistics */
rhash_data.processed = rhash_data.ok = rhash_data.miss = 0;
rhash_data.total_size = 0;
if (file->mode & FILE_IFSTDIN) {
fd = stdin;
hash_file_path = "<stdin>";
} else if ( !(fd = rsh_fopen_bin(hash_file_path, "rb") )) {
log_file_error(hash_file_path);
return -1;
}
pos = strlen(hash_file_path)+16;
ralign = str_set(buf, '-', (pos < 80 ? 80 - (int)pos : 2));
fprintf(rhash_data.out, _("\n--( Verifying %s )%s\n"), hash_file_path, ralign);
fflush(rhash_data.out);
rsh_timer_start(&timer);
/* mark the directory part of the path, by setting the pos index */
if (chdir) {
pos = strlen(hash_file_path);
for (; pos > 0 && !IS_PATH_SEPARATOR(hash_file_path[pos]); pos--);
if (IS_PATH_SEPARATOR(hash_file_path[pos])) pos++;
} else pos = 0;
/* read crc file line by line */
for (line_num = 0; fgets(buf, 2048, fd); line_num++)
{
char* line = buf;
char* path_without_ext = NULL;
/* skip unicode BOM */
if (line_num == 0 && buf[0] == (char)0xEF && buf[1] == (char)0xBB && buf[2] == (char)0xBF) line += 3;
if (*line == 0) continue; /* skip empty lines */
if (is_binary_string(line)) {
log_error(_("file is binary: %s\n"), hash_file_path);
if (fd != stdin) fclose(fd);
return -1;
}
/* skip comments and empty lines */
if (IS_COMMENT(*line) || *line == '\r' || *line == '\n') continue;
memset(&info, 0, sizeof(info));
if (!hash_check_parse_line(line, &info.hc, !feof(fd))) continue;
if (info.hc.hash_mask == 0) continue;
info.print_path = info.hc.file_path;
info.sums_flags = info.hc.hash_mask;
/* see if crc file contains a hash sum without a filename */
if (info.print_path == NULL) {
char* point;
path_without_ext = rsh_strdup(hash_file_path);
point = strrchr(path_without_ext, '.');
if (point) {
*point = '\0';
file_info_set_print_path(&info, path_without_ext);
}
}
if (info.print_path != NULL) {
file_t file_to_check;
int is_absolute = IS_PATH_SEPARATOR(info.print_path[0]);
IF_WINDOWS(is_absolute = is_absolute || (info.print_path[0] && info.print_path[1] == ':'));
/* if filename shall be prepended by a directory path */
if (pos && !is_absolute) {
size_t len = strlen(info.print_path);
info.full_path = (char*)rsh_malloc(pos + len + 1);
memcpy(info.full_path, hash_file_path, pos);
strcpy(info.full_path + pos, info.print_path);
} else {
info.full_path = rsh_strdup(info.print_path);
}
memset(&file_to_check, 0, sizeof(file_t));
file_to_check.path = info.full_path;
file_stat(&file_to_check);
info.file = &file_to_check;
/* verify hash sums of the file */
res = verify_sums(&info);
fflush(rhash_data.out);
file_cleanup(&file_to_check);
file_info_destroy(&info);
if (rhash_data.interrupted) {
free(path_without_ext);
break;
}
/* update statistics */
if (res == 0) rhash_data.ok++;
else if (res == -1 && errno == ENOENT) rhash_data.miss++;
rhash_data.processed++;
}
free(path_without_ext);
}
time = rsh_timer_stop(&timer);
fprintf(rhash_data.out, "%s\n", str_set(buf, '-', 80));
print_check_stats();
if (rhash_data.processed != rhash_data.ok) rhash_data.error_flag = 1;
if (opt.flags & OPT_SPEED && rhash_data.processed > 1) {
print_time_stats(time, rhash_data.total_size, 1);
}
rhash_data.processed = 0;
res = ferror(fd); /* check that crc file has been read without errors */
if (fd != stdin) fclose(fd);
return (res == 0 ? 0 : -1);
}
/*=========================================================================
* Benchmark functions
*=========================================================================*/
/**
* Hash a repeated message chunk by specified hash function.
*
* @param hash_id hash function identifier
* @param message a message chunk to hash
* @param msg_size message chunk size
* @param count number of chunks
* @param out computed hash
* @return 1 on success, 0 on error
*/
static int benchmark_loop(unsigned hash_id, const unsigned char* message, size_t msg_size, int count, unsigned char* out)
{
int i;
struct rhash_context *context = rhash_init(hash_id);
if (!context) return 0;
/* process the repeated message buffer */
for (i = 0; i < count && !rhash_data.interrupted; i++) {
rhash_update(context, message, msg_size);
}
rhash_final(context, out);
rhash_free(context);
return 1;
}
#if defined(_MSC_VER)
#define ALIGN_DATA(n) __declspec(align(n))
#elif defined(__GNUC__)
#define ALIGN_DATA(n) __attribute__((aligned (n)))
#else
#define ALIGN_DATA(n) /* do nothing */
#endif
/* define read_tsc() if possible */
#if defined(__i386__) || defined(_M_IX86) || \
defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64)
#if defined( _MSC_VER ) /* if MS VC */
# include <intrin.h>
# pragma intrinsic( __rdtsc )
# define read_tsc() __rdtsc()
# define HAVE_TSC
#elif defined( __GNUC__ ) /* if GCC */
static uint64_t read_tsc(void) {
unsigned long lo, hi;
__asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return (((uint64_t)hi) << 32) + lo;
}
# define HAVE_TSC
#endif /* _MSC_VER, __GNUC__ */
#endif /* x86/amd64 arch */
void run_benchmark(unsigned hash_id, unsigned flags)
{
unsigned char ALIGN_DATA(16) message[8192]; /* 8 KiB */
timedelta_t timer;
int i, j;
size_t sz_mb, msg_size;
double time, total_time = 0;
const int rounds = 4;
const char* hash_name;
unsigned char out[130];
#ifdef HAVE_TSC
double cpb = 0;
#endif /* HAVE_TSC */
#ifdef _WIN32
set_benchmark_cpu_affinity(); /* set CPU affinity to improve test results */
#endif
/* set message size for fast and slow hash functions */
msg_size = 1073741824 / 2;
if (hash_id & (RHASH_WHIRLPOOL | RHASH_SNEFRU128 | RHASH_SNEFRU256 | RHASH_SHA3_224 | RHASH_SHA3_256 | RHASH_SHA3_384 | RHASH_SHA3_512)) {
msg_size /= 8;
} else if (hash_id & (RHASH_GOST | RHASH_GOST_CRYPTOPRO | RHASH_SHA384 | RHASH_SHA512)) {
msg_size /= 2;
}
sz_mb = msg_size / (1 << 20); /* size in MiB */
hash_name = rhash_get_name(hash_id);
if (!hash_name) hash_name = ""; /* benchmarking several hashes*/
for (i = 0; i < (int)sizeof(message); i++) message[i] = i & 0xff;
for (j = 0; j < rounds && !rhash_data.interrupted; j++) {
rsh_timer_start(&timer);
benchmark_loop(hash_id, message, sizeof(message), (int)(msg_size / sizeof(message)), out);
time = rsh_timer_stop(&timer);
total_time += time;
if ((flags & BENCHMARK_RAW) == 0 && !rhash_data.interrupted) {
fprintf(rhash_data.out, "%s %u MiB calculated in %.3f sec, %.3f MBps\n", hash_name, (unsigned)sz_mb, time, (double)sz_mb / time);
fflush(rhash_data.out);
}
}
#if defined(HAVE_TSC)
/* measure the CPU "clocks per byte" speed */
if ((flags & BENCHMARK_CPB) != 0 && !rhash_data.interrupted) {
unsigned int c1 = -1, c2 = -1;
unsigned volatile long long cy0, cy1, cy2;
int msg_size = 128 * 1024;
/* make 200 tries */
for (i = 0; i < 200; i++) {
cy0 = read_tsc();
benchmark_loop(hash_id, message, sizeof(message), msg_size / sizeof(message), out);
cy1 = read_tsc();
benchmark_loop(hash_id, message, sizeof(message), msg_size / sizeof(message), out);
benchmark_loop(hash_id, message, sizeof(message), msg_size / sizeof(message), out);
cy2 = read_tsc();
cy2 -= cy1;
cy1 -= cy0;
c1 = (unsigned int)(c1 > cy1 ? cy1 : c1);
c2 = (unsigned int)(c2 > cy2 ? cy2 : c2);
}
cpb = ((c2 - c1) + 1) / (double)msg_size;
}
#endif /* HAVE_TSC */
if (rhash_data.interrupted) {
report_interrupted();
return;
}
if (flags & BENCHMARK_RAW) {
/* output result in a "raw" machine-readable format */
fprintf(rhash_data.out, "%s\t%u\t%.3f\t%.3f", hash_name, ((unsigned)sz_mb * rounds), total_time, (double)(sz_mb * rounds) / total_time);
#if defined(HAVE_TSC)
if (flags & BENCHMARK_CPB) {
fprintf(rhash_data.out, "\t%.2f", cpb);
}
#endif /* HAVE_TSC */
fprintf(rhash_data.out, "\n");
} else {
fprintf(rhash_data.out, "%s %u MiB total in %.3f sec, %.3f MBps", hash_name, ((unsigned)sz_mb * rounds), total_time, (double)(sz_mb * rounds) / total_time);
#if defined(HAVE_TSC)
if (flags & BENCHMARK_CPB) {
fprintf(rhash_data.out, ", CPB=%.2f", cpb);
}
#endif /* HAVE_TSC */
fprintf(rhash_data.out, "\n");
}
}