From f1d2130d80cbedbebcee648483e1e006abad1b70 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Wed, 27 Jul 2016 02:35:29 +0100 Subject: [PATCH] * DiscImageChef/Main.cs: * DiscImageChef/Options.cs: * DiscImageChef/DiscImageChef.csproj: * DiscImageChef/Commands/ExtractFiles.cs: Added command to extract all files from a filesystem. * DiscImageChef.Filesystems/LisaFS/Consts.cs: Corrected comments. Added ftype known values. * DiscImageChef.Filesystems/LisaFS/Dir.cs: Changed field name. * DiscImageChef.Filesystems/LisaFS/Extent.cs: * DiscImageChef.Filesystems/LisaFS/Structs.cs: Reverse engineered new fields from ExtentsFile * DiscImageChef.Filesystems/LisaFS/File.cs: Added support for reading tags. Added flags and ftype fields from ExtentsFile. * DiscImageChef.Filesystems/LisaFS/Xattr.cs: Changed how serial number is returned. Allow to get tags in debug mode as an xattr. * DiscImageChef.Filesystems/Structs.cs: Added PIPE attribute. --- DiscImageChef.Filesystems/LisaFS/Consts.cs | 24 +- DiscImageChef.Filesystems/LisaFS/Dir.cs | 2 +- DiscImageChef.Filesystems/LisaFS/Extent.cs | 42 ++- DiscImageChef.Filesystems/LisaFS/File.cs | 98 ++++- DiscImageChef.Filesystems/LisaFS/Structs.cs | 70 +++- DiscImageChef.Filesystems/LisaFS/Xattr.cs | 51 ++- DiscImageChef.Filesystems/Structs.cs | 4 +- DiscImageChef/Commands/ExtractFiles.cs | 394 ++++++++++++++++++++ DiscImageChef/DiscImageChef.csproj | 1 + DiscImageChef/Main.cs | 12 +- DiscImageChef/Options.cs | 14 + 11 files changed, 641 insertions(+), 71 deletions(-) create mode 100644 DiscImageChef/Commands/ExtractFiles.cs diff --git a/DiscImageChef.Filesystems/LisaFS/Consts.cs b/DiscImageChef.Filesystems/LisaFS/Consts.cs index 9755f405..a51fe636 100644 --- a/DiscImageChef.Filesystems/LisaFS/Consts.cs +++ b/DiscImageChef.Filesystems/LisaFS/Consts.cs @@ -44,20 +44,40 @@ namespace DiscImageChef.Filesystems.LisaFS const byte LisaFSv1 = 0x0E; const byte LisaFSv2 = 0x0F; const byte LisaFSv3 = 0x11; + /// Maximum string size in LisaFS const uint E_NAME = 32; - // Maximum string size in LisaFS const UInt16 FILEID_FREE = 0x0000; const UInt16 FILEID_BOOT = 0xAAAA; const UInt16 FILEID_LOADER = 0xBBBB; const UInt16 FILEID_MDDF = 0x0001; const UInt16 FILEID_BITMAP = 0x0002; const UInt16 FILEID_SRECORD = 0x0003; + /// "Catalog file" const UInt16 FILEID_DIRECTORY = 0x0004; const Int16 FILEID_BOOT_SIGNED = -21846; const Int16 FILEID_LOADER_SIGNED = -17477; - // "Catalog file" const UInt16 FILEID_ERASED = 0x7FFF; const UInt16 FILEID_MAX = FILEID_ERASED; + + enum FileType : byte + { + Undefined = 0, + MDDFile = 1, + RootCat = 2, + FreeList = 3, + BadBlocks = 4, + SysData = 5, + Spool = 6, + Exec = 7, + UserCat = 8, + Pipe = 9, + BootFile = 10, + SwapData = 11, + SwapCode = 12, + RamAP = 13, + UserFile = 14, + KilledObject = 15 + } } } diff --git a/DiscImageChef.Filesystems/LisaFS/Dir.cs b/DiscImageChef.Filesystems/LisaFS/Dir.cs index 10aede8b..cdd61624 100644 --- a/DiscImageChef.Filesystems/LisaFS/Dir.cs +++ b/DiscImageChef.Filesystems/LisaFS/Dir.cs @@ -147,7 +147,7 @@ namespace DiscImageChef.Filesystems.LisaFS entry.zero = BigEndianBitConverter.ToUInt16(buf, offset + 0x01); entry.filename = new byte[E_NAME]; Array.Copy(buf, offset + 0x03, entry.filename, 0, E_NAME); - entry.padding = buf[offset + 0x23]; + entry.terminator = buf[offset + 0x23]; entry.fileType = buf[offset + 0x24]; entry.unknown = buf[offset + 0x25]; entry.fileID = BigEndianBitConverter.ToInt16(buf, offset + 0x26); diff --git a/DiscImageChef.Filesystems/LisaFS/Extent.cs b/DiscImageChef.Filesystems/LisaFS/Extent.cs index eacf8cd4..ebfe5b9b 100644 --- a/DiscImageChef.Filesystems/LisaFS/Extent.cs +++ b/DiscImageChef.Filesystems/LisaFS/Extent.cs @@ -88,27 +88,43 @@ namespace DiscImageChef.Filesystems.LisaFS file.filenameLen = sector[0]; file.filename = new byte[file.filenameLen]; Array.Copy(sector, 0x01, file.filename, 0, file.filenameLen); - file.timestamp = BigEndianBitConverter.ToUInt32(sector, 0x20); - file.unknown1 = new byte[3]; - Array.Copy(sector, 0x24, file.unknown1, 0, 3); - file.serial = new byte[3]; - Array.Copy(sector, 0x27, file.serial, 0, 3); - file.unknown2 = BigEndianBitConverter.ToUInt32(sector, 0x2A); + file.unknown1 = BigEndianBitConverter.ToUInt16(sector, 0x20); + file.file_uid = BigEndianBitConverter.ToUInt64(sector, 0x22); + file.unknown2 = sector[0x2A]; + file.etype = sector[0x2B]; + file.ftype = (FileType)sector[0x2C]; + file.unknown3 = sector[0x2D]; file.dtc = BigEndianBitConverter.ToUInt32(sector, 0x2E); file.dta = BigEndianBitConverter.ToUInt32(sector, 0x32); file.dtm = BigEndianBitConverter.ToUInt32(sector, 0x36); file.dtb = BigEndianBitConverter.ToUInt32(sector, 0x3A); file.dts = BigEndianBitConverter.ToUInt32(sector, 0x3E); - file.unknown3 = new byte[32]; - Array.Copy(sector, 0x42, file.unknown3, 0, 32); - file.flags = sector[0x62]; + file.serial = BigEndianBitConverter.ToUInt32(sector, 0x42); + file.unknown4 = sector[0x46]; + file.locked = sector[0x47]; + file.protect = sector[0x48]; + file.master = sector[0x49]; + file.scavenged = sector[0x4A]; + file.closed = sector[0x4B]; + file.open = sector[0x4C]; + file.unknown5 = new byte[11]; + Array.Copy(sector, 0x4D, file.unknown5, 0, 11); + file.release = BigEndianBitConverter.ToUInt16(sector, 0x58); + file.build = BigEndianBitConverter.ToUInt16(sector, 0x5A); + file.compatibility = BigEndianBitConverter.ToUInt16(sector, 0x5C); + file.revision = BigEndianBitConverter.ToUInt16(sector, 0x5E); + file.unknown6 = BigEndianBitConverter.ToUInt16(sector, 0x60); + file.password_valid = sector[0x62]; file.password = new byte[8]; Array.Copy(sector, 0x63, file.password, 0, 8); - file.unknown4 = new byte[21]; - Array.Copy(sector, 0x6B, file.unknown4, 0, 21); + file.unknown7 = new byte[3]; + Array.Copy(sector, 0x6B, file.unknown7, 0, 3); + file.overhead = BigEndianBitConverter.ToUInt16(sector, 0x6E); + file.unknown8 = new byte[16]; + Array.Copy(sector, 0x70, file.unknown8, 0, 16); file.length = BigEndianBitConverter.ToInt32(sector, 0x80); - file.unknown5 = BigEndianBitConverter.ToInt32(sector, 0x84); - file.unknown6 = BigEndianBitConverter.ToInt16(sector, 0x17E); + file.unknown9 = BigEndianBitConverter.ToInt32(sector, 0x84); + file.unknown10 = BigEndianBitConverter.ToInt16(sector, 0x17E); file.LisaInfo = new byte[128]; Array.Copy(sector, 0x180, file.LisaInfo, 0, 128); diff --git a/DiscImageChef.Filesystems/LisaFS/File.cs b/DiscImageChef.Filesystems/LisaFS/File.cs index 3b727e8f..ee7b187e 100644 --- a/DiscImageChef.Filesystems/LisaFS/File.cs +++ b/DiscImageChef.Filesystems/LisaFS/File.cs @@ -41,6 +41,7 @@ using DiscImageChef.ImagePlugins; using System.Runtime.InteropServices; using System.Collections.Generic; using DiscImageChef.Console; +using System.IO; namespace DiscImageChef.Filesystems.LisaFS { @@ -143,16 +144,44 @@ namespace DiscImageChef.Filesystems.LisaFS return error; attributes = new FileAttributes(); - // TODO: Subcatalogs - attributes = FileAttributes.File; - attributes |= FileAttributes.Extents; - if((extFile.flags & 0x08) == 0x08) + + switch(extFile.ftype) + { + case FileType.Spool: + attributes |= FileAttributes.CharDevice; + break; + case FileType.UserCat: + case FileType.RootCat: + attributes |= FileAttributes.Directory; + break; + case FileType.Pipe: + attributes |= FileAttributes.Pipe; + break; + case FileType.Undefined: + break; + default: + attributes |= FileAttributes.File; + // Subcatalogs use extents? + attributes |= FileAttributes.Extents; + break; + } + + if(extFile.protect > 0) + attributes |= FileAttributes.Immutable; + if(extFile.locked > 0) + attributes |= FileAttributes.ReadOnly; + if(extFile.password_valid > 0) attributes |= FileAttributes.Password; return Errno.NoError; } Errno ReadSystemFile(Int16 fileId, out byte[] buf) + { + return ReadSystemFile(fileId, out buf, false); + } + + Errno ReadSystemFile(Int16 fileId, out byte[] buf, bool tags) { buf = null; if(!mounted || !debug) @@ -164,7 +193,7 @@ namespace DiscImageChef.Filesystems.LisaFS return Errno.InvalidArgument; } - if(systemFileCache.TryGetValue(fileId, out buf)) + if(systemFileCache.TryGetValue(fileId, out buf) && !tags) return Errno.NoError; int count = 0; @@ -182,7 +211,10 @@ namespace DiscImageChef.Filesystems.LisaFS if(count == 0) return Errno.NoSuchFile; - buf = new byte[count * device.GetSectorSize()]; + if(!tags) + buf = new byte[count * device.GetSectorSize()]; + else + buf = new byte[count * 12]; // Should be enough to check 100 sectors? for(ulong i = 0; i < 100; i++) @@ -193,12 +225,20 @@ namespace DiscImageChef.Filesystems.LisaFS if(id == fileId) { UInt16 pos = BigEndianBitConverter.ToUInt16(tag, 0x06); - byte[] sector = device.ReadSector(i); + byte[] sector; + + if(!tags) + sector = device.ReadSector(i); + else + sector = device.ReadSectorTag(i, SectorTagType.AppleSectorTag); + Array.Copy(sector, 0, buf, sector.Length * pos, sector.Length); } } - systemFileCache.Add(fileId, buf); + if(!tags) + systemFileCache.Add(fileId, buf); + return Errno.NoError; } @@ -233,7 +273,6 @@ namespace DiscImageChef.Filesystems.LisaFS stat.CreationTime = DateHandlers.LisaToDateTime(file.dtc); stat.AccessTime = DateHandlers.LisaToDateTime(file.dta); - stat.StatusChangeTime = DateHandlers.LisaToDateTime(file.timestamp); stat.BackupTime = DateHandlers.LisaToDateTime(file.dtb); stat.LastWriteTime = DateHandlers.LisaToDateTime(file.dtm); @@ -294,7 +333,6 @@ namespace DiscImageChef.Filesystems.LisaFS stat.CreationTime = DateHandlers.LisaToDateTime(file.dtc); stat.AccessTime = DateHandlers.LisaToDateTime(file.dta); - stat.StatusChangeTime = DateHandlers.LisaToDateTime(file.timestamp); stat.BackupTime = DateHandlers.LisaToDateTime(file.dtb); stat.LastWriteTime = DateHandlers.LisaToDateTime(file.dtm); @@ -316,15 +354,22 @@ namespace DiscImageChef.Filesystems.LisaFS } Errno ReadFile(Int16 fileId, out byte[] buf) + { + return ReadFile(fileId, out buf, false); + } + + Errno ReadFile(Int16 fileId, out byte[] buf, bool tags) { buf = null; if(!mounted) return Errno.AccessDenied; + tags &= debug; + if(fileId <= 4) return Errno.InvalidArgument; - if(fileCache.TryGetValue(fileId, out buf)) + if(!tags && fileCache.TryGetValue(fileId, out buf)) return Errno.NoError; Errno error; @@ -334,27 +379,44 @@ namespace DiscImageChef.Filesystems.LisaFS if(error != Errno.NoError) return error; - byte[] temp = new byte[file.length * (int)device.GetSectors()]; + int sectorSize; + if(tags) + sectorSize = 12; + else + sectorSize = (int)device.GetSectorSize(); + + byte[] temp = new byte[file.length * sectorSize]; int offset = 0; for(int i = 0; i < file.extents.Length; i++) { - byte[] sector = device.ReadSectors((ulong)(file.extents[i].start + mddf.mddf_block), (uint)file.extents[i].length); + byte[] sector; + + if(!tags) + sector = device.ReadSectors((ulong)(file.extents[i].start + mddf.mddf_block), (uint)file.extents[i].length); + else + sector = device.ReadSectorsTag((ulong)(file.extents[i].start + mddf.mddf_block), (uint)file.extents[i].length, SectorTagType.AppleSectorTag); Array.Copy(sector, 0, temp, offset, sector.Length); offset += sector.Length; } - int realSize; - if(fileSizeCache.TryGetValue(fileId, out realSize)) + if(!tags) { - buf = new byte[realSize]; - Array.Copy(temp, 0, buf, 0, realSize); + int realSize; + if(fileSizeCache.TryGetValue(fileId, out realSize)) + { + buf = new byte[realSize]; + Array.Copy(temp, 0, buf, 0, realSize); + } + else + buf = temp; + + fileCache.Add(fileId, buf); } else buf = temp; - fileCache.Add(fileId, buf); return Errno.NoError; } diff --git a/DiscImageChef.Filesystems/LisaFS/Structs.cs b/DiscImageChef.Filesystems/LisaFS/Structs.cs index e0252b48..a2084d57 100644 --- a/DiscImageChef.Filesystems/LisaFS/Structs.cs +++ b/DiscImageChef.Filesystems/LisaFS/Structs.cs @@ -229,8 +229,8 @@ namespace DiscImageChef.Filesystems.LisaFS public ushort zero; /// 0x03, filename, 32-bytes, null-padded public byte[] filename; - /// 0x23, seems to be always zero - public byte padding; + /// 0x23, null-termination + public byte terminator; /// /// At 0x24 /// 0x03 here for entries 64 bytes long @@ -267,14 +267,18 @@ namespace DiscImageChef.Filesystems.LisaFS public byte filenameLen; /// 0x01, filename public byte[] filename; - /// 0x20, unknown timestamp - public UInt32 timestamp; - /// 0x24, 3 bytes, unknown - public byte[] unknown1; - /// 0x27, 3 bytes, machine serial number - public byte[] serial; - /// 0x2A, 4 bytes, unknown - public uint unknown2; + /// 0x20, unknown + public ushort unknown1; + /// 0x22, 8 bytes + public UInt64 file_uid; + /// 0x2A, unknown + public byte unknown2; + /// 0x2B, entry type? gets modified + public byte etype; + /// 0x2C, file type + public FileType ftype; + /// 0x2D, unknown + public byte unknown3; /// 0x2E, creation time public UInt32 dtc; /// 0x32, last access time @@ -285,22 +289,52 @@ namespace DiscImageChef.Filesystems.LisaFS public UInt32 dtb; /// 0x3E, scavenge time public UInt32 dts; - /// 0x42, unknown, 32 bytes - public byte[] unknown3; - /// 0x62, flags?, 0x08 set if password is valid - public byte flags; + /// 0x42, machine serial number + public UInt32 serial; + /// 0x46, unknown + public byte unknown4; + /// 0x47, locked file + public byte locked; + /// 0x48, protected file + public byte protect; + /// 0x49, master file + public byte master; + /// 0x4A, scavenged file + public byte scavenged; + /// 0x4B, file closed by os + public byte closed; + /// 0x4C, file left open + public byte open; + /// 0x4D, 11 bytes, unknown + public byte[] unknown5; + /// 0x58, Release number + public UInt16 release; + /// 0x5A, Build number + public UInt16 build; + /// 0x5C, Compatibility level + public UInt16 compatibility; + /// 0x5E, Revision level + public UInt16 revision; + /// 0x60, unknown + public ushort unknown6; + /// 0x62, 0x08 set if password is valid + public byte password_valid; /// 0x63, 8 bytes, scrambled password public byte[] password; - /// 0x6B, 21 bytes, unknown - public byte[] unknown4; + /// 0x6B, 3 bytes, unknown + public byte[] unknown7; + /// 0x6E, filesystem overhead + public ushort overhead; + /// 0x70, 16 bytes, unknown + public byte[] unknown8; /// 0x80, file length in blocks public Int32 length; /// 0x84, unknown - public Int32 unknown5; + public Int32 unknown9; /// 0x88, extents, can contain up to 41 extents, dunno LisaOS maximum (never seen more than 3) public Extent[] extents; /// 0x17E, unknown, empty, padding? - public short unknown6; + public short unknown10; /// 0x180, 128 bytes public byte[] LisaInfo; } diff --git a/DiscImageChef.Filesystems/LisaFS/Xattr.cs b/DiscImageChef.Filesystems/LisaFS/Xattr.cs index 196ed091..539b8263 100644 --- a/DiscImageChef.Filesystems/LisaFS/Xattr.cs +++ b/DiscImageChef.Filesystems/LisaFS/Xattr.cs @@ -38,6 +38,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Configuration; namespace DiscImageChef.Filesystems.LisaFS { @@ -79,29 +80,40 @@ namespace DiscImageChef.Filesystems.LisaFS if(fileId == FILEID_MDDF) xattrs.Add("com.apple.lisa.password"); + } + else + { - return Errno.NoError; + ExtentFile file; + + Errno error = ReadExtentsFile(fileId, out file); + + if(error != Errno.NoError) + return error; + + xattrs = new List(); + if(file.password_valid > 0) + xattrs.Add("com.apple.lisa.password"); + xattrs.Add("com.apple.lisa.serial"); + + if(!ArrayHelpers.ArrayIsNullOrEmpty(file.LisaInfo)) + xattrs.Add("com.apple.lisa.label"); } - ExtentFile file; + if(debug) + xattrs.Add("com.apple.lisa.tags"); - Errno error = ReadExtentsFile(fileId, out file); - - if(error != Errno.NoError) - return error; - - xattrs = new List(); - if((file.flags & 0x08) == 0x08) - xattrs.Add("com.apple.lisa.password"); - xattrs.Add("com.apple.lisa.serial"); - - if(!ArrayHelpers.ArrayIsNullOrEmpty(file.LisaInfo)) - xattrs.Add("com.apple.lisa.label"); + xattrs.Sort(); return Errno.NoError; } Errno GetXattr(Int16 fileId, string xattr, out byte[] buf) + { + return GetXattr(fileId, xattr, out buf, false); + } + + Errno GetXattr(Int16 fileId, string xattr, out byte[] buf, bool tags) { buf = null; @@ -122,6 +134,9 @@ namespace DiscImageChef.Filesystems.LisaFS } } + if(debug && xattr == "com.apple.lisa.tags") + return ReadSystemFile(fileId, out buf, true); + return Errno.NoSuchExtendedAttribute; } @@ -132,7 +147,7 @@ namespace DiscImageChef.Filesystems.LisaFS if(error != Errno.NoError) return error; - if(xattr == "com.apple.lisa.password" && (file.flags & 0x08) == 0x08) + if(xattr == "com.apple.lisa.password" && file.password_valid > 0) { buf = new byte[8]; Array.Copy(file.password, 0, buf, 0, 8); @@ -141,8 +156,7 @@ namespace DiscImageChef.Filesystems.LisaFS if(xattr == "com.apple.lisa.serial") { - buf = new byte[3]; - Array.Copy(file.serial, 0, buf, 0, 3); + buf = Encoding.ASCII.GetBytes(file.serial.ToString()); return Errno.NoError; } @@ -153,6 +167,9 @@ namespace DiscImageChef.Filesystems.LisaFS return Errno.NoError; } + if(debug && xattr == "com.apple.lisa.tags") + return ReadFile(fileId, out buf, true); + return Errno.NoSuchExtendedAttribute; } } diff --git a/DiscImageChef.Filesystems/Structs.cs b/DiscImageChef.Filesystems/Structs.cs index cdb3e10a..51156198 100644 --- a/DiscImageChef.Filesystems/Structs.cs +++ b/DiscImageChef.Filesystems/Structs.cs @@ -142,7 +142,9 @@ namespace DiscImageChef.Filesystems /// Subdirectories inside of this directory are not related and should be allocated elsewhere TopDirectory = 0x400000000000, /// If file is deleted, contents should be stored, for a possible future undeletion - Undeletable = 0x800000000000 + Undeletable = 0x800000000000, + /// File is a pipe + Pipe = 0x1000000000000 } /// diff --git a/DiscImageChef/Commands/ExtractFiles.cs b/DiscImageChef/Commands/ExtractFiles.cs new file mode 100644 index 00000000..2acbc8de --- /dev/null +++ b/DiscImageChef/Commands/ExtractFiles.cs @@ -0,0 +1,394 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : ExtractFiles.cs +// Version : 1.0 +// Author(s) : Natalia Portillo +// +// Component : Component +// +// Revision : $Revision$ +// Last change by : $Author$ +// Date : $Date$ +// +// --[ Description ] ---------------------------------------------------------- +// +// Description +// +// --[ 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 . +// +// ---------------------------------------------------------------------------- +// Copyright (C) 2011-2015 Claunia.com +// ****************************************************************************/ +// //$Id$ + +// //$Id$ +using System; +using System.Collections.Generic; +using DiscImageChef.Console; +using DiscImageChef.Filesystems; +using DiscImageChef.ImagePlugins; +using DiscImageChef.PartPlugins; +using System.IO; + +namespace DiscImageChef.Commands +{ + public class ExtractFiles + { + public static void doExtractFiles(ExtractFilesOptions options) + { + DicConsole.DebugWriteLine("Extract-Files command", "--debug={0}", options.Debug); + DicConsole.DebugWriteLine("Extract-Files command", "--verbose={0}", options.Verbose); + DicConsole.DebugWriteLine("Extract-Files command", "--input={0}", options.InputFile); + DicConsole.DebugWriteLine("Extract-Files command", "--xattrs={0}", options.Xattrs); + DicConsole.DebugWriteLine("Extract-Files command", "--output={0}", options.OutputDir); + + if(!System.IO.File.Exists(options.InputFile)) + { + DicConsole.ErrorWriteLine("Specified file does not exist."); + return; + } + + PluginBase plugins = new PluginBase(); + plugins.RegisterAllPlugins(); + + List id_plugins; + Filesystem _plugin; + ImagePlugin _imageFormat; + Errno error; + + try + { + _imageFormat = ImageFormat.Detect(options.InputFile); + + if(_imageFormat == null) + { + DicConsole.WriteLine("Image format not identified, not proceeding with analysis."); + return; + } + else + { + if(options.Verbose) + DicConsole.VerboseWriteLine("Image format identified by {0} ({1}).", _imageFormat.Name, _imageFormat.PluginUUID); + else + DicConsole.WriteLine("Image format identified by {0}.", _imageFormat.Name); + } + + if(Directory.Exists(options.OutputDir) || File.Exists(options.OutputDir)) + { + DicConsole.ErrorWriteLine("Destination exists, aborting."); + return; + } + + Directory.CreateDirectory(options.OutputDir); + + try + { + if(!_imageFormat.OpenImage(options.InputFile)) + { + DicConsole.WriteLine("Unable to open image format"); + DicConsole.WriteLine("No error given"); + return; + } + + DicConsole.DebugWriteLine("Extract-Files command", "Correctly opened image file."); + DicConsole.DebugWriteLine("Extract-Files command", "Image without headers is {0} bytes.", _imageFormat.GetImageSize()); + DicConsole.DebugWriteLine("Extract-Files command", "Image has {0} sectors.", _imageFormat.GetSectors()); + DicConsole.DebugWriteLine("Extract-Files command", "Image identifies disk type as {0}.", _imageFormat.GetMediaType()); + + Core.Statistics.AddMediaFormat(_imageFormat.GetImageFormat()); + Core.Statistics.AddMedia(_imageFormat.ImageInfo.mediaType, false); + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Unable to open image format"); + DicConsole.ErrorWriteLine("Error: {0}", ex.Message); + return; + } + + List partitions = new List(); + string partition_scheme = ""; + + // TODO: Solve possibility of multiple partition schemes (CUE + MBR, MBR + RDB, CUE + APM, etc) + foreach(PartPlugin _partplugin in plugins.PartPluginsList.Values) + { + List _partitions; + if(_partplugin.GetInformation(_imageFormat, out _partitions)) + { + partition_scheme = _partplugin.Name; + partitions.AddRange(_partitions); + Core.Statistics.AddPartition(_partplugin.Name); + } + } + + if(_imageFormat.ImageHasPartitions()) + { + partition_scheme = _imageFormat.GetImageFormat(); + partitions.AddRange(_imageFormat.GetPartitions()); + } + + if(partition_scheme == "") + DicConsole.DebugWriteLine("Extract-Files command", "No partitions found"); + else + { + DicConsole.WriteLine("Partition scheme identified as {0}", partition_scheme); + DicConsole.WriteLine("{0} partitions found.", partitions.Count); + + for(int i = 0; i < partitions.Count; i++) + { + DicConsole.WriteLine(); + DicConsole.WriteLine("Partition {0}:", partitions[i].PartitionSequence); + + DicConsole.WriteLine("Identifying filesystem on partition"); + + IdentifyFilesystems(_imageFormat, out id_plugins, partitions[i].PartitionStartSector, partitions[i].PartitionStartSector + partitions[i].PartitionSectors); + if(id_plugins.Count == 0) + DicConsole.WriteLine("Filesystem not identified"); + else if(id_plugins.Count > 1) + { + DicConsole.WriteLine(String.Format("Identified by {0} plugins", id_plugins.Count)); + + foreach(string plugin_name in id_plugins) + { + if(plugins.PluginsList.TryGetValue(plugin_name, out _plugin)) + { + DicConsole.WriteLine(String.Format("As identified by {0}.", _plugin.Name)); + Filesystem fs = (Filesystem)_plugin.GetType().GetConstructor(new Type[] { typeof(ImagePlugin), typeof(ulong), typeof(ulong) }).Invoke(new object[] { _imageFormat, partitions[i].PartitionStartSector, partitions[i].PartitionStartSector + partitions[i].PartitionSectors }); + + error = fs.Mount(options.Debug); + if(error == Errno.NoError) + { + List rootDir = new List(); + error = fs.ReadDir("/", ref rootDir); + if(error == Errno.NoError) + { + foreach(string entry in rootDir) + DicConsole.WriteLine("{0}", entry); + } + else + DicConsole.ErrorWriteLine("Error {0} reading root directory {0}", error.ToString()); + + Core.Statistics.AddFilesystem(fs.XmlFSType.Type); + } + else + DicConsole.ErrorWriteLine("Unable to mount device, error {0}", error.ToString()); + } + } + } + else + { + plugins.PluginsList.TryGetValue(id_plugins[0], out _plugin); + DicConsole.WriteLine(String.Format("Identified by {0}.", _plugin.Name)); + Filesystem fs = (Filesystem)_plugin.GetType().GetConstructor(new Type[] { typeof(ImagePlugin), typeof(ulong), typeof(ulong) }).Invoke(new object[] { _imageFormat, partitions[i].PartitionStartSector, partitions[i].PartitionStartSector + partitions[i].PartitionSectors }); + error = fs.Mount(options.Debug); + if(error == Errno.NoError) + { + List rootDir = new List(); + error = fs.ReadDir("/", ref rootDir); + if(error == Errno.NoError) + { + foreach(string entry in rootDir) + DicConsole.WriteLine("{0}", entry); + } + else + DicConsole.ErrorWriteLine("Error {0} reading root directory {0}", error.ToString()); + + Core.Statistics.AddFilesystem(fs.XmlFSType.Type); + } + else + DicConsole.ErrorWriteLine("Unable to mount device, error {0}", error.ToString()); + } + } + } + + IdentifyFilesystems(_imageFormat, out id_plugins, 0, _imageFormat.GetSectors() - 1); + if(id_plugins.Count == 0) + DicConsole.WriteLine("Filesystem not identified"); + else if(id_plugins.Count > 1) + { + DicConsole.WriteLine(String.Format("Identified by {0} plugins", id_plugins.Count)); + + foreach(string plugin_name in id_plugins) + { + if(plugins.PluginsList.TryGetValue(plugin_name, out _plugin)) + { + DicConsole.WriteLine(String.Format("As identified by {0}.", _plugin.Name)); + Filesystem fs = (Filesystem)_plugin.GetType().GetConstructor(new Type[] { typeof(ImagePlugin), typeof(ulong), typeof(ulong) }).Invoke(new object[] { _imageFormat, (ulong)0, (ulong)(_imageFormat.GetSectors() - 1) }); + error = fs.Mount(options.Debug); + if(error == Errno.NoError) + { + List rootDir = new List(); + error = fs.ReadDir("/", ref rootDir); + if(error == Errno.NoError) + { + foreach(string entry in rootDir) + DicConsole.WriteLine("{0}", entry); + } + else + DicConsole.ErrorWriteLine("Error {0} reading root directory {0}", error.ToString()); + + Core.Statistics.AddFilesystem(fs.XmlFSType.Type); + } + else + DicConsole.ErrorWriteLine("Unable to mount device, error {0}", error.ToString()); + } + } + } + else + { + plugins.PluginsList.TryGetValue(id_plugins[0], out _plugin); + DicConsole.WriteLine(String.Format("Identified by {0}.", _plugin.Name)); + Filesystem fs = (Filesystem)_plugin.GetType().GetConstructor(new Type[] { typeof(ImagePlugin), typeof(ulong), typeof(ulong) }).Invoke(new object[] { _imageFormat, (ulong)0, (ulong)(_imageFormat.GetSectors() - 1) }); + error = fs.Mount(options.Debug); + if(error == Errno.NoError) + { + List rootDir = new List(); + error = fs.ReadDir("/", ref rootDir); + if(error == Errno.NoError) + { + foreach(string entry in rootDir) + { + FileEntryInfo stat = new FileEntryInfo(); + string outputPath; + FileStream outputFile; + + error = fs.Stat(entry, ref stat); + if(error == Errno.NoError) + { + if(options.Xattrs) + { + List xattrs = new List(); + + error = fs.ListXAttr(entry, ref xattrs); + if(error == Errno.NoError) + { + foreach(string xattr in xattrs) + { + byte[] xattrBuf = new byte[0]; + error = fs.GetXattr(entry, xattr, ref xattrBuf); + if(error == Errno.NoError) + { + Directory.CreateDirectory(Path.Combine(options.OutputDir, + fs.XmlFSType.Type, + fs.XmlFSType.VolumeName, + ".xattrs", + xattr)); + + outputPath = Path.Combine(options.OutputDir, + fs.XmlFSType.Type, + fs.XmlFSType.VolumeName, + ".xattrs", + xattr, + entry); + + if(!File.Exists(outputPath)) + { + outputFile = new FileStream(outputPath, FileMode.CreateNew, FileAccess.ReadWrite, + FileShare.None); + outputFile.Write(xattrBuf, 0, xattrBuf.Length); + outputFile.Close(); + System.IO.FileInfo fi = new System.IO.FileInfo(outputPath); +#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body + try { fi.CreationTimeUtc = stat.CreationTimeUtc; } catch { } + try { fi.LastWriteTimeUtc = stat.LastWriteTimeUtc; } catch { } + try { fi.LastAccessTimeUtc = stat.AccessTimeUtc; } catch { } +#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body + DicConsole.WriteLine("Written {0} bytes of xattr {1} from file {2} to {3}", + xattrBuf.Length, xattr, entry, outputPath); + } + else + DicConsole.ErrorWriteLine("Cannot write xattr {0} for {1}, output exists", + xattr, entry); + + } + } + } + } + + Directory.CreateDirectory(Path.Combine(options.OutputDir, + fs.XmlFSType.Type, + fs.XmlFSType.VolumeName)); + + outputPath = Path.Combine(options.OutputDir, + fs.XmlFSType.Type, + fs.XmlFSType.VolumeName, + entry); + + if(!File.Exists(outputPath)) + { + byte[] outBuf = new byte[0]; + + error = fs.Read(entry, 0, stat.Length, ref outBuf); + + if(error == Errno.NoError) + { + outputFile = new FileStream(outputPath, FileMode.CreateNew, FileAccess.ReadWrite, + FileShare.None); + outputFile.Write(outBuf, 0, outBuf.Length); + outputFile.Close(); + System.IO.FileInfo fi = new System.IO.FileInfo(outputPath); +#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body + try { fi.CreationTimeUtc = stat.CreationTimeUtc; } catch { } + try { fi.LastWriteTimeUtc = stat.LastWriteTimeUtc; } catch { } + try { fi.LastAccessTimeUtc = stat.AccessTimeUtc; } catch { } +#pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body + DicConsole.WriteLine("Written {0} bytes of file {1} to {2}", + outBuf.Length, entry, outputPath); + } + else + DicConsole.ErrorWriteLine("Error {0} reading file {1}", error, entry); + } + else + DicConsole.ErrorWriteLine("Cannot write file {1}, output exists", entry); + } + else + DicConsole.ErrorWriteLine("Error reading file {0}", entry); + } + } + else + DicConsole.ErrorWriteLine("Error {0} reading root directory {0}", error.ToString()); + + Core.Statistics.AddFilesystem(fs.XmlFSType.Type); + } + else + DicConsole.ErrorWriteLine("Unable to mount device, error {0}", error.ToString()); + } + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine(String.Format("Error reading file: {0}", ex.Message)); + DicConsole.DebugWriteLine("Extract-Files command", ex.StackTrace); + } + + Core.Statistics.AddCommand("ls"); + } + + static void IdentifyFilesystems(ImagePlugin imagePlugin, out List id_plugins, ulong partitionStart, ulong partitionEnd) + { + id_plugins = new List(); + PluginBase plugins = new PluginBase(); + plugins.RegisterAllPlugins(); + + foreach(Filesystem _plugin in plugins.PluginsList.Values) + { + if(_plugin.Identify(imagePlugin, partitionStart, partitionEnd)) + id_plugins.Add(_plugin.Name.ToLower()); + } + } + } +} + diff --git a/DiscImageChef/DiscImageChef.csproj b/DiscImageChef/DiscImageChef.csproj index 4fb3e940..5b991271 100644 --- a/DiscImageChef/DiscImageChef.csproj +++ b/DiscImageChef/DiscImageChef.csproj @@ -67,6 +67,7 @@ + diff --git a/DiscImageChef/Main.cs b/DiscImageChef/Main.cs index 39f927ff..6bf018bf 100644 --- a/DiscImageChef/Main.cs +++ b/DiscImageChef/Main.cs @@ -57,7 +57,7 @@ namespace DiscImageChef Parser.Default.ParseArguments(args) + DumpMediaOptions, DeviceReportOptions, ConfigureOptions, StatsOptions, LsOptions, ExtractFilesOptions>(args) .WithParsed(opts => { if(opts.Debug) @@ -216,6 +216,16 @@ namespace DiscImageChef Commands.Ls.doLs(opts); }) + .WithParsed(opts => + { + if(opts.Debug) + DicConsole.DebugWriteLineEvent += System.Console.Error.WriteLine; + if(opts.Verbose) + DicConsole.VerboseWriteLineEvent += System.Console.WriteLine; + PrintCopyright(); + Commands.ExtractFiles.doExtractFiles(opts); + }) + .WithParsed(opts => { PrintCopyright(); Commands.Configure.doConfigure(); }) .WithParsed(opts => { PrintCopyright(); Commands.Statistics.showStats(); }) diff --git a/DiscImageChef/Options.cs b/DiscImageChef/Options.cs index 3479b2c5..e31cf282 100644 --- a/DiscImageChef/Options.cs +++ b/DiscImageChef/Options.cs @@ -332,5 +332,19 @@ namespace DiscImageChef [Option('l', "long", Default = false, HelpText = "Uses long format.")] public bool Long { get; set; } } + + [Verb("extract-files", HelpText = "Extracts all files in disc image.")] + public class ExtractFilesOptions : CommonOptions + { + [Option('i', "input", Required = true, HelpText = "Disc image.")] + public string InputFile { get; set; } + + [Option('o', "output", Required = true, HelpText = "Directory where extracted files will be created. Will abort if it exists.")] + public string OutputDir { get; set; } + + [Option('x', "xattrs", Default = false, HelpText = "Extract extended attributes if present.")] + public bool Xattrs { get; set; } + } + }