Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 61 additions & 16 deletions pkgs/unix_api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand 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
Expand All @@ -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.Char>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Int>,
)
>()
external int libc_shim_rename(
ffi.Pointer<ffi.Char> arg0,
ffi.Pointer<ffi.Char> arg1,
ffi.Pointer<ffi.Int> 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<ffi.Char> arg0, ffi.Pointer<ffi.Char> arg1) =>
libc_shim_rename(arg0, arg1, errnoPtr);
```

`errno` is implemented [locally in the package](lib/src/errno.dart).

## Status: Experimental

Expand All @@ -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
8 changes: 7 additions & 1 deletion pkgs/unix_api/lib/src/errno.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ffi.Int> errnoPtr;
Expand All @@ -24,3 +24,9 @@ class _Errno implements ffi.Finalizable {

final _errno = _Errno();
ffi.Pointer<ffi.Int> 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;
11 changes: 1 addition & 10 deletions pkgs/unix_api/lib/unix_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading