Compare commits

...

109 Commits
v0.15 ... main

Author SHA1 Message Date
Thomas Leonard
8f7f82d2c1
Merge pull request #795 from bdodrem/windows_read-stdin__issue_793_and_792
On Windows, fix stdin broken-pipe and blocking domain.  Issues #793 and #792.
2025-01-27 12:27:06 +00:00
bdodrem
d3cb04a67e eio_windows: fix blocking bug and pipe error
* fix blocking issue on Windows : issue #793.
      Adding await_readable before reading fd

* fix broken pipe exception : issue #792.
      Use Unix.read_bigarray instead of Unix_cstruct.read

* replace eio_windows_cstruct_stubs.c by Unix functions.
     Since OCaml 5.2, Unix.read_bigarray and Unix.write_bigarray can be used.
2025-01-27 12:04:08 +00:00
Thomas Leonard
c78db1a757
Merge pull request #794 from talex5/docs
Minor documentation improvements
2025-01-16 10:51:37 +00:00
Thomas Leonard
4a19b2eea9 Minor documentation improvements
- Remove reference to Meio until it's actually working with stable
  versions of Eio.
- Remove the "Please try porting your programs" request now that Eio is
  stable.
- Use a simpler and more typical example for the switches section.
- In the `Executor_pool` make the second example create the pool the
  same way as the first one (and shrink it a bit).
- Replace out-of-date retro-httpaf-bench example with cohttp-eio and
  capnp-rpc examples.
- In `Net.run_server`, suggest using `Executor_pool`.
2025-01-15 16:59:06 +00:00
Thomas Leonard
fdd2593e33
Merge pull request #789 from talex5/fix-benchmarks
Disable dune subst
2024-12-06 20:00:33 +00:00
Thomas Leonard
c39449a631 Disable dune subst
It was always unnecessary, but with dune 3.17.0 it's causing the
benchmarks to fail to install. The error is:

    Error: There is no dune-project file in the current directory, please add one
    with a (name <name>) field in it.
    Hint: 'dune subst' must be executed from the root of the project
2024-12-06 14:54:15 +00:00
Thomas Leonard
f195295e3d
Merge pull request #787 from talex5/freebsd-close
Ignore ECONNRESET on close
2024-12-06 09:02:12 +00:00
Thomas Leonard
17665689f5 Ignore ECONNRESET on close
FreeBSD returns ECONNRESET in certain (unclear) circumstances, but it
does still close the FD successfully. If you care about this case, you
should probably use `shutdown` instead and check for it there.

