blob: 90ba9856005fc463a98c4609c28a8d3a0fe0fef4 [file] [log] [blame]
// Copyright (c) 2022, 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.
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'src/mmap_impl.dart';
/// Whether memory mapping files is supported.
///
/// Currently we only support Linux.
final bool supportsMMap = finalizerCode[Abi.current()] != null;
class MemoryMappedFile {
/// The memory mapped region of the address space as bytes.
///
/// The [Uint8List]'s length is a multiple of page size and as such may be
/// larger than the file length. The extra bytes are guaranteed to be zero.
///
/// Once this list becomes unreachable the underlying mapping will be freed &
/// file descriptor will be closed.
final Uint8List mappedBytes;
/// The length of the file that was mapped.
final int fileLength;
MemoryMappedFile(this.mappedBytes, this.fileLength);
/// Whether the [mappedBytes] contain extra zeros after the file content.
bool get hasZeroPadding => (fileLength % kPageSize) != 0;
/// The bytes representing the file.
Uint8List get fileBytes => Uint8List.sublistView(mappedBytes, 0, fileLength);
/// The bytes representing the file plus an extra zero byte.
Uint8List get fileBytesZeroTerminated {
if (!hasZeroPadding) throw 'The mapped file is page aligned.';
return Uint8List.sublistView(mappedBytes, 0, fileLength + 1);
}
}
/// Maps the given [filename] into the virtual address space.
///
/// Notice this only works on platforms where [supportsMMap] returns `true`.
/// Notice that files of length 0 cannot be mapped.
MemoryMappedFile mmapFile(String filename) {
if (!supportsMMap) throw 'MMap not supported';
final Pointer<Utf8> cfilename = filename.toNativeUtf8();
final int fd = open(cfilename, 0);
malloc.free(cfilename);
if (fd == 0) throw 'failed to open';
try {
final int length = File(filename).lengthSync();
final int lengthRoundedUp = (length + kPageSize - 1) & ~(kPageSize - 1);
final Pointer<Uint8> result =
mmap(nullptr, lengthRoundedUp, kProtRead, kMapPrivate, fd, 0);
if (result.address == kMapFailed) throw 'failed to map';
try {
final Uint8List bytes = toExternalDataWithFinalizer(
result, lengthRoundedUp, lengthRoundedUp, fd);
return MemoryMappedFile(bytes, length);
} catch (_) {
munmap(result, lengthRoundedUp);
rethrow;
}
} catch (e) {
close(fd);
rethrow;
}
}
Uint8List mmapOrReadFileSync(String filename) {
if (supportsMMap) {
try {
return mmapFile(filename).fileBytes;
} catch (_) {}
}
return File(filename).readAsBytesSync();
}