1 /* vim: set sw=4 ts=4 noexpandtab : */
2 /*
3  * Copyright (C) 2007-2019 Abel Cheung.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. Neither the name of the project nor the names of its contributors
15  *    may be used to endorse or promote products derived from this software
16  *    without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include "config.h"
32 
33 #include <errno.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #if HAVE_SETLOCALE
37 #include <locale.h>
38 #endif
39 #include "utils.h"
40 #include <glib/gi18n.h>
41 #include <glib/gstdio.h>
42 
43 #ifdef G_OS_WIN32
44 #  include <sys/timeb.h>
45 #  include "utils-win.h"
46 #endif
47 
48 /* These aren't intended for public */
49 #define DECL_OPT_CALLBACK(func)          \
50 gboolean func (const gchar   *opt_name,  \
51                const gchar   *value,     \
52                gpointer       data,      \
53                GError       **err)
54 
55 static DECL_OPT_CALLBACK(_check_legacy_encoding);
56 static DECL_OPT_CALLBACK(_set_output_path);
57 static DECL_OPT_CALLBACK(_option_deprecated);
58 static DECL_OPT_CALLBACK(_set_opt_delim);
59 static DECL_OPT_CALLBACK(_set_opt_noheading);
60 static DECL_OPT_CALLBACK(_set_output_xml);
61 
62 /* WARNING: MUST match order of _os_guess enum */
63 static char *os_strings[] = {
64 	N_("Windows 95"),
65 	N_("Windows NT 4.0"),
66 	N_("Windows 98"),
67 	N_("Windows ME"),
68 	N_("Windows 2000"),
69 	N_("Windows XP or 2003"),
70 	N_("Windows 2000, XP or 2003"),
71 	N_("Windows Vista - 8.1"),
72 	N_("Windows 10 or above")
73 };
74 
75 static int          output_mode        = OUTPUT_NONE;
76 static gboolean     no_heading         = FALSE;
77 static gboolean     use_localtime      = FALSE;
78        char        *delim              = NULL;
79        char        *legacy_encoding    = NULL; /*!< INFO2 only, or upon request */
80        char        *output_loc         = NULL;
81        char        *tmppath            = NULL; /*!< used iff output_loc is defined */
82        char       **fileargs           = NULL;
83        FILE        *out_fh             = NULL; /*!< unused for Windows console */
84 
85 /*! These options are only effective for tab delimited mode output */
86 static const GOptionEntry text_options[] = {
87 	{
88 		"delimiter", 't', 0,
89 		G_OPTION_ARG_CALLBACK, _set_opt_delim,
90 		N_("String to use as delimiter (TAB by default)"), N_("STRING")
91 	},
92 	{
93 		"no-heading", 'n', G_OPTION_FLAG_NO_ARG,
94 		G_OPTION_ARG_CALLBACK, _set_opt_noheading,
95 		N_("Don't show column header and metadata"), NULL
96 	},
97 	{
98 		"always-utf8", '8', G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG,
99 		G_OPTION_ARG_CALLBACK, _option_deprecated,
100 		N_("(This option is deprecated)"), NULL
101 	},
102 	{NULL}
103 };
104 
105 static const GOptionEntry main_options[] = {
106 	{
107 		"output", 'o', 0,
108 		G_OPTION_ARG_CALLBACK, _set_output_path,
109 		N_("Write output to FILE"), N_("FILE")
110 	},
111 	{
112 		"xml", 'x', G_OPTION_FLAG_NO_ARG,
113 		G_OPTION_ARG_CALLBACK, _set_output_xml,
114 		N_("Output in XML format instead of tab-delimited values"), NULL
115 	},
116 	{
117 		"localtime", 'z', 0,
118 		G_OPTION_ARG_NONE, &use_localtime,
119 		N_("Present deletion time in time zone of local system (default is UTC)"),
120 		NULL
121 	},
122 	{
123 		"version", 'v', G_OPTION_FLAG_NO_ARG,
124 		G_OPTION_ARG_CALLBACK, (GOptionArgFunc) print_version_and_exit,
125 		N_("Print version information and exit"), NULL
126 	},
127 	{
128 		G_OPTION_REMAINING, 0, 0,
129 		G_OPTION_ARG_FILENAME_ARRAY, &fileargs,
130 		N_("INFO2 file name"), NULL
131 	},
132 	{NULL}
133 };
134 
135 /*! Appended to main option group if program is INFO2 reader */
136 const GOptionEntry rbinfile_options[] = {
137 	{
138 		"legacy-filename", 'l', 0,
139 		G_OPTION_ARG_CALLBACK, _check_legacy_encoding,
140 		N_("Show legacy (8.3) path if available and specify its CODEPAGE"),
141 		N_("CODEPAGE")
142 	},
143 	{NULL}
144 };
145 
146 /*
147  * Option handling related routines
148  */
149 
150 static gboolean
_set_output_mode(int mode,GError ** err)151 _set_output_mode (int       mode,
152                   GError  **err)
153 {
154 	if (output_mode == mode)
155 		return TRUE;
156 
157 	if (output_mode == OUTPUT_NONE) {
158 		output_mode = mode;
159 		return TRUE;
160 	}
161 
162 	g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
163 		_("Plain text format options can not be used in XML mode."));
164 	return FALSE;
165 }
166 
167 static gboolean
_set_output_xml(const gchar * opt_name,const gchar * value,gpointer data,GError ** err)168 _set_output_xml (const gchar *opt_name,
169                  const gchar *value,
170                  gpointer     data,
171                  GError     **err)
172 {
173 	return _set_output_mode (OUTPUT_XML, err);
174 }
175 
176 static gboolean
_set_opt_noheading(const gchar * opt_name,const gchar * value,gpointer data,GError ** err)177 _set_opt_noheading (const gchar *opt_name,
178                     const gchar *value,
179                     gpointer     data,
180                     GError     **err)
181 {
182 	no_heading = TRUE;
183 
184 	return _set_output_mode (OUTPUT_CSV, err);
185 }
186 
187 /*!
188  * single/double quotes and backslashes have already been
189  * quoted / unquoted when parsing arguments. We need to
190  * interpret \\r, \\n etc separately
191  */
192 static char *
_filter_escapes(const char * str)193 _filter_escapes (const char *str)
194 {
195 	GString *result, *debug_str;
196 	char *i = (char *) str;
197 
198 	g_return_val_if_fail ( (str != NULL) && (*str != '\0'), NULL);
199 
200 	result = g_string_new (NULL);
201 	do
202 	{
203 		if ( *i != '\\' )
204 		{
205 			result = g_string_append_c (result, *i);
206 			continue;
207 		}
208 
209 		switch ( *(++i) )
210 		{
211 		  case 'r':
212 			result = g_string_append_c (result, '\r'); break;
213 		  case 'n':
214 			result = g_string_append_c (result, '\n'); break;
215 		  case 't':
216 			result = g_string_append_c (result, '\t'); break;
217 		  case 'e':
218 			result = g_string_append_c (result, '\x1B'); break;
219 		  default:
220 			result = g_string_append_c (result, '\\'); i--;
221 		}
222 	}
223 	while ( *(++i) );
224 
225 	debug_str = g_string_new ("filtered delimiter = ");
226 	i = result->str;
227 	do
228 	{
229 		if ( *i >= 0x20 && *i <= 0x7E )  /* problem during linking with g_ascii_isprint */
230 			debug_str = g_string_append_c (debug_str, *i);
231 		else
232 			g_string_append_printf (debug_str, "\\x%02X", *(unsigned char *) i);
233 	}
234 	while ( *(++i) );
235 	g_debug ("%s", debug_str->str);
236 	g_string_free (debug_str, TRUE);
237 	return g_string_free (result, FALSE);
238 }
239 
240 static gboolean
_set_opt_delim(const gchar * opt_name,const gchar * value,gpointer data,GError ** err)241 _set_opt_delim (const gchar *opt_name,
242                const gchar *value,
243                gpointer     data,
244                GError     **err)
245 {
246 	static gboolean seen = FALSE;
247 
248 	if (seen)
249 	{
250 		g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
251 			_("Multiple delimiter options disallowed."));
252 		return FALSE;
253 	}
254 	seen = TRUE;
255 
256 	delim = (*value) ? _filter_escapes (value) : g_strdup ("");
257 
258 	return _set_output_mode (OUTPUT_CSV, err);
259 }
260 
261 static gboolean
_set_output_path(const gchar * opt_name,const gchar * value,gpointer data,GError ** err)262 _set_output_path (const gchar *opt_name,
263                   const gchar *value,
264                   gpointer     data,
265                   GError     **err)
266 {
267 	static gboolean seen     = FALSE;
268 
269 	if (seen)
270 	{
271 		g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
272 			_("Multiple output destinations disallowed."));
273 		return FALSE;
274 	}
275 	seen = TRUE;
276 
277 	if ( *value == '\0' )
278 	{
279 		g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
280 			_("Empty output filename disallowed."));
281 		return FALSE;
282 	}
283 
284 	if (g_file_test (value, G_FILE_TEST_EXISTS)) {
285 		g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
286 			_("Output destinations already exists."));
287 		return FALSE;
288 	}
289 
290 	output_loc = g_strdup (value);
291 	return TRUE;
292 }
293 
294 static gboolean
_option_deprecated(const gchar * opt_name,const gchar * unused,gpointer data,GError ** err)295 _option_deprecated (const gchar *opt_name,
296                     const gchar *unused,
297                     gpointer     data,
298                     GError     **err)
299 {
300 	g_printerr (_("NOTE: Option '%s' is deprecated and ignored."), opt_name);
301 	g_printerr ("\n");
302 	return TRUE;
303 }
304 
305 static gboolean
_check_legacy_encoding(const gchar * opt_name,const gchar * enc,gpointer data,GError ** err)306 _check_legacy_encoding (const gchar *opt_name,
307                         const gchar *enc,
308                         gpointer     data,
309                         GError     **err)
310 {
311 	char           *s;
312 	gint            e;
313 	gboolean        ret      = FALSE;
314 	static gboolean seen     = FALSE;
315 	GError         *conv_err = NULL;
316 
317 	if (seen)
318 	{
319 		g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
320 			_("Multiple encoding options disallowed."));
321 		return FALSE;
322 	}
323 	seen = TRUE;
324 
325 	if ( *enc == '\0' )
326 	{
327 		g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
328 			_("Empty encoding option disallowed."));
329 		return FALSE;
330 	}
331 
332 	s = g_convert ("C:\\", -1, "UTF-8", enc, NULL, NULL, &conv_err);
333 
334 	if (conv_err == NULL)
335 	{
336 		if (strcmp ("C:\\", s) != 0) /* e.g. EBCDIC based code pages */
337 		{
338 			g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
339 				_("'%s' can't possibly be a code page or compatible "
340 				"encoding used by localized Windows."), enc);
341 		} else {
342 			legacy_encoding = g_strdup (enc);
343 			ret = TRUE;
344 		}
345 		goto done_check_encoding;
346 	}
347 
348 	e = conv_err->code;
349 	g_clear_error (&conv_err);
350 
351 	switch (e)
352 	{
353 		case G_CONVERT_ERROR_NO_CONVERSION:
354 
355 			g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
356 				_("'%s' encoding is not supported by glib library "
357 				"on this system.  If iconv program is present on "
358 				"system, use 'iconv -l' for a list of possible "
359 				"alternatives; otherwise check out following site for "
360 				"a list of probable encodings to use:\n\n\t%s"), enc,
361 #ifdef G_OS_WIN32
362 				"https://github.com/win-iconv/win-iconv/blob/master/win_iconv.c"
363 #else
364 				"https://www.gnu.org/software/libiconv/"
365 #endif
366 			);
367 			break;
368 
369 		/* Encodings not ASCII compatible can't possibly be ANSI/OEM code pages */
370 		case G_CONVERT_ERROR_ILLEGAL_SEQUENCE:
371 		case G_CONVERT_ERROR_PARTIAL_INPUT:
372 
373 			g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
374 				_("'%s' can't possibly be a code page or compatible "
375 				"encoding used by localized Windows."), enc);
376 			break;
377 
378 		default:
379 			g_assert_not_reached ();
380 	}
381 
382 done_check_encoding:
383 
384 	g_free (s);
385 	return ret;
386 }
387 
388 static gboolean
_count_fileargs(GOptionContext * context,GOptionGroup * group,gpointer data,GError ** err)389 _count_fileargs (GOptionContext *context,
390                  GOptionGroup   *group,
391                  gpointer        data,
392                  GError        **err)
393 {
394 	if (fileargs && g_strv_length (fileargs) == 1)
395 		return TRUE;
396 
397 	/* FIXME unable to pull user data, so only print generic mesg */
398 	g_set_error (err, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
399 		_("Must specify exactly one file or folder argument."));
400 
401 /*
402 		_("Must specify exactly one INFO2 file as argument."));
403 		(_("Must specify exactly one directory containing "
404 			"$Recycle.bin index files, or one such index file "
405 			"as argument.")); */
406 	return FALSE;
407 }
408 
409 
410 /*
411  * Charset conversion routines
412  */
413 
414 size_t
ucs2_strnlen(const gunichar2 * str,size_t max_sz)415 ucs2_strnlen (const gunichar2 *str, size_t max_sz)
416 {
417 #ifdef G_OS_WIN32
418 
419 	return wcsnlen_s ((const wchar_t *) str, max_sz);
420 
421 #else
422 
423 	size_t i;
424 
425 	if (str == NULL)
426 		return 0;
427 
428 	for (i=0; (i<max_sz) && str[i]; i++) {}
429 	return i;
430 
431 #endif
432 }
433 
434 static void
_advance_char(size_t sz,gchar ** in_str,gsize * read_bytes,gchar ** out_str,gsize * write_bytes,const char * tmpl)435 _advance_char (size_t       sz,
436                gchar      **in_str,
437                gsize       *read_bytes,
438                gchar      **out_str,
439                gsize       *write_bytes,
440                const char  *tmpl)
441 {
442 	gchar *repl;
443 
444 	switch (sz) {
445 		case 1:
446 		{
447 			unsigned char c = *(unsigned char *) (*in_str);
448 			repl = g_strdup_printf (tmpl, c);
449 		}
450 			break;
451 
452 		case 2:
453 		{
454 			guint16 c = GUINT16_FROM_LE (*(guint16 *) (*in_str));
455 			repl = g_strdup_printf (tmpl, c);
456 		}
457 			break;
458 
459 		default:
460 			g_assert_not_reached();
461 	}
462 
463 	(*in_str) += sz;
464 	if (read_bytes != NULL)
465 		(*read_bytes) -= sz;
466 
467 	*out_str = g_stpcpy (*out_str, (const char *) repl);
468 	if (write_bytes != NULL)
469 		*write_bytes -= strlen (repl);
470 
471 	g_free (repl);
472 	return;
473 }
474 
475 /*! Last argument is there to avoid recomputing */
476 static char *
_filter_printable_char(const char * str,const char * tmpl,size_t out_ch_width)477 _filter_printable_char (const char *str,
478                         const char *tmpl,
479                         size_t      out_ch_width)
480 {
481 	char     *p, *np;
482 	gunichar  c;
483 	GString  *s;
484 
485 	s = g_string_sized_new (strlen (str) * 2);
486 	p = (char *) str;
487 	while (*p)
488 	{
489 		c  = g_utf8_get_char  (p);
490 		np = g_utf8_next_char (p);
491 
492 		/*
493 		 * ASCII space is the norm (e.g. Program Files), but
494 		 * all other kinds of spaces are rare, so escape them too
495 		 */
496 		if (g_unichar_isgraph (c) || (c == 0x20))
497 			s = g_string_append_len (s, p, (gssize) (np - p));
498 		else
499 			g_string_append_printf (s, tmpl, c);
500 
501 		p = np;
502 	}
503 
504 	return g_string_free (s, FALSE);
505 }
506 
507 /*!
508  * Converts a Windows path in specified legacy encoding or unicode
509  * path into UTF-8 encoded version. When encoding error arises,
510  * it attempts to be robust and substitute concerned bytes or
511  * unicode codepoints with escaped ones specified by printf-style
512  * template. This routine is not for generic charset conversion.
513  *
514  * 1. Caller is responsible to only supply non-stateful encoding
515  * meant to be used as Windows code page, or use NULL to represent
516  * UTF-16LE (the Windows unicode path encoding). Never supply
517  * any unicode encoding directly.
518  *
519  * 2. Caller is responsible for using correct printf template
520  * for desired data type, no check is done here.
521  */
522 char *
conv_path_to_utf8_with_tmpl(const char * path,const char * from_enc,const char * tmpl,size_t * read,r2status * st)523 conv_path_to_utf8_with_tmpl (const char *path,
524                              const char *from_enc,
525                              const char *tmpl,
526                              size_t     *read,
527                              r2status   *st)
528 {
529 	char *u8_path, *i_ptr, *o_ptr, *result = NULL;
530 	gsize len, r_total, rbyte, wbyte, status, in_ch_width, out_ch_width;
531 	GIConv conv;
532 
533 	/* for UTF-16, first byte of str can be null */
534 	g_return_val_if_fail (path != NULL, NULL);
535 	g_return_val_if_fail ((from_enc == NULL) || (*from_enc != '\0'), NULL);
536 	g_return_val_if_fail ((    tmpl != NULL) && (    *tmpl != '\0'), NULL);
537 
538 	/* try the template */
539 	{
540 		char *s = g_strdup_printf (tmpl, from_enc ? 0xFF : 0xFFFF);
541 		/* UTF-8 character occupies at most 6 bytes */
542 		out_ch_width = MAX (strlen(s), 6);
543 		g_free (s);
544 	}
545 
546 	if (from_enc != NULL) {
547 		in_ch_width = sizeof (char);
548 		len = strnlen (path, WIN_PATH_MAX);
549 	} else {
550 		in_ch_width = sizeof (gunichar2);
551 		len = ucs2_strnlen ((const gunichar2 *)path, WIN_PATH_MAX);
552 	}
553 
554 	if (! len)
555 		return NULL;
556 
557 	rbyte   = len *  in_ch_width;
558 	wbyte   = len * out_ch_width;
559 	u8_path = g_malloc0 (wbyte);
560 
561 	r_total = rbyte;
562 	i_ptr   = (char *) path;
563 	o_ptr   = u8_path;
564 
565 	/* Shouldn't fail, from_enc already tested upon start of prog */
566 	conv = g_iconv_open ("UTF-8", from_enc ? from_enc : "UTF-16LE");
567 
568 	g_debug ("Initial: read=%" G_GSIZE_FORMAT ", write=%" G_GSIZE_FORMAT,
569 			rbyte, wbyte);
570 
571 	/* Pass 1: Convert whole string to UTF-8, all illegal seq become escaped hex */
572 	while (TRUE)
573 	{
574 		int e;
575 
576 		if (*i_ptr == '\0') {
577 			if (from_enc   != NULL) break;
578 			if (*(i_ptr+1) == '\0') break; /* utf-16: check "\0\0" */
579 		}
580 
581 		status = g_iconv (conv, &i_ptr, &rbyte, &o_ptr, &wbyte);
582 		e = errno;
583 
584 		if ( status != (gsize) -1 ) break;
585 
586 		g_debug ("r=%02" G_GSIZE_FORMAT ", w=%02" G_GSIZE_FORMAT
587 			", stt=%" G_GSIZE_FORMAT " (%s) str=%s",
588 			rbyte, wbyte, status, strerror(e), u8_path);
589 
590 		/* XXX Should I consider the possibility of odd bytes for EINVAL? */
591 		switch (e) {
592 			case EILSEQ:
593 			case EINVAL:
594 				_advance_char (in_ch_width, &i_ptr, &rbyte, &o_ptr, &wbyte, tmpl);
595 				/* reset state, hopefully Windows don't use stateful encoding at all */
596 				g_iconv (conv, NULL, NULL, &o_ptr, &wbyte);
597 				*st = R2_ERR_USER_ENCODING;
598 				break;
599 			case E2BIG:
600 				/* Should have already allocated enough buffer. Let it KABOOM! otherwise. */
601 				g_assert_not_reached();
602 		}
603 	}
604 
605 	g_debug ("r=%02" G_GSIZE_FORMAT ", w=%02" G_GSIZE_FORMAT
606 		", stt=%" G_GSIZE_FORMAT ", str=%s", rbyte, wbyte, status, u8_path);
607 
608 	g_iconv_close (conv);
609 
610 	if (read != NULL)
611 		*read = r_total - rbyte;
612 
613 	/* Pass 2: Convert all ctrl characters (and some more) to hex */
614 	if (g_utf8_validate (u8_path, -1, NULL))
615 		result = _filter_printable_char (u8_path, tmpl, out_ch_width);
616 	else {
617 		g_critical (_("Converted path failed UTF-8 validation"));
618 		*st = R2_ERR_INTERNAL;
619 	}
620 
621 	g_free (u8_path);
622 
623 	return result;
624 }
625 
626 /*
627  * Date / Time handling routines
628  */
629 
630 static GString *
get_datetime_str(struct tm * tm)631 get_datetime_str (struct tm *tm)
632 {
633 	GString         *output;
634 	size_t           len;
635 
636 	output = g_string_sized_new (30);  /* enough for appending numeric timezone */
637 	len = strftime (output->str, output->allocated_len, "%Y-%m-%d %H:%M:%S", tm);
638 	if ( !len )
639 	{
640 		g_string_free (output, TRUE);
641 		return NULL;
642 	}
643 	output->len = len;
644 	return output;
645 }
646 
647 /* Return full name of current timezone */
648 static char *
get_timezone_name(struct tm * tm)649 get_timezone_name (struct tm *tm)
650 {
651 #ifdef G_OS_WIN32
652 
653 	/* Impossible to use strftime() family on Windows,
654 	 * see get_win_timezone_name() for reason */
655 
656 	if (tm == NULL)
657 		return g_strdup (_("Coordinated Universal Time (UTC)"));
658 
659 	return get_win_timezone_name ();
660 
661 #else /* ! G_OS_WIN32 */
662 
663 	char  buf[128];
664 
665 	if (tm == NULL)
666 		return g_strdup (_("Coordinated Universal Time (UTC)"));
667 
668 	if ( 0 == strftime (buf, sizeof (buf) - 1, "%Z", tm) )
669 		return g_strdup (_("(Failed to retrieve timezone name)"));
670 
671 	return g_strdup (buf);
672 
673 #endif
674 }
675 
676 /*! Return ISO8601 numeric timezone, like "+0400" */
677 static const char *
get_timezone_numeric(struct tm * tm)678 get_timezone_numeric (struct tm *tm)
679 {
680 	static char buf[10];
681 
682 	if (tm == NULL)
683 		return "+0000";  /* ISO8601 forbids -0000 */
684 
685 	/*
686 	 * Turns out strftime is not so cross-platform, Windows one supports far
687 	 * less format strings than that defined in Single Unix Specification.
688 	 * However, GDateTime is not available until 2.26, so bite the bullet.
689 	 */
690 #ifdef G_OS_WIN32
691 	struct _timeb timeb;
692 	_ftime (&timeb);
693 	/*
694 	 * 1. timezone value is in opposite sign of what people expect
695 	 * 2. it doesn't account for DST.
696 	 * 3. tm.tm_isdst is merely a flag and not indication on difference of
697 	 *    hours between DST and standard time. But there is no way to
698 	 *    override timezone in C library other than $TZ, and it always use
699 	 *    US rule, so again, just give up and use the value
700 	 */
701 	int offset = MAX(tm->tm_isdst, 0) * 60 - timeb.timezone;
702 	g_snprintf (buf, sizeof(buf), "%+.2i%.2i", offset / 60, abs(offset) % 60);
703 
704 #else /* !def G_OS_WIN32 */
705 	size_t len = strftime (buf, sizeof(buf), "%z", tm);
706 	if ( !len )
707 		return "+????";
708 #endif
709 	return (const char *) (&buf);
710 }
711 
712 /*! Return ISO 8601 formatted time with timezone */
713 static GString *
get_iso8601_datetime_str(struct tm * tm)714 get_iso8601_datetime_str (struct tm *tm)
715 {
716 	GString         *output;
717 
718 	if ( ( output = get_datetime_str (tm) ) == NULL )
719 		return NULL;
720 
721 	output->str[10] = 'T';
722 	if ( !use_localtime )
723 		return g_string_append_c (output, 'Z');
724 
725 	return g_string_append (output, get_timezone_numeric(tm));
726 }
727 
728 time_t
win_filetime_to_epoch(uint64_t win_filetime)729 win_filetime_to_epoch (uint64_t win_filetime)
730 {
731 	uint64_t epoch;
732 
733 	g_debug ("%s(): FileTime = %" G_GUINT64_FORMAT, __func__, win_filetime);
734 
735 	/* Let's assume we don't need millisecond resolution time for now */
736 	epoch = (win_filetime - 116444736000000000LL) / 10000000;
737 
738 	/* Let's assume this program won't survive till 22th century */
739 	return (time_t) (epoch & 0xFFFFFFFF);
740 }
741 
742 void
rifiuti_init(const char * progpath)743 rifiuti_init (const char *progpath)
744 {
745 	if (NULL != g_getenv ("RIFIUTI_DEBUG"))
746 		g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
747 			my_debug_handler, NULL);
748 
749 	setlocale (LC_ALL, "");
750 
751 #ifdef G_OS_WIN32
752 	{
753 		/*
754 		 * Setting GETTEXT_MUI is not enough. Though it successfully
755 		 * pick up user default locale under Windows, glib internally
756 		 * only considers g_win32_getlocale() for decision making,
757 		 * which in turn only considers thread locale.
758 		 * So we need to override g_win32_getlocale() result. And
759 		 * overriding that with LC_* would render GETTEXT_MUI useless.
760 		 */
761 		/* _putenv_s ("GETTEXT_MUI", "1"); */
762 		char *loc = get_win32_locale();
763 
764 		if (0 == _putenv_s ("LC_MESSAGES", loc)) {
765 			g_debug ("(Windows) Use LC_MESSAGES = %s", loc);
766 		} else {
767 			g_warning ("Failed setting LC_MESSAGES variable, "
768 				"move on as if no translation is used.");
769 		}
770 		g_free (loc);
771 	}
772 #endif
773 
774 	{
775 		/* searching current dir is more useful on Windows */
776 		char *d = g_path_get_dirname (progpath);
777 		char *p = g_build_filename (d, LOCALEDIR_PORTABLE, NULL);
778 
779 		if (g_file_test (p, G_FILE_TEST_IS_DIR))
780 		{
781 			g_debug ("Portable LOCALEDIR = %s", p);
782 			bindtextdomain (PACKAGE, p);
783 		}
784 		else
785 			bindtextdomain (PACKAGE, LOCALEDIR);
786 
787 		g_free (p);
788 		g_free (d);
789 	}
790 
791 	bind_textdomain_codeset (PACKAGE, "UTF-8");
792 	textdomain (PACKAGE);
793 }
794 
795 void
rifiuti_setup_opt_ctx(GOptionContext ** context,rbin_type type)796 rifiuti_setup_opt_ctx (GOptionContext **context,
797                        rbin_type        type)
798 {
799 	char         *bug_report_str;
800 	GOptionGroup *group;
801 
802 	g_option_context_set_translation_domain (*context, PACKAGE);
803 
804 	bug_report_str = g_strdup_printf (
805 		/* TRANSLATOR COMMENT: argument is bug report webpage */
806 		_("Report bugs to %s"), PACKAGE_BUGREPORT);
807 	g_option_context_set_description (*context, bug_report_str);
808 	g_free (bug_report_str);
809 
810 	/* main group */
811 	group = g_option_group_new (NULL, NULL, NULL, NULL, NULL);
812 
813 	g_option_group_add_entries (group, main_options);
814 	switch (type)
815 	{
816 		case RECYCLE_BIN_TYPE_FILE:
817 			g_option_group_add_entries (group, rbinfile_options);
818 			break;
819 		default: break;
820 		/* There will be option for recycle bin dir later */
821 	}
822 
823 	g_option_group_set_parse_hooks (group, NULL, _count_fileargs);
824 	g_option_group_set_translation_domain (group, PACKAGE);
825 	g_option_context_set_main_group (*context, group);
826 
827 	/* text group */
828 	/* FIXME For unknown reason, short description of option
829 	 * groups are not translated at all if using N_() */
830 	group = g_option_group_new ("text",
831 		_("Plain text output options:"),
832 		N_("Show plain text output options"), NULL, NULL);
833 
834 	g_option_group_add_entries (group, text_options);
835 	g_option_group_set_translation_domain (group, PACKAGE);
836 	g_option_context_add_group (*context, group);
837 
838 	g_option_context_set_help_enabled (*context, TRUE);
839 }
840 
841 r2status
rifiuti_parse_opt_ctx(GOptionContext ** context,int * argc,char *** argv)842 rifiuti_parse_opt_ctx (GOptionContext **context,
843                        int             *argc,
844                        char          ***argv)
845 {
846 	GError   *err = NULL;
847 	char     *help_msg;
848 	gboolean  ret, do_print_help = FALSE;
849 
850 	/* Must be done before parsing, since argc might be modified later */
851 	if (*argc <= 1)
852 		do_print_help = TRUE;
853 
854 #if GLIB_CHECK_VERSION (2, 40, 0)
855 	{
856 		char **args;
857 
858 #  ifdef G_OS_WIN32
859 		args = g_win32_get_command_line ();
860 #  else
861 		args = g_strdupv (*argv);
862 #  endif
863 		ret = g_option_context_parse_strv (*context, &args, &err);
864 		g_strfreev (args);
865 	}
866 #else /* glib < 2.40 */
867 	ret = g_option_context_parse (*context, argc, argv, &err);
868 #endif
869 
870 	help_msg = g_option_context_get_help (*context, FALSE, NULL);
871 
872 	g_option_context_free (*context);
873 
874 	if (do_print_help)
875 	{
876 #ifdef G_OS_WIN32
877 		g_set_print_handler (gui_message);
878 #endif
879 		g_print ("%s", help_msg);
880 		g_free (help_msg);
881 
882 		exit (EXIT_SUCCESS);
883 	}
884 
885 	g_free (help_msg);
886 
887 	if ( !ret )
888 	{
889 		g_printerr (_("Error parsing options: %s"), err->message);
890 		g_printerr ("\n");
891 		g_error_free (err);
892 		return R2_ERR_ARG;
893 	}
894 
895 	/* Some fallback values after successful option parsing... */
896 	if (delim == NULL)
897 		delim = g_strdup ("\t");
898 
899 	if (output_mode == OUTPUT_NONE)
900 		output_mode = OUTPUT_CSV;
901 
902 	return EXIT_SUCCESS;
903 }
904 
905 
906 /*!
907  * Wrapper of g_utf16_to_utf8 for big endian system.
908  * Always assume string is nul-terminated. (Unused now?)
909  */
910 char *
utf16le_to_utf8(const gunichar2 * str,glong len,glong * items_read,glong * items_written,GError ** error)911 utf16le_to_utf8 (const gunichar2   *str,
912                  glong              len,
913                  glong             *items_read,
914                  glong             *items_written,
915                  GError           **error)
916 {
917 #if ((G_BYTE_ORDER) == (G_LITTLE_ENDIAN))
918 	return g_utf16_to_utf8 (str, -1, items_read, items_written, error);
919 #else
920 
921 	gunichar2 *buf;
922 	char *ret;
923 
924 	/* should be guaranteed to succeed */
925 	buf = (gunichar2 *) g_convert ((const char *) str, len * 2, "UTF-16BE",
926 	                               "UTF-16LE", NULL, NULL, NULL);
927 	ret = g_utf16_to_utf8 (buf, -1, items_read, items_written, error);
928 	g_free (buf);
929 	return ret;
930 #endif
931 }
932 
933 
934 void
my_debug_handler(const char * log_domain,GLogLevelFlags log_level,const char * message,gpointer data)935 my_debug_handler (const char     *log_domain,
936                   GLogLevelFlags  log_level,
937                   const char     *message,
938                   gpointer        data)
939 {
940 	g_printerr ("DEBUG: %s\n", message);
941 }
942 
943 static r2status
_get_tempfile(void)944 _get_tempfile (void)
945 {
946 	int     fd, e = 0;
947 	FILE   *h;
948 	char   *t;
949 
950 	/* segfaults if string is pre-allocated in stack */
951 	t = g_strdup ("rifiuti-XXXXXX");
952 
953 	if ( -1 == ( fd = g_mkstemp (t) ) ) {
954 		e = errno;
955 		goto tempfile_fail;
956 	}
957 
958 	h = fdopen (fd, "wb");
959 	if (h == NULL) {
960 		e = errno;
961 		close (fd);
962 		goto tempfile_fail;
963 	}
964 
965 	out_fh   = h;
966 	tmppath  = t;
967 	return EXIT_SUCCESS;
968 
969   tempfile_fail:
970 
971 	g_printerr (_("Error opening temp file for writing: %s"),
972 		g_strerror (e));
973 	g_printerr ("\n");
974 	return R2_ERR_OPEN_FILE;
975 }
976 
977 /*! Scan folder and add all "$Ixxxxxx.xxx" to filelist for parsing */
978 static gboolean
_populate_index_file_list(GSList ** list,const char * path)979 _populate_index_file_list (GSList     **list,
980                           const char  *path)
981 {
982 	GDir           *dir;
983 	const char     *direntry;
984 	char           *fname;
985 	GPatternSpec   *pattern1, *pattern2;
986 	GError         *error = NULL;
987 
988 	/*
989 	 * g_dir_open returns cryptic error message or even succeeds on Windows,
990 	 * when in fact the directory content is inaccessible.
991 	 */
992 #ifdef G_OS_WIN32
993 	if ( !can_list_win32_folder (path) )
994 		return FALSE;
995 #endif
996 
997 	if (NULL == (dir = g_dir_open (path, 0, &error)))
998 	{
999 		g_printerr (_("Error opening directory '%s': %s"), path, error->message);
1000 		g_printerr ("\n");
1001 		g_clear_error (&error);
1002 		return FALSE;
1003 	}
1004 
1005 	pattern1 = g_pattern_spec_new ("$I??????.*");
1006 	pattern2 = g_pattern_spec_new ("$I??????");
1007 
1008 	while ((direntry = g_dir_read_name (dir)) != NULL)
1009 	{
1010 		if (!g_pattern_match_string (pattern1, direntry) &&
1011 		    !g_pattern_match_string (pattern2, direntry))
1012 			continue;
1013 		fname = g_build_filename (path, direntry, NULL);
1014 		*list = g_slist_prepend (*list, fname);
1015 	}
1016 
1017 	g_dir_close (dir);
1018 
1019 	g_pattern_spec_free (pattern1);
1020 	g_pattern_spec_free (pattern2);
1021 
1022 	return TRUE;
1023 }
1024 
1025 
1026 /*! Search for desktop.ini in folder for hint of recycle bin */
1027 static gboolean
found_desktop_ini(const char * path)1028 found_desktop_ini (const char *path)
1029 {
1030 	char *filename, *content, *found;
1031 
1032 	filename = g_build_filename (path, "desktop.ini", NULL);
1033 	if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
1034 		goto desktop_ini_error;
1035 
1036 	/* assume desktop.ini is ASCII and not something spurious */
1037 	if (!g_file_get_contents (filename, &content, NULL, NULL))
1038 		goto desktop_ini_error;
1039 
1040 	/* Don't bother parsing, we don't use the content at all */
1041 	found = strstr (content, RECYCLE_BIN_CLSID);
1042 	g_free (content);
1043 	g_free (filename);
1044 	return (found != NULL);
1045 
1046 	desktop_ini_error:
1047 	g_free (filename);
1048 	return FALSE;
1049 }
1050 
1051 
1052 /*! Add potentially valid file(s) to list */
1053 int
check_file_args(const char * path,GSList ** list,rbin_type type)1054 check_file_args (const char  *path,
1055                  GSList     **list,
1056                  rbin_type    type)
1057 {
1058 	g_debug ("Start basic file checking...");
1059 
1060 	g_return_val_if_fail ( (path != NULL) && (list != NULL), R2_ERR_INTERNAL );
1061 
1062 	if ( !g_file_test (path, G_FILE_TEST_EXISTS) )
1063 	{
1064 		g_printerr (_("'%s' does not exist."), path);
1065 		g_printerr ("\n");
1066 		return R2_ERR_OPEN_FILE;
1067 	}
1068 	else if ( (type == RECYCLE_BIN_TYPE_DIR) &&
1069 		g_file_test (path, G_FILE_TEST_IS_DIR) )
1070 	{
1071 		if ( ! _populate_index_file_list (list, path) )
1072 			return R2_ERR_OPEN_FILE;
1073 		/*
1074 		 * last ditch effort: search for desktop.ini. Just print empty content
1075 		 * representing empty recycle bin if found.
1076 		 */
1077 		if ( !*list && !found_desktop_ini (path) )
1078 		{
1079 			g_printerr (_("No files with name pattern '%s' "
1080 				"are found in directory."), "$Ixxxxxx.*");
1081 			g_printerr ("\n");
1082 			return R2_ERR_OPEN_FILE;
1083 		}
1084 	}
1085 	else if ( g_file_test (path, G_FILE_TEST_IS_REGULAR) )
1086 		*list = g_slist_prepend ( *list, g_strdup (path) );
1087 	else
1088 	{
1089 		g_printerr ( (type == RECYCLE_BIN_TYPE_DIR) ?
1090 			_("'%s' is not a normal file or directory.") :
1091 			_("'%s' is not a normal file."), path);
1092 		g_printerr ("\n");
1093 		return R2_ERR_OPEN_FILE;
1094 	}
1095 	return EXIT_SUCCESS;
1096 }
1097 
1098 
1099 static gboolean
_local_printf(const char * format,...)1100 _local_printf (const char *format, ...)
1101 {
1102 	va_list        args;
1103 	char          *str;
1104 
1105 	g_return_val_if_fail (format != NULL, FALSE);
1106 
1107 	va_start (args, format);
1108 	str = g_strdup_vprintf (format, args);
1109 	va_end (args);
1110 
1111 	if ( !g_utf8_validate (str, -1, NULL)) {
1112 		g_critical (_("Supplied format or arguments not in UTF-8 encoding"));
1113 		g_free (str);
1114 		return FALSE;
1115 	}
1116 
1117 #ifdef G_OS_WIN32
1118 	/*
1119 	 * Use Windows API only if:
1120 	 * 1. On Windows console
1121 	 * 2. Output is not piped nor redirected
1122 	 * See init_wincon_handle().
1123 	 */
1124 	if (out_fh == NULL)
1125 	{
1126 		GError  *err  = NULL;
1127 		wchar_t *wstr = g_utf8_to_utf16 (str, -1, NULL, NULL, &err);
1128 
1129 		if (err != NULL) {
1130 			g_critical (_("Error converting output from UTF-8 to UTF-16: %s"), err->message);
1131 			g_clear_error (&err);
1132 			wstr = g_utf8_to_utf16 ("(Original message failed to be displayed in UTF-16)",
1133 				-1, NULL, NULL, NULL);
1134 		}
1135 
1136 		puts_wincon (wstr);
1137 		g_free (wstr);
1138 	}
1139 	else
1140 #endif
1141 		fputs (str, out_fh);
1142 
1143 	g_free (str);
1144 	return TRUE;
1145 }
1146 
1147 
1148 r2status
prepare_output_handle(void)1149 prepare_output_handle (void)
1150 {
1151 	r2status s = EXIT_SUCCESS;
1152 
1153 	if (output_loc)
1154 		s = _get_tempfile ();
1155 	else
1156 	{
1157 #ifdef G_OS_WIN32
1158 		if (!init_wincon_handle())
1159 #endif
1160 			out_fh = stdout;
1161 	}
1162 	return s;
1163 }
1164 
1165 void
print_header(metarecord meta)1166 print_header (metarecord  meta)
1167 {
1168 	char *rbin_path;
1169 
1170 	if (no_heading) return;
1171 
1172 	g_return_if_fail (meta.filename != NULL);
1173 
1174 	g_debug ("Entering %s()", __func__);
1175 
1176 	rbin_path = g_filename_display_name (meta.filename);
1177 
1178 	switch (output_mode)
1179 	{
1180 		case OUTPUT_CSV:
1181 
1182 			_local_printf (_("Recycle bin path: '%s'"), rbin_path);
1183 			_local_printf ("\n");
1184 
1185 			{
1186 				char *ver;
1187 				if (meta.version == VERSION_NOT_FOUND) {
1188 					/* TRANSLATOR COMMENT: Empty folder, no file avaiable for analysis */
1189 					ver = g_strdup (_("??? (empty folder)"));
1190 				} else
1191 					ver = g_strdup_printf ("%" G_GUINT64_FORMAT, meta.version);
1192 
1193 				_local_printf (_("Version: %s"), ver);
1194 				_local_printf ("\n");
1195 				g_free (ver);
1196 			}
1197 
1198 			if (( meta.type == RECYCLE_BIN_TYPE_FILE ) && ( ! meta.keep_deleted_entry ))
1199 			{
1200 				_local_printf (_("Total entries ever existed: %d"), meta.total_entry);
1201 				_local_printf ("\n");
1202 			}
1203 
1204 			if (meta.os_guess == OS_GUESS_UNKNOWN)
1205 				_local_printf (_("OS detection failed"));
1206 			else
1207 				_local_printf (_("OS Guess: %s"), gettext (os_strings[meta.os_guess]) );
1208 
1209 			_local_printf ("\n");
1210 
1211 			{
1212 				char       *tz_name;
1213 				const char *tz_numeric;
1214 
1215 				if (use_localtime)
1216 				{
1217 					struct tm  _tm;
1218 					time_t     t = time (NULL);
1219 
1220 					localtime_r (&t, &_tm);
1221 					tz_name    = get_timezone_name    (&_tm);
1222 					tz_numeric = get_timezone_numeric (&_tm);
1223 				}
1224 				else
1225 				{
1226 					tz_name    = get_timezone_name    (NULL);
1227 					tz_numeric = get_timezone_numeric (NULL);
1228 				}
1229 
1230 				_local_printf (_("Time zone: %s [%s]"), tz_name, tz_numeric);
1231 				_local_printf ("\n");
1232 
1233 				g_free (tz_name);
1234 			}
1235 
1236 			_local_printf ("\n");
1237 
1238 			{
1239 				GArray   *a;
1240 				char    **c, *headerline;
1241 				char     *colhead[] = {
1242 					/* TRANSLATOR COMMENT: appears in column header */
1243 					N_("Index"), N_("Deleted Time"), N_("Size"), N_("Path"), NULL
1244 				};
1245 
1246 				a = g_array_sized_new (TRUE, TRUE, sizeof (gpointer), 5);
1247 				c = colhead;
1248 				while (*c != NULL) {
1249 					const char *t = gettext (*c++);
1250 					g_array_append_val (a, t);
1251 				}
1252 				if (meta.keep_deleted_entry) {
1253 					/* TRANSLATOR COMMENT: appears in column header, means file is restored or purged */
1254 					char *t = _("Gone?");
1255 					g_array_insert_val (a, 2, t);
1256 				}
1257 
1258 				headerline = g_strjoinv (delim, (char **) a->data);
1259 				_local_printf ("%s", headerline);
1260 				_local_printf ("\n");
1261 
1262 				g_free (headerline);
1263 				g_array_free (a, TRUE);
1264 			}
1265 
1266 			break;
1267 
1268 		case OUTPUT_XML:
1269 			/* No proper way to report wrong version info yet */
1270 			_local_printf (
1271 				"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1272 				"<recyclebin format=\"%s\" version=\"%" G_GINT64_FORMAT "\">\n"
1273 				"  <filename><![CDATA[%s]]></filename>\n",
1274 				( meta.type == RECYCLE_BIN_TYPE_FILE ) ? "file" : "dir",
1275 				MAX (meta.version, 0), rbin_path);
1276 			break;
1277 
1278 		default:
1279 			g_assert_not_reached();
1280 	}
1281 	g_free (rbin_path);
1282 
1283 	g_debug ("Leaving %s()", __func__);
1284 }
1285 
1286 
1287 void
print_record_cb(rbin_struct * record)1288 print_record_cb (rbin_struct *record)
1289 {
1290 	char             *out_fname, *index, *size = NULL;
1291 	char             *outstr = NULL, *deltime = NULL;
1292 	GString          *t;
1293 	struct tm         del_tm;
1294 
1295 	g_return_if_fail (record != NULL);
1296 
1297 	index = (record->meta->type == RECYCLE_BIN_TYPE_FILE) ?
1298 		g_strdup_printf ("%u", record->index_n) :
1299 		g_strdup (record->index_s);
1300 
1301 	/*
1302 	 * Used to check TZ environment variable here, but no more.
1303 	 * Problems with that approach is now documented elsewhere.
1304 	 */
1305 
1306 	/*
1307 	 * g_warning() further down below is an inline func that makes use of
1308 	 * localtime(), and if localtime/gmtime is used here (it used to be),
1309 	 * the value would be overwritten upon the g_warning call, into current
1310 	 * machine time. Nasty.
1311 	 *
1312 	 * But why is the behavior occuring on MinGW-w64, but not even on MSYS2
1313 	 * itself or any other OS; and why only manifesting now but not earlier?
1314 	 * -- 2019-03-19
1315 	 */
1316 	if (use_localtime)
1317 		localtime_r (&(record->deltime), &del_tm);
1318 	else
1319 		gmtime_r    (&(record->deltime), &del_tm);
1320 
1321 	if ( record->legacy_path != NULL )
1322 		out_fname = g_strdup (record->legacy_path);
1323 	else
1324 	{
1325 		out_fname = record->uni_path ?
1326 			g_strdup (record->uni_path) :
1327 			g_strdup (_("(File name not representable in UTF-8 encoding)"));
1328 	}
1329 
1330 	switch (output_mode)
1331 	{
1332 		case OUTPUT_CSV:
1333 
1334 			if ((t = get_datetime_str (&del_tm)) != NULL)
1335 				deltime = g_string_free (t, FALSE);
1336 			else
1337 			{
1338 				g_warning (_("Error formatting file deletion time for record index %s."),
1339 						index);
1340 				deltime = g_strdup ("???");
1341 			}
1342 
1343 			if ( record->filesize == G_MAXUINT64 ) /* faulty */
1344 				size = g_strdup ("???");
1345 			else
1346 				size = g_strdup_printf ("%" G_GUINT64_FORMAT, record->filesize);
1347 
1348 			if (record->meta->keep_deleted_entry)
1349 			{
1350 				const char *purged = record->emptied ? _("Yes") : _("No");
1351 				outstr = g_strjoin (delim, index, deltime, purged, size, out_fname, NULL);
1352 			}
1353 			else
1354 				outstr = g_strjoin (delim, index, deltime, size, out_fname, NULL);
1355 
1356 			_local_printf ("%s\n", outstr);
1357 
1358 			break;
1359 
1360 		case OUTPUT_XML:
1361 		{
1362 			GString *s = g_string_new (NULL);
1363 
1364 			if ((t = get_iso8601_datetime_str (&del_tm)) != NULL)
1365 				deltime = g_string_free (t, FALSE);
1366 			else
1367 			{
1368 				g_warning (_("Error formatting file deletion time for record index %s."),
1369 						index);
1370 				deltime = g_strdup ("???");
1371 			}
1372 
1373 			g_string_printf (s, "  <record index=\"%s\" time=\"%s\"", index, deltime);
1374 
1375 			if (record->meta->keep_deleted_entry)
1376 				g_string_append_printf (s, " emptied=\"%c\"", record->emptied ? 'Y' : 'N');
1377 
1378 			if ( record->filesize == G_MAXUINT64 ) /* faulty */
1379 				size = g_strdup_printf (" size=\"-1\"");
1380 			else
1381 				size = g_strdup_printf (" size=\"%" G_GUINT64_FORMAT "\"", record->filesize);
1382 			s = g_string_append (s, (const gchar*) size);
1383 
1384 			g_string_append_printf (s,
1385 				">\n"
1386 				"    <path><![CDATA[%s]]></path>\n"
1387 				"  </record>\n", out_fname);
1388 
1389 			outstr = g_string_free (s, FALSE);
1390 			_local_printf ("%s", outstr);
1391 		}
1392 			break;
1393 
1394 		default:
1395 			g_assert_not_reached();
1396 	}
1397 	g_free (outstr);
1398 	g_free (out_fname);
1399 	g_free (deltime);
1400 	g_free (size);
1401 	g_free (index);
1402 }
1403 
1404 
1405 void
print_footer(void)1406 print_footer (void)
1407 {
1408 	switch (output_mode)
1409 	{
1410 		case OUTPUT_CSV:
1411 			/* do nothing */
1412 			break;
1413 
1414 		case OUTPUT_XML:
1415 			_local_printf ("%s", "</recyclebin>\n");
1416 			break;
1417 
1418 		default:
1419 			g_assert_not_reached();
1420 	}
1421 }
1422 
1423 void
close_output_handle(void)1424 close_output_handle (void)
1425 {
1426 	if (out_fh != NULL)
1427 		fclose (out_fh);
1428 
1429 #ifdef G_OS_WIN32
1430 	close_wincon_handle();
1431 #endif
1432 }
1433 
1434 r2status
move_temp_file(void)1435 move_temp_file (void)
1436 {
1437 	int e;
1438 
1439 	if ( !tmppath || !output_loc )
1440 		return EXIT_SUCCESS;
1441 
1442 	if ( 0 == g_rename (tmppath, output_loc) )
1443 		return EXIT_SUCCESS;
1444 
1445 	e = errno;
1446 
1447 	/* TRANSLATOR COMMENT: argument is system error message */
1448 	g_printerr (_("Error moving output data to desinated file: %s"),
1449 		g_strerror(e));
1450 	g_printerr ("\n");
1451 
1452 	/* TRANSLATOR COMMENT: argument is temp file location */
1453 	g_printerr (_("Output content is left in '%s'."), tmppath);
1454 	g_printerr ("\n");
1455 
1456 	return R2_ERR_WRITE_FILE;
1457 }
1458 
1459 void
print_version_and_exit(void)1460 print_version_and_exit (void)
1461 {
1462 	fprintf (stdout, "%s %s\n", PACKAGE, VERSION);
1463 	/* TRANSLATOR COMMENT: %s is software name */
1464 	fprintf (stdout, _("%s is distributed under the "
1465 		"BSD 3-Clause License.\n"), PACKAGE);
1466 	/* TRANSLATOR COMMENT: 1st argument is software name, 2nd is official URL */
1467 	fprintf (stdout, _("Information about %s can be found on\n\n\t%s\n"),
1468 		PACKAGE, PACKAGE_URL);
1469 
1470 	exit (EXIT_SUCCESS);
1471 }
1472 
1473 
1474 void
free_record_cb(rbin_struct * record)1475 free_record_cb (rbin_struct *record)
1476 {
1477 	if ( record->meta->type == RECYCLE_BIN_TYPE_DIR )
1478 		g_free (record->index_s);
1479 	g_free (record->uni_path);
1480 	g_free (record->legacy_path);
1481 	g_free (record);
1482 }
1483 
1484 
1485 void
free_vars(void)1486 free_vars (void)
1487 {
1488 	g_strfreev (fileargs);
1489 	g_free (output_loc);
1490 	g_free (legacy_encoding);
1491 	g_free (delim);
1492 	g_free (tmppath);
1493 }