@@ -16,6 +16,16 @@ internal class ChunkAccess : IDisposable
1616 protected readonly IContentCrypt contentCrypt ;
1717 protected readonly IFileSystemStatistics fileSystemStatistics ;
1818
19+ /// <summary>
20+ /// The synchronization root guarding chunk operations.
21+ /// </summary>
22+ /// <remarks>
23+ /// A chunk access instance can be shared by multiple streams of the same file,
24+ /// and the reader/writer also share the position of one ciphertext stream,
25+ /// so chunk operations must not interleave.
26+ /// </remarks>
27+ protected readonly object chunkLock = new ( ) ;
28+
1929 /// <summary>
2030 /// Determines whether there are outstanding chunks ready to be flushed to disk.
2131 /// </summary>
@@ -42,24 +52,28 @@ public virtual int CopyFromChunk(long chunkNumber, Span<byte> destination, int o
4252 var plaintextChunk = ArrayPool < byte > . Shared . Rent ( contentCrypt . ChunkPlaintextSize ) ;
4353 try
4454 {
45- // ArrayPool may return a larger array than requested
46- var realPlaintextChunk = plaintextChunk . AsSpan ( 0 , contentCrypt . ChunkPlaintextSize ) ;
55+ // Hold the cache lock for the entire operation
56+ lock ( chunkLock )
57+ {
58+ // ArrayPool may return a larger array than requested
59+ var realPlaintextChunk = plaintextChunk . AsSpan ( 0 , contentCrypt . ChunkPlaintextSize ) ;
4760
48- // Read chunk
49- var read = chunkReader . ReadChunk ( chunkNumber , realPlaintextChunk ) ;
61+ // Read chunk
62+ var read = chunkReader . ReadChunk ( chunkNumber , realPlaintextChunk ) ;
5063
51- // Check for any errors
52- if ( read < 0 )
53- return read ;
64+ // Check for any errors
65+ if ( read < 0 )
66+ return read ;
5467
55- // Copy from chunk
56- var count = Math . Min ( read - offsetInChunk , destination . Length ) ;
57- if ( count <= 0 )
58- return 0 ;
68+ // Copy from chunk
69+ var count = Math . Min ( read - offsetInChunk , destination . Length ) ;
70+ if ( count <= 0 )
71+ return 0 ;
5972
60- realPlaintextChunk . Slice ( offsetInChunk , count ) . CopyTo ( destination ) ;
73+ realPlaintextChunk . Slice ( offsetInChunk , count ) . CopyTo ( destination ) ;
6174
62- return count ;
75+ return count ;
76+ }
6377 }
6478 finally
6579 {
@@ -84,28 +98,32 @@ public virtual int CopyToChunk(long chunkNumber, ReadOnlySpan<byte> source, int
8498 var plaintextChunk = ArrayPool < byte > . Shared . Rent ( contentCrypt . ChunkPlaintextSize ) ;
8599 try
86100 {
87- // ArrayPool may return larger array than requested
88- var realPlaintextChunk = plaintextChunk . AsSpan ( 0 , contentCrypt . ChunkPlaintextSize ) ;
101+ // Hold the cache lock for the entire operation
102+ lock ( chunkLock )
103+ {
104+ // ArrayPool may return larger array than requested
105+ var realPlaintextChunk = plaintextChunk . AsSpan ( 0 , contentCrypt . ChunkPlaintextSize ) ;
89106
90- // Read chunk
91- var read = chunkReader . ReadChunk ( chunkNumber , realPlaintextChunk ) ;
107+ // Read chunk
108+ var read = chunkReader . ReadChunk ( chunkNumber , realPlaintextChunk ) ;
92109
93- // Check for any errors
94- if ( read < 0 )
95- return read ;
110+ // Check for any errors
111+ if ( read < 0 )
112+ return read ;
96113
97- // Copy to chunk
98- var count = Math . Min ( contentCrypt . ChunkPlaintextSize - offsetInChunk , source . Length ) ;
99- if ( count <= 0 )
100- return 0 ;
114+ // Copy to chunk
115+ var count = Math . Min ( contentCrypt . ChunkPlaintextSize - offsetInChunk , source . Length ) ;
116+ if ( count <= 0 )
117+ return 0 ;
101118
102- var destination = realPlaintextChunk . Slice ( offsetInChunk , count ) ;
103- source . Slice ( 0 , count ) . CopyTo ( destination ) ;
119+ var destination = realPlaintextChunk . Slice ( offsetInChunk , count ) ;
120+ source . Slice ( 0 , count ) . CopyTo ( destination ) ;
104121
105- // Write to chunk
106- chunkWriter . WriteChunk ( chunkNumber , destination ) ;
122+ // Write to chunk
123+ chunkWriter . WriteChunk ( chunkNumber , destination ) ;
107124
108- return count ;
125+ return count ;
126+ }
109127 }
110128 finally
111129 {
@@ -129,41 +147,45 @@ public virtual void SetChunkLength(long chunkNumber, int length, bool includeCur
129147 var plaintextChunk = ArrayPool < byte > . Shared . Rent ( contentCrypt . ChunkPlaintextSize ) ;
130148 try
131149 {
132- // ArrayPool may return larger array than requested
133- var realPlaintextChunk = plaintextChunk . AsSpan ( 0 , contentCrypt . ChunkPlaintextSize ) ;
134-
135- // Read chunk
136- var read = chunkReader . ReadChunk ( chunkNumber , realPlaintextChunk ) ;
137-
138- // Check for any errors
139- if ( read < 0 )
140- throw new CryptographicException ( ) ;
141-
142- // Add read length of existing chunk data to the full length if specified
143- length += includeCurrentLength ? read : 0 ;
144- length = Math . Max ( length , 0 ) ;
145-
146- Span < byte > newPlaintextChunk ;
147-
148- // Determine whether to extend or truncate the chunk
149- if ( length < read )
150+ // Hold the cache lock for the entire operation
151+ lock ( chunkLock )
150152 {
151- // Truncate chunk
152- newPlaintextChunk = realPlaintextChunk . Slice ( 0 , Math . Min ( read , length ) ) ;
153+ // ArrayPool may return larger array than requested
154+ var realPlaintextChunk = plaintextChunk . AsSpan ( 0 , contentCrypt . ChunkPlaintextSize ) ;
155+
156+ // Read chunk
157+ var read = chunkReader . ReadChunk ( chunkNumber , realPlaintextChunk ) ;
158+
159+ // Check for any errors
160+ if ( read < 0 )
161+ throw new CryptographicException ( ) ;
162+
163+ // Add read length of existing chunk data to the full length if specified
164+ length += includeCurrentLength ? read : 0 ;
165+ length = Math . Max ( length , 0 ) ;
166+
167+ Span < byte > newPlaintextChunk ;
168+
169+ // Determine whether to extend or truncate the chunk
170+ if ( length < read )
171+ {
172+ // Truncate chunk
173+ newPlaintextChunk = realPlaintextChunk . Slice ( 0 , Math . Min ( read , length ) ) ;
174+ }
175+ else if ( read < length )
176+ {
177+ // Clear residual data from ArrayPool and append zeros
178+ realPlaintextChunk . Slice ( read ) . Clear ( ) ;
179+
180+ // Extend chunk
181+ newPlaintextChunk = realPlaintextChunk . Slice ( 0 , Math . Min ( length , contentCrypt . ChunkPlaintextSize ) ) ;
182+ }
183+ else
184+ return ; // Ignore resizing the same length
185+
186+ // Save newly modified chunk
187+ chunkWriter . WriteChunk ( chunkNumber , newPlaintextChunk ) ;
153188 }
154- else if ( read < length )
155- {
156- // Clear residual data from ArrayPool and append zeros
157- realPlaintextChunk . Slice ( read ) . Clear ( ) ;
158-
159- // Extend chunk
160- newPlaintextChunk = realPlaintextChunk . Slice ( 0 , Math . Min ( length , contentCrypt . ChunkPlaintextSize ) ) ;
161- }
162- else
163- return ; // Ignore resizing the same length
164-
165- // Save newly modified chunk
166- chunkWriter . WriteChunk ( chunkNumber , newPlaintextChunk ) ;
167189 }
168190 finally
169191 {
0 commit comments