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