diff --git a/pkgs/unix_api/README.md b/pkgs/unix_api/README.md index b26155de..f9aef667 100644 --- a/pkgs/unix_api/README.md +++ b/pkgs/unix_api/README.md @@ -3,9 +3,11 @@ This package provides **experimental** bindings to POSIX APIs e.g. `open`, ## Why have another POSIX API implementation for Dart? -Thare are two existing packages that provide POSIX API bindings for Dart: -1. [`package:posix`](https://pub.dev/packages/posix) -2. [`package:stdlibc`](https://pub.dev/packages/stdlibc) +There are two existing packages that provide POSIX API bindings for Dart: +1. [`package:posix`] +2. [`package:stdlibc`] + +Both are excellent and offer easier-to-use APIs than `package:unix_api`. `package:unix_api` requires a native tool chain and has a small amount of native code that cannot be tree shaken away. In exchange, it works on all @@ -17,21 +19,22 @@ API calls as part of JIT compilation then the `errno` exported by `package:unix_api` is not affected (see the Dart SDK issue [Support for capturing errno across calls](https://github.com/dart-lang/sdk/issues/38832)). -| Package | Required Tools | Reliable `errno` | Supported Platforms | Fixed Disk Usage | -| :--- | :-------------- | :-------------- | :-------------------------------------- | :-------------- | -| `posix` | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB | -| `stdlibc` | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB | -| `unix_api` | Dart, C compiler | Yes | Android (x64, arm32, arm64), iOS (arm64), Linux (x64, arm64), macOS (x64, arm64) | ~60 KiB | +| Package | Required Tools | Reliable `errno` | Supported Platforms | Fixed Disk Usage | +| :--- | :-------------- | :-------------- | :-------------------------------------- | :-------------- | +| [`package:posix`] | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB | +| [`package:stdlibc`] | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB | +| `package:unix_api` | Dart, C compiler | Yes | Android (x64, arm32, arm64), iOS (arm64), Linux (x64, arm64), macOS (x64, arm64) | ~60 KiB | ## Design The POSIX API is a defined in terms of source, not object compatibility. -For example, glibc defines `stat` as: +For example, glibc defines `stat` using a macro: `#define stat(fname, buf) __xstat (_STAT_VER, fname, buf)` -So running `ffigen` on `sys/stat.h` will not produce an entry for `stat`. +So using [`package:ffigen`] to generate Dart bindings for `sys/stat.h` will not +produce an entry for `stat`. libc may also reorder `struct` fields across architectures, add extra fields, etc. For example, the glibc definition of `struct stat` starts @@ -49,23 +52,60 @@ struct stat #else ``` +When using [`package:ffigen`] to generate bindings for such code, separate +bindings must be generated for every combination of platform (e.g. +Android) and architecture (e.g. arm64). `package:unix_api` works around this problem by defining a native (C) function for every POSIX function. The native function just calls the corresponding -POSIX function. For example: +POSIX function while preserving `errno` by passing a reference to it explicitly. +For example: ```c -int libc_shim_rename(const char *old, const char *newy) { - return rename(old, newy); +int libc_shim_rename(const char * arg0, const char * arg1, int * err) { + int r; + errno = *err; + r = rename(arg0, arg1); + *err = errno; + return r; } ``` This allows the platforms C compiler to deal with macro expansions, -platform-specific struct layout, etc. +platform-specific struct layout, etc. [`package:hooks'] is used to +transparently compile the C code on the developer's behalf. + +Then `package:unix_api` uses `package:ffigen` to generate Dart bindings to +these functions. For example: + +```dart +// ffigen'd bindings +@ffi.Native< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) +>() +external int libc_shim_rename( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer arg2, +); +``` -Then `package:unix_api` uses `package:ffigen` to provide Dart bindings to -these functions. +And finally, `package:unix_api` provides a function that provides the public +interface. For example: +```dart +/// Renames a file. +/// +/// See the [POSIX specification for `rename`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html). +int rename(ffi.Pointer arg0, ffi.Pointer arg1) => + libc_shim_rename(arg0, arg1, errnoPtr); +``` + +`errno` is implemented [locally in the package](lib/src/errno.dart). ## Status: Experimental @@ -81,3 +121,8 @@ much higher expected rate of API and breaking changes. Your feedback is valuable and will help us evolve this package. For general feedback, suggestions, and comments, please file an issue in the [bug tracker](https://github.com/dart-lang/labs/issues). + +[`package:ffigen`]: https://pub.dev/packages/ffigen +[`package:hooks`]: https://pub.dev/packages/hooks +[`package:posix`]: https://pub.dev/packages/posix +[`package:stdlibc`]: https://pub.dev/packages/stdlibc diff --git a/pkgs/unix_api/lib/src/errno.dart b/pkgs/unix_api/lib/src/errno.dart index cbc492b2..4da95197 100644 --- a/pkgs/unix_api/lib/src/errno.dart +++ b/pkgs/unix_api/lib/src/errno.dart @@ -12,7 +12,7 @@ import 'package:ffi/ffi.dart'; /// /// Another approach would be to just track the value of `errno` and create a /// pointer only as needed. But that would means doing a memory allocation for -/// any POSIX call. +/// every POSIX call. class _Errno implements ffi.Finalizable { static final _finalizer = ffi.NativeFinalizer(malloc.nativeFree); ffi.Pointer errnoPtr; @@ -24,3 +24,9 @@ class _Errno implements ffi.Finalizable { final _errno = _Errno(); ffi.Pointer get errnoPtr => _errno.errnoPtr; + +/// The code that indicates the reason for a failed function call. +/// +/// See the [POSIX specification for `errno`](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/errno.h.html). +int get errno => errnoPtr.value; +set errno(int err) => errnoPtr.value = err; diff --git a/pkgs/unix_api/lib/unix_api.dart b/pkgs/unix_api/lib/unix_api.dart index a68c8c98..faa643e2 100644 --- a/pkgs/unix_api/lib/unix_api.dart +++ b/pkgs/unix_api/lib/unix_api.dart @@ -2,17 +2,8 @@ // 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 'src/errno.dart'; - export 'src/bespoke.dart'; export 'src/constants.g.dart'; +export 'src/errno.dart' show errno; export 'src/functions.g.dart'; export 'src/handwritten_constants.dart'; - -/// The code that indicates the reason for a failed function call. -/// -/// See the [POSIX specification for `errno`](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/errno.h.html). -int get errno => errnoPtr.value; -set errno(int err) => errnoPtr.value = err;