hash_print.c 19.1 KB
Newer Older
1
/* hash_print.c - output hash sums using printf-like format */
2 3 4 5 6

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
7 8 9 10
#ifdef _WIN32
# include <fcntl.h>
# include <io.h>
#endif /* _WIN32 */
11

12
#include "hash_print.h"
13
#include "calc_sums.h"
14 15
#include "common_func.h"
#include "file.h"
16
#include "parse_cmdline.h"
17
#include "win_utils.h"
18
#include "librhash/rhash.h"
19

20
/*=========================================================================
21
* Formatted output functions and structures
22 23 24 25 26
*=========================================================================*/

/**
 * The table with information about hash functions.
 */
27 28
print_hash_info hash_info_table[32];

29 30 31
/**
 * Possible types of a print_item.
 */
32
enum {
33 34 35 36 37 38 39 40 41 42 43
	PRINT_ED2K_LINK = 0x100000,
	PRINT_FLAG_UPPERCASE = 0x200000,
	PRINT_FLAG_RAW    = 0x0400000,
	PRINT_FLAG_HEX    = 0x0800000,
	PRINT_FLAG_BASE32 = 0x1000000,
	PRINT_FLAG_BASE64 = 0x2000000,
	PRINT_FLAG_PAD_WITH_ZERO = 0x4000000,
	PRINT_FLAGS_ALL = PRINT_FLAG_UPPERCASE | PRINT_FLAG_PAD_WITH_ZERO | PRINT_FLAG_RAW
		| PRINT_FLAG_HEX | PRINT_FLAG_BASE32 | PRINT_FLAG_BASE64,
	PRINT_STR = 0x10000000,
	PRINT_ZERO,
44
	PRINT_NEWLINE,
45 46 47 48 49
	PRINT_FILEPATH,
	PRINT_BASENAME,
	PRINT_URLNAME,
	PRINT_SIZE,
	PRINT_MTIME /*PRINT_ATIME, PRINT_CTIME*/
50 51
};

52
/* parse a token following a percent sign '%' */
53 54 55 56 57 58 59
static print_item* parse_percent_item(const char** str);

/**
 * Allocate new print_item.
 *
 * @param flags the print_item flags
 * @param hash_id optional hash_id
60
 * @param data optional string to store
61 62 63 64
 * @return allocated print_item
 */
static print_item* new_print_item(unsigned flags, unsigned hash_id, const char *data)
{
65 66 67 68 69 70 71
	print_item* item = (print_item*)rsh_malloc(sizeof(print_item));
	item->flags = flags;
	item->hash_id = hash_id;
	item->width = 0;
	item->data = (data ? rsh_strdup(data) : NULL);
	item->next = NULL;
	return item;
72 73 74
}

/**
75
 * Parse an escaped sequence in a printf-like format string.
76 77 78 79 80 81 82
 *
 * @param pformat pointer to the sequence, the pointer
 *   is changed to point to the next symbol after parsed sequence
 * @return result character
 */
static char parse_escaped_char(const char **pformat)
{
83
	const char* start = *pformat;
84
	switch ( *((*pformat)++) ) {
85
		case '0': return '\0';
86 87 88 89 90 91
		case 't': return '\t';
		case 'r': return '\r';
		case 'n': return '\n';
		case '\\': return '\\';
		case 'x':
			/* \xNN byte with hexadecimal value NN (1 to 2 digits) */
92
			if ( IS_HEX(**pformat) ) {
93 94 95
				int ch;
				ch = (**pformat <= '9' ? **pformat & 15 : (**pformat + 9) & 15);
				(*pformat) ++;
96
				if (IS_HEX(**pformat)) {
97 98 99 100
					/* read the second digit */
					ch = 16 * ch + (**pformat <= '9' ? **pformat & 15 : (**pformat + 9) & 15);
					(*pformat)++;
				}
101
				return ch;
102 103 104 105 106
			}
			break;
		default:
			(*pformat)--;
			/* \NNN - character with octal value NNN (1 to 3 digits) */
107
			if ('0' < **pformat && **pformat <= '7') {
108
				int ch = *((*pformat)++) - '0';
109
				if ('0' <= **pformat && **pformat <= '7') {
110
					ch = ch * 8 + *((*pformat)++) - '0';
111
					if ('0' <= **pformat && **pformat <= '7')
112 113 114 115 116 117 118
						ch = ch * 8 + *((*pformat)++) - '0';
				}
				return (char)ch;
			}
	}
	*pformat = start;
	return '\\';
119 120 121 122 123 124 125 126 127
}

