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 }