mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
[cramfs] Implement readlink.
This commit is contained in:
@@ -27,7 +27,10 @@
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
@@ -69,4 +72,182 @@ public sealed partial class Cram
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <summary>Reads and decompresses a single block from a file</summary>
|
||||
/// <param name="fileNode">The file node</param>
|
||||
/// <param name="blockIndex">Block index (0-based)</param>
|
||||
/// <param name="blockData">Output decompressed block data</param>
|
||||
/// <returns>Error number indicating success or failure</returns>
|
||||
ErrorNumber ReadBlock(CramFileNode fileNode, int blockIndex, out byte[] blockData)
|
||||
{
|
||||
blockData = null;
|
||||
|
||||
if(blockIndex < 0 || blockIndex >= fileNode.BlockCount) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Read the block pointer
|
||||
uint blkPtrOffset = fileNode.BlockPtrOffset + (uint)(blockIndex * 4);
|
||||
|
||||
ErrorNumber errno = ReadBytes(blkPtrOffset, 4, out byte[] ptrData);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
uint blockPtr = _littleEndian
|
||||
? BitConverter.ToUInt32(ptrData, 0)
|
||||
: (uint)(ptrData[0] << 24 | ptrData[1] << 16 | ptrData[2] << 8 | ptrData[3]);
|
||||
|
||||
bool uncompressed = (blockPtr & CRAMFS_BLK_FLAG_UNCOMPRESSED) != 0;
|
||||
bool direct = (blockPtr & CRAMFS_BLK_FLAG_DIRECT_PTR) != 0;
|
||||
|
||||
blockPtr &= ~CRAMFS_BLK_FLAGS;
|
||||
|
||||
uint blockStart;
|
||||
uint blockLen;
|
||||
|
||||
if(direct)
|
||||
{
|
||||
// Direct pointer: absolute start pointer shifted by 2 bits
|
||||
blockStart = blockPtr << CRAMFS_BLK_DIRECT_PTR_SHIFT;
|
||||
|
||||
if(uncompressed)
|
||||
{
|
||||
// Uncompressed: size is PAGE_SIZE (or less for last block)
|
||||
blockLen = PAGE_SIZE;
|
||||
|
||||
if(blockIndex == fileNode.BlockCount - 1)
|
||||
{
|
||||
// Last block: cap to remaining file length
|
||||
blockLen = (uint)(fileNode.Length % PAGE_SIZE);
|
||||
|
||||
if(blockLen == 0) blockLen = PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compressed: size is in first 2 bytes
|
||||
errno = ReadBytes(blockStart, 2, out byte[] sizeData);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
blockLen = _littleEndian ? BitConverter.ToUInt16(sizeData, 0) : (uint)(sizeData[0] << 8 | sizeData[1]);
|
||||
|
||||
blockStart += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-direct: block pointer indicates end of current block
|
||||
// Start comes from end of block pointer table (for first block) or previous block's pointer
|
||||
blockStart = fileNode.BlockPtrOffset + fileNode.BlockCount * 4;
|
||||
|
||||
if(blockIndex > 0)
|
||||
{
|
||||
// Read previous block's pointer to get start
|
||||
errno = ReadBytes(blkPtrOffset - 4, 4, out byte[] prevPtrData);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
uint prevPtr = _littleEndian
|
||||
? BitConverter.ToUInt32(prevPtrData, 0)
|
||||
: (uint)(prevPtrData[0] << 24 |
|
||||
prevPtrData[1] << 16 |
|
||||
prevPtrData[2] << 8 |
|
||||
prevPtrData[3]);
|
||||
|
||||
// Handle case where previous pointer is direct
|
||||
if((prevPtr & CRAMFS_BLK_FLAG_DIRECT_PTR) != 0)
|
||||
{
|
||||
uint prevStart = (prevPtr & ~CRAMFS_BLK_FLAGS) << CRAMFS_BLK_DIRECT_PTR_SHIFT;
|
||||
|
||||
if((prevPtr & CRAMFS_BLK_FLAG_UNCOMPRESSED) != 0)
|
||||
blockStart = prevStart + PAGE_SIZE;
|
||||
else
|
||||
{
|
||||
// Read previous block's size
|
||||
errno = ReadBytes(prevStart, 2, out byte[] prevSizeData);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
uint prevLen = _littleEndian
|
||||
? BitConverter.ToUInt16(prevSizeData, 0)
|
||||
: (uint)(prevSizeData[0] << 8 | prevSizeData[1]);
|
||||
|
||||
blockStart = prevStart + 2 + prevLen;
|
||||
}
|
||||
}
|
||||
else
|
||||
blockStart = prevPtr & ~CRAMFS_BLK_FLAGS;
|
||||
}
|
||||
|
||||
blockLen = (blockPtr & ~CRAMFS_BLK_FLAGS) - blockStart;
|
||||
}
|
||||
|
||||
// Handle hole (zero-length block)
|
||||
if(blockLen == 0)
|
||||
{
|
||||
blockData = new byte[PAGE_SIZE];
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Sanity check block length
|
||||
if(blockLen > 2 * PAGE_SIZE || uncompressed && blockLen > PAGE_SIZE)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadBlock: bad block length {0}", blockLen);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
|
||||
// Read the block data
|
||||
errno = ReadBytes(blockStart, blockLen, out byte[] compressedData);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(uncompressed)
|
||||
{
|
||||
// Pad to PAGE_SIZE if needed
|
||||
if(compressedData.Length >= PAGE_SIZE)
|
||||
{
|
||||
blockData = compressedData;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
blockData = new byte[PAGE_SIZE];
|
||||
Array.Copy(compressedData, blockData, compressedData.Length);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Decompress data
|
||||
// Standard cramfs uses zlib compression (with header, not raw deflate)
|
||||
// Note: The CramCompression enum exists for potential future extensions,
|
||||
// but standard cramfs only uses zlib. There's no flag in the superblock
|
||||
// to indicate alternative compression methods.
|
||||
try
|
||||
{
|
||||
blockData = new byte[PAGE_SIZE];
|
||||
|
||||
using var compressedStream = new MemoryStream(compressedData);
|
||||
using var zlibStream = new ZLibStream(compressedStream, CompressionMode.Decompress);
|
||||
|
||||
var totalRead = 0;
|
||||
|
||||
while(totalRead < PAGE_SIZE)
|
||||
{
|
||||
int bytesDecompressed = zlibStream.Read(blockData, totalRead, PAGE_SIZE - totalRead);
|
||||
|
||||
if(bytesDecompressed == 0) break;
|
||||
|
||||
totalRead += bytesDecompressed;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadBlock: decompression failed: {0}", ex);
|
||||
|
||||
return ErrorNumber.InvalidArgument;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,5 +97,43 @@ public sealed partial class Cram
|
||||
/// </summary>
|
||||
const int CRAMFS_BLK_DIRECT_PTR_SHIFT = 2;
|
||||
|
||||
#endregion
|
||||
|
||||
#region POSIX file type constants
|
||||
|
||||
/// <summary>File type mask</summary>
|
||||
const ushort S_IFMT = 0xF000;
|
||||
|
||||
/// <summary>Socket</summary>
|
||||
const ushort S_IFSOCK = 0xC000;
|
||||
|
||||
/// <summary>Symbolic link</summary>
|
||||
const ushort S_IFLNK = 0xA000;
|
||||
|
||||
/// <summary>Regular file</summary>
|
||||
const ushort S_IFREG = 0x8000;
|
||||
|
||||
/// <summary>Block device</summary>
|
||||
const ushort S_IFBLK = 0x6000;
|
||||
|
||||
/// <summary>Directory</summary>
|
||||
const ushort S_IFDIR = 0x4000;
|
||||
|
||||
/// <summary>Character device</summary>
|
||||
const ushort S_IFCHR = 0x2000;
|
||||
|
||||
/// <summary>FIFO (named pipe)</summary>
|
||||
const ushort S_IFIFO = 0x1000;
|
||||
|
||||
/// <summary>Permission bits mask</summary>
|
||||
const ushort S_IPERM = 0x0FFF;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Page size
|
||||
|
||||
/// <summary>CramFS page size (4KB)</summary>
|
||||
const int PAGE_SIZE = 4096;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -29,27 +29,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
using Aaru.CommonTypes.Structs;
|
||||
using Aaru.Logging;
|
||||
using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed partial class Cram
|
||||
{
|
||||
// POSIX file type constants (from mode)
|
||||
const ushort S_IFMT = 0xF000; // File type mask
|
||||
const ushort S_IFSOCK = 0xC000; // Socket
|
||||
const ushort S_IFLNK = 0xA000; // Symbolic link
|
||||
const ushort S_IFREG = 0x8000; // Regular file
|
||||
const ushort S_IFBLK = 0x6000; // Block device
|
||||
const ushort S_IFDIR = 0x4000; // Directory
|
||||
const ushort S_IFCHR = 0x2000; // Character device
|
||||
const ushort S_IFIFO = 0x1000; // FIFO (named pipe)
|
||||
|
||||
// Permission mask
|
||||
const ushort S_IPERM = 0x0FFF; // Permission bits
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber Stat(string path, out FileEntryInfo stat)
|
||||
{
|
||||
@@ -136,4 +125,277 @@ public sealed partial class Cram
|
||||
|
||||
return ErrorNumber.NoSuchFile;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber OpenFile(string path, out IFileNode node)
|
||||
{
|
||||
node = null;
|
||||
|
||||
if(!_mounted) return ErrorNumber.AccessDenied;
|
||||
|
||||
if(string.IsNullOrEmpty(path) || path == "/") return ErrorNumber.IsDirectory;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenFile: path='{0}'", path);
|
||||
|
||||
// Get file stat to verify it exists and determine its type
|
||||
ErrorNumber errno = Stat(path, out FileEntryInfo stat);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenFile: Stat failed with {0}", errno);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
// Check it's not a directory
|
||||
if(stat.Attributes.HasFlag(FileAttributes.Directory))
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenFile: path is a directory");
|
||||
|
||||
return ErrorNumber.IsDirectory;
|
||||
}
|
||||
|
||||
// Look up the file's inode
|
||||
errno = LookupFile(path, out DirectoryEntryInfo entry);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenFile: LookupFile failed with {0}", errno);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
uint size = entry.Inode.Size;
|
||||
uint blockCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
|
||||
node = new CramFileNode
|
||||
{
|
||||
Path = path,
|
||||
Length = size,
|
||||
Offset = 0,
|
||||
Inode = entry.Inode,
|
||||
BlockPtrOffset = entry.Inode.Offset << 2,
|
||||
BlockCount = blockCount
|
||||
};
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "OpenFile: success, size={0}, blocks={1}", size, blockCount);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber CloseFile(IFileNode node)
|
||||
{
|
||||
if(node is not CramFileNode fileNode) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Clear node state to indicate it's closed
|
||||
fileNode.Offset = -1;
|
||||
fileNode.Length = 0;
|
||||
fileNode.BlockPtrOffset = 0;
|
||||
fileNode.BlockCount = 0;
|
||||
fileNode.Path = null;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadLink(string path, out string dest)
|
||||
{
|
||||
dest = null;
|
||||
|
||||
if(!_mounted) return ErrorNumber.AccessDenied;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadLink: path='{0}'", path);
|
||||
|
||||
// Get file stat to verify it's a symlink
|
||||
ErrorNumber errno = Stat(path, out FileEntryInfo stat);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
if(!stat.Attributes.HasFlag(FileAttributes.Symlink)) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Look up the file's entry
|
||||
errno = LookupFile(path, out DirectoryEntryInfo entry);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
uint size = entry.Inode.Size;
|
||||
|
||||
if(size == 0)
|
||||
{
|
||||
dest = "";
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Read symlink target - symlinks are typically small
|
||||
// Create a temporary file node to read the data
|
||||
uint blockCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
|
||||
var tempNode = new CramFileNode
|
||||
{
|
||||
Path = path,
|
||||
Length = size,
|
||||
Offset = 0,
|
||||
Inode = entry.Inode,
|
||||
BlockPtrOffset = entry.Inode.Offset << 2,
|
||||
BlockCount = blockCount
|
||||
};
|
||||
|
||||
var linkData = new byte[size];
|
||||
var bytesRead = 0;
|
||||
|
||||
// Read block by block
|
||||
for(var blockIndex = 0; blockIndex < blockCount && bytesRead < size; blockIndex++)
|
||||
{
|
||||
errno = ReadBlock(tempNode, blockIndex, out byte[] blockData);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadLink: ReadBlock failed for block {0}: {1}", blockIndex, errno);
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(blockData.Length, (int)(size - bytesRead));
|
||||
Array.Copy(blockData, 0, linkData, bytesRead, toCopy);
|
||||
bytesRead += toCopy;
|
||||
}
|
||||
|
||||
dest = _encoding.GetString(linkData, 0, (int)size).TrimEnd('\0');
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadLink: target='{0}'", dest);
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read)
|
||||
{
|
||||
read = 0;
|
||||
|
||||
if(!_mounted) return ErrorNumber.AccessDenied;
|
||||
|
||||
if(node is not CramFileNode fileNode) return ErrorNumber.InvalidArgument;
|
||||
|
||||
if(buffer == null) return ErrorNumber.InvalidArgument;
|
||||
|
||||
if(fileNode.Offset < 0 || fileNode.Offset >= fileNode.Length) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Clamp read length to remaining 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;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadFile: offset={0}, length={1}, toRead={2}", fileNode.Offset, length, toRead);
|
||||
|
||||
long bytesRead = 0;
|
||||
long currentOffset = fileNode.Offset;
|
||||
|
||||
while(bytesRead < toRead)
|
||||
{
|
||||
// Calculate which block we need
|
||||
var blockIndex = (int)(currentOffset / PAGE_SIZE);
|
||||
|
||||
// Read and decompress this block
|
||||
ErrorNumber errno = ReadBlock(fileNode, blockIndex, out byte[] blockData);
|
||||
|
||||
if(errno != ErrorNumber.NoError)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "ReadFile: ReadBlock failed for block {0}: {1}", blockIndex, errno);
|
||||
|
||||
if(bytesRead > 0) break;
|
||||
|
||||
return errno;
|
||||
}
|
||||
|
||||
// Calculate offset within the block
|
||||
var offsetInBlock = (int)(currentOffset % PAGE_SIZE);
|
||||
|
||||
// Calculate how much to copy from this block
|
||||
int toCopy = Math.Min(blockData.Length - offsetInBlock, (int)(toRead - bytesRead));
|
||||
|
||||
if(toCopy <= 0) break;
|
||||
|
||||
Array.Copy(blockData, offsetInBlock, buffer, bytesRead, toCopy);
|
||||
|
||||
bytesRead += toCopy;
|
||||
currentOffset += toCopy;
|
||||
}
|
||||
|
||||
read = bytesRead;
|
||||
fileNode.Offset += bytesRead;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Looks up a file by path and returns its directory entry</summary>
|
||||
/// <param name="path">Path to the file</param>
|
||||
/// <param name="entry">The directory entry info</param>
|
||||
/// <returns>Error number indicating success or failure</returns>
|
||||
ErrorNumber LookupFile(string path, out DirectoryEntryInfo entry)
|
||||
{
|
||||
entry = null;
|
||||
|
||||
// Normalize path
|
||||
string normalizedPath = path ?? "/";
|
||||
|
||||
if(normalizedPath is "" or ".") normalizedPath = "/";
|
||||
|
||||
if(normalizedPath == "/") return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Remove leading slash
|
||||
string pathWithoutLeadingSlash = normalizedPath.StartsWith("/", StringComparison.Ordinal)
|
||||
? normalizedPath[1..]
|
||||
: normalizedPath;
|
||||
|
||||
string[] pathComponents = pathWithoutLeadingSlash.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(pathComponents.Length == 0) return ErrorNumber.InvalidArgument;
|
||||
|
||||
// Start from root directory cache
|
||||
Dictionary<string, DirectoryEntryInfo> currentEntries = _rootDirectoryCache;
|
||||
|
||||
// Traverse path to find the file
|
||||
for(var p = 0; p < pathComponents.Length; p++)
|
||||
{
|
||||
string component = pathComponents[p];
|
||||
|
||||
// Skip "." and ".."
|
||||
if(component is "." or "..") continue;
|
||||
|
||||
// Find the component in current directory
|
||||
if(!currentEntries.TryGetValue(component, out DirectoryEntryInfo foundEntry)) return ErrorNumber.NoSuchFile;
|
||||
|
||||
// If this is the last component, return it
|
||||
if(p == pathComponents.Length - 1)
|
||||
{
|
||||
entry = foundEntry;
|
||||
|
||||
return ErrorNumber.NoError;
|
||||
}
|
||||
|
||||
// Not the last component - must be a directory
|
||||
if(!IsDirectory(foundEntry.Inode.Mode)) return ErrorNumber.NotDirectory;
|
||||
|
||||
// Read directory contents for next iteration
|
||||
uint dirOffset = foundEntry.Inode.Offset << 2;
|
||||
uint dirSize = foundEntry.Inode.Size;
|
||||
|
||||
var dirEntries = new Dictionary<string, DirectoryEntryInfo>(StringComparer.Ordinal);
|
||||
|
||||
ErrorNumber errno = ReadDirectoryContents(dirOffset, dirSize, dirEntries);
|
||||
|
||||
if(errno != ErrorNumber.NoError) return errno;
|
||||
|
||||
currentEntries = dirEntries;
|
||||
}
|
||||
|
||||
return ErrorNumber.NoSuchFile;
|
||||
}
|
||||
}
|
||||
@@ -66,17 +66,21 @@ public sealed partial class Cram
|
||||
sealed class CramFileNode : IFileNode
|
||||
{
|
||||
/// <summary>The cramfs inode</summary>
|
||||
internal Inode Inode { get; init; }
|
||||
internal Inode Inode { get; set; }
|
||||
|
||||
/// <summary>Byte offset of block pointers in the filesystem (inode.Offset << 2)</summary>
|
||||
internal uint BlockPtrOffset { get; set; }
|
||||
|
||||
/// <summary>Number of blocks in the file (ceiling of size / PAGE_SIZE)</summary>
|
||||
internal uint BlockCount { get; set; }
|
||||
|
||||
/// <summary>Byte offset of data in the filesystem</summary>
|
||||
internal uint DataOffset { get; init; }
|
||||
/// <inheritdoc />
|
||||
public string Path { get; init; }
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>Current read position within the file</summary>
|
||||
public long Offset { get; set; }
|
||||
|
||||
/// <summary>File length in bytes</summary>
|
||||
public long Length { get; init; }
|
||||
public long Length { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// /***************************************************************************
|
||||
// Aaru Data Preservation Suite
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Filename : Unimplemented.cs
|
||||
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Component : Cram file system 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
|
||||
// ****************************************************************************/
|
||||
|
||||
// ReSharper disable UnusedMember.Local
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Aaru.CommonTypes.Enums;
|
||||
using Aaru.CommonTypes.Interfaces;
|
||||
|
||||
namespace Aaru.Filesystems;
|
||||
|
||||
/// <inheritdoc />
|
||||
[SuppressMessage("ReSharper", "UnusedType.Local")]
|
||||
public sealed partial class Cram
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadLink(string path, out string dest) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber OpenFile(string path, out IFileNode node) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber CloseFile(IFileNode node) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ErrorNumber ReadFile(IFileNode node, long length, byte[] buffer, out long read) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
Reference in New Issue
Block a user