mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-04-05 21:44:17 +00:00
324 lines
12 KiB
C#
324 lines
12 KiB
C#
// /***************************************************************************
|
|
// Aaru Data Preservation Suite
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Filename : File.cs
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
//
|
|
// Component : BeOS old filesystem plugin.
|
|
//
|
|
// --[ 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.Interfaces;
|
|
using Aaru.CommonTypes.Structs;
|
|
using Aaru.Helpers;
|
|
|
|
namespace Aaru.Filesystems;
|
|
|
|
public sealed partial class BOFS
|
|
{
|
|
/// <inheritdoc />
|
|
public ErrorNumber OpenFile(string path, out IFileNode node)
|
|
{
|
|
node = null;
|
|
|
|
if(string.IsNullOrEmpty(path) || path == "/") return ErrorNumber.NotSupported;
|
|
|
|
// Use helper to lookup the entry
|
|
ErrorNumber lookupErr = LookupEntry(path, out FileEntry entry);
|
|
|
|
if(lookupErr != ErrorNumber.NoError) return ErrorNumber.NoSuchFile;
|
|
|
|
// Check if it's a file, not a directory
|
|
if(entry.FileType == DIR_TYPE) return ErrorNumber.NotSupported;
|
|
|
|
var fileNode = new BOFSFileNode
|
|
{
|
|
Path = path,
|
|
Length = entry.LogicalSize,
|
|
Offset = 0,
|
|
Entry = entry
|
|
};
|
|
|
|
// Check if file is contiguous (high bit set) or fragmented
|
|
if((entry.FirstAllocList & 0x80000000) != 0)
|
|
{
|
|
// Contiguous file
|
|
fileNode.ContiguousSector = entry.FirstAllocList & 0x7FFFFFFF;
|
|
fileNode.Fat = null;
|
|
}
|
|
else
|
|
{
|
|
// Fragmented file - read FAT
|
|
fileNode.ContiguousSector = -1;
|
|
|
|
// Read the FAT block (512 bytes)
|
|
const int bofsLogicalSectorSize = 512;
|
|
ulong fatByteOffset = (ulong)entry.FirstAllocList * bofsLogicalSectorSize;
|
|
ulong deviceSectorOffsetFromPartition = fatByteOffset / _imagePlugin.Info.SectorSize;
|
|
ulong offsetInDeviceSector = fatByteOffset % _imagePlugin.Info.SectorSize;
|
|
ulong absoluteDeviceSector = _partition.Start + deviceSectorOffsetFromPartition;
|
|
|
|
ErrorNumber errno = _imagePlugin.ReadSectors(absoluteDeviceSector, false, 1, out byte[] sectorData, out _);
|
|
|
|
if(errno != ErrorNumber.NoError) return errno;
|
|
|
|
if(sectorData.Length < (int)(offsetInDeviceSector + bofsLogicalSectorSize))
|
|
return ErrorNumber.InvalidArgument;
|
|
|
|
var fatBuffer = new byte[bofsLogicalSectorSize];
|
|
Array.Copy(sectorData, (int)offsetInDeviceSector, fatBuffer, 0, bofsLogicalSectorSize);
|
|
|
|
// Parse FAT as array of uint32
|
|
fileNode.Fat = new uint[bofsLogicalSectorSize / sizeof(uint)];
|
|
|
|
for(var i = 0; i < fileNode.Fat.Length; i++)
|
|
fileNode.Fat[i] = BigEndianBitConverter.ToUInt32(fatBuffer, i * sizeof(uint));
|
|
}
|
|
|
|
node = fileNode;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber CloseFile(IFileNode node)
|
|
{
|
|
if(node is not BOFSFileNode fileNode) return ErrorNumber.InvalidArgument;
|
|
|
|
// Nothing to clean up - no caching
|
|
fileNode.Offset = 0;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read)
|
|
{
|
|
read = 0;
|
|
|
|
if(node is not BOFSFileNode fileNode) return ErrorNumber.InvalidArgument;
|
|
|
|
// Directories cannot be read as files
|
|
if(fileNode.Entry.FileType == DIR_TYPE) return ErrorNumber.IsDirectory;
|
|
|
|
if(fileNode.Offset < 0 || fileNode.Offset > fileNode.Length) return ErrorNumber.InvalidArgument;
|
|
|
|
if(buffer == null) return ErrorNumber.InvalidArgument;
|
|
|
|
// Clamp read length to file size and buffer size
|
|
long toRead = length;
|
|
if(fileNode.Offset + toRead > fileNode.Length) toRead = fileNode.Length - fileNode.Offset;
|
|
|
|
if(toRead <= 0) return ErrorNumber.NoError;
|
|
|
|
if(toRead > buffer.Length) toRead = buffer.Length;
|
|
|
|
const int bofsLogicalSectorSize = 512;
|
|
|
|
// Read data using logical_to_physical logic
|
|
long bytesRead = 0;
|
|
long currentOffset = fileNode.Offset;
|
|
|
|
while(bytesRead < toRead)
|
|
{
|
|
// Calculate how much to read in this iteration
|
|
long remaining = toRead - bytesRead;
|
|
|
|
// Get physical location and max readable length from this extent
|
|
ErrorNumber getPhysErr = GetPhysicalLocation(fileNode,
|
|
currentOffset,
|
|
remaining,
|
|
out long physicalByteOffset,
|
|
out long maxReadLength);
|
|
|
|
if(getPhysErr != ErrorNumber.NoError) return getPhysErr;
|
|
|
|
// Read from physical location
|
|
var absoluteByteOffset = (ulong)physicalByteOffset;
|
|
ulong deviceSectorOffset = absoluteByteOffset / _imagePlugin.Info.SectorSize;
|
|
ulong offsetInDeviceSector = absoluteByteOffset % _imagePlugin.Info.SectorSize;
|
|
ulong absoluteDeviceSector = _partition.Start + deviceSectorOffset;
|
|
|
|
ulong bytesNeeded = offsetInDeviceSector + (ulong)maxReadLength;
|
|
var sectorsToRead = (uint)((bytesNeeded + _imagePlugin.Info.SectorSize - 1) / _imagePlugin.Info.SectorSize);
|
|
|
|
ErrorNumber errno =
|
|
_imagePlugin.ReadSectors(absoluteDeviceSector, false, sectorsToRead, out byte[] sectorData, out _);
|
|
|
|
if(errno != ErrorNumber.NoError) return errno;
|
|
|
|
if(sectorData.Length < (int)(offsetInDeviceSector + (ulong)maxReadLength))
|
|
return ErrorNumber.InvalidArgument;
|
|
|
|
// Copy data to buffer
|
|
Array.Copy(sectorData, (int)offsetInDeviceSector, buffer, bytesRead, maxReadLength);
|
|
|
|
bytesRead += maxReadLength;
|
|
currentOffset += maxReadLength;
|
|
}
|
|
|
|
read = bytesRead;
|
|
fileNode.Offset += bytesRead;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber Stat(string path, out FileEntryInfo stat)
|
|
{
|
|
stat = null;
|
|
|
|
if(string.IsNullOrEmpty(path) || path == "/")
|
|
{
|
|
// Root directory - use mode from superblock
|
|
stat = new FileEntryInfo
|
|
{
|
|
Attributes = FileAttributes.Directory,
|
|
Inode = 0,
|
|
Links = 2, // . and ..
|
|
Mode = (uint)_track0.RootMode,
|
|
Length = 0,
|
|
Blocks = 0,
|
|
BlockSize = _track0.BytesPerSector
|
|
};
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
// Use helper to lookup the entry
|
|
ErrorNumber lookupErr = LookupEntry(path, out FileEntry entry);
|
|
|
|
return lookupErr != ErrorNumber.NoError ? ErrorNumber.NoSuchFile : PopulateStat(entry, out stat);
|
|
}
|
|
|
|
/// <summary>Get physical byte offset and max readable length for a logical file offset</summary>
|
|
private ErrorNumber GetPhysicalLocation(BOFSFileNode file, long logicalOffset, long requestedLength,
|
|
out long physicalByteOffset, out long maxReadLength)
|
|
{
|
|
physicalByteOffset = 0;
|
|
maxReadLength = 0;
|
|
|
|
const int bofsLogicalSectorSize = 512;
|
|
|
|
if(file.Fat == null)
|
|
{
|
|
// Contiguous file
|
|
physicalByteOffset = file.ContiguousSector * bofsLogicalSectorSize + logicalOffset;
|
|
maxReadLength = requestedLength;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
// Fragmented file - search FAT for the extent containing logicalOffset
|
|
long currentOffset = 0;
|
|
|
|
// FAT starts at index 2, entries are pairs of (start_sector, size_in_sectors)
|
|
// Maximum 63 extents (indices 2-127, but we check i < 63 which gives us indices up to 126)
|
|
for(var i = 0; i < 63; i++)
|
|
{
|
|
int startIndex = 2 + i * 2;
|
|
int sizeIndex = 2 + i * 2 + 1;
|
|
|
|
// Bounds check
|
|
if(startIndex >= file.Fat.Length || sizeIndex >= file.Fat.Length) return ErrorNumber.InvalidArgument;
|
|
|
|
uint startSector = file.Fat[startIndex];
|
|
uint sizeSectors = file.Fat[sizeIndex];
|
|
|
|
// Check for end of FAT
|
|
if(startSector == 0xFFFFFFFF) return ErrorNumber.InvalidArgument;
|
|
|
|
long extentSizeBytes = (long)sizeSectors * bofsLogicalSectorSize;
|
|
|
|
// Check if logicalOffset is in this extent
|
|
if(logicalOffset < currentOffset + extentSizeBytes)
|
|
{
|
|
// Found the extent
|
|
long offsetInExtent = logicalOffset - currentOffset;
|
|
physicalByteOffset = (long)startSector * bofsLogicalSectorSize + offsetInExtent;
|
|
|
|
// Max readable is from this point to end of extent
|
|
maxReadLength = extentSizeBytes - offsetInExtent;
|
|
|
|
// But don't exceed requested length
|
|
if(maxReadLength > requestedLength) maxReadLength = requestedLength;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
currentOffset += extentSizeBytes;
|
|
}
|
|
|
|
return ErrorNumber.InvalidArgument;
|
|
}
|
|
|
|
private ErrorNumber PopulateStat(FileEntry entry, out FileEntryInfo stat)
|
|
{
|
|
stat = new FileEntryInfo
|
|
{
|
|
Inode = (ulong)entry.RecordId,
|
|
Links = 1,
|
|
Length = entry.LogicalSize,
|
|
Blocks = entry.PhysicalSize > 0
|
|
? (long)((ulong)(entry.PhysicalSize + _track0.BytesPerSector - 1) /
|
|
(ulong)_track0.BytesPerSector)
|
|
: 0,
|
|
BlockSize = _track0.BytesPerSector,
|
|
Mode = (uint)entry.Mode
|
|
};
|
|
|
|
// Set attributes based on FileType
|
|
if(entry.FileType == DIR_TYPE)
|
|
stat.Attributes |= FileAttributes.Directory;
|
|
else
|
|
stat.Attributes |= FileAttributes.File;
|
|
|
|
// Convert BeOS timestamps (seconds since 1970) to .NET DateTime
|
|
if(entry.CreationDate != 0) stat.CreationTimeUtc = DateHandlers.UnixToDateTime(entry.CreationDate);
|
|
|
|
if(entry.ModificationDate != 0) stat.LastWriteTimeUtc = DateHandlers.UnixToDateTime(entry.ModificationDate);
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <summary>File node for tracking open file state</summary>
|
|
private sealed class BOFSFileNode : IFileNode
|
|
{
|
|
/// <summary>File entry data</summary>
|
|
public FileEntry Entry { get; set; }
|
|
|
|
/// <summary>FAT (File Allocation Table) if fragmented, null if contiguous</summary>
|
|
public uint[] Fat { get; set; }
|
|
|
|
/// <summary>Contiguous sector if not fragmented, -1 otherwise</summary>
|
|
public long ContiguousSector { get; set; }
|
|
/// <inheritdoc />
|
|
public string Path { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public long Length { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public long Offset { get; set; }
|
|
}
|
|
} |