/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: File.sg // // Note: // using Microsoft.SingSharp; using Microsoft.Singularity.Channels; using System; using MSD = Microsoft.Singularity.Directory; namespace Microsoft.Singularity.Services.Fat.Fs { enum FileError : int { NoError = 0, ReadOnly = 1, BadBufferOffset = 2, BadFileOffset = 3, NoSpace = 4 } class File : FsObject { private BlockIndex blockIndex; // cluster index private byte attributes; // cached attributes private uint bytes; // file size private const long MaxFileBytes = 0xffffffff; [ Microsoft.Contracts.NotDelayed ] internal File(Directory! parent, int shortEntryOffset, int firstCluster, byte attributes, uint fileLength) : base(parent, shortEntryOffset) { this.blockIndex = new BlockIndex(); this.attributes = attributes; this.bytes = fileLength; FatVolume.Fat.PopulateIndex(this.blockIndex, firstCluster); } [ Microsoft.Contracts.NotDelayed ] internal File(Directory! parent, int shortEntryOffset, int firstCluster, byte attributes) : this(parent, shortEntryOffset, firstCluster, attributes, 0) { } /// Close open file. internal void Close() { uint expectedClusters = (this.bytes + BytesPerCluster - 1) / BytesPerCluster; DebugStub.Assert(this.blockIndex.Count == expectedClusters); base.CloseInstance(); } bool IsReadOnly { [ Microsoft.Contracts.Pure ] get { return (this.attributes & DirectoryEntry.AttributeReadOnly) != 0; } } private static uint BytesPerCluster { [ Microsoft.Contracts.Pure ] get { return FatVolume.BpbSummary.BytesPerCluster; } } internal override int FirstCluster { get { int blockId; if (blockIndex.Lookup(0, out blockId) == false) { assert false; } return blockId; } } internal FileError Read(byte[]! in ExHeap destBuffer, int destOffset, uint fileOffset, int bytesToRead, out int bytesRead) requires bytesToRead >= 0; { bytesRead = 0; if (bytesToRead == 0) { return FileError.NoError; } if (destOffset < 0 || destOffset > destBuffer.Length) { return FileError.BadBufferOffset; } if (fileOffset > this.bytes) { return FileError.BadFileOffset; } bytesToRead = Math.Min((int)(this.bytes - fileOffset), bytesToRead); bytesToRead = Math.Min(destBuffer.Length - destOffset, bytesToRead); bytesRead = bytesToRead; lock (this) { int clusterNumber = (int)(fileOffset / BytesPerCluster); int clusterStart = (int)(fileOffset % BytesPerCluster); int clusterRemain = (int)Math.Min(BytesPerCluster - clusterStart, bytesToRead); while (bytesToRead != 0) { byte[]! in ExHeap block = (!)AcquireCluster(clusterNumber); try { Bitter.Copy(destBuffer, (int)destOffset, clusterRemain, block, clusterStart); } finally { ReleaseCluster(clusterNumber, block, false); } destOffset += clusterRemain; bytesToRead -= clusterRemain; clusterStart = 0; clusterRemain = (int)Math.Min(BytesPerCluster, bytesToRead); clusterNumber++; } UpdateLastAccessTime(); } return FileError.NoError; } private bool LockedGrowFileSpace(uint proposedFileBytes) { int oldChainLength = this.blockIndex.Count; int newChainLength = (int)((proposedFileBytes + BytesPerCluster - 1) / BytesPerCluster); int currentLength = oldChainLength; while (currentLength != newChainLength) { int allocLength = Math.Min(Fat.MaxAllocationLength, newChainLength - currentLength); int extensionLength; if (FatVolume.Fat.GrowChain(this.blockIndex, allocLength, out extensionLength) == true) { assert extensionLength == allocLength; currentLength += allocLength; } else { FatVolume.Fat.TruncateChain(this.blockIndex, oldChainLength); return false; } } return true; } private void LockedZeroFileRegion(uint start, uint length) { int clusterNumber = (int)(start / BytesPerCluster); int clusterStart = (int)(start % BytesPerCluster); if (clusterStart != 0) { int trim = Math.Min((int)BytesPerCluster - clusterStart, (int)length); byte[]! in ExHeap cluster = (!)AcquireCluster(clusterNumber); Bitter.Zero(cluster, clusterStart, trim); ReleaseCluster(clusterNumber, cluster, true); length -= (uint)trim; clusterStart = 0; clusterNumber++; } while (length >= BytesPerCluster) { ZeroCluster(clusterNumber); length -= BytesPerCluster; clusterNumber++; } if (length != 0) { byte[]! in ExHeap cluster = (!)AcquireCluster(clusterNumber); Bitter.Zero(cluster, 0, (int)length); ReleaseCluster(clusterNumber, cluster, true); } } private void LockedCopyFileRegion(byte[]! in ExHeap srcBuffer, int srcOffset, uint fileOffset, int length) { int clusterNumber = (int)(fileOffset / BytesPerCluster); int clusterStart = (int)(fileOffset % BytesPerCluster); if (clusterStart != 0) { int toCopy = Math.Min((int)BytesPerCluster - clusterStart, length); byte[]! in ExHeap cluster = (!)AcquireCluster(clusterNumber); Bitter.Copy(cluster, clusterStart, toCopy, srcBuffer, srcOffset); ReleaseCluster(clusterNumber, cluster, true); srcOffset += toCopy; length -= toCopy; clusterStart = 0; clusterNumber++; } while (length >= BytesPerCluster) { WriteCluster(clusterNumber, srcBuffer, srcOffset); srcOffset += (int)BytesPerCluster; length -= (int)BytesPerCluster; clusterNumber++; } if (length != 0) { byte[]! in ExHeap cluster2 = (!)AcquireCluster(clusterNumber); Bitter.Copy(cluster2, 0, length, srcBuffer, srcOffset); ReleaseCluster(clusterNumber, cluster2, true); } } internal FileError Write(byte[]! in ExHeap srcBuffer, int srcOffset, uint fileOffset, int bytesToWrite, out int bytesWritten) { bytesWritten = 0; if (FatVolume.IsReadOnly || this.IsReadOnly) { return FileError.ReadOnly; } if (bytesToWrite == 0) { return FileError.NoError; } if (srcOffset < 0 || srcOffset > srcBuffer.Length) { return FileError.BadBufferOffset; } if (bytesToWrite > srcBuffer.Length || srcBuffer.Length - srcOffset < bytesToWrite) { return FileError.BadBufferOffset; } long writeLimit = (long)fileOffset + bytesToWrite; if (writeLimit > MaxFileBytes) { return FileError.BadFileOffset; } lock (this) { if (writeLimit > this.bytes) { uint oldBytes = bytes; if (LockedGrowFileSpace((uint)writeLimit) == false) { return FileError.NoSpace; } this.bytes = Math.Max(fileOffset + (uint)bytesToWrite, this.bytes); if (fileOffset > oldBytes) { LockedZeroFileRegion(fileOffset, fileOffset - oldBytes); } } LockedCopyFileRegion(srcBuffer, srcOffset, fileOffset, bytesToWrite); bytesWritten = bytesToWrite; UpdateFileSize(this.bytes); } DebugStub.Assert((uint)writeLimit <= this.bytes); DebugStub.Assert( this.blockIndex.Count == (this.bytes + BytesPerCluster - 1) / BytesPerCluster ); return FileError.NoError; } // -------------------------------------------------------------------- // Cluster access methods [ Microsoft.Contracts.Pure ] private bool ValidClusterNumber(int clusterNumber) { return (clusterNumber >= 0 && clusterNumber < this.blockIndex.Count); } private byte[] in ExHeap AcquireCluster(int clusterNumber) requires ValidClusterNumber(clusterNumber); { int blockId = -1; if (blockIndex.Lookup(clusterNumber, out blockId)) { BlockCache bc = FatVolume.ClusterCache; byte [] in ExHeap buffer = bc.BeginQuickBlockOperation((uint)blockId); return buffer; } assert false; return null; } private void ReleaseCluster(int clusterNumber, [Claims] byte[]! in ExHeap buffer, bool dirty) requires ValidClusterNumber(clusterNumber); { int blockId; if (blockIndex.Lookup(clusterNumber, out blockId)) { BlockCache bc = FatVolume.ClusterCache; bc.EndQuickBlockOperation((uint)blockId, buffer, dirty); return; } assert false; } private void WriteCluster(int clusterNumber, byte[]! in ExHeap buffer, int bufferOffset) requires ValidClusterNumber(clusterNumber); requires buffer.Length >= bufferOffset; requires buffer.Length - bufferOffset >= BytesPerCluster; { int blockId; if (blockIndex.Lookup(clusterNumber, out blockId)) { BlockCache bc = FatVolume.ClusterCache; bc.WriteEntireBlock((uint)blockId, buffer, bufferOffset); return; } assert false; } private void ZeroCluster(int clusterNumber) { int blockId; if (blockIndex.Lookup(clusterNumber, out blockId)) { FatVolume.ClusterCache.ZeroBlock((uint)blockId); return; } assert false; } } }