Python and Ruby at least both explicitly check for and ignore this error
too.
2024-12-02 11:57:55 +00:00
Thomas Leonard
f26d70d642
Merge pull request #784 from talex5/changelog
Update changelog
2024-11-22 11:25:01 +00:00
Thomas Leonard
730033c0f1 Update changelog 2024-11-22 10:14:28 +00:00
Thomas Leonard
cc0274dd2f
Merge pull request #783 from talex5/windows-conn-aborted
eio_windows: group ECONNABORTED with other connection reset errors
2024-11-22 09:12:14 +00:00
Thomas Leonard
b971fa1cca eio_windows: group ECONNABORTED with other connection reset errors
Windows returns this if the other end disconnects (seen while testing
with both ends on the same machine).
2024-11-21 15:00:52 +00:00
Thomas Leonard
2d9e24a67f
Merge pull request #782 from talex5/openbsd
Fix tests on OpenBSD
2024-11-21 10:46:45 +00:00
Thomas Leonard
ae0eaa2dfd Fix tests on OpenBSD
- Add test dependency on bash.
- Allow www instead of http as the service name for port 80.
2024-11-21 10:27:00 +00:00
Thomas Leonard
681400c18e
Merge pull request #780 from talex5/windows-getaddrinfo
eio_windows: work around problems in Unix.getaddrinfo
2024-11-21 10:00:18 +00:00
Thomas Leonard
b41d4d8a49 eio_windows: work around problems in Unix.getaddrinfo
OCaml's `Unix.getaddrinfo` on Windows doesn't set `ai_protocol` to
anything useful, so you can't tell which addresses are TCP and which are
UDP. So, do two separate queries.
2024-11-20 12:21:04 +00:00
Thomas Leonard
9b939abedd
Merge pull request #779 from talex5/fork-bt
Preserve backtraces in fork_daemon and fork_promise_exn
2024-11-16 10:33:37 +00:00
Thomas Leonard
61f738df51 Preserve backtraces in fork_daemon and fork_promise_exn
If the fiber fails, record the backtrace when failing the switch. `fork`
itself already did this, but `fork_daemon` and `fork_promise_exn`
didn't.
2024-11-16 10:19:20 +00:00
Onah_Anthony
eb8fe3ef82
Check if windows has_symlink for tests (#771)
Symlinking is privileged operation on Windows, so we check if the running user can make symlinks before running tests that require them.
2024-10-21 15:42:36 +01:00
Thomas Leonard
debe35621e
Merge pull request #769 from patricoferris/include-fork-action-h
Make fork_action.h a public_header
2024-10-19 11:26:23 +01:00
Patrick Ferris
dd13303a4a Make fork_action.h a public_header 2024-10-17 19:43:32 +01:00
Patrick Ferris
216ccff845
Merge pull request #765 from ocaml-multicore/ai-hacking-guidance
Add advice about using AI for code generation
2024-10-07 13:22:02 +01:00
Patrick Ferris
a65fd2e612
Add advice about using AI for code generation 2024-10-06 11:28:52 +01:00
Thomas Leonard
f8c9441852
Merge pull request #756 from talex5/win-unregister-fd
eio_windows: unregister FDs on cancel
2024-09-30 12:52:39 +01:00
Thomas Leonard
8364428147 eio_windows: unregister FDs on cancel
This was fixed in cc19aa160626f5b for `eio_posix`, but not for
`eio_windows`.

The error looks like:

    exception Unix.Unix_error(Unix.ENOTSOCK, "select", "")
2024-09-29 14:15:13 +01:00
Thomas Leonard
d2a3e21bc4
Merge pull request #755 from talex5/cleanup
Minor code cleanups
2024-09-18 16:45:45 +01:00
Thomas Leonard
424f5e8c96 Move Dla to eio.utils
It doesn't need to be in eio.core, as nothing else there depends on it.
2024-09-18 12:02:49 +01:00
Thomas Leonard
89019ddd3d Remove unused library
eio.core doesn't use cstruct.
2024-09-18 11:51:57 +01:00
Thomas Leonard
534f89cf0e
Merge pull request #754 from talex5/quiet-tsan
eio_linux: avoid triggering a TSan warning
2024-09-07 14:06:38 +01:00
Thomas Leonard
9ca440ac5c eio_linux: avoid triggering a TSan warning
TSan warns that setting `statx_works` races with users of it. This isn't
really a problem, because we always set it to the same value, but it's
distracting when looking for other bugs.

Reported by Anil Madhavapeddy in #751.
2024-09-06 17:23:32 +01:00
Thomas Leonard
e2dc1d79e5
Merge pull request #753 from talex5/cancel-alloc-fixed
eio_linux: allow alloc_fixed_or_wait to be cancelled
2024-09-06 17:23:14 +01:00
Thomas Leonard
0c414327d1 eio_linux: allow alloc_fixed_or_wait to be cancelled 2024-09-06 17:11:37 +01:00
Thomas Leonard
0f6b65dea2
Merge pull request #752 from talex5/linux-get-sched
eio_linux: refactor fixed buffer code
2024-09-05 11:07:08 +01:00
Thomas Leonard
cc2cd3da32 eio_linux: refactor fixed buffer code
Instead of having separate Alloc, Alloc_or_wait and Free effects,
the scheduler now provides a single Get effect to return itself,
and the actual work is now done in the calling fiber. This is cleaner,
and seems to be slightly faster too.

Note that `alloc_fixed_or_wait` is currently not cancellable (it wasn't
before either, but it's more obvious now).

It would be possible to use DLS to store the scheduler rather than using
an effect. However, the improvement in speed is minimal and there are
some complications with sys-threads, so probably better to wait for
OCaml to support thread-local-storage first.
2024-09-04 15:41:36 +01:00
Thomas Leonard
d47b5e29f7
Merge pull request #749 from talex5/trace-spawn
Record trace event when spawning processes
2024-08-23 15:16:55 +01:00
Thomas Leonard
c2d314b930 Record trace event when spawning processes
This can take quite a long time.
2024-08-23 15:03:43 +01:00
Thomas Leonard
33d4e01a30
Merge pull request #745 from talex5/fs-example
examples/fs: show how to read files while scanning
2024-06-28 11:03:33 +01:00
Thomas Leonard
c45978252a examples/fs: show how to read files while scanning 2024-06-22 16:41:21 +01:00
Thomas Leonard
37840760b1
Merge pull request #744 from talex5/unix-net-types
Eio_unix.Net: make some return types more polymorphic
2024-06-21 10:17:40 +01:00
Thomas Leonard
8cb86a703e Eio_unix.Net: make some return types more polymorphic
This allows e.g. using the result of the `import_socket_stream` as a
`stream_socket_ty` without needing a cast.
2024-06-20 09:37:40 +01:00
Thomas Leonard
12721820e4
Merge pull request #743 from talex5/win-fwd-slash
Eio.Path: always use "/" as separator
2024-06-19 17:13:42 +01:00
Thomas Leonard
32e501a198 Eio.Path: always use "/" as separator
path.mli says:

> In Eio, the directory separator is always "/", even on Windows.

However, `Path.(/)` used `Filename.concat` to create paths, which uses
the native separator ("\" on Windows).
2024-06-19 14:27:26 +01:00
Thomas Leonard
a21b507de8
Merge pull request #742 from talex5/win-openat-debug
eio_windows: improve openat error handling
2024-06-19 14:12:20 +01:00
Thomas Leonard
54df8fdd62 eio_windows: run the fs example in CI 2024-06-19 11:53:18 +01:00
Thomas Leonard
2cd09925d0 eio_windows: improve openat error handling 2024-06-19 11:45:19 +01:00
Thomas Leonard
642bdbef7e
Merge pull request #741 from copy/main
define struct clone_args for linux-lts versions that don't have it
2024-06-19 11:26:07 +01:00
Fabian
574afc103a define struct clone_args for linux-lts versions that don't have it 2024-06-18 19:12:52 +01:00
Thomas Leonard
17562f24fe
Merge pull request #739 from talex5/doc-seq
Add example to `Buf_read.seq` documentation
2024-06-17 09:27:07 +01:00
Thomas Leonard
8ef76f06bf Add example to Buf_read.seq documentation 2024-06-14 11:30:09 +01:00
Thomas Leonard
77d881014d
Merge pull request #735 from talex5/release
Prepare release
2024-05-28 12:06:37 +01:00
Thomas Leonard
e32331a835 Prepare release 2024-05-28 11:58:43 +01:00
Thomas Leonard
3e67f7d4cb
Merge pull request #733 from alyssais/listening
Add Eio_unix.Net.import_socket_listening
2024-05-28 11:57:08 +01:00
Alyssa Ross
3b2a67966e Add Eio_unix.Net.import_socket_listening 2024-05-28 10:41:45 +01:00
Thomas Leonard
2c5eb612db
Merge pull request #734 from talex5/fix-signal-race
eio_linux: add work-around for signals race
2024-05-26 14:38:25 +01:00
Thomas Leonard
ab12b0b77c eio_linux: add work-around for signals race
This is a quick fix for https://github.com/ocaml-multicore/eio/issues/732.
2024-05-23 12:12:46 +01:00
Thomas Leonard
c023b2e750
Merge pull request #731 from talex5/release
Prepare release
2024-05-21 15:59:51 +01:00
Thomas Leonard
687017078a Prepare release 2024-05-21 14:56:35 +01:00
Thomas Leonard
2146c8a181
Merge pull request #730 from talex5/fs-example
Add examples/fs showing how to walk a directory tree
2024-05-21 12:19:38 +01:00
Thomas Leonard
7d718405a6 Add examples/fs showing how to walk a directory tree 2024-05-21 11:20:26 +01:00
Thomas Leonard
d834d7391f
Merge pull request #729 from talex5/uring-submit
eio_linux: don't record submit events when there's nothing to submit
2024-05-21 11:07:56 +01:00
Thomas Leonard
d26184dbf7 eio_linux: don't record submit events when there's nothing to submit 2024-05-20 16:53:10 +01:00
Thomas Leonard
73f913c108
Merge pull request #728 from talex5/linux-skip-submit
eio_linux: don't call submit immediately before wait
2024-05-15 09:36:13 +01:00
Thomas Leonard
62c1925dfe eio_linux: don't call submit immediately before wait
It's quicker to do it in a single call.
2024-05-14 12:56:59 +01:00
Thomas Leonard
b126756eb8
Merge pull request #727 from talex5/split-linux
eio_linux: split flow into its own file
2024-05-11 18:25:48 +01:00
Thomas Leonard
3c93b4405c eio_linux: split flow into its own file 2024-05-10 10:00:18 +01:00
Thomas Leonard
bd2c92e7ba
Merge pull request #726 from talex5/timeout-sleep
Add Timeout.sleep
2024-05-02 14:37:46 +01:00
Thomas Leonard
a0cb744256 Add Timeout.sleep
This makes it easier to use timeouts as simple delays.
2024-05-01 14:33:46 +01:00
Thomas Leonard
49c9774ee3
Merge pull request #715 from patricoferris/symlinks
Add symlink support
2024-04-28 11:18:20 +01:00
Patrick Ferris
d3f30696c2 Add symlink support 2024-04-25 14:40:26 +01:00
Thomas Leonard
c1c2d634de
Merge pull request #723 from jebrosen/fix/openbsd
eio_posix: fix filesystem tests on OpenBSD
2024-04-05 11:23:00 +01:00
Jeb Rosen
7c9396ef19 eio_posix: check for ELOOP in low-level fs 'resolve'
In the 'resolve' function there is a check for items being symlinks
which was missing ELOOP as an indicator. This patch adds ELOOP, to match
the similar check in 'open_beneath_fallback'.

This fixes filesystem tests on OpenBSD.
2024-04-04 17:06:29 -04:00
Thomas Leonard
58aa3f6663
Merge pull request #722 from prgbln/fix_openbsd
Add _BSD_SOURCE flag to fix eio_posix on OpenBSD.
2024-04-04 16:58:26 +01:00
prgbln
b8a99fa036 Add _BSD_SOURCE flag to fix eio_posix on OpenBSD. 2024-04-04 12:53:55 +02:00
Thomas Leonard
b128edce66
Merge pull request #718 from lucperkins/mdash-in-readme-title
Long dash in README title
2024-03-29 11:27:24 +00:00
Thomas Leonard
3be614e86f
Merge pull request #657 from SGrondin/pool-never-block
Add Eio.Pool.use ~never_block
2024-03-26 09:57:03 +00:00
Simon Grondin
321bc093b4 Add Eio.Pool.use ~never_block 2024-03-25 14:33:51 +00:00
Thomas Leonard
1776925870
Merge pull request #719 from talex5/bench-info
Benchmarks: record uname, Eio backend, and number of cores
2024-03-25 09:46:45 +00:00
Thomas Leonard
94ab6cb65f Benchmarks: record uname, Eio backend, and number of cores 2024-03-25 09:24:22 +00:00
Thomas Leonard
c53d897cbd
Merge pull request #720 from talex5/fix-isatty
eio_linux: require Linux >= 5.15
2024-03-24 16:52:50 +00:00
Thomas Leonard
f2ce0c26ed eio_linux: require Linux >= 5.15
This allows removing a work-around that required checking whether every
flow was a tty (and which was doing this on every read).
2024-03-24 09:27:13 +00:00
Luc Perkins
7608cbaa60
Use long dash in README title 2024-03-23 09:31:10 -03:00
Thomas Leonard
911ccc8f6a
Merge pull request #717 from talex5/read-all-hint
README: explain that read_all reads until shutdown
2024-03-23 11:13:48 +00:00
Thomas Leonard
12530ebeca README: explain that read_all reads until shutdown 2024-03-23 10:57:57 +00:00
Thomas Leonard
14ae3cfee3
Merge pull request #712 from talex5/update-mdx
Update to MDX 2.4.1
2024-03-15 11:29:21 +00:00
Thomas Leonard
6ae124ecc5 Update to MDX 2.4.1 2024-03-14 11:53:04 +00:00
Thomas Leonard
34b650bd47
Merge pull request #711 from talex5/docs-1.0
Update README for Eio 1.0
2024-03-14 11:10:39 +00:00
Thomas Leonard
240e04f761 Update README for Eio 1.0
- Remove the Status section.
- Mention `Eio_js`.
- Simplify the hello-world example.
- Mention `Eio_unix.sleep` in the time section.
- Mention non-determinism only applies to the main Eio API.
2024-03-13 12:00:27 +00:00
Thomas Leonard
15f2047323
Merge pull request #710 from talex5/mdx
Bound MDX version on all packages
2024-03-11 12:48:57 +00:00
Thomas Leonard
9bbd4e0108 Bound MDX version on all packages 2024-03-10 15:28:05 +00:00
Thomas Leonard
edfe8debb2
Merge pull request #709 from talex5/release
Prepare release
2024-03-10 15:10:31 +00:00
Thomas Leonard
f3f24738c4 Prepare release 2024-03-10 12:51:56 +00:00
Thomas Leonard
ce30c9a270
Merge pull request #707 from talex5/executor-pool-docs
Executor pool docs
2024-03-10 12:38:31 +00:00
Simon Grondin
feb8d11d91 Add README documentation for Eio.Executor_pool 2024-03-10 12:32:35 +00:00
Thomas Leonard
82dcca77bf Add multicore trace illustration to README 2024-03-10 11:51:47 +00:00
Thomas Leonard
ed9c4a5574
Merge pull request #708 from talex5/nologs
eio_linux: remove logging
2024-03-10 11:51:33 +00:00
Thomas Leonard
ebf7fa0230 eio_linux: remove logging
There were only two remaining uses, neither of which has proved useful.
The warning about an unknown response to cancellation has never been
seen, and the warning about being unable to allocate a fixed buffer just
annoys people.
2024-03-08 17:51:12 +00:00
Thomas Leonard
5e6618168c
Merge pull request #705 from talex5/linux-ll
eio_linux: expose more functions in the Low_level module
2024-02-29 16:19:07 +00:00
Thomas Leonard
fb731b5161 eio_linux: move Low_level signature to low_level.mli
This ensures that the low-level API provided to users is the same one
that is used internally, so we can't forget to expose things in future.
2024-02-29 16:09:34 +00:00
Thomas Leonard
e48f6114b1 eio_linux: Expose more functions in Low_level module
Add all the functions used by other parts of eio_linux (`openat`,
`mkdir`, `read_link`, `unlink`, `rename` and `pipe`).

Tidied the API up a bit too:
- `mkdir_beneath` is now just `mkdir`.
- `statx_confined` is now just `statx`.
- `open_dir` is gone; the single user now calls `openat` directly.
2024-02-29 16:09:34 +00:00
Thomas Leonard
7b58999f51
Merge pull request #706 from talex5/mdx-fail
Require MDX < 2.4.0
2024-02-29 16:08:21 +00:00
Thomas Leonard
6309314933 Require MDX < 2.4.0
The new version attempts to execute included blocks, and also seems
broken on macos.
2024-02-29 16:01:05 +00:00
Thomas Leonard
ef415fbdfe
Merge pull request #704 from ocaml-multicore/fix-to-pass-with-old-and-new-kcas
Fix to pass with both old and new Kcas
2024-02-26 17:04:37 +00:00
Thomas Leonard
31ee72e25a
Merge pull request #697 from talex5/cap-enter
Add Eio_unix.Cap module to enable Capsicum mode
2024-02-26 17:03:40 +00:00
Thomas Leonard
ca121eea64 Add Eio_unix.Cap module to enable Capsicum mode 2024-02-26 11:43:13 +00:00
Vesa Karvonen
6ee8ab97b8 Fix to pass with both old and new Kcas
The `Loc.t` type has changed, which makes a MDX block fail.  This changes the
block such that it will pass with both old and new Kcas.
2024-02-25 23:49:22 +02:00
Thomas Leonard
261b583c5b
Merge pull request #703 from SGrondin/idempotent-test
Make posix open_beneath test idempotent
2024-02-25 19:17:01 +00:00
Simon Grondin
ec576100ef Make posix open_beneath test idempotent 2024-02-25 11:16:55 -06:00
Thomas Leonard
57c02073ee
Merge pull request #702 from talex5/execpool-err
Executor_pool: mention requested weight in error message
2024-02-23 17:20:22 +00:00
Thomas Leonard
e5444c0f8b Executor_pool: mention requested weight in error message
Spotted by Yawar Amin.
2024-02-23 14:06:04 +00:00
98 changed files with 3468 additions and 1206 deletions

View File

@ -12,7 +12,7 @@ jobs:
os:
- macos-latest
ocaml-compiler:
- 5.1.x
- 5.2.x
local-packages:
- eio eio_posix eio_main
@ -44,7 +44,7 @@ jobs:
with:
opam-pin: false
opam-depext: false
ocaml-compiler: ocaml.5.1.0,ocaml-option-mingw
ocaml-compiler: ocaml.5.2.0,ocaml-option-mingw
opam-repositories: |
dra27: https://github.com/dra27/opam-repository.git#windows-5.0
normal: https://github.com/ocaml/opam-repository.git
@ -57,6 +57,7 @@ jobs:
- run: opam exec -- dune build
- run: opam exec -- dune runtest
- run: opam exec -- dune exec -- ./examples/net/main.exe
- run: opam exec -- dune exec -- ./examples/fs/main.exe
docker:
runs-on: ubuntu-latest
steps:

View File

@ -1,3 +1,139 @@
## v1.2
Changes:
- Make `fork_action.h` a public header (@patricoferris #769, reviewed by @talex5).
Allows other libraries to add new actions.
- Record trace event when spawning processes (@talex5 #749).
Spawning a subprocess can take a long time in some cases, so show it clearly in the traces.
- Eio_unix.Net: make some return types more polymorphic (@talex5 #744).
Bug fixes:
- Preserve backtraces in `fork_daemon` (@talex5 #779).
- Eio.Path: always use "/" as separator (@talex5 #743).
Linux backend:
- Allow `alloc_fixed_or_wait` to be cancelled (@talex5 #753).
- Avoid triggering a (harmless) TSan warning (@talex5 #754, reported by @avsm).
Windows backend:
- Unregister FDs on cancel (@talex5 #756).
Fixes `exception Unix.Unix_error(Unix.ENOTSOCK, "select", "")`.
- Work around problems in `Unix.getaddrinfo` (@talex5 #780).
Fixes e.g. `No addresses found for host name "127.0.0.1"`.
- Group `ECONNABORTED` with other connection reset errors (@talex5 #783).
- Check `has_symlink` for tests (@create2000 #771, reviewed by @patricoferris and @talex5).
- Improve `openat` error handling (@talex5 #742, reported by @kentookura).
Fixes `exception Unix.Unix_error(Unix.ENOENT, "openat", "")`.
Documentation:
- examples/fs: show how to read files while scanning (@talex5 #745).
- Add example to `Buf_read.seq` documentation (@talex5 #739, requested by @darrenldl and @rizo).
Build and test:
- Fix tests on OpenBSD (@talex5 #782).
- Add advice about using AI for code generation (@patricoferris #765, reviewed by @avsm and @talex5).
- Minor code cleanups (@talex5 #755).
- Define `struct clone_args` for linux-lts versions that don't have it (@copy #741, reviewed by @talex5).
- eio_linux: refactor fixed buffer code (@talex5 #752).
## v1.1
New features:
- Add `Eio.Path.symlink` (@patricoferris #715, reviewed by @talex5).
- Add `Eio.Pool.use ~never_block` (@SGrondin #657, reviewed by @talex5).
- Add `Eio_unix.Net.import_socket_listening` (@alyssais #733).
- Add `Eio.Time.Timeout.sleep` (@talex5 #726).
Documentation:
- Add `examples/fs` showing how to walk a directory tree (@talex5 #730).
- README: explain that `read_all` reads until shutdown (@talex5 #717, reported by @Wenke-D).
- Use long dash in README title (@lucperkins #718).
Linux backend:
- Require Linux >= 5.15 (@talex5 #720, reviewed by @SGrondin and @avsm).
Removes a work-around that required checking whether every flow was a tty.
- Don't call submit immediately before wait (@talex5 #728).
This is slightly faster and makes the traces clearer.
- Don't record submit events when there's nothing to submit (@talex5 #729).
Makes the traces a bit clearer.
- Split flow into its own file (@talex5 #727).
- Add work-around for signals race (@talex5 #734).
POSIX backend:
- Add `_BSD_SOURCE` flag to fix build on OpenBSD (@prgbln #722).
- Fix sandboxed path resolution on OpenBSD (@jebrosen #723, reviewed by @talex5).
OpenBSD uses `ELOOP` when opening a symlink with `O_NOFOLLOW`.
Build and test:
- Benchmarks: record uname, Eio backend, and number of cores (@talex5 #719).
- Update to MDX 2.4.1 for OCaml 5.2 (@talex5 #712).
## v1.0
New features:
- Add `Eio_unix.Cap` module to enable Capsicum mode (@talex5 #697, reviewed by @SGrondin).
- eio_linux: expose more functions in the `Low_level` module (@talex5 #705, reviewed by @SGrondin).
Add all the functions used by other parts of eio_linux (`openat`, `mkdir`, `read_link`, `unlink`, `rename` and `pipe`).
Tidied the API up a bit too:
- `mkdir_beneath` is now just `mkdir`.
- `statx_confined` is now just `statx`.
- `open_dir` is gone; the single user now calls `openat` directly.
Documentation:
- Add README documentation for `Eio.Executor_pool` (@SGrondin @talex5 #707, reviewed by @Sudha247).
- eio_linux: remove logging (@talex5 #708, requested by @clecat).
There were only two remaining uses of Logs, neither of which has proved useful.
Build:
- Add upper-bound on MDX (@talex5 #706).
The new version attempts to execute included blocks.
- Fix tests to pass with both old and new Kcas (@polytypic #704).
- Make posix `open_beneath` test idempotent (@SGrondin #703).
- Executor_pool: mention requested weight in error message (@talex5 #702, reported by @yawaramin).
## v0.15
New features:

View File

@ -1,8 +1,8 @@
FROM ocaml/opam:debian-11-ocaml-5.1
FROM ocaml/opam:debian-11-ocaml-5.2
# Make sure we're using opam-2.1:
RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam
# Ensure opam-repository is up-to-date:
RUN cd opam-repository && git pull -q origin 0ac3fc79fd11ee365dd46119d43e9763cf57da52 && opam update
RUN cd opam-repository && git pull -q origin 97de3378749cf8d2d70a5d710d310e5cc17c9dea && opam update
# Install utop for interactive use:
RUN opam install utop fmt
# Install Eio's dependencies (adding just the opam files first to help with caching):

View File

@ -135,4 +135,15 @@ Try to avoid making unnecessary changes; this makes review harder and clutters u
`ocamlformat` may be useful to get badly messed up code to a baseline unformatted state,
from which human formatting can be added where needed.
## AI-generated Code
Contributing to Eio should not be done _solely_ using "AI tools" such as ChatGPT. This is for a few reasons:
1. **It obfuscates how you think**. Purely AI-generated code tells us little about how you think and the problems you might be having. This makes it harder to provide good feedback on PRs and issues.
2. **It is often more work to review**. Particularly for the OCaml ecosystem and libraries like Eio, it seems that these tools are not very good and generate a lot of believable code that is in actual fact completely wrong. PR comments and the code submitted with them can say completely different things.
3. **It is a grey area for licensing**. Models like ChatGPT have been trained on lots of code with different licenses and has been known to simply copy code as an answer to a prompt. We would like to avoid this headache as best we can.
Use AI tools, if you wish, to help you understand OCaml and Eio. Do not offload all of the work of a PR or a comment to these tools.
[dscheck]: https://github.com/ocaml-multicore/dscheck

201
README.md
View File

@ -1,6 +1,6 @@
[API reference][Eio API] | [#eio Matrix chat](https://matrix.to/#/#eio:roscidus.com) | [Dev meetings][]
# Eio -- Effects-Based Parallel IO for OCaml
# Eio &mdash; Effects-Based Parallel IO for OCaml
Eio provides an effects-based direct-style IO stack for OCaml 5.
For example, you can use Eio to read and write files, make network connections,
@ -15,9 +15,8 @@ Eio replaces existing concurrency libraries such as Lwt
<!-- vim-markdown-toc GFM -->
* [Motivation](#motivation)
* [Current Status](#current-status)
* [Structure of the Code](#structure-of-the-code)
* [Getting OCaml 5.1](#getting-ocaml-51)
* [Eio packages](#eio-packages)
* [Getting OCaml](#getting-ocaml)
* [Getting Eio](#getting-eio)
* [Running Eio](#running-eio)
* [Testing with Mocks](#testing-with-mocks)
@ -35,6 +34,8 @@ Eio replaces existing concurrency libraries such as Lwt
* [Running processes](#running-processes)
* [Time](#time)
* [Multicore Support](#multicore-support)
* [Domain Manager](#domain-manager)
* [Executor Pool](#executor-pool)
* [Synchronisation Tools](#synchronisation-tools)
* [Promises](#promises)
* [Example: Concurrent Cache](#example-concurrent-cache)
@ -78,24 +79,19 @@ Additionally, modern operating systems provide high-performance alternatives to
For example, Linux's io_uring system has applications write the operations they want to perform to a ring buffer,
which Linux handles asynchronously, and Eio can take advantage of this.
## Current Status
You can always [fall back to using Lwt libraries](#lwt) to provide missing features if necessary.
See [Awesome Multicore OCaml][] for links to other projects using Eio.
See [Eio 1.0 progress tracking](https://github.com/ocaml-multicore/eio/issues/388) for the current status.
Please try porting your programs to use Eio and submit PRs or open issues when you find problems.
Remember that you can always [fall back to using Lwt libraries](#lwt) to provide missing features if necessary.
See [Awesome Multicore OCaml][] for links to work migrating other projects to Eio.
## Structure of the Code
## Eio packages
- [Eio][] provides concurrency primitives (promises, etc.) and a high-level, cross-platform OS API.
- [Eio_posix][] provides a cross-platform backend for these APIs for POSIX-type systems.
- [Eio_linux][] provides a Linux io-uring backend for these APIs,
plus a low-level API that can be used directly (in non-portable code).
- [Eio_linux][] provides a Linux io_uring backend for these APIs.
- [Eio_windows][] is for use on Windows (incomplete - [help wanted](https://github.com/ocaml-multicore/eio/issues/125)).
- [Eio_main][] selects an appropriate backend (e.g. `eio_linux` or `eio_posix`), depending on your platform.
- [Eio_js][] allows Eio code to run in the browser, using `js_of_ocaml`.
## Getting OCaml 5.1
## Getting OCaml
You'll need OCaml 5.1.0 or later.
You can either install it yourself or build the included [Dockerfile](./Dockerfile).
@ -107,7 +103,7 @@ To install it yourself:
2. Use opam to install OCaml:
```
opam switch create 5.1.1
opam switch create 5.2.0
```
## Getting Eio
@ -133,18 +129,18 @@ prompt and return after each line.)
# open Eio.Std;;
```
This function writes a greeting to `stdout` using [Eio.Flow][]:
This function writes a greeting to `out` using [Eio.Flow][]:
```ocaml
let main ~stdout =
Eio.Flow.copy_string "Hello, world!\n" stdout
let main out =
Eio.Flow.copy_string "Hello, world!\n" out
```
We use [Eio_main.run][] to run the event loop and call `main` from there:
```ocaml
# Eio_main.run @@ fun env ->
main ~stdout:(Eio.Stdenv.stdout env);;
main (Eio.Stdenv.stdout env);;
Hello, world!
- : unit = ()
```
@ -154,7 +150,7 @@ Note that:
- The `env` argument represents the standard environment of a Unix process, allowing it to interact with the outside world.
A program will typically start by extracting from `env` whatever things the program will need and then calling `main` with them.
- The type of the `main` function here tells us that this program only interacts via `stdout`.
- The type of the `main` function here tells us that this program only interacts via the `out` flow.
- `Eio_main.run` automatically calls the appropriate run function for your platform.
For example, on Linux this will call `Eio_linux.run`. For non-portable code you can use the platform-specific library directly.
@ -169,7 +165,7 @@ For example, instead of giving `main` the real standard output, we can have it w
```ocaml
# Eio_main.run @@ fun _env ->
let buffer = Buffer.create 20 in
main ~stdout:(Eio.Flow.buffer_sink buffer);
main (Eio.Flow.buffer_sink buffer);
traceln "Main would print %S" (Buffer.contents buffer);;
+Main would print "Hello, world!\n"
- : unit = ()
@ -183,8 +179,7 @@ The [Eio_mock][] library provides some convenient pre-built mocks:
```ocaml
# #require "eio.mock";;
# Eio_main.run @@ fun _env ->
let mock_stdout = Eio_mock.Flow.make "mock-stdout" in
main ~stdout:mock_stdout;;
main (Eio_mock.Flow.make "mock-stdout");;
+mock-stdout: wrote "Hello, world!\n"
- : unit = ()
```
@ -236,12 +231,7 @@ The green segments show when each fiber is running.
Note that the output from `traceln` appears in the trace as well as on the console.
In the eio-trace window, scrolling with the mouse or touchpad will zoom in or out of the diagram.
There are various third-party tools that can also consume this data
(but may currently require patches to support the new system):
- [Meio][] (Monitoring for Eio) provides an interactive console-based UI for exploring running fibers.
- [Olly][] can save Perfetto traces and report statistics.
Third-party tools, such as [Olly][], can also consume this data.
[examples/trace](./examples/trace/) shows how to consume the events manually.
## Cancellation
@ -315,22 +305,23 @@ For example:
```ocaml
# Eio_main.run @@ fun _env ->
Switch.run (fun sw ->
Fiber.fork ~sw
(fun () -> for i = 1 to 3 do traceln "i = %d" i; Fiber.yield () done);
traceln "First thread forked";
Fiber.fork ~sw
(fun () -> for j = 1 to 3 do traceln "j = %d" j; Fiber.yield () done);
traceln "Second thread forked; top-level code is finished"
);
for i = 1 to 3 do
Fiber.fork ~sw (fun () ->
traceln "Job %d starting" i;
Fiber.yield ();
traceln "%d done" i;
);
done;
traceln "All child fibers forked";
);
traceln "Switch is finished";;
+i = 1
+First thread forked
+j = 1
+Second thread forked; top-level code is finished
+i = 2
+j = 2
+i = 3
+j = 3
+Job 1 starting
+Job 2 starting
+Job 3 starting
+All child fibers forked
+1 done
+2 done
+3 done
+Switch is finished
- : unit = ()
```
@ -395,6 +386,7 @@ let run_client ~net ~addr =
Switch.run ~name:"client" @@ fun sw ->
traceln "Client: connecting to server";
let flow = Eio.Net.connect ~sw net addr in
(* Read all data until end-of-stream (shutdown): *)
traceln "Client: received %S" (Eio.Flow.read_all flow)
```
@ -838,6 +830,25 @@ A program that operates on the current directory will probably want to use `cwd`
whereas a program that accepts a path from the user will probably want to use `fs`,
perhaps with `open_dir` to constrain all access to be within that directory.
On systems that provide the [cap_enter][] system call, you can ask the OS to reject accesses
that don't use capabilities.
[examples/capsicum/](./examples/capsicum/) contains an example that
restricts itself to using a directory passed on the command-line, and then
tries reading `/etc/passwd` via the stdlib.
Running on FreeBSD, you should see:
```
mkdir /tmp/cap
dune exec -- ./examples/capsicum/main.exe /tmp/cap
+Opened directory <fs:/tmp/cap>
+Capsicum mode enabled
+Using the file-system via the directory resource works:
+Writing <cap:capsicum-test.txt>...
+Read: "A test file"
+Bypassing Eio and accessing other resources should fail in Capsicum mode:
Fatal error: exception Sys_error("/etc/passwd: Not permitted in capability mode")
```
## Running processes
Spawning a child process can be done using the [Eio.Process][] module:
@ -913,11 +924,19 @@ The mock backend provides a mock clock that advances automatically where there i
- : unit = ()
```
Note: You could also just use `Eio_unix.sleep 5.0` if you don't want to pass a clock around.
This is especially useful if you need to insert a delay for some quick debugging.
## Multicore Support
OCaml allows a program to create multiple *domains* in which to run code, allowing multiple CPUs to be used at once.
Fibers are scheduled cooperatively within a single domain, but fibers in different domains run in parallel.
This is useful to perform CPU-intensive operations quickly.
This is useful to perform CPU-intensive operations quickly
(though extra care needs to be taken when using multiple cores; see the [Multicore Guide](./doc/multicore.md) for details).
### Domain Manager
[Eio.Domain_manager][] provides a basic API for spawning domains.
For example, let's say we have a CPU intensive task:
```ocaml
@ -931,7 +950,7 @@ let sum_to n =
!total
```
We can use [Eio.Domain_manager][] to run this in a separate domain:
We can use the domain manager to run this in a separate domain:
```ocaml
let main ~domain_mgr =
@ -958,6 +977,10 @@ let main ~domain_mgr =
- : unit = ()
```
<p align='center'>
<img src="./doc/traces/multicore-posix.svg"/>
</p>
Notes:
- `traceln` can be used safely from multiple domains.
@ -969,8 +992,72 @@ Notes:
- `Domain_manager.run` waits for the domain to finish, but it allows other fibers to run while waiting.
This is why we use `Fiber.both` to create multiple fibers.
For more information, see the [Multicore Guide](./doc/multicore.md).
### Executor Pool
An [Eio.Executor_pool][] distributes jobs among a pool of domain workers.
Domains are reused and can execute multiple jobs concurrently.
Each domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.
The `~weight` represents the expected proportion of a CPU core that the job will take up.
Jobs are queued up if they cannot be started immediately due to all domain workers being busy (`>= 1.0`).
This is the recommended way of leveraging OCaml 5's multicore capabilities.
Usually you will only want one pool for an entire application, so the pool is typically created when the application starts:
<!-- $MDX skip -->
```ocaml
let () =
Eio_main.run @@ fun env ->
Switch.run @@ fun sw ->
let dm = Eio.Stdenv.domain_mgr env in
main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)
```
The pool starts its domain workers immediately upon creation.
The pool will not block our switch `sw` from completing;
when the switch finishes, all domain workers and running jobs are cancelled.
`~domain_count` is the number of domain workers to create.
The total number of domains should not exceed `Domain.recommended_domain_count` or the number of cores on your system.
We can run the previous example using an Executor Pool like this:
```ocaml
let main ~pool =
let test n =
traceln "sum 1..%d = %d" n
(Eio.Executor_pool.submit_exn pool ~weight:1.0
(fun () -> sum_to n))
in
Fiber.both
(fun () -> test 100000)
(fun () -> test 50000)
```
<!-- $MDX non-deterministic=output -->
```ocaml
# Eio_main.run @@ fun env ->
Switch.run @@ fun sw ->
let dm = Eio.Stdenv.domain_mgr env in
main ~pool:(Eio.Executor_pool.create ~sw ~domain_count:2 dm)
+Starting CPU-intensive task...
+Starting CPU-intensive task...
+Finished
+sum 1..50000 = 1250025000
+Finished
+sum 1..100000 = 5000050000
- : unit = ()
```
`~weight` is the anticipated proportion of a CPU core used by the job.
In other words, the fraction of time actively spent executing OCaml code, not just waiting for I/O or system calls.
In the above code snippet we use `~weight:1.0` because the job is entirely CPU-bound: it never waits for I/O or other syscalls.
`~weight` must be `>= 0.0` and `<= 1.0`.
Example: given an IO-bound job that averages 2% of one CPU core, pass `~weight:0.02`.
Each domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.
## Synchronisation Tools
Eio provides several sub-modules for communicating between fibers,
@ -1222,6 +1309,8 @@ The `Fiber.check ()` checks whether the worker itself has been cancelled, and ex
It's not actually necessary in this case,
because if we continue instead then the following `Stream.take` will perform the check anyway.
Note: in a real system, you would probably use [Eio.Executor_pool][] for this rather than making your own pool.
### Mutexes and Semaphores
Eio also provides `Mutex` and `Semaphore` sub-modules.
@ -1491,6 +1580,9 @@ In particular, if you test your code by providing (deterministic) mocks then the
An easy way to write tests is by having the mocks call `traceln` and then comparing the trace output with the expected output.
See Eio's own tests for examples, e.g., [tests/switch.md](tests/switch.md).
Note: this only applies to the high-level APIs in the `Eio` module.
Programs can behave non-deterministically when using `Eio_unix` or the various `Low_level` APIs provided by the backends.
## Provider Interfaces
Eio applications use resources by calling functions (such as `Eio.Flow.write`).
@ -1529,7 +1621,8 @@ It can then be used like any other Eio flow:
## Example Applications
- [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`.
- [ocaml-multicore/retro-httpaf-bench](https://github.com/ocaml-multicore/retro-httpaf-bench) includes a simple HTTP server using Eio. It shows how to use Eio with `httpaf`, and how to use multiple domains for increased performance.
- [cohttp-eio/examples](https://github.com/mirage/ocaml-cohttp/tree/master/cohttp-eio/examples) shows how to use Eio with HTTP.
- [capnp-rpc](https://github.com/mirage/capnp-rpc) shows how to use Eio with Cap'n Proto.
- [Awesome Multicore OCaml][] lists many other projects.
## Integrations
@ -1608,10 +1701,8 @@ To demonstrate **kcas**
let's first create a couple of shared memory locations
```ocaml
# let x = Loc.make 0
val x : int Loc.t = <abstr>
# let y = Loc.make 0
val y : int Loc.t = <abstr>
let x = Loc.make 0
let y = Loc.make 0
```
and spawn a domain
@ -1788,6 +1879,7 @@ Some background about the effects system can be found in:
[Eio.Path]: https://ocaml-multicore.github.io/eio/eio/Eio/Path/index.html
[Eio.Time]: https://ocaml-multicore.github.io/eio/eio/Eio/Time/index.html
[Eio.Domain_manager]: https://ocaml-multicore.github.io/eio/eio/Eio/Domain_manager/index.html
[Eio.Executor_pool]: https://ocaml-multicore.github.io/eio/eio/Eio/Executor_pool/index.html
[Eio.Promise]: https://ocaml-multicore.github.io/eio/eio/Eio/Promise/index.html
[Eio.Stream]: https://ocaml-multicore.github.io/eio/eio/Eio/Stream/index.html
[Eio_posix]: https://ocaml-multicore.github.io/eio/eio_posix/Eio_posix/index.html
@ -1804,9 +1896,10 @@ Some background about the effects system can be found in:
[Eio.Condition]: https://ocaml-multicore.github.io/eio/eio/Eio/Condition/index.html
[Domainslib]: https://github.com/ocaml-multicore/domainslib
[kcas]: https://github.com/ocaml-multicore/kcas
[Meio]: https://github.com/tarides/meio
[Lambda Capabilities]: https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities/
[Eio.Process]: https://ocaml-multicore.github.io/eio/eio/Eio/Process/index.html
[Dev meetings]: https://docs.google.com/document/d/1ZBfbjAkvEkv9ldumpZV5VXrEc_HpPeYjHPW_TiwJe4Q
[Olly]: https://github.com/tarides/runtime_events_tools
[eio-trace]: https://github.com/ocaml-multicore/eio-trace
[cap_enter]: https://man.freebsd.org/cgi/man.cgi?query=cap_enter
[eio_js]: https://github.com/ocaml-multicore/eio_js

View File

@ -1,8 +1,8 @@
FROM ocaml/opam:debian-11-ocaml-5.1
FROM ocaml/opam:debian-11-ocaml-5.2
# Make sure we're using opam-2.1:
RUN sudo ln -sf /usr/bin/opam-2.1 /usr/bin/opam
# Ensure opam-repository is up-to-date:
RUN cd opam-repository && git pull -q origin 0ac3fc79fd11ee365dd46119d43e9763cf57da52 && opam update
RUN cd opam-repository && git pull -q origin 97de3378749cf8d2d70a5d710d310e5cc17c9dea && opam update
# Install Eio's dependencies (adding just the opam files first to help with caching):
RUN mkdir eio
WORKDIR eio

View File

@ -45,6 +45,19 @@ let () =
"metrics", `List metrics;
]
in
(* The benchmark machine runs an old Docker that blocks pidfd_open *)
(* let uname = Eio.Process.parse_out env#process_mgr Eio.Buf_read.take_all ["uname"; "-a"] in *)
let uname =
let ch = Unix.open_process_in "uname -a" in
let x = input_line ch in
close_in ch;
x
in
Fmt.pr "%a@." (Yojson.Safe.pretty_print ~std:true) @@ `Assoc [
"config", `Assoc [
"uname", `String uname;
"backend", `String env#backend_id;
"recommended_domain_count", `Int (Domain.recommended_domain_count ());
];
"results", `List (List.map run benchmarks);
]

View File

@ -4,21 +4,22 @@
* [Introduction](#introduction)
* [Problems with Multicore Programming](#problems-with-multicore-programming)
* [Optimisation 1: Caching](#optimisation-1-caching)
* [Optimisation 2: Out-of-Order Execution](#optimisation-2-out-of-order-execution)
* [Optimisation 3: Compiler Optimisations](#optimisation-3-compiler-optimisations)
* [Optimisation 4: Multiple Cores](#optimisation-4-multiple-cores)
* [Optimisation 1: Caching](#optimisation-1-caching)
* [Optimisation 2: Out-of-Order Execution](#optimisation-2-out-of-order-execution)
* [Optimisation 3: Compiler Optimisations](#optimisation-3-compiler-optimisations)
* [Optimisation 4: Multiple Cores](#optimisation-4-multiple-cores)
* [The OCaml Memory Model](#the-ocaml-memory-model)
* [Atomic Locations](#atomic-locations)
* [Initialisation](#initialisation)
* [Guidelines](#guidelines)
* [Atomic Locations](#atomic-locations)
* [Initialisation](#initialisation)
* [Safety Guidelines](#safety-guidelines)
* [Performance Guidelines](#performance-guidelines)
* [Further Reading](#further-reading)
<!-- vim-markdown-toc -->
## Introduction
OCaml 5.00 adds support for using multiple CPU cores in a single OCaml process.
OCaml 5.0 adds support for using multiple CPU cores in a single OCaml process.
An OCaml process is made up of one or more *domains*, and
the operating system can run each domain on a different core, so that they run in parallel.
This can make programs run much faster, but also introduces new ways for programs to go wrong.
@ -446,7 +447,7 @@ So it will always see a correct list:
- : unit = ()
```
## Guidelines
## Safety Guidelines
It's important to understand the above to avoid writing incorrect code,
but there are several general principles that avoid most problems:
@ -502,6 +503,28 @@ Finally, note that OCaml remains type-safe even with multiple domains.
For example, accessing a `Queue` in parallel from multiple domains may result in a corrupted queue,
but it won't cause a segfault.
## Performance Guidelines
The following recommendations will help you extract as much performance as possible from your hardware:
- There's a certain overhead associated with placing execution onto another domain,
but that overhead will be paid off quickly if your job takes at least a few milliseconds to complete.
Jobs that complete under 2-5ms may not be worth running on a separate domain.
- Similarly, jobs that are 100% I/O-bound may not be worth running on a separate domain.
The small initial overhead is simply never recouped.
- If your program never hits 100% CPU usage, it's unlikely that parallelizing it will improve performance.
- Try to avoid reading or writing to memory that's modified by other domains after the start of your job.
Ideally, your jobs shouldn't need to interact with other domains' "working data".
Aim to make your jobs as independent as possible.
If unavoidable, the [Saturn](https://github.com/ocaml-multicore/saturn) library offers a collection of efficient threadsafe data structures.
- It's often easier to design code to be multithreading friendly from the start
(by making longer, independent jobs) than by refactoring existing code.
- There's a cost associated with creating a domain, so try to use the same domains for longer periods of time.
`Eio.Executor_pool` takes care of this automatically.
- Obviously, reuse the same executor pool whenever possible! Don't recreate it over and over.
- Having a large number of domains active at the same time imposes additional overhead on
both the OS scheduler and the OCaml runtime, even if those domains are idle.
## Further Reading
- [OCaml Memory Model][] describes the full details of the memory model.

View File

@ -1,4 +1,4 @@
all: both-posix.svg cancel-posix.svg switch-mock.svg net-posix.svg
all: both-posix.svg cancel-posix.svg switch-mock.svg net-posix.svg multicore-posix.svg
%.svg: %.fxt
eio-trace render "$<"

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280pt" height="152pt" viewBox="0 0 1280 152" version="1.1">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280pt" height="184pt" viewBox="0 0 1280 184" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
@ -81,489 +81,450 @@
<path style="stroke:none;" d="M 0.40625 1.421875 L 0.40625 -5.640625 L 4.40625 -5.640625 L 4.40625 1.421875 Z M 0.84375 0.96875 L 3.953125 0.96875 L 3.953125 -5.1875 L 0.84375 -5.1875 Z M 0.84375 0.96875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-1">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 -5.171875 L 0.75 -5.171875 Z M 0.75 -6.078125 "/>
<path style="stroke:none;" d="M 0.78125 -5.828125 L 1.578125 -5.828125 L 1.578125 -0.40625 C 1.578125 0.300781 1.441406 0.8125 1.171875 1.125 C 0.910156 1.445312 0.484375 1.609375 -0.109375 1.609375 L -0.421875 1.609375 L -0.421875 0.9375 L -0.171875 0.9375 C 0.179688 0.9375 0.425781 0.835938 0.5625 0.640625 C 0.707031 0.453125 0.78125 0.101562 0.78125 -0.40625 Z M 0.78125 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-2">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 0.84375 -3.640625 L 5.859375 -3.640625 L 5.859375 -2.984375 L 0.84375 -2.984375 Z M 0.84375 -2.046875 L 5.859375 -2.046875 L 5.859375 -1.375 L 0.84375 -1.375 Z M 0.84375 -2.046875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d="M 1 -0.671875 L 2.28125 -0.671875 L 2.28125 -5.109375 L 0.875 -4.828125 L 0.875 -5.546875 L 2.28125 -5.828125 L 3.0625 -5.828125 L 3.0625 -0.671875 L 4.359375 -0.671875 L 4.359375 0 L 1 0 Z M 1 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-5">
<path style="stroke:none;" d="M 1.53125 -0.671875 L 4.296875 -0.671875 L 4.296875 0 L 0.59375 0 L 0.59375 -0.671875 C 0.882812 -0.972656 1.289062 -1.382812 1.8125 -1.90625 C 2.332031 -2.4375 2.65625 -2.773438 2.78125 -2.921875 C 3.039062 -3.203125 3.21875 -3.441406 3.3125 -3.640625 C 3.414062 -3.835938 3.46875 -4.03125 3.46875 -4.21875 C 3.46875 -4.53125 3.359375 -4.785156 3.140625 -4.984375 C 2.921875 -5.179688 2.640625 -5.28125 2.296875 -5.28125 C 2.046875 -5.28125 1.78125 -5.234375 1.5 -5.140625 C 1.226562 -5.054688 0.9375 -4.925781 0.625 -4.75 L 0.625 -5.546875 C 0.945312 -5.679688 1.242188 -5.78125 1.515625 -5.84375 C 1.796875 -5.90625 2.050781 -5.9375 2.28125 -5.9375 C 2.882812 -5.9375 3.363281 -5.785156 3.71875 -5.484375 C 4.082031 -5.179688 4.265625 -4.78125 4.265625 -4.28125 C 4.265625 -4.039062 4.21875 -3.8125 4.125 -3.59375 C 4.03125 -3.375 3.867188 -3.117188 3.640625 -2.828125 C 3.566406 -2.753906 3.351562 -2.535156 3 -2.171875 C 2.65625 -1.816406 2.164062 -1.316406 1.53125 -0.671875 Z M 1.53125 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-6">
<path style="stroke:none;" d="M 3.25 -3.140625 C 3.625 -3.066406 3.914062 -2.898438 4.125 -2.640625 C 4.34375 -2.390625 4.453125 -2.078125 4.453125 -1.703125 C 4.453125 -1.117188 4.253906 -0.671875 3.859375 -0.359375 C 3.460938 -0.046875 2.898438 0.109375 2.171875 0.109375 C 1.921875 0.109375 1.664062 0.0820312 1.40625 0.03125 C 1.15625 -0.0078125 0.890625 -0.078125 0.609375 -0.171875 L 0.609375 -0.9375 C 0.828125 -0.8125 1.066406 -0.710938 1.328125 -0.640625 C 1.585938 -0.578125 1.859375 -0.546875 2.140625 -0.546875 C 2.640625 -0.546875 3.019531 -0.644531 3.28125 -0.84375 C 3.539062 -1.039062 3.671875 -1.328125 3.671875 -1.703125 C 3.671875 -2.046875 3.546875 -2.3125 3.296875 -2.5 C 3.054688 -2.695312 2.722656 -2.796875 2.296875 -2.796875 L 1.625 -2.796875 L 1.625 -3.4375 L 2.328125 -3.4375 C 2.710938 -3.4375 3.007812 -3.515625 3.21875 -3.671875 C 3.425781 -3.828125 3.53125 -4.050781 3.53125 -4.34375 C 3.53125 -4.644531 3.421875 -4.875 3.203125 -5.03125 C 2.992188 -5.195312 2.691406 -5.28125 2.296875 -5.28125 C 2.078125 -5.28125 1.84375 -5.253906 1.59375 -5.203125 C 1.351562 -5.160156 1.082031 -5.085938 0.78125 -4.984375 L 0.78125 -5.6875 C 1.082031 -5.769531 1.363281 -5.832031 1.625 -5.875 C 1.882812 -5.914062 2.132812 -5.9375 2.375 -5.9375 C 2.96875 -5.9375 3.4375 -5.800781 3.78125 -5.53125 C 4.132812 -5.257812 4.3125 -4.890625 4.3125 -4.421875 C 4.3125 -4.097656 4.21875 -3.828125 4.03125 -3.609375 C 3.851562 -3.390625 3.59375 -3.234375 3.25 -3.140625 Z M 3.25 -3.140625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-7">
<path style="stroke:none;" d="M 0.78125 -5.828125 L 4.140625 -5.828125 L 4.140625 -5.171875 L 1.578125 -5.171875 L 1.578125 -3.453125 L 3.890625 -3.453125 L 3.890625 -2.78125 L 1.578125 -2.78125 L 1.578125 0 L 0.78125 0 Z M 0.78125 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-8">
<path style="stroke:none;" d="M 3.296875 -3.703125 C 3.210938 -3.753906 3.125 -3.789062 3.03125 -3.8125 C 2.9375 -3.832031 2.832031 -3.84375 2.71875 -3.84375 C 2.3125 -3.84375 2 -3.707031 1.78125 -3.4375 C 1.5625 -3.175781 1.453125 -2.800781 1.453125 -2.3125 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.597656 -3.960938 1.789062 -4.15625 2.03125 -4.28125 C 2.28125 -4.414062 2.578125 -4.484375 2.921875 -4.484375 C 2.972656 -4.484375 3.023438 -4.476562 3.078125 -4.46875 C 3.140625 -4.46875 3.207031 -4.457031 3.28125 -4.4375 Z M 3.296875 -3.703125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-9">
<path style="stroke:none;" d="M 3.546875 -4.25 L 3.546875 -3.5625 C 3.335938 -3.664062 3.125 -3.742188 2.90625 -3.796875 C 2.6875 -3.847656 2.460938 -3.875 2.234375 -3.875 C 1.878906 -3.875 1.609375 -3.816406 1.421875 -3.703125 C 1.242188 -3.597656 1.15625 -3.4375 1.15625 -3.21875 C 1.15625 -3.050781 1.21875 -2.921875 1.34375 -2.828125 C 1.476562 -2.734375 1.738281 -2.644531 2.125 -2.5625 L 2.375 -2.5 C 2.882812 -2.394531 3.242188 -2.242188 3.453125 -2.046875 C 3.671875 -1.847656 3.78125 -1.566406 3.78125 -1.203125 C 3.78125 -0.796875 3.617188 -0.472656 3.296875 -0.234375 C 2.972656 -0.00390625 2.53125 0.109375 1.96875 0.109375 C 1.738281 0.109375 1.492188 0.0820312 1.234375 0.03125 C 0.984375 -0.0078125 0.71875 -0.0703125 0.4375 -0.15625 L 0.4375 -0.90625 C 0.695312 -0.769531 0.957031 -0.664062 1.21875 -0.59375 C 1.476562 -0.519531 1.734375 -0.484375 1.984375 -0.484375 C 2.328125 -0.484375 2.585938 -0.539062 2.765625 -0.65625 C 2.953125 -0.78125 3.046875 -0.945312 3.046875 -1.15625 C 3.046875 -1.351562 2.976562 -1.503906 2.84375 -1.609375 C 2.707031 -1.710938 2.421875 -1.8125 1.984375 -1.90625 L 1.734375 -1.96875 C 1.285156 -2.0625 0.960938 -2.203125 0.765625 -2.390625 C 0.566406 -2.585938 0.46875 -2.851562 0.46875 -3.1875 C 0.46875 -3.601562 0.613281 -3.921875 0.90625 -4.140625 C 1.195312 -4.367188 1.609375 -4.484375 2.140625 -4.484375 C 2.410156 -4.484375 2.660156 -4.460938 2.890625 -4.421875 C 3.128906 -4.378906 3.347656 -4.320312 3.546875 -4.25 Z M 3.546875 -4.25 "/>
</symbol>
<symbol overflow="visible" id="glyph1-10">
<path style="stroke:none;" d="M 1.46875 -5.625 L 1.46875 -4.375 L 2.953125 -4.375 L 2.953125 -3.8125 L 1.46875 -3.8125 L 1.46875 -1.4375 C 1.46875 -1.082031 1.515625 -0.851562 1.609375 -0.75 C 1.710938 -0.65625 1.910156 -0.609375 2.203125 -0.609375 L 2.953125 -0.609375 L 2.953125 0 L 2.203125 0 C 1.648438 0 1.269531 -0.101562 1.0625 -0.3125 C 0.851562 -0.519531 0.75 -0.894531 0.75 -1.4375 L 0.75 -3.8125 L 0.21875 -3.8125 L 0.21875 -4.375 L 0.75 -4.375 L 0.75 -5.625 Z M 1.46875 -5.625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-11">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-12">
<path style="stroke:none;" d="M 4.5 -2.375 L 4.5 -2.015625 L 1.1875 -2.015625 C 1.21875 -1.523438 1.367188 -1.148438 1.640625 -0.890625 C 1.910156 -0.628906 2.28125 -0.5 2.75 -0.5 C 3.03125 -0.5 3.300781 -0.53125 3.5625 -0.59375 C 3.820312 -0.664062 4.078125 -0.769531 4.328125 -0.90625 L 4.328125 -0.21875 C 4.066406 -0.113281 3.800781 -0.0351562 3.53125 0.015625 C 3.257812 0.078125 2.988281 0.109375 2.71875 0.109375 C 2.019531 0.109375 1.460938 -0.09375 1.046875 -0.5 C 0.640625 -0.90625 0.4375 -1.453125 0.4375 -2.140625 C 0.4375 -2.859375 0.628906 -3.425781 1.015625 -3.84375 C 1.410156 -4.269531 1.9375 -4.484375 2.59375 -4.484375 C 3.175781 -4.484375 3.640625 -4.289062 3.984375 -3.90625 C 4.328125 -3.53125 4.5 -3.019531 4.5 -2.375 Z M 3.78125 -2.578125 C 3.769531 -2.972656 3.65625 -3.285156 3.4375 -3.515625 C 3.226562 -3.753906 2.945312 -3.875 2.59375 -3.875 C 2.195312 -3.875 1.875 -3.757812 1.625 -3.53125 C 1.382812 -3.300781 1.25 -2.984375 1.21875 -2.578125 Z M 3.78125 -2.578125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-13">
<path style="stroke:none;" d="M 2.75 -2.203125 C 2.164062 -2.203125 1.757812 -2.132812 1.53125 -2 C 1.3125 -1.863281 1.203125 -1.640625 1.203125 -1.328125 C 1.203125 -1.066406 1.285156 -0.859375 1.453125 -0.703125 C 1.617188 -0.554688 1.847656 -0.484375 2.140625 -0.484375 C 2.535156 -0.484375 2.851562 -0.625 3.09375 -0.90625 C 3.332031 -1.195312 3.453125 -1.578125 3.453125 -2.046875 L 3.453125 -2.203125 Z M 4.171875 -2.5 L 4.171875 0 L 3.453125 0 L 3.453125 -0.671875 C 3.296875 -0.398438 3.09375 -0.203125 2.84375 -0.078125 C 2.601562 0.046875 2.304688 0.109375 1.953125 0.109375 C 1.503906 0.109375 1.144531 -0.015625 0.875 -0.265625 C 0.613281 -0.515625 0.484375 -0.851562 0.484375 -1.28125 C 0.484375 -1.769531 0.644531 -2.140625 0.96875 -2.390625 C 1.300781 -2.640625 1.796875 -2.765625 2.453125 -2.765625 L 3.453125 -2.765625 L 3.453125 -2.828125 C 3.453125 -3.160156 3.34375 -3.414062 3.125 -3.59375 C 2.914062 -3.78125 2.613281 -3.875 2.21875 -3.875 C 1.96875 -3.875 1.722656 -3.84375 1.484375 -3.78125 C 1.242188 -3.71875 1.015625 -3.628906 0.796875 -3.515625 L 0.796875 -4.171875 C 1.066406 -4.273438 1.320312 -4.351562 1.5625 -4.40625 C 1.8125 -4.457031 2.054688 -4.484375 2.296875 -4.484375 C 2.921875 -4.484375 3.390625 -4.316406 3.703125 -3.984375 C 4.015625 -3.660156 4.171875 -3.164062 4.171875 -2.5 Z M 4.171875 -2.5 "/>
</symbol>
<symbol overflow="visible" id="glyph1-14">
<path style="stroke:none;" d="M 3.640625 -3.71875 L 3.640625 -6.078125 L 4.359375 -6.078125 L 4.359375 0 L 3.640625 0 L 3.640625 -0.65625 C 3.484375 -0.394531 3.289062 -0.203125 3.0625 -0.078125 C 2.832031 0.046875 2.554688 0.109375 2.234375 0.109375 C 1.703125 0.109375 1.269531 -0.0976562 0.9375 -0.515625 C 0.601562 -0.941406 0.4375 -1.5 0.4375 -2.1875 C 0.4375 -2.875 0.601562 -3.425781 0.9375 -3.84375 C 1.269531 -4.269531 1.703125 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.421875 3.0625 -4.296875 C 3.289062 -4.171875 3.484375 -3.976562 3.640625 -3.71875 Z M 1.1875 -2.1875 C 1.1875 -1.65625 1.296875 -1.238281 1.515625 -0.9375 C 1.734375 -0.632812 2.03125 -0.484375 2.40625 -0.484375 C 2.789062 -0.484375 3.09375 -0.632812 3.3125 -0.9375 C 3.53125 -1.238281 3.640625 -1.65625 3.640625 -2.1875 C 3.640625 -2.71875 3.53125 -3.128906 3.3125 -3.421875 C 3.09375 -3.722656 2.789062 -3.875 2.40625 -3.875 C 2.03125 -3.875 1.734375 -3.722656 1.515625 -3.421875 C 1.296875 -3.128906 1.1875 -2.71875 1.1875 -2.1875 Z M 1.1875 -2.1875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-15">
<path style="stroke:none;" d="M 2.96875 -6.078125 L 2.96875 -5.484375 L 2.28125 -5.484375 C 2.019531 -5.484375 1.835938 -5.429688 1.734375 -5.328125 C 1.640625 -5.222656 1.59375 -5.035156 1.59375 -4.765625 L 1.59375 -4.375 L 2.78125 -4.375 L 2.78125 -3.8125 L 1.59375 -3.8125 L 1.59375 0 L 0.875 0 L 0.875 -3.8125 L 0.1875 -3.8125 L 0.1875 -4.375 L 0.875 -4.375 L 0.875 -4.6875 C 0.875 -5.164062 0.984375 -5.515625 1.203125 -5.734375 C 1.429688 -5.960938 1.796875 -6.078125 2.296875 -6.078125 Z M 2.96875 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-16">
<path style="stroke:none;" d="M 2.453125 -3.875 C 2.066406 -3.875 1.757812 -3.722656 1.53125 -3.421875 C 1.3125 -3.117188 1.203125 -2.707031 1.203125 -2.1875 C 1.203125 -1.664062 1.3125 -1.253906 1.53125 -0.953125 C 1.757812 -0.648438 2.066406 -0.5 2.453125 -0.5 C 2.835938 -0.5 3.140625 -0.648438 3.359375 -0.953125 C 3.585938 -1.253906 3.703125 -1.664062 3.703125 -2.1875 C 3.703125 -2.707031 3.585938 -3.117188 3.359375 -3.421875 C 3.140625 -3.722656 2.835938 -3.875 2.453125 -3.875 Z M 2.453125 -4.484375 C 3.078125 -4.484375 3.566406 -4.28125 3.921875 -3.875 C 4.273438 -3.46875 4.453125 -2.90625 4.453125 -2.1875 C 4.453125 -1.46875 4.273438 -0.90625 3.921875 -0.5 C 3.566406 -0.09375 3.078125 0.109375 2.453125 0.109375 C 1.828125 0.109375 1.332031 -0.09375 0.96875 -0.5 C 0.613281 -0.90625 0.4375 -1.46875 0.4375 -2.1875 C 0.4375 -2.90625 0.613281 -3.46875 0.96875 -3.875 C 1.332031 -4.28125 1.828125 -4.484375 2.453125 -4.484375 Z M 2.453125 -4.484375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-17">
<path style="stroke:none;" d="M 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -2.484375 L 3.59375 -4.375 L 4.515625 -4.375 L 2.1875 -2.328125 L 4.609375 0 L 3.671875 0 L 1.453125 -2.140625 L 1.453125 0 L 0.734375 0 Z M 0.734375 -6.078125 "/>
<symbol overflow="visible" id="glyph1-3">
<path style="stroke:none;" d="M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 Z M 1.453125 -3.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-18">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0.078125 C 1.46875 0.640625 1.363281 1.046875 1.15625 1.296875 C 0.945312 1.546875 0.601562 1.671875 0.125 1.671875 L -0.140625 1.671875 L -0.140625 1.0625 L 0.046875 1.0625 C 0.316406 1.0625 0.5 0.992188 0.59375 0.859375 C 0.695312 0.734375 0.75 0.472656 0.75 0.078125 Z M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 -5.171875 L 0.75 -5.171875 Z M 0.75 -6.078125 "/>
<symbol overflow="visible" id="glyph1-4">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph1-19">
<path style="stroke:none;" d="M 4.28125 -5.640625 L 4.28125 -4.875 C 3.976562 -5.019531 3.691406 -5.125 3.421875 -5.1875 C 3.160156 -5.257812 2.910156 -5.296875 2.671875 -5.296875 C 2.234375 -5.296875 1.898438 -5.210938 1.671875 -5.046875 C 1.441406 -4.878906 1.328125 -4.644531 1.328125 -4.34375 C 1.328125 -4.082031 1.398438 -3.882812 1.546875 -3.75 C 1.703125 -3.625 2 -3.519531 2.4375 -3.4375 L 2.90625 -3.34375 C 3.5 -3.226562 3.9375 -3.03125 4.21875 -2.75 C 4.5 -2.46875 4.640625 -2.085938 4.640625 -1.609375 C 4.640625 -1.046875 4.445312 -0.617188 4.0625 -0.328125 C 3.6875 -0.0351562 3.132812 0.109375 2.40625 0.109375 C 2.125 0.109375 1.828125 0.078125 1.515625 0.015625 C 1.203125 -0.046875 0.878906 -0.140625 0.546875 -0.265625 L 0.546875 -1.078125 C 0.867188 -0.890625 1.179688 -0.75 1.484375 -0.65625 C 1.796875 -0.570312 2.101562 -0.53125 2.40625 -0.53125 C 2.851562 -0.53125 3.195312 -0.617188 3.4375 -0.796875 C 3.6875 -0.972656 3.8125 -1.222656 3.8125 -1.546875 C 3.8125 -1.835938 3.722656 -2.0625 3.546875 -2.21875 C 3.367188 -2.382812 3.082031 -2.507812 2.6875 -2.59375 L 2.203125 -2.6875 C 1.609375 -2.800781 1.179688 -2.984375 0.921875 -3.234375 C 0.660156 -3.484375 0.53125 -3.832031 0.53125 -4.28125 C 0.53125 -4.789062 0.710938 -5.191406 1.078125 -5.484375 C 1.441406 -5.785156 1.941406 -5.9375 2.578125 -5.9375 C 2.847656 -5.9375 3.125 -5.910156 3.40625 -5.859375 C 3.695312 -5.816406 3.988281 -5.742188 4.28125 -5.640625 Z M 4.28125 -5.640625 "/>
<symbol overflow="visible" id="glyph1-5">
<path style="stroke:none;" d="M 1 -0.671875 L 2.28125 -0.671875 L 2.28125 -5.109375 L 0.875 -4.828125 L 0.875 -5.546875 L 2.28125 -5.828125 L 3.0625 -5.828125 L 3.0625 -0.671875 L 4.359375 -0.671875 L 4.359375 0 L 1 0 Z M 1 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-20">
<path style="stroke:none;" d="M 3.90625 -4.203125 L 3.90625 -3.53125 C 3.695312 -3.644531 3.488281 -3.726562 3.28125 -3.78125 C 3.082031 -3.84375 2.878906 -3.875 2.671875 -3.875 C 2.203125 -3.875 1.835938 -3.722656 1.578125 -3.421875 C 1.328125 -3.128906 1.203125 -2.71875 1.203125 -2.1875 C 1.203125 -1.65625 1.328125 -1.238281 1.578125 -0.9375 C 1.835938 -0.644531 2.203125 -0.5 2.671875 -0.5 C 2.878906 -0.5 3.082031 -0.523438 3.28125 -0.578125 C 3.488281 -0.640625 3.695312 -0.722656 3.90625 -0.828125 L 3.90625 -0.171875 C 3.707031 -0.078125 3.5 -0.0078125 3.28125 0.03125 C 3.0625 0.0820312 2.832031 0.109375 2.59375 0.109375 C 1.9375 0.109375 1.410156 -0.09375 1.015625 -0.5 C 0.628906 -0.914062 0.4375 -1.476562 0.4375 -2.1875 C 0.4375 -2.894531 0.632812 -3.453125 1.03125 -3.859375 C 1.425781 -4.273438 1.960938 -4.484375 2.640625 -4.484375 C 2.859375 -4.484375 3.070312 -4.457031 3.28125 -4.40625 C 3.5 -4.363281 3.707031 -4.296875 3.90625 -4.203125 Z M 3.90625 -4.203125 "/>
<symbol overflow="visible" id="glyph1-6">
<path style="stroke:none;" d="M 3.546875 -4.25 L 3.546875 -3.5625 C 3.335938 -3.664062 3.125 -3.742188 2.90625 -3.796875 C 2.6875 -3.847656 2.460938 -3.875 2.234375 -3.875 C 1.878906 -3.875 1.609375 -3.816406 1.421875 -3.703125 C 1.242188 -3.597656 1.15625 -3.4375 1.15625 -3.21875 C 1.15625 -3.050781 1.21875 -2.921875 1.34375 -2.828125 C 1.476562 -2.734375 1.738281 -2.644531 2.125 -2.5625 L 2.375 -2.5 C 2.882812 -2.394531 3.242188 -2.242188 3.453125 -2.046875 C 3.671875 -1.847656 3.78125 -1.566406 3.78125 -1.203125 C 3.78125 -0.796875 3.617188 -0.472656 3.296875 -0.234375 C 2.972656 -0.00390625 2.53125 0.109375 1.96875 0.109375 C 1.738281 0.109375 1.492188 0.0820312 1.234375 0.03125 C 0.984375 -0.0078125 0.71875 -0.0703125 0.4375 -0.15625 L 0.4375 -0.90625 C 0.695312 -0.769531 0.957031 -0.664062 1.21875 -0.59375 C 1.476562 -0.519531 1.734375 -0.484375 1.984375 -0.484375 C 2.328125 -0.484375 2.585938 -0.539062 2.765625 -0.65625 C 2.953125 -0.78125 3.046875 -0.945312 3.046875 -1.15625 C 3.046875 -1.351562 2.976562 -1.503906 2.84375 -1.609375 C 2.707031 -1.710938 2.421875 -1.8125 1.984375 -1.90625 L 1.734375 -1.96875 C 1.285156 -2.0625 0.960938 -2.203125 0.765625 -2.390625 C 0.566406 -2.585938 0.46875 -2.851562 0.46875 -3.1875 C 0.46875 -3.601562 0.613281 -3.921875 0.90625 -4.140625 C 1.195312 -4.367188 1.609375 -4.484375 2.140625 -4.484375 C 2.410156 -4.484375 2.660156 -4.460938 2.890625 -4.421875 C 3.128906 -4.378906 3.347656 -4.320312 3.546875 -4.25 Z M 3.546875 -4.25 "/>
</symbol>
<symbol overflow="visible" id="glyph1-21">
<symbol overflow="visible" id="glyph1-7">
<path style="stroke:none;" d="M 1.46875 -5.625 L 1.46875 -4.375 L 2.953125 -4.375 L 2.953125 -3.8125 L 1.46875 -3.8125 L 1.46875 -1.4375 C 1.46875 -1.082031 1.515625 -0.851562 1.609375 -0.75 C 1.710938 -0.65625 1.910156 -0.609375 2.203125 -0.609375 L 2.953125 -0.609375 L 2.953125 0 L 2.203125 0 C 1.648438 0 1.269531 -0.101562 1.0625 -0.3125 C 0.851562 -0.519531 0.75 -0.894531 0.75 -1.4375 L 0.75 -3.8125 L 0.21875 -3.8125 L 0.21875 -4.375 L 0.75 -4.375 L 0.75 -5.625 Z M 1.46875 -5.625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-8">
<path style="stroke:none;" d="M 2.75 -2.203125 C 2.164062 -2.203125 1.757812 -2.132812 1.53125 -2 C 1.3125 -1.863281 1.203125 -1.640625 1.203125 -1.328125 C 1.203125 -1.066406 1.285156 -0.859375 1.453125 -0.703125 C 1.617188 -0.554688 1.847656 -0.484375 2.140625 -0.484375 C 2.535156 -0.484375 2.851562 -0.625 3.09375 -0.90625 C 3.332031 -1.195312 3.453125 -1.578125 3.453125 -2.046875 L 3.453125 -2.203125 Z M 4.171875 -2.5 L 4.171875 0 L 3.453125 0 L 3.453125 -0.671875 C 3.296875 -0.398438 3.09375 -0.203125 2.84375 -0.078125 C 2.601562 0.046875 2.304688 0.109375 1.953125 0.109375 C 1.503906 0.109375 1.144531 -0.015625 0.875 -0.265625 C 0.613281 -0.515625 0.484375 -0.851562 0.484375 -1.28125 C 0.484375 -1.769531 0.644531 -2.140625 0.96875 -2.390625 C 1.300781 -2.640625 1.796875 -2.765625 2.453125 -2.765625 L 3.453125 -2.765625 L 3.453125 -2.828125 C 3.453125 -3.160156 3.34375 -3.414062 3.125 -3.59375 C 2.914062 -3.78125 2.613281 -3.875 2.21875 -3.875 C 1.96875 -3.875 1.722656 -3.84375 1.484375 -3.78125 C 1.242188 -3.71875 1.015625 -3.628906 0.796875 -3.515625 L 0.796875 -4.171875 C 1.066406 -4.273438 1.320312 -4.351562 1.5625 -4.40625 C 1.8125 -4.457031 2.054688 -4.484375 2.296875 -4.484375 C 2.921875 -4.484375 3.390625 -4.316406 3.703125 -3.984375 C 4.015625 -3.660156 4.171875 -3.164062 4.171875 -2.5 Z M 4.171875 -2.5 "/>
</symbol>
<symbol overflow="visible" id="glyph1-9">
<path style="stroke:none;" d="M 3.296875 -3.703125 C 3.210938 -3.753906 3.125 -3.789062 3.03125 -3.8125 C 2.9375 -3.832031 2.832031 -3.84375 2.71875 -3.84375 C 2.3125 -3.84375 2 -3.707031 1.78125 -3.4375 C 1.5625 -3.175781 1.453125 -2.800781 1.453125 -2.3125 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.597656 -3.960938 1.789062 -4.15625 2.03125 -4.28125 C 2.28125 -4.414062 2.578125 -4.484375 2.921875 -4.484375 C 2.972656 -4.484375 3.023438 -4.476562 3.078125 -4.46875 C 3.140625 -4.46875 3.207031 -4.457031 3.28125 -4.4375 Z M 3.296875 -3.703125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-10">
<path style="stroke:none;" d="M 0.75 -4.375 L 1.46875 -4.375 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 -5.171875 L 0.75 -5.171875 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-11">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-22">
<path style="stroke:none;" d="M 0.9375 -4.140625 L 1.765625 -4.140625 L 1.765625 -3.140625 L 0.9375 -3.140625 Z M 0.9375 -1 L 1.765625 -1 L 1.765625 -0.328125 L 1.125 0.9375 L 0.625 0.9375 L 0.9375 -0.328125 Z M 0.9375 -1 "/>
<symbol overflow="visible" id="glyph1-12">
<path style="stroke:none;" d="M 3.640625 -2.234375 C 3.640625 -2.753906 3.53125 -3.15625 3.3125 -3.4375 C 3.09375 -3.726562 2.789062 -3.875 2.40625 -3.875 C 2.019531 -3.875 1.71875 -3.726562 1.5 -3.4375 C 1.289062 -3.15625 1.1875 -2.753906 1.1875 -2.234375 C 1.1875 -1.722656 1.289062 -1.320312 1.5 -1.03125 C 1.71875 -0.75 2.019531 -0.609375 2.40625 -0.609375 C 2.789062 -0.609375 3.09375 -0.75 3.3125 -1.03125 C 3.53125 -1.320312 3.640625 -1.722656 3.640625 -2.234375 Z M 4.359375 -0.546875 C 4.359375 0.203125 4.191406 0.757812 3.859375 1.125 C 3.523438 1.488281 3.019531 1.671875 2.34375 1.671875 C 2.082031 1.671875 1.835938 1.648438 1.609375 1.609375 C 1.390625 1.566406 1.175781 1.507812 0.96875 1.4375 L 0.96875 0.734375 C 1.175781 0.847656 1.382812 0.929688 1.59375 0.984375 C 1.800781 1.046875 2.007812 1.078125 2.21875 1.078125 C 2.695312 1.078125 3.050781 0.953125 3.28125 0.703125 C 3.519531 0.453125 3.640625 0.078125 3.640625 -0.421875 L 3.640625 -0.765625 C 3.484375 -0.515625 3.289062 -0.320312 3.0625 -0.1875 C 2.832031 -0.0625 2.554688 0 2.234375 0 C 1.691406 0 1.253906 -0.203125 0.921875 -0.609375 C 0.597656 -1.023438 0.4375 -1.566406 0.4375 -2.234375 C 0.4375 -2.910156 0.597656 -3.453125 0.921875 -3.859375 C 1.253906 -4.273438 1.691406 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.414062 3.0625 -4.28125 C 3.289062 -4.15625 3.484375 -3.96875 3.640625 -3.71875 L 3.640625 -4.375 L 4.359375 -4.375 Z M 4.359375 -0.546875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-23">
<path style="stroke:none;" d="M 1.453125 -0.65625 L 1.453125 1.671875 L 0.734375 1.671875 L 0.734375 -4.375 L 1.453125 -4.375 L 1.453125 -3.71875 C 1.597656 -3.976562 1.785156 -4.171875 2.015625 -4.296875 C 2.253906 -4.421875 2.53125 -4.484375 2.84375 -4.484375 C 3.375 -4.484375 3.804688 -4.269531 4.140625 -3.84375 C 4.472656 -3.425781 4.640625 -2.875 4.640625 -2.1875 C 4.640625 -1.5 4.472656 -0.941406 4.140625 -0.515625 C 3.804688 -0.0976562 3.375 0.109375 2.84375 0.109375 C 2.53125 0.109375 2.253906 0.046875 2.015625 -0.078125 C 1.785156 -0.203125 1.597656 -0.394531 1.453125 -0.65625 Z M 3.890625 -2.1875 C 3.890625 -2.71875 3.78125 -3.128906 3.5625 -3.421875 C 3.351562 -3.722656 3.054688 -3.875 2.671875 -3.875 C 2.296875 -3.875 2 -3.722656 1.78125 -3.421875 C 1.5625 -3.128906 1.453125 -2.71875 1.453125 -2.1875 C 1.453125 -1.65625 1.5625 -1.238281 1.78125 -0.9375 C 2 -0.632812 2.296875 -0.484375 2.671875 -0.484375 C 3.054688 -0.484375 3.351562 -0.632812 3.5625 -0.9375 C 3.78125 -1.238281 3.890625 -1.65625 3.890625 -2.1875 Z M 3.890625 -2.1875 "/>
<symbol overflow="visible" id="glyph1-13">
<path style="stroke:none;" d="M 3.640625 -3.71875 L 3.640625 -6.078125 L 4.359375 -6.078125 L 4.359375 0 L 3.640625 0 L 3.640625 -0.65625 C 3.484375 -0.394531 3.289062 -0.203125 3.0625 -0.078125 C 2.832031 0.046875 2.554688 0.109375 2.234375 0.109375 C 1.703125 0.109375 1.269531 -0.0976562 0.9375 -0.515625 C 0.601562 -0.941406 0.4375 -1.5 0.4375 -2.1875 C 0.4375 -2.875 0.601562 -3.425781 0.9375 -3.84375 C 1.269531 -4.269531 1.703125 -4.484375 2.234375 -4.484375 C 2.554688 -4.484375 2.832031 -4.421875 3.0625 -4.296875 C 3.289062 -4.171875 3.484375 -3.976562 3.640625 -3.71875 Z M 1.1875 -2.1875 C 1.1875 -1.65625 1.296875 -1.238281 1.515625 -0.9375 C 1.734375 -0.632812 2.03125 -0.484375 2.40625 -0.484375 C 2.789062 -0.484375 3.09375 -0.632812 3.3125 -0.9375 C 3.53125 -1.238281 3.640625 -1.65625 3.640625 -2.1875 C 3.640625 -2.71875 3.53125 -3.128906 3.3125 -3.421875 C 3.09375 -3.722656 2.789062 -3.875 2.40625 -3.875 C 2.03125 -3.875 1.734375 -3.722656 1.515625 -3.421875 C 1.296875 -3.128906 1.1875 -2.71875 1.1875 -2.1875 Z M 1.1875 -2.1875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-24">
<path style="stroke:none;" d="M 0.390625 -2.515625 L 2.5 -2.515625 L 2.5 -1.875 L 0.390625 -1.875 Z M 0.390625 -2.515625 "/>
<symbol overflow="visible" id="glyph1-14">
<path style="stroke:none;" d="M 4.5 -2.375 L 4.5 -2.015625 L 1.1875 -2.015625 C 1.21875 -1.523438 1.367188 -1.148438 1.640625 -0.890625 C 1.910156 -0.628906 2.28125 -0.5 2.75 -0.5 C 3.03125 -0.5 3.300781 -0.53125 3.5625 -0.59375 C 3.820312 -0.664062 4.078125 -0.769531 4.328125 -0.90625 L 4.328125 -0.21875 C 4.066406 -0.113281 3.800781 -0.0351562 3.53125 0.015625 C 3.257812 0.078125 2.988281 0.109375 2.71875 0.109375 C 2.019531 0.109375 1.460938 -0.09375 1.046875 -0.5 C 0.640625 -0.90625 0.4375 -1.453125 0.4375 -2.140625 C 0.4375 -2.859375 0.628906 -3.425781 1.015625 -3.84375 C 1.410156 -4.269531 1.9375 -4.484375 2.59375 -4.484375 C 3.175781 -4.484375 3.640625 -4.289062 3.984375 -3.90625 C 4.328125 -3.53125 4.5 -3.019531 4.5 -2.375 Z M 3.78125 -2.578125 C 3.769531 -2.972656 3.65625 -3.285156 3.4375 -3.515625 C 3.226562 -3.753906 2.945312 -3.875 2.59375 -3.875 C 2.195312 -3.875 1.875 -3.757812 1.625 -3.53125 C 1.382812 -3.300781 1.25 -2.984375 1.21875 -2.578125 Z M 3.78125 -2.578125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-25">
<symbol overflow="visible" id="glyph1-15">
<path style="stroke:none;" d="M 1.53125 -0.671875 L 4.296875 -0.671875 L 4.296875 0 L 0.59375 0 L 0.59375 -0.671875 C 0.882812 -0.972656 1.289062 -1.382812 1.8125 -1.90625 C 2.332031 -2.4375 2.65625 -2.773438 2.78125 -2.921875 C 3.039062 -3.203125 3.21875 -3.441406 3.3125 -3.640625 C 3.414062 -3.835938 3.46875 -4.03125 3.46875 -4.21875 C 3.46875 -4.53125 3.359375 -4.785156 3.140625 -4.984375 C 2.921875 -5.179688 2.640625 -5.28125 2.296875 -5.28125 C 2.046875 -5.28125 1.78125 -5.234375 1.5 -5.140625 C 1.226562 -5.054688 0.9375 -4.925781 0.625 -4.75 L 0.625 -5.546875 C 0.945312 -5.679688 1.242188 -5.78125 1.515625 -5.84375 C 1.796875 -5.90625 2.050781 -5.9375 2.28125 -5.9375 C 2.882812 -5.9375 3.363281 -5.785156 3.71875 -5.484375 C 4.082031 -5.179688 4.265625 -4.78125 4.265625 -4.28125 C 4.265625 -4.039062 4.21875 -3.8125 4.125 -3.59375 C 4.03125 -3.375 3.867188 -3.117188 3.640625 -2.828125 C 3.566406 -2.753906 3.351562 -2.535156 3 -2.171875 C 2.65625 -1.816406 2.164062 -1.316406 1.53125 -0.671875 Z M 1.53125 -0.671875 "/>
</symbol>
<symbol overflow="visible" id="glyph1-16">
<path style="stroke:none;" d="M 3.25 -3.140625 C 3.625 -3.066406 3.914062 -2.898438 4.125 -2.640625 C 4.34375 -2.390625 4.453125 -2.078125 4.453125 -1.703125 C 4.453125 -1.117188 4.253906 -0.671875 3.859375 -0.359375 C 3.460938 -0.046875 2.898438 0.109375 2.171875 0.109375 C 1.921875 0.109375 1.664062 0.0820312 1.40625 0.03125 C 1.15625 -0.0078125 0.890625 -0.078125 0.609375 -0.171875 L 0.609375 -0.9375 C 0.828125 -0.8125 1.066406 -0.710938 1.328125 -0.640625 C 1.585938 -0.578125 1.859375 -0.546875 2.140625 -0.546875 C 2.640625 -0.546875 3.019531 -0.644531 3.28125 -0.84375 C 3.539062 -1.039062 3.671875 -1.328125 3.671875 -1.703125 C 3.671875 -2.046875 3.546875 -2.3125 3.296875 -2.5 C 3.054688 -2.695312 2.722656 -2.796875 2.296875 -2.796875 L 1.625 -2.796875 L 1.625 -3.4375 L 2.328125 -3.4375 C 2.710938 -3.4375 3.007812 -3.515625 3.21875 -3.671875 C 3.425781 -3.828125 3.53125 -4.050781 3.53125 -4.34375 C 3.53125 -4.644531 3.421875 -4.875 3.203125 -5.03125 C 2.992188 -5.195312 2.691406 -5.28125 2.296875 -5.28125 C 2.078125 -5.28125 1.84375 -5.253906 1.59375 -5.203125 C 1.351562 -5.160156 1.082031 -5.085938 0.78125 -4.984375 L 0.78125 -5.6875 C 1.082031 -5.769531 1.363281 -5.832031 1.625 -5.875 C 1.882812 -5.914062 2.132812 -5.9375 2.375 -5.9375 C 2.96875 -5.9375 3.4375 -5.800781 3.78125 -5.53125 C 4.132812 -5.257812 4.3125 -4.890625 4.3125 -4.421875 C 4.3125 -4.097656 4.21875 -3.828125 4.03125 -3.609375 C 3.851562 -3.390625 3.59375 -3.234375 3.25 -3.140625 Z M 3.25 -3.140625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-17">
<path style="stroke:none;" d="M 2.734375 -5.0625 L 1.671875 -2.15625 L 3.8125 -2.15625 Z M 2.296875 -5.828125 L 3.1875 -5.828125 L 5.40625 0 L 4.59375 0 L 4.0625 -1.5 L 1.421875 -1.5 L 0.890625 0 L 0.0625 0 Z M 2.296875 -5.828125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-18">
<path style="stroke:none;" d="M 0.75 -6.078125 L 1.46875 -6.078125 L 1.46875 0 L 0.75 0 Z M 0.75 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-26">
<path style="stroke:none;" d="M 0.234375 -4.375 L 1 -4.375 L 2.375 -0.703125 L 3.734375 -4.375 L 4.5 -4.375 L 2.859375 0 L 1.875 0 Z M 0.234375 -4.375 "/>
<symbol overflow="visible" id="glyph1-19">
<path style="stroke:none;" d="M 3.90625 -4.203125 L 3.90625 -3.53125 C 3.695312 -3.644531 3.488281 -3.726562 3.28125 -3.78125 C 3.082031 -3.84375 2.878906 -3.875 2.671875 -3.875 C 2.203125 -3.875 1.835938 -3.722656 1.578125 -3.421875 C 1.328125 -3.128906 1.203125 -2.71875 1.203125 -2.1875 C 1.203125 -1.65625 1.328125 -1.238281 1.578125 -0.9375 C 1.835938 -0.644531 2.203125 -0.5 2.671875 -0.5 C 2.878906 -0.5 3.082031 -0.523438 3.28125 -0.578125 C 3.488281 -0.640625 3.695312 -0.722656 3.90625 -0.828125 L 3.90625 -0.171875 C 3.707031 -0.078125 3.5 -0.0078125 3.28125 0.03125 C 3.0625 0.0820312 2.832031 0.109375 2.59375 0.109375 C 1.9375 0.109375 1.410156 -0.09375 1.015625 -0.5 C 0.628906 -0.914062 0.4375 -1.476562 0.4375 -2.1875 C 0.4375 -2.894531 0.632812 -3.453125 1.03125 -3.859375 C 1.425781 -4.273438 1.960938 -4.484375 2.640625 -4.484375 C 2.859375 -4.484375 3.070312 -4.457031 3.28125 -4.40625 C 3.5 -4.363281 3.707031 -4.296875 3.90625 -4.203125 Z M 3.90625 -4.203125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-27">
<symbol overflow="visible" id="glyph1-20">
<path style="stroke:none;" d="M 4.390625 -2.640625 L 4.390625 0 L 3.671875 0 L 3.671875 -2.625 C 3.671875 -3.03125 3.585938 -3.335938 3.421875 -3.546875 C 3.265625 -3.753906 3.023438 -3.859375 2.703125 -3.859375 C 2.316406 -3.859375 2.007812 -3.734375 1.78125 -3.484375 C 1.5625 -3.234375 1.453125 -2.894531 1.453125 -2.46875 L 1.453125 0 L 0.734375 0 L 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -3.703125 C 1.617188 -3.960938 1.816406 -4.15625 2.046875 -4.28125 C 2.285156 -4.414062 2.554688 -4.484375 2.859375 -4.484375 C 3.367188 -4.484375 3.75 -4.328125 4 -4.015625 C 4.257812 -3.703125 4.390625 -3.242188 4.390625 -2.640625 Z M 4.390625 -2.640625 "/>
</symbol>
<symbol overflow="visible" id="glyph1-21">
<path style="stroke:none;" d="M 2.96875 -6.078125 L 2.96875 -5.484375 L 2.28125 -5.484375 C 2.019531 -5.484375 1.835938 -5.429688 1.734375 -5.328125 C 1.640625 -5.222656 1.59375 -5.035156 1.59375 -4.765625 L 1.59375 -4.375 L 2.78125 -4.375 L 2.78125 -3.8125 L 1.59375 -3.8125 L 1.59375 0 L 0.875 0 L 0.875 -3.8125 L 0.1875 -3.8125 L 0.1875 -4.375 L 0.875 -4.375 L 0.875 -4.6875 C 0.875 -5.164062 0.984375 -5.515625 1.203125 -5.734375 C 1.429688 -5.960938 1.796875 -6.078125 2.296875 -6.078125 Z M 2.96875 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-22">
<path style="stroke:none;" d="M 0.734375 -6.078125 L 1.453125 -6.078125 L 1.453125 -2.484375 L 3.59375 -4.375 L 4.515625 -4.375 L 2.1875 -2.328125 L 4.609375 0 L 3.671875 0 L 1.453125 -2.140625 L 1.453125 0 L 0.734375 0 Z M 0.734375 -6.078125 "/>
</symbol>
<symbol overflow="visible" id="glyph1-23">
<path style="stroke:none;" d="M 0.34375 -4.375 L 1.0625 -4.375 L 1.953125 -0.96875 L 2.84375 -4.375 L 3.703125 -4.375 L 4.59375 -0.96875 L 5.484375 -4.375 L 6.203125 -4.375 L 5.0625 0 L 4.21875 0 L 3.28125 -3.59375 L 2.328125 0 L 1.484375 0 Z M 0.34375 -4.375 "/>
</symbol>
<symbol overflow="visible" id="glyph1-24">
<path style="stroke:none;" d="M 4.28125 -5.640625 L 4.28125 -4.875 C 3.976562 -5.019531 3.691406 -5.125 3.421875 -5.1875 C 3.160156 -5.257812 2.910156 -5.296875 2.671875 -5.296875 C 2.234375 -5.296875 1.898438 -5.210938 1.671875 -5.046875 C 1.441406 -4.878906 1.328125 -4.644531 1.328125 -4.34375 C 1.328125 -4.082031 1.398438 -3.882812 1.546875 -3.75 C 1.703125 -3.625 2 -3.519531 2.4375 -3.4375 L 2.90625 -3.34375 C 3.5 -3.226562 3.9375 -3.03125 4.21875 -2.75 C 4.5 -2.46875 4.640625 -2.085938 4.640625 -1.609375 C 4.640625 -1.046875 4.445312 -0.617188 4.0625 -0.328125 C 3.6875 -0.0351562 3.132812 0.109375 2.40625 0.109375 C 2.125 0.109375 1.828125 0.078125 1.515625 0.015625 C 1.203125 -0.046875 0.878906 -0.140625 0.546875 -0.265625 L 0.546875 -1.078125 C 0.867188 -0.890625 1.179688 -0.75 1.484375 -0.65625 C 1.796875 -0.570312 2.101562 -0.53125 2.40625 -0.53125 C 2.851562 -0.53125 3.195312 -0.617188 3.4375 -0.796875 C 3.6875 -0.972656 3.8125 -1.222656 3.8125 -1.546875 C 3.8125 -1.835938 3.722656 -2.0625 3.546875 -2.21875 C 3.367188 -2.382812 3.082031 -2.507812 2.6875 -2.59375 L 2.203125 -2.6875 C 1.609375 -2.800781 1.179688 -2.984375 0.921875 -3.234375 C 0.660156 -3.484375 0.53125 -3.832031 0.53125 -4.28125 C 0.53125 -4.789062 0.710938 -5.191406 1.078125 -5.484375 C 1.441406 -5.785156 1.941406 -5.9375 2.578125 -5.9375 C 2.847656 -5.9375 3.125 -5.910156 3.40625 -5.859375 C 3.695312 -5.816406 3.988281 -5.742188 4.28125 -5.640625 Z M 4.28125 -5.640625 "/>
</symbol>
</g>
<clipPath id="clip1">
<path d="M 683.339844 48 L 725 48 L 725 59 L 683.339844 59 Z M 683.339844 48 "/>
<path d="M 1149.457031 48 L 1191 48 L 1191 59 L 1149.457031 59 Z M 1149.457031 48 "/>
</clipPath>
<clipPath id="clip2">
<path d="M 812.351562 48 L 847.65625 48 L 847.65625 59 L 812.351562 59 Z M 812.351562 48 "/>
<path d="M 168.484375 80 L 210 80 L 210 91 L 168.484375 91 Z M 168.484375 80 "/>
</clipPath>
<clipPath id="clip3">
<path d="M 1068.0625 48 L 1099.449219 48 L 1099.449219 59 L 1068.0625 59 Z M 1068.0625 48 "/>
<path d="M 945.597656 80 L 987 80 L 987 91 L 945.597656 91 Z M 945.597656 80 "/>
</clipPath>
<clipPath id="clip4">
<path d="M 865.34375 80 L 898 80 L 898 91 L 865.34375 91 Z M 865.34375 80 "/>
<path d="M 250.011719 69 L 303 69 L 303 78 L 250.011719 78 Z M 250.011719 69 "/>
</clipPath>
<clipPath id="clip5">
<path d="M 951.027344 80 L 981.15625 80 L 981.15625 91 L 951.027344 91 Z M 951.027344 80 "/>
<path d="M 979.003906 69 L 1003.683594 69 L 1003.683594 77 L 979.003906 77 Z M 979.003906 69 "/>
</clipPath>
<clipPath id="clip6">
<path d="M 764.328125 112 L 801.253906 112 L 801.253906 123 L 764.328125 123 Z M 764.328125 112 "/>
<path d="M 1018.117188 112 L 1060 112 L 1060 123 L 1018.117188 123 Z M 1018.117188 112 "/>
</clipPath>
<clipPath id="clip7">
<path d="M 910 112 L 939.957031 112 L 939.957031 123 L 910 123 Z M 910 112 "/>
<path d="M 583.375 101 L 637 101 L 637 110 L 583.375 110 Z M 583.375 101 "/>
</clipPath>
<clipPath id="clip8">
<path d="M 1007.089844 112 L 1022.230469 112 L 1022.230469 123 L 1007.089844 123 Z M 1007.089844 112 "/>
<path d="M 1049.300781 101 L 1070.394531 101 L 1070.394531 109 L 1049.300781 109 Z M 1049.300781 101 "/>
</clipPath>
<clipPath id="clip9">
<path d="M 789.792969 101 L 809 101 L 809 110 L 789.792969 110 Z M 789.792969 101 "/>
<path d="M 1082.304688 144 L 1124 144 L 1124 155 L 1082.304688 155 Z M 1082.304688 144 "/>
</clipPath>
<clipPath id="clip10">
<path d="M 931.160156 101 L 950 101 L 950 110 L 931.160156 110 Z M 931.160156 101 "/>
<path d="M 819.433594 133 L 873 133 L 873 142 L 819.433594 142 Z M 819.433594 133 "/>
</clipPath>
<clipPath id="clip11">
<path d="M 1013.980469 101 L 1033 101 L 1033 110 L 1013.980469 110 Z M 1013.980469 101 "/>
<path d="M 1112 133 L 1132.316406 133 L 1132.316406 141 L 1112 141 Z M 1112 133 "/>
</clipPath>
<clipPath id="clip12">
<path d="M 836.167969 37 L 1028 37 L 1028 46 L 836.167969 46 Z M 836.167969 37 "/>
<path d="M 901.039062 37 L 985 37 L 985 45 L 901.039062 45 Z M 901.039062 37 "/>
</clipPath>
<clipPath id="clip13">
<path d="M 1088.320312 37 L 1158 37 L 1158 45 L 1088.320312 45 Z M 1088.320312 37 "/>
<path d="M 1179.296875 37 L 1200.945312 37 L 1200.945312 45 L 1179.296875 45 Z M 1179.296875 37 "/>
</clipPath>
</defs>
<g id="surface2">
<rect x="0" y="0" width="1280" height="152" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4 0 L 4 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 155.40625 0 L 155.40625 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 306.808594 0 L 306.808594 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 458.214844 0 L 458.214844 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 609.621094 0 L 609.621094 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 761.027344 0 L 761.027344 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 912.429688 0 L 912.429688 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1063.835938 0 L 1063.835938 152 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1215.242188 0 L 1215.242188 152 "/>
<rect x="0" y="0" width="1280" height="184" style="fill:rgb(90%,90%,90%);fill-opacity:1;stroke:none;"/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4 0 L 4 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 195.652344 0 L 195.652344 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 387.304688 0 L 387.304688 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 578.957031 0 L 578.957031 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 770.613281 0 L 770.613281 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 962.265625 0 L 962.265625 184 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(70%,70%,70%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1153.917969 0 L 1153.917969 184 "/>
<g style="fill:rgb(40%,40%,40%);fill-opacity:1;">
<use xlink:href="#glyph0-1" x="4" y="147.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="147.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="147.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="147.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="147.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="147.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="147.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="147.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="147.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="147.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="147.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="147.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="147.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="147.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="147.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="147.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="147.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="147.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="147.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="147.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="147.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="147.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="147.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="147.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="147.503906"/>
<use xlink:href="#glyph0-1" x="4" y="179.503906"/>
<use xlink:href="#glyph0-2" x="11.582031" y="179.503906"/>
<use xlink:href="#glyph0-3" x="18.935547" y="179.503906"/>
<use xlink:href="#glyph0-4" x="25.533203" y="179.503906"/>
<use xlink:href="#glyph0-5" x="33.138672" y="179.503906"/>
<use xlink:href="#glyph0-6" x="36.953125" y="179.503906"/>
<use xlink:href="#glyph0-7" x="44.570312" y="179.503906"/>
<use xlink:href="#glyph0-8" x="49.503906" y="179.503906"/>
<use xlink:href="#glyph0-9" x="52.837891" y="179.503906"/>
<use xlink:href="#glyph0-5" x="60.455078" y="179.503906"/>
<use xlink:href="#glyph0-9" x="64.269531" y="179.503906"/>
<use xlink:href="#glyph0-8" x="71.886719" y="179.503906"/>
<use xlink:href="#glyph0-10" x="75.220703" y="179.503906"/>
<use xlink:href="#glyph0-8" x="82.322266" y="179.503906"/>
<use xlink:href="#glyph0-11" x="85.65625" y="179.503906"/>
<use xlink:href="#glyph0-8" x="91.908203" y="179.503906"/>
<use xlink:href="#glyph0-12" x="95.242188" y="179.503906"/>
<use xlink:href="#glyph0-13" x="102.583984" y="179.503906"/>
<use xlink:href="#glyph0-14" x="110.189453" y="179.503906"/>
<use xlink:href="#glyph0-5" x="114.232422" y="179.503906"/>
<use xlink:href="#glyph0-15" x="118.046875" y="179.503906"/>
<use xlink:href="#glyph0-16" x="125.681641" y="179.503906"/>
<use xlink:href="#glyph0-5" x="133.316406" y="179.503906"/>
<use xlink:href="#glyph0-17" x="137.130859" y="179.503906"/>
<use xlink:href="#glyph0-11" x="144.736328" y="179.503906"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 11.480469 46 L 18.324219 46 L 18.324219 60 L 11.480469 60 Z M 11.480469 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 18.324219 46 L 93.210938 46 L 93.210938 60 L 18.324219 60 Z M 18.324219 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 93.207031 46 L 671.605469 46 L 671.605469 60 L 93.207031 60 Z M 93.207031 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 671.605469 46 L 681.339844 46 L 681.339844 60 L 671.605469 60 Z M 671.605469 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 681.339844 46 L 732.652344 46 L 732.652344 60 L 681.339844 60 Z M 681.339844 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 10.746094 46 L 18.640625 46 L 18.640625 60 L 10.746094 60 Z M 10.746094 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 18.640625 46 L 59.882812 46 L 59.882812 60 L 18.640625 60 Z M 18.640625 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 59.886719 46 L 491.21875 46 L 491.21875 60 L 59.886719 60 Z M 59.886719 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 491.21875 46 L 523.03125 46 L 523.03125 60 L 491.21875 60 Z M 491.21875 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 523.035156 46 L 618.25 46 L 618.25 60 L 523.035156 60 Z M 523.035156 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 618.246094 46 L 621.214844 46 L 621.214844 60 L 618.246094 60 Z M 618.246094 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 621.21875 46 L 864.0625 46 L 864.0625 60 L 621.21875 60 Z M 621.21875 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 864.0625 46 L 871 46 L 871 60 L 864.0625 60 Z M 864.0625 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 871 46 L 918.414062 46 L 918.414062 60 L 871 60 Z M 871 46 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="873" y="58"/>
<use xlink:href="#glyph0-7" x="877.705078" y="58"/>
<use xlink:href="#glyph0-2" x="882.638672" y="58"/>
<use xlink:href="#glyph0-3" x="889.992188" y="58"/>
<use xlink:href="#glyph0-19" x="896.589844" y="58"/>
<use xlink:href="#glyph0-20" x="903.972656" y="58"/>
<use xlink:href="#glyph0-13" x="907.306641" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 918.414062 46 L 927.882812 46 L 927.882812 60 L 918.414062 60 Z M 918.414062 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 927.882812 46 L 1134.214844 46 L 1134.214844 60 L 927.882812 60 Z M 927.882812 46 "/>
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-21" x="929.882812" y="58"/>
<use xlink:href="#glyph0-22" x="937.5" y="58"/>
<use xlink:href="#glyph0-8" x="947.314453" y="58"/>
<use xlink:href="#glyph0-18" x="950.648438" y="58"/>
<use xlink:href="#glyph0-3" x="955.353516" y="58"/>
<use xlink:href="#glyph0-4" x="961.951172" y="58"/>
<use xlink:href="#glyph0-23" x="969.556641" y="58"/>
<use xlink:href="#glyph0-2" x="973.371094" y="58"/>
<use xlink:href="#glyph0-22" x="980.724609" y="58"/>
<use xlink:href="#glyph0-2" x="990.539062" y="58"/>
<use xlink:href="#glyph0-8" x="997.892578" y="58"/>
<use xlink:href="#glyph0-18" x="1001.226562" y="58"/>
<use xlink:href="#glyph0-24" x="1005.931641" y="58"/>
<use xlink:href="#glyph0-8" x="1011.931641" y="58"/>
<use xlink:href="#glyph0-9" x="1015.265625" y="58"/>
<use xlink:href="#glyph0-20" x="1022.882812" y="58"/>
<use xlink:href="#glyph0-19" x="1026.216797" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1134.214844 46 L 1147.457031 46 L 1147.457031 60 L 1134.214844 60 Z M 1134.214844 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1147.457031 46 L 1196.730469 46 L 1196.730469 60 L 1147.457031 60 Z M 1147.457031 46 "/>
<g clip-path="url(#clip1)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="683.339844" y="58"/>
<use xlink:href="#glyph0-7" x="688.044922" y="58"/>
<use xlink:href="#glyph0-2" x="692.978516" y="58"/>
<use xlink:href="#glyph0-3" x="700.332031" y="58"/>
<use xlink:href="#glyph0-19" x="706.929688" y="58"/>
<use xlink:href="#glyph0-20" x="714.3125" y="58"/>
<use xlink:href="#glyph0-13" x="717.646484" y="58"/>
<use xlink:href="#glyph0-18" x="1149.457031" y="58"/>
<use xlink:href="#glyph0-7" x="1154.162109" y="58"/>
<use xlink:href="#glyph0-2" x="1159.095703" y="58"/>
<use xlink:href="#glyph0-3" x="1166.449219" y="58"/>
<use xlink:href="#glyph0-19" x="1173.046875" y="58"/>
<use xlink:href="#glyph0-20" x="1180.429688" y="58"/>
<use xlink:href="#glyph0-13" x="1183.763672" y="58"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 732.652344 46 L 740.554688 46 L 740.554688 60 L 732.652344 60 Z M 732.652344 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 740.554688 46 L 805.128906 46 L 805.128906 60 L 740.554688 60 Z M 740.554688 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 805.128906 46 L 810.351562 46 L 810.351562 60 L 805.128906 60 Z M 805.128906 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 810.351562 46 L 847.65625 46 L 847.65625 60 L 810.351562 60 Z M 810.351562 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1196.730469 46 L 1200.945312 46 L 1200.945312 60 L 1196.730469 60 Z M 1196.730469 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 59.886719 78 L 62.914062 78 L 62.914062 92 L 59.886719 92 Z M 59.886719 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 62.914062 78 L 166.484375 78 L 166.484375 92 L 62.914062 92 Z M 62.914062 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 166.484375 78 L 471.960938 78 L 471.960938 92 L 166.484375 92 Z M 166.484375 78 "/>
<g clip-path="url(#clip2)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="812.351562" y="58"/>
<use xlink:href="#glyph0-7" x="817.056641" y="58"/>
<use xlink:href="#glyph0-2" x="821.990234" y="58"/>
<use xlink:href="#glyph0-3" x="829.34375" y="58"/>
<use xlink:href="#glyph0-19" x="835.941406" y="58"/>
<use xlink:href="#glyph0-20" x="843.324219" y="58"/>
<use xlink:href="#glyph0-13" x="846.658203" y="58"/>
<use xlink:href="#glyph0-18" x="168.484375" y="90"/>
<use xlink:href="#glyph0-7" x="173.189453" y="90"/>
<use xlink:href="#glyph0-2" x="178.123047" y="90"/>
<use xlink:href="#glyph0-3" x="185.476562" y="90"/>
<use xlink:href="#glyph0-19" x="192.074219" y="90"/>
<use xlink:href="#glyph0-20" x="199.457031" y="90"/>
<use xlink:href="#glyph0-13" x="202.791016" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 847.660156 46 L 852.671875 46 L 852.671875 60 L 847.660156 60 Z M 847.660156 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 852.671875 46 L 1053.917969 46 L 1053.917969 60 L 852.671875 60 Z M 852.671875 46 "/>
<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
<use xlink:href="#glyph0-21" x="854.671875" y="58"/>
<use xlink:href="#glyph0-22" x="862.289062" y="58"/>
<use xlink:href="#glyph0-8" x="872.103516" y="58"/>
<use xlink:href="#glyph0-18" x="875.4375" y="58"/>
<use xlink:href="#glyph0-3" x="880.142578" y="58"/>
<use xlink:href="#glyph0-4" x="886.740234" y="58"/>
<use xlink:href="#glyph0-23" x="894.345703" y="58"/>
<use xlink:href="#glyph0-2" x="898.160156" y="58"/>
<use xlink:href="#glyph0-22" x="905.513672" y="58"/>
<use xlink:href="#glyph0-2" x="915.328125" y="58"/>
<use xlink:href="#glyph0-8" x="922.681641" y="58"/>
<use xlink:href="#glyph0-18" x="926.015625" y="58"/>
<use xlink:href="#glyph0-24" x="930.720703" y="58"/>
<use xlink:href="#glyph0-8" x="936.720703" y="58"/>
<use xlink:href="#glyph0-9" x="940.054688" y="58"/>
<use xlink:href="#glyph0-20" x="947.671875" y="58"/>
<use xlink:href="#glyph0-19" x="951.005859" y="58"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1053.917969 46 L 1066.0625 46 L 1066.0625 60 L 1053.917969 60 Z M 1053.917969 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1066.0625 46 L 1099.449219 46 L 1099.449219 60 L 1066.0625 60 Z M 1066.0625 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 471.957031 78 L 483.074219 78 L 483.074219 92 L 471.957031 92 Z M 471.957031 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 483.074219 78 L 932.367188 78 L 932.367188 92 L 483.074219 92 Z M 483.074219 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 932.367188 78 L 943.597656 78 L 943.597656 92 L 932.367188 92 Z M 932.367188 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 943.597656 78 L 996.683594 78 L 996.683594 92 L 943.597656 92 Z M 943.597656 78 "/>
<g clip-path="url(#clip3)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1068.0625" y="58"/>
<use xlink:href="#glyph0-7" x="1072.767578" y="58"/>
<use xlink:href="#glyph0-2" x="1077.701172" y="58"/>
<use xlink:href="#glyph0-3" x="1085.054688" y="58"/>
<use xlink:href="#glyph0-19" x="1091.652344" y="58"/>
<use xlink:href="#glyph0-20" x="1099.035156" y="58"/>
<use xlink:href="#glyph0-13" x="1102.369141" y="58"/>
<use xlink:href="#glyph0-18" x="945.597656" y="90"/>
<use xlink:href="#glyph0-7" x="950.302734" y="90"/>
<use xlink:href="#glyph0-2" x="955.236328" y="90"/>
<use xlink:href="#glyph0-3" x="962.589844" y="90"/>
<use xlink:href="#glyph0-19" x="969.1875" y="90"/>
<use xlink:href="#glyph0-20" x="976.570312" y="90"/>
<use xlink:href="#glyph0-13" x="979.904297" y="90"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1099.445312 46 L 1190.574219 46 L 1190.574219 60 L 1099.445312 60 Z M 1099.445312 46 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 93.207031 78 L 97.839844 78 L 97.839844 92 L 93.207031 92 Z M 93.207031 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 97.839844 78 L 311.714844 78 L 311.714844 92 L 97.839844 92 Z M 97.839844 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 311.714844 78 L 657.222656 78 L 657.222656 92 L 311.714844 92 Z M 311.714844 78 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="313.714844" y="90"/>
<use xlink:href="#glyph0-7" x="318.419922" y="90"/>
<use xlink:href="#glyph0-2" x="323.353516" y="90"/>
<use xlink:href="#glyph0-3" x="330.707031" y="90"/>
<use xlink:href="#glyph0-19" x="337.304688" y="90"/>
<use xlink:href="#glyph0-20" x="344.6875" y="90"/>
<use xlink:href="#glyph0-13" x="348.021484" y="90"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 657.222656 78 L 662.550781 78 L 662.550781 92 L 657.222656 92 Z M 657.222656 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 662.550781 78 L 856.289062 78 L 856.289062 92 L 662.550781 92 Z M 662.550781 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 856.289062 78 L 863.34375 78 L 863.34375 92 L 856.289062 92 Z M 856.289062 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 863.34375 78 L 898 78 L 898 92 L 863.34375 92 Z M 863.34375 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 996.683594 78 L 1006.457031 78 L 1006.457031 92 L 996.683594 92 Z M 996.683594 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1006.460938 78 L 1003.683594 78 L 1003.683594 92 L 1006.460938 92 Z M 1006.460938 78 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 248.011719 81 L 248.011719 75 "/>
<g clip-path="url(#clip4)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="865.34375" y="90"/>
<use xlink:href="#glyph0-7" x="870.048828" y="90"/>
<use xlink:href="#glyph0-2" x="874.982422" y="90"/>
<use xlink:href="#glyph0-3" x="882.335938" y="90"/>
<use xlink:href="#glyph0-19" x="888.933594" y="90"/>
<use xlink:href="#glyph0-20" x="896.316406" y="90"/>
<use xlink:href="#glyph0-13" x="899.650391" y="90"/>
<use xlink:href="#glyph1-1" x="250.011719" y="76"/>
<use xlink:href="#glyph1-2" x="252.371094" y="76"/>
<use xlink:href="#glyph1-3" x="257.265625" y="76"/>
<use xlink:href="#glyph1-4" x="262.34375" y="76"/>
<use xlink:href="#glyph1-5" x="264.886719" y="76"/>
<use xlink:href="#glyph1-4" x="269.976562" y="76"/>
<use xlink:href="#glyph1-6" x="272.519531" y="76"/>
<use xlink:href="#glyph1-7" x="276.6875" y="76"/>
<use xlink:href="#glyph1-8" x="279.824219" y="76"/>
<use xlink:href="#glyph1-9" x="284.726562" y="76"/>
<use xlink:href="#glyph1-7" x="288.015625" y="76"/>
<use xlink:href="#glyph1-10" x="291.152344" y="76"/>
<use xlink:href="#glyph1-11" x="293.375" y="76"/>
<use xlink:href="#glyph1-12" x="298.445312" y="76"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 898 78 L 899.648438 78 L 899.648438 92 L 898 92 Z M 898 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 899.652344 78 L 943.257812 78 L 943.257812 92 L 899.652344 92 Z M 899.652344 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 943.257812 78 L 949.027344 78 L 949.027344 92 L 943.257812 92 Z M 943.257812 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 949.027344 78 L 981.15625 78 L 981.15625 92 L 949.027344 92 Z M 949.027344 78 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 977.003906 81 L 977.003906 75 "/>
<g clip-path="url(#clip5)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="951.027344" y="90"/>
<use xlink:href="#glyph0-7" x="955.732422" y="90"/>
<use xlink:href="#glyph0-2" x="960.666016" y="90"/>
<use xlink:href="#glyph0-3" x="968.019531" y="90"/>
<use xlink:href="#glyph0-19" x="974.617188" y="90"/>
<use xlink:href="#glyph0-20" x="982" y="90"/>
<use xlink:href="#glyph0-13" x="985.333984" y="90"/>
<use xlink:href="#glyph1-5" x="979.003906" y="76"/>
<use xlink:href="#glyph1-4" x="984.09375" y="76"/>
<use xlink:href="#glyph1-13" x="986.636719" y="76"/>
<use xlink:href="#glyph1-2" x="991.714844" y="76"/>
<use xlink:href="#glyph1-11" x="996.609375" y="76"/>
<use xlink:href="#glyph1-14" x="1001.679688" y="76"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 981.152344 78 L 982.46875 78 L 982.46875 92 L 981.152344 92 Z M 981.152344 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 982.472656 78 L 1041.113281 78 L 1041.113281 92 L 982.472656 92 Z M 982.472656 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1041.109375 78 L 1048.574219 78 L 1048.574219 92 L 1041.109375 92 Z M 1041.109375 78 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1048.574219 78 L 1046.953125 78 L 1046.953125 92 L 1048.574219 92 Z M 1048.574219 78 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 430.597656 81 L 430.597656 75 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 59.886719 46 L 59.886719 92 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 523.035156 110 L 525.679688 110 L 525.679688 124 L 523.035156 124 Z M 523.035156 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 525.679688 110 L 544.980469 110 L 544.980469 124 L 525.679688 124 Z M 525.679688 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 544.976562 110 L 611.632812 110 L 611.632812 124 L 544.976562 124 Z M 544.976562 110 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="432.597656" y="76"/>
<use xlink:href="#glyph1-2" x="434.820312" y="76"/>
<use xlink:href="#glyph1-3" x="437.363281" y="76"/>
<use xlink:href="#glyph1-2" x="444.066406" y="76"/>
<use xlink:href="#glyph1-4" x="446.609375" y="76"/>
<use xlink:href="#glyph0-18" x="546.976562" y="122"/>
<use xlink:href="#glyph0-7" x="551.681641" y="122"/>
<use xlink:href="#glyph0-2" x="556.615234" y="122"/>
<use xlink:href="#glyph0-3" x="563.96875" y="122"/>
<use xlink:href="#glyph0-19" x="570.566406" y="122"/>
<use xlink:href="#glyph0-20" x="577.949219" y="122"/>
<use xlink:href="#glyph0-13" x="581.283203" y="122"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 886.390625 81 L 886.390625 75 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="888.390625" y="76"/>
<use xlink:href="#glyph1-2" x="890.613281" y="76"/>
<use xlink:href="#glyph1-3" x="893.15625" y="76"/>
<use xlink:href="#glyph1-2" x="899.859375" y="76"/>
<use xlink:href="#glyph1-5" x="902.402344" y="76"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 970.753906 81 L 970.753906 75 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-1" x="972.753906" y="76"/>
<use xlink:href="#glyph1-2" x="974.976562" y="76"/>
<use xlink:href="#glyph1-3" x="977.519531" y="76"/>
<use xlink:href="#glyph1-2" x="984.222656" y="76"/>
<use xlink:href="#glyph1-6" x="986.765625" y="76"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 93.207031 46 L 93.207031 92 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 713.289062 49 L 713.289062 43 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-7" x="715.289062" y="44"/>
<use xlink:href="#glyph1-1" x="719.890625" y="44"/>
<use xlink:href="#glyph1-8" x="722.113281" y="44"/>
<use xlink:href="#glyph1-9" x="725.402344" y="44"/>
<use xlink:href="#glyph1-10" x="729.570312" y="44"/>
<use xlink:href="#glyph1-2" x="732.707031" y="44"/>
<use xlink:href="#glyph1-10" x="735.25" y="44"/>
<use xlink:href="#glyph1-11" x="738.386719" y="44"/>
<use xlink:href="#glyph1-8" x="743.457031" y="44"/>
<use xlink:href="#glyph1-12" x="746.746094" y="44"/>
<use xlink:href="#glyph1-13" x="751.667969" y="44"/>
<use xlink:href="#glyph1-14" x="756.570312" y="44"/>
<use xlink:href="#glyph1-2" x="761.648438" y="44"/>
<use xlink:href="#glyph1-15" x="764.191406" y="44"/>
<use xlink:href="#glyph1-16" x="767.007812" y="44"/>
<use xlink:href="#glyph1-8" x="771.902344" y="44"/>
<use xlink:href="#glyph1-17" x="775.191406" y="44"/>
<use xlink:href="#glyph1-12" x="779.824219" y="44"/>
<use xlink:href="#glyph1-14" x="784.746094" y="44"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 740.554688 110 L 742.976562 110 L 742.976562 124 L 740.554688 124 Z M 740.554688 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 742.976562 110 L 762.324219 110 L 762.324219 124 L 742.976562 124 Z M 742.976562 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 762.328125 110 L 801.253906 110 L 801.253906 124 L 762.328125 124 Z M 762.328125 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 611.636719 110 L 615.625 110 L 615.625 124 L 611.636719 124 Z M 611.636719 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 615.621094 110 L 1006.457031 110 L 1006.457031 124 L 615.621094 124 Z M 615.621094 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1006.460938 110 L 1016.121094 110 L 1016.121094 124 L 1006.460938 124 Z M 1006.460938 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1016.117188 110 L 1065.160156 110 L 1065.160156 124 L 1016.117188 124 Z M 1016.117188 110 "/>
<g clip-path="url(#clip6)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="764.328125" y="122"/>
<use xlink:href="#glyph0-7" x="769.033203" y="122"/>
<use xlink:href="#glyph0-2" x="773.966797" y="122"/>
<use xlink:href="#glyph0-3" x="781.320312" y="122"/>
<use xlink:href="#glyph0-19" x="787.917969" y="122"/>
<use xlink:href="#glyph0-20" x="795.300781" y="122"/>
<use xlink:href="#glyph0-13" x="798.634766" y="122"/>
<use xlink:href="#glyph0-18" x="1018.117188" y="122"/>
<use xlink:href="#glyph0-7" x="1022.822266" y="122"/>
<use xlink:href="#glyph0-2" x="1027.755859" y="122"/>
<use xlink:href="#glyph0-3" x="1035.109375" y="122"/>
<use xlink:href="#glyph0-19" x="1041.707031" y="122"/>
<use xlink:href="#glyph0-20" x="1049.089844" y="122"/>
<use xlink:href="#glyph0-13" x="1052.423828" y="122"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 801.253906 110 L 803.496094 110 L 803.496094 124 L 801.253906 124 Z M 801.253906 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 803.496094 110 L 901.589844 110 L 901.589844 124 L 803.496094 124 Z M 803.496094 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 901.589844 110 L 907.84375 110 L 907.84375 124 L 901.589844 124 Z M 901.589844 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 907.84375 110 L 939.957031 110 L 939.957031 124 L 907.84375 124 Z M 907.84375 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1065.164062 110 L 1071.8125 110 L 1071.8125 124 L 1065.164062 124 Z M 1065.164062 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1071.8125 110 L 1070.394531 110 L 1070.394531 124 L 1071.8125 124 Z M 1071.8125 110 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 581.375 113 L 581.375 107 "/>
<g clip-path="url(#clip7)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="909.84375" y="122"/>
<use xlink:href="#glyph0-7" x="914.548828" y="122"/>
<use xlink:href="#glyph0-2" x="919.482422" y="122"/>
<use xlink:href="#glyph0-3" x="926.835938" y="122"/>
<use xlink:href="#glyph0-19" x="933.433594" y="122"/>
<use xlink:href="#glyph0-20" x="940.816406" y="122"/>
<use xlink:href="#glyph0-13" x="944.150391" y="122"/>
<use xlink:href="#glyph1-1" x="583.375" y="108"/>
<use xlink:href="#glyph1-2" x="585.734375" y="108"/>
<use xlink:href="#glyph1-3" x="590.628906" y="108"/>
<use xlink:href="#glyph1-4" x="595.707031" y="108"/>
<use xlink:href="#glyph1-15" x="598.25" y="108"/>
<use xlink:href="#glyph1-4" x="603.339844" y="108"/>
<use xlink:href="#glyph1-6" x="605.882812" y="108"/>
<use xlink:href="#glyph1-7" x="610.050781" y="108"/>
<use xlink:href="#glyph1-8" x="613.1875" y="108"/>
<use xlink:href="#glyph1-9" x="618.089844" y="108"/>
<use xlink:href="#glyph1-7" x="621.378906" y="108"/>
<use xlink:href="#glyph1-10" x="624.515625" y="108"/>
<use xlink:href="#glyph1-11" x="626.738281" y="108"/>
<use xlink:href="#glyph1-12" x="631.808594" y="108"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 939.957031 110 L 941.289062 110 L 941.289062 124 L 939.957031 124 Z M 939.957031 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 941.289062 110 L 984.167969 110 L 984.167969 124 L 941.289062 124 Z M 941.289062 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 984.167969 110 L 1005.09375 110 L 1005.09375 124 L 984.167969 124 Z M 984.167969 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1005.089844 110 L 1022.230469 110 L 1022.230469 124 L 1005.089844 124 Z M 1005.089844 110 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1047.300781 113 L 1047.300781 107 "/>
<g clip-path="url(#clip8)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="1007.089844" y="122"/>
<use xlink:href="#glyph0-7" x="1011.794922" y="122"/>
<use xlink:href="#glyph0-2" x="1016.728516" y="122"/>
<use xlink:href="#glyph0-3" x="1024.082031" y="122"/>
<use xlink:href="#glyph0-19" x="1030.679688" y="122"/>
<use xlink:href="#glyph0-20" x="1038.0625" y="122"/>
<use xlink:href="#glyph0-13" x="1041.396484" y="122"/>
<use xlink:href="#glyph1-15" x="1049.300781" y="108"/>
<use xlink:href="#glyph1-4" x="1054.390625" y="108"/>
<use xlink:href="#glyph1-13" x="1056.933594" y="108"/>
<use xlink:href="#glyph1-2" x="1062.011719" y="108"/>
<use xlink:href="#glyph1-11" x="1066.90625" y="108"/>
<use xlink:href="#glyph1-14" x="1071.976562" y="108"/>
</g>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1022.230469 110 L 1023.820312 110 L 1023.820312 124 L 1022.230469 124 Z M 1022.230469 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1023.820312 110 L 1048.574219 110 L 1048.574219 124 L 1023.820312 124 Z M 1023.820312 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1048.574219 110 L 1053.917969 110 L 1053.917969 124 L 1048.574219 124 Z M 1048.574219 110 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1053.917969 110 L 1052.722656 110 L 1052.722656 124 L 1053.917969 124 Z M 1053.917969 110 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 787.792969 113 L 787.792969 107 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 523.035156 46 L 523.035156 124 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 621.21875 142 L 622.960938 142 L 622.960938 156 L 621.21875 156 Z M 621.21875 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 622.960938 142 L 634.785156 142 L 634.785156 156 L 622.960938 156 Z M 622.960938 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 634.785156 142 L 838.511719 142 L 838.511719 156 L 634.785156 156 Z M 634.785156 142 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-18" x="636.785156" y="154"/>
<use xlink:href="#glyph0-7" x="641.490234" y="154"/>
<use xlink:href="#glyph0-2" x="646.423828" y="154"/>
<use xlink:href="#glyph0-3" x="653.777344" y="154"/>
<use xlink:href="#glyph0-19" x="660.375" y="154"/>
<use xlink:href="#glyph0-20" x="667.757812" y="154"/>
<use xlink:href="#glyph0-13" x="671.091797" y="154"/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 838.515625 142 L 861.878906 142 L 861.878906 156 L 838.515625 156 Z M 838.515625 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 861.875 142 L 1071.8125 142 L 1071.8125 156 L 861.875 156 Z M 861.875 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1071.8125 142 L 1080.300781 142 L 1080.300781 156 L 1071.8125 156 Z M 1071.8125 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(50%,90%,50%);fill-opacity:1;" d="M 1080.304688 142 L 1126.570312 142 L 1126.570312 156 L 1080.304688 156 Z M 1080.304688 142 "/>
<g clip-path="url(#clip9)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-18" x="789.792969" y="108"/>
<use xlink:href="#glyph1-2" x="792.015625" y="108"/>
<use xlink:href="#glyph1-3" x="794.558594" y="108"/>
<use xlink:href="#glyph1-2" x="801.261719" y="108"/>
<use xlink:href="#glyph1-4" x="803.804688" y="108"/>
<use xlink:href="#glyph0-18" x="1082.304688" y="154"/>
<use xlink:href="#glyph0-7" x="1087.009766" y="154"/>
<use xlink:href="#glyph0-2" x="1091.943359" y="154"/>
<use xlink:href="#glyph0-3" x="1099.296875" y="154"/>
<use xlink:href="#glyph0-19" x="1105.894531" y="154"/>
<use xlink:href="#glyph0-20" x="1113.277344" y="154"/>
<use xlink:href="#glyph0-13" x="1116.611328" y="154"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 929.160156 113 L 929.160156 107 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,80%,40%);fill-opacity:1;" d="M 1126.566406 142 L 1134.214844 142 L 1134.214844 156 L 1126.566406 156 Z M 1126.566406 142 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(40%,40%,40%);fill-opacity:1;" d="M 1134.214844 142 L 1132.316406 142 L 1132.316406 156 L 1134.214844 156 Z M 1134.214844 142 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 817.433594 145 L 817.433594 139 "/>
<g clip-path="url(#clip10)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-18" x="931.160156" y="108"/>
<use xlink:href="#glyph1-2" x="933.382812" y="108"/>
<use xlink:href="#glyph1-3" x="935.925781" y="108"/>
<use xlink:href="#glyph1-2" x="942.628906" y="108"/>
<use xlink:href="#glyph1-5" x="945.171875" y="108"/>
<use xlink:href="#glyph1-1" x="819.433594" y="140"/>
<use xlink:href="#glyph1-2" x="821.792969" y="140"/>
<use xlink:href="#glyph1-3" x="826.6875" y="140"/>
<use xlink:href="#glyph1-4" x="831.765625" y="140"/>
<use xlink:href="#glyph1-16" x="834.308594" y="140"/>
<use xlink:href="#glyph1-4" x="839.398438" y="140"/>
<use xlink:href="#glyph1-6" x="841.941406" y="140"/>
<use xlink:href="#glyph1-7" x="846.109375" y="140"/>
<use xlink:href="#glyph1-8" x="849.246094" y="140"/>
<use xlink:href="#glyph1-9" x="854.148438" y="140"/>
<use xlink:href="#glyph1-7" x="857.4375" y="140"/>
<use xlink:href="#glyph1-10" x="860.574219" y="140"/>
<use xlink:href="#glyph1-11" x="862.796875" y="140"/>
<use xlink:href="#glyph1-12" x="867.867188" y="140"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1011.980469 113 L 1011.980469 107 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1109.605469 145 L 1109.605469 139 "/>
<g clip-path="url(#clip11)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-18" x="1013.980469" y="108"/>
<use xlink:href="#glyph1-2" x="1016.203125" y="108"/>
<use xlink:href="#glyph1-3" x="1018.746094" y="108"/>
<use xlink:href="#glyph1-2" x="1025.449219" y="108"/>
<use xlink:href="#glyph1-6" x="1027.992188" y="108"/>
<use xlink:href="#glyph1-16" x="1111.605469" y="140"/>
<use xlink:href="#glyph1-4" x="1116.695312" y="140"/>
<use xlink:href="#glyph1-13" x="1119.238281" y="140"/>
<use xlink:href="#glyph1-2" x="1124.316406" y="140"/>
<use xlink:href="#glyph1-11" x="1129.210938" y="140"/>
<use xlink:href="#glyph1-14" x="1134.28125" y="140"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 740.554688 46 L 740.554688 124 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 834.167969 49 L 834.167969 43 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 621.21875 46 L 621.21875 156 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 899.039062 49 L 899.039062 43 "/>
<g clip-path="url(#clip12)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-19" x="836.167969" y="44"/>
<use xlink:href="#glyph1-12" x="841.246094" y="44"/>
<use xlink:href="#glyph1-20" x="846.167969" y="44"/>
<use xlink:href="#glyph1-16" x="850.566406" y="44"/>
<use xlink:href="#glyph1-21" x="855.460938" y="44"/>
<use xlink:href="#glyph1-14" x="860.53125" y="44"/>
<use xlink:href="#glyph1-2" x="865.609375" y="44"/>
<use xlink:href="#glyph1-10" x="868.152344" y="44"/>
<use xlink:href="#glyph1-11" x="871.289062" y="44"/>
<use xlink:href="#glyph1-8" x="876.359375" y="44"/>
<use xlink:href="#glyph1-12" x="879.648438" y="44"/>
<use xlink:href="#glyph1-13" x="884.570312" y="44"/>
<use xlink:href="#glyph1-14" x="889.472656" y="44"/>
<use xlink:href="#glyph1-2" x="894.550781" y="44"/>
<use xlink:href="#glyph1-15" x="897.09375" y="44"/>
<use xlink:href="#glyph1-16" x="899.910156" y="44"/>
<use xlink:href="#glyph1-8" x="904.804688" y="44"/>
<use xlink:href="#glyph1-17" x="908.09375" y="44"/>
<use xlink:href="#glyph1-12" x="912.726562" y="44"/>
<use xlink:href="#glyph1-14" x="917.648438" y="44"/>
<use xlink:href="#glyph1-22" x="922.726562" y="44"/>
<use xlink:href="#glyph1-2" x="925.421875" y="44"/>
<use xlink:href="#glyph1-10" x="927.964844" y="44"/>
<use xlink:href="#glyph1-16" x="931.101562" y="44"/>
<use xlink:href="#glyph1-23" x="935.996094" y="44"/>
<use xlink:href="#glyph1-24" x="941.074219" y="44"/>
<use xlink:href="#glyph1-25" x="943.960938" y="44"/>
<use xlink:href="#glyph1-12" x="946.183594" y="44"/>
<use xlink:href="#glyph1-26" x="951.105469" y="44"/>
<use xlink:href="#glyph1-12" x="955.839844" y="44"/>
<use xlink:href="#glyph1-25" x="960.761719" y="44"/>
<use xlink:href="#glyph1-2" x="962.984375" y="44"/>
<use xlink:href="#glyph1-20" x="965.527344" y="44"/>
<use xlink:href="#glyph1-16" x="969.925781" y="44"/>
<use xlink:href="#glyph1-14" x="974.820312" y="44"/>
<use xlink:href="#glyph1-12" x="979.898438" y="44"/>
<use xlink:href="#glyph1-2" x="984.820312" y="44"/>
<use xlink:href="#glyph1-1" x="987.363281" y="44"/>
<use xlink:href="#glyph1-9" x="989.585938" y="44"/>
<use xlink:href="#glyph1-2" x="993.753906" y="44"/>
<use xlink:href="#glyph1-15" x="996.296875" y="44"/>
<use xlink:href="#glyph1-1" x="999.113281" y="44"/>
<use xlink:href="#glyph1-21" x="1001.335938" y="44"/>
<use xlink:href="#glyph1-1" x="1006.40625" y="44"/>
<use xlink:href="#glyph1-9" x="1008.628906" y="44"/>
<use xlink:href="#glyph1-11" x="1012.796875" y="44"/>
<use xlink:href="#glyph1-12" x="1017.867188" y="44"/>
<use xlink:href="#glyph1-14" x="1022.789062" y="44"/>
<use xlink:href="#glyph1-17" x="901.039062" y="44"/>
<use xlink:href="#glyph1-18" x="906.511719" y="44"/>
<use xlink:href="#glyph1-18" x="908.734375" y="44"/>
<use xlink:href="#glyph1-4" x="910.957031" y="44"/>
<use xlink:href="#glyph1-19" x="913.5" y="44"/>
<use xlink:href="#glyph1-20" x="917.898438" y="44"/>
<use xlink:href="#glyph1-10" x="922.96875" y="44"/>
<use xlink:href="#glyph1-18" x="925.191406" y="44"/>
<use xlink:href="#glyph1-13" x="927.414062" y="44"/>
<use xlink:href="#glyph1-4" x="932.492188" y="44"/>
<use xlink:href="#glyph1-21" x="935.035156" y="44"/>
<use xlink:href="#glyph1-10" x="937.851562" y="44"/>
<use xlink:href="#glyph1-3" x="940.074219" y="44"/>
<use xlink:href="#glyph1-14" x="945.152344" y="44"/>
<use xlink:href="#glyph1-9" x="950.074219" y="44"/>
<use xlink:href="#glyph1-6" x="953.363281" y="44"/>
<use xlink:href="#glyph1-4" x="957.53125" y="44"/>
<use xlink:href="#glyph1-21" x="960.074219" y="44"/>
<use xlink:href="#glyph1-2" x="962.890625" y="44"/>
<use xlink:href="#glyph1-9" x="967.785156" y="44"/>
<use xlink:href="#glyph1-22" x="971.074219" y="44"/>
<use xlink:href="#glyph1-14" x="975.707031" y="44"/>
<use xlink:href="#glyph1-13" x="980.628906" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 35.601562 36 L 31.601562 36 L 31.601562 128 L 35.601562 128 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1057.957031 36 L 1061.957031 36 L 1061.957031 128 L 1057.957031 128 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 46.675781 36 L 42.675781 36 L 42.675781 160 L 46.675781 160 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1138.78125 36 L 1142.78125 36 L 1142.78125 160 L 1138.78125 160 "/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-9" x="33.601562" y="44"/>
<use xlink:href="#glyph1-27" x="37.769531" y="44"/>
<use xlink:href="#glyph1-1" x="44.3125" y="44"/>
<use xlink:href="#glyph1-10" x="46.535156" y="44"/>
<use xlink:href="#glyph1-20" x="49.671875" y="44"/>
<use xlink:href="#glyph1-11" x="54.070312" y="44"/>
<use xlink:href="#glyph1-6" x="44.675781" y="44"/>
<use xlink:href="#glyph1-23" x="48.84375" y="44"/>
<use xlink:href="#glyph1-10" x="55.386719" y="44"/>
<use xlink:href="#glyph1-7" x="57.609375" y="44"/>
<use xlink:href="#glyph1-19" x="60.746094" y="44"/>
<use xlink:href="#glyph1-20" x="65.144531" y="44"/>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1086.320312 49 L 1086.320312 43 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1177.296875 49 L 1177.296875 43 "/>
<g clip-path="url(#clip13)" clip-rule="nonzero">
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph1-19" x="1088.320312" y="44"/>
<use xlink:href="#glyph1-27" x="1093.398438" y="44"/>
<use xlink:href="#glyph1-1" x="1099.941406" y="44"/>
<use xlink:href="#glyph1-10" x="1102.164062" y="44"/>
<use xlink:href="#glyph1-20" x="1105.300781" y="44"/>
<use xlink:href="#glyph1-11" x="1109.699219" y="44"/>
<use xlink:href="#glyph1-2" x="1114.769531" y="44"/>
<use xlink:href="#glyph1-1" x="1117.3125" y="44"/>
<use xlink:href="#glyph1-9" x="1119.535156" y="44"/>
<use xlink:href="#glyph1-2" x="1123.703125" y="44"/>
<use xlink:href="#glyph1-15" x="1126.246094" y="44"/>
<use xlink:href="#glyph1-1" x="1129.0625" y="44"/>
<use xlink:href="#glyph1-21" x="1131.285156" y="44"/>
<use xlink:href="#glyph1-1" x="1136.355469" y="44"/>
<use xlink:href="#glyph1-9" x="1138.578125" y="44"/>
<use xlink:href="#glyph1-11" x="1142.746094" y="44"/>
<use xlink:href="#glyph1-12" x="1147.816406" y="44"/>
<use xlink:href="#glyph1-14" x="1152.738281" y="44"/>
<use xlink:href="#glyph1-24" x="1179.296875" y="44"/>
<use xlink:href="#glyph1-23" x="1184.375" y="44"/>
<use xlink:href="#glyph1-10" x="1190.917969" y="44"/>
<use xlink:href="#glyph1-7" x="1193.140625" y="44"/>
<use xlink:href="#glyph1-19" x="1196.277344" y="44"/>
<use xlink:href="#glyph1-20" x="1200.675781" y="44"/>
<use xlink:href="#glyph1-4" x="1205.746094" y="44"/>
<use xlink:href="#glyph1-10" x="1208.289062" y="44"/>
<use xlink:href="#glyph1-6" x="1210.511719" y="44"/>
<use xlink:href="#glyph1-4" x="1214.679688" y="44"/>
<use xlink:href="#glyph1-21" x="1217.222656" y="44"/>
<use xlink:href="#glyph1-10" x="1220.039062" y="44"/>
<use xlink:href="#glyph1-11" x="1222.261719" y="44"/>
<use xlink:href="#glyph1-10" x="1227.332031" y="44"/>
<use xlink:href="#glyph1-6" x="1229.554688" y="44"/>
<use xlink:href="#glyph1-20" x="1233.722656" y="44"/>
<use xlink:href="#glyph1-14" x="1238.792969" y="44"/>
<use xlink:href="#glyph1-13" x="1243.714844" y="44"/>
</g>
</g>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 11.480469 46 L 11.480469 60 "/>
<path style="fill:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(40%,80%,40%);stroke-opacity:1;stroke-miterlimit:10;" d="M 10.746094 46 L 10.746094 60 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -1,6 +1,7 @@
(lang dune 3.9)
(name eio)
(formatting disabled)
(subst disabled)
(generate_opam_files true)
(source (github ocaml-multicore/eio))
(license ISC)
@ -13,7 +14,7 @@
(description "An effect-based IO API for multicore OCaml with fibers.")
(conflicts (seq (< 0.3)))
(depends
(ocaml (>= 5.1.0))
(ocaml (>= 5.2.0))
(bigstringaf (>= 0.9.0))
(cstruct (>= 6.0.1))
lwt-dllist
@ -24,7 +25,7 @@
(domain-local-await (>= 0.1.0))
(crowbar (and (>= 0.2) :with-test))
(mtime (>= 2.0.0))
(mdx (and (>= 2.2.0) :with-test))
(mdx (and (>= 2.4.1) :with-test))
(dscheck (and (>= 0.1.0) :with-test))))
(package
(name eio_linux)
@ -34,11 +35,11 @@
(depends
(alcotest (and (>= 1.7.0) :with-test))
(eio (= :version))
(mdx (and (>= 2.2.0) :with-test))
(logs (>= 0.7.0))
(mdx (and (>= 2.4.1) :with-test))
(logs (and (>= 0.7.0) :with-test))
(fmt (>= 0.8.9))
(cmdliner (and (>= 1.1.0) :with-test))
(uring (>= 0.7))))
(uring (>= 0.9))))
(package
(name eio_posix)
(allow_empty) ; Work-around for dune bug #6938
@ -47,7 +48,8 @@
(depends
(eio (= :version))
(iomux (>= 0.2))
(mdx (and (>= 2.2.0) :with-test))
(mdx (and (>= 2.4.1) :with-test))
(conf-bash :with-test)
(fmt (>= 0.8.9))))
(package
(name eio_windows)
@ -64,7 +66,7 @@
(synopsis "Effect-based direct-style IO mainloop for OCaml")
(description "Selects an appropriate Eio backend for the current platform.")
(depends
(mdx (and (>= 2.2.0) :with-test))
(mdx (and (>= 2.4.1) :with-test))
(kcas (and (>= 0.3.0) :with-test))
(yojson (and (>= 2.0.2) :with-test))
(eio_linux (and

View File

@ -10,7 +10,7 @@ doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [
"dune" {>= "3.9"}
"ocaml" {>= "5.1.0"}
"ocaml" {>= "5.2.0"}
"bigstringaf" {>= "0.9.0"}
"cstruct" {>= "6.0.1"}
"lwt-dllist"
@ -21,7 +21,7 @@ depends: [
"domain-local-await" {>= "0.1.0"}
"crowbar" {>= "0.2" & with-test}
"mtime" {>= "2.0.0"}
"mdx" {>= "2.2.0" & with-test}
"mdx" {>= "2.4.1" & with-test}
"dscheck" {>= "0.1.0" & with-test}
"odoc" {with-doc}
]
@ -29,7 +29,6 @@ conflicts: [
"seq" {< "0.3"}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"

View File

@ -12,15 +12,14 @@ depends: [
"dune" {>= "3.9"}
"alcotest" {>= "1.7.0" & with-test}
"eio" {= version}
"mdx" {>= "2.2.0" & with-test}
"logs" {>= "0.7.0"}
"mdx" {>= "2.4.1" & with-test}
"logs" {>= "0.7.0" & with-test}
"fmt" {>= "0.8.9"}
"cmdliner" {>= "1.1.0" & with-test}
"uring" {>= "0.7"}
"uring" {>= "0.9"}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"

View File

@ -10,7 +10,7 @@ doc: "https://ocaml-multicore.github.io/eio/"
bug-reports: "https://github.com/ocaml-multicore/eio/issues"
depends: [
"dune" {>= "3.9"}
"mdx" {>= "2.2.0" & with-test}
"mdx" {>= "2.4.1" & with-test}
"kcas" {>= "0.3.0" & with-test}
"yojson" {>= "2.0.2" & with-test}
"eio_linux"
@ -21,7 +21,6 @@ depends: [
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"

View File

@ -12,12 +12,12 @@ depends: [
"dune" {>= "3.9"}
"eio" {= version}
"iomux" {>= "0.2"}
"mdx" {>= "2.2.0" & with-test}
"mdx" {>= "2.4.1" & with-test}
"conf-bash" {with-test}
"fmt" {>= "0.8.9"}
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"

View File

@ -17,7 +17,6 @@ depends: [
"odoc" {with-doc}
]
build: [
["dune" "subst"] {dev}
[
"dune"
"build"

3
examples/capsicum/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

40
examples/capsicum/main.ml Normal file
View File

@ -0,0 +1,40 @@
open Eio.Std
let ( / ) = Eio.Path.( / )
let test_eio dir =
traceln "Using the file-system via the directory resource works:";
let test_file = dir / "capsicum-test.txt" in
traceln "Writing %a..." Eio.Path.pp test_file;
Eio.Path.save test_file "A test file" ~create:(`Exclusive 0o644);
traceln "Read: %S" (Eio.Path.load test_file);
Eio.Path.unlink test_file
let test_legacy () =
traceln "Bypassing Eio and accessing other resources should fail in Capsicum mode:";
let ch = open_in "/etc/passwd" in
let len = in_channel_length ch in
let data = really_input_string ch len in
close_in ch;
traceln "Was able to read /etc/passwd:@.%s" (String.trim data)
let () =
Eio_main.run @@ fun env ->
(* Parse command-line arguments *)
let path =
match Sys.argv with
| [| _; dir |] -> Eio.Stdenv.fs env / dir
| _ -> failwith "Usage: main.exe DIR"
in
if not (Eio.Path.is_directory path) then Fmt.failwith "%a is not a directory" Eio.Path.pp path;
(* Get access to resources before calling cap_enter: *)
Eio.Path.with_open_dir path @@ fun dir ->
traceln "Opened directory %a" Eio.Path.pp path;
(* Switch to capability mode, if possible: *)
begin match Eio_unix.Cap.enter () with
| Ok () -> traceln "Capsicum mode enabled"
| Error `Not_supported -> traceln "!! CAPSICUM PROTECTION NOT AVAILABLE !!"
end;
(* Run tests: *)
test_eio dir;
test_legacy ()

3
examples/fs/dune Normal file
View File

@ -0,0 +1,3 @@
(executable
(name main)
(libraries eio_main))

32
examples/fs/main.ml Normal file
View File

@ -0,0 +1,32 @@
(* Walk the directory tree rooted at the current directory,
showing a summary for any .mli files. *)
let ( / ) = Eio.Path.( / )
let is_doc_comment = String.starts_with ~prefix:"(** "
(* Print the first line of [t]'s doc-comment, if any *)
let scan_mli t f =
Eio.Path.with_lines t (fun lines ->
Seq.find is_doc_comment lines
|> Option.iter (fun line ->
let stop = String.index_from_opt line 4 '*' |> Option.value ~default:(String.length line) in
Format.fprintf f "%a: %s@." Eio.Path.pp t (String.sub line 4 (stop - 4))
)
)
(* Walk the tree rooted at [t] and scan any .mli files found. *)
let rec scan t f =
match Eio.Path.kind ~follow:false t with
| `Directory ->
Eio.Path.read_dir t |> List.iter (function
| "_build" | "_opam" -> () (* Don't examine these directories *)
| item when String.starts_with ~prefix:"." item -> () (* Skip hidden items *)
| item -> scan (t / item) f
)
| `Regular_file when Filename.check_suffix (snd t) ".mli" -> scan_mli t f
| _ -> ()
let () =
Eio_main.run @@ fun env ->
scan (Eio.Stdenv.cwd env) Format.std_formatter

View File

@ -218,6 +218,13 @@ val seq : ?stop:bool parser -> 'a parser -> 'a Seq.t parser
It is not necessary to consume all the elements of the
sequence.
Example ([head 4] is a parser that takes 4 lines):
{[
let head n r =
r |> Buf_read.(seq line) |> Seq.take n |> List.of_seq
]}
@param stop This is used before parsing each item.
The sequence ends if this returns [true].
The default is {!at_end_of_input}. *)

View File

@ -1,4 +1,4 @@
(library
(name eio__core)
(public_name eio.core)
(libraries cstruct hmap lwt-dllist fmt optint domain-local-await eio.runtime_events))
(libraries hmap lwt-dllist fmt optint eio.runtime_events))

View File

@ -7,6 +7,7 @@ module Private = struct
module Suspend = Suspend
module Cells = Cells
module Broadcast = Broadcast
module Single_waiter = Single_waiter
module Trace = Trace
module Fiber_context = Cancel.Fiber_context
module Debug = Debug
@ -18,6 +19,4 @@ module Private = struct
| Fork = Fiber.Fork
| Get_context = Cancel.Get_context
end
module Dla = Dla
end

View File

@ -606,6 +606,7 @@ module Private : sig
module Cells = Cells
module Broadcast = Broadcast
module Single_waiter = Single_waiter
(** Every fiber has an associated context. *)
module Fiber_context : sig
@ -781,8 +782,4 @@ module Private : sig
val v : t
(** Backends should use this for {!Eio.Stdenv.debug}. *)
end
module Dla : sig
val prepare_for_await : unit -> Domain_local_await.t
end
end

View File

@ -39,7 +39,8 @@ let fork_daemon ~sw f =
(* The daemon was cancelled because all non-daemon fibers are finished. *)
()
| exception ex ->
Switch.fail sw ex; (* The [with_daemon] ensures this will succeed *)
let bt = Printexc.get_raw_backtrace () in
Switch.fail ~bt sw ex; (* The [with_daemon] ensures this will succeed *)
) (* else the fiber should report the error to [sw], but [sw] is failed anyway *)
let fork_promise ~sw f =
@ -65,7 +66,8 @@ let fork_promise_exn ~sw f =
match Switch.with_op sw f with
| x -> Promise.resolve r x
| exception ex ->
Switch.fail sw ex (* The [with_op] ensures this will succeed *)
let bt = Printexc.get_raw_backtrace () in
Switch.fail ~bt sw ex (* The [with_op] ensures this will succeed *)
);
p
@ -226,7 +228,7 @@ module List = struct
let release t =
t.free_fibers <- t.free_fibers + 1;
if t.free_fibers = 1 then Single_waiter.wake t.cond (Ok ())
if t.free_fibers = 1 then Single_waiter.wake_if_sleeping t.cond
let use t fn x =
await_free t;

View File

@ -1,25 +1,32 @@
(* Allows a single fiber to wait to be notified by another fiber in the same domain.
If multiple fibers need to wait at once, or the notification comes from another domain,
this can't be used. *)
type 'a state =
| Running
| Sleeping of (('a, exn) result -> unit)
type 'a t = {
mutable wake : ('a, exn) result -> unit;
}
type 'a t = 'a state ref
let create () = { wake = ignore }
let create () = ref Running
let wake t v = t.wake v
let wake t v =
match !t with
| Running -> false
| Sleeping fn ->
t := Running;
fn v;
true
let wake_if_sleeping t =
ignore (wake t (Ok ()) : bool)
let await t op id =
let x =
Suspend.enter op @@ fun ctx enqueue ->
Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
t.wake <- ignore;
t := Running;
enqueue (Error ex)
);
t.wake <- (fun x ->
t := Sleeping (fun x ->
Cancel.Fiber_context.clear_cancel_fn ctx;
t.wake <- ignore;
t := Running;
enqueue x
)
in
@ -29,7 +36,7 @@ let await t op id =
let await_protect t op id =
let x =
Suspend.enter_unchecked op @@ fun _ctx enqueue ->
t.wake <- (fun x -> t.wake <- ignore; enqueue x)
t := Sleeping (fun x -> t := Running; enqueue x)
in
Trace.get id;
x

View File

@ -0,0 +1,25 @@
(** Allows a single fiber to wait to be notified by another fiber in the same domain.
If multiple fibers need to wait at once, or the notification comes from another domain,
this can't be used. *)
type 'a t
(** A handle representing a fiber that might be sleeping.
It is either in the Running or Sleeping state. *)
val create : unit -> 'a t
(** [create ()] is a new waiter, initially in the Running state. *)
val wake : 'a t -> ('a, exn) result -> bool
(** [wake t v] resumes [t]'s fiber with value [v] and returns [true] if it was sleeping.
If [t] is Running then this just returns [false]. *)
val wake_if_sleeping : unit t -> unit
(** [wake_if_sleeping] is [ignore (wake t (Ok ()))]. *)
val await : 'a t -> string -> Trace.id -> 'a
(** [await t op id] suspends the calling fiber, changing [t]'s state to Sleeping.
If the fiber is cancelled, a cancel exception is raised.
[op] and [id] are used for tracing. *)
val await_protect : 'a t -> string -> Trace.id -> 'a
(** [await_protect] is like {!await}, but the sleep cannot be cancelled. *)

View File

@ -72,7 +72,7 @@ let dec_fibers t =
if t.daemon_fibers > 0 && t.fibers = t.daemon_fibers then
Cancel.cancel t.cancel Exit;
if t.fibers = 0 then
Single_waiter.wake t.waiter (Ok ())
Single_waiter.wake_if_sleeping t.waiter
let with_op t fn =
inc_fibers t;

View File

@ -55,7 +55,7 @@ let create ~sw ~domain_count domain_mgr =
let enqueue { queue } ~weight fn =
if not (weight >= 0. && weight <= 1.) (* Handles NaN *)
then Fmt.invalid_arg "Executor_pool: weight not >= 0.0 && <= 1.0" weight
then Fmt.invalid_arg "Executor_pool: weight %g not >= 0.0 && <= 1.0" weight
else (
let weight = Float.to_int (weight *. max_capacity_f) in
let p, w = Promise.create () in

View File

@ -70,6 +70,7 @@ module Pi = struct
val rmdir : t -> path -> unit
val rename : t -> path -> _ dir -> path -> unit
val read_link : t -> path -> string
val symlink : link_to:path -> t -> path -> unit
val pp : t Fmt.t
val native : t -> string -> string option
end

View File

@ -105,7 +105,7 @@ let run_full main =
let result = ref None in
let `Exit_scheduler =
Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await
~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () ->
fork ~new_fiber (fun () -> result := Some (main stdenv))) in
match !result with

View File

@ -231,6 +231,9 @@ val run_server :
In such cases you must ensure that [connection_handler] only accesses thread-safe values.
Note that having more than {!Domain.recommended_domain_count} domains in total is likely to result in bad performance.
For services that are bottlenecked on CPU rather than IO,
you can run a single accept loop and have the handler submit CPU-intensive jobs to an {!module:Executor_pool}.
@param max_connections The maximum number of concurrent connections accepted by [s] at any time.
The default is [Int.max_int].
@param stop Resolving this promise causes [s] to stop accepting new connections.

View File

@ -1,11 +1,22 @@
type 'a t = 'a Fs.dir * Fs.path
(* Like [Filename.is_relative] but always using "/" as the separator. *)
let is_relative = function
| "" -> true
| x -> x.[0] <> '/'
(* Like [Filename.concat] but always using "/" as the separator. *)
let concat a b =
let l = String.length a in
if l = 0 || a.[l - 1] = '/' then a ^ b
else a ^ "/" ^ b
let ( / ) (dir, p1) p2 =
match p1, p2 with
| p1, "" -> (dir, Filename.concat p1 p2)
| _, p2 when not (Filename.is_relative p2) -> (dir, p2)
| p1, "" -> (dir, concat p1 p2)
| _, p2 when not (is_relative p2) -> (dir, p2)
| ".", p2 -> (dir, p2)
| p1, p2 -> (dir, Filename.concat p1 p2)
| p1, p2 -> (dir, concat p1 p2)
let pp f (Resource.T (t, ops), p) =
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
@ -199,6 +210,14 @@ let rename t1 t2 =
let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "renaming %a to %a" pp t1 pp t2
let symlink ~link_to source =
let (Resource.T (dir, ops), path) = source in
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.symlink dir path ~link_to
with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "creating symlink %a -> %s" pp source link_to
let rec mkdirs ?(exists_ok=false) ~perm t =
(* Check parent exists first. *)
split t |> Option.iter (fun (parent, _) ->

View File

@ -207,3 +207,13 @@ val rename : _ t -> _ t -> unit
(** [rename old_t new_t] atomically unlinks [old_t] and links it as [new_t].
If [new_t] already exists, it is atomically replaced. *)
val symlink : link_to:string -> _ t -> unit
(** [symlink ~link_to t] creates a symbolic link [t] to [link_to].
[t] is the symlink that is created and [link_to] is the name used in the link.
For example, this creates a "current" symlink pointing at "version-1.0":
{[
Eio.Path.symlink (dir / "current") ~link_to:"version-1.0"
]} *)

View File

@ -19,7 +19,7 @@ type 'a slot = 'a option ref
module Cell = struct
(* The possible behaviours are:
1. Suspender : In_transition -> Request Suspender waits for a resource
1. Suspender : In_transition -> Request Suspender waits for a resource
1.1. Resumer : Request -> Finished Resumer then providers a resource
1.2. Suspender : Request -> Finished Suspender cancels
2. Resumer : In_transition -> Resource Resumer provides a spare resource
@ -89,11 +89,10 @@ let cancel segment cell =
| In_transition | Resource _ -> assert false (* Can't get here from [Request]. *)
(* If [t] is under capacity, add another (empty) slot. *)
let rec maybe_add_slot t =
let current = Atomic.get t.slots in
let rec maybe_add_slot t current =
if current < t.max_slots then (
if Atomic.compare_and_set t.slots current (current + 1) then add t (ref None)
else maybe_add_slot t (* Concurrent update; try again *)
else maybe_add_slot t (Atomic.get t.slots) (* Concurrent update; try again *)
)
(* [run_with t f slot] ensures that [slot] contains a valid resource and then runs [f resource] with it.
@ -114,7 +113,7 @@ let run_with t f slot =
f x
end
with
| r ->
| r ->
add t slot;
r
| exception ex ->
@ -122,7 +121,19 @@ let run_with t f slot =
add t slot;
Printexc.raise_with_backtrace ex bt
let use t f =
(* Creates a fresh resource [x], runs [f x], then disposes of [x] *)
let run_new_and_dispose t f =
let x = t.alloc () in
match f x with
| r ->
t.dispose x;
r
| exception ex ->
let bt = Printexc.get_raw_backtrace () in
t.dispose x;
Printexc.raise_with_backtrace ex bt
let use t ?(never_block=false) f =
let segment, cell = Q.next_suspend t.q in
match Atomic.get cell with
| Finished | Request _ -> assert false
@ -130,9 +141,18 @@ let use t f =
Atomic.set cell Finished; (* Allow value to be GC'd *)
run_with t f slot
| In_transition ->
(* It would have been better if more resources were available.
If we still have capacity, add a new slot now. *)
maybe_add_slot t;
let current = Atomic.get t.slots in
match current < t.max_slots with
| false when never_block -> (
(* We are at capacity, but cannot block.
Create a new resource to run f but don't add it to the pool. *)
match Atomic.exchange cell Finished with
| Resource slot -> run_with t f slot
| _ -> run_new_and_dispose t f
)
| can_add ->
(* Create a slot if not at capacity. *)
if can_add then maybe_add_slot t current;
(* No item is available right now. Start waiting *)
let slot =
Suspend.enter_unchecked "Pool.acquire" (fun ctx enqueue ->

View File

@ -35,6 +35,12 @@ val create :
If it raises, the exception is passed on to the user,
but resource is still considered to have been disposed. *)
val use : 'a t -> ('a -> 'b) -> 'b
val use : 'a t -> ?never_block:bool -> ('a -> 'b) -> 'b
(** [use t fn] waits for some resource [x] to be available and then runs [f x].
Afterwards (on success or error), [x] is returned to the pool. *)
Afterwards (on success or error), [x] is returned to the pool.
@param never_block If [true] and the pool has reached maximum capacity,
then a fresh resource is created to ensure that this [use]
call does not wait for a resource to become available.
This resource is immediately disposed after [f x] returns.
*)

View File

@ -1,3 +1,7 @@
type error = ECONNRESET
exception Unix_error of error * string * string
type file_descr = [`Open | `Closed] Atomic.t
let make () = Atomic.make `Open

View File

@ -87,6 +87,11 @@ module Timeout = struct
| Timeout (clock, d) ->
Fiber.first (fun () -> Mono.sleep_span clock d; raise Timeout) fn
let sleep t =
match t with
| Unlimited -> Fiber.await_cancel ()
| Timeout (clock, d) -> Mono.sleep_span clock d
let pp_duration f d =
if d >= 0.001 && d < 0.1 then
Fmt.pf f "%.2gms" (d *. 1000.)

View File

@ -70,6 +70,9 @@ module Timeout : sig
(** [run_exn t fn] runs [fn ()] but cancels it if it takes longer than allowed by timeout [t],
raising exception {!exception-Timeout}. *)
val sleep : t -> unit
(** [sleep t] sleeps for [t]'s duration. *)
val pp : t Fmt.t
(** [pp] formats a timeout as a duration (e.g. "5s").
This is intended for use in error messages and logging and is rounded. *)

27
lib_eio/unix/cap.c Normal file
View File

@ -0,0 +1,27 @@
#include "primitives.h"
#include <errno.h>
#include <sys/param.h>
#ifdef __FreeBSD__
# define HAVE_CAPSICUM
#endif
#ifdef HAVE_CAPSICUM
# include <sys/capsicum.h>
#endif
#include <caml/mlvalues.h>
#include <caml/unixsupport.h>
CAMLprim value eio_unix_cap_enter(value v_unit) {
#ifdef HAVE_CAPSICUM
int r = cap_enter();
if (r == -1 && errno != ENOSYS)
caml_uerror("cap_enter", Nothing);
return Val_bool(r == 0);
#else
return Val_bool(0);
#endif
}

5
lib_eio/unix/cap.ml Normal file
View File

@ -0,0 +1,5 @@
external eio_cap_enter : unit -> bool = "eio_unix_cap_enter"
let enter () =
if eio_cap_enter () then Ok ()
else Error `Not_supported

11
lib_eio/unix/cap.mli Normal file
View File

@ -0,0 +1,11 @@
val enter : unit -> (unit, [`Not_supported]) result
(** Call {{:https://man.freebsd.org/cgi/man.cgi?query=cap_enter}cap_enter}.
Once in capability mode, access to global name spaces, such as file system
or IPC name spaces, is prevented by the operating system. A program can call
this after opening any directories, files or network sockets that it will need,
to prevent accidental access to other resources.
The standard environment directories {!Eio.Stdenv.fs} and {!Eio.Stdenv.cwd} cannot
be used after calling this, but directories opened from them via {!Eio.Path.with_open_dir}
will continue to work. *)

View File

@ -1,10 +1,11 @@
(library
(name eio_unix)
(public_name eio.unix)
(public_headers include/fork_action.h)
(foreign_stubs
(language c)
(include_dirs include)
(names fork_action stubs))
(names fork_action stubs cap))
(libraries eio eio.utils unix threads mtime.clock.os))
(rule
@ -15,6 +16,7 @@
(run %{bin:lintcstubs_arity_cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Fd.cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Private.cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Cap.cmt}
%{dep:.eio_unix.objs/byte/eio_unix__Fork_action.cmt}))))
(rule

View File

@ -28,6 +28,7 @@ module Ipaddr = Net.Ipaddr
module Process = Process
module Net = Net
module Cap = Cap
module Pi = Pi
module Stdenv = struct

View File

@ -66,6 +66,9 @@ val pipe : Switch.t -> source_ty r * sink_ty r
module Process = Process
(** Spawning child processes with extra control. *)
module Cap = Cap
(** Capsicum security. *)
(** The set of resources provided to a process on a Unix-compatible system. *)
module Stdenv : sig
type base = <

View File

@ -61,18 +61,23 @@ type t = [`Generic | `Unix] Eio.Net.ty r
type _ Effect.t +=
| Import_socket_stream : Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t
| Import_socket_listening : Switch.t * bool * Unix.file_descr -> [`Unix_fd | listening_socket_ty] r Effect.t
| Import_socket_datagram : Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t
| Socketpair_stream : Switch.t * Unix.socket_domain * int ->
([`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r) Effect.t
| Socketpair_datagram : Switch.t * Unix.socket_domain * int ->
([`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r) Effect.t
let open_stream s = (s : _ stream_socket :> [< `Unix_fd | stream_socket_ty] r)
let open_datagram s = (s : _ datagram_socket :> [< `Unix_fd | datagram_socket_ty] r)
let open_stream s = (s : [`Unix_fd | stream_socket_ty] r :> [< `Unix_fd | stream_socket_ty] r)
let open_listening s = (s : [`Unix_fd | listening_socket_ty] r :> [< `Unix_fd | listening_socket_ty] r)
let open_datagram s = (s : [`Unix_fd | datagram_socket_ty] r :> [< `Unix_fd | datagram_socket_ty] r)
let import_socket_stream ~sw ~close_unix fd =
open_stream @@ Effect.perform (Import_socket_stream (sw, close_unix, fd))
let import_socket_listening ~sw ~close_unix fd =
open_listening @@ Effect.perform (Import_socket_listening (sw, close_unix, fd))
let import_socket_datagram ~sw ~close_unix fd =
open_datagram @@ Effect.perform (Import_socket_datagram (sw, close_unix, fd))
@ -81,7 +86,8 @@ let socketpair_stream ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () =
(open_stream a, open_stream b)
let socketpair_datagram ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () =
Effect.perform (Socketpair_datagram (sw, domain, protocol))
let a, b = Effect.perform (Socketpair_datagram (sw, domain, protocol)) in
(open_datagram a, open_datagram b)
let fd socket =
Option.get (Resource.fd_opt socket)

View File

@ -55,16 +55,23 @@ end
(** {2 Creating or importing sockets} *)
val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [`Unix_fd | stream_socket_ty] r
(** [import_socket_stream ~sw ~close_unix:true fd] is an Eio flow that uses [fd].
val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [< `Unix_fd | stream_socket_ty] r
(** [import_socket_stream ~sw ~close_unix fd] is an Eio flow that uses [fd].
It can be cast to e.g. {!source} for a one-way flow.
The socket object will be closed when [sw] finishes.
The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *)
val import_socket_datagram : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r
(** [import_socket_datagram ~sw ~close_unix:true fd] is an Eio datagram socket that uses [fd].
val import_socket_listening : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [< `Unix_fd | listening_socket_ty] r
(** [import_socket_listening ~sw ~close_unix fd] is an Eio listening socket that uses [fd].
The socket object will be closed when [sw] finishes.
The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *)
val import_socket_datagram : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [< `Unix_fd | datagram_socket_ty] r
(** [import_socket_datagram ~sw ~close_unix fd] is an Eio datagram socket that uses [fd].
The socket object will be closed when [sw] finishes.
@ -75,7 +82,7 @@ val socketpair_stream :
?domain:Unix.socket_domain ->
?protocol:int ->
unit ->
[`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r
[< `Unix_fd | stream_socket_ty] r * [< `Unix_fd | stream_socket_ty] r
(** [socketpair_stream ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other.
This creates OS-level resources using [socketpair(2)].
@ -86,7 +93,7 @@ val socketpair_datagram :
?domain:Unix.socket_domain ->
?protocol:int ->
unit ->
[`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r
[< `Unix_fd | datagram_socket_ty] r * [< `Unix_fd | datagram_socket_ty] r
(** [socketpair_datagram ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other.
This creates OS-level resources using [socketpair(2)].
@ -100,6 +107,8 @@ val getnameinfo : Eio.Net.Sockaddr.t -> (string * string)
type _ Effect.t +=
| Import_socket_stream :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t (** See {!import_socket_stream} *)
| Import_socket_listening :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | listening_socket_ty] r Effect.t (** See {!import_socket_listening} *)
| Import_socket_datagram :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t (** See {!import_socket_datagram} *)
| Socketpair_stream : Eio.Switch.t * Unix.socket_domain * int ->

View File

@ -7,5 +7,6 @@ CAMLprim value eio_unix_fork_execve(value);
CAMLprim value eio_unix_fork_chdir(value);
CAMLprim value eio_unix_fork_fchdir(value);
CAMLprim value eio_unix_fork_dups(value);
CAMLprim value eio_unix_cap_enter(value);
CAMLprim value eio_unix_readlinkat(value, value, value);
CAMLprim value eio_unix_is_blocking(value);

View File

@ -98,7 +98,13 @@ let get t =
None
let close_fd fd =
Eio.Private.Trace.with_span "close" (fun () -> Unix.close fd)
Eio.Private.Trace.with_span "close" (fun () ->
try
Unix.close fd
with Unix.Unix_error (ECONNRESET, _, _) ->
(* For FreeBSD. See https://github.com/ocaml-multicore/eio/issues/786 *)
()
)
(* Note: we could simplify this a bit by incrementing [t.ops], as [remove] does.
However, that makes dscheck too slow. *)

View File

@ -7,10 +7,10 @@ let prepare_for_await () =
| _ -> ()
and await () =
if Atomic.get state != `Released then
Suspend.enter "domain-local-await" @@ fun ctx enqueue ->
Eio.Private.Suspend.enter "domain-local-await" @@ fun ctx enqueue ->
let awaiting = `Awaiting enqueue in
if Atomic.compare_and_set state `Init awaiting then (
Cancel.Fiber_context.set_cancel_fn ctx (fun ex ->
Eio.Private.Fiber_context.set_cancel_fn ctx (fun ex ->
if Atomic.compare_and_set state awaiting `Released then (
enqueue (Error ex)
)

1
lib_eio/utils/dla.mli Normal file
View File

@ -0,0 +1 @@
val prepare_for_await : unit -> Domain_local_await.t

View File

@ -1,4 +1,4 @@
(library
(name eio_utils)
(public_name eio.utils)
(libraries eio psq fmt optint))
(libraries eio psq fmt optint domain-local-await))

View File

@ -5,3 +5,4 @@
module Lf_queue = Lf_queue
module Suspended = Suspended
module Zzz = Zzz
module Dla = Dla

View File

@ -11,7 +11,7 @@
(flags :standard -D_LARGEFILE64_SOURCE)
(include_dirs ../lib_eio/unix/include)
(names eio_stubs))
(libraries eio eio.utils eio.unix uring logs fmt))
(libraries eio eio.utils eio.unix uring fmt))
(rule
(enabled_if

View File

@ -38,82 +38,6 @@ let get_dir_fd_opt (Eio.Resource.T (t, ops)) =
| Some f -> Some (f t)
| None -> None
(* When copying between a source with an FD and a sink with an FD, we can share the chunk
and avoid copying. *)
let fast_copy src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Low_level.readv src [buf] in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_size = Uring.Region.length chunk in
try
while true do
let got = Low_level.read_upto src chunk chunk_size in
Low_level.write dst chunk got
done
with End_of_file -> ()
(* Try a fast copy using splice. If the FDs don't support that, switch to copying. *)
let _fast_copy_try_splice src dst =
try
while true do
let _ : int = Low_level.splice src ~dst ~len:max_int in
()
done
with
| End_of_file -> ()
| Eio.Exn.Io (Eio.Exn.X Eio_unix.Unix_error ((EAGAIN | EINVAL), "splice", _), _) -> fast_copy src dst
(* XXX workaround for issue #319, PR #327 *)
let fast_copy_try_splice src dst = fast_copy src dst
let[@tail_mod_cons] rec list_take n = function
| [] -> []
| x :: xs ->
if n = 0 then []
else x :: list_take (n - 1) xs
let truncate_to_iomax xs =
if List.compare_length_with xs Uring.iov_max <= 0 then xs
else list_take Uring.iov_max xs
(* Copy using the [Read_source_buffer] optimisation.
Avoids a copy if the source already has the data. *)
let copy_with_rsb rsb dst =
let write xs = Low_level.writev_single dst (truncate_to_iomax xs) in
try
while true do rsb write done
with End_of_file -> ()
(* Copy by allocating a chunk from the pre-shared buffer and asking
the source to write into it. This used when the other methods
aren't available. *)
let fallback_copy (type src) (module Src : Eio.Flow.Pi.SOURCE with type t = src) src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Src.single_read src buf in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_cs = Uring.Region.to_cstruct chunk in
try
while true do
let got = Src.single_read src chunk_cs in
Low_level.write dst chunk got
done
with End_of_file -> ()
module Datagram_socket = struct
type tag = [`Generic | `Unix]
@ -145,78 +69,6 @@ let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler)
module Flow = struct
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let fd t = t
let close = Eio_unix.Fd.close
let is_tty t = Fd.use_exn "isatty" t Unix.isatty
let stat = Low_level.fstat
let single_read t buf =
if is_tty t then (
(* Work-around for https://github.com/axboe/liburing/issues/354
(should be fixed in Linux 5.14) *)
Low_level.await_readable t
);
Low_level.readv t [buf]
let pread t ~file_offset bufs =
Low_level.readv ~file_offset t bufs
let pwrite t ~file_offset bufs =
Low_level.writev_single ~file_offset t (truncate_to_iomax bufs)
let read_methods = []
let single_write t bufs = Low_level.writev_single t (truncate_to_iomax bufs)
let copy t ~src =
match Eio_unix.Resource.fd_opt src with
| Some src -> fast_copy_try_splice src t
| None ->
let Eio.Resource.T (src, ops) = src in
let module Src = (val (Eio.Resource.get ops Eio.Flow.Pi.Source)) in
let rec aux = function
| Eio.Flow.Read_source_buffer rsb :: _ -> copy_with_rsb (rsb src) t
| _ :: xs -> aux xs
| [] -> fallback_copy (module Src) src t
in
aux Src.read_methods
let shutdown t cmd =
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
let send_msg t ~fds data =
Low_level.send_msg t ~fds data
let recv_msg_with_fds t ~sw ~max_fds data =
let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds data in
n, fds
let seek = Low_level.lseek
let sync = Low_level.fsync
let truncate = Low_level.ftruncate
end
let flow_handler = Eio_unix.Pi.flow_handler (module Flow)
let flow fd =
let r = Eio.Resource.T (fd, flow_handler) in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :>
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r)
let source fd = (flow fd :> _ Eio_unix.source)
let sink fd = (flow fd :> _ Eio_unix.sink)
module Listening_socket = struct
type t = Fd.t
@ -233,7 +85,7 @@ module Listening_socket = struct
| Unix.ADDR_UNIX path -> `Unix path
| Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port)
in
let flow = (flow client :> _ Eio.Net.stream_socket) in
let flow = (Flow.of_fd client :> _ Eio.Net.stream_socket) in
flow, client_addr
let listening_addr fd =
@ -261,7 +113,7 @@ let connect ~sw connect_addr =
let sock_unix = Unix.socket ~cloexec:true (socket_domain_of connect_addr) Unix.SOCK_STREAM 0 in
let sock = Fd.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in
Low_level.connect sock addr;
(flow sock :> _ Eio_unix.Net.stream_socket)
(Flow.of_fd sock :> _ Eio_unix.Net.stream_socket)
module Impl = struct
type t = unit
@ -495,7 +347,7 @@ end = struct
~flags:Uring.Open_flags.cloexec
~perm:0
in
(flow fd :> Eio.File.ro_ty r)
(Flow.of_fd fd :> Eio.File.ro_ty r)
let open_out t ~sw ~append ~create path =
let perm, flags =
@ -511,7 +363,7 @@ end = struct
~flags:Uring.Open_flags.(cloexec + flags)
~perm
in
(flow fd :> Eio.File.rw_ty r)
(Flow.of_fd fd :> Eio.File.rw_ty r)
let native_internal t path =
if Filename.is_relative path then (
@ -532,11 +384,18 @@ end = struct
let d = v ~label ~path:(native_internal t path) (Low_level.FD fd) in
Eio.Resource.T (d, Dir_handler.v)
let mkdir t ~perm path = Low_level.mkdir_beneath ~perm t.fd path
let mkdir t ~perm path = Low_level.mkdir ~perm t.fd path
let read_dir t path =
Switch.run ~name:"read_dir" @@ fun sw ->
let fd = Low_level.open_dir ~sw t.fd (if path = "" then "." else path) in
let path = if path = "" then "." else path in
let fd =
Low_level.openat ~sw t.fd path
~seekable:false
~access:`R
~flags:Uring.Open_flags.(cloexec + directory)
~perm:0
in
Low_level.read_dir fd
let read_link t path = Low_level.read_link t.fd path
@ -562,7 +421,7 @@ end = struct
if !Sched.statx_works then (
let module X = Uring.Statx in
let x = X.create () in
Low_level.statx_confined ~follow ~mask:X.Mask.basic_stats t.fd path x;
Low_level.statx ~follow ~mask:X.Mask.basic_stats t.fd path x;
{ Eio.File.Stat.
dev = X.dev x;
ino = X.ino x;
@ -585,7 +444,7 @@ end = struct
~flags:Uring.Open_flags.(cloexec + path + (if follow then empty else nofollow))
~perm:0
in
Flow.stat fd
Low_level.fstat fd
)
let rename t old_path t2 new_path =
@ -593,6 +452,9 @@ end = struct
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path
| None -> raise (Unix.Unix_error (Unix.EXDEV, "rename-dst", new_path))
let symlink ~link_to t path =
Low_level.symlink ~link_to t.fd path
let pp f t = Fmt.string f (String.escaped t.label)
let fd t = t.fd
@ -612,26 +474,13 @@ end
let dir ~label ~path fd = Eio.Resource.T (Dir.v ~label ~path fd, Dir_handler.v)
module Secure_random = struct
type t = unit
let single_read () buf = Low_level.getrandom buf; Cstruct.length buf
let read_methods = []
end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)
let stdenv ~run_event_loop =
let stdin = source Eio_unix.Fd.stdin in
let stdout = sink Eio_unix.Fd.stdout in
let stderr = sink Eio_unix.Fd.stderr in
let fs = (dir ~label:"fs" ~path:"" Fs, "") in
let cwd = (dir ~label:"cwd" ~path:"" Cwd, "") in
object (_ : stdenv)
method stdin = stdin
method stdout = stdout
method stderr = stderr
method stdin = Flow.stdin
method stdout = Flow.stdout
method stderr = Flow.stderr
method net = net
method process_mgr = process_mgr
method domain_mgr = domain_mgr ~run_event_loop
@ -639,7 +488,7 @@ let stdenv ~run_event_loop =
method mono_clock = mono_clock
method fs = (fs :> Eio.Fs.dir_ty Eio.Path.t)
method cwd = (cwd :> Eio.Fs.dir_ty Eio.Path.t)
method secure_random = secure_random
method secure_random = Flow.secure_random
method debug = Eio.Private.Debug.v
method backend_id = "linux"
end
@ -653,7 +502,11 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k mono_clock)
| Eio_unix.Net.Import_socket_stream (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
continue k (flow fd :> _ Eio_unix.Net.stream_socket)
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket)
)
| Eio_unix.Net.Import_socket_listening (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
continue k (listening_socket fd)
)
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
@ -662,8 +515,8 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k ->
match
let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_STREAM protocol in
let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> flow in
let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> flow in
let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> Flow.of_fd in
let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> Flow.of_fd in
((a :> _ Eio_unix.Net.stream_socket), (b :> _ Eio_unix.Net.stream_socket))
with
| r -> continue k r
@ -684,8 +537,8 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Private.Pipe sw -> Some (fun k ->
match
let r, w = Low_level.pipe ~sw in
let r = (flow r :> _ Eio_unix.source) in
let w = (flow w :> _ Eio_unix.sink) in
let r = (Flow.of_fd r :> _ Eio_unix.source) in
let w = (Flow.of_fd w :> _ Eio_unix.sink) in
(r, w)
with
| r -> continue k r

View File

@ -21,10 +21,6 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
open Eio.Std
type fd := Eio_unix.Fd.t
(** {1 Main Loop} *)
type stdenv = Eio_unix.Stdenv.base
@ -51,203 +47,4 @@ val run :
The argument is a message describing the problem (for logging).
The default simply raises an exception. *)
(** {1 Low-level API} *)
(** Low-level API for using uring directly. *)
module Low_level : sig
val noop : unit -> unit
(** [noop ()] performs a uring noop. This is only useful for benchmarking. *)
(** {1 Time functions} *)
val sleep_until : Mtime.t -> unit
(** [sleep_until time] blocks until the current time is [time]. *)
(** {1 Fixed-buffer memory allocation functions}
The size of the fixed buffer is set when calling {!run}, which attempts to allocate a fixed buffer.
However, that may fail due to resource limits. *)
val alloc_fixed : unit -> Uring.Region.chunk option
(** Allocate a chunk of memory from the fixed buffer.
Warning: The memory is NOT zeroed out.
Passing such memory to Linux can be faster than using normal memory, in certain cases.
There is a limited amount of such memory, and this will return [None] if none is available at present. *)
val alloc_fixed_or_wait : unit -> Uring.Region.chunk
(** Like {!alloc_fixed}, but if there are no chunks available then it waits until one is. *)
val free_fixed : Uring.Region.chunk -> unit
val with_chunk : fallback:(unit -> 'a) -> (Uring.Region.chunk -> 'a) -> 'a
(** [with_chunk ~fallback fn] runs [fn chunk] with a freshly allocated chunk and then frees it.
If no chunks are available, it runs [fallback ()] instead. *)
(** {1 File manipulation functions} *)
val openat2 :
sw:Switch.t ->
?seekable:bool ->
access:[`R|`W|`RW] ->
flags:Uring.Open_flags.t ->
perm:Unix.file_perm ->
resolve:Uring.Resolve.t ->
?dir:fd -> string -> fd
(** [openat2 ~sw ~flags ~perm ~resolve ~dir path] opens [dir/path].
See {!Uring.openat2} for details. *)
val read_upto : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> int
(** [read_upto fd chunk len] reads at most [len] bytes from [fd],
returning as soon as some data is available.
@param file_offset Read from the given position in [fd] (default: 0).
@raise End_of_file Raised if all data has already been read. *)
val read_exactly : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> unit
(** [read_exactly fd chunk len] reads exactly [len] bytes from [fd],
performing multiple read operations if necessary.
@param file_offset Read from the given position in [fd] (default: 0).
@raise End_of_file Raised if the stream ends before [len] bytes have been read. *)
val readv : ?file_offset:Optint.Int63.t -> fd -> Cstruct.t list -> int
(** [readv] is like {!read_upto} but can read into any cstruct(s),
not just chunks of the pre-shared buffer.
If multiple buffers are given, they are filled in order. *)
val write : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> unit
(** [write fd buf len] writes exactly [len] bytes from [buf] to [fd].
It blocks until the OS confirms the write is done,
and resubmits automatically if the OS doesn't write all of it at once. *)
val writev : ?file_offset:Optint.Int63.t -> fd -> Cstruct.t list -> unit
(** [writev] is like {!write} but can write from any cstruct(s),
not just chunks of the pre-shared buffer.
If multiple buffers are given, they are sent in order.
It will make multiple OS calls if the OS doesn't write all of it at once. *)
val writev_single : ?file_offset:Optint.Int63.t -> fd -> Cstruct.t list -> int
(** [writev_single] is like [writev] but only performs a single write operation.
It returns the number of bytes written, which may be smaller than the requested amount. *)
val splice : fd -> dst:fd -> len:int -> int
(** [splice src ~dst ~len] attempts to copy up to [len] bytes of data from [src] to [dst].
@return The number of bytes copied.
@raise End_of_file [src] is at the end of the file.
@raise Unix.Unix_error(EINVAL, "splice", _) if splice is not supported for these FDs. *)
val connect : fd -> Unix.sockaddr -> unit
(** [connect fd addr] attempts to connect socket [fd] to [addr]. *)
val await_readable : fd -> unit
(** [await_readable fd] blocks until [fd] is readable (or has an error). *)
val await_writable : fd -> unit
(** [await_writable fd] blocks until [fd] is writable (or has an error). *)
val fstat : fd -> Eio.File.Stat.t
(** Like {!Unix.LargeFile.fstat}. *)
val statx : ?fd:fd -> mask:Uring.Statx.Mask.t -> string -> Uring.Statx.t -> Uring.Statx.Flags.t -> unit
(** [statx t ?fd ~mask path buf flags] stats [path], which is resolved relative to [fd]
(or the current directory if [fd] is not given).
The results are written to [buf]. *)
val read_dir : fd -> string list
(** [read_dir dir] reads all directory entries from [dir].
The entries are not returned in any particular order
(not even necessarily the order in which Linux returns them). *)
val lseek : fd -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t
(** Set and/or get the current file position.
Like {!Unix.lseek}. *)
val fsync : fd -> unit
(** Flush file buffers to disk.
Like {!Unix.fsync}. *)
val ftruncate : fd -> Optint.Int63.t -> unit
(** Set the length of a file.
Like {!Unix.ftruncate}. *)
(** {1 Sockets} *)
val accept : sw:Switch.t -> fd -> (fd * Unix.sockaddr)
(** [accept ~sw t] blocks until a new connection is received on listening socket [t].
It returns the new connection and the address of the connecting peer.
The new connection has the close-on-exec flag set automatically.
The new connection is attached to [sw] and will be closed when that finishes, if
not already closed manually by then. *)
val shutdown : fd -> Unix.shutdown_command -> unit
(** Like {!Unix.shutdown}. *)
val send_msg : fd -> ?fds:fd list -> ?dst:Unix.sockaddr -> Cstruct.t list -> int
(** [send_msg socket bufs] is like [writev socket bufs], but also allows setting the destination address
(for unconnected sockets) and attaching FDs (for Unix-domain sockets). *)
val recv_msg : fd -> Cstruct.t list -> Uring.Sockaddr.t * int
(** [recv_msg socket bufs] is like [readv socket bufs] but also returns the address of the sender. *)
val recv_msg_with_fds : sw:Switch.t -> max_fds:int -> fd -> Cstruct.t list -> Uring.Sockaddr.t * int * fd list
(** [recv_msg_with_fds] is like [recv_msg] but also allows receiving up to [max_fds] file descriptors
(sent using SCM_RIGHTS over a Unix domain socket). *)
(** {1 Randomness} *)
val getrandom : Cstruct.t -> unit
(**[getrandom buf] fills [buf] with random bytes.
It uses Linux's [getrandom] call, which is like reading from /dev/urandom
except that it will block (the whole domain) if used at early boot
when the random system hasn't been initialised yet. *)
(** {1 DNS functions} *)
val getaddrinfo : service:string -> string -> Eio.Net.Sockaddr.t list
(** [getaddrinfo host] returns a list of IP addresses for [host]. [host] is either a domain name or
an ipaddress. *)
(** {1 Processes} *)
module Process : sig
type t
(** A child process. *)
module Fork_action = Eio_unix.Private.Fork_action
(** Setup actions to perform in the child process. *)
val spawn : sw:Switch.t -> Fork_action.t list -> t
(** [spawn ~sw actions] forks a child process, which executes [actions].
The last action should be {!Fork_action.execve}.
You will typically want to do [Promise.await (exit_status child)] after this.
@param sw The child will be sent {!Sys.sigkill} if [sw] finishes. *)
val signal : t -> int -> unit
(** [signal t x] sends signal [x] to [t].
This is similar to doing [Unix.kill t.pid x],
except that it ensures no signal is sent after [t] has been reaped. *)
val pid : t -> int
val exit_status : t -> Unix.process_status Promise.t
(** [exit_status t] is a promise for the process's exit status. *)
end
end
module Low_level = Low_level

View File

@ -39,7 +39,11 @@
#ifndef SYS_clone3
# define SYS_clone3 435
# define CLONE_PIDFD 0x00001000
struct clone_args {
#endif
// struct clone_args isn't defined in linux-lts headers, so define it here
// Note that this struct is versioned by size. See linux/sched.h for details
struct caml_eio_clone_args {
uint64_t flags;
uint64_t pidfd;
uint64_t child_tid;
@ -48,11 +52,7 @@ struct clone_args {
uint64_t stack;
uint64_t stack_size;
uint64_t tls;
uint64_t set_tid;
uint64_t set_tid_size;
uint64_t cgroup;
};
#endif
// Make sure we have enough space for at least one entry.
#define DIRENT_BUF_SIZE (PATH_MAX + sizeof(struct dirent64))
@ -97,6 +97,24 @@ CAMLprim value caml_eio_renameat(value v_old_fd, value v_old_path, value v_new_f
CAMLreturn(Val_unit);
}
CAMLprim value caml_eio_symlinkat(value v_old_path, value v_new_fd, value v_new_path) {
CAMLparam2(v_old_path, v_new_path);
char *old_path;
char *new_path;
int ret;
caml_unix_check_path(v_old_path, "symlinkat-old");
caml_unix_check_path(v_new_path, "symlinkat-new");
old_path = caml_stat_strdup(String_val(v_old_path));
new_path = caml_stat_strdup(String_val(v_new_path));
caml_enter_blocking_section();
ret = symlinkat(old_path, Int_val(v_new_fd), new_path);
caml_leave_blocking_section();
caml_stat_free(old_path);
caml_stat_free(new_path);
if (ret == -1) uerror("symlinkat", v_old_path);
CAMLreturn(Val_unit);
}
CAMLprim value caml_eio_getrandom(value v_ba, value v_off, value v_len) {
CAMLparam1(v_ba);
ssize_t ret;
@ -160,9 +178,9 @@ static int pidfd_open(pid_t pid, unsigned int flags) {
/* Like clone3, but falls back to fork if not supported.
Also, raises exceptions rather then returning an error. */
static pid_t clone3_with_fallback(struct clone_args *cl_args) {
static pid_t clone3_with_fallback(struct caml_eio_clone_args *cl_args) {
int *pidfd = (int *)(uintptr_t) cl_args->pidfd;
pid_t child_pid = syscall(SYS_clone3, cl_args, sizeof(struct clone_args));
pid_t child_pid = syscall(SYS_clone3, cl_args, sizeof(struct caml_eio_clone_args));
if (child_pid >= 0)
return child_pid; /* Success! */
@ -198,7 +216,7 @@ CAMLprim value caml_eio_clone3(value v_errors, value v_actions) {
CAMLlocal1(v_result);
pid_t child_pid;
int pidfd = -1; /* Is automatically close-on-exec */
struct clone_args cl_args = {
struct caml_eio_clone_args cl_args = {
.flags = CLONE_PIDFD,
.pidfd = (uintptr_t) &pidfd,
.exit_signal = SIGCHLD, /* Needed for wait4 to work if we exit before exec */

157
lib_eio_linux/flow.ml Normal file
View File

@ -0,0 +1,157 @@
open Eio.Std
(* When copying between a source with an FD and a sink with an FD, we can share the chunk
and avoid copying. *)
let fast_copy src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Low_level.readv src [buf] in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_size = Uring.Region.length chunk in
try
while true do
let got = Low_level.read_upto src chunk chunk_size in
Low_level.write dst chunk got
done
with End_of_file -> ()
(* Try a fast copy using splice. If the FDs don't support that, switch to copying. *)
let _fast_copy_try_splice src dst =
try
while true do
let _ : int = Low_level.splice src ~dst ~len:max_int in
()
done
with
| End_of_file -> ()
| Eio.Exn.Io (Eio.Exn.X Eio_unix.Unix_error ((EAGAIN | EINVAL), "splice", _), _) -> fast_copy src dst
(* XXX workaround for issue #319, PR #327 *)
let fast_copy_try_splice src dst = fast_copy src dst
let[@tail_mod_cons] rec list_take n = function
| [] -> []
| x :: xs ->
if n = 0 then []
else x :: list_take (n - 1) xs
let truncate_to_iomax xs =
if List.compare_length_with xs Uring.iov_max <= 0 then xs
else list_take Uring.iov_max xs
(* Copy using the [Read_source_buffer] optimisation.
Avoids a copy if the source already has the data. *)
let copy_with_rsb rsb dst =
let write xs = Low_level.writev_single dst (truncate_to_iomax xs) in
try
while true do rsb write done
with End_of_file -> ()
(* Copy by allocating a chunk from the pre-shared buffer and asking
the source to write into it. This used when the other methods
aren't available. *)
let fallback_copy (type src) (module Src : Eio.Flow.Pi.SOURCE with type t = src) src dst =
let fallback () =
(* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in
try
while true do
let got = Src.single_read src buf in
Low_level.writev dst [Cstruct.sub buf 0 got]
done
with End_of_file -> ()
in
Low_level.with_chunk ~fallback @@ fun chunk ->
let chunk_cs = Uring.Region.to_cstruct chunk in
try
while true do
let got = Src.single_read src chunk_cs in
Low_level.write dst chunk got
done
with End_of_file -> ()
module Impl = struct
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let fd t = t
let close = Eio_unix.Fd.close
let stat = Low_level.fstat
let single_read t buf =
Low_level.readv t [buf]
let pread t ~file_offset bufs =
Low_level.readv ~file_offset t bufs
let pwrite t ~file_offset bufs =
Low_level.writev_single ~file_offset t (truncate_to_iomax bufs)
let read_methods = []
let single_write t bufs = Low_level.writev_single t (truncate_to_iomax bufs)
let copy t ~src =
match Eio_unix.Resource.fd_opt src with
| Some src -> fast_copy_try_splice src t
| None ->
let Eio.Resource.T (src, ops) = src in
let module Src = (val (Eio.Resource.get ops Eio.Flow.Pi.Source)) in
let rec aux = function
| Eio.Flow.Read_source_buffer rsb :: _ -> copy_with_rsb (rsb src) t
| _ :: xs -> aux xs
| [] -> fallback_copy (module Src) src t
in
aux Src.read_methods
let shutdown t cmd =
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
let send_msg t ~fds data =
Low_level.send_msg t ~fds data
let recv_msg_with_fds t ~sw ~max_fds data =
let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds data in
n, fds
let seek = Low_level.lseek
let sync = Low_level.fsync
let truncate = Low_level.ftruncate
end
let flow_handler = Eio_unix.Pi.flow_handler (module Impl)
let of_fd fd =
let r = Eio.Resource.T (fd, flow_handler) in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :>
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r)
let source fd = (of_fd fd :> Eio_unix.source_ty r)
let sink fd = (of_fd fd :> Eio_unix.sink_ty r)
let stdin = source Eio_unix.Fd.stdin
let stdout = sink Eio_unix.Fd.stdout
let stderr = sink Eio_unix.Fd.stderr
module Secure_random = struct
type t = unit
let single_read () buf = Low_level.getrandom buf; Cstruct.length buf
let read_methods = []
end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)

9
lib_eio_linux/flow.mli Normal file
View File

@ -0,0 +1,9 @@
open Eio.Std
val of_fd : Eio_unix.Fd.t -> [< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r
val stdin : Eio_unix.source_ty r
val stdout : Eio_unix.sink_ty r
val stderr : Eio_unix.sink_ty r
val secure_random : Eio.Flow.source_ty r

View File

@ -1,2 +0,0 @@
let src = Logs.Src.create "eio_linux" ~doc:"Effect-based IO system for Linux/io-uring"
include (val Logs.src_log src : Logs.LOG)

View File

@ -207,11 +207,39 @@ let write ?file_offset:off fd buf len =
raise @@ Err.wrap (Uring.error_of_errno res) "write" ""
)
let alloc_fixed () = Effect.perform Sched.Alloc
let alloc_fixed () =
let s = Sched.get () in
match s.mem with
| None -> None
| Some mem ->
match Uring.Region.alloc mem with
| buf -> Some buf
| exception Uring.Region.No_space -> None
let alloc_fixed_or_wait () = Effect.perform Sched.Alloc_or_wait
let alloc_fixed_or_wait () =
let s = Sched.get () in
match s.mem with
| None -> failwith "No fixed buffer available"
| Some mem ->
match Uring.Region.alloc mem with
| buf -> buf
| exception Uring.Region.No_space ->
let id = Eio.Private.Trace.mint_id () in
let trigger = Eio.Private.Single_waiter.create () in
let node = Lwt_dllist.add_r trigger s.mem_q in
try
Eio.Private.Single_waiter.await trigger "alloc_fixed_or_wait" id
with ex ->
Lwt_dllist.remove node;
raise ex
let free_fixed buf = Effect.perform (Sched.Free buf)
let rec free_fixed buf =
let s = Sched.get () in
match Lwt_dllist.take_opt_l s.mem_q with
| None -> Uring.Region.free buf
| Some k ->
if not (Eio.Private.Single_waiter.wake k (Ok buf)) then
free_fixed buf (* [k] was already cancelled, but not yet removed from the queue *)
let splice src ~dst ~len =
Fd.use_exn "splice-src" src @@ fun src ->
@ -330,6 +358,8 @@ external eio_mkdirat : Unix.file_descr -> string -> Unix.file_perm -> unit = "ca
external eio_renameat : Unix.file_descr -> string -> Unix.file_descr -> string -> unit = "caml_eio_renameat"
external eio_symlinkat : string -> Unix.file_descr -> string -> unit = "caml_eio_symlinkat"
external eio_getrandom : Cstruct.buffer -> int -> int -> int = "caml_eio_getrandom"
external eio_getdents : Unix.file_descr -> string list = "caml_eio_getdents"
@ -401,7 +431,7 @@ let with_parent_dir op dir path fn =
Fd.use_exn op parent @@ fun parent ->
fn parent leaf
let statx ?fd ~mask path buf flags =
let statx_raw ?fd ~mask path buf flags =
let res =
match fd with
| None -> Sched.enter "statx" (enqueue_statx (None, path, buf, flags, mask))
@ -411,14 +441,15 @@ let statx ?fd ~mask path buf flags =
in
if res <> 0 then raise @@ Err.wrap_fs (Uring.error_of_errno res) "statx" path
let statx_confined ~mask ~follow fd path buf =
let statx ~mask ~follow fd path buf =
let module X = Uring.Statx in
let flags = if follow then X.Flags.empty else X.Flags.symlink_nofollow in
let flags = if follow then X.Flags.empty_path else X.Flags.(empty_path + symlink_nofollow) in
match fd with
| Fs -> statx ~mask path buf flags
| Fs -> statx_raw ~mask path buf flags
| FD fd when path = "" -> statx_raw ~fd ~mask "" buf flags
| Cwd | FD _ when not follow ->
with_parent_dir_fd fd path @@ fun parent leaf ->
statx ~mask ~fd:parent leaf buf flags
statx_raw ~mask ~fd:parent leaf buf flags
| Cwd | FD _ ->
Switch.run ~name:"statx" @@ fun sw ->
let fd = openat ~sw ~seekable:false fd (if path = "" then "." else path)
@ -426,9 +457,9 @@ let statx_confined ~mask ~follow fd path buf =
~flags:Uring.Open_flags.(cloexec + path)
~perm:0
in
statx ~fd ~mask "" buf Uring.Statx.Flags.(flags + empty_path)
statx_raw ~fd ~mask "" buf flags
let mkdir_beneath ~perm dir path =
let mkdir ~perm dir path =
(* [mkdir] is really an operation on [path]'s parent. Get a reference to that first: *)
with_parent_dir "mkdir" dir path @@ fun parent leaf ->
try eio_mkdirat parent leaf perm
@ -449,6 +480,12 @@ let rename old_dir old_path new_dir new_path =
new_parent new_leaf
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg
let symlink ~link_to dir path =
with_parent_dir "symlinkat-new" dir path @@ fun parent leaf ->
try
eio_symlinkat link_to parent leaf
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg
let shutdown socket command =
try
Fd.use_exn "shutdown" socket @@ fun fd ->
@ -470,12 +507,6 @@ let accept ~sw fd =
client, client_addr
)
let open_dir ~sw dir path =
openat ~sw ~seekable:false dir path
~access:`R
~flags:Uring.Open_flags.(cloexec + directory)
~perm:0
let read_dir fd =
Fd.use_exn "read_dir" fd @@ fun fd ->
let rec read_all acc fd =
@ -568,7 +599,10 @@ module Process = struct
let exit_status, set_exit_status = Promise.create () in
let t =
Fd.use_exn "errors-w" errors_w @@ fun errors_w ->
let pid, pid_fd = eio_spawn errors_w c_actions in
let pid, pid_fd =
Eio.Private.Trace.with_span "spawn" @@ fun () ->
eio_spawn errors_w c_actions
in
let pid_fd = Fd.of_unix ~sw ~seekable:false ~close_unix:true pid_fd in
{ pid; pid_fd; exit_status }
in

245
lib_eio_linux/low_level.mli Normal file
View File

@ -0,0 +1,245 @@
(** {1 Low-level API}
Low-level API for using uring directly. *)
open Eio.Std
type fd := Eio_unix.Fd.t
type dir_fd =
| FD of fd
| Cwd (** Confined to "." *)
| Fs (** Unconfined "."; also allows absolute paths *)
val noop : unit -> unit
(** [noop ()] performs a uring noop. This is only useful for benchmarking. *)
(** {1 Time functions} *)
val sleep_until : Mtime.t -> unit
(** [sleep_until time] blocks until the current time is [time]. *)
(** {1 Fixed-buffer memory allocation functions}
The size of the fixed buffer is set when calling {!run}, which attempts to allocate a fixed buffer.
However, that may fail due to resource limits. *)
val alloc_fixed : unit -> Uring.Region.chunk option
(** Allocate a chunk of memory from the fixed buffer.
Warning: The memory is NOT zeroed out.
Passing such memory to Linux can be faster than using normal memory, in certain cases.
There is a limited amount of such memory, and this will return [None] if none is available at present. *)
val alloc_fixed_or_wait : unit -> Uring.Region.chunk
(** Like {!alloc_fixed}, but if there are no chunks available then it waits until one is. *)
val free_fixed : Uring.Region.chunk -> unit
val with_chunk : fallback:(unit -> 'a) -> (Uring.Region.chunk -> 'a) -> 'a
(** [with_chunk ~fallback fn] runs [fn chunk] with a freshly allocated chunk and then frees it.
If no chunks are available, it runs [fallback ()] instead. *)
(** {1 File manipulation functions} *)
val openat :
sw:Switch.t ->
?seekable:bool ->
access:[`R|`W|`RW] ->
flags:Uring.Open_flags.t ->
perm:Unix.file_perm ->
dir_fd -> string ->
fd
(** [openat ~sw ~access ~flags ~perm dir path] opens [dir/path]. *)
val openat2 :
sw:Switch.t ->
?seekable:bool ->
access:[`R|`W|`RW] ->
flags:Uring.Open_flags.t ->
perm:Unix.file_perm ->
resolve:Uring.Resolve.t ->
?dir:fd -> string -> fd
(** [openat2 ~sw ~access ~flags ~perm ~resolve ~dir path] opens [dir/path].
It provides full access to the resolve flags.
See {!Uring.openat2} for details. *)
val read_upto : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> int
(** [read_upto fd chunk len] reads at most [len] bytes from [fd],
returning as soon as some data is available.
@param file_offset Read from the given position in [fd] (default: 0).
@raise End_of_file Raised if all data has already been read. *)
val read_exactly : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> unit
(** [read_exactly fd chunk len] reads exactly [len] bytes from [fd],
performing multiple read operations if necessary.
@param file_offset Read from the given position in [fd] (default: 0).
@raise End_of_file Raised if the stream ends before [len] bytes have been read. *)
val readv : ?file_offset:Optint.Int63.t -> fd -> Cstruct.t list -> int
(** [readv] is like {!read_upto} but can read into any cstruct(s),
not just chunks of the pre-shared buffer.
If multiple buffers are given, they are filled in order. *)
val write : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> unit
(** [write fd buf len] writes exactly [len] bytes from [buf] to [fd].
It blocks until the OS confirms the write is done,
and resubmits automatically if the OS doesn't write all of it at once. *)
val writev : ?file_offset:Optint.Int63.t -> fd -> Cstruct.t list -> unit
(** [writev] is like {!write} but can write from any cstruct(s),
not just chunks of the pre-shared buffer.
If multiple buffers are given, they are sent in order.
It will make multiple OS calls if the OS doesn't write all of it at once. *)
val writev_single : ?file_offset:Optint.Int63.t -> fd -> Cstruct.t list -> int
(** [writev_single] is like [writev] but only performs a single write operation.
It returns the number of bytes written, which may be smaller than the requested amount. *)
val splice : fd -> dst:fd -> len:int -> int
(** [splice src ~dst ~len] attempts to copy up to [len] bytes of data from [src] to [dst].
@return The number of bytes copied.
@raise End_of_file [src] is at the end of the file.
@raise Unix.Unix_error(EINVAL, "splice", _) if splice is not supported for these FDs. *)
val connect : fd -> Unix.sockaddr -> unit
(** [connect fd addr] attempts to connect socket [fd] to [addr]. *)
val await_readable : fd -> unit
(** [await_readable fd] blocks until [fd] is readable (or has an error). *)
val await_writable : fd -> unit
(** [await_writable fd] blocks until [fd] is writable (or has an error). *)
val fstat : fd -> Eio.File.Stat.t
(** Like {!Unix.LargeFile.fstat}. *)
val statx :
mask:Uring.Statx.Mask.t ->
follow:bool ->
dir_fd -> string ->
Uring.Statx.t ->
unit
(** [statx ~mask ~follow dir path buf] stats [dir / path].
The results are written to [buf].
If [follow = true] and the item is a symlink, information is reported about the target of the link.
Otherwise, information about the symlink itself is returned. *)
val mkdir : perm:int -> dir_fd -> string -> unit
(** [mkdir ~perm dir path] creates directory [dir / path]. *)
val read_link : dir_fd -> string -> string
(** [read_link dir path] reads the target of symlink [dir / path]. *)
val unlink : rmdir:bool -> dir_fd -> string -> unit
(** [unlink ~rmdir dir path] removes directory entry [dir / path].
If [rmdir = true] then the target must be a directory.
Otherwise, it must not be a directory. *)
val rename : dir_fd -> string -> dir_fd -> string -> unit
(** [rename old_dir old_path new_dir new_path] renames [old_dir / old_path] as [new_dir / new_path]. *)
val symlink : link_to:string -> dir_fd -> string -> unit
(** [symlink ~link_to dir path] creates a new symlink at [dir / path] pointing to [link_to]. *)
val pipe : sw:Switch.t -> fd * fd
(** [pipe ~sw] returns a pair [r, w] with the readable and writeable ends of a new pipe. *)
val read_dir : fd -> string list
(** [read_dir dir] reads all directory entries from [dir].
The entries are not returned in any particular order
(not even necessarily the order in which Linux returns them). *)
val lseek : fd -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t
(** Set and/or get the current file position.
Like {!Unix.lseek}. *)
val fsync : fd -> unit
(** Flush file buffers to disk.
Like {!Unix.fsync}. *)
val ftruncate : fd -> Optint.Int63.t -> unit
(** Set the length of a file.
Like {!Unix.ftruncate}. *)
(** {1 Sockets} *)
val accept : sw:Switch.t -> fd -> (fd * Unix.sockaddr)
(** [accept ~sw t] blocks until a new connection is received on listening socket [t].
It returns the new connection and the address of the connecting peer.
The new connection has the close-on-exec flag set automatically.
The new connection is attached to [sw] and will be closed when that finishes, if
not already closed manually by then. *)
val shutdown : fd -> Unix.shutdown_command -> unit
(** Like {!Unix.shutdown}. *)
val send_msg : fd -> ?fds:fd list -> ?dst:Unix.sockaddr -> Cstruct.t list -> int
(** [send_msg socket bufs] is like [writev socket bufs], but also allows setting the destination address
(for unconnected sockets) and attaching FDs (for Unix-domain sockets). *)
val recv_msg : fd -> Cstruct.t list -> Uring.Sockaddr.t * int
(** [recv_msg socket bufs] is like [readv socket bufs] but also returns the address of the sender. *)
val recv_msg_with_fds : sw:Switch.t -> max_fds:int -> fd -> Cstruct.t list -> Uring.Sockaddr.t * int * fd list
(** [recv_msg_with_fds] is like [recv_msg] but also allows receiving up to [max_fds] file descriptors
(sent using SCM_RIGHTS over a Unix domain socket). *)
(** {1 Randomness} *)
val getrandom : Cstruct.t -> unit
(**[getrandom buf] fills [buf] with random bytes.
It uses Linux's [getrandom] call, which is like reading from /dev/urandom
except that it will block (the whole domain) if used at early boot
when the random system hasn't been initialised yet. *)
(** {1 DNS functions} *)
val getaddrinfo : service:string -> string -> Eio.Net.Sockaddr.t list
(** [getaddrinfo host] returns a list of IP addresses for [host]. [host] is either a domain name or
an ipaddress. *)
(** {1 Processes} *)
module Process : sig
type t
(** A child process. *)
module Fork_action = Eio_unix.Private.Fork_action
(** Setup actions to perform in the child process. *)
val spawn : sw:Switch.t -> Fork_action.t list -> t
(** [spawn ~sw actions] forks a child process, which executes [actions].
The last action should be {!Fork_action.execve}.
You will typically want to do [Promise.await (exit_status child)] after this.
@param sw The child will be sent {!Sys.sigkill} if [sw] finishes. *)
val signal : t -> int -> unit
(** [signal t x] sends signal [x] to [t].
This is similar to doing [Unix.kill t.pid x],
except that it ensures no signal is sent after [t] has been reaped. *)
val pid : t -> int
val exit_status : t -> Unix.process_status Promise.t
(** [exit_status t] is a promise for the process's exit status. *)
end

View File

@ -5,6 +5,7 @@
CAMLprim value caml_eio_eventfd(value);
CAMLprim value caml_eio_mkdirat(value, value, value);
CAMLprim value caml_eio_renameat(value, value, value, value);
CAMLprim value caml_eio_symlinkat(value, value, value);
CAMLprim value caml_eio_getrandom(value, value, value);
CAMLprim value caml_eio_getdents(value);
CAMLprim value caml_eio_clone3(value, value);

View File

@ -50,7 +50,7 @@ type t = {
uring: io_job Uring.t;
mem: Uring.Region.t option;
io_q: (t -> unit) Queue.t; (* waiting for room on [uring] *)
mem_q : Uring.Region.chunk Suspended.t Queue.t;
mem_q : Uring.Region.chunk Eio.Private.Single_waiter.t Lwt_dllist.t;
(* The queue of runnable fibers ready to be resumed. Note: other domains can also add work items here. *)
run_q : runnable Lf_queue.t;
@ -74,9 +74,9 @@ type t = {
type _ Effect.t +=
| Enter : (t -> 'a Suspended.t -> unit) -> 'a Effect.t
| Cancel : io_job Uring.job -> unit Effect.t
| Alloc : Uring.Region.chunk option Effect.t
| Alloc_or_wait : Uring.Region.chunk Effect.t
| Free : Uring.Region.chunk -> unit Effect.t
| Get : t Effect.t
let get () = Effect.perform Get
let wake_buffer =
let b = Bytes.create 8 in
@ -113,7 +113,10 @@ let enter op fn =
Effect.perform (Enter fn)
let submit uring =
Trace.with_span "submit" (fun () -> Uring.submit uring)
if Uring.sqe_ready uring > 0 then
Trace.with_span "submit" (fun () -> Uring.submit uring)
else
0
let rec enqueue_job t fn =
match fn () with
@ -228,7 +231,6 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
handle_complete st ~runnable result
| None ->
ignore (submit uring : int);
let timeout =
match next_due with
| `Wait_until time ->
@ -239,12 +241,13 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
| `Nothing -> None
in
if not (Lf_queue.is_empty st.run_q) then (
ignore (submit uring : int);
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
schedule st
) else if timeout = None && Uring.active_ops uring = 0 then (
(* Nothing further can happen at this point.
If there are no events in progress but also still no memory available, something has gone wrong! *)
assert (Queue.length mem_q = 0);
assert (Lwt_dllist.length mem_q = 0);
Lf_queue.close st.run_q; (* Just to catch bugs if something tries to enqueue later *)
`Exit_scheduler
) else (
@ -254,7 +257,14 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
If [need_wakeup] is still [true], this is fine because we don't promise to do that.
If [need_wakeup = false], a wake-up event will arrive and wake us up soon. *)
Trace.suspend_domain Begin;
let result = Uring.wait ?timeout uring in
let result =
(* Hack: liburing automatically retries [io_uring_enter] if an
interrupt is received and no timeout is set. However, we need
to return to OCaml mode so any pending signal handlers can
run. See: https://github.com/ocaml-multicore/eio/issues/732 *)
let timeout = Option.value timeout ~default:1e9 in
Uring.wait ~timeout uring
in
Trace.suspend_domain End;
Atomic.set st.need_wakeup false;
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
@ -267,6 +277,7 @@ let rec schedule ({run_q; sleep_q; mem_q; uring; _} as st) : [`Exit_scheduler] =
) else (
(* Someone added a new job while we were setting [need_wakeup] to [true].
They might or might not have seen that, so we can't be sure they'll send an event. *)
ignore (submit uring : int);
Atomic.set st.need_wakeup false;
Lf_queue.push run_q IO; (* Re-inject IO job in the run queue *)
schedule st
@ -292,14 +303,11 @@ and handle_complete st ~runnable result =
| Job_no_cancel k ->
Suspended.continue k result
| Cancel_job ->
begin match result with
| 0 (* Operation cancelled successfully *)
| -2 (* ENOENT - operation completed before cancel took effect *)
| -114 (* EALREADY - operation already in progress *)
-> ()
| errno ->
Log.warn (fun f -> f "Cancel returned unexpected error: %s" (Unix.error_message (Uring.error_of_errno errno)))
end;
(* We don't care about the result of the cancel operation, and there's nowhere to send it.
The possibilities are:
0 : Operation cancelled successfully
-2 : ENOENT - operation completed before cancel took effect
-114 : EALREADY - operation already in progress *)
schedule st
| Job_fn (k, f) ->
Fiber_context.clear_cancel_fn k.fiber;
@ -331,21 +339,6 @@ and complete_rw_req st ({len; cur_off; action; _} as req) res =
| _, Exactly len -> Suspended.continue action len
| n, Upto _ -> Suspended.continue action n
let alloc_buf_or_wait st k =
match st.mem with
| None -> Suspended.discontinue k (Failure "No fixed buffer available")
| Some mem ->
match Uring.Region.alloc mem with
| buf -> Suspended.continue k buf
| exception Uring.Region.No_space ->
Queue.push k st.mem_q;
schedule st
let free_buf st buf =
match Queue.take_opt st.mem_q with
| None -> Uring.Region.free buf
| Some k -> enqueue_thread st k buf
let rec enqueue_poll_add fd poll_mask st action =
Trace.log "poll_add";
let retry = with_cancel_hook ~action st (fun () ->
@ -403,8 +396,9 @@ let run ~extra_effects st main arg =
Fiber_context.destroy fiber;
Printexc.raise_with_backtrace ex (Printexc.get_raw_backtrace ())
);
effc = fun (type a) (e : a Effect.t) ->
effc = fun (type a) (e : a Effect.t) : ((a, _) continuation -> _) option ->
match e with
| Get -> Some (fun k -> continue k st)
| Enter fn -> Some (fun k ->
match Fiber_context.get_error fiber with
| Some e -> discontinue k e
@ -459,22 +453,6 @@ let run ~extra_effects st main arg =
Eio_unix.Private.Thread_pool.submit st.thread_pool ~ctx:fiber ~enqueue fn;
schedule st
)
| Alloc -> Some (fun k ->
match st.mem with
| None -> continue k None
| Some mem ->
match Uring.Region.alloc mem with
| buf -> continue k (Some buf)
| exception Uring.Region.No_space -> continue k None
)
| Alloc_or_wait -> Some (fun k ->
let k = { Suspended.k; fiber } in
alloc_buf_or_wait st k
)
| Free buf -> Some (fun k ->
free_buf st buf;
continue k ()
)
| e -> extra_effects.effc e
}
in
@ -482,7 +460,7 @@ let run ~extra_effects st main arg =
let `Exit_scheduler =
let new_fiber = Fiber_context.make_root () in
Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await
~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () ->
fork ~new_fiber (fun () ->
Switch.run_protected ~name:"eio_linux" (fun sw ->
@ -539,11 +517,14 @@ let with_sched ?(fallback=no_fallback) config fn =
| exception Unix.Unix_error(EPERM, _, _) -> fallback (`Msg "io_uring is not available (permission denied)")
| uring ->
let probe = Uring.get_probe uring in
if not (Uring.op_supported probe Uring.Op.shutdown) then (
if not (Uring.op_supported probe Uring.Op.mkdirat) then (
Uring.exit uring;
fallback (`Msg "Linux >= 5.11 is required for io_uring support")
fallback (`Msg "Linux >= 5.15 is required for io_uring support")
) else (
statx_works := Uring.op_supported probe Uring.Op.msg_ring;
(* The reason for an if here is to make sure we only set it once, when
the first domain is starting. This is just to avoid a tsan warning. *)
if not !statx_works && Uring.op_supported probe Uring.Op.msg_ring then
statx_works := true;
match
let mem =
let fixed_buf_len = block_size * n_blocks in
@ -552,14 +533,13 @@ let with_sched ?(fallback=no_fallback) config fn =
| Ok () ->
Some (Uring.Region.init ~block_size buf n_blocks)
| Error `ENOMEM ->
Log.warn (fun f -> f "Failed to allocate %d byte fixed buffer" fixed_buf_len);
None
in
let run_q = Lf_queue.create () in
Lf_queue.push run_q IO;
let sleep_q = Zzz.create () in
let io_q = Queue.create () in
let mem_q = Queue.create () in
let mem_q = Lwt_dllist.create () in
with_eventfd @@ fun eventfd ->
let thread_pool = Eio_unix.Private.Thread_pool.create ~sleep_q in
fn { mem; uring; run_q; io_q; mem_q; eventfd; need_wakeup = Atomic.make false; sleep_q; thread_pool }

View File

@ -6,7 +6,7 @@
(= %{system} "linux_elf") ; Historically, Linux-x86_32
(= %{system} "elf"))) ; Historically, Linux-ppc64
(modules eurcp_lib)
(libraries eio_linux))
(libraries eio_linux logs))
(executable
(name eurcp)
@ -47,7 +47,7 @@
(= %{system} "linux_elf") ; Historically, Linux-x86_32
(= %{system} "elf"))) ; Historically, Linux-ppc64
(modules test)
(libraries alcotest eio_linux))
(libraries alcotest eio_linux logs))
(mdx
(package eio_linux)

View File

@ -130,7 +130,7 @@ let test_read_exact () =
let ( / ) = Eio.Path.( / ) in
let path = env#cwd / "test.data" in
let msg = "hello" in
Eio.Path.save path ("!" ^ msg) ~create:(`Exclusive 0o600);
Eio.Path.save path ("!" ^ msg) ~create:(`Or_truncate 0o600);
Switch.run @@ fun sw ->
let fd = Eio_linux.Low_level.openat2 ~sw
~access:`R
@ -162,16 +162,16 @@ let test_statx () =
Eio_linux.run ~queue_depth:4 @@ fun env ->
let ( / ) = Eio.Path.( / ) in
let path = env#cwd / "test2.data" in
Eio.Path.with_open_out path ~create:(`Exclusive 0o600) @@ fun file ->
Eio.Path.with_open_out path ~create:(`Or_truncate 0o600) @@ fun file ->
Eio.Flow.copy_string "hello" file;
let buf = Uring.Statx.create () in
let test expected_len ~flags ?fd path =
Eio_linux.Low_level.statx ~mask:X.Mask.(type' + size) ?fd path buf flags;
let test expected_len ~follow dir path =
Eio_linux.Low_level.statx ~follow ~mask:X.Mask.(type' + size) dir path buf;
Alcotest.check kind_t "kind" `Regular_file (Uring.Statx.kind buf);
Alcotest.(check int64) "size" expected_len (Uring.Statx.size buf)
in
(* Lookup via cwd *)
test 5L ~flags:X.Flags.empty ?fd:None "test2.data";
test 5L ~follow:false Cwd "test2.data";
Eio.Flow.copy_string "+" file;
(* Lookup via file FD *)
Switch.run (fun sw ->
@ -182,7 +182,7 @@ let test_statx () =
~resolve:Uring.Resolve.empty
"test2.data"
in
test 6L ~flags:X.Flags.empty_path ~fd ""
test 6L ~follow:false (FD fd) ""
);
(* Lookup via directory FD *)
Eio.Flow.copy_string "+" file;
@ -194,22 +194,69 @@ let test_statx () =
~resolve:Uring.Resolve.empty
"."
in
test 7L ~flags:X.Flags.empty_path ~fd "test2.data"
test 7L ~follow:false (FD fd) "test2.data"
);
()
(* Ensure that an OCaml signal handler will run, even if we're sleeping in liburing at the time.
The problem here is that [__sys_io_uring_enter2] doesn't return EINTR, because it did successfully
submit an item. This causes liburing to retry without giving our OCaml signal handler a chance to run.
Note: we can't run this test with a timeout because liburing does return in that case! *)
let test_signal_race () =
Eio_linux.run @@ fun _env ->
let cond = Eio.Condition.create () in
let handle _ = Eio.Condition.broadcast cond in
Sys.(set_signal sigalrm) (Signal_handle handle);
Fiber.both
(fun () -> Eio.Condition.await_no_mutex cond)
(fun () -> ignore (Unix.setitimer ITIMER_REAL { it_interval = 0.; it_value = 0.001 } : Unix.interval_timer_status))
let test_alloc_fixed_or_wait () =
Eio_linux.run ~n_blocks:1 @@ fun _env ->
let block = Eio_linux.Low_level.alloc_fixed_or_wait () in
(* We have to wait for the block, but get cancelled while waiting. *)
begin
try
Fiber.both
(fun () -> ignore (Eio_linux.Low_level.alloc_fixed_or_wait () : Uring.Region.chunk))
(fun () -> raise Exit);
with Exit -> ()
end;
(* We have to wait for the block, and get it when the old one is freed. *)
Fiber.both
(fun () ->
let x = Eio_linux.Low_level.alloc_fixed_or_wait () in
Eio_linux.Low_level.free_fixed x
)
(fun () ->
Eio_linux.Low_level.free_fixed block
);
(* The old block is passed to the waiting fiber, but it's cancelled. *)
let block = Eio_linux.Low_level.alloc_fixed_or_wait () in
Fiber.both
(fun () ->
Fiber.first
(fun () -> ignore (Eio_linux.Low_level.alloc_fixed_or_wait ()); assert false)
(fun () -> ())
)
(fun () -> Eio_linux.Low_level.free_fixed block);
let block = Eio_linux.Low_level.alloc_fixed_or_wait () in
Eio_linux.Low_level.free_fixed block
let () =
let open Alcotest in
run "eio_linux" [
"io", [
test_case "copy" `Quick test_copy;
test_case "direct_copy" `Quick test_direct_copy;
test_case "poll_add" `Quick test_poll_add;
test_case "poll_add_busy" `Quick test_poll_add_busy;
test_case "iovec" `Quick test_iovec;
test_case "no_sqe" `Quick test_no_sqe;
test_case "read_exact" `Quick test_read_exact;
test_case "expose_backend" `Quick test_expose_backend;
test_case "statx" `Quick test_statx;
test_case "copy" `Quick test_copy;
test_case "direct_copy" `Quick test_direct_copy;
test_case "poll_add" `Quick test_poll_add;
test_case "poll_add_busy" `Quick test_poll_add_busy;
test_case "iovec" `Quick test_iovec;
test_case "no_sqe" `Quick test_no_sqe;
test_case "read_exact" `Quick test_read_exact;
test_case "expose_backend" `Quick test_expose_backend;
test_case "statx" `Quick test_statx;
test_case "signal_race" `Quick test_signal_race;
test_case "alloc-fixed-or-wait" `Quick test_alloc_fixed_or_wait;
];
]

View File

@ -48,6 +48,11 @@ let run_event_loop fn x =
Unix.set_nonblock unix_fd;
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket)
)
| Eio_unix.Net.Import_socket_listening (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd;
continue k (Net.listening_socket ~hook:Switch.null_hook fd)
)
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd;

View File

@ -384,6 +384,27 @@ CAMLprim value caml_eio_posix_renameat(value v_old_fd, value v_old_path, value v
CAMLreturn(Val_unit);
}
CAMLprim value caml_eio_posix_symlinkat(value v_old_path, value v_new_fd, value v_new_path) {
CAMLparam2(v_old_path, v_new_path);
size_t old_path_len = caml_string_length(v_old_path);
size_t new_path_len = caml_string_length(v_new_path);
char *old_path;
char *new_path;
int ret;
caml_unix_check_path(v_old_path, "symlinkat-old");
caml_unix_check_path(v_new_path, "symlinkat-new");
old_path = caml_stat_alloc(old_path_len + new_path_len + 2);
new_path = old_path + old_path_len + 1;
memcpy(old_path, String_val(v_old_path), old_path_len + 1);
memcpy(new_path, String_val(v_new_path), new_path_len + 1);
caml_enter_blocking_section();
ret = symlinkat(old_path, Int_val(v_new_fd), new_path);
caml_leave_blocking_section();
caml_stat_free_preserving_errno(old_path);
if (ret == -1) uerror("symlinkat", v_old_path);
CAMLreturn(Val_unit);
}
CAMLprim value caml_eio_posix_spawn(value v_errors, value v_actions) {
CAMLparam1(v_actions);
pid_t child_pid;

View File

@ -94,6 +94,9 @@ end = struct
| None -> invalid_arg "Target is not an eio_posix directory!"
| Some new_dir -> Err.run (Low_level.rename t.fd old_path new_dir) new_path
let symlink ~link_to t path =
Err.run (Low_level.symlink ~link_to t.fd) path
let open_dir t ~sw path =
let flags = Low_level.Open_flags.(rdonly + directory +? path) in
let fd = Err.run (Low_level.openat ~sw ~mode:0 t.fd path) flags in

View File

@ -8,7 +8,7 @@ let optional_flags = [
let () =
C.main ~name:"discover" (fun c ->
let c_flags = ["-D_LARGEFILE64_SOURCE"; "-D_XOPEN_SOURCE=700"; "-D_DARWIN_C_SOURCE"; "-D_GNU_SOURCE"] in
let c_flags = ["-D_LARGEFILE64_SOURCE"; "-D_XOPEN_SOURCE=700"; "-D_DARWIN_C_SOURCE"; "-D_GNU_SOURCE"; "-D_BSD_SOURCE"] in
let includes = ["sys/types.h"; "sys/stat.h"; "fcntl.h"] in
let extra_flags, missing_defs =
C.C_define.import c ~c_flags ~includes

View File

@ -263,7 +263,8 @@ module Resolve = struct
| new_base ->
state.dir_stack <- Tmp (new_base, state.dir_stack);
resolve state xs
| exception (Unix.Unix_error ((ENOTDIR | EMLINK | EUNKNOWNERR _), _, _) as e) ->
| exception (Unix.Unix_error ((ELOOP | ENOTDIR | EMLINK | EUNKNOWNERR _), _, _) as e) ->
(* Note: Linux uses ELOOP or ENOTDIR. FreeBSD uses EMLINK. NetBSD uses EFTYPE. *)
match Eio_unix.Private.read_link_unix base x with
| target ->
decr_max_follows state x;
@ -414,6 +415,14 @@ let rename old_dir old_path new_dir new_path =
let new_dir = Option.value new_dir ~default:at_fdcwd in
eio_renameat old_dir old_path new_dir new_path
external eio_symlinkat : string -> Unix.file_descr -> string -> unit = "caml_eio_posix_symlinkat"
let symlink ~link_to new_dir new_path =
in_worker_thread "symlink" @@ fun () ->
Resolve.with_parent "symlink-new" new_dir new_path @@ fun new_dir new_path ->
let new_dir = Option.value new_dir ~default:at_fdcwd in
eio_symlinkat link_to new_dir new_path
let read_link dirfd path =
in_worker_thread "read_link" @@ fun () ->
Resolve.with_parent "read_link" dirfd path @@ fun dirfd path ->
@ -549,6 +558,7 @@ module Process = struct
let t =
let pid =
Fd.use_exn "errors-w" errors_w @@ fun errors_w ->
Eio.Private.Trace.with_span "spawn" @@ fun () ->
eio_spawn errors_w c_actions
in
Fd.close errors_w;

View File

@ -78,6 +78,10 @@ val mkdir : mode:int -> dir_fd -> string -> unit
val unlink : dir:bool -> dir_fd -> string -> unit
val rename : dir_fd -> string -> dir_fd -> string -> unit
val symlink : link_to:string -> dir_fd -> string -> unit
(** [symlink ~link_to dir path] will create a new symlink at [dir / path]
linking to [link_to]. *)
val readdir : dir_fd -> string -> string array
val readv : fd -> Cstruct.t array -> int

View File

@ -14,6 +14,7 @@ CAMLprim value caml_eio_posix_fdopendir(value);
CAMLprim value caml_eio_posix_mkdirat(value, value, value);
CAMLprim value caml_eio_posix_unlinkat(value, value, value);
CAMLprim value caml_eio_posix_renameat(value, value, value, value);
CAMLprim value caml_eio_posix_symlinkat(value, value, value);
CAMLprim value caml_eio_posix_make_stat(value);
CAMLprim value caml_eio_posix_fstatat(value, value, value, value);
CAMLprim value caml_eio_posix_fstat(value, value);

View File

@ -379,7 +379,7 @@ let run ~extra_effects t main x =
let `Exit_scheduler =
let new_fiber = Fiber_context.make_root () in
Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await
~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () ->
fork ~new_fiber (fun () ->
Eio_unix.Private.Thread_pool.run t.thread_pool @@ fun () ->

View File

@ -3,8 +3,8 @@
(enabled_if (= %{os_type} "Unix"))
(deps (package eio_posix)))
(test
(name open_beneath)
(tests
(names open_beneath test_await)
(package eio_posix)
(build_if (= %{os_type} "Unix"))
(libraries eio_posix))

View File

@ -40,7 +40,7 @@ let test base path =
check ~mode:0 base (path ^ "/") L.Open_flags.rdonly;
check ~mode:0 base (path ^ "/.") L.Open_flags.rdonly
)
let test_denied base path =
match L.Open_flags.resolve_beneath with
| Some some_resolve_beneath ->
@ -57,7 +57,8 @@ let test_denied base path =
let () =
try
Eio_posix.run @@ fun _env ->
Eio_posix.run @@ fun env ->
Eio.Path.(rmtree ~missing_ok:true (Eio.Stdenv.cwd env / "test_beneath"));
Unix.mkdir "test_beneath" 0o700;
Unix.mkdir "test_beneath/subdir" 0o700;
Unix.symlink "subdir" "test_beneath/link_subdir";

View File

@ -0,0 +1,25 @@
open Eio.Std
let () =
Eio_posix.run @@ fun _ ->
let a, b = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
(* Start awaiting readable/writable state, but cancel immediately. *)
try
Eio.Cancel.sub (fun cc ->
Fiber.all [
(fun () -> Eio_unix.await_readable a);
(fun () -> Eio_unix.await_writable b);
(fun () -> Eio.Cancel.cancel cc Exit);
];
assert false
)
with Eio.Cancel.Cancelled _ ->
(* Now wait for something else. Will fail if the old FDs are still being waited on. *)
let c, d = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
Unix.close a;
Unix.close b;
Fiber.first
(fun () -> Eio_unix.await_readable c)
(fun () -> Eio_unix.await_writable d);
Unix.close c;
Unix.close d

View File

@ -48,6 +48,11 @@ let run_event_loop fn x =
(try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ());
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket)
)
| Eio_unix.Net.Import_socket_listening (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd;
continue k (Net.listening_socket ~hook:Switch.null_hook fd)
)
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd;

View File

@ -6,7 +6,7 @@
(foreign_stubs
(language c)
(include_dirs ../lib_eio/unix/include)
(names eio_windows_stubs eio_windows_cstruct_stubs))
(names eio_windows_stubs))
(c_library_flags :standard -lbcrypt -lntdll)
(libraries eio eio.unix eio.utils fmt))

View File

@ -1,149 +0,0 @@
/* From mirage/ocaml-cstruct
Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
Copyright (c) 2012 Pierre Chambart
Copyright (c) Christiano F. Haesbaert <haesbaert@haesbaert.org>
Copyright (c) Citrix Inc
Copyright (c) David Sheets <sheets@alum.mit.edu>
Copyright (c) Drup <drupyog@zoho.com>
Copyright (c) Hannes Mehnert <hannes@mehnert.org>
Copyright (c) Jeremy Yallop <yallop@gmail.com>
Copyright (c) Mindy Preston <meetup@yomimono.org>
Copyright (c) Nicolas Ojeda Bar <n.oje.bar@gmail.com>
Copyright (c) Richard Mortier <mort@cantab.net>
Copyright (c) Rudi Grinberg <rudi.grinberg@gmail.com>
Copyright (c) Thomas Gazagnaire <thomas@gazagnaire.com>
Copyright (c) Thomas Leonard <talex5@gmail.com>
Copyright (c) Vincent Bernardoff <vb@luminar.eu.org>
Copyright (c) pqwy <david@numm.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/custom.h>
#include <caml/callback.h>
#include <caml/alloc.h>
#include <caml/unixsupport.h>
#include <caml/bigarray.h>
#include <caml/threads.h>
#include <caml/fail.h>
#include <stdio.h>
#include <errno.h>
CAMLprim value eio_windows_cstruct_read(value val_fd, value val_c)
{
CAMLparam2(val_fd, val_c);
CAMLlocal3(val_buf, val_ofs, val_len);
uint8_t *buf;
size_t len;
ssize_t n = 0;
int win32err = 0;
SOCKET s;
HANDLE h;
DWORD numread;
int ok;
val_buf = Field(val_c, 0);
val_ofs = Field(val_c, 1);
val_len = Field(val_c, 2);
buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs);
len = (size_t)Long_val(val_len);
switch (Descr_kind_val(val_fd))
{
case KIND_SOCKET:
s = Socket_val(val_fd);
caml_release_runtime_system();
n = recv(s, buf, len, 0);
win32err = WSAGetLastError();
caml_acquire_runtime_system();
if (n == SOCKET_ERROR)
{
win32_maperr(win32err);
uerror("stub_cstruct_read", Nothing);
}
break;
case KIND_HANDLE:
h = Handle_val(val_fd);
caml_release_runtime_system();
ok = ReadFile(h, buf, len, &numread, NULL);
win32err = GetLastError();
n = numread;
caml_acquire_runtime_system();
if (!ok)
{
win32_maperr(win32err);
uerror("stub_cstruct_read", Nothing);
}
break;
default:
caml_failwith("unknown Descr_kind_val");
}
CAMLreturn(Val_int(n));
}
CAMLprim value eio_windows_cstruct_write(value val_fd, value val_c)
{
CAMLparam2(val_fd, val_c);
CAMLlocal3(val_buf, val_ofs, val_len);
val_buf = Field(val_c, 0);
val_ofs = Field(val_c, 1);
val_len = Field(val_c, 2);
void *buf = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs);
size_t len = Long_val(val_len);
ssize_t n = 0;
int win32err = 0;
switch (Descr_kind_val(val_fd))
{
case KIND_SOCKET:
SOCKET s = Socket_val(val_fd);
caml_release_runtime_system();
n = send(s, buf, len, 0);
win32err = WSAGetLastError();
caml_acquire_runtime_system();
if (n == SOCKET_ERROR)
{
win32_maperr(win32err);
unix_error(errno, "stub_cstruct_write", Nothing);
}
break;
case KIND_HANDLE:
HANDLE h = Handle_val(val_fd);
DWORD numwritten;
caml_release_runtime_system();
int ok = WriteFile(h, buf, len, &numwritten, NULL);
win32err = GetLastError();
n = numwritten;
caml_acquire_runtime_system();
if (!ok)
{
win32_maperr(win32err);
uerror("stub_cstruct_write", Nothing);
}
break;
default:
caml_failwith("unknown Descr_kind_val");
}
CAMLreturn(Val_int(n));
}

View File

@ -237,7 +237,7 @@ CAMLprim value caml_eio_windows_unlinkat(value v_dirfd, value v_pathname, value
if (!NT_SUCCESS(r)) {
caml_win32_maperr(RtlNtStatusToDosError(r));
uerror("openat", Nothing);
uerror("openat", v_pathname);
}
// Now close the file to delete it
@ -252,6 +252,11 @@ CAMLprim value caml_eio_windows_renameat(value v_old_fd, value v_old_path, value
uerror("renameat is not supported on windows yet", Nothing);
}
CAMLprim value caml_eio_windows_symlinkat(value v_old_path, value v_new_fd, value v_new_path)
{
uerror("symlinkat is not supported on windows yet", Nothing);
}
CAMLprim value caml_eio_windows_spawn(value v_errors, value v_actions)
{
uerror("processes are not supported on windows yet", Nothing);

View File

@ -20,7 +20,7 @@ let wrap code name arg =
| ENOENT -> Eio.Fs.err (Not_found e)
| EXDEV | EACCES | EPERM -> Eio.Fs.err (Permission_denied e)
| ECONNREFUSED -> Eio.Net.err (Connection_failure (Refused e))
| ECONNRESET | EPIPE -> Eio.Net.err (Connection_reset e)
| ECONNRESET | EPIPE | ECONNABORTED -> Eio.Net.err (Connection_reset e)
| _ -> unclassified_error e
let run fn x =

View File

@ -82,7 +82,7 @@ end = struct
let dir = resolve t dir in
Switch.run @@ fun sw ->
let open Low_level in
let dirfd = Low_level.openat ~sw ~nofollow:true dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(directory) in
let dirfd = Err.run (Low_level.openat ~sw ~nofollow:true dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if)) Flags.Create.(directory) in
fn (Some dirfd) leaf
)
) else fn None path
@ -172,6 +172,10 @@ end = struct
with_parent_dir new_dir new_path @@ fun new_dir new_path ->
Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path
let symlink ~link_to t path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.symlink ~link_to dirfd) path
let close t = t.closed <- true
let open_dir t ~sw path =

View File

@ -39,17 +39,25 @@ let rec do_nonblocking ty fn fd =
do_nonblocking ty fn fd
let read fd buf start len =
await_readable fd;
Fd.use_exn "read" fd @@ fun fd ->
do_nonblocking Read (fun fd -> Unix.read fd buf start len) fd
let read_cstruct fd buf =
let read_cstruct fd (buf:Cstruct.t) =
await_readable fd;
Fd.use_exn "read_cstruct" fd @@ fun fd ->
do_nonblocking Read (fun fd -> Unix_cstruct.read fd buf) fd
do_nonblocking Read (fun fd -> Unix.read_bigarray fd buf.buffer buf.off buf.len) fd
let write fd buf start len =
await_writable fd;
Fd.use_exn "write" fd @@ fun fd ->
do_nonblocking Write (fun fd -> Unix.write fd buf start len) fd
let write_cstruct fd (buf:Cstruct.t) =
await_writable fd;
Fd.use_exn "write_cstruct" fd @@ fun fd ->
do_nonblocking Write (fun fd -> Unix.write_bigarray fd buf.buffer buf.off buf.len) fd
let sleep_until time =
Sched.enter @@ fun t k ->
Sched.await_timeout t k time
@ -148,8 +156,11 @@ let readv fd bufs =
do_nonblocking Read (fun fd -> eio_readv fd bufs) fd
let writev fd bufs =
Fd.use_exn "writev" fd @@ fun fd ->
do_nonblocking Write (fun fd -> Unix_cstruct.writev fd bufs) fd
let rec loop buf = if Cstruct.length buf > 0 then begin
let n = write_cstruct fd buf in
loop @@ Cstruct.shift buf n
end in
List.iter loop bufs
let preadv ~file_offset fd bufs =
Fd.use_exn "preadv" fd @@ fun fd ->
@ -234,6 +245,14 @@ let rename ?old_dir old_path ?new_dir new_path =
in_worker_thread @@ fun () ->
eio_renameat old_dir old_path new_dir new_path
external eio_symlinkat : string -> Unix.file_descr option -> string -> unit = "caml_eio_windows_symlinkat"
let symlink ~link_to new_dir new_path =
with_dirfd "symlink-new" new_dir @@ fun new_dir ->
in_worker_thread @@ fun () ->
eio_symlinkat link_to new_dir new_path
let lseek fd off cmd =
Fd.use_exn "lseek" fd @@ fun fd ->
let cmd =

View File

@ -22,6 +22,7 @@ val sleep_until : Mtime.t -> unit
val read : fd -> bytes -> int -> int -> int
val read_cstruct : fd -> Cstruct.t -> int
val write : fd -> bytes -> int -> int -> int
val write_cstruct : fd -> Cstruct.t -> int
val socket : sw:Switch.t -> Unix.socket_domain -> Unix.socket_type -> int -> fd
val connect : fd -> Unix.sockaddr -> unit
@ -48,6 +49,10 @@ val mkdir : ?dirfd:fd -> ?nofollow:bool -> mode:int -> string -> unit
val unlink : ?dirfd:fd -> dir:bool -> string -> unit
val rename : ?old_dir:fd -> string -> ?new_dir:fd -> string -> unit
val symlink : link_to:string -> fd option -> string -> unit
(** [symlink ~link_to dir path] will create a new symlink at [dir / path]
linking to [link_to]. *)
val readdir : string -> string array
val readv : fd -> Cstruct.t array -> int

View File

@ -83,24 +83,23 @@ let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler)
(* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *)
let getaddrinfo ~service node =
let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } =
match ai_family, ai_socktype, ai_addr with
| (Unix.PF_INET | PF_INET6),
(Unix.SOCK_STREAM | SOCK_DGRAM),
Unix.ADDR_INET (inet_addr,port) -> (
match ai_protocol with
| 6 -> Some (`Tcp (Eio_unix.Net.Ipaddr.of_unix inet_addr, port))
| 17 -> Some (`Udp (Eio_unix.Net.Ipaddr.of_unix inet_addr, port))
| _ -> None)
| _ -> None
(* OCaml's [Unix.getaddrinfo] on Windows doesn't set [ai_protocol] to
anything useful, so you can't tell which addresses are TCP and which are
UDP. So, do two separate queries. *)
let get ty k =
Unix.getaddrinfo node service [AI_SOCKTYPE ty]
|> List.filter_map (function
| {Unix.ai_addr = ADDR_INET (host, port); _} ->
Some (k (Eio_unix.Net.Ipaddr.of_unix host, port))
| _ -> None
)
in
Err.run (Eio_unix.run_in_systhread ~label:"getaddrinfo") @@ fun () ->
let rec aux () =
try
Unix.getaddrinfo node service []
|> List.filter_map to_eio_sockaddr_t
get SOCK_STREAM (fun x -> `Tcp x) @
get SOCK_DGRAM (fun x -> `Udp x)
with Unix.Unix_error (EINTR, _, _) -> aux ()
in
aux ()

View File

@ -271,6 +271,8 @@ let await_readable t (k : unit Suspended.t) fd =
if was_empty then update t waiters fd;
Fiber_context.set_cancel_fn k.fiber (fun ex ->
Lwt_dllist.remove node;
if Lwt_dllist.is_empty waiters.read then
update t waiters fd;
t.active_ops <- t.active_ops - 1;
enqueue_failed_thread t k ex
);
@ -287,6 +289,8 @@ let await_writable t (k : unit Suspended.t) fd =
if was_empty then update t waiters fd;
Fiber_context.set_cancel_fn k.fiber (fun ex ->
Lwt_dllist.remove node;
if Lwt_dllist.is_empty waiters.write then
update t waiters fd;
t.active_ops <- t.active_ops - 1;
enqueue_failed_thread t k ex
);
@ -370,7 +374,7 @@ let run ~extra_effects t main x =
let `Exit_scheduler =
let new_fiber = Fiber_context.make_root () in
Domain_local_await.using
~prepare_for_await:Eio.Private.Dla.prepare_for_await
~prepare_for_await:Eio_utils.Dla.prepare_for_await
~while_running:(fun () ->
fork ~new_fiber (fun () ->
Eio_unix.Private.Thread_pool.run t.thread_pool @@ fun () ->

View File

@ -1,3 +1,5 @@
open Eio.Std
module Timeout = struct
let test clock () =
let t0 = Unix.gettimeofday () in
@ -48,6 +50,35 @@ module Dla = struct
]
end
module Await_fd = struct
let test_cancel () =
let a, b = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
(* Start awaiting readable/writable state, but cancel immediately. *)
try
Eio.Cancel.sub (fun cc ->
Fiber.all [
(fun () -> Eio_unix.await_readable a);
(fun () -> Eio_unix.await_writable b);
(fun () -> Eio.Cancel.cancel cc Exit);
];
assert false
)
with Eio.Cancel.Cancelled _ ->
(* Now wait for something else. Will fail if the old FDs are still being waited on. *)
let c, d = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
Unix.close a;
Unix.close b;
Fiber.first
(fun () -> Eio_unix.await_readable c)
(fun () -> Eio_unix.await_writable d);
Unix.close c;
Unix.close d
let tests = [
"cancel", `Quick, test_cancel;
]
end
let () =
Eio_windows.run @@ fun env ->
@ -56,5 +87,6 @@ let () =
"fs", Test_fs.tests env;
"timeout", Timeout.tests env;
"random", Random.tests env;
"dla", Dla.tests
]
"dla", Dla.tests;
"await", Await_fd.tests;
]

View File

@ -158,6 +158,9 @@ let test_symlink env () =
Unix.mkdir "another" 0o700;
print_endline @@ Unix.realpath "to-subdir" |}
*)
if not (Unix.has_symlink ()) then
Printf.printf "Skipping test_symlink on systems that don't support symlinks.\n"
else
let cwd = Eio.Stdenv.cwd env in
try_mkdir (cwd / "sandbox");
Unix.symlink ~to_dir:true ".." "sandbox\\to-root";
@ -277,4 +280,5 @@ let tests env = [
"unlink", `Quick, test_unlink env;
"failing-unlink", `Quick, try_failing_unlink env;
"rmdir", `Quick, test_remove_dir env;
"mkdirs", `Quick, test_mkdirs env;
]

View File

@ -168,7 +168,7 @@ Must be between 0 and 1:
let pool = Executor_pool.create ~sw ~domain_count:2 mgr in
Executor_pool.submit_exn pool ~weight:(-5.) (fun () -> ())
;;
Exception: Invalid_argument "Executor_pool: weight not >= 0.0 && <= 1.0".
Exception: Invalid_argument "Executor_pool: weight -5 not >= 0.0 && <= 1.0".
```
```ocaml
# run @@ fun mgr sleep duration ->
@ -176,7 +176,7 @@ Exception: Invalid_argument "Executor_pool: weight not >= 0.0 && <= 1.0".
let pool = Executor_pool.create ~sw ~domain_count:2 mgr in
Executor_pool.submit_exn pool ~weight:1.1 (fun () -> ())
;;
Exception: Invalid_argument "Executor_pool: weight not >= 0.0 && <= 1.0".
Exception: Invalid_argument "Executor_pool: weight 1.1 not >= 0.0 && <= 1.0".
```

View File

@ -89,11 +89,17 @@ let try_stat path =
traceln "%a -> %s" Eio.Path.pp path a
else
traceln "%a -> %s / %s" Eio.Path.pp path a b
let try_symlink ~link_to path =
match Path.symlink ~link_to path with
| s -> traceln "symlink %a -> %S" Path.pp path link_to
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
```
# Basic test cases
Creating a file and reading it back:
```ocaml
# run ~clear:["test-file"] @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -104,6 +110,7 @@ Creating a file and reading it back:
```
Check the file got the correct permissions (subject to the umask set above):
```ocaml
# Printf.printf "Perm = %o\n" ((Unix.stat "test-file").st_perm);;
Perm = 644
@ -113,6 +120,7 @@ Perm = 644
# Sandboxing
Trying to use cwd to access a file outside of that subtree fails:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -123,6 +131,7 @@ Exception: Eio.Io Fs Permission_denied _,
```
Trying to use cwd to access an absolute path fails:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -135,6 +144,7 @@ Exception: Eio.Io Fs Permission_denied _,
# Creation modes
Exclusive create fails if already exists:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -146,6 +156,7 @@ Exception: Eio.Io Fs Already_exists _,
```
If-missing create succeeds if already exists:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -158,6 +169,7 @@ If-missing create succeeds if already exists:
```
Truncate create succeeds if already exists, and truncates:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -172,6 +184,7 @@ Truncate create succeeds if already exists, and truncates:
```
Error if no create and doesn't exist:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -183,6 +196,7 @@ Exception: Eio.Io Fs Not_found _,
```
Appending to an existing file:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -215,12 +229,13 @@ Appending to an existing file:
```
Creating directories with nesting, symlinks, etc:
```ocaml
# run ~clear:["to-subdir"; "to-root"; "dangle"] @@ fun env ->
Unix.symlink "/" "to-root";
Unix.symlink "subdir" "to-subdir";
Unix.symlink "foo" "dangle";
let cwd = Eio.Stdenv.cwd env in
Path.symlink ~link_to:"/" (cwd / "to-root");
Path.symlink ~link_to:"subdir" (cwd / "to-subdir");
Path.symlink ~link_to:"foo" (cwd / "dangle");
try_mkdir (cwd / "subdir");
try_mkdir (cwd / "to-subdir/nested");
try_mkdir (cwd / "to-root/tmp/foo");
@ -384,10 +399,10 @@ Reads and writes follow symlinks, but unlink operates on the symlink itself:
let file2 = cwd / "file2" in
try_write_file ~create:(`Exclusive 0o600) file1 "data1";
try_write_file ~create:(`Exclusive 0o400) file2 "data2";
Unix.symlink "dir1/file1" "link1";
Unix.symlink "../file2" "dir1/link2";
Unix.symlink "dir1" "linkdir";
Unix.symlink "/" "linkroot";
Path.symlink ~link_to:"dir1/file1" (cwd / "link1");
Path.symlink ~link_to:"../file2" (cwd / "dir1/link2");
Path.symlink ~link_to:"dir1" (cwd / "linkdir");
Path.symlink ~link_to:"/" (cwd / "linkroot");
try_read_file file1;
try_read_file (cwd / "link1");
try_read_file (cwd / "linkdir" / "file1");
@ -504,6 +519,7 @@ Removing something that doesn't exist or is out of scope:
# Limiting to a subdirectory
Create a sandbox, write a file with it, then read it from outside:
```ocaml
# run ~clear:["sandbox"] @@ fun env ->
Switch.run @@ fun sw ->
@ -540,10 +556,10 @@ Create a sandbox, write a file with it, then read it from outside:
reject (cwd / "/");
test (cwd / "foo/bar/..");
test (fs / "foo/bar");
Unix.symlink ".." "foo/up";
Path.symlink ~link_to:".." (cwd / "foo/up");
test (cwd / "foo/up/foo/bar");
reject (cwd / "foo/up/../bar");
Unix.symlink "/" "foo/root";
Path.symlink ~link_to:"/" (cwd / "foo/root");
reject (cwd / "foo/root/..");
reject (cwd / "missing");
+open_dir <cwd:foo/bar> -> OK
@ -566,6 +582,7 @@ Create a sandbox, write a file with it, then read it from outside:
We create a directory and chdir into it.
Using `cwd` we can't access the parent, but using `fs` we can:
```ocaml
# run ~clear:["fs-test"; "outside-cwd"] @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -604,7 +621,7 @@ Reading directory entries under `cwd` and outside of `cwd`.
try_read_dir (tmpdir / ".");
try_read_dir (tmpdir / "..");
try_read_dir (tmpdir / "test-3");
Unix.symlink "test-1" "readdir/link-1";
Path.symlink ~link_to:"test-1" (cwd / "readdir/link-1");
try_read_dir (tmpdir / "link-1");
+mkdir <cwd:readdir> -> ok
+mkdir <readdir:test-1> -> ok
@ -650,6 +667,29 @@ Exception: Eio.Io Fs Permission_denied _,
opening <cwd:/dev/null>
```
Symlinking and sandboxing:
```ocaml
# run ~clear:["hello.txt"; "world.txt"] @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
Path.save ~create:(`Exclusive 0o600) (cwd / "hello.txt") "Hello World!";
try_symlink ~link_to:"hello.txt" (cwd / "../world.txt");
try_symlink ~link_to:"hello.txt" (cwd / "/world.txt");
try_symlink ~link_to:"hello.txt" (cwd / "world.txt");
traceln "world.txt -> hello.txt: %s" (Path.load (cwd / "world.txt"));
try_symlink ~link_to:"hello.txt" (cwd / "world.txt");
try_symlink ~link_to:"/" (cwd / "root");
try_read_dir (cwd / "root");;
+Eio.Io Fs Permission_denied _, creating symlink <cwd:../world.txt> -> hello.txt
+Eio.Io Fs Permission_denied _, creating symlink <cwd:/world.txt> -> hello.txt
+symlink <cwd:world.txt> -> "hello.txt"
+world.txt -> hello.txt: Hello World!
+Eio.Io Fs Already_exists _, creating symlink <cwd:world.txt> -> hello.txt
+symlink <cwd:root> -> "/"
+Eio.Io Fs Permission_denied _, reading directory <cwd:root>
- : unit = ()
```
## Streamling lines
```ocaml
@ -781,15 +821,15 @@ Unconfined:
let cwd = Eio.Stdenv.cwd env in
Switch.run @@ fun sw ->
try_mkdir (cwd / "stat_subdir2");
Unix.symlink "stat_subdir2" "symlink";
Unix.symlink "missing" "broken-symlink";
Path.symlink ~link_to:"stat_subdir2" (cwd / "symlink");
Path.symlink ~link_to:"missing" (cwd / "broken-symlink");
try_stat (cwd / "stat_subdir2");
try_stat (cwd / "symlink");
try_stat (cwd / "broken-symlink");
try_stat cwd;
try_stat (cwd / "..");
try_stat (cwd / "stat_subdir2/..");
Unix.symlink ".." "parent-symlink";
Path.symlink ~link_to:".." (cwd / "parent-symlink");
try_stat (cwd / "parent-symlink");
try_stat (cwd / "missing1" / "missing2");
+mkdir <cwd:stat_subdir2> -> ok
@ -811,7 +851,7 @@ Unconfined:
let fs = Eio.Stdenv.fs env in
let cwd = Eio.Stdenv.cwd env in
Switch.run @@ fun sw ->
Unix.symlink "file" "symlink";
Path.symlink ~link_to:"file" (cwd / "symlink");
try_read_link (cwd / "symlink");
try_read_link (fs / "symlink");
try_write_file (cwd / "file") "data" ~create:(`Exclusive 0o600);
@ -945,3 +985,29 @@ Exception: Failure "Simulated error".
+seek from end: 9
- : unit = ()
```
# Extending paths
```ocaml
# run @@ fun env ->
let base = fst env#cwd in
List.iter (fun (a, b) -> traceln "%S / %S = %S" a b (snd ((base, a) / b))) [
"foo", "bar";
"foo/", "bar";
"foo", "/bar";
"foo", "";
"foo/", "";
"", "";
"", "bar";
"/", "";
]
+"foo" / "bar" = "foo/bar"
+"foo/" / "bar" = "foo/bar"
+"foo" / "/bar" = "/bar"
+"foo" / "" = "foo/"
+"foo/" / "" = "foo/"
+"" / "" = ""
+"" / "bar" = "bar"
+"/" / "" = "/"
- : unit = ()
```

View File

@ -373,6 +373,29 @@ Wrapping a Unix FD as an Eio stream socket:
- : unit = ()
```
Wrapping a Unix FD as a listening Eio socket:
```ocaml
# run @@ fun ~net sw ->
let l = Unix.(socket PF_INET SOCK_STREAM 0) in
Unix.bind l (Unix.ADDR_INET (Unix.inet_addr_loopback, 8082));
Unix.listen l 40;
let l = Eio_unix.Net.import_socket_listening ~sw ~close_unix:true l in
Fiber.both
(fun () -> run_server ~sw l)
(fun () ->
run_client ~sw ~net ~addr:(`Tcp (Eio.Net.Ipaddr.V4.loopback, 8082));
traceln "Client finished - cancelling server";
raise Graceful_shutdown
);;
+Connecting to server...
+Server accepted connection from client
+Server received: "Hello from client"
+Client received: "Bye"
+Client finished - cancelling server
Exception: Graceful_shutdown.
```
Wrapping a Unix FD as an datagram Eio socket:
```ocaml
@ -597,7 +620,9 @@ Exception: Eio.Io Fs Not_found _,
```ocaml
# Eio_main.run @@ fun env ->
let sockaddr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 80) in
Eio.Net.getnameinfo env#net sockaddr;;
let (host, service) = Eio.Net.getnameinfo env#net sockaddr in
let service = if service = "www" then "http" else service in (* OpenBSD *)
(host, service)
- : string * string = ("localhost", "http")
```

View File

@ -77,6 +77,37 @@ Two uses with a capacity of 2; they run in parallel:
- : unit = ()
```
Capacity of 1; two uses that cannot block and two normal uses; first 2 are parallel, next 2 are sequential.
Note that the pool always suspends the calling fiber when creating a new slot,
even if the fiber ends up providing the new slot to itself,
which is why the items get assigned out of order in this test.
```ocaml
# Eio_mock.Backend.run @@ fun () ->
let p0, r0 = Promise.create () in
let p1, r1 = Promise.create () in
let t = create 1 [p0; p1] ~dispose in
Fiber.all [
(fun () -> P.use t ~never_block:true (fun x -> traceln "A: using item %d" x; Fiber.yield (); traceln "A done"));
(fun () -> P.use t ~never_block:true (fun x -> traceln "B: using item %d" x; Fiber.yield (); traceln "B done"));
(fun () -> P.use t (fun x -> traceln "C: using item %d" x; Fiber.yield (); traceln "C done"));
(fun () -> P.use t (fun x -> traceln "D: using item %d" x; Fiber.yield (); traceln "D done"));
(fun () -> Promise.resolve r0 (Ok 0); Promise.resolve r1 (Ok 1));
];
+Creating item 0
+Creating item 1
+A: using item 1
+B: using item 0
+A done
+B done
+disposing 0
+C: using item 1
+C done
+D: using item 1
+D done
- : unit = ()
```
## Cancellation
```ocaml

View File

@ -145,24 +145,24 @@ If a command fails, we get shown the arguments (quoted if necessary):
```ocaml
# run @@ fun mgr env ->
Process.run mgr ["bash"; "-c"; "exit 3"; ""; "foo"; "\"bar\""];;
Process.run mgr ["sh"; "-c"; "exit 3"; ""; "foo"; "\"bar\""];;
Exception:
Eio.Io Process Child_error Exited (code 3),
running command: bash -c "exit 3" "" foo "\"bar\""
running command: sh -c "exit 3" "" foo "\"bar\""
```
Exit code success can be determined by is_success (Process.run):
```ocaml
# run @@ fun mgr env ->
Process.run ~is_success:(Int.equal 3) mgr ["bash"; "-c"; "exit 3"];;
Process.run ~is_success:(Int.equal 3) mgr ["sh"; "-c"; "exit 3"];;
- : unit = ()
# run @@ fun mgr env ->
Process.run ~is_success:(Int.equal 3) mgr ["bash"; "-c"; "exit 0"];;
Process.run ~is_success:(Int.equal 3) mgr ["sh"; "-c"; "exit 0"];;
Exception:
Eio.Io Process Child_error Exited (code 0),
running command: bash -c "exit 0"
running command: sh -c "exit 0"
```
Exit code success can be determined by is_success (Process.parse_out):

View File

@ -231,3 +231,21 @@ Cancellation:
+Cancel sleeper
Exception: Invalid_argument "No further events scheduled on mock clock".
```
Sleep:
```ocaml
# try
Eio_mock.Backend.run_full @@ fun env ->
let timeout = Eio.Time.Timeout.seconds env#mono_clock 2. in
Eio.Time.Timeout.sleep timeout;
traceln "Timeout done";
Eio.Time.Timeout.(sleep none);
assert false
with Eio_mock.Backend.Deadlock_detected ->
traceln "Never finished";;
+mock time is now 2
+Timeout done
+Never finished
- : unit = ()
```