Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: $Recycle.bin output shows whether trash file exists #36

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 35 additions & 18 deletions src/rifiuti-vista.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

static r2status exit_status = R2_OK;
static metarecord meta;
// Whether input argument is single index file out of `$Recycle.bin`
gboolean isolated_index = FALSE;


/*!
Expand Down Expand Up @@ -199,24 +201,26 @@ static void
parse_record_cb (char *index_file,
GSList **recordlist)
{
rbin_struct *record;
char *basename;
rbin_struct *record = NULL;
char *basename = NULL;
uint64_t version = 0;
uint32_t pathlen = 0;
gsize bufsize;
void *buf = NULL;
r2status validate_st;

basename = g_path_get_basename (index_file);

validate_st = validate_index_file (
index_file, &buf, &bufsize, &version, &pathlen);
if ( validate_st != R2_OK )
{
g_printerr (_("File '%s' fails validation."), basename);
g_printerr ("\n");
exit_status = validate_st;
goto parse_record_error;
r2status sts = validate_index_file (
index_file, &buf, &bufsize, &version, &pathlen);
if ( sts != R2_OK )
{
g_printerr (_("File '%s' fails validation.\n"), basename);
exit_status = sts;
g_free (buf);
g_free (basename);
return;
}
}

g_debug ("Start populating record for '%s'...", basename);
Expand All @@ -225,7 +229,8 @@ parse_record_cb (char *index_file,
{
case VERSION_VISTA:
/* see populate_record_data() for meaning of last parameter */
record = populate_record_data (buf, version, pathlen, (bufsize == VERSION1_FILE_SIZE - 1));
record = populate_record_data (buf, version, pathlen,
(bufsize == VERSION1_FILE_SIZE - 1));
break;

case VERSION_WIN10:
Expand All @@ -236,17 +241,29 @@ parse_record_cb (char *index_file,
g_assert_not_reached();
}

/* Check corresponding $R.... file existance and set record->gone */
if (isolated_index)
record->gone = FILESTATUS_UNKNOWN;
else
{
char *dirname = g_path_get_dirname (index_file);
char *trash_basename = g_strdup (basename);
trash_basename[1] = 'R'; /* $R... versus $I... */
char *trash_path = g_build_filename (dirname, trash_basename, NULL);
record->gone = g_file_test (trash_path, G_FILE_TEST_EXISTS) ?
FILESTATUS_EXISTS : FILESTATUS_GONE;
g_free (dirname);
g_free (trash_basename);
g_free (trash_path);
}

g_debug ("Parsing done for '%s'", basename);
record->index_s = basename;
record->meta = &meta;
*recordlist = g_slist_prepend (*recordlist, record);
g_free (buf);
return;

parse_record_error:

g_free (buf);
g_free (basename);
}


