diff --git a/DiscImageChef.Filesystems/LisaFS/Consts.cs b/DiscImageChef.Filesystems/LisaFS/Consts.cs index ce6561df..9755f405 100644 --- a/DiscImageChef.Filesystems/LisaFS/Consts.cs +++ b/DiscImageChef.Filesystems/LisaFS/Consts.cs @@ -53,6 +53,8 @@ namespace DiscImageChef.Filesystems.LisaFS const UInt16 FILEID_BITMAP = 0x0002; const UInt16 FILEID_SRECORD = 0x0003; 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; diff --git a/DiscImageChef.Filesystems/LisaFS/Dir.cs b/DiscImageChef.Filesystems/LisaFS/Dir.cs index e047f662..f4654212 100644 --- a/DiscImageChef.Filesystems/LisaFS/Dir.cs +++ b/DiscImageChef.Filesystems/LisaFS/Dir.cs @@ -37,6 +37,7 @@ // //$Id$ using System; using System.Collections.Generic; +using DiscImageChef.ImagePlugins; namespace DiscImageChef.Filesystems.LisaFS { @@ -50,7 +51,127 @@ namespace DiscImageChef.Filesystems.LisaFS public override Errno ReadDir(string path, ref List contents) { - return Errno.NotImplemented; + Int16 fileId; + bool isDir; + Errno error = LookupFileId(path, out fileId, out isDir); + if(error != Errno.NoError) + return error; + + if(!isDir) + return Errno.NotDirectory; + + List catalog; + ReadCatalog(fileId, out catalog); + + foreach(CatalogEntry entry in catalog) + contents.Add(StringHandlers.CToString(entry.filename).Replace('/',':')); + + if(debug && fileId == FILEID_DIRECTORY) + { + contents.Add("$MDDF"); + contents.Add("$Boot"); + contents.Add("$Loader"); + contents.Add("$Bitmap"); + contents.Add("$S-Record"); + contents.Add("$"); + } + + contents.Sort(); + return Errno.NoError; + } + + Errno ReadCatalog(Int16 fileId, out List catalog) + { + catalog = null; + + if(!mounted) + return Errno.AccessDenied; + + if(fileId < 4) + return Errno.InvalidArgument; + + if(catalogCache.TryGetValue(fileId, out catalog)) + return Errno.NoError; + + int count = 0; + + // Catalogs don't have extents files so we need to traverse all disk searching pieces (tend to be non fragmented and non expandable) + for(ulong i = 0; i < device.GetSectors(); i++) + { + byte[] tag = device.ReadSectorTag((ulong)i, SectorTagType.AppleSectorTag); + UInt16 id = BigEndianBitConverter.ToUInt16(tag, 0x04); + + if(id == fileId) + count++; + + // Extents file found, it's not a catalog + if(id == -fileId) + return Errno.NotDirectory; + } + + if(count == 0) + return Errno.NoSuchFile; + + byte[] buf = new byte[count * device.GetSectorSize()]; + + // This can be enhanced to follow linked tags. However on some disks a linked tag cuts a file, better not let it do with a catalog + for(ulong i = 0; i < device.GetSectors(); i++) + { + byte[] tag = device.ReadSectorTag((ulong)i, SectorTagType.AppleSectorTag); + UInt16 id = BigEndianBitConverter.ToUInt16(tag, 0x04); + + if(id == fileId) + { + UInt16 pos = BigEndianBitConverter.ToUInt16(tag, 0x06); + byte[] sector = device.ReadSector(i); + Array.Copy(sector, 0, buf, sector.Length * pos, sector.Length); + } + } + + int offset = 0; + + catalog = new List(); + + while((offset + 64) <= buf.Length) + { + if(buf[offset + 0x24] == 0x08) + offset += 78; + else if(buf[offset + 0x24] == 0x7C) + offset += 50; + else if(buf[offset + 0x24] == 0xFF) + break; + else if(buf[offset + 0x24] == 0x03 && buf[offset] == 0x24) + { + CatalogEntry entry = new CatalogEntry(); + entry.marker = buf[offset]; + 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.fileType = buf[offset + 0x24]; + entry.unknown = buf[offset + 0x25]; + entry.fileID = BigEndianBitConverter.ToInt16(buf, offset + 0x26); + entry.dtc = BigEndianBitConverter.ToUInt32(buf, offset + 0x28); + entry.dtm = BigEndianBitConverter.ToUInt32(buf, offset + 0x2C); + entry.wasted = BigEndianBitConverter.ToInt32(buf, offset + 0x30); + entry.length = BigEndianBitConverter.ToInt32(buf, offset + 0x34); + entry.tail = new byte[8]; + Array.Copy(buf, offset + 0x38, entry.tail, 0, 8); + catalog.Add(entry); + + if(fileSizeCache.ContainsKey(entry.fileID)) + fileSizeCache.Remove(entry.fileID); + + fileSizeCache.Add(entry.fileID, entry.length); + + offset += 64; + } + else + break; + } + + catalogCache.Add(fileId, catalog); + return Errno.NoError; } } } diff --git a/DiscImageChef.Filesystems/LisaFS/Extent.cs b/DiscImageChef.Filesystems/LisaFS/Extent.cs index aa8af1cb..eacf8cd4 100644 --- a/DiscImageChef.Filesystems/LisaFS/Extent.cs +++ b/DiscImageChef.Filesystems/LisaFS/Extent.cs @@ -36,14 +36,108 @@ // ****************************************************************************/ // //$Id$ using System; +using DiscImageChef.ImagePlugins; + namespace DiscImageChef.Filesystems.LisaFS { partial class LisaFS : Filesystem { public override Errno MapBlock(string path, long fileBlock, ref long deviceBlock) { + // TODO: Not really important. return Errno.NotImplemented; } + + /// + /// Searches the disk for an extents file (or gets it from cache) + /// + /// Error. + /// File identifier. + /// Extents file. + Errno ReadExtentsFile(Int16 fileId, out ExtentFile file) + { + file = new ExtentFile(); + + if(!mounted) + return Errno.AccessDenied; + + if(fileId < 5) + return Errno.InvalidArgument; + + if(extentCache.TryGetValue(fileId, out file)) + return Errno.NoError; + + // If the file is found but not its extents file we should suppose it's a directory + bool fileFound = false; + + for(ulong i = 0; i < device.GetSectors(); i++) + { + byte[] tag = device.ReadSectorTag((ulong)i, SectorTagType.AppleSectorTag); + Int16 foundid = BigEndianBitConverter.ToInt16(tag, 0x04); + + if(foundid == fileId) + fileFound = true; + + if(foundid == ((short)(-1 * fileId))) + { + byte[] sector = device.ReadSector((ulong)i); + + if(sector[0] >= 32 || sector[0] == 0) + return Errno.InvalidArgument; + + 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.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.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.length = BigEndianBitConverter.ToInt32(sector, 0x80); + file.unknown5 = BigEndianBitConverter.ToInt32(sector, 0x84); + file.unknown6 = BigEndianBitConverter.ToInt16(sector, 0x17E); + file.LisaInfo = new byte[128]; + Array.Copy(sector, 0x180, file.LisaInfo, 0, 128); + + int extentsCount = 0; + + for(int j = 0; j < 41; j++) + { + if(BigEndianBitConverter.ToInt16(sector, 0x88 + j * 6 + 4) == 0) + break; + + extentsCount++; + } + + file.extents = new Extent[extentsCount]; + + for(int j = 0; j < extentsCount; j++) + { + file.extents[j] = new Extent(); + file.extents[j].start = BigEndianBitConverter.ToInt32(sector, 0x88 + j * 6); + file.extents[j].length = BigEndianBitConverter.ToInt16(sector, 0x88 + j * 6 + 4); + } + + extentCache.Add(fileId, file); + return Errno.NoError; + } + } + + return fileFound ? Errno.IsDirectory : Errno.NoSuchFile; + } } } diff --git a/DiscImageChef.Filesystems/LisaFS/File.cs b/DiscImageChef.Filesystems/LisaFS/File.cs index a2313439..3b727e8f 100644 --- a/DiscImageChef.Filesystems/LisaFS/File.cs +++ b/DiscImageChef.Filesystems/LisaFS/File.cs @@ -36,23 +36,416 @@ // ****************************************************************************/ // //$Id$ using System; +using System.Diagnostics; +using DiscImageChef.ImagePlugins; +using System.Runtime.InteropServices; +using System.Collections.Generic; +using DiscImageChef.Console; + namespace DiscImageChef.Filesystems.LisaFS { partial class LisaFS : Filesystem { public override Errno GetAttributes(string path, ref FileAttributes attributes) { - return Errno.NotImplemented; + Int16 fileId; + Errno error = LookupFileId(path, out fileId); + if(error != Errno.NoError) + return error; + + return GetAttributes(fileId, ref attributes); } public override Errno Read(string path, long offset, long size, ref byte[] buf) { - return Errno.NotImplemented; + if(offset < 0 || size < 0) + return Errno.EINVAL; + + Int16 fileId; + bool isDir; + Errno error = LookupFileId(path, out fileId, out isDir); + if(error != Errno.NoError) + return error; + + byte[] tmp; + if(debug) + { + switch(fileId) + { + case FILEID_BOOT_SIGNED: + case FILEID_LOADER_SIGNED: + case (short)FILEID_MDDF: + case (short)FILEID_BITMAP: + case (short)FILEID_SRECORD: + case (short)FILEID_DIRECTORY: + error = ReadSystemFile(fileId, out tmp); + break; + default: + error = ReadFile(fileId, out tmp); + break; + } + } + else + error = ReadFile(fileId, out tmp); + + if(error != Errno.NoError) + return error; + + if(offset >= tmp.Length) + return Errno.EINVAL; + + if(size + offset >= tmp.Length) + size = tmp.Length - offset; + + buf = new byte[size]; + Array.Copy(tmp, offset, buf, 0, size); + return Errno.NoError; } public override Errno Stat(string path, ref FileEntryInfo stat) { - return Errno.NotImplemented; + Int16 fileId; + Errno error = LookupFileId(path, out fileId); + if(error != Errno.NoError) + return error; + + return Stat(fileId, out stat); + } + + Errno GetAttributes(Int16 fileId, ref FileAttributes attributes) + { + if(!mounted) + return Errno.AccessDenied; + + if(fileId <= 4) + { + if(!debug || fileId == 0) + return Errno.NoSuchFile; + else + { + attributes = new FileAttributes(); + attributes = FileAttributes.System; + attributes |= FileAttributes.Hidden; + + if(fileId == 4) + attributes |= FileAttributes.Directory; + else + attributes |= FileAttributes.File; + + return Errno.NoError; + } + } + + ExtentFile extFile; + Errno error = ReadExtentsFile(fileId, out extFile); + + if(error != Errno.NoError) + return error; + + attributes = new FileAttributes(); + // TODO: Subcatalogs + attributes = FileAttributes.File; + attributes |= FileAttributes.Extents; + if((extFile.flags & 0x08) == 0x08) + attributes |= FileAttributes.Password; + + return Errno.NoError; + } + + Errno ReadSystemFile(Int16 fileId, out byte[] buf) + { + buf = null; + if(!mounted || !debug) + return Errno.AccessDenied; + + if(fileId > 4 || fileId <= 0) + { + if(fileId != FILEID_BOOT_SIGNED && fileId != FILEID_LOADER_SIGNED) + return Errno.InvalidArgument; + } + + if(systemFileCache.TryGetValue(fileId, out buf)) + return Errno.NoError; + + int count = 0; + + // Should be enough to check 100 sectors? + for(ulong i = 0; i < 100; i++) + { + byte[] tag = device.ReadSectorTag((ulong)i, SectorTagType.AppleSectorTag); + Int16 id = BigEndianBitConverter.ToInt16(tag, 0x04); + + if(id == fileId) + count++; + } + + if(count == 0) + return Errno.NoSuchFile; + + buf = new byte[count * device.GetSectorSize()]; + + // Should be enough to check 100 sectors? + for(ulong i = 0; i < 100; i++) + { + byte[] tag = device.ReadSectorTag((ulong)i, SectorTagType.AppleSectorTag); + UInt16 id = BigEndianBitConverter.ToUInt16(tag, 0x04); + + if(id == fileId) + { + UInt16 pos = BigEndianBitConverter.ToUInt16(tag, 0x06); + byte[] sector = device.ReadSector(i); + Array.Copy(sector, 0, buf, sector.Length * pos, sector.Length); + } + } + + systemFileCache.Add(fileId, buf); + return Errno.NoError; + } + + Errno Stat(Int16 fileId, out FileEntryInfo stat) + { + stat = null; + + if(!mounted) + return Errno.AccessDenied; + + Errno error; + ExtentFile file; + + if(fileId <= 4) + { + if(!debug || fileId == 0) + return Errno.NoSuchFile; + else + { + stat = new FileEntryInfo(); + stat.Attributes = new FileAttributes(); + + error = GetAttributes(fileId, ref stat.Attributes); + if(error != Errno.NoError) + return error; + + if(fileId < 0 && fileId != FILEID_BOOT_SIGNED && fileId != FILEID_LOADER_SIGNED) + { + error = ReadExtentsFile((short)(fileId * -1), out file); + if(error != Errno.NoError) + return error; + + 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); + + stat.Inode = (ulong)fileId; + stat.Mode = 0x124; + stat.Links = 0; + stat.UID = 0; + stat.GID = 0; + stat.DeviceNo = 0; + stat.Length = mddf.datasize; + stat.BlockSize = mddf.datasize; + stat.Blocks = 1; + } + else + { + byte[] buf; + error = ReadSystemFile(fileId, out buf); + if(error != Errno.NoError) + return error; + + if(fileId != 4) + stat.CreationTime = mddf.dtvc; + else + stat.CreationTime = mddf.dtcc; + + stat.BackupTime = mddf.dtvb; + + stat.Inode = (ulong)fileId; + stat.Mode = 0x124; + stat.Links = 0; + stat.UID = 0; + stat.GID = 0; + stat.DeviceNo = 0; + stat.Length = buf.Length; + stat.BlockSize = mddf.datasize; + stat.Blocks = buf.Length / mddf.datasize; + } + + return Errno.NoError; + } + } + + stat = new FileEntryInfo(); + stat.Attributes = new FileAttributes(); + error = GetAttributes(fileId, ref stat.Attributes); + if(error != Errno.NoError) + { + //DicConsole.ErrorWriteLine("Error {0} reading attributes for file {1}", error, fileId); + return error; + } + + error = ReadExtentsFile(fileId, out file); + if(error != Errno.NoError) + { + //DicConsole.ErrorWriteLine("Error {0} reading extents for file {1}", error, fileId); + return error; + } + + 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); + + stat.Inode = (ulong)fileId; + stat.Mode = 0x1B6; + stat.Links = 1; + stat.UID = 0; + stat.GID = 0; + stat.DeviceNo = 0; + int len; + if(!fileSizeCache.TryGetValue(fileId, out len)) + stat.Length = file.length; + else + stat.Length = len; + stat.BlockSize = mddf.datasize; + stat.Blocks = file.length; + + return Errno.NoError; + } + + Errno ReadFile(Int16 fileId, out byte[] buf) + { + buf = null; + if(!mounted) + return Errno.AccessDenied; + + if(fileId <= 4) + return Errno.InvalidArgument; + + if(fileCache.TryGetValue(fileId, out buf)) + return Errno.NoError; + + Errno error; + ExtentFile file; + + error = ReadExtentsFile(fileId, out file); + if(error != Errno.NoError) + return error; + + byte[] temp = new byte[file.length * (int)device.GetSectors()]; + + 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); + + Array.Copy(sector, 0, temp, offset, sector.Length); + offset += sector.Length; + } + + 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); + return Errno.NoError; + } + + Errno LookupFileId(string path, out Int16 fileId) + { + bool temp; + return LookupFileId(path, out fileId, out temp); + } + + Errno LookupFileId(string path, out Int16 fileId, out bool isDir) + { + fileId = 0; + isDir = false; + + if(!mounted) + return Errno.AccessDenied; + + string[] pathElements = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + if(pathElements.Length == 0) + { + fileId = (short)FILEID_DIRECTORY; + isDir = true; + return Errno.NoError; + } + + // TODO: Subcatalogs + if(pathElements.Length > 1) + return Errno.NotImplemented; + + if(debug) + { + if(String.Compare(pathElements[0], "$MDDF", StringComparison.InvariantCulture) == 0) + { + fileId = (short)FILEID_MDDF; + return Errno.NoError; + } + + if(String.Compare(pathElements[0], "$Boot", StringComparison.InvariantCulture) == 0) + { + fileId = FILEID_BOOT_SIGNED; + return Errno.NoError; + } + + if(String.Compare(pathElements[0], "$Loader", StringComparison.InvariantCulture) == 0) + { + fileId = FILEID_LOADER_SIGNED; + return Errno.NoError; + } + + if(String.Compare(pathElements[0], "$Bitmap", StringComparison.InvariantCulture) == 0) + { + fileId = (short)FILEID_BITMAP; + return Errno.NoError; + } + + if(String.Compare(pathElements[0], "$S-Record", StringComparison.InvariantCulture) == 0) + { + fileId = (short)FILEID_SRECORD; + return Errno.NoError; + } + + if(String.Compare(pathElements[0], "$", StringComparison.InvariantCulture) == 0) + { + fileId = (short)FILEID_DIRECTORY; + isDir = true; + return Errno.NoError; + } + } + + List catalog; + + Errno error = ReadCatalog((short)FILEID_DIRECTORY, out catalog); + if(error != Errno.NoError) + return error; + + string wantedFilename = pathElements[0].Replace(':', '/'); + + foreach(CatalogEntry entry in catalog) + { + string filename = StringHandlers.CToString(entry.filename); + // Should they be case sensitive? + if(String.Compare(wantedFilename, filename, StringComparison.InvariantCultureIgnoreCase) == 0) + { + fileId = entry.fileID; + isDir |= entry.fileType != 0x03; + return Errno.NoError; + } + } + + return Errno.NoSuchFile; } } } diff --git a/DiscImageChef.Filesystems/LisaFS/LisaFS.cs b/DiscImageChef.Filesystems/LisaFS/LisaFS.cs index e10b0aff..7de09a87 100644 --- a/DiscImageChef.Filesystems/LisaFS/LisaFS.cs +++ b/DiscImageChef.Filesystems/LisaFS/LisaFS.cs @@ -40,14 +40,36 @@ using System; // All information by Natalia Portillo // Variable names from Lisa API +using System.Collections.Generic; +using DiscImageChef.ImagePlugins; + namespace DiscImageChef.Filesystems.LisaFS { partial class LisaFS : Filesystem { + bool mounted; + bool debug; + readonly ImagePlugin device; + + MDDF mddf; + + #region Caches + Dictionary extentCache; + Dictionary systemFileCache; + Dictionary fileCache; + Dictionary> catalogCache; + Dictionary fileSizeCache; + #endregion Caches + public LisaFS() { Name = "Apple Lisa File System"; PluginUUID = new Guid("7E6034D1-D823-4248-A54D-239742B28391"); } + + public LisaFS(ImagePlugins.ImagePlugin imagePlugin, ulong partitionStart, ulong partitionEnd) + { + device = imagePlugin; + } } } diff --git a/DiscImageChef.Filesystems/LisaFS/Super.cs b/DiscImageChef.Filesystems/LisaFS/Super.cs index 0ed0fce1..6c187009 100644 --- a/DiscImageChef.Filesystems/LisaFS/Super.cs +++ b/DiscImageChef.Filesystems/LisaFS/Super.cs @@ -36,23 +36,299 @@ // ****************************************************************************/ // //$Id$ using System; +using System.Collections.Generic; +using DiscImageChef.Console; +using DiscImageChef.ImagePlugins; +using System.CodeDom.Compiler; + namespace DiscImageChef.Filesystems.LisaFS { partial class LisaFS : Filesystem { public override Errno Mount() { - return Errno.NotImplemented; + return Mount(false); + } + + public override Errno Mount(bool debug) + { + try + { + if(device.ImageInfo.readableSectorTags == null || + !device.ImageInfo.readableSectorTags.Contains(SectorTagType.AppleSectorTag)) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Underlying device does not support Lisa tags"); + return Errno.InOutError; + } + + // LisaOS is big-endian + BigEndianBitConverter.IsLittleEndian = BitConverter.IsLittleEndian; + + // Minimal LisaOS disk is 3.5" single sided double density, 800 sectors + if(device.GetSectors() < 800) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Device is too small"); + return Errno.InOutError; + } + + // LisaOS searches sectors until tag tells MDDF resides there, so we'll search 100 sectors + for(int i = 0; i < 100; i++) + { + byte[] tag = device.ReadSectorTag((ulong)i, SectorTagType.AppleSectorTag); + UInt16 fileid = BigEndianBitConverter.ToUInt16(tag, 0x04); + + DicConsole.DebugWriteLine("LisaFS plugin", "Sector {0}, file ID 0x{1:X4}", i, fileid); + + if(fileid == FILEID_MDDF) + { + byte[] sector = device.ReadSector((ulong)i); + mddf = new MDDF(); + byte[] pString = new byte[33]; + UInt32 lisa_time; + + mddf.fsversion = BigEndianBitConverter.ToUInt16(sector, 0x00); + mddf.volid = BigEndianBitConverter.ToUInt64(sector, 0x02); + mddf.volnum = BigEndianBitConverter.ToUInt16(sector, 0x0A); + Array.Copy(sector, 0x0C, pString, 0, 33); + mddf.volname = StringHandlers.PascalToString(pString); + mddf.unknown1 = sector[0x2D]; + Array.Copy(sector, 0x2E, pString, 0, 33); + // Prevent garbage + if(pString[0] <= 32) + mddf.password = StringHandlers.PascalToString(pString); + else + mddf.password = ""; + mddf.unknown2 = sector[0x4F]; + mddf.machine_id = BigEndianBitConverter.ToUInt32(sector, 0x50); + mddf.master_copy_id = BigEndianBitConverter.ToUInt32(sector, 0x54); + lisa_time = BigEndianBitConverter.ToUInt32(sector, 0x58); + mddf.dtvc = DateHandlers.LisaToDateTime(lisa_time); + lisa_time = BigEndianBitConverter.ToUInt32(sector, 0x5C); + mddf.dtcc = DateHandlers.LisaToDateTime(lisa_time); + lisa_time = BigEndianBitConverter.ToUInt32(sector, 0x60); + mddf.dtvb = DateHandlers.LisaToDateTime(lisa_time); + lisa_time = BigEndianBitConverter.ToUInt32(sector, 0x64); + mddf.dtvs = DateHandlers.LisaToDateTime(lisa_time); + mddf.unknown3 = BigEndianBitConverter.ToUInt32(sector, 0x68); + mddf.mddf_block = BigEndianBitConverter.ToUInt32(sector, 0x6C); + mddf.volsize_minus_one = BigEndianBitConverter.ToUInt32(sector, 0x70); + mddf.volsize_minus_mddf_minus_one = BigEndianBitConverter.ToUInt32(sector, 0x74); + mddf.vol_size = BigEndianBitConverter.ToUInt32(sector, 0x78); + mddf.blocksize = BigEndianBitConverter.ToUInt16(sector, 0x7C); + mddf.datasize = BigEndianBitConverter.ToUInt16(sector, 0x7E); + mddf.unknown4 = BigEndianBitConverter.ToUInt16(sector, 0x80); + mddf.unknown5 = BigEndianBitConverter.ToUInt32(sector, 0x82); + mddf.unknown6 = BigEndianBitConverter.ToUInt32(sector, 0x86); + mddf.clustersize = BigEndianBitConverter.ToUInt16(sector, 0x8A); + mddf.fs_size = BigEndianBitConverter.ToUInt32(sector, 0x8C); + mddf.unknown7 = BigEndianBitConverter.ToUInt32(sector, 0x90); + mddf.unknown8 = BigEndianBitConverter.ToUInt32(sector, 0x94); + mddf.unknown9 = BigEndianBitConverter.ToUInt32(sector, 0x98); + mddf.unknown10 = BigEndianBitConverter.ToUInt32(sector, 0x9C); + mddf.unknown11 = BigEndianBitConverter.ToUInt32(sector, 0xA0); + mddf.unknown12 = BigEndianBitConverter.ToUInt32(sector, 0xA4); + mddf.unknown13 = BigEndianBitConverter.ToUInt32(sector, 0xA8); + mddf.unknown14 = BigEndianBitConverter.ToUInt32(sector, 0xAC); + mddf.filecount = BigEndianBitConverter.ToUInt16(sector, 0xB0); + mddf.unknown15 = BigEndianBitConverter.ToUInt32(sector, 0xB2); + mddf.unknown16 = BigEndianBitConverter.ToUInt32(sector, 0xB6); + mddf.freecount = BigEndianBitConverter.ToUInt32(sector, 0xBA); + mddf.unknown17 = BigEndianBitConverter.ToUInt16(sector, 0xBE); + mddf.unknown18 = BigEndianBitConverter.ToUInt32(sector, 0xC0); + mddf.overmount_stamp = BigEndianBitConverter.ToUInt64(sector, 0xC4); + mddf.serialization = BigEndianBitConverter.ToUInt32(sector, 0xCC); + mddf.unknown19 = BigEndianBitConverter.ToUInt32(sector, 0xD0); + mddf.unknown_timestamp = BigEndianBitConverter.ToUInt32(sector, 0xD4); + mddf.unknown20 = BigEndianBitConverter.ToUInt32(sector, 0xD8); + mddf.unknown21 = BigEndianBitConverter.ToUInt32(sector, 0xDC); + mddf.unknown22 = BigEndianBitConverter.ToUInt32(sector, 0xE0); + mddf.unknown23 = BigEndianBitConverter.ToUInt32(sector, 0xE4); + mddf.unknown24 = BigEndianBitConverter.ToUInt32(sector, 0xE8); + mddf.unknown25 = BigEndianBitConverter.ToUInt32(sector, 0xEC); + mddf.unknown26 = BigEndianBitConverter.ToUInt32(sector, 0xF0); + mddf.unknown27 = BigEndianBitConverter.ToUInt32(sector, 0xF4); + mddf.unknown28 = BigEndianBitConverter.ToUInt32(sector, 0xF8); + mddf.unknown29 = BigEndianBitConverter.ToUInt32(sector, 0xFC); + mddf.unknown30 = BigEndianBitConverter.ToUInt32(sector, 0x100); + mddf.unknown31 = BigEndianBitConverter.ToUInt32(sector, 0x104); + mddf.unknown32 = BigEndianBitConverter.ToUInt32(sector, 0x108); + mddf.unknown33 = BigEndianBitConverter.ToUInt32(sector, 0x10C); + mddf.unknown34 = BigEndianBitConverter.ToUInt32(sector, 0x110); + mddf.unknown35 = BigEndianBitConverter.ToUInt32(sector, 0x114); + mddf.backup_volid = BigEndianBitConverter.ToUInt64(sector, 0x118); + mddf.label_size = BigEndianBitConverter.ToUInt16(sector, 0x120); + mddf.fs_overhead = BigEndianBitConverter.ToUInt16(sector, 0x122); + mddf.result_scavenge = BigEndianBitConverter.ToUInt16(sector, 0x124); + mddf.boot_code = BigEndianBitConverter.ToUInt16(sector, 0x126); + mddf.boot_environ = BigEndianBitConverter.ToUInt16(sector, 0x6C); + mddf.unknown36 = BigEndianBitConverter.ToUInt32(sector, 0x12A); + mddf.unknown37 = BigEndianBitConverter.ToUInt32(sector, 0x12E); + mddf.unknown38 = BigEndianBitConverter.ToUInt32(sector, 0x132); + mddf.vol_sequence = BigEndianBitConverter.ToUInt16(sector, 0x136); + mddf.vol_left_mounted = sector[0x138]; + + if(mddf.mddf_block != i || + mddf.vol_size > device.GetSectors() || + mddf.vol_size - 1 != mddf.volsize_minus_one || + mddf.vol_size - i - 1 != mddf.volsize_minus_mddf_minus_one || + mddf.datasize > mddf.blocksize || + mddf.blocksize < device.GetSectorSize() || + mddf.datasize != device.GetSectorSize()) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Incorrect MDDF found"); + return Errno.InvalidArgument; + } + + if(mddf.fsversion != LisaFSv3) + { + string version = mddf.fsversion.ToString(); + + switch(mddf.fsversion) + { + case LisaFSv1: + version = "v1"; + break; + case LisaFSv2: + version = "v2"; + break; + } + + DicConsole.DebugWriteLine("LisaFS plugin", "Cannot mount LisaFS version {0}", version); + return Errno.NotSupported; + } + + extentCache = new Dictionary(); + systemFileCache = new Dictionary(); + fileCache = new Dictionary(); + catalogCache = new Dictionary>(); + fileSizeCache = new Dictionary(); + + Errno error; + + mounted = true; + + List tempCat; + error = ReadCatalog((short)FILEID_DIRECTORY, out tempCat); + + if(error != Errno.NoError) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Cannot read root catalog, error {0}", error.ToString()); + mounted = false; + return error; + } + + this.debug = debug; + + if(debug) + { + byte[] temp; + + error = ReadSystemFile(FILEID_BOOT_SIGNED, out temp); + if(error != Errno.NoError) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Unable to read boot blocks"); + mounted = false; + return error; + } + + error = ReadSystemFile(FILEID_LOADER_SIGNED, out temp); + if(error != Errno.NoError) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Unable to read boot loader"); + mounted = false; + return error; + } + + error = ReadSystemFile((short)FILEID_MDDF, out temp); + if(error != Errno.NoError) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Unable to read MDDF"); + mounted = false; + return error; + } + + error = ReadSystemFile((short)FILEID_BITMAP, out temp); + if(error != Errno.NoError) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Unable to read volume bitmap"); + mounted = false; + return error; + } + + error = ReadSystemFile((short)FILEID_SRECORD, out temp); + if(error != Errno.NoError) + { + DicConsole.DebugWriteLine("LisaFS plugin", "Unable to read S-Records file"); + mounted = false; + return error; + } + } + + xmlFSType = new Schemas.FileSystemType(); + if(DateTime.Compare(mddf.dtvb, DateHandlers.LisaToDateTime(0)) > 0) + { + xmlFSType.BackupDate = mddf.dtvb; + xmlFSType.BackupDateSpecified = true; + } + xmlFSType.Clusters = mddf.vol_size; + xmlFSType.ClusterSize = mddf.clustersize * mddf.datasize; + if(DateTime.Compare(mddf.dtvc, DateHandlers.LisaToDateTime(0)) > 0) + { + xmlFSType.CreationDate = mddf.dtvc; + xmlFSType.CreationDateSpecified = true; + } + xmlFSType.Dirty = mddf.vol_left_mounted != 0; + xmlFSType.Files = mddf.filecount; + xmlFSType.FilesSpecified = true; + xmlFSType.FreeClusters = mddf.freecount; + xmlFSType.FreeClustersSpecified = true; + xmlFSType.Type = "LisaFS"; + xmlFSType.VolumeName = mddf.volname; + xmlFSType.VolumeSerial = String.Format("{0:X16}", mddf.volid); + + return Errno.NoError; + } + } + + DicConsole.DebugWriteLine("LisaFS plugin", "Not a Lisa filesystem"); + return Errno.NotSupported; + } + catch(Exception ex) + { + DicConsole.ErrorWriteLine("Exception {0}, {1}, {2}", ex.Message, ex.InnerException, ex.StackTrace); + return Errno.InOutError; + } } public override Errno Unmount() { - return Errno.NotImplemented; + mounted = false; + extentCache = null; + systemFileCache = null; + fileCache = null; + catalogCache = null; + fileSizeCache = null; + + return Errno.NoError; } public override Errno StatFs(ref FileSystemInfo stat) { - return Errno.NotImplemented; + if(!mounted) + return Errno.AccessDenied; + + stat = new FileSystemInfo(); + stat.Blocks = mddf.vol_size; + stat.FilenameLength = (ushort)E_NAME; + stat.Files = mddf.filecount; + stat.FreeBlocks = mddf.freecount; + stat.FreeFiles = FILEID_MAX - stat.Files; + stat.Id.Serial64 = mddf.volid; + stat.Id.IsLong = true; + stat.PluginId = PluginUUID; + stat.Type = "LisaFS v3"; + + return Errno.NoError; } } } diff --git a/DiscImageChef.Filesystems/LisaFS/Xattr.cs b/DiscImageChef.Filesystems/LisaFS/Xattr.cs index 8c5cf9e9..196ed091 100644 --- a/DiscImageChef.Filesystems/LisaFS/Xattr.cs +++ b/DiscImageChef.Filesystems/LisaFS/Xattr.cs @@ -37,6 +37,7 @@ // //$Id$ using System; using System.Collections.Generic; +using System.Text; namespace DiscImageChef.Filesystems.LisaFS { @@ -44,12 +45,115 @@ namespace DiscImageChef.Filesystems.LisaFS { public override Errno ListXAttr(string path, ref List xattrs) { - return Errno.NotImplemented; + Int16 fileId; + Errno error = LookupFileId(path, out fileId); + if(error != Errno.NoError) + return error; + + return ListXAttr(fileId, ref xattrs); } public override Errno GetXattr(string path, string xattr, ref byte[] buf) { - return Errno.NotImplemented; + Int16 fileId; + Errno error = LookupFileId(path, out fileId); + if(error != Errno.NoError) + return error; + + return GetXattr(fileId, xattr, out buf); + } + + Errno ListXAttr(Int16 fileId, ref List xattrs) + { + xattrs = null; + + if(!mounted) + return Errno.AccessDenied; + + if(fileId < 4) + { + if(!debug || fileId == 0) + return Errno.InvalidArgument; + + xattrs = new List(); + + if(fileId == FILEID_MDDF) + xattrs.Add("com.apple.lisa.password"); + + return Errno.NoError; + } + + ExtentFile file; + + 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"); + + return Errno.NoError; + } + + Errno GetXattr(Int16 fileId, string xattr, out byte[] buf) + { + buf = null; + + if(!mounted) + return Errno.AccessDenied; + + if(fileId < 4) + { + if(!debug || fileId == 0) + return Errno.InvalidArgument; + + if(fileId == FILEID_MDDF) + { + if(xattr == "com.apple.lisa.password") + { + buf = Encoding.ASCII.GetBytes(mddf.password); + return Errno.NoError; + } + } + + return Errno.NoSuchExtendedAttribute; + } + + ExtentFile file; + + Errno error = ReadExtentsFile(fileId, out file); + + if(error != Errno.NoError) + return error; + + if(xattr == "com.apple.lisa.password" && (file.flags & 0x08) == 0x08) + { + buf = new byte[8]; + Array.Copy(file.password, 0, buf, 0, 8); + return Errno.NoError; + } + + if(xattr == "com.apple.lisa.serial") + { + buf = new byte[3]; + Array.Copy(file.serial, 0, buf, 0, 3); + return Errno.NoError; + } + + if(!ArrayHelpers.ArrayIsNullOrEmpty(file.LisaInfo) && xattr == "com.apple.lisa.label") + { + buf = new byte[128]; + Array.Copy(file.LisaInfo, 0, buf, 0, 128); + return Errno.NoError; + } + + return Errno.NoSuchExtendedAttribute; } } }