From 2dd661880b36735d95dcb666163ea1e2c70069be Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sat, 28 Feb 2026 11:15:37 +0000 Subject: [PATCH] [ECMA67] Implement stat. --- Aaru.Filesystems/ECMA67/File.cs | 94 ++++++++++++++++++++++++ Aaru.Filesystems/ECMA67/Unimplemented.cs | 3 - 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/Aaru.Filesystems/ECMA67/File.cs b/Aaru.Filesystems/ECMA67/File.cs index c5914a1d3..abd82e4de 100644 --- a/Aaru.Filesystems/ECMA67/File.cs +++ b/Aaru.Filesystems/ECMA67/File.cs @@ -36,8 +36,10 @@ using System.IO; using System.Text; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; using Aaru.Helpers; using Aaru.Logging; +using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes; namespace Aaru.Filesystems; @@ -117,8 +119,100 @@ public sealed partial class ECMA67 return Math.Min(fullBlocks, dataFromBlockCalc > 0 ? dataFromBlockCalc : fullBlocks); } + /// Parses a 6-byte ECMA-67 date field (YYMMDD) into a DateTime + /// 6-byte ASCII date field + /// Parsed DateTime in UTC, or null if the field is blank or invalid + static DateTime? ParseEcma67Date(byte[] date) + { + if(date is null || date.Length < 6) return null; + + string str = Encoding.ASCII.GetString(date).Trim(); + + if(string.IsNullOrEmpty(str)) return null; + + if(str.Length < 6) return null; + + if(!int.TryParse(str[..2], NumberStyles.Integer, CultureInfo.InvariantCulture, out int year) || + !int.TryParse(str[2..4], NumberStyles.Integer, CultureInfo.InvariantCulture, out int month) || + !int.TryParse(str[4..6], NumberStyles.Integer, CultureInfo.InvariantCulture, out int day)) + return null; + + if(month is < 1 or > 12 || day is < 1 or > 31) return null; + + // Two-digit year: 00-99. Assume 1900s for this 1981-era standard. + year += 1900; + + try + { + return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc); + } + catch(ArgumentOutOfRangeException) + { + return null; + } + } + #region IReadOnlyFilesystem Members + /// + public ErrorNumber Stat(string path, out FileEntryInfo stat) + { + stat = null; + + if(!_mounted) return ErrorNumber.AccessDenied; + + string[] pathElements = path.Split(['/'], StringSplitOptions.RemoveEmptyEntries); + + // Root directory + if(pathElements.Length == 0 || + string.IsNullOrEmpty(path) || + string.Equals(path, "/", StringComparison.OrdinalIgnoreCase)) + { + stat = new FileEntryInfo + { + Attributes = FileAttributes.Directory, + BlockSize = PHYSICAL_RECORD_LENGTH_DATA, + Links = 1 + }; + + return ErrorNumber.NoError; + } + + if(pathElements.Length != 1) return ErrorNumber.NotSupported; + + string fileName = pathElements[0]; + + if(!_fileLabels.TryGetValue(fileName, out FileLabel label)) return ErrorNumber.NoSuchFile; + + if(!TryParseExtentAddress(label.beginOfExtent, out ulong startLba) || + !TryParseExtentAddress(label.endOfExtent, out ulong endLba)) + return ErrorNumber.InvalidArgument; + + int blockLength = ParseAsciiNumber(label.blockLength, PHYSICAL_RECORD_LENGTH_DATA); + long fileLength = ComputeFileLength(label, startLba, endLba, blockLength); + long blocks = (fileLength + PHYSICAL_RECORD_LENGTH_DATA - 1) / PHYSICAL_RECORD_LENGTH_DATA; + + FileAttributes attributes = FileAttributes.File; + + if(label.writeProtect == WRITE_PROTECT_YES) attributes |= FileAttributes.ReadOnly; + + stat = new FileEntryInfo + { + Attributes = attributes, + BlockSize = PHYSICAL_RECORD_LENGTH_DATA, + Length = fileLength, + Blocks = blocks, + Links = 1 + }; + + // Parse creation date (YYMMDD) if present + DateTime? creationDate = ParseEcma67Date(label.creationDate); + + if(creationDate.HasValue) stat.CreationTimeUtc = creationDate.Value; + + return ErrorNumber.NoError; + } + /// public ErrorNumber OpenFile(string path, out IFileNode node) { diff --git a/Aaru.Filesystems/ECMA67/Unimplemented.cs b/Aaru.Filesystems/ECMA67/Unimplemented.cs index d0a689889..56f0874a5 100644 --- a/Aaru.Filesystems/ECMA67/Unimplemented.cs +++ b/Aaru.Filesystems/ECMA67/Unimplemented.cs @@ -40,7 +40,4 @@ public sealed partial class ECMA67 { /// public ErrorNumber StatFs(out FileSystemInfo stat) => throw new NotImplementedException(); - - /// - public ErrorNumber Stat(string path, out FileEntryInfo stat) => throw new NotImplementedException(); } \ No newline at end of file