Expand Down Expand Up @@ -293,7 +310,8 @@ main (int argc,
while (ptr) {
// Ignore errors, pretty common that *some* folders don't
// exist or is empty.
check_file_args (ptr->data, &filelist, RECYCLE_BIN_TYPE_DIR, NULL);
check_file_args (ptr->data, &filelist,
RECYCLE_BIN_TYPE_DIR, NULL, NULL);
ptr = ptr->next;
}
ptr = NULL;
Expand All @@ -303,7 +321,7 @@ main (int argc,
#endif
{
exit_status = check_file_args (fileargs[0], &filelist,
RECYCLE_BIN_TYPE_DIR, &error);
RECYCLE_BIN_TYPE_DIR, &isolated_index, &error);
if (exit_status != R2_OK)
goto cleanup;
}
Expand All @@ -312,7 +330,6 @@ main (int argc,

/* Fill in recycle bin metadata */
meta.type = RECYCLE_BIN_TYPE_DIR;
meta.keep_deleted_entry = FALSE;
meta.is_empty = (filelist == NULL);
meta.has_unicode_path = TRUE;
if (live_mode)
Expand Down
8 changes: 4 additions & 4 deletions src/rifiuti.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,11 @@ populate_record_data (void *buf)
drivenum, record->index_n);
record->drive = driveletters[MIN (drivenum, sizeof (driveletters) - 1)];

record->emptied = FALSE;
record->gone = FILESTATUS_EXISTS;
/* first byte will be removed from filename if file is not in recycle bin */
if (!*legacy_fname)
{
record->emptied = TRUE;
record->gone = FILESTATUS_GONE;
*legacy_fname = record->drive;
}

Expand Down Expand Up @@ -353,7 +353,7 @@ main (int argc,
goto cleanup;

exit_status = check_file_args (fileargs[0], &filelist,
RECYCLE_BIN_TYPE_FILE, &error);
RECYCLE_BIN_TYPE_FILE, NULL, &error);
if (exit_status != R2_OK)
goto cleanup;

Expand All @@ -370,7 +370,7 @@ main (int argc,
* Note: always set this variable after parse_record_cb() because
* meta.version is not set beforehand
*/
meta.keep_deleted_entry = ( meta.version >= VERSION_WIN98 );
/* meta.keep_deleted_entry = ( meta.version >= VERSION_WIN98 ); */

if ( !meta.is_empty && (recordlist == NULL) )
{
Expand Down
113 changes: 74 additions & 39 deletions src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,33 @@ static DECL_OPT_CALLBACK(_set_opt_delim);
static DECL_OPT_CALLBACK(_set_opt_noheading);
static DECL_OPT_CALLBACK(_set_output_xml);

/* WARNING: MUST match order of _os_guess enum */

/**
* @brief More detailed OS version guess from artifacts
* @note This is different from `detected_os_ver`, which only checks for
* first few bytes. It is a more detailed breakdown, and for detection of
* exact Windows version from various recycle bin artifacts.
* @warning MUST match order of `os_strings` array except
* the `UNKNOWN` entry
*/
typedef enum
{
OS_GUESS_UNKNOWN = -1,
OS_GUESS_95,
OS_GUESS_NT4,
OS_GUESS_98,
OS_GUESS_ME,
OS_GUESS_2K,
OS_GUESS_XP_03,
OS_GUESS_2K_03, /* Empty recycle bin, full detection impossible */
OS_GUESS_VISTA, /* includes everything up to 8.1 */
OS_GUESS_10
} _os_guess;

/**
* @brief Outputed string for OS detection from artifacts
* @warning MUST match order of `_os_guess` enum
*/
static char *os_strings[] = {
N_("Windows 95"),
N_("Windows NT 4.0"),
Expand Down Expand Up @@ -1016,11 +1042,25 @@ _guess_windows_ver (const metarecord meta)
}
}

/*! Add potentially valid file(s) to list */
/**
* @brief Add potentially valid file(s) to list
* @param path The file or folder to be checked
* @param list A `GSList` pointer to store potential index files
* to be validated later on
* @param type Recycle bin type
* @param isolated_index Pointer to `gboolean`, indicating whether
* the concerned `path` is a single `$Recycle.bin` type index
* taken out of its original folder. Can be `NULL`, which means
* this check is not performed.
* @param error A `GError` pointer to store potential problems
* @return Exit status, which can potentially be used as global
* exit status of program.
*/
int
check_file_args (const char *path,
GSList **list,
rbin_type type,
gboolean *isolated_index,
GError **error)
{
g_debug ("Start checking path '%s'...", path);
Expand All @@ -1033,8 +1073,9 @@ check_file_args (const char *path,
_("'%s' does not exist."), path);
return R2_ERR_OPEN_FILE;
}
else if ( (type == RECYCLE_BIN_TYPE_DIR) &&
g_file_test (path, G_FILE_TEST_IS_DIR) )

if ((type == RECYCLE_BIN_TYPE_DIR) &&
g_file_test (path, G_FILE_TEST_IS_DIR))
{
if ( ! _populate_index_file_list (list, path, error) )
return R2_ERR_OPEN_FILE;
Expand All @@ -1050,8 +1091,15 @@ check_file_args (const char *path,
return R2_ERR_OPEN_FILE;
}
}
else if ( g_file_test (path, G_FILE_TEST_IS_REGULAR) )
else if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
{
if (isolated_index && (type == RECYCLE_BIN_TYPE_DIR)) {
char *parent_dir = g_path_get_dirname (path);
*isolated_index = ! _found_desktop_ini (parent_dir);
g_free (parent_dir);
}
*list = g_slist_prepend ( *list, g_strdup (path) );
}
else
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
Expand Down Expand Up @@ -1145,20 +1193,13 @@ _print_csv_header (metarecord meta)
g_print (_("Recycle bin path: '%s'\n"), rbin_path);
g_free (rbin_path);

{
char *ver;
if (meta.version == VERSION_NOT_FOUND) {
/* TRANSLATOR COMMENT: Empty folder, no file avaiable for analysis */
ver = g_strdup (_("??? (empty folder)"));
} else
ver = g_strdup_printf ("%" G_GUINT64_FORMAT, meta.version);

g_print (_("Version: %s"), ver);
g_print ("\n");
g_free (ver);
if (meta.version == VERSION_NOT_FOUND) {
g_print ("%s\n", _("Version: ??? (empty folder)"));
} else {
g_print (_("Version: %" G_GUINT64_FORMAT "\n"), meta.version);
}

if (( meta.type == RECYCLE_BIN_TYPE_FILE ) && ( ! meta.keep_deleted_entry ))
if (( meta.type == RECYCLE_BIN_TYPE_FILE ) && meta.total_entry)
{
g_print (_("Total entries ever existed: %d"), meta.total_entry);
g_print ("\n");
Expand Down Expand Up @@ -1222,19 +1263,14 @@ _print_csv_header (metarecord meta)
char *headerline;
char *fields[] = {
/* TRANSLATOR COMMENT: appears in column header */
N_("Index"), N_("Deleted Time"), N_("Size"), N_("Path"), NULL
N_("Index"), N_("Deleted Time"), N_("Gone?"), N_("Size"), N_("Path"), NULL
};

col_array = g_array_sized_new (TRUE, TRUE, sizeof (gpointer), 5);
for (char **col_ptr = fields; *col_ptr != NULL; col_ptr++) {
// const char *t = gettext (*col_ptr++);
g_array_append_val (col_array, *col_ptr);
}
if (meta.keep_deleted_entry) {
/* TRANSLATOR COMMENT: appears in column header, means file is restored or purged */
char *t = _("Gone?");
g_array_insert_val (col_array, 2, t);
}

headerline = g_strjoinv (delim, (char **) col_array->data);
g_print ("%s\n", headerline);
Expand Down Expand Up @@ -1323,13 +1359,11 @@ print_record_cb (rbin_struct *record,
else
size = g_strdup_printf ("%" G_GUINT64_FORMAT, record->filesize);

if (record->meta->keep_deleted_entry)
{
const char *purged = record->emptied ? _("Yes") : _("No");
outstr = g_strjoin (delim, index, deltime, purged, size, out_fname, NULL);
}
else
outstr = g_strjoin (delim, index, deltime, size, out_fname, NULL);
const char *gone =
record->gone == FILESTATUS_EXISTS ? "FALSE" :
record->gone == FILESTATUS_GONE ? "TRUE" :
"???" ;
outstr = g_strjoin (delim, index, deltime, gone, size, out_fname, NULL);

g_print ("%s\n", outstr);

Expand All @@ -1342,19 +1376,20 @@ print_record_cb (rbin_struct *record,
deltime = use_localtime ? g_date_time_format (dt, "%FT%T%z" ):
g_date_time_format (dt, "%FT%TZ");

g_string_printf (s, " <record index=\"%s\" time=\"%s\"", index, deltime);

if (record->meta->keep_deleted_entry)
g_string_append_printf (s, " emptied=\"%c\"", record->emptied ? 'Y' : 'N');
g_string_printf (s,
" <record index=\"%s\" time=\"%s\" gone=\"%s\"",
index, deltime,
(record->gone == FILESTATUS_GONE ) ? "true" :
(record->gone == FILESTATUS_EXISTS) ? "false":
"unknown");

if ( record->filesize == G_MAXUINT64 ) /* faulty */
size = g_strdup_printf (" size=\"-1\"");
g_string_append_printf (s, " size=\"-1\"");
else
size = g_strdup_printf (" size=\"%" G_GUINT64_FORMAT "\"", record->filesize);
s = g_string_append (s, (const gchar*) size);
g_string_append_printf (s,
" size=\"%" G_GUINT64_FORMAT "\"", record->filesize);

g_string_append_printf (s,
">\n"
g_string_append_printf (s, ">\n"
" <path><![CDATA[%s]]></path>\n"
" </record>\n", out_fname);

Expand Down
Loading