1 /* output.c - output of results, errors and percents */
2 
3 #include "output.h"
4 #include "calc_sums.h"
5 #include "parse_cmdline.h"
6 #include "platform.h"
7 #include "rhash_main.h"
8 #include "win_utils.h"
9 #include "librhash/rhash.h"
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdlib.h> /* exit() */
14 #include <assert.h>
15 #include <errno.h>
16 
17 #ifdef _WIN32
18 # include <windows.h>
19 # include <io.h> /* for isatty */
20 #endif
21 
22 /* global pointer to the selected method of percents output */
23 struct percents_output_info_t* percents_output = NULL;
24 
25 #ifdef _WIN32
26 # define IS_UTF8() (opt.flags & OPT_UTF8)
27 #else
28 # define IS_UTF8() 1
29 #endif
30 
31 /**
32  * Calculate the number of symbols printed by fprintf_file_t(...) for a given formated message.
33  * @patam format (nullable) message format string
34  * @param path file path
35  * @param output_flags bitmask specifying path output mode
36  * @return the number of printed symbols
37  */
count_printed_size(const char * format,const char * path,unsigned output_flags)38 static int count_printed_size(const char* format, const char* path, unsigned output_flags)
39 {
40 	size_t format_length = 0;
41 	if (format) {
42 		assert(strstr(format, "%s") != NULL);
43 		format_length = (IS_UTF8() ? count_utf8_symbols(format) : strlen(format)) - 2;
44 	}
45 	assert(path != NULL);
46 	return format_length + (IS_UTF8() || (output_flags & OutForceUtf8) ? count_utf8_symbols(path) : strlen(path));
47 }
48 
49 /**
50  * Print formatted file path to the specified stream.
51  *
52  * @param out the stream to write to
53  * @param format (nullable) format string
54  * @param file the file, which path will be formatted
55  * @param output_flags bitmask containing mix of OutForceUtf8, OutBaseName, OutCountSymbols flags
56  * @return the number of characters printed, -1 on fail with error code stored in errno
57  */
fprintf_file_t(FILE * out,const char * format,struct file_t * file,unsigned output_flags)58 int fprintf_file_t(FILE* out, const char* format, struct file_t* file, unsigned output_flags)
59 {
60 	unsigned basename_bit = output_flags & FPathBaseName;
61 #ifdef _WIN32
62 	const char* print_path;
63 	if (!file->real_path) {
64 		print_path = file_get_print_path(file, FPathPrimaryEncoding | FPathNotNull | basename_bit);
65 	} else {
66 		unsigned ppf = ((output_flags & OutForceUtf8) || (opt.flags & OPT_UTF8) ? FPathUtf8 | FPathNotNull : FPathPrimaryEncoding);
67 		assert(file->real_path != NULL);
68 		assert((int)OutBaseName == (int)FPathBaseName);
69 		print_path = file_get_print_path(file, ppf | basename_bit);
70 		if (!print_path) {
71 			print_path = file_get_print_path(file, FPathUtf8 | FPathNotNull | basename_bit);
72 			assert(print_path);
73 			assert(!(opt.flags & OPT_UTF8));
74 		}
75 	}
76 #else
77 	const char* print_path = file_get_print_path(file, FPathPrimaryEncoding | FPathNotNull | basename_bit);
78 	assert((int)OutBaseName == (int)FPathBaseName);
79 	assert(print_path);
80 #endif
81 	if (rsh_fprintf(out, (format ? format : "%s"), print_path) < 0)
82 		return -1;
83 	if ((output_flags & OutCountSymbols) != 0)
84 		return count_printed_size(format, print_path, output_flags);
85 	return 0;
86 }
87 
88 /* RFC 3986: safe url characters are ascii alpha-numeric and "-._~", other characters should be percent-encoded */
89 static unsigned url_safe_char_mask[4] = { 0, 0x03ff6000, 0x87fffffe, 0x47fffffe };
90 #define IS_URL_GOOD_CHAR(c) ((unsigned)(c) < 128 && (url_safe_char_mask[c >> 5] & (1 << (c & 31))))
91 
92 /**
93  * Print to a stram an url-encoded representation of the given string.
94  *
95  * @param out the stream to print the result to
96  * @param str string to encode
97  * @param upper_case flag to print hex-codes in uppercase
98  * @return 0 on success, -1 on fail with error code stored in errno
99  */
fprint_urlencoded(FILE * out,const char * str,int upper_case)100 int fprint_urlencoded(FILE* out, const char* str, int upper_case)
101 {
102 	char buffer[1024];
103 	char* buffer_limit = buffer + (sizeof(buffer) - 3);
104 	char *p;
105 	const char hex_add = (upper_case ? 'A' - 10 : 'a' - 10);
106 	while (*str) {
107 		for (p = buffer; p < buffer_limit && *str; str++) {
108 			if (IS_URL_GOOD_CHAR(*str)) {
109 				*(p++) = *str;
110 			} else {
111 				unsigned char hi = ((unsigned char)(*str) >> 4) & 0x0f;
112 				unsigned char lo = (unsigned char)(*str) & 0x0f;
113 				*(p++) = '%';
114 				*(p++) = (hi > 9 ? hi + hex_add : hi + '0');
115 				*(p++) = (lo > 9 ? lo + hex_add : lo + '0');
116 			}
117 		}
118 		*p = 0;
119 		if (rsh_fprintf(out, "%s", buffer) < 0)
120 			return -1;
121 	}
122 	return 0;
123 }
124 
125 /*=========================================================================
126  * Logging functions
127  *=========================================================================*/
128 
129 /**
130  * Print message prefix before printing an error/warning message.
131  *
132  * @return the number of characters printed, -1 on error
133  */
print_log_prefix(void)134 static int print_log_prefix(void)
135 {
136 	return rsh_fprintf(rhash_data.log, "%s: ", PROGRAM_NAME);
137 }
138 
139 /**
140  * Print a formatted message to the program log, and flush the log stream.
141  *
142  * @param format print a formatted message to the program log
143  * @param args
144  */
log_va_msg(const char * format,va_list args)145 static void log_va_msg(const char* format, va_list args)
146 {
147 	rsh_vfprintf(rhash_data.log, format, args);
148 	fflush(rhash_data.log);
149 }
150 
151 /**
152  * Print a formatted message to the program log, and flush the log stream.
153  *
154  * @param format print a formatted message to the program log
155  */
log_msg(const char * format,...)156 void log_msg(const char* format, ...)
157 {
158 	va_list ap;
159 	va_start(ap, format);
160 	log_va_msg(format, ap);
161 	va_end(ap);
162 }
163 
164 /**
165  * Print a formatted message, where a single %s substring is replaced with a filepath, and flush the log stream.
166  * This function aims to correctly process utf8 conversion on windows.
167  * Note: on windows the format string must be in utf8 encoding.
168  *
169  * @param format the format string of a formatted message
170  * @param file the file, which path will be formatted
171  */
log_msg_file_t(const char * format,struct file_t * file)172 void log_msg_file_t(const char* format, struct file_t* file)
173 {
174 	fprintf_file_t(rhash_data.log, format, file, OutDefaultFlags);
175 	fflush(rhash_data.log);
176 }
177 
178 /**
179  * Print a formatted error message to the program log.
180  *
181  * @param format the format string
182  */
log_error(const char * format,...)183 void log_error(const char* format, ...)
184 {
185 	va_list ap;
186 	va_start(ap, format);
187 	print_log_prefix();
188 	log_va_msg(format, ap);
189 	va_end(ap);
190 }
191 
192 /**
193  * Print file error to the program log.
194  *
195  * @param file the file, caused the error
196  */
log_error_file_t(struct file_t * file)197 void log_error_file_t(struct file_t* file)
198 {
199 	int file_errno = errno;
200 	print_log_prefix();
201 	fprintf_file_t(rhash_data.log, "%s", file, OutDefaultFlags);
202 	rsh_fprintf(rhash_data.log, ": %s\n", strerror(file_errno));
203 	fflush(rhash_data.log);
204 }
205 
206 /**
207  * Print a formated error message with file path.
208  *
209  * @param file the file, caused the error
210  */
log_error_msg_file_t(const char * format,struct file_t * file)211 void log_error_msg_file_t(const char* format, struct file_t* file)
212 {
213 	print_log_prefix();
214 	fprintf_file_t(rhash_data.log, format, file, OutDefaultFlags);
215 	fflush(rhash_data.log);
216 }
217 
218 /**
219  * Log the message, that the program was interrupted.
220  * The function should be called only once.
221  */
report_interrupted(void)222 void report_interrupted(void)
223 {
224 	static int is_interrupted_reported = 0;
225 	assert(rhash_data.stop_flags != 0);
226 	if (rhash_data.stop_flags == InterruptedFlag && !is_interrupted_reported) {
227 		is_interrupted_reported = 1;
228 		log_msg(_("Interrupted by user...\n"));
229 	}
230 }
231 
232 /**
233  * Information about printed percents.
234  */
235 struct percents_t
236 {
237 	int points;
238 	int same_output;
239 	unsigned ticks;
240 };
241 static struct percents_t percents;
242 
243 /* the hash functions, which needs to be reported first on mismatch */
244 #define REPORT_FIRST_MASK (RHASH_MD5 | RHASH_SHA256 | RHASH_SHA512)
245 
246 /**
247  * Print verbose error on a message digest mismatch.
248  *
249  * @param info file information with path and its message digests
250  * @return 0 on success, -1 on error
251  */
print_verbose_hash_check_error(struct file_info * info)252 static int print_verbose_hash_check_error(struct file_info* info)
253 {
254 	char actual[130], expected[130];
255 	assert(HP_FAILED(info->hp->bit_flags));
256 
257 	/* TRANSLATORS: printed in the verbose mode on a message digest mismatch */
258 	if (rsh_fprintf(rhash_data.out, _("ERROR")) < 0)
259 		return -1;
260 
261 	if ((HpWrongFileSize & info->hp->bit_flags)) {
262 		sprintI64(actual, info->rctx->msg_size, 0);
263 		sprintI64(expected, info->hp->file_size, 0);
264 		if (rsh_fprintf(rhash_data.out, _(", size is %s should be %s"), actual, expected) < 0)
265 			return -1;
266 	}
267 
268 	if (HpWrongEmbeddedCrc32 & info->hp->bit_flags) {
269 		rhash_print(expected, info->rctx, RHASH_CRC32, RHPR_UPPERCASE);
270 		if (rsh_fprintf(rhash_data.out, _(", embedded CRC32 should be %s"), expected) < 0)
271 			return -1;
272 	}
273 
274 	if (HpWrongHashes & info->hp->bit_flags) {
275 		unsigned reported = 0;
276 		int i;
277 		for (i = 0; i < info->hp->hashes_num; i++) {
278 			struct hash_value* hv = &info->hp->hashes[i];
279 			char* expected_hash = info->hp->line_begin + hv->offset;
280 			unsigned hid = hv->hash_id;
281 			int pflags;
282 			if ((info->hp->wrong_hashes & (1 << i)) == 0)
283 				continue;
284 
285 			assert(hid != 0);
286 
287 			/* if can't detect precise hash */
288 			if ((hid & (hid - 1)) != 0) {
289 				/* guess the hash id */
290 				if (hid & opt.sum_flags) hid &= opt.sum_flags;
291 				if (hid & ~info->hp->found_hash_ids) hid &= ~info->hp->found_hash_ids;
292 				if (hid & ~reported) hid &= ~reported; /* avoid repeating */
293 				if (hid & REPORT_FIRST_MASK) hid &= REPORT_FIRST_MASK;
294 				hid &= -(int)hid; /* take the lowest bit */
295 			}
296 			assert(hid != 0 && (hid & (hid - 1)) == 0); /* single bit only */
297 			reported |= hid;
298 
299 			pflags = (hv->length == (rhash_get_digest_size(hid) * 2) ?
300 				(RHPR_HEX | RHPR_UPPERCASE) : (RHPR_BASE32 | RHPR_UPPERCASE));
301 			rhash_print(actual, info->rctx, hid, pflags);
302 			/* TRANSLATORS: print a message like "CRC32 is ABC12345 should be BCA54321" */
303 			if (rsh_fprintf(rhash_data.out, _(", %s is %s should be %s"),
304 					rhash_get_name(hid), actual, expected_hash) < 0)
305 				return -1;
306 		}
307 	}
308 	return PRINTF_RES(rsh_fprintf(rhash_data.out, "\n"));
309 }
310 
311 /**
312  * Print file path and no more than 52 spaces.
313  *
314  * @param out stream to print the filepath
315  * @param info pointer to the file-info structure
316  * @return 0 on success, -1 on i/o error
317  */
print_aligned_filepath(FILE * out,struct file_info * info)318 static int print_aligned_filepath(FILE* out, struct file_info* info)
319 {
320 	int symbols_count = fprintf_file_t(out, NULL, info->file, OutCountSymbols);
321 	if (symbols_count >= 0) {
322 		char buf[56];
323 		int spaces_count = (symbols_count <= 51 ? 52 - symbols_count : 1);
324 		return PRINTF_RES(rsh_fprintf(out, "%s", str_set(buf, ' ', spaces_count)));
325 	}
326 	return -1;
327 }
328 
329 /**
330  * Print file path and result of its verification against message digests.
331  * Also if error occurred, print error message.
332  *
333  * @param info pointer to the file-info structure
334  * @param print_name set to non-zero to print file path
335  * @param print_result set to non-zero to print verification result
336  * @return 0 on success, -1 on i/o error
337  */
print_check_result(struct file_info * info,int print_name,int print_result)338 static int print_check_result(struct file_info* info, int print_name, int print_result)
339 {
340 	int saved_errno = errno;
341 	int res = 0;
342 	if (print_name)
343 		res = print_aligned_filepath(rhash_data.out, info);
344 	if (print_result && res == 0) {
345 		if (info->processing_result < 0) {
346 			/* print error to stdout */
347 			res = PRINTF_RES(rsh_fprintf(rhash_data.out, "%s\n", strerror(saved_errno)));
348 		} else if (!HP_FAILED(info->hp->bit_flags) || !(opt.flags & OPT_VERBOSE)) {
349 			res = PRINTF_RES(rsh_fprintf(rhash_data.out, (!HP_FAILED(info->hp->bit_flags) ?
350 				/* TRANSLATORS: printed when all message digests match, use at least 3 characters to overwrite "99%" */
351 				_("OK \n") :
352 				/* TRANSLATORS: ERR (short for 'error') is printed on a message digest mismatch */
353 				_("ERR\n"))));
354 		} else {
355 			res = print_verbose_hash_check_error(info);
356 		}
357 	}
358 	if (fflush(rhash_data.out) < 0)
359 		res = -1;
360 	return res;
361 }
362 
363 /**
364  * Prepare or print result of file processing.
365  *
366  * @param info pointer to the file-info structure
367  * @param init non-zero on initialization before message digests calculation,
368  *             and zero after message digests calculation finished.
369  * @return 0 on success, -1 on i/o error
370  */
print_results_on_check(struct file_info * info,int init)371 static int print_results_on_check(struct file_info* info, int init)
372 {
373 	if (opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED)) {
374 		int print_name = (opt.flags & (OPT_PERCENTS | OPT_SKIP_OK) ? !init : init);
375 
376 		/* print result, but skip OK messages if required */
377 		if (init || info->processing_result != 0 || !(opt.flags & OPT_SKIP_OK) || HP_FAILED(info->hp->bit_flags))
378 			return print_check_result(info, print_name, !init);
379 	}
380 	return 0;
381 }
382 
383 /* functions to output file info without percents */
384 
385 /**
386  * Print file name in the verification mode.
387  * No information is printed in other modes.
388  *
389  * @param info pointer to the file-info structure
390  * @return 0 on success, -1 if output to rhash_data.out failed
391  */
dummy_init_percents(struct file_info * info)392 static int dummy_init_percents(struct file_info* info)
393 {
394 	return print_results_on_check(info, 1);
395 }
396 
397 /**
398  * Print file check results without printing percents.
399  * Information is printed only in the verification mode.
400  *
401  * @param info pointer to the file-info structure
402  * @param process_res non-zero if error occurred while hashing/checking
403  * @return 0 on success, -1 if output to rhash_data.out failed
404  */
dummy_finish_percents(struct file_info * info,int process_res)405 static int dummy_finish_percents(struct file_info* info, int process_res)
406 {
407 	info->processing_result = process_res;
408 	return print_results_on_check(info, 0);
409 }
410 
411 /* functions to output file info with simple multi-line wget-like percents */
412 
413 /**
414  * Initialize dots percent mode.
415  *
416  * @param info pointer to the file-info structure
417  * @return 0 on success, -1 if output to rhash_data.out failed
418  */
dots_init_percents(struct file_info * info)419 static int dots_init_percents(struct file_info* info)
420 {
421 	int res = fflush(rhash_data.out);
422 	fflush(rhash_data.log);
423 	percents.points = 0;
424 	if (print_results_on_check(info, 1) < 0)
425 		res = -1;
426 	return res;
427 }
428 
429 /**
430  * Finish dots percent mode. If in the verification mode,
431  * then print the results of file check.
432  *
433  * @param info pointer to the file-info structure
434  * @param process_res non-zero if error occurred while hashing/checking
435  * @return 0 on success, -1 if output to rhash_data.out failed
436  */
dots_finish_percents(struct file_info * info,int process_res)437 static int dots_finish_percents(struct file_info* info, int process_res)
438 {
439 	char buf[80];
440 	info->processing_result = process_res;
441 	if ((percents.points % 74) != 0) {
442 		log_msg("%s 100%%\n", str_set(buf, ' ', 74 - (percents.points % 74)));
443 	}
444 	return print_results_on_check(info, 0);
445 }
446 
447 /**
448  * Output percents by printing one dot per each processed 1MiB.
449  *
450  * @param info pointer to the file-info structure
451  * @param offset the number of hashed bytes
452  */
dots_update_percents(struct file_info * info,uint64_t offset)453 static void dots_update_percents(struct file_info* info, uint64_t offset)
454 {
455 	const int pt_size = 1024 * 1024; /* 1MiB */
456 	offset -= info->msg_offset; /* get real file offset */
457 	if ( (offset % pt_size) != 0 ) return;
458 
459 	if (percents.points == 0) {
460 		if (opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED)) {
461 			rsh_fprintf(rhash_data.log, _("\nChecking %s\n"), file_get_print_path(info->file, FPathPrimaryEncoding | FPathNotNull));
462 		} else {
463 			rsh_fprintf(rhash_data.log, _("\nProcessing %s\n"), file_get_print_path(info->file, FPathPrimaryEncoding | FPathNotNull));
464 		}
465 		fflush(rhash_data.log);
466 	}
467 	putc('.', rhash_data.log);
468 
469 	if (((++percents.points) % 74) == 0) {
470 		if (info->size > 0) {
471 			int perc = (int)( offset * 100.0 / (uint64_t)info->size + 0.5 );
472 			rsh_fprintf(rhash_data.log, "  %2u%%\n", perc);
473 			fflush(rhash_data.log);
474 		} else {
475 			putc('\n', rhash_data.log);
476 		}
477 	}
478 }
479 
480 /* console one-line percents */
481 
482 /**
483  * Initialize one-line percent mode.
484  *
485  * @param info pointer to the file-info structure
486  * @return 0 on success, -1 if output to rhash_data.out failed
487  */
p_init_percents(struct file_info * info)488 static int p_init_percents(struct file_info* info)
489 {
490 	int res = fflush(rhash_data.out);
491 	fflush(rhash_data.log);
492 	/* ingnore output errors, while logging to rhash_data.log */
493 	print_aligned_filepath(rhash_data.log, info);
494 	percents.points = 0;
495 	percents.same_output = (rhash_data.out == stdout && isatty(0) &&
496 			rhash_data.log == stderr && isatty(1));
497 	percents.ticks = rhash_get_ticks();
498 	return res;
499 }
500 
501 /**
502  * Output one-line percents by printing them after file path.
503  * If the total file length is unknow (i.e. hashing stdin),
504  * then output a rotating stick.
505  *
506  * @param info pointer to the file-info structure
507  * @param offset the number of hashed bytes
508  */
p_update_percents(struct file_info * info,uint64_t offset)509 static void p_update_percents(struct file_info* info, uint64_t offset)
510 {
511 	static const char rotated_bar[4] = {'-', '\\', '|', '/'};
512 	int perc = 0;
513 	unsigned ticks;
514 
515 	if (info->size > 0) {
516 		offset -= info->msg_offset;
517 		/* use only two digits to display percents: 0%-99% */
518 		perc = (int)( offset * 99.9 / (uint64_t)info->size );
519 		if (percents.points == perc) return;
520 	}
521 
522 	/* update percents no more than 20 times per second */
523 	ticks = rhash_get_ticks(); /* clock ticks count in milliseconds */
524 	if ((unsigned)(ticks - percents.ticks) < 50)
525 		return;
526 
527 	/* output percents or a rotated bar */
528 	if (info->size > 0) {
529 		rsh_fprintf(rhash_data.log, "%u%%\r", perc);
530 		percents.points = perc;
531 	} else {
532 		rsh_fprintf(rhash_data.log, "%c\r", rotated_bar[(percents.points++) & 3]);
533 	}
534 	print_aligned_filepath(rhash_data.log, info);
535 	fflush(rhash_data.log);
536 	percents.ticks  = ticks;
537 }
538 
539 /**
540  * Finish one-line percent mode. If in the verification mode,
541  * then print the results of file check.
542  *
543  * @param info pointer to the file-info structure
544  * @param process_res non-zero if error occurred while hashing/checking
545  * @return 0 on success, -1 if output to rhash_data.out failed
546  */
p_finish_percents(struct file_info * info,int process_res)547 static int p_finish_percents(struct file_info* info, int process_res)
548 {
549 	int need_check_result = (opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED)) &&
550 		!((opt.flags & OPT_SKIP_OK) && process_res == 0 && !HP_FAILED(info->hp->bit_flags));
551 	info->processing_result = process_res;
552 
553 	if (percents.same_output && need_check_result) {
554 		return print_check_result(info, 0, 1);
555 	} else {
556 		rsh_fprintf(rhash_data.log, "100%%\n");
557 		fflush(rhash_data.log);
558 		if (need_check_result)
559 			return print_check_result(info, 1, 1);
560 	}
561 	return 0;
562 }
563 
564 /* three methods of percents output */
565 struct percents_output_info_t dummy_perc = {
566 	dummy_init_percents, 0, dummy_finish_percents, "dummy"
567 };
568 struct percents_output_info_t dots_perc = {
569 	dots_init_percents, dots_update_percents, dots_finish_percents, "dots"
570 };
571 struct percents_output_info_t p_perc = {
572 	p_init_percents, p_update_percents, p_finish_percents, "digits"
573 };
574 
575 /**
576  * Initialize given output stream.
577  *
578  * @param p_stream the stream to initialize
579  * @param stream_path the path to the log file, or NULL, to use the default stream
580  * @param default_stream the default stream value, for the case of invalid stream_path
581  */
setup_log_stream(FILE ** p_stream,file_t * file,const opt_tchar * stream_path,FILE * default_stream)582 static void setup_log_stream(FILE** p_stream, file_t* file, const opt_tchar* stream_path, FILE* default_stream)
583 {
584 	FILE* result;
585 	/* set to the default stream, to enable error reporting via log_error_file_t() */
586 	*p_stream = default_stream;
587 	if (!stream_path) {
588 		file_init_by_print_path(file, NULL, (default_stream == stdout ? "(stdout)" : "(stderr)"), FileIsStdStream);
589 		return;
590 	}
591 	file_init(file, stream_path, FileInitReusePath);
592 	result = file_fopen(file, FOpenWrite);
593 	if (!result) {
594 		log_error_file_t(file);
595 		rsh_exit(2);
596 	}
597 	*p_stream = result;
598 }
599 
600 /**
601  * Initialize pointers to output functions.
602  */
setup_output(void)603 void setup_output(void)
604 {
605 	setup_log_stream(&rhash_data.log, &rhash_data.log_file, opt.log, stderr);
606 	setup_log_stream(&rhash_data.out, &rhash_data.out_file, opt.output, stdout);
607 }
608 
setup_percents(void)609 void setup_percents(void)
610 {
611 	if (opt.flags & OPT_PERCENTS) {
612 		/* NB: we don't use _fileno() cause it is not in ISO C90, and so
613 		 * is incompatible with the GCC -ansi option */
614 		if (rhash_data.log == stderr && isatty(2)) {
615 			/* use one-line percents by default on console */
616 			percents_output  = &p_perc;
617 			IF_WINDOWS(hide_cursor());
618 		} else {
619 			/* print percents as dots */
620 			percents_output  = &dots_perc;
621 		}
622 	} else {
623 		percents_output  = &dummy_perc; /* no percents */
624 	}
625 }
626 
627 /* misc output functions */
628 
629 /**
630  * Print the "Verifying <FILE>" heading line.
631  *
632  * @param file the file containing message digests to verify
633  * @return 0 on success, -1 on fail with error code stored in errno
634  */
print_verifying_header(file_t * file)635 int print_verifying_header(file_t* file)
636 {
637 	char dash_line[84];
638 	int count = fprintf_file_t(rhash_data.out, _("\n--( Verifying %s )"), file, OutCountSymbols);
639 	int tail_dash_len = (0 < count && count < 81 ? 81 - count : 2);
640 	int res = rsh_fprintf(rhash_data.out, "%s\n", str_set(dash_line, '-', tail_dash_len));
641 	if (res >= 0)
642 		res = fflush(rhash_data.out);
643 	return (count < 0 ? count : res);
644 }
645 
646 /**
647  * Print a line consisting of 80 dashes.
648  *
649  * @return 0 on success, -1 on fail with error code stored in errno
650  */
print_verifying_footer(void)651 int print_verifying_footer(void)
652 {
653 	char dash_line[84];
654 	return rsh_fprintf(rhash_data.out, "%s\n", str_set(dash_line, '-', 80));
655 }
656 
657 /**
658  * Print total statistics of hash file checking.
659  *
660  * @return 0 on success, -1 on i/o error with error code stored in errno
661  */
print_check_stats(void)662 int print_check_stats(void)
663 {
664 	int res;
665 	if (rhash_data.processed == rhash_data.ok) {
666 		/* NOTE: don't use puts() here cause it messes up with fprintf stdout buffering */
667 		const char* message = (rhash_data.processed > 0 ?
668 			_("Everything OK\n") :
669 			_("Nothing to verify\n"));
670 		res = PRINTF_RES(rsh_fprintf(rhash_data.out, "%s", message));
671 	} else {
672 		res = PRINTF_RES(rsh_fprintf(rhash_data.out, _("Errors Occurred: Errors:%-3u Miss:%-3u Success:%-3u Total:%-3u\n"),
673 			rhash_data.processed - rhash_data.ok - rhash_data.miss, rhash_data.miss, rhash_data.ok, rhash_data.processed));
674 	}
675 	return (fflush(rhash_data.out) < 0 ? -1 : res);
676 }
677 
678 /**
679  * Print file processing times.
680  */
print_file_time_stats(struct file_info * info)681 void print_file_time_stats(struct file_info* info)
682 {
683 	print_time_stats(info->time, info->size, 0);
684 }
685 
686 /**
687  * Print processing time statistics.
688  */
print_time_stats(double time,uint64_t size,int total)689 void print_time_stats(double time, uint64_t size, int total)
690 {
691 	double speed = (time == 0 ? 0 : (double)(int64_t)size / 1048576.0 / time);
692 	if (total) {
693 		rsh_fprintf(rhash_data.log, _("Total %.3f sec, %4.2f MBps\n"), time, speed);
694 	} else {
695 		rsh_fprintf(rhash_data.log, _("Calculated in %.3f sec, %4.2f MBps\n"), time, speed);
696 	}
697 	fflush(rhash_data.log);
698 }
699