Add _ChrootRandomAccessFile and ForwardingRandomAccessFile (#159)

`ChrootFile.open`/`ChrootFile.openSync` previously returned the
delegate's `RandomAccessFile` directly.  However, `RandomAccessFile`
has a `path` property (sigh) that exposes the underlying path instead
of the chroot'd path.

Add delegating wrapper classes for `RandomAccessFile` so that we can
override the returned `path`.
diff --git a/packages/file/lib/src/backends/chroot.dart b/packages/file/lib/src/backends/chroot.dart
index 5c90a4f..2f39f15 100644
--- a/packages/file/lib/src/backends/chroot.dart
+++ b/packages/file/lib/src/backends/chroot.dart
@@ -18,3 +18,4 @@
 part 'chroot/chroot_file_system.dart';
 part 'chroot/chroot_file_system_entity.dart';
 part 'chroot/chroot_link.dart';
+part 'chroot/chroot_random_access_file.dart';
diff --git a/packages/file/lib/src/backends/chroot/chroot_file.dart b/packages/file/lib/src/backends/chroot/chroot_file.dart
index 515720a..aabe24f 100644
--- a/packages/file/lib/src/backends/chroot/chroot_file.dart
+++ b/packages/file/lib/src/backends/chroot/chroot_file.dart
@@ -242,11 +242,13 @@
   Future<RandomAccessFile> open({
     FileMode mode = FileMode.read,
   }) async =>
-      getDelegate(followLinks: true).open(mode: mode);
+      _ChrootRandomAccessFile(
+          path, await getDelegate(followLinks: true).open(mode: mode));
 
   @override
   RandomAccessFile openSync({FileMode mode = FileMode.read}) =>
-      getDelegate(followLinks: true).openSync(mode: mode);
+      _ChrootRandomAccessFile(
+          path, getDelegate(followLinks: true).openSync(mode: mode));
 
   @override
   Stream<Uint8List> openRead([int? start, int? end]) =>
diff --git a/packages/file/lib/src/backends/chroot/chroot_random_access_file.dart b/packages/file/lib/src/backends/chroot/chroot_random_access_file.dart
new file mode 100644
index 0000000..968c2c2
--- /dev/null
+++ b/packages/file/lib/src/backends/chroot/chroot_random_access_file.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.10
+part of file.src.backends.chroot;
+
+class _ChrootRandomAccessFile with ForwardingRandomAccessFile {
+  _ChrootRandomAccessFile(this.path, this.delegate);
+
+  @override
+  final io.RandomAccessFile delegate;
+
+  @override
+  final String path;
+}
diff --git a/packages/file/lib/src/forwarding.dart b/packages/file/lib/src/forwarding.dart
index e413acf..9566a30 100644
--- a/packages/file/lib/src/forwarding.dart
+++ b/packages/file/lib/src/forwarding.dart
@@ -7,3 +7,4 @@
 export 'forwarding/forwarding_file_system.dart';
 export 'forwarding/forwarding_file_system_entity.dart';
 export 'forwarding/forwarding_link.dart';
+export 'forwarding/forwarding_random_access_file.dart';
diff --git a/packages/file/lib/src/forwarding/forwarding_random_access_file.dart b/packages/file/lib/src/forwarding/forwarding_random_access_file.dart
new file mode 100644
index 0000000..4d193f0
--- /dev/null
+++ b/packages/file/lib/src/forwarding/forwarding_random_access_file.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.10
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:file/src/io.dart' as io;
+import 'package:meta/meta.dart';
+
+/// A [RandomAccessFile] implementation that forwards all methods and properties
+/// to a delegate.
+abstract class ForwardingRandomAccessFile implements io.RandomAccessFile {
+  /// The entity to which this entity will forward all methods and properties.
+  @protected
+  io.RandomAccessFile get delegate;
+
+  @override
+  String get path => delegate.path;
+
+  @override
+  Future<void> close() => delegate.close();
+
+  @override
+  void closeSync() => delegate.closeSync();
+
+  @override
+  Future<io.RandomAccessFile> flush() async {
+    await delegate.flush();
+    return this;
+  }
+
+  @override
+  void flushSync() => delegate.flushSync();
+
+  @override
+  Future<int> length() => delegate.length();
+
+  @override
+  int lengthSync() => delegate.lengthSync();
+
+  @override
+  Future<io.RandomAccessFile> lock([
+    io.FileLock mode = io.FileLock.exclusive,
+    int start = 0,
+    int end = -1,
+  ]) async {
+    await delegate.lock(mode, start, end);
+    return this;
+  }
+
+  @override
+  void lockSync([
+    io.FileLock mode = io.FileLock.exclusive,
+    int start = 0,
+    int end = -1,
+  ]) =>
+      delegate.lockSync(mode, start, end);
+
+  @override
+  Future<int> position() => delegate.position();
+
+  @override
+  int positionSync() => delegate.positionSync();
+
+  @override
+  Future<Uint8List> read(int bytes) => delegate.read(bytes);
+
+  @override
+  Uint8List readSync(int bytes) => delegate.readSync(bytes);
+
+  @override
+  Future<int> readByte() => delegate.readByte();
+
+  @override
+  int readByteSync() => delegate.readByteSync();
+
+  @override
+  Future<int> readInto(List<int> buffer, [int start = 0, int? end]) =>
+      delegate.readInto(buffer, start, end);
+
+  @override
+  int readIntoSync(List<int> buffer, [int start = 0, int? end]) =>
+      delegate.readIntoSync(buffer, start, end);
+
+  @override
+  Future<io.RandomAccessFile> setPosition(int position) async {
+    await delegate.setPosition(position);
+    return this;
+  }
+
+  @override
+  void setPositionSync(int position) => delegate.setPositionSync(position);
+
+  @override
+  Future<io.RandomAccessFile> truncate(int length) async {
+    await delegate.truncate(length);
+    return this;
+  }
+
+  @override
+  void truncateSync(int length) => delegate.truncateSync(length);
+
+  @override
+  Future<io.RandomAccessFile> unlock([int start = 0, int end = -1]) async {
+    await delegate.unlock(start, end);
+    return this;
+  }
+
+  @override
+  void unlockSync([int start = 0, int end = -1]) =>
+      delegate.unlockSync(start, end);
+
+  @override
+  Future<io.RandomAccessFile> writeByte(int value) async {
+    await delegate.writeByte(value);
+    return this;
+  }
+
+  @override
+  int writeByteSync(int value) => delegate.writeByteSync(value);
+
+  @override
+  Future<io.RandomAccessFile> writeFrom(
+    List<int> buffer, [
+    int start = 0,
+    int? end,
+  ]) async {
+    await delegate.writeFrom(buffer, start, end);
+    return this;
+  }
+
+  @override
+  void writeFromSync(List<int> buffer, [int start = 0, int? end]) =>
+      delegate.writeFromSync(buffer, start, end);
+
+  @override
+  Future<io.RandomAccessFile> writeString(
+    String string, {
+    Encoding encoding = utf8,
+  }) async {
+    await delegate.writeString(string, encoding: encoding);
+    return this;
+  }
+
+  @override
+  void writeStringSync(String string, {Encoding encoding = utf8}) =>
+      delegate.writeStringSync(string, encoding: encoding);
+}