diff --git a/.github/workflows/io_file.yml b/.github/workflows/io_file.yml index 9fe0639e9..8302cc355 100644 --- a/.github/workflows/io_file.yml +++ b/.github/workflows/io_file.yml @@ -33,23 +33,6 @@ jobs: - run: dart analyze --fatal-infos - run: dart format --output=none --set-exit-if-changed . - check-generation: - # Verify that the generated code is up-to-date. Every platform/arch should - # generate the same API bindings. - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c - with: - sdk: dev - - run: dart pub get - - run: dart --enable-experiment=native-assets run tool/generate.dart - - run: git diff --exit-code - desktop-vm-test: strategy: fail-fast: false diff --git a/.github/workflows/unix_api.yml b/.github/workflows/unix_api.yml new file mode 100644 index 000000000..970e5213e --- /dev/null +++ b/.github/workflows/unix_api.yml @@ -0,0 +1,65 @@ +name: package:unix_api + +permissions: read-all + +on: + # Run CI on pushes to the main branch, and on PRs against main. + push: + branches: [ main ] + paths: + - '.github/workflows/unix_api.yml' + - 'pkgs/unix_api/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/unix_api.yml' + - 'pkgs/unix_api/**' + schedule: + - cron: '0 0 * * 0' # weekly + +defaults: + run: + working-directory: pkgs/unix_api + +jobs: + analyze_and_format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c + with: + sdk: dev + - run: dart pub get + - run: dart analyze --fatal-infos + - run: dart format --output=none --set-exit-if-changed . + + check-generation: + # Verify that the generated code is up-to-date. Every platform/arch should + # generate the same API bindings. + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c + with: + sdk: dev + - run: dart pub get + - run: dart --enable-experiment=native-assets run tool/generate.dart + - run: git diff --exit-code + + desktop-vm-test: + strategy: + fail-fast: false + matrix: + sdk: [dev] + os: [macos-latest, ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c + with: + sdk: ${{ matrix.sdk }} + - run: dart --enable-experiment=native-assets test --reporter=failures-only --test-randomize-ordering-seed=random --platform vm diff --git a/pkgs/io_file/lib/src/libc.dart b/pkgs/io_file/lib/src/libc.dart deleted file mode 100644 index 9c1ca96fd..000000000 --- a/pkgs/io_file/lib/src/libc.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'libc_bindings.g.dart' as libc; - -export 'constants.g.dart'; -export 'libc_bindings.g.dart' hide errno, seterrno; - -int get errno => libc.errno(); -set errno(int err) => libc.seterrno(err); diff --git a/pkgs/io_file/lib/src/vm_posix_file_system.dart b/pkgs/io_file/lib/src/vm_posix_file_system.dart index 48f9565db..692ea847a 100644 --- a/pkgs/io_file/lib/src/vm_posix_file_system.dart +++ b/pkgs/io_file/lib/src/vm_posix_file_system.dart @@ -10,11 +10,11 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' as ffi; import 'package:path/path.dart' as p; +import 'package:unix_api/unix_api.dart' as libc; import 'exceptions.dart'; import 'file_system.dart'; import 'internal_constants.dart'; -import 'libc.dart' as libc; /// The default `mode` to use with `open` calls that may create a file. const _defaultMode = 438; // => 0666 => rw-rw-rw- diff --git a/pkgs/io_file/mobile_test/pubspec.yaml b/pkgs/io_file/mobile_test/pubspec.yaml index b718c3bba..6d9cdb597 100644 --- a/pkgs/io_file/mobile_test/pubspec.yaml +++ b/pkgs/io_file/mobile_test/pubspec.yaml @@ -37,3 +37,4 @@ dev_dependencies: url: https://github.com/canonical/stdlibc.dart.git test: ^1.24.0 uuid: ^4.5.1 + diff --git a/pkgs/io_file/pubspec.yaml b/pkgs/io_file/pubspec.yaml index 4752ebf09..490fe6e4d 100644 --- a/pkgs/io_file/pubspec.yaml +++ b/pkgs/io_file/pubspec.yaml @@ -11,10 +11,12 @@ dependencies: code_assets: ^0.19.0 ffi: ^2.1.4 hooks: ^0.19.0 - logging: ^1.3.0 meta: ^1.16.0 native_toolchain_c: ^0.16.0 path: ^1.9.1 + unix_api: + # Change this to a released version when this package is published. + path: ../unix_api win32: ^5.14.0 dev_dependencies: diff --git a/pkgs/io_file/test/copy_file_test.dart b/pkgs/io_file/test/copy_file_test.dart index 79b7aa93a..438ec04c9 100644 --- a/pkgs/io_file/test/copy_file_test.dart +++ b/pkgs/io_file/test/copy_file_test.dart @@ -12,10 +12,10 @@ import 'package:ffi/ffi.dart'; import 'package:io_file/io_file.dart'; import 'package:io_file/posix_file_system.dart'; import 'package:io_file/src/internal_constants.dart' show blockSize; -import 'package:io_file/src/libc.dart' as libc; import 'package:io_file/src/vm_windows_file_system.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; +import 'package:unix_api/unix_api.dart' as libc; import 'package:win32/win32.dart' as win32; import 'errors.dart' as errors; diff --git a/pkgs/io_file/test/remove_directory_tree_test.dart b/pkgs/io_file/test/remove_directory_tree_test.dart index ba38f5242..85ef2fc40 100644 --- a/pkgs/io_file/test/remove_directory_tree_test.dart +++ b/pkgs/io_file/test/remove_directory_tree_test.dart @@ -9,10 +9,11 @@ import 'dart:io' as io; import 'package:ffi/ffi.dart'; import 'package:io_file/io_file.dart'; -import 'package:io_file/src/libc.dart' as libc; import 'package:path/path.dart' as p; import 'package:test/test.dart'; +import 'package:unix_api/unix_api.dart' as libc; import 'package:win32/win32.dart' as win32; + import 'errors.dart' as errors; import 'test_utils.dart'; diff --git a/pkgs/io_file/.gitattributes b/pkgs/unix_api/.gitattributes similarity index 100% rename from pkgs/io_file/.gitattributes rename to pkgs/unix_api/.gitattributes diff --git a/pkgs/unix_api/.gitignore b/pkgs/unix_api/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/pkgs/unix_api/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/pkgs/unix_api/CHANGELOG.md b/pkgs/unix_api/CHANGELOG.md new file mode 100644 index 000000000..1b9970e51 --- /dev/null +++ b/pkgs/unix_api/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0-wip + +- Initial version. diff --git a/pkgs/unix_api/README.md b/pkgs/unix_api/README.md new file mode 100644 index 000000000..b4b52ef7c --- /dev/null +++ b/pkgs/unix_api/README.md @@ -0,0 +1,77 @@ +This package provides **experimental** bindings to POSIX APIs e.g. `open`, +`close`. + +## 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) + +`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 +POSIX platforms that Dart supports. + +| Package | Required Tools | Supported Platforms | Fixed Disk Usage | +| :--- | :-------------- | :------------------------------------ | :-------------- | +| `posix` | Dart | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB | +| `stdlibc` | Dart | iOS (arm64), Linux (x64), macOS (arm64) | 0 KiB | +| `unix_api` | Dart, C compiler | 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: + +`#define stat(fname, buf) __xstat (_STAT_VER, fname, buf)` + +So running `ffigen` on `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 +with: + +```c +struct stat +{ + __dev_t st_dev; /* Device. */ +#ifndef __x86_64__ + unsigned short int __pad1; +#endif +#if defined __x86_64__ || !defined __USE_FILE_OFFSET64 + __ino_t st_ino; /* File serial number. */ +#else +``` + + +`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: + +```c +int libc_shim_rename(const char *old, const char *newy) { + return rename(old, newy); +} +``` + +This allows the platforms C compiler to deal with macro expansions, +platform-specific struct layout, etc. + +Then `package:unix_api` uses `package:ffigen` to provide Dart bindings to +these functions. + + +## Status: Experimental + +**NOTE**: This package is currently experimental and published under the +[labs.dart.dev](https://dart.dev/dart-team-packages) pub publisher in order to +solicit feedback. + +For packages in the labs.dart.dev publisher we generally plan to either graduate +the package into a supported publisher (dart.dev, tools.dart.dev) after a period +of feedback and iteration, or discontinue the package. These packages have a +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). diff --git a/pkgs/io_file/constants-ffigen.yaml b/pkgs/unix_api/constants-ffigen.yaml similarity index 93% rename from pkgs/io_file/constants-ffigen.yaml rename to pkgs/unix_api/constants-ffigen.yaml index 1ab763073..82efa618c 100644 --- a/pkgs/io_file/constants-ffigen.yaml +++ b/pkgs/unix_api/constants-ffigen.yaml @@ -15,7 +15,7 @@ comments: style: any length: full ffi-native: - asset-id: package:io_file/libc_shim + asset-id: package:unix_api/libc_shim structs: rename: 'libc_shim_(.*)': '$1' diff --git a/pkgs/io_file/constants.json b/pkgs/unix_api/constants.json similarity index 100% rename from pkgs/io_file/constants.json rename to pkgs/unix_api/constants.json diff --git a/pkgs/unix_api/example/unix_api_example.dart b/pkgs/unix_api/example/unix_api_example.dart new file mode 100644 index 000000000..40c6934e4 --- /dev/null +++ b/pkgs/unix_api/example/unix_api_example.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2025, 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 'package:unix_api/unix_api.dart'; + +void main() { + print('I am ${getpid()} and my parent is ${getppid()}'); +} diff --git a/pkgs/io_file/ffigen.yaml b/pkgs/unix_api/ffigen.yaml similarity index 93% rename from pkgs/io_file/ffigen.yaml rename to pkgs/unix_api/ffigen.yaml index 2d83623ce..a6789d097 100644 --- a/pkgs/io_file/ffigen.yaml +++ b/pkgs/unix_api/ffigen.yaml @@ -17,7 +17,7 @@ comments: style: any length: full ffi-native: - asset-id: package:io_file/libc_shim + asset-id: package:unix_api/libc_shim structs: rename: 'libc_shim_(.*)': '$1' diff --git a/pkgs/io_file/hook/build.dart b/pkgs/unix_api/hook/build.dart similarity index 86% rename from pkgs/io_file/hook/build.dart rename to pkgs/unix_api/hook/build.dart index ca3865983..534cdba36 100644 --- a/pkgs/io_file/hook/build.dart +++ b/pkgs/unix_api/hook/build.dart @@ -19,10 +19,9 @@ void main(List args) async { ).run( input: input, output: output, - logger: - Logger('') - ..level = Level.ALL - ..onRecord.listen((record) => print(record.message)), + logger: Logger('') + ..level = Level.ALL + ..onRecord.listen((record) => print(record.message)), ); }); } diff --git a/pkgs/unix_api/lib/src/constant_bindings.g.dart b/pkgs/unix_api/lib/src/constant_bindings.g.dart new file mode 100644 index 000000000..3368cb9c7 --- /dev/null +++ b/pkgs/unix_api/lib/src/constant_bindings.g.dart @@ -0,0 +1,193 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +@ffi.DefaultAsset('package:unix_api/libc_shim') +library; + +import 'dart:ffi' as ffi; + +@ffi.Native(symbol: 'libc_shim_get_DT_BLK') +external int get_DT_BLK(); + +@ffi.Native(symbol: 'libc_shim_get_DT_CHR') +external int get_DT_CHR(); + +@ffi.Native(symbol: 'libc_shim_get_DT_DIR') +external int get_DT_DIR(); + +@ffi.Native(symbol: 'libc_shim_get_DT_FIFO') +external int get_DT_FIFO(); + +@ffi.Native(symbol: 'libc_shim_get_DT_LNK') +external int get_DT_LNK(); + +@ffi.Native(symbol: 'libc_shim_get_DT_REG') +external int get_DT_REG(); + +@ffi.Native(symbol: 'libc_shim_get_DT_SOCK') +external int get_DT_SOCK(); + +@ffi.Native(symbol: 'libc_shim_get_DT_UNKNOWN') +external int get_DT_UNKNOWN(); + +@ffi.Native(symbol: 'libc_shim_get_EACCES') +external int get_EACCES(); + +@ffi.Native(symbol: 'libc_shim_get_EEXIST') +external int get_EEXIST(); + +@ffi.Native(symbol: 'libc_shim_get_EINTR') +external int get_EINTR(); + +@ffi.Native(symbol: 'libc_shim_get_EMFILE') +external int get_EMFILE(); + +@ffi.Native(symbol: 'libc_shim_get_ENOENT') +external int get_ENOENT(); + +@ffi.Native(symbol: 'libc_shim_get_ENOSPC') +external int get_ENOSPC(); + +@ffi.Native(symbol: 'libc_shim_get_ENOTDIR') +external int get_ENOTDIR(); + +@ffi.Native(symbol: 'libc_shim_get_ENOTEMPTY') +external int get_ENOTEMPTY(); + +@ffi.Native(symbol: 'libc_shim_get_EPERM') +external int get_EPERM(); + +@ffi.Native(symbol: 'libc_shim_get_AT_FDCWD') +external int get_AT_FDCWD(); + +@ffi.Native(symbol: 'libc_shim_get_AT_REMOVEDIR') +external int get_AT_REMOVEDIR(); + +@ffi.Native(symbol: 'libc_shim_get_O_APPEND') +external int get_O_APPEND(); + +@ffi.Native(symbol: 'libc_shim_get_O_CLOEXEC') +external int get_O_CLOEXEC(); + +@ffi.Native(symbol: 'libc_shim_get_O_CREAT') +external int get_O_CREAT(); + +@ffi.Native(symbol: 'libc_shim_get_O_DIRECTORY') +external int get_O_DIRECTORY(); + +@ffi.Native(symbol: 'libc_shim_get_O_EXCL') +external int get_O_EXCL(); + +@ffi.Native(symbol: 'libc_shim_get_O_RDONLY') +external int get_O_RDONLY(); + +@ffi.Native(symbol: 'libc_shim_get_O_TRUNC') +external int get_O_TRUNC(); + +@ffi.Native(symbol: 'libc_shim_get_O_WRONLY') +external int get_O_WRONLY(); + +@ffi.Native(symbol: 'libc_shim_get_PATH_MAX') +external int get_PATH_MAX(); + +@ffi.Native(symbol: 'libc_shim_get_AT_SYMLINK_NOFOLLOW') +external int get_AT_SYMLINK_NOFOLLOW(); + +@ffi.Native(symbol: 'libc_shim_get_AT_RESOLVE_BENEATH') +external int get_AT_RESOLVE_BENEATH(); + +@ffi.Native(symbol: 'libc_shim_get_AT_EMPTY_PATH') +external int get_AT_EMPTY_PATH(); + +@ffi.Native(symbol: 'libc_shim_get_S_IEXEC') +external int get_S_IEXEC(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFBLK') +external int get_S_IFBLK(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFCHR') +external int get_S_IFCHR(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFDIR') +external int get_S_IFDIR(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFIFO') +external int get_S_IFIFO(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFLNK') +external int get_S_IFLNK(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFMT') +external int get_S_IFMT(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFREG') +external int get_S_IFREG(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFSOCK') +external int get_S_IFSOCK(); + +@ffi.Native(symbol: 'libc_shim_get_S_IFWHT') +external int get_S_IFWHT(); + +@ffi.Native(symbol: 'libc_shim_get_S_IREAD') +external int get_S_IREAD(); + +@ffi.Native(symbol: 'libc_shim_get_S_IRGRP') +external int get_S_IRGRP(); + +@ffi.Native(symbol: 'libc_shim_get_S_IROTH') +external int get_S_IROTH(); + +@ffi.Native(symbol: 'libc_shim_get_S_IRUSR') +external int get_S_IRUSR(); + +@ffi.Native(symbol: 'libc_shim_get_S_IRWXG') +external int get_S_IRWXG(); + +@ffi.Native(symbol: 'libc_shim_get_S_IRWXO') +external int get_S_IRWXO(); + +@ffi.Native(symbol: 'libc_shim_get_S_IRWXU') +external int get_S_IRWXU(); + +@ffi.Native(symbol: 'libc_shim_get_S_ISGID') +external int get_S_ISGID(); + +@ffi.Native(symbol: 'libc_shim_get_S_ISTXT') +external int get_S_ISTXT(); + +@ffi.Native(symbol: 'libc_shim_get_S_ISUID') +external int get_S_ISUID(); + +@ffi.Native(symbol: 'libc_shim_get_S_ISVTX') +external int get_S_ISVTX(); + +@ffi.Native(symbol: 'libc_shim_get_S_IWGRP') +external int get_S_IWGRP(); + +@ffi.Native(symbol: 'libc_shim_get_S_IWOTH') +external int get_S_IWOTH(); + +@ffi.Native(symbol: 'libc_shim_get_S_IWRITE') +external int get_S_IWRITE(); + +@ffi.Native(symbol: 'libc_shim_get_S_IWUSR') +external int get_S_IWUSR(); + +@ffi.Native(symbol: 'libc_shim_get_S_IXGRP') +external int get_S_IXGRP(); + +@ffi.Native(symbol: 'libc_shim_get_S_IXOTH') +external int get_S_IXOTH(); + +@ffi.Native(symbol: 'libc_shim_get_S_IXUSR') +external int get_S_IXUSR(); + +@ffi.Native(symbol: 'libc_shim_get_UF_APPEND') +external int get_UF_APPEND(); + +@ffi.Native(symbol: 'libc_shim_get_UF_HIDDEN') +external int get_UF_HIDDEN(); + +const int libc_shim_UNDEFINED = 5635263260456932693; diff --git a/pkgs/unix_api/lib/src/constants.g.dart b/pkgs/unix_api/lib/src/constants.g.dart new file mode 100644 index 000000000..3196b33ca --- /dev/null +++ b/pkgs/unix_api/lib/src/constants.g.dart @@ -0,0 +1,555 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Regenerate with `dart run tool/build_constants.dart`. + +// ignore_for_file: non_constant_identifier_names + +import 'constant_bindings.g.dart'; + +int get DT_BLK { + final v = get_DT_BLK(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_BLK'); + } else { + return v; + } +} + +int get DT_CHR { + final v = get_DT_CHR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_CHR'); + } else { + return v; + } +} + +int get DT_DIR { + final v = get_DT_DIR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_DIR'); + } else { + return v; + } +} + +int get DT_FIFO { + final v = get_DT_FIFO(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_FIFO'); + } else { + return v; + } +} + +int get DT_LNK { + final v = get_DT_LNK(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_LNK'); + } else { + return v; + } +} + +int get DT_REG { + final v = get_DT_REG(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_REG'); + } else { + return v; + } +} + +int get DT_SOCK { + final v = get_DT_SOCK(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_SOCK'); + } else { + return v; + } +} + +int get DT_UNKNOWN { + final v = get_DT_UNKNOWN(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('DT_UNKNOWN'); + } else { + return v; + } +} + +int get EACCES { + final v = get_EACCES(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('EACCES'); + } else { + return v; + } +} + +int get EEXIST { + final v = get_EEXIST(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('EEXIST'); + } else { + return v; + } +} + +int get EINTR { + final v = get_EINTR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('EINTR'); + } else { + return v; + } +} + +int get EMFILE { + final v = get_EMFILE(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('EMFILE'); + } else { + return v; + } +} + +int get ENOENT { + final v = get_ENOENT(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('ENOENT'); + } else { + return v; + } +} + +int get ENOSPC { + final v = get_ENOSPC(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('ENOSPC'); + } else { + return v; + } +} + +int get ENOTDIR { + final v = get_ENOTDIR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('ENOTDIR'); + } else { + return v; + } +} + +int get ENOTEMPTY { + final v = get_ENOTEMPTY(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('ENOTEMPTY'); + } else { + return v; + } +} + +int get EPERM { + final v = get_EPERM(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('EPERM'); + } else { + return v; + } +} + +int get AT_FDCWD { + final v = get_AT_FDCWD(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('AT_FDCWD'); + } else { + return v; + } +} + +int get AT_REMOVEDIR { + final v = get_AT_REMOVEDIR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('AT_REMOVEDIR'); + } else { + return v; + } +} + +int get O_APPEND { + final v = get_O_APPEND(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_APPEND'); + } else { + return v; + } +} + +int get O_CLOEXEC { + final v = get_O_CLOEXEC(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_CLOEXEC'); + } else { + return v; + } +} + +int get O_CREAT { + final v = get_O_CREAT(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_CREAT'); + } else { + return v; + } +} + +int get O_DIRECTORY { + final v = get_O_DIRECTORY(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_DIRECTORY'); + } else { + return v; + } +} + +int get O_EXCL { + final v = get_O_EXCL(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_EXCL'); + } else { + return v; + } +} + +int get O_RDONLY { + final v = get_O_RDONLY(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_RDONLY'); + } else { + return v; + } +} + +int get O_TRUNC { + final v = get_O_TRUNC(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_TRUNC'); + } else { + return v; + } +} + +int get O_WRONLY { + final v = get_O_WRONLY(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('O_WRONLY'); + } else { + return v; + } +} + +int get PATH_MAX { + final v = get_PATH_MAX(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('PATH_MAX'); + } else { + return v; + } +} + +int get AT_SYMLINK_NOFOLLOW { + final v = get_AT_SYMLINK_NOFOLLOW(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('AT_SYMLINK_NOFOLLOW'); + } else { + return v; + } +} + +int get AT_RESOLVE_BENEATH { + final v = get_AT_RESOLVE_BENEATH(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('AT_RESOLVE_BENEATH'); + } else { + return v; + } +} + +int get AT_EMPTY_PATH { + final v = get_AT_EMPTY_PATH(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('AT_EMPTY_PATH'); + } else { + return v; + } +} + +int get S_IEXEC { + final v = get_S_IEXEC(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IEXEC'); + } else { + return v; + } +} + +int get S_IFBLK { + final v = get_S_IFBLK(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFBLK'); + } else { + return v; + } +} + +int get S_IFCHR { + final v = get_S_IFCHR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFCHR'); + } else { + return v; + } +} + +int get S_IFDIR { + final v = get_S_IFDIR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFDIR'); + } else { + return v; + } +} + +int get S_IFIFO { + final v = get_S_IFIFO(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFIFO'); + } else { + return v; + } +} + +int get S_IFLNK { + final v = get_S_IFLNK(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFLNK'); + } else { + return v; + } +} + +int get S_IFMT { + final v = get_S_IFMT(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFMT'); + } else { + return v; + } +} + +int get S_IFREG { + final v = get_S_IFREG(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFREG'); + } else { + return v; + } +} + +int get S_IFSOCK { + final v = get_S_IFSOCK(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFSOCK'); + } else { + return v; + } +} + +int get S_IFWHT { + final v = get_S_IFWHT(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IFWHT'); + } else { + return v; + } +} + +int get S_IREAD { + final v = get_S_IREAD(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IREAD'); + } else { + return v; + } +} + +int get S_IRGRP { + final v = get_S_IRGRP(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IRGRP'); + } else { + return v; + } +} + +int get S_IROTH { + final v = get_S_IROTH(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IROTH'); + } else { + return v; + } +} + +int get S_IRUSR { + final v = get_S_IRUSR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IRUSR'); + } else { + return v; + } +} + +int get S_IRWXG { + final v = get_S_IRWXG(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IRWXG'); + } else { + return v; + } +} + +int get S_IRWXO { + final v = get_S_IRWXO(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IRWXO'); + } else { + return v; + } +} + +int get S_IRWXU { + final v = get_S_IRWXU(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IRWXU'); + } else { + return v; + } +} + +int get S_ISGID { + final v = get_S_ISGID(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_ISGID'); + } else { + return v; + } +} + +int get S_ISTXT { + final v = get_S_ISTXT(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_ISTXT'); + } else { + return v; + } +} + +int get S_ISUID { + final v = get_S_ISUID(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_ISUID'); + } else { + return v; + } +} + +int get S_ISVTX { + final v = get_S_ISVTX(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_ISVTX'); + } else { + return v; + } +} + +int get S_IWGRP { + final v = get_S_IWGRP(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IWGRP'); + } else { + return v; + } +} + +int get S_IWOTH { + final v = get_S_IWOTH(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IWOTH'); + } else { + return v; + } +} + +int get S_IWRITE { + final v = get_S_IWRITE(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IWRITE'); + } else { + return v; + } +} + +int get S_IWUSR { + final v = get_S_IWUSR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IWUSR'); + } else { + return v; + } +} + +int get S_IXGRP { + final v = get_S_IXGRP(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IXGRP'); + } else { + return v; + } +} + +int get S_IXOTH { + final v = get_S_IXOTH(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IXOTH'); + } else { + return v; + } +} + +int get S_IXUSR { + final v = get_S_IXUSR(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('S_IXUSR'); + } else { + return v; + } +} + +int get UF_APPEND { + final v = get_UF_APPEND(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('UF_APPEND'); + } else { + return v; + } +} + +int get UF_HIDDEN { + final v = get_UF_HIDDEN(); + if (v == libc_shim_UNDEFINED) { + throw UnsupportedError('UF_HIDDEN'); + } else { + return v; + } +} diff --git a/pkgs/unix_api/lib/src/libc_bindings.g.dart b/pkgs/unix_api/lib/src/libc_bindings.g.dart new file mode 100644 index 000000000..98085bd81 --- /dev/null +++ b/pkgs/unix_api/lib/src/libc_bindings.g.dart @@ -0,0 +1,201 @@ +// ignore_for_file: type=lint, unused_field + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +@ffi.DefaultAsset('package:unix_api/libc_shim') +library; + +import 'dart:ffi' as ffi; + +/// Returns `libc_shim_dirent.d_name`. +/// +/// TODO(brianquinlan): Remove `libc_shim_d_name_ptr` when there is a fix for: +/// https://github.com/dart-lang/sdk/issues/41237 +@ffi.Native Function(ffi.Pointer)>( + symbol: 'libc_shim_d_name_ptr', +) +external ffi.Pointer d_name_ptr(ffi.Pointer d); + +@ffi.Native)>(symbol: 'libc_shim_closedir') +external int closedir(ffi.Pointer d); + +@ffi.Native Function(ffi.Int)>(symbol: 'libc_shim_fdopendir') +external ffi.Pointer fdopendir(int fd); + +@ffi.Native Function(ffi.Pointer)>( + symbol: 'libc_shim_opendir', +) +external ffi.Pointer opendir(ffi.Pointer path); + +@ffi.Native Function(ffi.Pointer)>( + symbol: 'libc_shim_readdir', +) +external ffi.Pointer readdir(ffi.Pointer d); + +/// +@ffi.Native(symbol: 'libc_shim_seterrno') +external void seterrno(int err); + +@ffi.Native(symbol: 'libc_shim_errno') +external int errno(); + +/// +@ffi.Native, ffi.Int, ffi.Int)>( + symbol: 'libc_shim_open', +) +external int open(ffi.Pointer pathname, int flags, int mode); + +@ffi.Native, ffi.Int, ffi.Int)>( + symbol: 'libc_shim_openat', +) +external int openat( + int fd, + ffi.Pointer pathname, + int flags, + int mode, +); + +@ffi.Native, ffi.Int)>( + symbol: 'libc_shim_chmod', +) +external int chmod(ffi.Pointer path, int mode); + +@ffi.Native, ffi.Int)>( + symbol: 'libc_shim_mkdir', +) +external int mkdir(ffi.Pointer pathname, int mode); + +@ffi.Native, ffi.Pointer)>( + symbol: 'libc_shim_stat', +) +external int stat(ffi.Pointer path, ffi.Pointer buf); + +@ffi.Native, ffi.Pointer)>( + symbol: 'libc_shim_lstat', +) +external int lstat(ffi.Pointer path, ffi.Pointer buf); + +@ffi.Native)>( + symbol: 'libc_shim_fstat', +) +external int fstat(int fd, ffi.Pointer buf); + +@ffi.Native< + ffi.Int Function(ffi.Int, ffi.Pointer, ffi.Pointer, ffi.Int) +>(symbol: 'libc_shim_fstatat') +external int fstatat( + int fd, + ffi.Pointer path, + ffi.Pointer buf, + int flag, +); + +/// +@ffi.Native, ffi.Pointer)>( + symbol: 'libc_shim_rename', +) +external int rename(ffi.Pointer old, ffi.Pointer newy); + +/// +@ffi.Native Function(ffi.Pointer)>( + symbol: 'libc_shim_getenv', +) +external ffi.Pointer getenv(ffi.Pointer name); + +@ffi.Native Function(ffi.Pointer)>( + symbol: 'libc_shim_mkdtemp', +) +external ffi.Pointer mkdtemp(ffi.Pointer template); + +/// +@ffi.Native Function(ffi.Int)>( + symbol: 'libc_shim_strerror', +) +external ffi.Pointer strerror(int errnum); + +/// +@ffi.Native)>(symbol: 'libc_shim_chdir') +external int chdir(ffi.Pointer path); + +@ffi.Native(symbol: 'libc_shim_close') +external int close(int fd); + +@ffi.Native Function(ffi.Pointer, ffi.Int64)>( + symbol: 'libc_shim_getcwd', +) +external ffi.Pointer getcwd(ffi.Pointer buf, int size); + +@ffi.Native(symbol: 'libc_shim_getpid') +external int getpid(); + +@ffi.Native(symbol: 'libc_shim_getppid') +external int getppid(); + +@ffi.Native, ffi.Int)>( + symbol: 'libc_shim_unlinkat', +) +external int unlinkat(int dirfd, ffi.Pointer pathname, int flags); + +/// +final class dirent extends ffi.Struct { + /// POSIX + @ffi.Int64() + external int d_ino; + + /// Linux, macOS/iOS + @ffi.Uint8() + external int d_type; + + /// POSIX; __DARWIN_MAXNAMLEN = 1024 + @ffi.Array.multi([1025]) + external ffi.Array d_name; +} + +final class DIR extends ffi.Struct { + external dirent libc_shim_dirent; + + external ffi.Pointer _dir; +} + +/// +final class timespec extends ffi.Struct { + @ffi.Int64() + external int tv_sec; + + @ffi.Int64() + external int tv_nsec; +} + +final class Stat extends ffi.Struct { + @ffi.Int64() + external int st_dev; + + @ffi.Int64() + external int st_ino; + + @ffi.Int64() + external int st_mode; + + @ffi.Int64() + external int st_nlink; + + @ffi.Int64() + external int std_uid; + + @ffi.Int64() + external int st_size; + + external timespec st_atim; + + external timespec st_mtim; + + external timespec st_ctim; + + /// Only valid on macOS/iOS + external timespec st_btime; + + /// Only valid on macOS/iOS + @ffi.Int64() + external int st_flags; +} diff --git a/pkgs/unix_api/lib/unix_api.dart b/pkgs/unix_api/lib/unix_api.dart new file mode 100644 index 000000000..5e74e9656 --- /dev/null +++ b/pkgs/unix_api/lib/unix_api.dart @@ -0,0 +1,11 @@ +// Copyright (c) 2025, 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 'src/libc_bindings.g.dart' as libc; + +export 'src/constants.g.dart'; +export 'src/libc_bindings.g.dart' hide errno, seterrno; + +int get errno => libc.errno(); +set errno(int err) => libc.seterrno(err); diff --git a/pkgs/unix_api/pubspec.yaml b/pkgs/unix_api/pubspec.yaml new file mode 100644 index 000000000..4e3a79a95 --- /dev/null +++ b/pkgs/unix_api/pubspec.yaml @@ -0,0 +1,23 @@ +name: unix_api +description: Access to POSIX APIs. +version: 0.1.0-wip +repository: https://github.com/dart-lang/labs/tree/main/pkgs/unix_api +publish_to: none + +environment: + sdk: ^3.9.2 + +dependencies: + code_assets: ^0.19.0 + ffi: ^2.1.4 + hooks: ^0.19.5 + logging: ^1.3.0 + native_toolchain_c: ^0.16.0 + +dev_dependencies: + benchmark_harness: ^2.3.1 + dart_flutter_team_lints: ^3.4.0 + errno: ^1.4.1 + ffigen: ^19.0.0 + path: ^1.9.1 + test: ^1.24.0 diff --git a/pkgs/io_file/src/constants.g.c b/pkgs/unix_api/src/constants.g.c similarity index 100% rename from pkgs/io_file/src/constants.g.c rename to pkgs/unix_api/src/constants.g.c diff --git a/pkgs/io_file/src/constants.g.h b/pkgs/unix_api/src/constants.g.h similarity index 100% rename from pkgs/io_file/src/constants.g.h rename to pkgs/unix_api/src/constants.g.h diff --git a/pkgs/io_file/src/libc_shim.c b/pkgs/unix_api/src/libc_shim.c similarity index 97% rename from pkgs/io_file/src/libc_shim.c rename to pkgs/unix_api/src/libc_shim.c index afd28df76..bb2c1c643 100644 --- a/pkgs/io_file/src/libc_shim.c +++ b/pkgs/unix_api/src/libc_shim.c @@ -181,6 +181,9 @@ int libc_shim_close(int fd) { return close(fd); } char *libc_shim_getcwd(char *buf, int64_t size) { return getcwd(buf, size); } +long libc_shim_getpid() { return getpid(); } +long libc_shim_getppid() { return getppid(); } + int libc_shim_unlinkat(int dirfd, const char *pathname, int flags) { return unlinkat(dirfd, pathname, flags); } diff --git a/pkgs/io_file/src/libc_shim.h b/pkgs/unix_api/src/libc_shim.h similarity index 98% rename from pkgs/io_file/src/libc_shim.h rename to pkgs/unix_api/src/libc_shim.h index 138c661bd..ee75105da 100644 --- a/pkgs/io_file/src/libc_shim.h +++ b/pkgs/unix_api/src/libc_shim.h @@ -121,5 +121,7 @@ LIBC_SHIM_EXPORT char *libc_shim_strerror(int errnum); LIBC_SHIM_EXPORT int libc_shim_chdir(const char *path); LIBC_SHIM_EXPORT int libc_shim_close(int fd); LIBC_SHIM_EXPORT char *libc_shim_getcwd(char *buf, int64_t size); +LIBC_SHIM_EXPORT long libc_shim_getpid(); +LIBC_SHIM_EXPORT long libc_shim_getppid(); LIBC_SHIM_EXPORT int libc_shim_unlinkat(int dirfd, const char *pathname, int flags); diff --git a/pkgs/unix_api/test/dirent_test.dart b/pkgs/unix_api/test/dirent_test.dart new file mode 100644 index 000000000..00bd91c9f --- /dev/null +++ b/pkgs/unix_api/test/dirent_test.dart @@ -0,0 +1,47 @@ +// Copyright (c) 2025, 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 'package:ffi/ffi.dart' as ffi; +import 'package:unix_api/unix_api.dart'; +import 'package:test/test.dart'; +import 'package:path/path.dart' as p; + +void main() { + group('dirent', () { + late Directory tmp; + + setUp(() { + tmp = Directory.systemTemp.createTempSync('dirent'); + }); + + tearDown(() { + tmp.deleteSync(recursive: true); + }); + + test('test', () { + File(p.join(tmp.path, 'test1')).createSync(); + Directory(p.join(tmp.path, 'dir1')).createSync(); + + ffi.using((arena) { + final dir = opendir(tmp.path.toNativeUtf8(allocator: arena).cast()); + expect(dir, isNot(nullptr)); + + final paths = []; + for ( + var dirent = readdir(dir); + dirent != nullptr; + dirent = readdir(dir) + ) { + paths.add(d_name_ptr(dirent).cast().toDartString()); + } + + expect(paths, containsAll(['test1', 'dir1'])); + expect(closedir(dir), 0); + }); + }); + }); +} diff --git a/pkgs/io_file/tool/build_constants.dart b/pkgs/unix_api/tool/build_constants.dart similarity index 100% rename from pkgs/io_file/tool/build_constants.dart rename to pkgs/unix_api/tool/build_constants.dart diff --git a/pkgs/io_file/tool/generate.dart b/pkgs/unix_api/tool/generate.dart similarity index 100% rename from pkgs/io_file/tool/generate.dart rename to pkgs/unix_api/tool/generate.dart