/**
 * Parse format string.
 *
 * @return a print_item list with parsed information
 */
print_item* parse_print_string(const char* format, unsigned *sum_mask)
{
128 129 130 131 132 133 134
	char *buf, *p;
	print_item *list = NULL, **tail, *item = NULL;

	buf = p = (char*)rsh_malloc( strlen(format) + 1 );
	tail = &list;
	*sum_mask = 0;

135 136
	for (;;) {
		while (*format && *format != '%' && *format != '\\')
137 138
			*(p++) = *(format++);

139
		if (*format == '\\') {
140 141 142
			format++;
			*p = parse_escaped_char(&format);
			if (*p == '\0') {
143
				item = new_print_item(PRINT_ZERO, 0, NULL);
144 145 146 147
#ifdef _WIN32
			} else if (*p == '\n') {
				item = new_print_item(PRINT_NEWLINE, 0, NULL);
#endif
148
			} else {
149
				p++;
150 151
				continue;
			}
152 153
		} else if (*format == '%') {
			if ( *(++format) == '%' ) {
154 155 156 157
				*(p++) = *format++;
				continue;
			} else {
				item = parse_percent_item(&format);
158
				if (!item) {
159 160 161
					*(p++) = '%';
					continue;
				}
162
				if (item->hash_id)
163 164 165
					*sum_mask |= item->hash_id;
			}
		}
166
		if (p > buf || (!*format && list == NULL && item == NULL)) {
167 168 169 170 171
			*p = '\0';
			*tail = new_print_item(PRINT_STR, 0, buf);
			tail = &(*tail)->next;
			p = buf;
		}
172
		if (item) {
173 174 175 176
			*tail = item;
			tail = &item->next;
			item = NULL;
		}
177
		if (!*format)
178 179 180 181
			break;
	};
	free(buf);
	return list;
182 183 184 185 186
}

/**
 * Convert given case-insensitive name to a printf directive id
 *
187
 * @param name printf directive name (not a 0-terminated)
188 189 190 191 192
 * @param length name length
 * @return directive id on success, 0 on fail
 */
static unsigned printf_name_to_id(const char* name, size_t length, unsigned *flags)
{
193 194 195 196 197
	char buf[20];
	size_t i;
	print_hash_info *info = hash_info_table;
	unsigned bit;

198 199
	if (length > (sizeof(buf)-1)) return 0;
	for (i = 0; i < length; i++) buf[i] = tolower(name[i]);
200 201

	/* check for old '%{urlname}' directive for compatibility */
202
	if (length == 7 && memcmp(buf, "urlname", 7) == 0) {
203 204
		*flags = PRINT_URLNAME;
		return 0;
205
	} else if (length == 5 && memcmp(buf, "mtime", 5) == 0) {
206 207 208 209
		*flags = PRINT_MTIME;
		return 0;
	}

210 211
	for (bit = 1; bit <= RHASH_ALL_HASHES; bit = bit << 1, info++) {
		if (memcmp(buf, info->short_name, length) == 0 &&
212 213 214
			info->short_name[length] == 0) return bit;
	}
	return 0;
215 216 217
}

/**
218
 * Parse a token followed by a percent sign in a printf-like format string.
219 220 221 222 223
 *
 * @return a print_item with parsed information
 */
