diff --git a/DiscImageChef.Core/DiscImageChef.Core.csproj b/DiscImageChef.Core/DiscImageChef.Core.csproj index 084803f1..7c65ef90 100644 --- a/DiscImageChef.Core/DiscImageChef.Core.csproj +++ b/DiscImageChef.Core/DiscImageChef.Core.csproj @@ -86,6 +86,7 @@ + @@ -163,7 +164,7 @@ - + diff --git a/DiscImageChef.Core/Sidecar/BlockTape.cs b/DiscImageChef.Core/Sidecar/BlockTape.cs new file mode 100644 index 00000000..27d8f2d5 --- /dev/null +++ b/DiscImageChef.Core/Sidecar/BlockTape.cs @@ -0,0 +1,200 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : BlockTape.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$ + +using System.Collections.Generic; +using Schemas; +using System.IO; + +namespace DiscImageChef.Core +{ + public static partial class Sidecar + { + public static CICMMetadataType Create(string folderName, List files, int blockSize) + { + CICMMetadataType sidecar = new CICMMetadataType + { + BlockMedia = new[] + { + new BlockMediaType + { + Image = new ImageType + { + format = "Directory", + offsetSpecified = false, + Value = folderName + }, + Sequence = new SequenceType + { + MediaTitle = folderName, + MediaSequence = 1, + TotalMedia = 1 + }, + PhysicalBlockSize = (int)blockSize, + LogicalBlockSize = (int)blockSize, + TapeInformation = new [] + { + new TapePartitionType + { + Image = new ImageType + { + format = "Directory", + offsetSpecified = false, + Value = folderName + } + } + } + } + } + }; + + long currentBlock = 0; + long totalSize = 0; + Checksum tapeWorker = new Checksum(); + List tapeFiles = new List(); + + for(int i = 0; i < files.Count; i++) + { + FileStream fs = new FileStream(files[i], FileMode.Open, FileAccess.Read); + Checksum fileWorker = new Checksum(); + TapeFileType tapeFile = new TapeFileType + { + Image = new ImageType + { + format = "Raw disk image (sector by sector copy)", + offset = 0, + Value = Path.GetFileName(files[i]) + }, + Size = fs.Length, + BlockSize = blockSize, + StartBlock = currentBlock, + Sequence = i + }; + + uint sectorsToRead = 512; + long sectors = fs.Length / blockSize; + long doneSectors = 0; + + InitProgress2(); + while(doneSectors < sectors) + { + byte[] sector; + + if((sectors - doneSectors) >= sectorsToRead) + { + sector = new byte[sectorsToRead * blockSize]; + fs.Read(sector, 0, sector.Length); + UpdateProgress2(string.Format("Hashing block {0} of {1} on file {2} of {3}", doneSectors, sectors, i + 1, files.Count), doneSectors, sectors); + doneSectors += sectorsToRead; + } + else + { + sector = new byte[(uint)(sectors - doneSectors) * blockSize]; + fs.Read(sector, 0, sector.Length); + UpdateProgress2(string.Format("Hashing block {0} of {1} on file {2} of {3}", doneSectors, sectors, i + 1, files.Count), doneSectors, sectors); + doneSectors += (sectors - doneSectors); + } + + fileWorker.Update(sector); + tapeWorker.Update(sector); + } + + tapeFile.EndBlock = (tapeFile.StartBlock + sectors) - 1; + currentBlock += sectors; + totalSize += fs.Length; + tapeFile.Checksums = fileWorker.End().ToArray(); + tapeFiles.Add(tapeFile); + + EndProgress2(); + } + + sidecar.BlockMedia[0].Checksums = tapeWorker.End().ToArray(); + sidecar.BlockMedia[0].ContentChecksums = sidecar.BlockMedia[0].Checksums; + sidecar.BlockMedia[0].Size = totalSize; + sidecar.BlockMedia[0].LogicalBlocks = currentBlock; + sidecar.BlockMedia[0].TapeInformation[0].EndBlock = currentBlock - 1; + sidecar.BlockMedia[0].TapeInformation[0].Size = totalSize; + sidecar.BlockMedia[0].TapeInformation[0].Checksums = sidecar.BlockMedia[0].Checksums; + sidecar.BlockMedia[0].TapeInformation[0].File = tapeFiles.ToArray(); + + // This is purely for convenience, as typically these kind of data represents QIC tapes + if(blockSize == 512) + { + sidecar.BlockMedia[0].DiskType = "Quarter-inch cartridge"; + + if(totalSize <= 20 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-11"; + else if(totalSize <= 40 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-40"; + else if(totalSize <= 60 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-24"; + else if(totalSize <= 80 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-80"; + else if(totalSize <= 120 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-120"; + else if(totalSize <= 150 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-150"; + else if(totalSize <= 320 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-320"; + else if(totalSize <= 340 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-3010"; + else if(totalSize <= 525 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-525"; + else if(totalSize <= 670 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-3020"; + else if(totalSize <= 1200 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-3080"; + else if(totalSize <= 1350 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-1350"; + else if(totalSize <= (long)4000 * 1048576) + sidecar.BlockMedia[0].DiskSubType = "QIC-3095"; + else + { + sidecar.BlockMedia[0].DiskType = "Unknown tape"; + sidecar.BlockMedia[0].DiskSubType = "Unknown tape"; + } + } + else + { + sidecar.BlockMedia[0].DiskType = "Unknown tape"; + sidecar.BlockMedia[0].DiskSubType = "Unknown tape"; + } + + return sidecar; + } + } +} diff --git a/DiscImageChef/ChangeLog b/DiscImageChef/ChangeLog index 7d4d6a19..50201b11 100644 --- a/DiscImageChef/ChangeLog +++ b/DiscImageChef/ChangeLog @@ -1,3 +1,8 @@ +* Options.cs: +* Commands/CreateSidecar.cs: + Added support for creating a sidecar from a folder containing the + files of a tape dump. e.g.: tape1/file01, tape2/file02, etc. + * Commands/Ls.cs: * Commands/Analyze.cs: * Commands/ExtractFiles.cs: diff --git a/DiscImageChef/Commands/CreateSidecar.cs b/DiscImageChef/Commands/CreateSidecar.cs index 525bfc50..63b3cf4f 100644 --- a/DiscImageChef/Commands/CreateSidecar.cs +++ b/DiscImageChef/Commands/CreateSidecar.cs @@ -31,6 +31,7 @@ // ****************************************************************************/ using System; +using System.Collections.Generic; using System.IO; using DiscImageChef.Console; using DiscImageChef.Core; @@ -44,70 +45,118 @@ namespace DiscImageChef.Commands { public static void doSidecar(CreateSidecarOptions options) { - ImagePlugin _imageFormat; + Sidecar.InitProgressEvent += Progress.InitProgress; + Sidecar.UpdateProgressEvent += Progress.UpdateProgress; + Sidecar.EndProgressEvent += Progress.EndProgress; + Sidecar.InitProgressEvent2 += Progress.InitProgress2; + Sidecar.UpdateProgressEvent2 += Progress.UpdateProgress2; + Sidecar.EndProgressEvent2 += Progress.EndProgress2; + Sidecar.UpdateStatusEvent += Progress.UpdateStatus; - FiltersList filtersList = new FiltersList(); - Filter inputFilter = filtersList.GetFilter(options.InputFile); - - if(inputFilter == null) + if(File.Exists(options.InputFile)) { - DicConsole.ErrorWriteLine("Cannot open specified file."); - return; - } - - try - { - _imageFormat = ImageFormat.Detect(inputFilter); - - if(_imageFormat == null) + if(options.Tape) { - DicConsole.WriteLine("Image format not identified, not proceeding with analysis."); + DicConsole.ErrorWriteLine("You cannot use --tape option when input is a file."); return; } - else + + ImagePlugin _imageFormat; + + FiltersList filtersList = new FiltersList(); + Filter inputFilter = filtersList.GetFilter(options.InputFile); + + if(inputFilter == null) { - 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); + DicConsole.ErrorWriteLine("Cannot open specified file."); + return; } try { - if(!_imageFormat.OpenImage(inputFilter)) + _imageFormat = ImageFormat.Detect(inputFilter); + + if(_imageFormat == null) { - DicConsole.WriteLine("Unable to open image format"); - DicConsole.WriteLine("No error given"); + 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); + } + + try + { + if(!_imageFormat.OpenImage(inputFilter)) + { + DicConsole.WriteLine("Unable to open image format"); + DicConsole.WriteLine("No error given"); + return; + } + + DicConsole.DebugWriteLine("Analyze command", "Correctly opened image file."); + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Unable to open image format"); + DicConsole.ErrorWriteLine("Error: {0}", ex.Message); return; } - DicConsole.DebugWriteLine("Analyze command", "Correctly opened image file."); + Core.Statistics.AddMediaFormat(_imageFormat.GetImageFormat()); + Core.Statistics.AddFilter(inputFilter.Name); + + CICMMetadataType sidecar = Sidecar.Create(_imageFormat, options.InputFile, inputFilter.UUID); + + DicConsole.WriteLine("Writing metadata sidecar"); + + FileStream xmlFs = new FileStream(Path.GetDirectoryName(options.InputFile) + + //Path.PathSeparator + + Path.GetFileNameWithoutExtension(options.InputFile) + ".cicm.xml", + FileMode.CreateNew); + + System.Xml.Serialization.XmlSerializer xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(CICMMetadataType)); + xmlSer.Serialize(xmlFs, sidecar); + xmlFs.Close(); + + Core.Statistics.AddCommand("create-sidecar"); } catch(Exception ex) { - DicConsole.ErrorWriteLine("Unable to open image format"); - DicConsole.ErrorWriteLine("Error: {0}", ex.Message); + DicConsole.ErrorWriteLine(string.Format("Error reading file: {0}", ex.Message)); + DicConsole.DebugWriteLine("Analyze command", ex.StackTrace); + } + } + else if(Directory.Exists(options.InputFile)) + { + if(!options.Tape) + { + DicConsole.ErrorWriteLine("Cannot create a sidecar from a directory."); return; } - Core.Statistics.AddMediaFormat(_imageFormat.GetImageFormat()); - Core.Statistics.AddFilter(inputFilter.Name); + string[] contents = Directory.GetFiles(options.InputFile, "*", SearchOption.TopDirectoryOnly); + List files = new List(); - Sidecar.InitProgressEvent += Progress.InitProgress; - Sidecar.UpdateProgressEvent += Progress.UpdateProgress; - Sidecar.EndProgressEvent += Progress.EndProgress; - Sidecar.InitProgressEvent2 += Progress.InitProgress2; - Sidecar.UpdateProgressEvent2 += Progress.UpdateProgress2; - Sidecar.EndProgressEvent2 += Progress.EndProgress2; - Sidecar.UpdateStatusEvent += Progress.UpdateStatus; + foreach(string file in contents) + { + if(new FileInfo(file).Length % options.BlockSize == 0) + files.Add(file); + } - CICMMetadataType sidecar = Sidecar.Create(_imageFormat, options.InputFile, inputFilter.UUID); + files.Sort(StringComparer.CurrentCultureIgnoreCase); + + CICMMetadataType sidecar = Sidecar.Create(Path.GetFileName(options.InputFile), files, options.BlockSize); DicConsole.WriteLine("Writing metadata sidecar"); FileStream xmlFs = new FileStream(Path.GetDirectoryName(options.InputFile) + //Path.PathSeparator + - Path.GetFileNameWithoutExtension(options.InputFile) + ".cicm.xml", + Path.GetFileName(options.InputFile) + ".cicm.xml", FileMode.CreateNew); System.Xml.Serialization.XmlSerializer xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(CICMMetadataType)); @@ -116,14 +165,12 @@ namespace DiscImageChef.Commands Core.Statistics.AddCommand("create-sidecar"); } - catch(Exception ex) + else { - DicConsole.ErrorWriteLine(string.Format("Error reading file: {0}", ex.Message)); - DicConsole.DebugWriteLine("Analyze command", ex.StackTrace); + DicConsole.ErrorWriteLine("The specified input file cannot be found."); + return; } - } - } } diff --git a/DiscImageChef/Options.cs b/DiscImageChef/Options.cs index 96141508..98377e17 100644 --- a/DiscImageChef/Options.cs +++ b/DiscImageChef/Options.cs @@ -268,6 +268,10 @@ namespace DiscImageChef { [Option('i', "input", Required = true, HelpText = "Disc image.")] public string InputFile { get; set; } + [Option('t', "tape", Required = false, Default = false, HelpText = "When used indicates that input is a folder containing alphabetically sorted files extracted from a linear block-based tape with fixed block size (e.g. a SCSI tape device).")] + public bool Tape { get; set; } + [Option('b', "block-size", Required = false, Default = 512, HelpText = "Only used for tapes, indicates block size. Files in the folder whose size is not a multiple of this value will simply be ignored.")] + public int BlockSize { get; set; } } [Verb("dump-media", HelpText = "Dumps the media inserted on a device to a media image.")]