[ods] Implement readlink.

This commit is contained in:
2026-02-07 19:53:31 +00:00
parent 1084134769
commit 493e5bbc2b
2 changed files with 107 additions and 38 deletions

View File

@@ -238,6 +238,113 @@ public sealed partial class ODS
return ErrorNumber.NoError;
}
/// <inheritdoc />
public ErrorNumber ReadLink(string path, out string dest)
{
dest = null;
if(!_mounted) return ErrorNumber.AccessDenied;
// Normalize path
string normalizedPath = string.IsNullOrWhiteSpace(path) ? "/" : path;
if(!normalizedPath.StartsWith("/", StringComparison.Ordinal)) normalizedPath = "/" + normalizedPath;
// Root directory cannot be a symlink
if(normalizedPath == "/") return ErrorNumber.InvalidArgument;
// Look up the file
ErrorNumber errno = LookupFile(normalizedPath, out CachedFile cachedFile);
if(errno != ErrorNumber.NoError) return errno;
// Read the file header
errno = ReadFileHeader(cachedFile.Fid.num, out FileHeader fileHeader);
if(errno != ErrorNumber.NoError) return errno;
// Verify it's a symbolic link (special file organization with symlink type)
if(fileHeader.recattr.Organization != FileOrganization.Special) return ErrorNumber.InvalidArgument;
var specialType = (SpecialFileType)((byte)fileHeader.recattr.rattrib & 0x0F);
if(specialType != SpecialFileType.SymLink && specialType != SpecialFileType.SymbolicLink)
return ErrorNumber.InvalidArgument;
// Get the mapping data for file extents
byte[] mapData = GetMapData(fileHeader);
if(mapData == null || mapData.Length == 0)
{
AaruLogging.Debug(MODULE_NAME, "Symlink has no mapping data");
return ErrorNumber.InvalidArgument;
}
// Calculate file size (symlink target length)
uint efblk = fileHeader.recattr.efblk.Value;
long length;
if(efblk > 0)
length = (efblk - 1) * ODS_BLOCK_SIZE + fileHeader.recattr.ffbyte;
else
length = 0;
if(length <= 0)
{
dest = string.Empty;
return ErrorNumber.NoError;
}
// Read the symlink target (file content)
var linkData = new byte[length];
var offset = 0L;
var read = 0;
while(read < length)
{
// Calculate which VBN we need (1-based)
uint vbn = (uint)(offset / ODS_BLOCK_SIZE) + 1;
var offsetInVbn = (int)(offset % ODS_BLOCK_SIZE);
// Map VBN to LBN
errno = MapVbnToLbn(mapData, fileHeader.map_inuse, vbn, out uint lbn, out _);
if(errno != ErrorNumber.NoError)
{
AaruLogging.Debug(MODULE_NAME, "Error mapping symlink VBN {0} to LBN: {1}", vbn, errno);
return errno;
}
// Read the ODS block
errno = ReadOdsBlock(_image, _partition, lbn, out byte[] blockData);
if(errno != ErrorNumber.NoError)
{
AaruLogging.Debug(MODULE_NAME, "Error reading symlink block at LBN {0}: {1}", lbn, errno);
return errno;
}
// Calculate how much data to copy from this block
int bytesAvailable = blockData.Length - offsetInVbn;
var bytesToCopy = (int)Math.Min(bytesAvailable, length - read);
// Copy data to buffer
Array.Copy(blockData, offsetInVbn, linkData, read, bytesToCopy);
offset += bytesToCopy;
read += bytesToCopy;
}
// Convert to string using the filesystem encoding
dest = _encoding.GetString(linkData).TrimEnd('\0');
return ErrorNumber.NoError;
}
/// <summary>Looks up a file by path and returns its cached entry.</summary>
/// <param name="path">Normalized path starting with /.</param>
/// <param name="cachedFile">Output cached file entry.</param>

View File

@@ -1,38 +0,0 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : Unimplemented.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Files-11 On-Disk Structure 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;
namespace Aaru.Filesystems;
public sealed partial class ODS
{
/// <inheritdoc />
public ErrorNumber ReadLink(string path, out string dest) => throw new NotImplementedException();
}