print_item* parse_percent_item(const char** str)
{
224 225 226 227 228 229 230 231 232 233 234 235
	const char* format = *str;
	const char* p = NULL;
	unsigned hash_id = 0;
	unsigned modifier_flags = 0;
	int id_found = 0;
	int width = 0;
	int pad_with_zero_bit = 0;
	print_item* item = NULL;

	static const char *short_hash = "CMHTGWRAE";
	static const char *short_other = "Llpfus";
	static const unsigned hash_ids[] = {
236
		RHASH_CRC32, RHASH_MD5, RHASH_SHA1, RHASH_TTH, RHASH_GOST12_256,
237 238 239 240 241 242
		RHASH_WHIRLPOOL, RHASH_RIPEMD160, RHASH_AICH, RHASH_ED2K
	};
	static const unsigned other_flags[] = {
		PRINT_ED2K_LINK, PRINT_ED2K_LINK, PRINT_FILEPATH, PRINT_BASENAME,
		PRINT_URLNAME, PRINT_SIZE
	};
243 244
	/* detect the padding by zeros */
	if (*format == '0') {
245 246 247 248
		pad_with_zero_bit = PRINT_FLAG_PAD_WITH_ZERO;
		format++;
	}

249 250
	/* parse the 'b','B','x' and '@' flags */
	if (*format == 'x') {
251 252
		modifier_flags |= PRINT_FLAG_HEX;
		format++;
253
	} else if (*format == 'b') {
254 255
		modifier_flags |= PRINT_FLAG_BASE32;
		format++;
256
	} else if (*format == 'B') {
257 258
		modifier_flags |= PRINT_FLAG_BASE64;
		format++;
259
	} else if (*format == '@') {
260 261 262
		modifier_flags |= PRINT_FLAG_RAW;
		format++;
	}
263
	for (; isdigit((unsigned char)*format); format++) width = 10 * width + (*format - '0');
264

265 266 267
	/* if a complicated token enconuntered */
	if (*format == '{') {
		/* parse the token of the kind "%{some-token}" */
268
		const char* p = ++format;
269 270
		for (; isalnum((unsigned char)*p) || (*p == '-'); p++);
		if (*p == '}') {
271 272
			hash_id = printf_name_to_id(format, (int)(p - (format)), &modifier_flags);
			format--;
273
			if (hash_id || modifier_flags == PRINT_URLNAME || modifier_flags == PRINT_MTIME) {
274 275 276 277 278
				/* set uppercase flag if the first letter of printf-entity is uppercase */
				modifier_flags |= (format[1] & 0x20 ? 0 : PRINT_FLAG_UPPERCASE);
				format = p;
				id_found = 1;
			}
279 280 281
		} else {
			format--;
		}
282 283
	}

284 285
	/* if still not found a token denoting a hash function */
	if (!id_found) {
286
		const char upper = *format & ~0x20;
287 288 289 290
		/* if the string terminated just after the '%' character */
		if ( *format == '\0' ) return NULL;
		/* look for a known token */
		if ( (p = strchr(short_hash, upper)) ) {
291 292 293
			assert( (p - short_hash) < (int)(sizeof(hash_ids) / sizeof(unsigned)) );
			hash_id = hash_ids[p - short_hash];
			modifier_flags |= (*format & 0x20 ? 0 : PRINT_FLAG_UPPERCASE);
294 295
		}
		else if ( (p = strchr(short_other, *format)) ) {
296 297 298
			assert( (p - short_other) < (int)(sizeof(other_flags) / sizeof(unsigned)) );
			modifier_flags = other_flags[p - short_other];

299
			if (modifier_flags == PRINT_ED2K_LINK) {
300 301 302 303
				modifier_flags |= (*p & 0x20 ? 0 : PRINT_FLAG_UPPERCASE);
				hash_id = RHASH_ED2K | RHASH_AICH;
			}
		} else {
304
			return 0; /* no valid token found */
305 306 307 308 309 310 311 312
		}
	}
	modifier_flags |= pad_with_zero_bit;

	item = new_print_item(modifier_flags, hash_id, NULL);
	item->width = width;
	*str = ++format;
	return item;
313 314 315 316 317 318 319 320 321 322 323 324
}

