mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
194 lines
9.0 KiB
C#
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;
|
|
}
|
|
} |