Skip to content

Commit 191512b

Browse files
authored
docs(unix_api): Add more details on the implementation (#294)
1 parent c378a7a commit 191512b

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

pkgs/unix_api/README.md

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ This package provides **experimental** bindings to POSIX APIs e.g. `open`,
33

44
## Why have another POSIX API implementation for Dart?
55

6-
Thare are two existing packages that provide POSIX API bindings for Dart:
7-
1. [`package:posix`](https://pub.dev/packages/posix)
8-
2. [`package:stdlibc`](https://pub.dev/packages/stdlibc)
6+
There are two existing packages that provide POSIX API bindings for Dart:
7+
1. [`package:posix`]
8+
2. [`package:stdlibc`]
9+
10+
Both are excellent and offer easier-to-use APIs than `package:unix_api`.
911

1012
`package:unix_api` requires a native tool chain and has a small amount of
1113
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
1719
`package:unix_api` is not affected (see the Dart SDK issue
1820
[Support for capturing errno across calls](https://github.com/dart-lang/sdk/issues/38832)).
1921

20-
| Package | Required Tools | Reliable `errno` | Supported Platforms | Fixed Disk Usage |
21-
| :--- | :-------------- | :-------------- | :-------------------------------------- | :-------------- |
22-
| `posix` | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB |
23-
| `stdlibc` | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB |
24-
| `unix_api` | Dart, C compiler | Yes | Android (x64, arm32, arm64), iOS (arm64), Linux (x64, arm64), macOS (x64, arm64) | ~60 KiB |
22+
| Package | Required Tools | Reliable `errno` | Supported Platforms | Fixed Disk Usage |
23+
| :--- | :-------------- | :-------------- | :-------------------------------------- | :-------------- |
24+
| [`package:posix`] | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB |
25+
| [`package:stdlibc`] | Dart | No | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB |
26+
| `package:unix_api` | Dart, C compiler | Yes | Android (x64, arm32, arm64), iOS (arm64), Linux (x64, arm64), macOS (x64, arm64) | ~60 KiB |
2527

2628
## Design
2729

2830
The POSIX API is a defined in terms of source, not object compatibility.
2931

30-
For example, glibc defines `stat` as:
32+
For example, glibc defines `stat` using a macro:
3133

3234
`#define stat(fname, buf) __xstat (_STAT_VER, fname, buf)`
3335

34-
So running `ffigen` on `sys/stat.h` will not produce an entry for `stat`.
36+
So using [`package:ffigen`] to generate Dart bindings for `sys/stat.h` will not
37+
produce an entry for `stat`.
3538

3639
libc may also reorder `struct` fields across architectures, add extra
3740
fields, etc. For example, the glibc definition of `struct stat` starts
@@ -49,23 +52,60 @@ struct stat
4952
#else
5053
```
5154
55+
When using [`package:ffigen`] to generate bindings for such code, separate
56+
bindings must be generated for every combination of platform (e.g.
57+
Android) and architecture (e.g. arm64).
5258
5359
`package:unix_api` works around this problem by defining a native (C) function
5460
for every POSIX function. The native function just calls the corresponding
55-
POSIX function. For example:
61+
POSIX function while preserving `errno` by passing a reference to it explicitly.
62+
For example:
5663
5764
```c
58-
int libc_shim_rename(const char *old, const char *newy) {
59-
return rename(old, newy);
65+
int libc_shim_rename(const char * arg0, const char * arg1, int * err) {
66+
int r;
67+
errno = *err;
68+
r = rename(arg0, arg1);
69+
*err = errno;
70+
return r;
6071
}
6172
```
6273

6374
This allows the platforms C compiler to deal with macro expansions,
64-
platform-specific struct layout, etc.
75+
platform-specific struct layout, etc. [`package:hooks'] is used to
76+
transparently compile the C code on the developer's behalf.
77+
78+
Then `package:unix_api` uses `package:ffigen` to generate Dart bindings to
79+
these functions. For example:
80+
81+
```dart
82+
// ffigen'd bindings
83+
@ffi.Native<
84+
ffi.Int Function(
85+
ffi.Pointer<ffi.Char>,
86+
ffi.Pointer<ffi.Char>,
87+
ffi.Pointer<ffi.Int>,
88+
)
89+
>()
90+
external int libc_shim_rename(
91+
ffi.Pointer<ffi.Char> arg0,
92+
ffi.Pointer<ffi.Char> arg1,
93+
ffi.Pointer<ffi.Int> arg2,
94+
);
95+
```
6596

66-
Then `package:unix_api` uses `package:ffigen` to provide Dart bindings to
67-
these functions.
97+
And finally, `package:unix_api` provides a function that provides the public
98+
interface. For example:
6899

100+
```dart
101+
/// Renames a file.
102+
///
103+
/// See the [POSIX specification for `rename`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html).
104+
int rename(ffi.Pointer<ffi.Char> arg0, ffi.Pointer<ffi.Char> arg1) =>
105+
libc_shim_rename(arg0, arg1, errnoPtr);
106+
```
107+
108+
`errno` is implemented [locally in the package](lib/src/errno.dart).
69109

70110
## Status: Experimental
71111

@@ -81,3 +121,8 @@ much higher expected rate of API and breaking changes.
81121
Your feedback is valuable and will help us evolve this package. For general
82122
feedback, suggestions, and comments, please file an issue in the
83123
[bug tracker](https://github.com/dart-lang/labs/issues).
124+
125+
[`package:ffigen`]: https://pub.dev/packages/ffigen
126+
[`package:hooks`]: https://pub.dev/packages/hooks
127+
[`package:posix`]: https://pub.dev/packages/posix
128+
[`package:stdlibc`]: https://pub.dev/packages/stdlibc

pkgs/unix_api/lib/src/errno.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import 'package:ffi/ffi.dart';
1212
///
1313
/// Another approach would be to just track the value of `errno` and create a
1414
/// pointer only as needed. But that would means doing a memory allocation for
15-
/// any POSIX call.
15+
/// every POSIX call.
1616
class _Errno implements ffi.Finalizable {
1717
static final _finalizer = ffi.NativeFinalizer(malloc.nativeFree);
1818
ffi.Pointer<ffi.Int> errnoPtr;
@@ -24,3 +24,9 @@ class _Errno implements ffi.Finalizable {
2424

2525
final _errno = _Errno();
2626
ffi.Pointer<ffi.Int> get errnoPtr => _errno.errnoPtr;
27+
28+
/// The code that indicates the reason for a failed function call.
29+
///
30+
/// See the [POSIX specification for `errno`](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/errno.h.html).
31+
int get errno => errnoPtr.value;
32+
set errno(int err) => errnoPtr.value = err;

pkgs/unix_api/lib/unix_api.dart

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:ffi';
6-
7-
import 'src/errno.dart';
8-
95
export 'src/bespoke.dart';
106
export 'src/constants.g.dart';
7+
export 'src/errno.dart' show errno;
118
export 'src/functions.g.dart';
129
export 'src/handwritten_constants.dart';
13-
14-
/// The code that indicates the reason for a failed function call.
15-
///
16-
/// See the [POSIX specification for `errno`](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/errno.h.html).
17-
int get errno => errnoPtr.value;
18-
set errno(int err) => errnoPtr.value = err;

0 commit comments

Comments
 (0)