/**
 * Print EDonkey 2000 url for given file to a stream.
 *
 * @param out the stream where to print url to
 * @param filename the file name
 * @param filesize the file size
 * @param sums the file hash sums
 */
static void fprint_ed2k_url(FILE* out, struct file_info *info, int print_type)
{
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
	const char *filename = get_basename(file_info_get_utf8_print_path(info));
	int upper_case = (print_type & PRINT_FLAG_UPPERCASE ? RHPR_UPPERCASE : 0);
	int len = urlencode(NULL, filename) + int_len(info->size) + (info->sums_flags & RHASH_AICH ? 84 : 49);
	char* buf = (char*)rsh_malloc( len + 1 );
	char* dst = buf;

	assert(info->sums_flags & (RHASH_ED2K|RHASH_AICH));
	assert(info->rctx);

	strcpy(dst, "ed2k://|file|");
	dst += 13;
	dst += urlencode(dst, filename);
	*dst++ = '|';
	sprintI64(dst, info->size, 0);
	dst += strlen(dst);
	*dst++ = '|';
	rhash_print(dst, info->rctx, RHASH_ED2K, upper_case);
	dst += 32;
343
	if ((info->sums_flags & RHASH_AICH) != 0) {
344 345 346 347 348
		strcpy(dst, "|h=");
		rhash_print(dst += 3, info->rctx, RHASH_AICH, RHPR_BASE32 | upper_case);
		dst += 32;
	}
	strcpy(dst, "|/");
349
	rsh_fprintf(out, "%s", buf);
350
	free(buf);
351 352 353 354 355 356 357 358
}

/**
 * Output aligned uint64_t number to specified output stream.
 *
 * @param out the stream to output to
 * @param filesize the 64-bit integer to output, usually a file size
 * @param width minimal width of integer to output
359
 * @param flag =1 if the integer shall be prepended by zeros
360 361 362
 */
static void fprintI64(FILE* out, uint64_t filesize, int width, int zero_pad)
{
363 364 365
	char *buf = (char*)rsh_malloc(width > 40 ? width + 1 : 41);
	int len = int_len(filesize);
	sprintI64(buf, filesize, width);
366
	if (len < width && zero_pad) {
367 368
		memset(buf, '0', width-len);
	}
369
	rsh_fprintf(out, "%s", buf);
370
	free(buf);
371 372
}

373
/**
374
* Print time formatted as hh:mm.ss YYYY-MM-DD to a file stream.
375 376 377 378 379 380 381 382 383 384 385 386 387 388
*
* @param out the stream to print the time to
* @param time the time to print
*/
static void print_time(FILE *out, time_t time)
{
	struct tm *t = localtime(&time);
	static struct tm zero_tm;
	if (t == NULL) {
		/* if a strange day, then print `00:00.00 1900-01-00' */
		t = &zero_tm;
		t->tm_hour = t->tm_min = t->tm_sec =
			t->tm_year = t->tm_mon = t->tm_mday = 0;
	}
389
	rsh_fprintf(out, "%02u:%02u.%02u %4u-%02u-%02u", t->tm_hour, t->tm_min,
390 391 392 393
		t->tm_sec, (1900 + t->tm_year), t->tm_mon + 1, t->tm_mday);
}

/**
394
* Print time formatted as hh:mm.ss YYYY-MM-DD to a file stream.
395 396 397 398 399 400 401 402 403
*
* @param out the stream to print the time to
* @param time the time to print
*/
static void print_time64(FILE *out, uint64_t time)
{
	print_time(out, (time_t)time);
}

404
/**
405
 * Print formatted file information to given output stream.
406 407 408 409 410 411 412
 *
 * @param out the stream to print information to
 * @param list the format according to which information shall be printed
 * @param info the file information
 */
void print_line(FILE* out, print_item* list, struct file_info *info)
{
413 414 415
	const char* basename = get_basename(info->print_path), *tmp;
	char *url = NULL, *ed2k_url = NULL;
	char buffer[130];
416 417 418 419 420
#ifdef _WIN32
	/* switch to binary mode to correctly output binary hashes */
	int out_fd = _fileno(out);
	int old_mode = (out_fd > 0 && !isatty(out_fd) ? _setmode(out_fd, _O_BINARY) : -1);
#endif
421

422
	for (; list; list = list->next) {
423 424 425 426
		int print_type = list->flags & ~(PRINT_FLAGS_ALL);
		size_t len;

		/* output a hash function digest */
427
		if (list->hash_id && print_type != PRINT_ED2K_LINK) {
428 429 430 431 432 433
			unsigned hash_id = list->hash_id;
			int print_flags = (list->flags & PRINT_FLAG_UPPERCASE ? RHPR_UPPERCASE : 0)
				| (list->flags & PRINT_FLAG_RAW ? RHPR_RAW : 0)
				| (list->flags & PRINT_FLAG_BASE32 ? RHPR_BASE32 : 0)
				| (list->flags & PRINT_FLAG_BASE64 ? RHPR_BASE64 : 0)
				| (list->flags & PRINT_FLAG_HEX ? RHPR_HEX : 0);
434
			if ((hash_id == RHASH_GOST94 || hash_id == RHASH_GOST94_CRYPTOPRO) && (opt.flags & OPT_GOST_REVERSE))
435 436 437 438 439 440
				print_flags |= RHPR_REVERSE;

			len = rhash_print(buffer, info->rctx, hash_id, print_flags);
			assert(len < sizeof(buffer));

			/* output the hash, exit on fail */
441
			rsh_fwrite(buffer, 1, len, out);
442 443 444
			continue;
		}

445
		/* output other special items: filepath, URL-encoded filename etc. */
446
		switch (print_type) {
447
			case PRINT_STR:
448
				rsh_fprintf(out, "%s", list->data);
449 450
				break;
			case PRINT_ZERO: /* the '\0' character */
451
				rsh_fprintf(out, "%c", 0);
452
				break;
453 454 455 456 457
#ifdef _WIN32
			case PRINT_NEWLINE:
				rsh_fprintf(out, "%s", "\r\n");
				break;
#endif
458
			case PRINT_FILEPATH:
459
				rsh_fprintf(out, "%s", info->print_path);
460 461
				break;
			case PRINT_BASENAME: /* the filename without directory */
462
				rsh_fprintf(out, "%s", basename);
463 464
				break;
			case PRINT_URLNAME: /* URL-encoded filename */
465
				if (!url) {
466 467 468 469
					tmp = get_basename(file_info_get_utf8_print_path(info));
					url = (char*)rsh_malloc(urlencode(NULL, tmp) + 1);
					urlencode(url, tmp);
				}
470
				rsh_fprintf(out, "%s", url);
471 472
				break;
			case PRINT_MTIME: /* the last-modified tine of the filename */
473
				print_time64(out, info->file->mtime);
474 475 476 477 478 479 480 481 482 483 484
				break;
			case PRINT_SIZE: /* file size */
				fprintI64(out, info->size, list->width, (list->flags & PRINT_FLAG_PAD_WITH_ZERO));
				break;
			case PRINT_ED2K_LINK:
				fprint_ed2k_url(out, info, list->flags);
				break;
		}
	}
	free(url);
	free(ed2k_url);
485 486 487 488 489
	fflush(out);
#ifdef _WIN32
	if (old_mode >= 0)
		_setmode(out_fd, old_mode);
#endif
490 491 492 493 494 495 496 497 498
}

/**
 * Release memory allocated by given print_item list.
 *
 * @param list the list to free
 */
