// /*************************************************************************** // The Disc Image Chef // ---------------------------------------------------------------------------- // // Filename : Xattr.cs // Author(s) : Natalia Portillo // // Component : Microsoft FAT filesystem plugin. // // --[ Description ] ---------------------------------------------------------- // // Methods to handle Microsoft FAT extended attributes. // // --[ License ] -------------------------------------------------------------- // // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2019 Natalia Portillo // ****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Helpers; namespace DiscImageChef.Filesystems.FAT { public partial class FAT { Dictionary> eaCache; /// /// Lists all extended attributes, alternate data streams and forks of the given file. /// /// Error number. /// Path. /// List of extended attributes, alternate data streams and forks. public Errno ListXAttr(string path, out List xattrs) { xattrs = null; if(!mounted) return Errno.AccessDenied; // No other xattr recognized yet if(cachedEaData is null && !fat32) return Errno.NotSupported; if(path[0] == '/') path = path.Substring(1); if(eaCache.TryGetValue(path.ToLower(cultureInfo), out Dictionary eas)) { xattrs = eas.Keys.ToList(); return Errno.NoError; } Errno err = GetFileEntry(path, out CompleteDirectoryEntry entry); if(err != Errno.NoError || entry is null) return err; xattrs = new List(); if(!fat32) { if(entry.Dirent.ea_handle == 0) return Errno.NoError; eas = GetEas(entry.Dirent.ea_handle); } else { if(entry.Fat32Ea.start_cluster == 0) return Errno.NoError; eas = GetEas(entry.Fat32Ea); } if(eas is null) return Errno.NoError; eaCache.Add(path.ToLower(cultureInfo), eas); xattrs = eas.Keys.ToList(); return Errno.NoError; } /// /// Reads an extended attribute, alternate data stream or fork from the given file. /// /// Error number. /// File path. /// Extendad attribute, alternate data stream or fork name. /// Buffer. public Errno GetXattr(string path, string xattr, ref byte[] buf) { if(!mounted) return Errno.AccessDenied; Errno err = ListXAttr(path, out List xattrs); if(err != Errno.NoError) return err; if(path[0] == '/') path = path.Substring(1); if(!xattrs.Contains(xattr.ToLower(cultureInfo))) return Errno.NoSuchExtendedAttribute; if(!eaCache.TryGetValue(path.ToLower(cultureInfo), out Dictionary eas)) return Errno.InvalidArgument; if(!eas.TryGetValue(xattr.ToLower(cultureInfo), out byte[] data)) return Errno.InvalidArgument; buf = new byte[data.Length]; data.CopyTo(buf, 0); return Errno.NoError; } Dictionary GetEas(DirectoryEntry entryFat32Ea) { MemoryStream eaMs = new MemoryStream(); uint[] rootDirectoryClusters = GetClusters(entryFat32Ea.start_cluster); foreach(uint cluster in rootDirectoryClusters) { byte[] buffer = image.ReadSectors(firstClusterSector + cluster * sectorsPerCluster, sectorsPerCluster); eaMs.Write(buffer, 0, buffer.Length); } byte[] full = eaMs.ToArray(); ushort size = BitConverter.ToUInt16(full, 0); byte[] eas = new byte[size]; Array.Copy(full, 0, eas, 0, size); full = null; eaMs.Close(); return GetEas(eas); } Dictionary GetEas(ushort eaHandle) { int aIndex = eaHandle >> 7; // First 0x20 bytes are the magic number and unused words ushort a = BitConverter.ToUInt16(cachedEaData, aIndex * 2 + 0x20); ushort b = BitConverter.ToUInt16(cachedEaData, eaHandle * 2 + 0x200); uint eaCluster = (uint)(a + b); if(b == EA_UNUSED) return null; EaHeader header = Marshal.ByteArrayToStructureLittleEndian(cachedEaData, (int)(eaCluster * bytesPerCluster), Marshal.SizeOf()); if(header.magic != 0x4145) return null; uint eaLen = BitConverter.ToUInt32(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf()); byte[] eaData = new byte[eaLen]; Array.Copy(cachedEaData, (int)(eaCluster * bytesPerCluster) + Marshal.SizeOf(), eaData, 0, eaLen); return GetEas(eaData); } Dictionary GetEas(byte[] eaData) { if(eaData is null || eaData.Length < 4) return null; Dictionary eas = new Dictionary(); if(debug) eas.Add("com.microsoft.os2.fea", eaData); int pos = 4; while(pos < eaData.Length) { byte fEA = eaData[pos++]; byte cbName = eaData[pos++]; ushort cbValue = BitConverter.ToUInt16(eaData, pos); pos += 2; string name = Encoding.ASCII.GetString(eaData, pos, cbName); pos += cbName; pos++; byte[] data = new byte[cbValue]; Array.Copy(eaData, pos, data, 0, cbValue); pos += cbValue; // OS/2 System Attributes if(name[0] == '.') { // This is WorkPlace System information so it's IBM if(name == ".CLASSINFO") name = "com.ibm.os2.classinfo"; else name = "com.microsoft.os2" + name.ToLower(); } eas.Add(name, data); } return eas; } void CacheEaData() { if(eaDirEntry.start_cluster == 0) return; MemoryStream eaDataMs = new MemoryStream(); foreach(byte[] buffer in GetClusters(eaDirEntry.start_cluster) .Select(cluster => image.ReadSectors(firstClusterSector + cluster * sectorsPerCluster, sectorsPerCluster))) eaDataMs.Write(buffer, 0, buffer.Length); cachedEaData = eaDataMs.ToArray(); } } }