1 /* 2 * Copyright (C) 2021 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package okio 17 18 /** 19 * An open file for reading and writing; using either streaming and random access. 20 * 21 * Use [read] and [write] to perform one-off random-access reads and writes. Use [source], [sink], 22 * and [appendingSink] for streaming reads and writes. 23 * 24 * File handles must be closed when they are no longer needed. It is an error to read, write, or 25 * create streams after a file handle is closed. The operating system resources held by a file 26 * handle will be released once the file handle **and** all of its streams are closed. 27 * 28 * Although this class offers both reading and writing APIs, file handle instances may be 29 * read-only or write-only. For example, a handle to a file on a read-only file system will throw an 30 * exception if a write is attempted. 31 * 32 * File handles may be used by multiple threads concurrently. But the individual sources and sinks 33 * produced by a file handle are not safe for concurrent use. 34 */ 35 abstract class FileHandle( 36 /** 37 * True if this handle supports both reading and writing. If this is false all write operations 38 * including [write], [sink], [resize], and [flush] will all throw [IllegalStateException] if 39 * called. 40 */ 41 val readWrite: Boolean, 42 ) : Closeable { 43 /** 44 * True once the file handle is closed. Resources should be released with [protectedClose] once 45 * this is true and [openStreamCount] is 0. 46 */ 47 private var closed = false 48 49 /** 50 * Reference count of the number of open sources and sinks on this file handle. Resources should 51 * be released with [protectedClose] once this is 0 and [closed] is true. 52 */ 53 private var openStreamCount = 0 54 55 val lock: Lock = newLock() 56 57 /** 58 * Reads at least 1, and up to [byteCount] bytes from this starting at [fileOffset] and copies 59 * them to [array] at [arrayOffset]. Returns the number of bytes read, or -1 if [fileOffset] 60 * equals [size]. 61 */ 62 @Throws(IOException::class) readnull63 fun read( 64 fileOffset: Long, 65 array: ByteArray, 66 arrayOffset: Int, 67 byteCount: Int, 68 ): Int { 69 lock.withLock { 70 check(!closed) { "closed" } 71 } 72 return protectedRead(fileOffset, array, arrayOffset, byteCount) 73 } 74 75 /** 76 * Reads at least 1, and up to [byteCount] bytes from this starting at [fileOffset] and appends 77 * them to [sink]. Returns the number of bytes read, or -1 if [fileOffset] equals [size]. 78 */ 79 @Throws(IOException::class) readnull80 fun read(fileOffset: Long, sink: Buffer, byteCount: Long): Long { 81 lock.withLock { 82 check(!closed) { "closed" } 83 } 84 return readNoCloseCheck(fileOffset, sink, byteCount) 85 } 86 87 /** 88 * Returns the total number of bytes in the file. This will change if the file size changes. 89 */ 90 @Throws(IOException::class) sizenull91 fun size(): Long { 92 lock.withLock { 93 check(!closed) { "closed" } 94 } 95 return protectedSize() 96 } 97 98 /** 99 * Changes the number of bytes in this file to [size]. This will remove bytes from the end if the 100 * new size is smaller. It will add `0` bytes to the end if it is larger. 101 */ 102 @Throws(IOException::class) resizenull103 fun resize(size: Long) { 104 check(readWrite) { "file handle is read-only" } 105 lock.withLock { 106 check(!closed) { "closed" } 107 } 108 return protectedResize(size) 109 } 110 111 /** Reads [byteCount] bytes from [array] and writes them to this at [fileOffset]. */ writenull112 fun write( 113 fileOffset: Long, 114 array: ByteArray, 115 arrayOffset: Int, 116 byteCount: Int, 117 ) { 118 check(readWrite) { "file handle is read-only" } 119 lock.withLock { 120 check(!closed) { "closed" } 121 } 122 return protectedWrite(fileOffset, array, arrayOffset, byteCount) 123 } 124 125 /** Removes [byteCount] bytes from [source] and writes them to this at [fileOffset]. */ 126 @Throws(IOException::class) writenull127 fun write(fileOffset: Long, source: Buffer, byteCount: Long) { 128 check(readWrite) { "file handle is read-only" } 129 lock.withLock { 130 check(!closed) { "closed" } 131 } 132 writeNoCloseCheck(fileOffset, source, byteCount) 133 } 134 135 /** Pushes all buffered bytes to their final destination. */ 136 @Throws(IOException::class) flushnull137 fun flush() { 138 check(readWrite) { "file handle is read-only" } 139 lock.withLock { 140 check(!closed) { "closed" } 141 } 142 return protectedFlush() 143 } 144 145 /** 146 * Returns a source that reads from this starting at [fileOffset]. The returned source must be 147 * closed when it is no longer needed. 148 */ 149 @Throws(IOException::class) sourcenull150 fun source(fileOffset: Long = 0L): Source { 151 lock.withLock { 152 check(!closed) { "closed" } 153 openStreamCount++ 154 } 155 return FileHandleSource(this, fileOffset) 156 } 157 158 /** 159 * Returns the position of [source] in the file. The argument [source] must be either a source 160 * produced by this file handle, or a [BufferedSource] that directly wraps such a source. If the 161 * parameter is a [BufferedSource], it adjusts for buffered bytes. 162 */ 163 @Throws(IOException::class) positionnull164 fun position(source: Source): Long { 165 var source = source 166 var bufferSize = 0L 167 168 if (source is RealBufferedSource) { 169 bufferSize = source.buffer.size 170 source = source.source 171 } 172 173 require(source is FileHandleSource && source.fileHandle === this) { 174 "source was not created by this FileHandle" 175 } 176 check(!source.closed) { "closed" } 177 178 return source.position - bufferSize 179 } 180 181 /** 182 * Change the position of [source] in the file to [position]. The argument [source] must be either 183 * a source produced by this file handle, or a [BufferedSource] that directly wraps such a source. 184 * If the parameter is a [BufferedSource], it will skip or clear buffered bytes. 185 */ 186 @Throws(IOException::class) repositionnull187 fun reposition(source: Source, position: Long) { 188 if (source is RealBufferedSource) { 189 val fileHandleSource = source.source 190 require(fileHandleSource is FileHandleSource && fileHandleSource.fileHandle === this) { 191 "source was not created by this FileHandle" 192 } 193 check(!fileHandleSource.closed) { "closed" } 194 195 val bufferSize = source.buffer.size 196 val toSkip = position - (fileHandleSource.position - bufferSize) 197 if (toSkip in 0L until bufferSize) { 198 // The new position requires only a buffer change. 199 source.skip(toSkip) 200 } else { 201 // The new position doesn't share data with the current buffer. 202 source.buffer.clear() 203 fileHandleSource.position = position 204 } 205 } else { 206 require(source is FileHandleSource && source.fileHandle === this) { 207 "source was not created by this FileHandle" 208 } 209 check(!source.closed) { "closed" } 210 source.position = position 211 } 212 } 213 214 /** 215 * Returns a sink that writes to this starting at [fileOffset]. The returned sink must be closed 216 * when it is no longer needed. 217 */ 218 @Throws(IOException::class) sinknull219 fun sink(fileOffset: Long = 0L): Sink { 220 check(readWrite) { "file handle is read-only" } 221 lock.withLock { 222 check(!closed) { "closed" } 223 openStreamCount++ 224 } 225 return FileHandleSink(this, fileOffset) 226 } 227 228 /** 229 * Returns a sink that writes to this starting at the end. The returned sink must be closed when 230 * it is no longer needed. 231 */ 232 @Throws(IOException::class) appendingSinknull233 fun appendingSink(): Sink { 234 return sink(size()) 235 } 236 237 /** 238 * Returns the position of [sink] in the file. The argument [sink] must be either a sink produced 239 * by this file handle, or a [BufferedSink] that directly wraps such a sink. If the parameter is a 240 * [BufferedSink], it adjusts for buffered bytes. 241 */ 242 @Throws(IOException::class) positionnull243 fun position(sink: Sink): Long { 244 var sink = sink 245 var bufferSize = 0L 246 247 if (sink is RealBufferedSink) { 248 bufferSize = sink.buffer.size 249 sink = sink.sink 250 } 251 252 require(sink is FileHandleSink && sink.fileHandle === this) { 253 "sink was not created by this FileHandle" 254 } 255 check(!sink.closed) { "closed" } 256 257 return sink.position + bufferSize 258 } 259 260 /** 261 * Change the position of [sink] in the file to [position]. The argument [sink] must be either a 262 * sink produced by this file handle, or a [BufferedSink] that directly wraps such a sink. If the 263 * parameter is a [BufferedSink], it emits for buffered bytes. 264 */ 265 @Throws(IOException::class) repositionnull266 fun reposition(sink: Sink, position: Long) { 267 if (sink is RealBufferedSink) { 268 val fileHandleSink = sink.sink 269 require(fileHandleSink is FileHandleSink && fileHandleSink.fileHandle === this) { 270 "sink was not created by this FileHandle" 271 } 272 check(!fileHandleSink.closed) { "closed" } 273 274 sink.emit() 275 fileHandleSink.position = position 276 } else { 277 require(sink is FileHandleSink && sink.fileHandle === this) { 278 "sink was not created by this FileHandle" 279 } 280 check(!sink.closed) { "closed" } 281 sink.position = position 282 } 283 } 284 285 @Throws(IOException::class) closenull286 final override fun close() { 287 lock.withLock { 288 if (closed) return 289 closed = true 290 if (openStreamCount != 0) return 291 } 292 protectedClose() 293 } 294 295 /** Like [read] but not performing any close check. */ 296 @Throws(IOException::class) protectedReadnull297 protected abstract fun protectedRead( 298 fileOffset: Long, 299 array: ByteArray, 300 arrayOffset: Int, 301 byteCount: Int, 302 ): Int 303 304 /** Like [write] but not performing any close check. */ 305 @Throws(IOException::class) 306 protected abstract fun protectedWrite( 307 fileOffset: Long, 308 array: ByteArray, 309 arrayOffset: Int, 310 byteCount: Int, 311 ) 312 313 /** Like [flush] but not performing any close check. */ 314 @Throws(IOException::class) 315 protected abstract fun protectedFlush() 316 317 /** Like [resize] but not performing any close check. */ 318 @Throws(IOException::class) 319 protected abstract fun protectedResize(size: Long) 320 321 /** Like [size] but not performing any close check. */ 322 @Throws(IOException::class) 323 protected abstract fun protectedSize(): Long 324 325 /** 326 * Subclasses should implement this to release resources held by this file handle. It is invoked 327 * once both the file handle is closed, and also all sources and sinks produced by it are also 328 * closed. 329 */ 330 @Throws(IOException::class) 331 protected abstract fun protectedClose() 332 333 private fun readNoCloseCheck(fileOffset: Long, sink: Buffer, byteCount: Long): Long { 334 require(byteCount >= 0L) { "byteCount < 0: $byteCount" } 335 336 var currentOffset = fileOffset 337 val targetOffset = fileOffset + byteCount 338 339 while (currentOffset < targetOffset) { 340 val tail = sink.writableSegment(1) 341 val readByteCount = protectedRead( 342 fileOffset = currentOffset, 343 array = tail.data, 344 arrayOffset = tail.limit, 345 byteCount = minOf(targetOffset - currentOffset, Segment.SIZE - tail.limit).toInt(), 346 ) 347 348 if (readByteCount == -1) { 349 if (tail.pos == tail.limit) { 350 // We allocated a tail segment, but didn't end up needing it. Recycle! 351 sink.head = tail.pop() 352 SegmentPool.recycle(tail) 353 } 354 if (fileOffset == currentOffset) return -1L // We wanted bytes but didn't return any. 355 break 356 } 357 358 tail.limit += readByteCount 359 currentOffset += readByteCount 360 sink.size += readByteCount 361 } 362 363 return currentOffset - fileOffset 364 } 365 writeNoCloseChecknull366 private fun writeNoCloseCheck(fileOffset: Long, source: Buffer, byteCount: Long) { 367 checkOffsetAndCount(source.size, 0L, byteCount) 368 369 var currentOffset = fileOffset 370 val targetOffset = fileOffset + byteCount 371 372 while (currentOffset < targetOffset) { 373 val head = source.head!! 374 val toCopy = minOf(targetOffset - currentOffset, head.limit - head.pos).toInt() 375 protectedWrite(currentOffset, head.data, head.pos, toCopy) 376 377 head.pos += toCopy 378 currentOffset += toCopy 379 source.size -= toCopy 380 381 if (head.pos == head.limit) { 382 source.head = head.pop() 383 SegmentPool.recycle(head) 384 } 385 } 386 } 387 388 private class FileHandleSink( 389 val fileHandle: FileHandle, 390 var position: Long, 391 ) : Sink { 392 var closed = false 393 writenull394 override fun write(source: Buffer, byteCount: Long) { 395 check(!closed) { "closed" } 396 fileHandle.writeNoCloseCheck(position, source, byteCount) 397 position += byteCount 398 } 399 flushnull400 override fun flush() { 401 check(!closed) { "closed" } 402 fileHandle.protectedFlush() 403 } 404 timeoutnull405 override fun timeout() = Timeout.NONE 406 407 override fun close() { 408 if (closed) return 409 closed = true 410 fileHandle.lock.withLock { 411 fileHandle.openStreamCount-- 412 if (fileHandle.openStreamCount != 0 || !fileHandle.closed) return@close 413 } 414 fileHandle.protectedClose() 415 } 416 } 417 418 private class FileHandleSource( 419 val fileHandle: FileHandle, 420 var position: Long, 421 ) : Source { 422 var closed = false 423 readnull424 override fun read(sink: Buffer, byteCount: Long): Long { 425 check(!closed) { "closed" } 426 val result = fileHandle.readNoCloseCheck(position, sink, byteCount) 427 if (result != -1L) position += result 428 return result 429 } 430 timeoutnull431 override fun timeout() = Timeout.NONE 432 433 override fun close() { 434 if (closed) return 435 closed = true 436 fileHandle.lock.withLock { 437 fileHandle.openStreamCount-- 438 if (fileHandle.openStreamCount != 0 || !fileHandle.closed) return@close 439 } 440 fileHandle.protectedClose() 441 } 442 } 443 } 444