void free_print_list(print_item* list)
{
499
	while (list) {
500
		print_item* next = list->next;
501
		free((char*)list->data);
502 503 504
		free(list);
		list = next;
	}
505 506
}

507 508 509 510
/*=========================================================================
* initialization of internal data
*=========================================================================*/

511
/**
512
 * Initialize information about hashes, stored in the
513 514 515 516
 * hash_info_table global variable.
 */
void init_hash_info_table(void)
{
517
	unsigned bit;
518
	unsigned short_opt_mask = RHASH_CRC32 | RHASH_MD5 | RHASH_SHA1 | RHASH_TTH | RHASH_ED2K |
519
		RHASH_AICH | RHASH_WHIRLPOOL | RHASH_RIPEMD160 | RHASH_GOST12_256 | OPT_ED2K_LINK;
520 521 522 523 524 525
	char* short_opt = "cmhteawrgl";
	print_hash_info *info = hash_info_table;
	unsigned fullmask = RHASH_ALL_HASHES | OPT_ED2K_LINK;

	memset(hash_info_table, 0, sizeof(hash_info_table));

526
	for (bit = 1; bit && bit <= fullmask; bit = bit << 1) {
527 528 529
		const char *p;
		char *e, *d;

530 531 532
		if (!(bit & fullmask))
			continue;

533 534 535
		info->short_char = ((bit & short_opt_mask) != 0 && *short_opt ?
			*(short_opt++) : 0);

536
		info->name = (bit & RHASH_ALL_HASHES ? rhash_get_name(bit) : "ED2K-LINK");
537
		assert(strlen(info->name) < 19);
538
		p = info->name;
539
		d = info->short_name;
540
		e = info->short_name + 19; /* buffer overflow protection */
541

542
		if (memcmp(info->name, "SHA", 3) == 0 || memcmp(info->name, "GOST", 4) == 0) {
543 544 545 546 547 548 549 550 551 552 553 554 555
			strcpy(d, p);
			for (; *d && d < e; d++) {
				if ('A' <= *d && *d <= 'Z') {
					*d |= 0x20;
				}
			}
		} else {
			for (; *p && d < e; p++) {
				if (*p != '-' || p[1] >= '9') {
					*(d++) = (*p | 0x20);
				}
			}
		}
556
		*d = 0;
557
		++info;
558
	}
559 560 561 562 563
}

/**
 * Initialize printf string according to program options.
 * The function is called only when a printf format string is not specified
564
 * from command line, so it should be constructed from other options.
565 566 567 568 569
 *
 * @param out a string buffer to place the resulting format string into.
 */
