Files
Aaru/Aaru.Filesystems/BTRFS/Inode.cs

194 lines
9.0 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Inode.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : B-tree file system plugin.
//
// --[ Description ] ----------------------------------------------------------
//
// Inode reading methods.
//
// --[ 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 <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/
using System;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Structs;
using Aaru.Helpers;
using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes;
using Marshal = Aaru.Helpers.Marshal;
namespace Aaru.Filesystems;
public sealed partial class BTRFS
{
/// <summary>Reads the INODE_ITEM for the specified objectid from the FS tree</summary>
/// <param name="objectId">The objectid (inode number) to read</param>
/// <param name="treeRoot">The logical byte address of the tree to read from</param>
/// <param name="inodeItem">The parsed InodeItem structure</param>
/// <returns>Error number indicating success or failure</returns>
ErrorNumber ReadInode(ulong objectId, ulong treeRoot, out InodeItem inodeItem)
{
inodeItem = default(InodeItem);
ErrorNumber errno = ReadTreeBlock(treeRoot, out byte[] fsTreeData);
if(errno != ErrorNumber.NoError) return errno;
Header fsTreeHeader = Marshal.ByteArrayToStructureLittleEndian<Header>(fsTreeData);
return WalkTreeForInodeItem(fsTreeData, fsTreeHeader, objectId, out inodeItem);
}
/// <summary>Walks a tree node recursively to find the INODE_ITEM for the specified objectid</summary>
/// <param name="nodeData">Raw tree node data</param>
/// <param name="header">Parsed node header</param>
/// <param name="objectId">The objectid to search for</param>
/// <param name="inodeItem">The parsed InodeItem if found</param>
/// <returns>Error number indicating success or failure</returns>
ErrorNumber WalkTreeForInodeItem(byte[] nodeData, in Header header, ulong objectId, out InodeItem inodeItem)
{
inodeItem = default(InodeItem);
int headerSize = Marshal.SizeOf<Header>();
if(header.level == 0) return ExtractInodeItemFromLeaf(nodeData, header, objectId, out inodeItem);
// Internal node — binary search for the correct child
var keyBytes = new byte[17];
BitConverter.TryWriteBytes(keyBytes.AsSpan(0), objectId);
keyBytes[8] = BTRFS_INODE_ITEM_KEY;
// offset = 0 already from array init
DiskKey targetKey = Marshal.ByteArrayToStructureLittleEndian<DiskKey>(keyBytes);
int childIdx = BinarySearchNode(nodeData, header, targetKey);
if(childIdx < 0) return ErrorNumber.NoSuchFile;
int keyPtrSize = Marshal.SizeOf<KeyPtr>();
int keyPtrOffset = headerSize + childIdx * keyPtrSize;
if(keyPtrOffset + keyPtrSize > nodeData.Length) return ErrorNumber.NoSuchFile;
KeyPtr keyPtr = Marshal.ByteArrayToStructureLittleEndian<KeyPtr>(nodeData, keyPtrOffset, keyPtrSize);
ErrorNumber errno = ReadTreeBlock(keyPtr.blockptr, out byte[] childData);
if(errno != ErrorNumber.NoError) return errno;
Header childHeader = Marshal.ByteArrayToStructureLittleEndian<Header>(childData);
return WalkTreeForInodeItem(childData, childHeader, objectId, out inodeItem);
}
/// <summary>Extracts an INODE_ITEM from a leaf node for the specified objectid</summary>
/// <param name="leafData">Raw leaf node data</param>
/// <param name="header">Parsed leaf header</param>
/// <param name="objectId">The objectid to search for</param>
/// <param name="inodeItem">The parsed InodeItem if found</param>
/// <returns>Error number indicating success or failure</returns>
ErrorNumber ExtractInodeItemFromLeaf(byte[] leafData, in Header header, ulong objectId, out InodeItem inodeItem)
{
inodeItem = default(InodeItem);
int itemSize = Marshal.SizeOf<Item>();
int headerSize = Marshal.SizeOf<Header>();
int inodeItemSize = Marshal.SizeOf<InodeItem>();
uint startItem = BinarySearchLeaf(leafData, header, objectId);
for(uint i = startItem; i < header.nritems; i++)
{
int itemOffset = headerSize + (int)i * itemSize;
if(itemOffset + itemSize > leafData.Length) break;
Item item = Marshal.ByteArrayToStructureLittleEndian<Item>(leafData, itemOffset, itemSize);
if(item.key.objectid > objectId) break;
if(item.key.objectid != objectId || item.key.type != BTRFS_INODE_ITEM_KEY) continue;
int dataOffset = headerSize + (int)item.offset;
if(dataOffset + inodeItemSize > leafData.Length) continue;
inodeItem = Marshal.ByteArrayToStructureLittleEndian<InodeItem>(leafData, dataOffset, inodeItemSize);
return ErrorNumber.NoError;
}
return ErrorNumber.NoSuchFile;
}
/// <summary>Converts a btrfs InodeItem to a FileEntryInfo</summary>
/// <param name="inode">The btrfs inode item</param>
/// <param name="objectId">The objectid (inode number)</param>
/// <returns>Populated FileEntryInfo</returns>
static FileEntryInfo InodeItemToFileEntryInfo(in InodeItem inode, ulong objectId)
{
var info = new FileEntryInfo
{
Inode = objectId,
Links = inode.nlink,
Length = (long)inode.size,
BlockSize = (long)inode.nbytes > 0 ? (long)inode.nbytes : (long)inode.size,
Blocks = inode.nbytes > 0 ? (long)((inode.nbytes + 4095) / 4096) : 0,
UID = inode.uid,
GID = inode.gid,
Mode = inode.mode & 0x0FFF,
AccessTimeUtc = DateHandlers.UnixUnsignedToDateTime(inode.atime.sec, inode.atime.nsec),
LastWriteTimeUtc = DateHandlers.UnixUnsignedToDateTime(inode.mtime.sec, inode.mtime.nsec),
StatusChangeTimeUtc = DateHandlers.UnixUnsignedToDateTime(inode.ctime.sec, inode.ctime.nsec),
CreationTimeUtc = DateHandlers.UnixUnsignedToDateTime(inode.otime.sec, inode.otime.nsec)
};
// Determine file type from S_IFMT bits in mode
info.Attributes = (inode.mode & S_IFMT) switch
{
S_IFDIR => FileAttributes.Directory,
S_IFREG => FileAttributes.File,
S_IFLNK => FileAttributes.Symlink,
S_IFCHR => FileAttributes.CharDevice,
S_IFBLK => FileAttributes.BlockDevice,
S_IFIFO => FileAttributes.FIFO,
S_IFSOCK => FileAttributes.Socket,
_ => FileAttributes.File
};
// Map btrfs inode flags to FileAttributes
if((inode.flags & BTRFS_INODE_NODATASUM) != 0) info.Attributes |= FileAttributes.NoScrub;
if((inode.flags & BTRFS_INODE_NODATACOW) != 0) info.Attributes |= FileAttributes.NoCopyOnWrite;
if((inode.flags & BTRFS_INODE_READONLY) != 0) info.Attributes |= FileAttributes.ReadOnly;
if((inode.flags & BTRFS_INODE_SYNC) != 0) info.Attributes |= FileAttributes.Sync;
if((inode.flags & BTRFS_INODE_IMMUTABLE) != 0) info.Attributes |= FileAttributes.Immutable;
if((inode.flags & BTRFS_INODE_APPEND) != 0) info.Attributes |= FileAttributes.AppendOnly;
if((inode.flags & BTRFS_INODE_NODUMP) != 0) info.Attributes |= FileAttributes.NoDump;
if((inode.flags & BTRFS_INODE_NOATIME) != 0) info.Attributes |= FileAttributes.NoAccessTime;
if((inode.flags & BTRFS_INODE_DIRSYNC) != 0) info.Attributes |= FileAttributes.Sync;
if((inode.flags & BTRFS_INODE_COMPRESS) != 0) info.Attributes |= FileAttributes.Compressed;
// Device number for character/block devices
if((inode.mode & S_IFMT) is S_IFCHR or S_IFBLK) info.DeviceNo = inode.rdev;
return info;
}
}