Files
Aaru/Aaru/Commands/Filesystem/Info.cs

422 lines
16 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Info.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Commands.
//
// --[ Description ] ----------------------------------------------------------
//
// Implements the 'fs-info' command.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading;
using Aaru.CommonTypes;
using Aaru.CommonTypes.AaruMetadata;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Core;
using Aaru.Localization;
using Aaru.Logging;
using Spectre.Console;
using Spectre.Console.Cli;
using Partition = Aaru.CommonTypes.Partition;
namespace Aaru.Commands.Filesystem;
sealed class FilesystemInfoCommand : Command<FilesystemInfoCommand.Settings>
{
const string MODULE_NAME = "Fs-info command";
public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken)
{
MainClass.PrintCopyright();
Statistics.AddCommand("fs-info");
AaruLogging.Debug(MODULE_NAME, "--debug={0}", settings.Debug);
AaruLogging.Debug(MODULE_NAME, "--encoding={0}", Markup.Escape(settings.Encoding ?? ""));
AaruLogging.Debug(MODULE_NAME, "--filesystems={0}", settings.Filesystems);
AaruLogging.Debug(MODULE_NAME, "--input={0}", Markup.Escape(settings.ImagePath ?? ""));
AaruLogging.Debug(MODULE_NAME, "--partitions={0}", settings.Partitions);
AaruLogging.Debug(MODULE_NAME, "--verbose={0}", settings.Verbose);
IFilter inputFilter = null;
Core.Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(UI.Identifying_file_filter).IsIndeterminate();
inputFilter = PluginRegister.Singleton.GetFilter(settings.ImagePath);
});
if(inputFilter == null)
{
AaruLogging.Error(UI.Cannot_open_specified_file);
return (int)ErrorNumber.CannotOpenFile;
}
Encoding encodingClass = null;
if(settings.Encoding != null)
{
try
{
encodingClass = Claunia.Encoding.Encoding.GetEncoding(settings.Encoding);
if(settings.Verbose) AaruLogging.Verbose(UI.encoding_for_0, encodingClass.EncodingName);
}
catch(ArgumentException)
{
AaruLogging.Error(UI.Specified_encoding_is_not_supported);
return (int)ErrorNumber.EncodingUnknown;
}
}
PluginRegister plugins = PluginRegister.Singleton;
var checkRaw = false;
try
{
IMediaImage imageFormat = null;
IBaseImage baseImage = null;
Core.Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(UI.Identifying_image_format).IsIndeterminate();
baseImage = ImageFormat.Detect(inputFilter);
imageFormat = baseImage as IMediaImage;
});
if(baseImage == null)
{
AaruLogging.WriteLine(UI.Image_format_not_identified_not_proceeding_with_analysis);
return (int)ErrorNumber.UnrecognizedFormat;
}
if(imageFormat == null)
{
AaruLogging.WriteLine(UI.Command_not_supported_for_this_image_type);
return (int)ErrorNumber.InvalidArgument;
}
if(settings.Verbose)
AaruLogging.Verbose(UI.Image_format_identified_by_0_1, Markup.Escape(imageFormat.Name), imageFormat.Id);
else
AaruLogging.WriteLine(UI.Image_format_identified_by_0, Markup.Escape(imageFormat.Name));
AaruLogging.WriteLine();
try
{
ErrorNumber opened = ErrorNumber.NoData;
Core.Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(UI.Invoke_Opening_image_file).IsIndeterminate();
opened = imageFormat.Open(inputFilter);
});
if(opened != ErrorNumber.NoError)
{
AaruLogging.WriteLine(UI.Unable_to_open_image_format);
AaruLogging.WriteLine(Localization.Core.Error_0, opened);
return (int)opened;
}
if(settings.Verbose)
{
ImageInfo.PrintImageInfo(imageFormat);
AaruLogging.WriteLine();
}
Statistics.AddMediaFormat(imageFormat.Format);
Statistics.AddMedia(imageFormat.Info.MediaType, false);
Statistics.AddFilter(inputFilter.Name);
}
catch(Exception ex)
{
AaruLogging.Error(UI.Unable_to_open_image_format);
AaruLogging.Error(Localization.Core.Error_0, ex.Message);
AaruLogging.Exception(ex, ex.Message);
return (int)ErrorNumber.CannotOpenFormat;
}
List<string> idPlugins = null;
IFilesystem fs;
string information;
if(settings.Partitions)
{
List<Partition> partitionsList = null;
Core.Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(UI.Enumerating_partitions).IsIndeterminate();
partitionsList = Core.Partitions.GetAll(imageFormat);
});
Core.Partitions.AddSchemesToStats(partitionsList);
if(partitionsList.Count == 0)
{
AaruLogging.Debug(MODULE_NAME, UI.No_partitions_found);
if(!settings.Filesystems)
{
AaruLogging.WriteLine(UI.No_partitions_found_not_searching_for_filesystems);
return (int)ErrorNumber.NothingFound;
}
checkRaw = true;
}
else
{
AaruLogging.WriteLine(UI._0_partitions_found, partitionsList.Count);
for(var i = 0; i < partitionsList.Count; i++)
{
Table table = new()
{
Title = new TableTitle(string.Format(UI.Partition_0, partitionsList[i].Sequence))
};
AaruLogging.Information(UI.Partition_0, partitionsList[i].Sequence);
table.AddColumn("");
table.AddColumn("");
table.HideHeaders();
table.AddRow(UI.Title_Name, $"[darkgreen]{Markup.Escape(partitionsList[i].Name ?? "")}[/]");
table.AddRow(UI.Title_Type, $"[olive]{Markup.Escape(partitionsList[i].Type ?? "")}[/]");
table.AddRow(Localization.Core.Title_Start,
string.Format(UI.sector_0_byte_1,
partitionsList[i].Start,
partitionsList[i].Offset));
table.AddRow(UI.Title_Length,
string.Format(UI._0_sectors_1_bytes,
partitionsList[i].Length,
partitionsList[i].Size));
table.AddRow(UI.Title_Scheme, $"[purple]{Markup.Escape(partitionsList[i].Scheme ?? "")}[/]");
table.AddRow(UI.Title_Description,
$"[slateblue1]{Markup.Escape(partitionsList[i].Description ?? "")}[/]");
AaruLogging.Information($"{UI.Title_Name}: {Markup.Escape(partitionsList[i].Name ?? "")}");
AaruLogging.Information($"{UI.Title_Type}: {Markup.Escape(partitionsList[i].Type ?? "")}");
AaruLogging
.Information($"{Localization.Core.Title_Start}: {string.Format(UI.sector_0_byte_1, partitionsList[i].Start, partitionsList[i].Offset)}");
AaruLogging
.Information($"{UI.Title_Length}: {string.Format(UI._0_sectors_1_bytes, partitionsList[i].Length, partitionsList[i].Size)}");
AaruLogging.Information($"{UI.Title_Scheme}: {Markup.Escape(partitionsList[i].Scheme ?? "")}");
AaruLogging
.Information($"{UI.Title_Description}: {Markup.Escape(partitionsList[i].Description ?? "")}");
AaruLogging.WriteLine();
AnsiConsole.Write(table);
if(!settings.Filesystems) continue;
Core.Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(UI.Identifying_filesystems_on_partition).IsIndeterminate();
Core.Filesystems.Identify(imageFormat, out idPlugins, partitionsList[i]);
});
switch(idPlugins.Count)
{
case 0:
AaruLogging.WriteLine($"[bold]{UI.Filesystem_not_identified}[/]");
break;
case > 1:
{
AaruLogging.WriteLine($"[italic]{string.Format(UI.Identified_by_0_plugins,
idPlugins.Count)}[/]");
foreach(string pluginName in idPlugins)
{
if(!plugins.Filesystems.TryGetValue(pluginName, out fs)) continue;
if(fs is null) continue;
AaruLogging.WriteLine($"[bold]{string.Format(UI.As_identified_by_0, fs.Name)
}[/]");
fs.GetInformation(imageFormat,
partitionsList[i],
encodingClass,
out information,
out FileSystem fsMetadata);
AaruLogging.Write(information);
Statistics.AddFilesystem(fsMetadata.Type);
}
break;
}
default:
{
plugins.Filesystems.TryGetValue(idPlugins[0], out fs);
if(fs is null) continue;
AaruLogging.WriteLine($"[bold]{string.Format(UI.Identified_by_0, fs.Name)}[/]");
fs.GetInformation(imageFormat,
partitionsList[i],
encodingClass,
out information,
out FileSystem fsMetadata);
AaruLogging.Write(information);
Statistics.AddFilesystem(fsMetadata.Type);
break;
}
}
AaruLogging.WriteLine();
}
}
}
if(checkRaw)
{
var wholePart = new Partition
{
Name = Localization.Core.Whole_device,
Length = imageFormat.Info.Sectors,
Size = imageFormat.Info.Sectors * imageFormat.Info.SectorSize
};
Core.Spectre.ProgressSingleSpinner(ctx =>
{
ctx.AddTask(UI.Identifying_filesystems).IsIndeterminate();
Core.Filesystems.Identify(imageFormat, out idPlugins, wholePart);
});
switch(idPlugins.Count)
{
case 0:
AaruLogging.WriteLine($"[bold]{UI.Filesystem_not_identified}[/]");
break;
case > 1:
{
AaruLogging.WriteLine($"[italic]{string.Format(UI.Identified_by_0_plugins, idPlugins.Count)
}[/]");
foreach(string pluginName in idPlugins)
{
if(!plugins.Filesystems.TryGetValue(pluginName, out fs)) continue;
if(fs is null) continue;
AaruLogging.WriteLine($"[bold]{string.Format(UI.As_identified_by_0, fs.Name)}[/]");
fs.GetInformation(imageFormat,
wholePart,
encodingClass,
out information,
out FileSystem fsMetadata);
AaruLogging.Write(information);
Statistics.AddFilesystem(fsMetadata.Type);
}
break;
}
default:
{
plugins.Filesystems.TryGetValue(idPlugins[0], out fs);
if(fs is null) break;
AaruLogging.WriteLine($"[bold]{string.Format(UI.Identified_by_0, fs.Name)}[/]");
fs.GetInformation(imageFormat,
wholePart,
encodingClass,
out information,
out FileSystem fsMetadata);
AaruLogging.Write(information);
Statistics.AddFilesystem(fsMetadata.Type);
break;
}
}
}
}
catch(Exception ex)
{
AaruLogging.Error(Markup.Escape(string.Format(UI.Error_reading_file_0, ex.Message)));
AaruLogging.Exception(ex, UI.Error_reading_file_0, ex.Message);
return (int)ErrorNumber.UnexpectedException;
}
return (int)ErrorNumber.NoError;
}
#region Nested type: Settings
public class Settings : FilesystemFamily
{
[LocalizedDescription(nameof(UI.Name_of_character_encoding_to_use))]
[DefaultValue(null)]
[CommandOption("-e|--encoding")]
public string Encoding { get; init; }
[LocalizedDescription(nameof(UI.Searches_and_interprets_partitions))]
[DefaultValue(true)]
[CommandOption("-p|--partitions")]
public bool Partitions { get; init; }
[LocalizedDescription(nameof(UI.Searches_and_prints_information_about_filesystems))]
[CommandOption("-f|--filesystems")]
[DefaultValue(true)]
public bool Filesystems { get; init; }
[LocalizedDescription(nameof(UI.Media_image_path))]
[CommandArgument(0, "<image-path>")]
public string ImagePath { get; init; }
}
#endregion
}