void init_printf_format(strbuf_t* out)
{
570
	const char* fmt, *tail = 0;
571
	unsigned bit, index = 0;
572 573
	int uppercase;
	char up_flag;
574
	unsigned force_base32_mask = 0;
575

576
	if (!opt.fmt) {
577
		/* print SFV header for CRC32 or if no hash sums options specified */
578 579 580 581 582 583 584 585
		opt.fmt = (opt.sum_flags == RHASH_CRC32 || !opt.sum_flags ? FMT_SFV : FMT_SIMPLE);
	}
	uppercase = ((opt.flags & OPT_UPPERCASE) ||
		(!(opt.flags & OPT_LOWERCASE) && (opt.fmt & FMT_SFV)));
	up_flag = (uppercase ? ~0x20 : 0xFF);

	rsh_str_ensure_size(out, 1024); /* allocate big enough buffer */

586
	if (opt.sum_flags & OPT_ED2K_LINK) {
587 588 589 590 591
		rsh_str_append_n(out, "%l", 2);
		out->str[1] &= up_flag;
		return;
	}

592
	if (opt.sum_flags == 0) return;
593

594
	if (opt.fmt == FMT_BSD) {
595
		fmt = "\003(%p) = \001\\n";
596
	} else if (opt.fmt == FMT_MAGNET) {
597 598
		rsh_str_append(out, "magnet:?xl=%s&dn=%{urlname}");
		fmt = "&xt=urn:\002:\001";
599
		force_base32_mask = (RHASH_SHA1 | RHASH_BTIH);
600
		tail = "\\n";
601
	} else if (opt.fmt == FMT_SIMPLE && 0 == (opt.sum_flags & (opt.sum_flags - 1))) {
602 603 604 605 606 607 608 609
		fmt = "\001  %p\\n";
	} else {
		rsh_str_append_n(out, "%p", 2);
		fmt = (opt.fmt == FMT_SFV ? " \001" : "  \001");
		tail = "\\n";
	}

	/* loop by hashes */
610
	for (bit = 1 << index; bit && bit <= opt.sum_flags; bit = bit << 1, index++) {
611 612 613
		const char *p;
		print_hash_info *info;

614
		if ((bit & opt.sum_flags) == 0) continue;
615 616
		p = fmt;
		info = &hash_info_table[index];
617

618 619
		/* ensure the output buffer have enough space */
		rsh_str_ensure_size(out, out->len + 256);
620

621
		for (;;) {
622
			int i;
623 624 625
			while (*p >= 0x20) out->str[out->len++] = *(p++);
			if (*p == 0) break;
			switch ((int)*(p++)) {
626 627
				case 1:
					out->str[out->len++] = '%';
628
					if ( (bit & force_base32_mask) != 0 ) {
629 630
						out->str[out->len++] = 'b';
					}
631
					if (info->short_char) out->str[out->len++] = info->short_char & up_flag;
632 633 634 635 636 637 638 639 640 641
					else {
						char *letter;
						out->str[out->len++] = '{';
						letter = out->str + out->len;
						rsh_str_append(out, info->short_name);
						*letter &= up_flag;
						out->str[out->len++] = '}';
					}
					break;
				case 2:
642
					rsh_str_append(out, rhash_get_magnet_name(bit));
643 644 645 646
					break;
				case 3:
					rsh_str_append(out, info->name);
					i = (int)strlen(info->name);
647
					for (i = (i < 5 ? 6 - i : 1); i > 0; i--) out->str[out->len++] = ' ';
648 649 650 651
					break;
			}
		}
	}
652
	if (tail) {
653 654 655
		rsh_str_append(out, tail);
	}
	out->str[out->len] = '\0';
656
}
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676

/*=========================================================================
* SFV format output functions
*=========================================================================*/

/**
* Format file information into SFV line and print it to the specified stream.
*
* @param out the stream to print the file information to
* @param file the file info to print
* @return 0 on success, -1 on fail with error code stored in errno
*/
int print_sfv_header_line(FILE* out, file_t* file, const char* printpath)
{
	char buf[24];

	/* skip stdin stream */
	if ((file->mode & FILE_IFSTDIN) != 0) return 0;

	/* skip file if it can't be opened with exclusive sharing rights */
677
	if (file_is_write_locked(file)) return 0;
678 679 680 681 682

	if (!printpath) printpath = file->path;
	if (printpath[0] == '.' && IS_PATH_SEPARATOR(printpath[1])) printpath += 2;

	sprintI64(buf, file->size, 12);
683
	rsh_fprintf(out, "; %s  ", buf);
684
	print_time64(out, file->mtime);
685
	rsh_fprintf(out, " %s\n", printpath);
686 687 688 689 690 691 692 693 694 695 696 697 698 699
	return 0;
}

/**
* Print an SFV header banner. The banner consist of 3 comment lines,
* with the program description and current time.
*
* @param out a stream to print to
*/
void print_sfv_banner(FILE* out)
{
	time_t cur_time = time(NULL);
	struct tm *t = localtime(&cur_time);
	if (t) {
700
		rsh_fprintf(out, _("; Generated by %s v%s on %4u-%02u-%02u at %02u:%02u.%02u\n"),
701 702
			PROGRAM_NAME, get_version_string(),
			(1900 + t->tm_year), t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
703
		rsh_fprintf(out, _("; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/\n;\n"));
704 705
	}
}