326 Commits

Author SHA1 Message Date
Red Bear OS d90f84883e fix: use local path deps for redox_syscall, redoxfs, libredox 2026-07-05 23:54:35 +03:00
Red Bear OS e604868baf version: use +rb0.2.5 build metadata instead of -rb0.2.5 pre-release
Semver pre-release suffix (-rb0.2.5) breaks Cargo's [patch.crates-io]
matching for transitive deps. Build metadata (+rb0.2.5) is semver-
compatible: ^0.9.0 matches 0.9.0+rb0.2.5, patch redirection works,
and the Red Bear suffix is still visible in the version string.
2026-07-05 09:16:12 +03:00
Red Bear OS 386b19566d fork: add Red Bear author attribution to Cargo.toml 2026-07-05 08:48:16 +03:00
vasilito 4591fbabcc fork: rebased onto upstream 0.2.42 with Red Bear additions
This rebases the local installer fork onto upstream 0.2.42
(commit ed6013c295) and reapplies
the documented Red Bear OS additions from the local state.

  * Cargo.toml: bumped version to 0.2.42-rb1 per the no-fake-version-
    label policy. Also bumped dep versions (libc 0.2.70 -> with
    optional, pkgar 0.2 -> 0.2.2, redox-pkg 0.3 -> 0.3.1, ring with
    optional, libredox with optional) so the build matches the local
    fork ecosystem.

  * src/installer.rs: integrate the collision detection Red Bear
    added in Phase J. The installer now aborts on init-service file
    collisions (e.g. /usr/lib/init.d/ vs /etc/init.d/) and prints
    a clear error pointing to the local docs.

  * src/lib.rs: declare the collision module and re-export the
    CollisionDetector.

  * src/config/package.rs: extend PackageConfig with PartialEq so
    the new package-groups feature (added in commit e45ce4d) can
    compare configs.

  * src/bin/installer.rs, src/bin/installer_tui.rs: expose the
    --strict-collision / REDBEAR_STRICT_COLLISION env var to
    optionally make non-critical file collisions fatal.

  * gui/: new redox_installer_gui binary — the TUI installer. This
    is the new binary added in Red Bear OS Phase 3 and lives in
    local/patches/installer/0001-redbear-os-additions.patch as a
    documented Red Bear addition.

The previous redox.patch in local/patches/installer/ is removed
and replaced with this regenerated 0001-redbear-os-additions.patch
that applies cleanly with --fuzz=0 to upstream 0.2.42.

Verified by local/scripts/verify-fork-versions.sh: the local
installer-0.2.42-rb1 branch source content matches upstream 0.2.42
plus this single documented patch.
2026-07-05 05:57:10 +03:00
Jeremy Soller ed6013c295 Merge branch 'bump-argon' into 'master'
Bump argon deps

See merge request redox-os/installer!76
2026-03-17 05:46:41 -06:00
Wildan M e79c1be43e Bump argon deps 2026-03-16 21:27:44 +07:00
Jeremy Soller b5b9bfd825 Merge branch 'bump-ver' into 'master'
Bump redox_syscall and version

See merge request redox-os/installer!75
2026-03-15 13:23:48 -06:00
Wildan M 8c2b0f9de7 Bump redox_syscall and version 2026-03-15 18:44:08 +07:00
Jeremy Soller 9800eac87c Merge branch 'misc_changes2' into 'master'
Make arg_parser non-optional

See merge request redox-os/installer!74
2026-03-11 14:42:10 -06:00
bjorn3 e61506c50f Make arg_parser non-optional
It is used by a Debug impl in the config code.
2026-03-11 21:01:48 +01:00
Jeremy Soller b29021c352 Merge branch 'misc_changes' into 'master'
Misc changes

See merge request redox-os/installer!73
2026-03-11 14:00:12 -06:00
bjorn3 474ff08431 Fix bootloader location 2026-03-11 20:53:46 +01:00
bjorn3 f691afabab Fix usage message 2026-03-11 20:31:03 +01:00
bjorn3 f74e3fad19 Make home directories 700 instead of 777 2026-03-11 20:12:40 +01:00
Jeremy Soller 3ef2cac56d Merge branch 'new-pkg' into 'master'
Update pkg to use new API

See merge request redox-os/installer!72
2026-03-08 13:25:42 -06:00
Wildan M aa9c978f90 Resolve ring dependency 2026-03-08 21:04:32 +07:00
Wildan M a6fd457652 Update pkg to use new API 2026-03-08 20:53:33 +07:00
Jeremy Soller 7806f87c56 Merge branch 'move_bootloader_to_user' into 'master'
Bootloader is now stored in /usr/lib/boot

See merge request redox-os/installer!71
2026-03-07 08:36:04 -07:00
bjorn3 70b8cb0e37 Bootloader is now stored in /usr/lib/boot 2026-03-07 16:30:35 +01:00
Jeremy Soller 397974cb68 Workaround for existing symlinks by overwriting them 2026-03-04 11:18:14 -07:00
Jeremy Soller 8765c51696 Log target of symlink 2026-03-04 10:53:22 -07:00
Jeremy Soller 2d3b2ed38a Improve some error reporting 2026-03-04 10:49:09 -07:00
Jeremy Soller 1840ceac85 Merge branch 'fix-redox-err' into 'master'
Fix redox compilation error

See merge request redox-os/installer!70
2026-02-14 12:31:56 -07:00
Wildan M 59d0872197 Fix redox compilation error 2026-02-15 01:47:13 +07:00
Jeremy Soller 7f918e544e Merge branch 'skel-copy' into 'master'
Copy /etc/skel to users directory

See merge request redox-os/installer!69
2026-02-14 06:41:32 -07:00
Wildan M 730eb9a606 Copy /etc/skel to users directory 2026-02-14 18:13:27 +07:00
Jeremy Soller a790f1c228 Merge branch 'local-err-help' into 'master'
Make missing file clear when locally installing

See merge request redox-os/installer!68
2026-02-10 05:59:32 -07:00
Wildan M b33f3c1a80 Make missing file clear when locally installing 2026-02-10 01:35:44 +07:00
Jeremy Soller b0f90da7b8 Merge branch 'cli-enh' into 'master'
Some improvement as a standalone CLI

See merge request redox-os/installer!67
2026-01-30 20:28:01 -07:00
Wildan M c4ed26e3d4 Don't confirm password if empty 2026-01-31 05:10:03 +07:00
Wildan M 753256cfaf Strip fallocate from CI test 2026-01-31 05:02:08 +07:00
Wildan M cfc7938ecc Shorten remote packages log 2026-01-31 04:53:30 +07:00
Wildan M 15de2b4272 Add help hint, remove repo_binary 2026-01-31 04:17:54 +07:00
Wildan M fed265252d Unify password prompt code 2026-01-31 04:17:11 +07:00
Wildan M d40619d66c Implement Display for Config 2026-01-31 04:15:10 +07:00
Jeremy Soller 64f46052db 0.2.41 - update dependencies 2026-01-22 10:39:26 -07:00
Jeremy Soller eff771e640 0.2.40: downgrade libredox to 0.1.11 for redox_syscall 0.6 compatibility 2026-01-02 20:00:13 -07:00
Jeremy Soller 397749f1f5 0.2.39: update redox_syscall and redoxfs 2026-01-02 19:42:55 -07:00
Jeremy Soller 753c8af0e5 Merge branch 'expose-fuse-opt' into 'master'
Make FUSE feature can be opted out

See merge request redox-os/installer!66
2026-01-01 08:15:36 -07:00
Wildan M 846e8446e0 Make FUSE feature can be opted out 2026-01-01 14:46:54 +07:00
Jeremy Soller 385696d84a Merge branch 'update-redoxfs' into 'master'
Bump redoxfs

See merge request redox-os/installer!64
2025-12-16 11:08:07 -07:00
Wildan M ca9748f1ba Bump redoxfs 2025-12-16 22:51:05 +07:00
Jeremy Soller 7e697cfb3a Merge branch 'bump-ver' into 'master'
Expose custom mount API and bump version

See merge request redox-os/installer!63
2025-12-15 15:54:52 -07:00
Wildan M 86d2af6ca0 Expose custom mount API and bump version 2025-12-16 05:27:09 +07:00
Jeremy Soller 8ba8d6e15b Merge branch 'no-mount' into 'master'
Add no_mount option and tidy up flags

See merge request redox-os/installer!62
2025-12-15 12:38:41 -07:00
Wildan M 3dba5827f2 Try fix CI 2025-12-16 01:55:54 +07:00
Wildan M 433aa99660 Add no_mount option and tidy up flags 2025-12-16 01:41:55 +07:00
Jeremy Soller 990d9d343e Merge branch 'bump-pkg' into 'master'
Update pkg version

See merge request redox-os/installer!61
2025-12-09 08:48:25 -07:00
Wildan M 8bc7c9d88d Update pkg version 2025-12-09 10:22:36 -05:00
Jeremy Soller 4fcd97e8da Merge branch 'split-cargo-features' into 'master'
Split installer code to cargo feature

See merge request redox-os/installer!60
2025-11-22 11:31:56 -07:00
Wildan M 7a85862733 Split installer code to cargo feature 2025-11-22 09:15:25 -08:00
Wildan M 7308cfe259 Move installer code 2025-11-22 09:15:24 -08:00
Jeremy Soller 58f161e019 Fix compilation on Redox 2025-11-20 08:58:51 -07:00
Jeremy Soller 693d9470f9 Merge branch 'always-local' into 'master'
Always Install binaries locally if cookbook set

See merge request redox-os/installer!59
2025-11-20 06:48:09 -07:00
Jeremy Soller 2a910e7058 Merge branch 'tidy-up' into 'master'
Remove list_packages

See merge request redox-os/installer!58
2025-11-20 06:46:28 -07:00
Wildan M 5fad110e5e Always Install binaries locally if cookbook set 2025-11-20 01:50:47 -08:00
Wildan M 09531dfdc8 Fix fmt 2025-11-20 01:24:41 -08:00
Wildan M e5233171ae Remove list_packages 2025-11-20 01:22:02 -08:00
Jeremy Soller 8281316190 Merge branch 'pkgar-local-path' into 'master'
Fix local package head install path

See merge request redox-os/installer!57
2025-11-12 06:58:47 -07:00
Wildan M 9594e87937 Fix local package head install path 2025-11-12 20:55:33 +07:00
Jeremy Soller bb9fa4ec7c Merge branch 'i586_target' into 'master'
Allow installing for i586-unknown-redox

See merge request redox-os/installer!56
2025-11-01 09:34:59 -06:00
bjorn3 f30179a889 Allow installing for i586-unknown-redox 2025-11-01 16:33:21 +01:00
Jeremy Soller 97dd77918e Merge branch 'update-pkg' into 'master'
Bump pkgar and rand

See merge request redox-os/installer!54
2025-10-01 13:31:25 -06:00
Wildan M 8e6a1c3aa4 Bump pkgar and rand 2025-10-01 21:41:02 +07:00
Jeremy Soller c35c9a88a9 Merge branch 'fix-deps' into 'master'
Fix local dependency install

See merge request redox-os/installer!53
2025-09-30 19:57:16 -06:00
Wildan M 43c1cd987f Fix local dependency install 2025-09-30 19:19:01 +07:00
Jeremy Soller 727c703bad Merge branch 'deterministic-source' into 'master'
Make install order predictable

See merge request redox-os/installer!52
2025-09-29 07:50:48 -06:00
Wildan M 0fa085c0f2 Make install order predictable 2025-09-29 20:29:43 +07:00
Jeremy Soller 69de3d62a1 Fix build on non-Redox 2025-09-26 15:52:31 -06:00
Jeremy Soller c540ed8f9d 0.2.37 - add support for fast install 2025-09-26 15:49:04 -06:00
Jeremy Soller 7023dc0eb4 0.2.36 2025-09-26 15:47:29 -06:00
Jeremy Soller 039ded92a4 Merge branch 'overridable-config' into 'master'
Allow overwriting package files

See merge request redox-os/installer!51
2025-09-26 14:25:40 -06:00
Wildan M e74c9279cf Apply postconfig configuration 2025-09-26 17:27:27 +07:00
Wildan M ee5a94dbe4 Tidy up file config init 2025-09-26 17:26:09 +07:00
Wildan Mubarok b425b12cf5 Allow overwriting package files 2025-09-26 08:32:28 +00:00
Jeremy Soller 432abd9249 0.2.35: relax serde requirement 2025-09-07 21:24:59 -06:00
Jeremy Soller 01a3adb662 Merge branch 'opt-pkg' into 'master'
Handle metapackages for local install

See merge request redox-os/installer!50
2025-07-27 10:31:27 -06:00
Wildan M 81e8bc09f8 Handle metapackages for local install 2025-07-27 22:49:37 +07:00
Jeremy Soller 5758ba854a Merge branch 'add-ci' into 'master'
Add CI Tests

See merge request redox-os/installer!49
2025-07-09 12:42:59 -06:00
Wildan Mubarok 64b047195d Add CI Tests 2025-07-09 12:42:59 -06:00
Jeremy Soller 2730f1a70c 0.2.34: update to redoxfs 0.7 2025-07-02 13:44:45 -06:00
Jeremy Soller d82dd8998d Merge branch 'clippy_lints' into 'master'
apply some simple clippy lints

See merge request redox-os/installer!48
2025-07-01 08:31:00 -06:00
auronandace 721d49adfb apply some simple clippy lints 2025-06-30 17:11:48 +01:00
Jeremy Soller a86aec193d Merge branch 'ignore-package' into 'master'
Add option to ignore packages

See merge request redox-os/installer!47
2025-06-27 07:09:14 -06:00
Wildan Mubarok 55a0bd1643 Add option to ignore packages 2025-06-27 08:25:59 +00:00
Jeremy Soller 2c3afb4956 0.2.33: fix compilation with new pkgar 2025-06-12 12:07:52 -06:00
Jeremy Soller 2c8c6bf30f update dependencies 2025-06-12 11:56:24 -06:00
Jeremy Soller 0db88e3cb4 Merge branch 'write_bootloader' into 'master'
Add flag to write bootloader to chosen location

See merge request redox-os/installer!46
2025-06-12 10:52:00 -06:00
bjorn3 7ce72b7299 Add flag to write bootloader to chosen location
The build system needs the bootloader separately for netboot support.
2025-06-01 20:53:01 +02:00
Jeremy Soller 9e888c06fa 0.2.32: update redoxfs 2025-04-20 18:55:44 -06:00
Jeremy Soller a4032c27b7 0.2.31: update pkgar 2025-03-27 15:47:03 -06:00
Jeremy Soller fcc2dfb1c8 Merge branch 'skip_partitions' into 'master'
Add support for only creating redoxfs partition installer

See merge request redox-os/installer!45
2025-03-27 13:49:21 +00:00
4lDO2 d85eceac71 Add support for only creating redoxfs partition installer. 2025-03-27 14:27:40 +01:00
Jeremy Soller a78377da19 0.2.30: update redoxfs 2025-03-25 15:46:05 -06:00
Jeremy Soller 2bf4865794 installer_tui: fix disk detection 2025-03-25 15:40:01 -06:00
Jeremy Soller d53435420b Merge branch 'repo-error-messages' into 'master'
Update to get better error messages

See merge request redox-os/installer!44
2024-12-06 21:57:56 +00:00
Ron Williams 3a084e1c27 Update to get better error messages 2024-12-06 11:43:32 -08:00
Jeremy Soller e17550b6bd Merge branch 'master' into 'master'
fix: install runtime dependencies of locally built packages

See merge request redox-os/installer!43
2024-12-06 17:36:23 +00:00
Anhad Singh 183e18be31 fix: install runtime dependencies of locally built packages
This is the same behavior as remote prebuilt packages and what the
package manager already does. It seems to be only unimplemented for
locally built packages.

Signed-off-by: Anhad Singh <andypython@protonmail.com>
2024-12-07 01:38:16 +11:00
Jeremy Soller 729c5631bc Merge branch 'source-recipe' into 'master'
Accept "source" as an alternative to "recipe"

See merge request redox-os/installer!42
2024-11-21 15:07:55 +00:00
Ron Williams 47282ba9dd Accept "source" as an alternative to "recipe" 2024-11-21 02:17:25 -08:00
Jeremy Soller 7c359f1622 Update dependencies 2024-11-06 06:13:23 -07:00
Jeremy Soller 37cc477d5c 0.2.29: update redoxfs 2024-10-29 08:55:39 -06:00
Jeremy Soller dc7d923e5a 0.2.28 2024-10-20 14:09:50 -06:00
Jeremy Soller 71ac2646bb Merge branch 'master' of https://gitlab.redox-os.org/redox-os/installer 2024-10-20 14:09:21 -06:00
Jeremy Soller 17db563677 0.2.27 - update pkgar 2024-10-20 14:08:47 -06:00
Jeremy Soller cf83106610 Merge branch 'fix-hostname-space' into 'master'
fix: Invalid space in `/etc/hostname`

See merge request redox-os/installer!41
2024-10-20 12:33:04 +00:00
Josh Megnauth 0ee8e95f62 fix: Invalid space in /etc/hostname
Spaces and newlines are invalid in hostnames. However, the base config inadvertently creates an `/etc/hostname` with a newline after "redox".

This can cause programs to fail.
2024-10-20 02:23:04 -04:00
Jeremy Soller b3e90be70b Link to ring issue requiring patch 2024-10-18 14:11:24 -06:00
Jeremy Soller 20fa660797 Patch ring to fix i686 build 2024-10-18 14:09:52 -06:00
Jeremy Soller eebccba932 0.2.26: Update to use pkgutils rewrite 2024-10-18 12:20:09 -06:00
Jeremy Soller 4b9e9e17ab 0.2.25 2024-10-16 13:26:17 -06:00
Jeremy Soller 17263316d9 Merge branch 'deps' into 'master'
Bump dependencies

See merge request redox-os/installer!40
2024-10-16 19:25:00 +00:00
Andrey Turkin 5c8cdd8950 Bump dependencies 2024-10-16 22:13:50 +03:00
Jeremy Soller 5c52a2e80e Merge branch 'riscv' into 'master'
Risc-v support

See merge request redox-os/installer!39
2024-10-16 18:02:45 +00:00
Andrey Turkin 7ccaccee4e Risc-v support 2024-10-16 20:26:51 +03:00
Jeremy Soller afc225c8d6 Merge branch 'ext' into 'master'
Modernize code and add support for local binary packages

See merge request redox-os/installer!38
2024-10-06 15:32:39 +00:00
4lDO2 098fd8bbc9 Support local binary packages. 2024-10-06 17:12:08 +02:00
4lDO2 66d66a537c Switch from failure to anyhow. 2024-10-06 13:08:17 +02:00
4lDO2 bfe70c409f 2021 edition. 2024-10-06 13:02:44 +02:00
4lDO2 cb707c3ce4 Update edition and rustfmt. 2024-10-06 13:02:16 +02:00
Jeremy Soller 9c3c5228df 0.2.24 2024-09-19 20:45:09 -06:00
Jeremy Soller caebc0d6f7 Add XDG user dirs configuration 2024-09-18 09:42:05 -06:00
Jeremy Soller 087810a252 Merge branch 'no_legacy_scheme' into 'master'
Remove all usages of the legacy scheme syntax

See merge request redox-os/installer!37
2024-08-18 22:16:53 +00:00
bjorn3 34a832b43e Remove all usages of the legacy scheme syntax 2024-08-18 21:44:10 +02:00
Jeremy Soller d352203794 Merge branch 'package-lister' into 'master'
Add list_packages binary

See merge request redox-os/installer!36
2024-08-17 23:58:19 +00:00
Ron Williams ad54d088d5 Add list_packages binary 2024-08-16 23:26:25 -07:00
Jeremy Soller a5534b8699 Install config before package files to ensure usr symlinks are properly handled 2024-06-24 21:04:31 -06:00
Jeremy Soller e409c11e67 0.2.23: fix install from inside Redox 2024-06-24 20:05:38 -06:00
Jeremy Soller 6895d9a9aa 0.2.22: pin cc to prevent ring build failure 2024-06-24 11:28:03 -06:00
Jeremy Soller 31d4816fcb 0.2.21 2024-06-24 11:21:14 -06:00
Jeremy Soller 2058101f61 Update dependencies 2024-05-27 09:37:39 -06:00
Jeremy Soller 69d4db511a 0.2.20 2024-04-22 15:28:20 -06:00
Jeremy Soller 52281ea9b5 0.2.19 2024-04-19 13:33:16 -06:00
Jeremy Soller 7b9f9f663d Update redoxfs 2024-04-01 13:31:54 -06:00
Jeremy Soller 7bcc07f357 Merge branch 'redox-users' into 'master'
Update Cargo.lock for redox_users

See merge request redox-os/installer!35
2024-04-01 13:25:03 +00:00
Ron Williams 5b7c64d587 Update Cargo.lock for redox_users 2024-03-31 22:11:37 -07:00
Jeremy Soller 248990b5ef Merge branch 'group' into 'master'
Add missing password field in /etc/group

See merge request redox-os/installer!34
2024-03-25 19:56:03 +00:00
Piotr Wójcik 2acf506d16 Add missing password field in /etc/group 2024-03-25 19:34:22 +01:00
Jeremy Soller f725079176 0.2.18: Update redoxfs 2024-03-23 08:57:20 -06:00
Jeremy Soller 1c30f4a3c1 0.2.17: update redoxfs 2024-02-15 16:04:43 -07:00
Jeremy Soller 17219dab9f 0.2.16 2024-02-07 20:00:51 -07:00
Jeremy Soller 6dfcee04ad Add output-config parameter to installer for getting merged config 2024-02-07 19:44:05 -07:00
Jeremy Soller dbfdd417f1 Remove file: prefix from /etc/passwd 2024-01-18 13:43:20 -07:00
Jeremy Soller 51f6efbaad Merge branch 'fix_config_serialization' into 'master'
Update the toml crate

See merge request redox-os/installer!33
2024-01-10 20:50:04 +00:00
bjorn3 3d417f7742 Update the toml crate
This fixes serialization of the config when using specifying that
packages should be downloaded as binary.
2024-01-10 21:48:05 +01:00
Jeremy Soller a258044dcc Merge branch 'limit_progressbar_rate' into 'master'
Update pkgutils

See merge request redox-os/installer!32
2024-01-10 15:55:29 +00:00
bjorn3 9a1463dd11 Update pkgutils 2024-01-10 16:08:06 +01:00
Jeremy Soller 7a7790e9bd Merge branch 'installer_groups' into 'master'
Move /etc/group generation to the installer

See merge request redox-os/installer!30
2024-01-06 16:28:32 +00:00
bjorn3 09da116ccd Move /etc/group generation to the installer
This ensures that the gid's for the auto-generated user groups stay in
sync with the gid in /etc/passwd. It also makes it easier to evolve the
format of /etc/group in the future.
2024-01-06 17:24:48 +01:00
Jeremy Soller 6a4802d556 Add flag to print filesystem size 2024-01-04 12:18:41 -07:00
Jeremy Soller 352407371c Make general config optional 2024-01-04 11:58:21 -07:00
Jeremy Soller 6f40e32d49 Make general.prompt optional 2024-01-04 11:54:48 -07:00
Jeremy Soller b84146edd5 Merge branch 'config_include' into 'master'
Support including other config files from a config file

See merge request redox-os/installer!29
2024-01-04 15:23:21 +00:00
bjorn3 adfee95484 Support including other config files from a config file
This will allow significantly reducing the amount of duplication
between all the different configs used by Redox OS.
2024-01-04 15:29:15 +01:00
bjorn3 1f4bbece4a Introduce Config::from_file for parsing the config toml 2024-01-04 14:54:13 +01:00
bjorn3 5cdec46800 Reserialize filesystem.toml instead of copying the original config
This will be necessary to handle including other configs from the config
to use as these configs will not be copied.
2024-01-04 14:53:05 +01:00
bjorn3 c9e091be67 Remove mention of /games
This is no longer used since redox-os/redox!1397
2024-01-03 21:58:52 +01:00
Jeremy Soller aa1a982479 Merge branch 'symlinks' into 'master'
Create symlinks before installing packages

See merge request redox-os/installer!27
2024-01-03 19:17:52 +00:00
bjorn3 b4f2f01d73 Create symlinks before installing packages
This allows redirecting the location packages are installed using
symlinks. This makes it much easier to change the filesystem hierarchy
by not having to change every recipe in the cookbook.
2023-12-29 15:16:56 +01:00
Jeremy Soller 38f6aa70bd 0.2.15 2023-12-13 14:11:29 -07:00
Jeremy Soller 39f4b1c8b6 Make bootloader install directory per-process 2023-12-13 14:11:09 -07:00
Jeremy Soller ba2fc38a3f Remove temporary mount directory 2023-12-13 14:10:56 -07:00
Jeremy Soller f241649bba 0.2.14 2023-12-12 13:10:15 -07:00
Jeremy Soller 4821c55242 Update redoxfs 2023-12-12 13:10:01 -07:00
Jeremy Soller cf9e2c9719 Use per-process mount path 2023-12-12 11:11:17 -07:00
Jeremy Soller 59e0f9e7ae Update redoxfs 2023-12-12 11:10:53 -07:00
Jeremy Soller e0c909536c Merge branch 'ivan/efi_size' into 'master'
add option for variable size of EFI system partition

See merge request redox-os/installer!26
2023-12-07 15:34:54 +00:00
Ivan Tan cbdea61a61 add option for variable size of EFI system partition 2023-11-18 02:50:06 +00:00
Jeremy Soller c5335d4aae Merge branch 'rw_van_231024' into 'master'
Allow package spec "binary" or "recipe" to override REPO_BINARY=0 or REPO_BINARY=1

See merge request redox-os/installer!25
2023-11-04 18:33:52 +00:00
Ron Williams 2b34b31956 Allow package spec "binary" or "recipe" to override REPO_BINARY=0 or REPO_BINARY=1 2023-11-04 18:33:52 +00:00
Jeremy Soller d61b30de3f 0.2.13: update redoxfs and redox_syscall 2023-09-07 16:22:13 -06:00
Jeremy Soller 970cbfaef6 Update redoxfs and uuid 2023-09-07 16:12:50 -06:00
Jeremy Soller 7a85c43974 Merge branch 'master' into 'master'
config: misc changes for GPU drivers

See merge request redox-os/installer!23
2023-07-07 14:09:05 +00:00
Anhad Singh 7b35a76e49 config: misc changes for GPU drivers
Signed-off-by: Anhad Singh <andypythonappdeveloper@gmail.com>
2023-07-07 17:55:10 +10:00
Jeremy Soller 363166ad1e Merge branch 'update-pkgar' into 'master'
Update pkgar to 0.1.10

See merge request redox-os/installer!22
2023-04-05 21:32:32 +00:00
Will Angenent 3f306765ad Update pkgar to 0.1.10 2023-04-05 22:30:51 +01:00
Jeremy Soller 20c0bdf60f Merge branch 'update-to-use-redoxfs-unmount' into 'master'
Update installer to use redoxfs unmount function

See merge request redox-os/installer!21
2023-04-03 17:16:27 +00:00
Will Angenent 46e14ea06f Update installer to use redoxfs unmount function
This fixes completing the image build on gentoo and MacOS.
2023-04-01 20:57:43 +01:00
Jeremy Soller 7e0f635d0e Merge branch 'update-cargo-lock' into 'master'
Update Cargo.toml

See merge request redox-os/installer!20
2023-03-21 20:16:11 +00:00
Will Angenent 46e39bc109 Update Cargo.toml 2023-03-21 10:45:21 +00:00
Jeremy Soller b04fcab423 Merge branch 'rw_van_230309' into 'master'
fix mixed build in a new clone

See merge request redox-os/installer!19
2023-03-10 14:13:48 +00:00
Ron Williams b23b2f6e5d fix mixed build in a new clone 2023-03-09 21:17:13 -08:00
Jeremy Soller c4a7020e93 Merge branch 'rw_van_230307' into 'master'
Add ability to read package = recipe from filesystem config

See merge request redox-os/installer!18
2023-03-09 02:24:13 +00:00
Ron Williams 1411257784 add ability to read package = recipe from filesystem config 2023-03-08 14:18:45 -08:00
Jeremy Soller cfd556f3b3 Merge branch 'fix_warnings' into 'master'
Remove unused imports and function

See merge request redox-os/installer!17
2023-02-24 22:01:22 +00:00
andrewdavidmackenzie 257020852d Remove unused imports and function 2023-02-24 22:48:17 +01:00
Jeremy Soller c91bea9452 Update libc crate 2023-02-11 14:42:31 -07:00
Jeremy Soller 41a656af92 Ignore live disks and partitions in TUI 2023-01-12 08:50:04 -07:00
Jeremy Soller fadb0a1c1d 0.2.12 2023-01-11 14:11:55 -07:00
Jeremy Soller f710fa79db Recursively set permissions on home directories 2022-11-21 14:55:08 -07:00
Jeremy Soller b724e714d3 Move EFI partition to second megabyte 2022-09-21 14:42:45 -06:00
Jeremy Soller c6d7cf16e1 Fix EFI partition alignment 2022-09-21 13:54:14 -06:00
Jeremy Soller e24f5ef8e3 Only use cookbook argument if not doing list-packages 2022-09-12 10:14:51 -06:00
Jeremy Soller fa836008c0 Rely on packages being built prior to installer running 2022-09-12 07:32:24 -06:00
Jeremy Soller 5e8e596d40 Fix seek not changing in last commit 2022-09-07 10:48:39 -06:00
Jeremy Soller 7b24f868b8 Optimization for block aligned I/O in disk wrapper 2022-09-07 10:46:24 -06:00
Jeremy Soller 0b4f836f2a Use same logic for read and write in disk wrapper 2022-09-07 10:40:41 -06:00
Jeremy Soller 8d2d4cd62d Add disk wrapper to ensure block aligned I/O 2022-09-07 10:29:15 -06:00
Jeremy Soller 9666c62036 Fix copying EFI partition message 2022-09-07 09:55:02 -06:00
Jeremy Soller f103b5c4b9 Fixup for last commit 2022-09-07 09:48:12 -06:00
Jeremy Soller 4b1df136a6 Write BIOS and EFI partitions in memory 2022-09-07 09:45:27 -06:00
Jeremy Soller ddf59d8c7d Print debug messages while doing partitioning 2022-09-07 09:02:06 -06:00
Jeremy Soller c3e679d5f1 Fix 32-bit x86 target name 2022-09-05 10:51:13 -06:00
Jeremy Soller ad11b2adb2 Ensure config and cookbook key are installed 2022-09-05 09:55:15 -06:00
Jeremy Soller e8e4687c05 Pass live argument from CLI to library 2022-09-05 09:10:46 -06:00
Jeremy Soller 1ecb9b49fc Support fetching live bootloader 2022-09-05 09:07:40 -06:00
Jeremy Soller f1a806b288 0.2.11 - allow installer to create full disk installs supporting both BIOS and EFI 2022-09-05 08:58:53 -06:00
Jeremy Soller 4e7f7d8413 Update to use new boot directory format 2022-08-26 17:56:22 -06:00
Jeremy Soller be5eefd44f Update Cargo.lock 2022-07-26 17:26:26 -06:00
Jeremy Soller 2b1107c856 Also copy initfs 2022-07-22 10:30:44 -06:00
Jeremy Soller 2418286a29 Improve installer size formatting 2022-04-26 08:22:58 -06:00
4lDO2 0ed65d7d3d Use crates.io version of syscall 2022-03-27 12:25:09 +02:00
4lDO2 f76771491c Update syscall 2022-03-27 11:25:01 +02:00
Jeremy Soller f8ec4d28bc Ensure pkg folder exists 2022-03-23 15:04:53 -06:00
Jeremy Soller e666636ab7 Create pkgar_head files when installing 2022-03-23 13:53:10 -06:00
Jeremy Soller 900f77e7f2 Use pkgar_head files for installer_tui 2022-03-23 13:52:33 -06:00
Jeremy Soller adebf727dc Update pkgar and lock file 2022-03-23 13:52:16 -06:00
Jeremy Soller 9ea9a6ecac 0.2.10: update pkgar 2022-03-17 16:35:15 -06:00
Jeremy Soller 3c03576518 0.2.9: add tests and make it possible to install to disk 2022-03-17 08:50:01 -06:00
Jeremy Soller 0c731043ac Use fs::copy to ensure metadata is copied 2022-03-16 14:13:41 -06:00
Jeremy Soller 2c54a9b868 installer_tui: support password encryption 2022-03-15 13:50:48 -06:00
Jeremy Soller b35e132153 Update Cargo.lock 2022-03-11 09:14:45 -07:00
Jeremy Soller 41eba65942 Merge branch 'redoxfs-0.5.0' into 'master'
Redoxfs 0.5.0

See merge request redox-os/installer!16
2022-03-11 16:14:20 +00:00
Jeremy Soller 1634877666 0.2.8 2022-03-11 09:14:07 -07:00
Jeremy Soller ff2fdeca84 Switch to crates.io version of RedoxFS 0.5 2022-03-11 09:13:33 -07:00
Jeremy Soller 621a4dee2c Update to RedoxFS 0.5.0 2022-02-28 12:24:00 -07:00
Jeremy Soller 84856f459d Update patches 2021-08-10 16:08:02 -06:00
Jeremy Soller 7f0a57108d Fix size formatting in installer TUI 2021-08-02 17:44:52 -06:00
4lDO2 70a2dc7532 Update Cargo.lock, again. 2021-06-18 09:23:19 +02:00
4lDO2 224bb047dc Update dependencies. 2021-06-17 18:10:32 +02:00
Jeremy Soller 150d65c31b Update syscall 2020-08-02 15:26:52 -06:00
Jeremy Soller af5f64b807 Remove pbr patch 2020-05-28 11:56:13 -06:00
Jeremy Soller 03320a2025 Remove libc and tar patch 2020-05-26 10:27:19 -06:00
Jeremy Soller 03f996c70f 0.2.7 - add patches for libc, pbr, ring, and tar-rs 2020-05-21 13:47:57 -06:00
Jeremy Soller c22b1cf71b Add cookbook pkgar support 2020-03-13 20:04:43 -06:00
Jeremy Soller 6a49914f0f 0.2.6 - update dependencies 2020-01-20 10:04:24 -07:00
Jeremy Soller 3a1a257f0f Update redoxfs 2020-01-20 09:58:10 -07:00
Jeremy Soller 1fccae679d Add default-run attribute 2019-11-29 10:15:45 -07:00
Jeremy Soller 7c5236fe35 0.2.5 - update dependencies and prepare for publishing 2019-11-29 10:09:27 -07:00
Jeremy Soller 3b0f59cbb0 Copy include, lib, and pkg 2019-09-22 10:58:55 -06:00
Jeremy Soller cd9e026705 Fix issue of trying to run read_dir on disk/live: 2019-09-22 10:06:25 -06:00
Jeremy Soller 9ff577792b Disable creation of liner context - it causes installer_tui to lock 2019-09-22 09:27:27 -06:00
Jeremy Soller fbfc4edf6f Add message when installer is successful 2019-09-19 21:22:05 -06:00
Jeremy Soller 505dec6069 Optimized file copy 2019-09-19 19:39:53 -06:00
Jeremy Soller 89dcc2c060 Add unmounting message 2019-09-15 13:31:59 -06:00
Jeremy Soller 7ef2567da6 Copy locally installed package files 2019-09-15 13:14:46 -06:00
Jeremy Soller 5762840f79 Installer TUI (WIP) 2019-09-08 20:47:05 -06:00
Jeremy Soller 39d68618ca Switch to using rust-argon2 crate, which is more actively maintained 2019-09-08 08:59:50 -06:00
Jeremy Soller b75b1c2f86 Revert libc to 0.2.54 to support older Redox 2019-06-10 17:37:05 -06:00
Jeremy Soller acb7bced58 0.2.4 - allow empty configuration sections and update dependencies 2019-06-10 17:31:44 -06:00
Jeremy Soller f30987b1e5 0.2.3: Update dependencies 2019-05-12 08:34:49 -06:00
Jeremy Soller 713359252a 0.2.2 - fix cargo doc 2019-05-12 08:15:18 -06:00
Jeremy Soller f794ab2a66 Add metadata 2019-05-12 08:02:59 -06:00
Jeremy Soller 40addc7020 Bump version and update dependencies 2019-05-12 08:01:30 -06:00
Jeremy Soller 8e88231ede Update for newer rust 2019-04-07 10:36:45 -06:00
Jeremy Soller a262db6f25 Merge branch 'get-target-from-env' into 'master'
Get TARGET from the environment

See merge request redox-os/installer!11
2019-03-02 21:07:12 +00:00
Robin Randhawa 78e9f77950 Get TARGET from the environment
This enables flexible cross-installation of packages as opposed to only
compiling for the x86_64-unknown-redox target.

This also permits native builds.
2019-03-01 21:02:10 +00:00
Jeremy Soller 03fc74e21f Update installer depends 2018-12-10 14:23:50 -07:00
Jeremy Soller ed25899b0e Update Cargo.lock 2018-10-14 19:58:26 -06:00
Jeremy Soller 49b7a5f427 Fix types when compiling for Redox 2018-08-19 12:33:51 -06:00
Jeremy Soller 7f38ffac1f Use forked libc crate, update lock file 2018-08-19 11:39:22 -06:00
Jeremy Soller 33fa0af521 Fix uid and gid unwrap 2018-08-17 19:59:22 -06:00
Jeremy Soller 91c303df7d Do not set uid or gid if not asked to 2018-08-17 19:34:29 -06:00
Jeremy Soller 123350ae60 Merge remote-tracking branch 'origin/v0.3' 2018-08-17 17:16:16 -06:00
Jeremy Soller be2e09472a Update lock file 2018-08-17 17:13:05 -06:00
MggMuggins 03cae0ca37 Implement shadowfile 2018-08-17 17:26:11 -05:00
Jeremy Soller 8e39b2ea32 Merge branch 'mggmuggins/shadowfile' into 'master'
Use shadowfile for user creation

See merge request redox-os/installer!7
2018-08-17 12:24:04 +00:00
MggMuggins 66b24d1116 Support Directories 2018-07-28 10:41:39 -05:00
MggMuggins e58815f89c Rudimentary support for permissions
I sorta OOP'ed this, combining the logic of what needs to happen for
each config object with those objects. More things are coming, this is a
very WIP commit.
2018-07-27 10:55:11 -05:00
MggMuggins ce6e96bd20 Major Refactor; Create passwd hashes
Maybe some of the refactoring things I did don't make much sense, but I
did them. The goal was to get rid of those macros (because macro
definitions are really hard to read). I replaced two of them with very
similar functions, and rearranged the `install` function.

The installer now generates password hashes from the password entered by
the user or listed in the toml file. This value is NO LONGER the
password hash. The example configs have been changed to reflect this.
2018-07-26 14:58:14 -05:00
MggMuggins 80add18dc9 Functional Partial rewrite 2018-07-26 12:43:57 -05:00
MggMuggins 1359501d3f Use shadowfile for user creation 2018-07-25 15:20:15 -05:00
Jeremy Soller a1afbb144f Update links to gitlab 2018-06-12 12:30:44 -06:00
Jeremy Soller 999db45b0e Update redoxfs 2017-11-19 16:24:25 -07:00
Jeremy Soller f6c5fd94a7 Merge pull request #6 from chebykinn/redox_users
Migrate to redox_users
2017-11-11 12:54:12 -07:00
Ivan Chebykin 681fd3d0ef Migrated to redox_users 2017-11-11 22:12:55 +03:00
Jeremy Soller 62412ef548 Create LICENSE 2017-10-20 20:08:54 -06:00
Jeremy Soller 43e506c447 Update Cargo.lock 2017-10-11 20:54:45 -06:00
Jeremy Soller ac759a17bd Update Cargo.lock 2017-10-04 20:29:41 -06:00
Jeremy Soller a01dc9f37a Switch to arg_parser 2017-09-27 20:53:13 -06:00
Jeremy Soller a82a577180 Change format of installer arguments 2017-09-26 20:52:54 -06:00
Jeremy Soller a7b16dcb97 Update configs, update dependencies 2017-09-26 20:00:50 -06:00
Jeremy Soller 093ae7de55 Fix method of adding scheme 2017-08-23 20:29:27 -06:00
Jeremy Soller 25d6a0fec4 Add scheme to paths 2017-08-23 20:16:45 -06:00
Jeremy Soller 7ad7827497 Merge pull request #5 from ids1024/symlink
Support adding symlinks in configuration
2017-08-21 15:07:56 -06:00
Ian Douglas Scott a4adb124d0 Support adding symlinks in configuration
I'm not sure if this is the best syntax...
2017-08-21 13:24:36 -07:00
Jeremy Soller 5cfe357821 Update Cargo.lock and Cargo.toml 2017-08-19 14:51:08 -06:00
Jeremy Soller a76c8df867 Update Cargo.lock 2017-08-02 21:14:10 -06:00
Jeremy Soller a9a1f0e76d Update Cargo.lock 2017-08-02 19:55:53 -06:00
Jeremy Soller ed468af4c3 Update Cargo.lock 2017-07-29 08:27:31 -06:00
Jeremy Soller 3efca19473 Update userutils 2017-07-26 20:35:27 -06:00
Jeremy Soller c0bc46b954 Update Cargo.lock 2017-07-26 08:25:05 -06:00
Jeremy Soller 2c0e14cadf Add Cargo.lock file 2017-07-26 08:02:37 -06:00
Jeremy Soller d73dd18f07 Add Cargo.lock 2017-07-26 07:54:39 -06:00
Jeremy Soller 15d37cea36 Merge pull request #4 from ids1024/list
--list-packages argument
2017-07-10 21:30:42 -06:00
Ian Douglas Scott e47108a70e --list-packages argument 2017-07-10 20:04:06 -07:00
Jeremy Soller d6f2936a53 Remove redoxfs submodule 2017-06-29 18:13:10 -06:00
Jeremy Soller f99fac30ea Update redoxfs 2017-06-23 17:59:16 -06:00
Jeremy Soller af1dd3b9fd Use .tar.gz for packages 2017-06-18 15:14:51 -06:00
Jeremy Soller 31a60eb8a3 Update for new location of script 2017-05-11 20:52:42 -06:00
Jeremy Soller 318e7bb89f Merge commit '8d6a4d7' 2017-05-10 21:38:17 -06:00
Jeremy Soller 8d6a4d7071 Add RedoxFS as submodule. Do not create passwd if no users are provided 2017-05-10 21:38:05 -06:00
Jeremy Soller 7e9f15e54f Print message when installing from cookbook 2017-05-08 21:20:15 -06:00
Jeremy Soller c2bae24835 Merge pull request #3 from ids1024/package
Update for pkgutils refactoring
2017-04-26 06:24:09 -06:00
Ian Douglas Scott 160a793017 Update for pkgutils refactoring 2017-04-25 21:57:04 -07:00
Jeremy Soller 7c7b30c16e Merge pull request #2 from ids1024/pkgutils_cookbook
Use install methods from pkgutils, and allow installing directly from cookbook with --cookbook=path
2017-04-09 12:59:47 -06:00
Ian Douglas Scott bb94edcb15 Support building directly from cookbook with --cookbook= 2017-04-09 10:11:49 -07:00
Ian Douglas Scott e2aa3c3032 Move package installing logic to function 2017-04-09 09:19:24 -07:00
Ian Douglas Scott bb56cee678 Let pkgutils handle downloading and extracting package 2017-04-09 09:06:07 -07:00
Jeremy Soller 4e9d22ada4 Fix installer paths 2017-04-07 19:57:00 -06:00
Jeremy Soller cfdb2a4968 Log creation of parent dirs 2017-01-09 22:29:03 -07:00
Jeremy Soller be092f3754 Create parent directories 2017-01-09 22:16:47 -07:00
Jeremy Soller eb2c0aafd4 Fix package loop 2017-01-09 22:11:11 -07:00
Jeremy Soller 5303301d3d Download packages from repo 2017-01-09 19:25:58 -07:00
Jeremy Soller 9125d089fb Add package extraction 2017-01-09 19:03:54 -07:00
Jeremy Soller aeb9f89ec1 Fix prompt macro, add ability to insert files in config 2017-01-09 16:05:27 -07:00
Jeremy Soller 7d5baf798e Macros 2017-01-09 15:41:40 -07:00
Jeremy Soller a56ec333ae Add sysroot config 2017-01-09 15:23:30 -07:00
Jeremy Soller dd0a541bfe Fix comment 2017-01-09 14:50:22 -07:00
Jeremy Soller a5773c3504 Add prompt example 2017-01-09 14:49:09 -07:00
Jeremy Soller e4db84e1d7 Add prompt example 2017-01-09 14:48:29 -07:00
Jeremy Soller 542ca6cb3d Fix example 2017-01-09 14:46:56 -07:00
Jeremy Soller 961983e788 Cleanup prompt macros 2017-01-09 14:46:21 -07:00
Jeremy Soller f666d06485 User configuration 2017-01-09 14:35:33 -07:00
Jeremy Soller 1abfd39b8d Initial commit 2017-01-09 10:12:49 -07:00
84 changed files with 5141 additions and 7097 deletions
+4 -2
View File
@@ -1,2 +1,4 @@
/build pkg
/target sysroot
/target/
/test.bin
+17 -28
View File
@@ -1,35 +1,24 @@
image: "redoxos/redoxer:latest" image: "rust:latest"
before_script:
- apt-get install nasm
- rustup component add rust-src
stages: stages:
- host - lint
- test
build:i686: workflow:
stage: host rules:
script: - if: '$CI_COMMIT_BRANCH == "master"'
- mkdir -p target/i686 - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
- cd target/i686
- TARGET=x86-unknown-none make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.bin `pwd`/bootloader-live.bin
build:x86_64:
stage: host
script:
- mkdir -p target/x86_64
- cd target/x86_64
- TARGET=x86_64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
build:aarch64:
stage: host
script:
- mkdir -p target/aarch64
- cd target/aarch64
- TARGET=aarch64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
fmt: fmt:
stage: host stage: lint
script: script:
- rustup component add rustfmt-preview - rustup component add rustfmt
- cargo fmt -- --check - cargo fmt -- --check
cargo-test:
stage: test
script:
- apt update && apt install -y fuse3 libfuse3-dev
- cargo build --locked
- cargo test
- ./target/debug/redox_installer -c res/test.toml test.bin --no-mount
-2
View File
@@ -1,2 +0,0 @@
[editor]
auto-format = false
-5
View File
@@ -1,5 +0,0 @@
[[language]]
name = "rust"
# TODO: Add more targets (BIOS, x86_32)
# Uncomment this line and set cargo.target to your target to get accurate completions
# config = { cargo.target = "aarch64-unknown-uefi", check.targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] }
Generated
+2419 -83
View File
File diff suppressed because it is too large Load Diff
+59 -43
View File
@@ -1,57 +1,73 @@
[package] [package]
name = "redox_bootloader" name = "redox_installer"
version = "1.0.0+rb0.2.5" version = "0.2.42+rb0.2.5"
description = "A Redox filesystem builder"
license = "MIT"
authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"] authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"]
edition = "2024" repository = "https://gitlab.redox-os.org/redox-os/installer"
default-run = "redox_installer"
edition = "2021"
# UEFI uses bin target
[[bin]] [[bin]]
name = "bootloader" name = "redox_installer"
path = "src/main.rs" path = "src/bin/installer.rs"
required-features = ["installer"]
[[bin]]
name = "redox_installer_tui"
path = "src/bin/installer_tui.rs"
required-features = ["installer"]
# BIOS uses lib target
[lib] [lib]
name = "bootloader" name = "redox_installer"
path = "src/main.rs" path = "src/lib.rs"
crate-type = ["staticlib"]
[dependencies] [dependencies]
bitflags = "1.3.2" anyhow = "1"
linked_list_allocator = "0.10.5" arg_parser = "0.1.0"
log = "0.4.17" fatfs = { version = "0.3.0", optional = true }
redox_syscall = { path = "../syscall" } fscommon = { version = "0.1.1", optional = true }
spin = "0.9.5" gpt = { version = "3.0.0", optional = true }
libc = "0.2.70"
pkgar = { version = "0.2", optional = true }
pkgar-core = { version = "0.2", optional = true }
pkgar-keys = { version = "0.2", optional = true }
rand = { version = "0.9", optional = true }
redox-pkg = { version = "0.3", features = ["indicatif"], optional = true }
redox_syscall = { path = "../syscall", optional = true }
redoxfs = { path = "../redoxfs", optional = true, default-features = false, features = ["std", "log"] }
rust-argon2 = { version = "3", optional = true }
serde = "1"
serde_derive = "1.0"
termion = { version = "4", optional = true }
toml = "0.8"
uuid = { version = "1.4", features = ["v4"], optional = true }
[dependencies.redoxfs] [target.'cfg(target_os = "redox")'.dependencies]
path = "../redoxfs" libredox = { path = "../libredox" }
default-features = false ring = "=0.17.8"
features = ["log"]
[target.'cfg(target_os = "uefi")'.dependencies]
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
#TODO: riscv cannot use target_os = "uefi" at this time
[target.'cfg(target_arch = "riscv64")'.dependencies]
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
[target."aarch64-unknown-uefi".dependencies]
dmidecode = "0.8.0"
[target."x86_64-unknown-uefi".dependencies]
x86 = "0.52.0"
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
byteorder = { version = "1", default-features = false }
fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa36b6a7c5351004be54" }
[features] [features]
default = [] default = ["installer", "fuse"]
live = [] installer = [
serial_debug = [] "fatfs",
"fscommon",
"gpt",
"pkgar",
"pkgar-core",
"pkgar-keys",
"rand",
"redox-pkg",
"redox_syscall",
"redoxfs",
"rust-argon2",
"termion",
"uuid",
]
fuse = ["redoxfs/fuse"]
[patch.crates-io] [patch.crates-io]
# https://github.com/briansmith/ring/issues/1999
ring = { git = "https://gitlab.redox-os.org/redox-os/ring.git", branch = "redox-0.17.8" }
redox_syscall = { path = "../syscall" } redox_syscall = { path = "../syscall" }
redoxfs = { path = "../redoxfs" } libredox = { path = "../libredox" }
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017-2022 Redox OS Copyright (c) 2017 Redox OS
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
-26
View File
@@ -1,26 +0,0 @@
TARGET?=x86_64-unknown-uefi
SOURCE:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
BUILD:=$(CURDIR)
export RUST_TARGET_PATH?=$(SOURCE)/targets
CARGO_ARGS=--release --locked --target $(TARGET) \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem
include $(SOURCE)/mk/$(TARGET).mk
clean:
rm -rf build target
$(BUILD)/filesystem:
mkdir -p $(BUILD)
rm -f $@.partial
mkdir $@.partial
fallocate -l 1MiB $@.partial/kernel
mv $@.partial $@
$(BUILD)/filesystem.bin: $(BUILD)/filesystem
mkdir -p $(BUILD)
rm -f $@.partial
fallocate -l 254MiB $@.partial
redoxfs-ar $@.partial $<
mv $@.partial $@
+38 -50
View File
@@ -1,62 +1,50 @@
# Bootloader # Redox OS installer
Redox OS Bootloader The Redox installer will allow you to produce a Redox OS image. You will
be able to specify:
- Output device (raw image, ISO, QEMU, VirtualBox, drive)
- Filesystem
- Included packages
- Method of installation (from source, from binary)
- User accounts
## Requirements You will be prompted to install dependencies, based on your OS and method of
installation. The easiest method is to install from binaries.
These software needs to be available on the PATH at build time: ## Usage
+ [mtools](https://www.gnu.org/software/mtools/) It is recommended to compile with `cargo`, in release mode:
+ [nasm](https://nasm.us/) ```bash
+ [redoxfs-ar](https://gitlab.redox-os.org/redox-os/redoxfs) cargo build --release
## Building
```sh
make TARGET=<triplet> BUILD=build all
``` ```
The `<triplet>` is one of: By default, you will be prompted to supply configuration options. You can
use the scripted mode by supplying a configuration file:
```bash
cargo run --release -- config/example.toml
```
An example configuration can be found in [config/example.toml](./config/example.toml).
Unsuplied configuration will use the default. You can use the `general.prompt`
setting to prompt when configuration is not set. Multiple configurations can
be specified, they will be built in order.
| ARCH | Boot Mode | Triplets | ## Embedding
|---|---|---|
| `i686` | BIOS | `x86-unknown-none` |
| `x86_64` | BIOS | `x86-unknown-none` |
| `x86_64` | UEFI | `x86_64-unknown-uefi` |
| `aarch64` | UEFI | `aarch64-unknown-uefi` |
| `riscv64gc` | UEFI | `riscv64gc-unknown-uefi` |
See [mk directory](./mk) for more information of how the build is working. The installer can also be used inside of other crates, as a library:
## Entry points ```toml
# Cargo.toml
Please read [Boot Process](https://doc.redox-os.org/book/boot-process.html) in the Redox OS Book for an introductory guide. [dependencies]
redox_installer = "0.1"
In this source code, some interesting files for entry points are:
+ BIOS boot stages: [asm/x86-unknown-none/bootloader.asm](./asm/x86-unknown-none/bootloader.asm)
+ BIOS boot entry: `fn start` at [src/os/bios/mod.rs](./src/os/bios/mod.rs)
+ UEFI boot entry: `fn main` at [src/os/uefi/mod.rs](src/os/uefi/mod.rs)
+ Common boot process: `fn main` at [src/main.rs](src/main.rs)
+ UEFI kernel entry: `fn kernel_entry` in each arch:
- `x86_64`: [src/os/uefi/arch/x86_64.rs](src/os/uefi/arch/x86_64.rs)
- `aarch64`: [src/os/uefi/arch/aarch64.rs](src/os/uefi/arch/aarch64.rs)
- `riscv64gc`: [src/os/uefi/arch/riscv64/mod.rs](src/os/uefi/arch/riscv64/mod.rs)
## Debugging
### QEMU
```sh
make TARGET=<triplet> BUILD=build qemu
``` ```
## How To Contribute ```rust
// src/main.rs
extern crate redox_installer;
To learn how to contribute to this system component you need to read the following document: fn main() {
let mut config = redox_installer::Config::default();
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) ...
redox_installer::install(config);
## Development }
```
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
View File
-17
View File
@@ -1,17 +0,0 @@
interrupt_vector_table:
b . @ Reset
b .
b . @ SWI instruction
b .
b .
b .
b .
b .
.comm stack, 0x10000 @ Reserve 64k stack in the BSS
_start:
.globl _start
ldr sp, =stack+0x10000 @ Set up the stack
bl kstart @ Jump to the main function
1:
b 1b @ Halt
-31
View File
@@ -1,31 +0,0 @@
sectalign off
; stage 1 is sector 0, loaded at 0x7C00
%include "stage1.asm"
; GPT area from sector 1 to 33, loaded at 0x7E00
times (33*512) db 0
; stage 2, loaded at 0xC000
stage2:
%include "stage2.asm"
align 512, db 0
stage2.end:
; the maximum size of stage2 is 4 KiB
times (4*1024)-($-stage2) db 0
; ISO compatibility, uses up space until 0x12400
%include "iso.asm"
times 3072 db 0 ; Pad to 0x13000
; stage3, loaded at 0x13000
stage3:
%defstr STAGE3_STR %[STAGE3]
incbin STAGE3_STR
align 512, db 0
.end:
; the maximum size of the boot loader portion is 384 KiB
times (384*1024)-($-$$) db 0
-176
View File
@@ -1,176 +0,0 @@
SECTION .text
USE16
cpuid_required_features:
.edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr
.ecx equ 0
cpuid_check:
; If bit 21 of EFLAGS can be changed, then CPUID is supported
pushfd ;Save EFLAGS
pushfd ;Store EFLAGS
xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS
popfd ;Load stored EFLAGS (with ID bit inverted)
pushfd ;Store EFLAGS again (ID bit may or may not be inverted)
pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] ;eax = whichever bits were changed
popfd ;Restore original EFLAGS
test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero
jz .no_cpuid
mov eax, 1
cpuid
and edx, cpuid_required_features.edx
cmp edx, cpuid_required_features.edx
jne .error
and ecx, cpuid_required_features.ecx
cmp ecx, cpuid_required_features.ecx
jne .error
ret
.no_cpuid:
mov si, .msg_cpuid
call print
mov si, .msg_line
call print
jmp .halt
.error:
push ecx
push edx
mov si, .msg_features
call print
mov si, .msg_line
call print
mov si, .msg_edx
call print
pop ebx
push ebx
shr ebx, 16
call print_hex
pop ebx
call print_hex
mov si, .msg_must_contain
call print
mov ebx, cpuid_required_features.edx
shr ebx, 16
call print_hex
mov ebx, cpuid_required_features.edx
call print_hex
mov si, .msg_line
call print
mov si, .msg_ecx
call print
pop ebx
push ebx
shr ebx, 16
call print_hex
pop ebx
call print_hex
mov si, .msg_must_contain
call print
mov ebx, cpuid_required_features.ecx
shr ebx, 16
call print_hex
mov ebx, cpuid_required_features.ecx
call print_hex
mov si, .msg_line
call print
.halt:
cli
hlt
jmp .halt
.msg_cpuid: db "CPUID not supported",0
.msg_features: db "Required CPU features are not present",0
.msg_line: db 13,10,0
.msg_edx: db "EDX ",0
.msg_ecx: db "ECX ",0
.msg_must_contain: db " must contain ",0
cpuid_edx:
.fpu equ 1 << 0
.vme equ 1 << 1
.de equ 1 << 2
.pse equ 1 << 3
.tsc equ 1 << 4
.msr equ 1 << 5
.pae equ 1 << 6
.mce equ 1 << 7
.cx8 equ 1 << 8
.apic equ 1 << 9
.sep equ 1 << 11
.mtrr equ 1 << 12
.pge equ 1 << 13
.mca equ 1 << 14
.cmov equ 1 << 15
.pat equ 1 << 16
.pse_36 equ 1 << 17
.psn equ 1 << 18
.clfsh equ 1 << 19
.ds equ 1 << 21
.acpi equ 1 << 22
.mmx equ 1 << 23
.fxsr equ 1 << 24
.sse equ 1 << 25
.sse2 equ 1 << 26
.ss equ 1 << 27
.htt equ 1 << 28
.tm equ 1 << 29
.ia64 equ 1 << 30
.pbe equ 1 << 31
cpuid_ecx:
.sse3 equ 1 << 0
.pclmulqdq equ 1 << 1
.dtes64 equ 1 << 2
.monitor equ 1 << 3
.ds_cpl equ 1 << 4
.vmx equ 1 << 5
.smx equ 1 << 6
.est equ 1 << 7
.tm2 equ 1 << 8
.ssse3 equ 1 << 9
.cnxt_id equ 1 << 10
.sdbg equ 1 << 11
.fma equ 1 << 12
.cmpxchg16b equ 1 << 13
.xtpr equ 1 << 14
.pdcm equ 1 << 15
.pcid equ 1 << 17
.dca equ 1 << 18
.sse4_1 equ 1 << 19
.sse4_2 equ 1 << 20
.x2apic equ 1 << 21
.movbe equ 1 << 22
.popcnt equ 1 << 23
.tsc_deadline equ 1 << 24
.aes equ 1 << 25
.xsave equ 1 << 26
.osxsave equ 1 << 27
.avx equ 1 << 28
.f16c equ 1 << 29
.rdrand equ 1 << 30
.hypervisor equ 1 << 31
-128
View File
@@ -1,128 +0,0 @@
SECTION .text ; cannot use .data
struc GDTEntry
.limitl resw 1
.basel resw 1
.basem resb 1
.attribute resb 1
.flags__limith resb 1
.baseh resb 1
endstruc
gdt_attr:
.present equ 1 << 7
.ring1 equ 1 << 5
.ring2 equ 1 << 6
.ring3 equ 1 << 5 | 1 << 6
.user equ 1 << 4
;user
.code equ 1 << 3
; code
.conforming equ 1 << 2
.readable equ 1 << 1
; data
.expand_down equ 1 << 2
.writable equ 1 << 1
.accessed equ 1 << 0
;system
; legacy
.tssAvailabe16 equ 0x1
.ldt equ 0x2
.tssBusy16 equ 0x3
.call16 equ 0x4
.task equ 0x5
.interrupt16 equ 0x6
.trap16 equ 0x7
.tssAvailabe32 equ 0x9
.tssBusy32 equ 0xB
.call32 equ 0xC
.interrupt32 equ 0xE
.trap32 equ 0xF
; long mode
.ldt32 equ 0x2
.tssAvailabe64 equ 0x9
.tssBusy64 equ 0xB
.call64 equ 0xC
.interrupt64 equ 0xE
.trap64 equ 0xF
gdt_flag:
.granularity equ 1 << 7
.available equ 1 << 4
;user
.default_operand_size equ 1 << 6
; code
.long_mode equ 1 << 5
; data
.reserved equ 1 << 5
gdtr:
dw gdt.end + 1 ; size
dq gdt ; offset
gdt:
.null equ $ - gdt
dq 0
.lm64_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code
at GDTEntry.flags__limith, db gdt_flag.long_mode
at GDTEntry.baseh, db 0
iend
.lm64_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0
at GDTEntry.baseh, db 0
iend
.pm32_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
at GDTEntry.baseh, db 0
iend
.pm32_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
at GDTEntry.baseh, db 0
iend
.pm16_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
at GDTEntry.flags__limith, db 0xF
at GDTEntry.baseh, db 0
iend
.pm16_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0xF
at GDTEntry.baseh, db 0
iend
.end equ $ - gdt
-161
View File
@@ -1,161 +0,0 @@
; Simple ISO emulation with el torito
; Fill until CD sector 0x10
times (0x10*2048)-($-$$) db 0
; Volume record
;TODO: fill in more fields
iso_volume_record:
db 1 ; Type volume record
db "CD001" ; Identifier
db 1 ; Version
db 0 ; Unused
times 32 db ' ' ; System identifier
.volume_id: ; Volume identifier
db 'Redox OS'
times 32-($-.volume_id) db ' '
times 8 db 0 ; Unused
db 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 ; Volume space size (0x15)
times 32 db 0 ; Unused
db 0x01, 0x00, 0x00, 0x01 ; Volume set size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x00, 0x08, 0x08, 0x00 ; Logical block size in little and big endian
times 156-($-iso_volume_record) db 0
; Root directory entry
.root_directory:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x00 ; File identifier
times 128 db ' ' ; Volume set identifier
times 128 db ' ' ; Publisher identifier
times 128 db ' ' ; Data preparer identifier
times 128 db ' ' ; Application identifier
times 37 db ' ' ; Copyright file ID
times 37 db ' ' ; Abstract file ID
times 37 db ' ' ; Bibliographic file ID
times 881-($-iso_volume_record) db 0
db 1 ; File structure version
; Fill until CD sector 0x11
times (0x11*2048)-($-$$) db 0
; Boot record
iso_boot_record:
db 0 ; Type boot record
db "CD001" ; Identifier
db 1 ; Version
db "EL TORITO SPECIFICATION" ; Boot system identifier
times 0x47-($ - iso_boot_record) db 0 ; Padding
dd 0x13 ; Sector of boot catalog
; Fill until CD sector 0x12
times (0x12*2048)-($-$$) db 0
; Terminator
iso_terminator:
db 0xFF ; Type terminator
db "CD001" ; Identifier
db 1 ; Version
; Fill until CD sector 0x13
times (0x13*2048)-($-$$) db 0
; Boot catalog
iso_boot_catalog:
; Validation entry
.validation:
db 1 ; Header ID
db 0 ; Platform ID (x86)
dw 0 ; Reserved
times 24 db 0 ; ID string
dw 0x55aa ; Checksum
dw 0xaa55 ; Key
; Default entry
.default:
db 0x88 ; Bootable
db 4 ; Hard drive emulation
dw 0 ; Load segment (0 is platform default)
db 0xEE ; Partition type (0xEE is protective MBR)
db 0 ; Unused
dw 1 ; Sector count
dd 0 ; Start address for virtual disk
times 20 db 0 ; Padding
; EFI section header entry
.efi_section_header:
db 0x91 ; Final header
db 0xEF ; Platform ID (EFI)
dw 1 ; Number of section header entries
times 28 db 0 ; ID string
; EFI section entry
.efi_section_entry:
db 0x88 ; Bootable
db 0 ; No emulation
dw 0 ; Load segment (0 is platform default)
db 0 ; Partition type (not used)
db 0 ; Unused
dw 512 ; Sector count (1 MiB = 512 CD sectors)
dd 512 ; Start address for virtual disk (1 MiB = 512 CD sectors)
times 20 db 0 ; Padding
; Fill until CD sector 0x14
times (0x14*2048)-($-$$) db 0
iso_root_directory:
.self:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x00 ; File identifier
.parent:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x01 ; File identifier
.boot_cat:
db 0x2C ; Length of entry
db 0x00 ; Length of extended attributes
db 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13 ; Location of extent (0x13)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x00 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x0A ; Length of file identifier
db "BOOT.CAT;1",0 ; File identifier
; Fill until CD sector 0x15
times (0x15*2048)-($-$$) db 0
-56
View File
@@ -1,56 +0,0 @@
SECTION .text
USE32
long_mode:
.func: dq 0
.page_table: dd 0
.entry:
; disable interrupts
cli
; disable paging
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
mov eax, cr4
or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4
mov cr4, eax
; load long mode GDT
lgdt [gdtr]
; enable long mode
mov ecx, 0xC0000080 ; Read from the EFER MSR.
rdmsr
or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit.
wrmsr
; set page table
mov eax, [.page_table]
mov cr3, eax
; enabling paging and protection simultaneously
mov eax, cr0
or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
mov cr0, eax
; far jump to enable Long Mode and load CS with 64 bit segment
jmp gdt.lm64_code:.inner
USE64
.inner:
; load all the other segments with 64 bit data segments
mov rax, gdt.lm64_data
mov ds, rax
mov es, rax
mov fs, rax
mov gs, rax
mov ss, rax
; jump to specified function
mov rax, [.func]
jmp rax
-67
View File
@@ -1,67 +0,0 @@
SECTION .text
USE16
; provide function for printing in x86 real mode
; print a string and a newline
; CLOBBER
; ax
print_line:
mov al, 13
call print_char
mov al, 10
jmp print_char
; print a string
; IN
; si: points at zero-terminated String
; CLOBBER
; si, ax
print:
pushf
cld
.loop:
lodsb
test al, al
jz .done
call print_char
jmp .loop
.done:
popf
ret
; print a character
; IN
; al: character to print
print_char:
pusha
mov bx, 7
mov ah, 0x0e
int 0x10
popa
ret
; print a number in hex
; IN
; bx: the number
; CLOBBER
; al, cx
print_hex:
mov cx, 4
.lp:
mov al, bh
shr al, 4
cmp al, 0xA
jb .below_0xA
add al, 'A' - 0xA - '0'
.below_0xA:
add al, '0'
call print_char
shl bx, 4
loop .lp
ret
-36
View File
@@ -1,36 +0,0 @@
SECTION .text
USE16
protected_mode:
.func: dd 0
.entry:
; disable interrupts
cli
; load protected mode GDT
lgdt [gdtr]
; set protected mode bit of cr0
mov eax, cr0
or eax, 1
mov cr0, eax
; far jump to load CS with 32 bit segment
jmp gdt.pm32_code:.inner
USE32
.inner:
; load all the other segments with 32 bit data segments
mov eax, gdt.pm32_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; jump to specified function
mov eax, [.func]
jmp eax
-222
View File
@@ -1,222 +0,0 @@
ORG 0x7C00
SECTION .text
USE16
stage1: ; dl comes with disk
; initialize segment registers
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
; initialize stack
mov sp, 0x7C00
; initialize CS
push ax
push word .set_cs
retf
.set_cs:
; save disk number
mov [disk], dl
mov si, stage_msg
call print
mov al, '1'
call print_char
call print_line
; read CHS gemotry
; CL (bits 0-5) = maximum sector number
; CL (bits 6-7) = high bits of max cylinder number
; CH = low bits of maximum cylinder number
; DH = maximum head number
mov ah, 0x08
mov dl, [disk]
xor di, di
int 0x13
jc error ; carry flag set on error
mov bl, ch
mov bh, cl
shr bh, 6
mov [chs.c], bx
shr dx, 8
inc dx ; returns heads - 1
mov [chs.h], dx
and cl, 0x3f
mov [chs.s], cl
mov eax, (stage2 - stage1) / 512
mov bx, stage2
mov cx, (stage3.end - stage2) / 512
mov dx, 0
call load
mov si, stage_msg
call print
mov al, '2'
call print_char
call print_line
jmp stage2.entry
; load some sectors from disk to a buffer in memory
; buffer has to be below 1MiB
; IN
; ax: start sector
; bx: offset of buffer
; cx: number of sectors (512 Bytes each)
; dx: segment of buffer
; CLOBBER
; ax, bx, cx, dx, si
; TODO rewrite to (eventually) move larger parts at once
; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end)
load:
cmp cx, 127
jbe .good_size
pusha
mov cx, 127
call load
popa
add eax, 127
add dx, 127 * 512 / 16
sub cx, 127
jmp load
.good_size:
mov [DAPACK.addr], eax
mov [DAPACK.buf], bx
mov [DAPACK.count], cx
mov [DAPACK.seg], dx
call print_dapack
cmp byte [chs.s], 0
jne .chs
;INT 0x13 extended read does not work on CDROM!
mov dl, [disk]
mov si, DAPACK
mov ah, 0x42
int 0x13
jc error ; carry flag set on error
ret
.chs:
; calculate CHS
xor edx, edx
mov eax, [DAPACK.addr]
div dword [chs.s] ; divide by sectors
mov ecx, edx ; move sector remainder to ecx
xor edx, edx
div dword [chs.h] ; divide by heads
; eax has cylinders, edx has heads, ecx has sectors
; Sector cannot be greater than 63
inc ecx ; Sector is base 1
cmp ecx, 63
ja error_chs
; Head cannot be greater than 255
cmp edx, 255
ja error_chs
; Cylinder cannot be greater than 1023
cmp eax, 1023
ja error_chs
; Move CHS values to parameters
mov ch, al
shl ah, 6
and cl, 0x3f
or cl, ah
shl dx, 8
; read from disk using CHS
mov al, [DAPACK.count]
mov ah, 0x02 ; disk read (CHS)
mov bx, [DAPACK.buf]
mov dl, [disk]
push es ; save ES
mov es, [DAPACK.seg]
int 0x13
pop es ; restore EC
jc error ; carry flag set on error
ret
print_dapack:
mov bx, [DAPACK.addr + 2]
call print_hex
mov bx, [DAPACK.addr]
call print_hex
mov al, '#'
call print_char
mov bx, [DAPACK.count]
call print_hex
mov al, ' '
call print_char
mov bx, [DAPACK.seg]
call print_hex
mov al, ':'
call print_char
mov bx, [DAPACK.buf]
call print_hex
call print_line
ret
error_chs:
mov ah, 0
error:
call print_line
mov bh, 0
mov bl, ah
call print_hex
mov al, ' '
call print_char
mov si, error_msg
call print
call print_line
.halt:
cli
hlt
jmp .halt
%include "print.asm"
stage_msg: db "Stage ",0
error_msg: db "ERROR",0
disk: db 0
chs:
.c: dd 0
.h: dd 0
.s: dd 0
DAPACK:
db 0x10
db 0
.count: dw 0 ; int 13 resets this to # of blocks actually read/written
.buf: dw 0 ; memory buffer destination address (0:7c00)
.seg: dw 0 ; in memory page zero
.addr: dq 0 ; put the lba to read in this spot
times 446-($-$$) db 0
partitions: times 4 * 16 db 0
db 0x55
db 0xaa
-134
View File
@@ -1,134 +0,0 @@
SECTION .text
USE16
stage2.entry:
; check for required features
call cpuid_check
; enable A20-Line via IO-Port 92, might not work on all motherboards
in al, 0x92
or al, 2
out 0x92, al
mov dword [protected_mode.func], stage3.entry
jmp protected_mode.entry
%include "cpuid.asm"
%include "gdt.asm"
%include "long_mode.asm"
%include "protected_mode.asm"
%include "thunk.asm"
USE32
stage3.entry:
; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer)
mov esp, 0x70000
; push arguments
mov eax, thunk.int16
push eax
mov eax, thunk.int15
push eax
mov eax, thunk.int13
push eax
mov eax, thunk.int10
push eax
xor eax, eax
mov al, [disk]
push eax
mov eax, kernel.entry
push eax
mov eax, [stage3 + 0x18]
call eax
.halt:
cli
hlt
jmp .halt
kernel:
.stack: dq 0
.func: dq 0
.args: dq 0
.entry:
; page_table: usize
mov eax, [esp + 4]
mov [long_mode.page_table], eax
; stack: u64
mov eax, [esp + 8]
mov [.stack], eax
mov eax, [esp + 12]
mov [.stack + 4], eax
; func: u64
mov eax, [esp + 16]
mov [.func], eax
mov eax, [esp + 20]
mov [.func + 4], eax
; args: *const KernelArgs
mov eax, [esp + 24]
mov [.args], eax
; long_mode: usize
mov eax, [esp + 28]
test eax, eax
jz .inner32
mov eax, .inner64
mov [long_mode.func], eax
jmp long_mode.entry
.inner32:
; disable paging
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
;TODO: PAE (1 << 5)
; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension
mov eax, cr4
or eax, 1 << 9 | 1 << 7 | 1 << 4
mov cr4, eax
; set page table
mov eax, [long_mode.page_table]
mov cr3, eax
; enabling paging and protection simultaneously
mov eax, cr0
; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
or eax, 1 << 31 | 1 << 16 | 1
mov cr0, eax
; enable FPU
;TODO: move to Rust
mov eax, cr0
and al, 11110011b ; Clear task switched (3) and emulation (2)
or al, 00100010b ; Set numeric error (5) monitor co-processor (1)
mov cr0, eax
fninit
mov esp, [.stack]
mov eax, [.args]
push eax
mov eax, [.func]
call eax
.halt32:
cli
hlt
jmp .halt32
USE64
.inner64:
mov rsp, [.stack]
mov rax, [.func]
mov rdi, [.args]
call rax
.halt64:
cli
hlt
jmp .halt64
-149
View File
@@ -1,149 +0,0 @@
SECTION .text
USE32
thunk:
.int10:
mov dword [.func], .int10_real
jmp .enter
.int13:
mov dword [.func], .int13_real
jmp .enter
.int15:
mov dword [.func], .int15_real
jmp .enter
.int16:
mov dword [.func], .int16_real
jmp .enter
.func: dd 0
.esp: dd 0
.cr0: dd 0
.enter:
; save flags
pushfd
; save registers
pushad
; save esp
mov [.esp], esp
; load gdt
lgdt [gdtr]
; far jump to protected mode 16-bit
jmp gdt.pm16_code:.pm16
.exit:
; set segment selectors to 32-bit protected mode
mov eax, gdt.pm32_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; restore esp
mov esp, [.esp]
; restore registers
popad
; restore flags
popfd
; return
ret
USE16
.int10_real:
int 0x10
ret
.int13_real:
int 0x13
ret
.int15_real:
int 0x15
ret
.int16_real:
int 0x16
ret
.pm16:
; set segment selectors to protected mode 16-bit
mov eax, gdt.pm16_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; save cr0
mov eax, cr0
mov [.cr0], eax
; disable paging and protected mode
and eax, 0x7FFFFFFE
mov cr0, eax
; far jump to real mode
jmp 0:.real
.real:
; set segment selectors to real mode
mov eax, 0
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; set stack
mov esp, 0x7C00 - 64
; load registers and ES
pop es
pop edi
pop esi
pop ebp
pop ebx
pop edx
pop ecx
pop eax
; enable interrupts
sti
; call real mode function
call [.func]
; disable interrupts
cli
; save registers and ES
push eax
push ecx
push edx
push ebx
push ebp
push esi
push edi
push es
; load gdt (BIOS sometimes overwrites this)
lgdt [gdtr]
; restore cr0, will enable protected mode
mov eax, [.cr0]
mov cr0, eax
; far jump to protected mode 32-bit
jmp gdt.pm32_code:.exit
+174
View File
@@ -0,0 +1,174 @@
# This is the default configuration file
# General settings
[general]
# Do not prompt if settings are not defined
prompt = false
# Package settings
[packages]
#acid = {}
#autoconf = {}
#automake = {}
#bash = {}
#binutils = {}
#ca-certificates = {}
#cargo = {}
#contain = {}
coreutils = {}
#curl = {}
#dash = {}
#diffutils = {}
drivers = {}
extrautils = {}
findutils = {}
#games = {}
#gawk = {}
#gcc = {}
#git = {}
#gnu-binutils = {}
#gnu-make = {}
#installer = {}
ion = {}
#lua = {}
#nasm = {}
netstack = {}
netutils = {}
#newlib = {}
#openssl = {}
orbdata = {}
orbital = {}
orbterm = {}
orbutils = {}
pastel = {}
#patch = {}
#pixelcannon = {}
pkgutils = {}
ptyd = {}
#python = {}
randd = {}
#redoxfs = {}
#rust = {}
#rustual-boy = {}
#sed = {}
smith = {}
sodium = {}
userutils = {}
uutils = {}
#xz = {}
# User settings
[users.root]
password = "password"
uid = 0
gid = 0
name = "root"
home = "/root"
[users.user]
# Password is unset
password = ""
[groups.sudo]
gid = 1
members = ["user"]
[[files]]
path = "/etc/init.d/00_base"
data = """
pcid /etc/pcid/filesystem.toml
randd
ptyd
"""
[[files]]
path = "/etc/init.d/10_net"
data = """
ethernetd
ipd
icmpd
tcpd
udpd
dhcpd -b
"""
[[files]]
path = "/etc/init.d/20_orbital"
data = """
orbital orblogin launcher
"""
[[files]]
path = "/etc/init.d/30_console"
data = """
getty display/vesa:2
getty debug: -J
"""
[[files]]
path = "/etc/net/dns"
data = """
208.67.222.222
"""
[[files]]
path = "/etc/net/ip"
data = """
10.0.2.15
"""
[[files]]
path = "/etc/net/ip_router"
data = """
10.0.2.2
"""
[[files]]
path = "/etc/net/ip_subnet"
data = """
255.255.255.0
"""
[[files]]
path = "/etc/net/mac"
data = """
54-52-00-ab-cd-ef
"""
[[files]]
path = "/etc/pkg.d/50_redox"
data = "https://static.redox-os.org/pkg"
[[files]]
path = "/etc/hostname"
data = "redox"
[[files]]
path = "/etc/issue"
data = """
########## Redox OS ##########
# Login with the following: #
# `user` #
# `root`:`password` #
##############################
"""
[[files]]
path = "/etc/motd"
data = """
Welcome to Redox OS!
"""
[[files]]
path = "/usr"
data = "/"
symlink = true
[[files]]
path = "/tmp"
data = ""
directory = true
# 0o1777
mode = 1023
+24
View File
@@ -0,0 +1,24 @@
# This is the default configuration file
# General settings
[general]
# Do not prompt if settings are not defined
prompt = false
# Package settings
[packages]
binutils = {}
coreutils = {}
extrautils = {}
ion = {}
netutils = {}
pkgutils = {}
userutils = {}
# User settings
[users.root]
password = "password"
uid = 0
gid = 0
name = "root"
home = "/root"
-78
View File
@@ -1,78 +0,0 @@
OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv")
OUTPUT_ARCH(riscv)
ENTRY(coff_start)
SECTIONS
{
PROVIDE(ImageBase = .);
. = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) *(.gnu.hash) }
. = ALIGN(4096);
.text :
{
PROVIDE(_text = .);
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(SORT(.text.sorted.*))
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf.em. */
*(.gnu.warning)
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
. = ALIGN(4096);
.rdata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
*(.rodata1)
KEEP (*(.eh_frame))
*(.eh_frame.*)
*(.dynamic)
}
. = ALIGN(4096);
.data :
{
*(.got) *(.igot)
*(.got.plt) *(.igot.plt)
*(.data .data.* .gnu.linkonce.d.*)
*(.data1)
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
*(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
*(.sdata .sdata.* .gnu.linkonce.s.*)
PROVIDE (_edata = .); PROVIDE (edata = .);
. = ALIGN(4096);
PROVIDE (__bss_start = .);
*(.sbss2 .sbss2.* .gnu.linkonce.sb2.*)
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(4096);
}
.reloc :
{
KEEP(*(.reloc*))
}
.rela :
{
*(.rela.*)
}
.data.rel.ro :
{
*(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*)
*(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
}
. = ALIGN(4096);
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}
-57
View File
@@ -1,57 +0,0 @@
ENTRY(start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
/* The start address must match bootloader.asm */
. = 0x13000;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
.tdata : {
__tdata_start = .;
*(.tdata*)
. = ALIGN(4096);
__tdata_end = .;
__tbss_start = .;
*(.tbss*)
. += 8;
. = ALIGN(4096);
__tbss_end = .;
}
__end = .;
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-63
View File
@@ -1,63 +0,0 @@
export PARTED?=parted
export QEMU?=qemu-system-aarch64
all: $(BUILD)/bootloader.efi
$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--bin bootloader \
-- \
--emit link="$@"
$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--bin bootloader \
--features live \
-- \
--emit link="$@"
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
rm -f "$@.partial"
fallocate -l 64MiB "$@.partial"
mkfs.vfat -F 32 "$@.partial"
mmd -i "$@.partial" efi
mmd -i "$@.partial" efi/boot
mcopy -i "$@.partial" "$<" ::efi/boot/bootaa64.efi
mv "$@.partial" "$@"
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
rm -f "$@.partial"
fallocate -l 320MiB "$@.partial"
$(PARTED) -s -a minimal "$@.partial" mklabel gpt
$(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 65MiB
$(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 65MiB 100%
$(PARTED) -s -a minimal "$@.partial" toggle 1 boot
dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=65 conv=notrunc
mv "$@.partial" "$@"
$(BUILD)/firmware.rom: /usr/share/AAVMF/AAVMF_CODE.fd
cp "$<" "$@"
qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom
$(QEMU) \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-device virtio-gpu-pci \
-machine virt \
-net none \
-cpu max \
-bios "$(BUILD)/firmware.rom" \
-drive file="$<",format=raw
-102
View File
@@ -1,102 +0,0 @@
LD=riscv64-unknown-redox-ld
OBJCOPY=riscv64-unknown-redox-objcopy
SCRIPT=$(SOURCE)/linkers/riscv64-unknown-uefi.ld
PARTED?=parted
QEMU?=qemu-system-riscv64
all: $(BUILD)/bootloader.efi
$(BUILD)/%.efi: $(BUILD)/%.efi.elf $(BUILD)/%.efi.sym
$(OBJCOPY) -j .text -j .data -j .rdata -j .rela -j .reloc --target pei-riscv64-little \
--file-alignment 512 --section-alignment 4096 --subsystem 10 "$<" "$@"
.PRECIOUS: $(BUILD)/%.efi.sym
$(BUILD)/%.efi.sym: $(BUILD)/%.efi.elf
$(OBJCOPY) --only-keep-debug "$<" "$@"
$(BUILD)/%.efi.elf: $(BUILD)/%.a $(SCRIPT)
$(LD) --gc-sections -z max-page-size=0x1000 --warn-common --no-undefined -z nocombreloc -shared \
--fatal-warnings -Bsymbolic --entry coff_start -T "$(SCRIPT)" -o "$@" "$<"
$(BUILD)/bootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--lib \
-- \
--emit link=$@
$(BUILD)/bootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--lib \
--features live \
-- \
--emit link=$@
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
rm -f $@.partial
fallocate -l 64MiB $@.partial
mkfs.vfat -F 32 $@.partial
mmd -i $@.partial EFI
mmd -i $@.partial EFI/BOOT
mcopy -i $@.partial $< ::EFI/BOOT/BOOTRISCV64.EFI
mv $@.partial $@
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
rm -f $@.partial
fallocate -l 320MiB $@.partial
$(PARTED) -s -a minimal $@.partial mklabel gpt
$(PARTED) -s -a minimal $@.partial mkpart ESP FAT32 1MiB 65MiB
$(PARTED) -s -a minimal $@.partial mkpart REDOXFS 65MiB 100%
$(PARTED) -s -a minimal $@.partial toggle 1 boot
dd if=$(BUILD)/esp.bin of=$@.partial bs=1MiB seek=1 conv=notrunc
dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=65 conv=notrunc
mv $@.partial $@
$(BUILD)/fw_vars.img: /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd
cp "$<" "$@"
$(BUILD)/firmware.rom: /usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd
cp "$<" "$@"
qemu-acpi: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img
$(QEMU) \
-M virt \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-device virtio-gpu-pci \
-machine virt \
-net none \
-cpu max \
-drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \
-drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \
-drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio
qemu-dtb: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom $(BUILD)/fw_vars.img
$(QEMU) \
-M virt,acpi=off \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-device virtio-gpu-pci \
-machine virt \
-net none \
-cpu max \
-drive if=pflash,format=raw,unit=0,file=$(BUILD)/firmware.rom,readonly=on \
-drive if=pflash,format=raw,unit=1,file=$(BUILD)/fw_vars.img \
-drive file=$(BUILD)/harddrive.bin,format=raw,if=virtio -s
-59
View File
@@ -1,59 +0,0 @@
export LD?=ld
export OBJCOPY?=objcopy
export PARTED?=parted
export QEMU?=qemu-system-x86_64
all: $(BUILD)/bootloader.bin
$(BUILD)/libbootloader.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--lib \
-- \
--emit link="$@"
$(BUILD)/libbootloader-live.a: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft -Zunstable-options" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--lib \
--features live \
-- \
--emit link="$@"
$(BUILD)/%.elf: $(BUILD)/lib%.a $(SOURCE)/linkers/$(TARGET).ld
$(LD) -m elf_i386 --gc-sections -z max-page-size=0x1000 -T "$(SOURCE)/linkers/$(TARGET).ld" -o "$@" "$<"
$(OBJCOPY) --only-keep-debug "$@" "$@.sym"
$(OBJCOPY) --strip-debug "$@"
$(BUILD)/%.bin: $(BUILD)/%.elf $(shell find $(SOURCE)/asm/$(TARGET) -type f)
nasm -f bin -o "$@" -l "$@.lst" -D STAGE3="$<" -i"$(SOURCE)/asm/$(TARGET)/" "$(SOURCE)/asm/$(TARGET)/bootloader.asm"
$(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin
rm -f "$@.partial"
fallocate -l 256MiB "$@.partial"
$(PARTED) -s -a minimal "$@.partial" mklabel msdos
$(PARTED) -s -a minimal "$@.partial" mkpart primary 2MiB 100%
dd if="$<" of="$@.partial" bs=1 count=446 conv=notrunc
dd if="$<" of="$@.partial" bs=512 skip=1 seek=1 conv=notrunc
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc
mv "$@.partial" "$@"
qemu: $(BUILD)/harddrive.bin
$(QEMU) \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-machine q35 \
-net none \
-enable-kvm \
-cpu host \
-drive file="$<",format=raw
-64
View File
@@ -1,64 +0,0 @@
export PARTED?=parted
export QEMU?=qemu-system-x86_64
all: $(BUILD)/bootloader.efi
$(BUILD)/bootloader.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p "$(BUILD)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--bin bootloader \
-- \
--emit link="$@"
$(BUILD)/bootloader-live.efi: $(SOURCE)/Cargo.toml $(SOURCE)/Cargo.lock $(shell find $(SOURCE)/src -type f)
mkdir -p $(BUILD)
cd "$(SOURCE)"
env RUSTFLAGS="--cfg aes_force_soft" \
cargo rustc \
--manifest-path="$<" \
$(CARGO_ARGS) \
--bin bootloader \
--features live \
-- \
--emit link="$@"
$(BUILD)/esp.bin: $(BUILD)/bootloader.efi
rm -f "$@.partial"
fallocate -l 1MiB $@.partial
mkfs.vfat "$@.partial"
mmd -i "$@.partial" efi
mmd -i "$@.partial" efi/boot
mcopy -i "$@.partial" "$<" ::efi/boot/bootx64.efi
mv "$@.partial" "$@"
$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin
rm -f "$@.partial"
fallocate -l 320MiB "$@.partial"
$(PARTED) -s -a minimal "$@.partial" mklabel gpt
$(PARTED) -s -a minimal "$@.partial" mkpart ESP FAT32 1MiB 2MiB
$(PARTED) -s -a minimal "$@.partial" mkpart REDOXFS 2MiB 100%
$(PARTED) -s -a minimal "$@.partial" toggle 1 boot
dd if="$(BUILD)/esp.bin" of="$@.partial" bs=1MiB seek=1 conv=notrunc
dd if="$(BUILD)/filesystem.bin" of="$@.partial" bs=1MiB seek=2 conv=notrunc
mv "$@.partial" "$@"
$(BUILD)/firmware.rom: /usr/share/OVMF/OVMF_CODE.fd
cp "$<" "$@"
qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom
$(QEMU) \
-d cpu_reset \
-no-reboot \
-smp 4 -m 2048 \
-chardev stdio,id=debug,signal=off,mux=on \
-serial chardev:debug \
-mon chardev=debug \
-machine q35 \
-net none \
-enable-kvm \
-cpu host \
-bios "$(BUILD)/firmware.rom" \
-drive file="$<",format=raw
+411
View File
@@ -0,0 +1,411 @@
# Automatically generated by update.sh
include = []
[general]
prompt = false
filesystem_size = 256
[packages.base]
[packages.base-initfs]
[packages.bootloader]
[packages.ca-certificates]
[packages.coreutils]
[packages.extrautils]
[packages.findutils]
[packages.ion]
[packages.kernel]
[packages.kibi]
[packages.libgcc]
[packages.libstdcxx]
[packages.netdb]
[packages.netutils]
[packages.pkgutils]
[packages.relibc]
[packages.userutils]
[packages.uutils]
[[files]]
path = "/usr/lib/init.d/00_base"
data = """
# clear and recreate tmpdir with 0o1777 permission
rm -rf /tmp
mkdir -m a=rwxt /tmp
ipcd
ptyd
nowait sudo --daemon
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/lib/init.d/00_drivers"
data = """
pcid-spawner /etc/pcid.d/
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/hostname"
data = "redox"
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/lib/os-release"
data = """
PRETTY_NAME="Redox OS 0.9.0"
NAME="Redox OS"
VERSION_ID="0.9.0"
VERSION="0.9.0"
ID="redox-os"
HOME_URL="https://redox-os.org/"
DOCUMENTATION_URL="https://redox-os.org/docs/"
SUPPORT_URL="https://redox-os.org/community/"
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/os-release"
data = "../usr/lib/os-release"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/pkg.d/50_redox"
data = "https://static.redox-os.org/pkg"
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/bin"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/bin"
data = "usr/bin"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/include"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/include"
data = "usr/include"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/lib"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/lib"
data = "usr/lib"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/libexec"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/share"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/share"
data = "usr/share"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/share/fonts"
data = "../../ui/fonts"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/var"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/var/cache"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/var/lib"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/var/lock"
data = ""
symlink = false
directory = true
mode = 1023
recursive_chown = false
postinstall = false
[[files]]
path = "/var/log"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/var/run"
data = ""
symlink = false
directory = true
mode = 493
recursive_chown = false
postinstall = false
[[files]]
path = "/var/tmp"
data = ""
symlink = false
directory = true
mode = 1023
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/null"
data = "/scheme/null"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/random"
data = "/scheme/rand"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/urandom"
data = "/scheme/rand"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/zero"
data = "/scheme/zero"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/tty"
data = "libc:tty"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/stdin"
data = "libc:stdin"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/stdout"
data = "libc:stdout"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/dev/stderr"
data = "libc:stderr"
symlink = true
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/lib/init.d/10_net"
data = """
smolnetd
nowait dhcpd
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/net/dns"
data = """
9.9.9.9
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/net/ip"
data = """
10.0.2.15
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/net/ip_router"
data = """
10.0.2.2
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/etc/net/ip_subnet"
data = """
255.255.255.0
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[[files]]
path = "/usr/lib/init.d/30_console"
data = """
inputd -A 2
nowait getty 2
nowait getty /scheme/debug -J
"""
symlink = false
directory = false
recursive_chown = false
postinstall = false
[users.root]
password = "password"
uid = 0
gid = 0
name = "root"
home = "/root"
shell = "/usr/bin/ion"
[users.user]
password = ""
shell = "/usr/bin/ion"
[groups.sudo]
gid = 1
members = ["user"]
Executable
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -e
RES_PATH="$(dirname "$0")"
if [ -d "$1" ]
then
REDOX_PATH="$1"
else
echo "$0 [path to redox repository]" >&2
exit 1
fi
set -x
# Update res/test.toml from the redoxer.toml template
"${REDOX_PATH}/build/fstools/bin/redox_installer" \
--config="${REDOX_PATH}/config/x86_64/minimal-net.toml" \
--output-config="${RES_PATH}/test.toml"
sed -i '1s/^/# Automatically generated by update.sh\n\n/' "${RES_PATH}/test.toml"
-3
View File
@@ -1,3 +0,0 @@
[toolchain]
channel = "nightly-2026-05-24"
components = ["rust-src"]
-154
View File
@@ -1,154 +0,0 @@
use crate::area_add;
use crate::os::{dtb::is_in_dev_mem_region, Os, OsMemoryEntry, OsMemoryKind};
use core::slice;
pub(crate) const PF_PRESENT: u64 = 1 << 0;
pub(crate) const PF_TABLE: u64 = 1 << 1;
pub(crate) const PF_OUTER_SHAREABLE: u64 = 0b01 << 8;
pub(crate) const PF_INNER_SHAREABLE: u64 = 0b11 << 8;
pub(crate) const PF_ACCESS: u64 = 1 << 10;
pub(crate) const PF_DEV: u64 = PF_OUTER_SHAREABLE | 2 << 2;
pub(crate) const PF_RAM: u64 = PF_INNER_SHAREABLE;
pub(crate) const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
pub(crate) const PAGE_ENTRIES: usize = 512;
const PAGE_SIZE: usize = 4096;
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
unsafe {
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
if !ptr.is_null() {
area_add(OsMemoryEntry {
base: ptr as u64,
size: PAGE_SIZE as u64,
kind: OsMemoryKind::Reclaim,
});
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
} else {
None
}
}
}
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
// Create L0
let l0 = paging_allocate(os)?;
{
// Create L1 for identity mapping
let l1 = paging_allocate(os)?;
// Link first user and first kernel L0 entry to L1
l0[0] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l0[256] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
// Identity map 8 GiB using 1 GiB pages
for l1_i in 0..8 {
let addr = l1_i as u64 * 0x4000_0000;
//TODO: is PF_RAM okay?
l1[l1_i] = addr | PF_ACCESS | PF_DEV | PF_PRESENT;
}
}
{
// Create L1 for kernel mapping
let l1 = paging_allocate(os)?;
// Link second to last L0 entry to L1
l0[510] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
// Map kernel_size at kernel offset
let mut kernel_mapped = 0;
let mut l1_i = 0;
while kernel_mapped < kernel_size && l1_i < l1.len() {
let l2 = paging_allocate(os)?;
l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l1_i += 1;
let mut l2_i = 0;
while kernel_mapped < kernel_size && l2_i < l2.len() {
let l3 = paging_allocate(os)?;
l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l2_i += 1;
let mut l3_i = 0;
while kernel_mapped < kernel_size && l3_i < l3.len() {
let addr = kernel_phys + kernel_mapped;
l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT;
l3_i += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
}
assert!(kernel_mapped >= kernel_size);
}
Some(l0.as_ptr() as usize)
}
}
pub unsafe fn paging_framebuffer(
os: &impl Os,
page_phys: usize,
framebuffer_phys: u64,
framebuffer_size: u64,
) -> Option<u64> {
unsafe {
//TODO: smarter test for framebuffer already mapped
if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 {
return Some(framebuffer_phys + PHYS_OFFSET);
}
let l0_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize;
let mut l1_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize;
let mut l2_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize;
let mut l3_i = ((framebuffer_phys % 0x20_0000) / (PAGE_SIZE as u64)) as usize;
assert_eq!(framebuffer_phys % (PAGE_SIZE as u64), 0);
let l0 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
// Create l1 for framebuffer mapping
let l1 = if l0[l0_i] == 0 {
let l1 = paging_allocate(os)?;
l0[l0_i] = l1.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
l1
} else {
slice::from_raw_parts_mut((l0[l0_i] & ENTRY_ADDRESS_MASK) as *mut u64, PAGE_ENTRIES)
};
// Map framebuffer_size at framebuffer offset
let mut framebuffer_mapped = 0;
while framebuffer_mapped < framebuffer_size && l1_i < l1.len() {
let l2 = paging_allocate(os)?;
assert_eq!(l1[l1_i], 0);
l1[l1_i] = l2.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
while framebuffer_mapped < framebuffer_size && l2_i < l2.len() {
let l3 = paging_allocate(os)?;
assert_eq!(l2[l2_i], 0);
l2[l2_i] = l3.as_ptr() as u64 | PF_ACCESS | PF_TABLE | PF_PRESENT;
while framebuffer_mapped < framebuffer_size && l3_i < l3.len() {
let addr = framebuffer_phys + framebuffer_mapped;
assert_eq!(l3[l3_i], 0);
//TODO: is PF_RAM okay?
l3[l3_i] = addr | PF_ACCESS | PF_RAM | PF_TABLE | PF_PRESENT;
framebuffer_mapped += PAGE_SIZE as u64;
l3_i += 1;
}
l2_i += 1;
l3_i = 0;
}
l1_i += 1;
l2_i = 0;
}
assert!(framebuffer_mapped >= framebuffer_size);
Some(framebuffer_phys + PHYS_OFFSET)
}
}
-17
View File
@@ -1,17 +0,0 @@
#[cfg(target_arch = "aarch64")]
pub use self::aarch64::*;
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::x86::*;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86;
#[cfg(target_arch = "riscv64")]
pub use self::riscv64::*;
#[cfg(target_arch = "riscv64")]
mod riscv64;
-59
View File
@@ -1,59 +0,0 @@
use core::slice;
use crate::area_add;
use crate::os::{Os, OsMemoryEntry, OsMemoryKind};
pub(crate) mod sv39;
pub(crate) mod sv48;
pub(crate) mod sv57;
// Common constants
const PAGE_SHIFT: usize = 12;
const TABLE_SHIFT: usize = 9;
const TABLE_MASK: usize = (1 << TABLE_SHIFT) - 1;
const PAGE_ENTRIES: usize = 512;
const PAGE_SIZE: usize = 4096;
const PHYS_MASK: usize = (1usize << 44) - 1;
const VALID: u64 = 1;
const RWX: u64 = 7 << 1;
const ACCESSED: u64 = 1 << 6;
const DIRTY: u64 = 1 << 7;
extern crate alloc;
pub(crate) use sv39::PHYS_OFFSET;
pub(crate) use sv39::SATP_BITS;
pub(crate) use sv39::paging_create;
pub(crate) use sv39::paging_physmem as paging_framebuffer;
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
unsafe {
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
if !ptr.is_null() {
area_add(OsMemoryEntry {
base: ptr as u64,
size: PAGE_SIZE as u64,
kind: OsMemoryKind::Reclaim,
});
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
} else {
None
}
}
}
unsafe fn get_table(os: &impl Os, parent: &mut [u64], index: usize) -> Option<&'static mut [u64]> {
unsafe {
if parent[index] == 0 {
let table = paging_allocate(os)?;
parent[index] = table.as_ptr() as u64 >> 2 | VALID;
Some(table)
} else {
Some(slice::from_raw_parts_mut(
(((parent[index] >> 10) & PHYS_MASK as u64) << 12) as *mut u64,
PAGE_ENTRIES,
))
}
}
}
-90
View File
@@ -1,90 +0,0 @@
use core::slice;
use super::*;
use crate::os::Os;
// Sv39 scheme
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_FFC0_0000_0000;
pub(crate) const SATP_BITS: usize = 8;
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
// Create L2
let l2 = paging_allocate(os)?;
{
// Create L1 for identity mapping
for l2_i in 0..8 {
let addr = l2_i as u64 * 0x4000_0000;
// Identity map 8 GiB using 1GB pages
l2[l2_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
// map phys into kernel VAS
l2[(PAGE_ENTRIES / 2) + l2_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
}
}
{
// Create L1 for kernel mapping
let l1 = paging_allocate(os)?;
// Link second to last L0 entry to L1
l2[510] = l1.as_ptr() as u64 >> 2 | VALID;
// Map kernel_size at kernel offset
let mut kernel_mapped = 0;
let mut l1_i = 0;
while kernel_mapped < kernel_size && l1_i < l1.len() {
let l0 = paging_allocate(os)?;
l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID;
l1_i += 1;
let mut l0_i = 0;
while kernel_mapped < kernel_size && l0_i < l2.len() {
let addr = kernel_phys + kernel_mapped;
l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
l0_i += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
assert!(kernel_mapped >= kernel_size);
}
Some(l2.as_ptr() as usize)
}
}
pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option<u64> {
unsafe {
if phys + size <= 0x2_0000_0000 {
return Some(phys + PHYS_OFFSET);
}
let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) + PAGE_ENTRIES / 2;
let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK;
assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0);
let l1 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
// Map framebuffer_size at framebuffer offset
let mut mapped = 0;
while mapped < size && l1_i < l1.len() {
let l0 = get_table(os, l1, l1_i)?;
while mapped < size && l0_i < l0.len() {
let addr = phys + mapped;
assert_eq!(l0[l0_i], 0);
l0[l0_i] = (addr >> 2) | RWX | VALID | ACCESSED | DIRTY;
mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT); // Map with 2mb mega-pages
l0_i += 1;
}
l1_i += 1;
l0_i = 0;
}
assert!(mapped >= size);
Some(phys + PHYS_OFFSET)
}
}
-108
View File
@@ -1,108 +0,0 @@
use core::slice;
use super::*;
use crate::os::Os;
// Sv48 scheme
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
pub(crate) const SATP_BITS: usize = 9;
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
// Create L3
let l3 = paging_allocate(os)?;
{
// Create L2 for identity mapping
let l2 = paging_allocate(os)?;
// Map L2 into beginning of userspace and kernelspace
l3[0] = (l2.as_ptr() as u64 >> 2) | VALID;
l3[PAGE_ENTRIES / 2] = (l2.as_ptr() as u64 >> 2) | VALID;
// Identity map 8 GiB using 1GB pages
for l2_i in 0..8 {
let addr = l2_i as u64 * 0x4000_0000;
l2[l2_i] = addr >> 2 | RWX | VALID;
}
}
{
// Create L2 for kernel mapping
let l2 = paging_allocate(os)?;
// Link last L3 entry to L2
l3[511] = l2.as_ptr() as u64 >> 2 | VALID;
// Create L1 for kernel mapping
let l1 = paging_allocate(os)?;
// Link last L1 entry to L2
l2[510] = l1.as_ptr() as u64 >> 2 | VALID;
// Map kernel_size at kernel offset
let mut kernel_mapped = 0;
let mut l1_i = 0;
while kernel_mapped < kernel_size && l1_i < l1.len() {
let l0 = paging_allocate(os)?;
l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID;
l1_i += 1;
let mut l0_i = 0;
while kernel_mapped < kernel_size && l0_i < l2.len() {
let addr = kernel_phys + kernel_mapped;
l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
l0_i += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
assert!(kernel_mapped >= kernel_size);
}
Some(l3.as_ptr() as usize)
}
}
pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option<u64> {
unsafe {
if phys + size <= 0x2_0000_0000 {
return Some(phys + PHYS_OFFSET);
}
let mut l2_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) + PAGE_ENTRIES / 2;
let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK;
let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK;
assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0);
let l2 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
// Map framebuffer_size at framebuffer offset
let mut mapped = 0;
while mapped < size && l2_i < l2.len() {
let l1 = get_table(os, l2, l2_i)?;
while mapped < size && l1_i < l1.len() {
let l0 = get_table(os, l1, l1_i)?;
while mapped < size && l0_i < l0.len() {
let addr = phys + mapped;
assert_eq!(l0[l0_i], 0);
l0[l0_i] = (addr >> 2) | RWX | VALID;
mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT);
l0_i += 1;
}
l1_i += 1;
l0_i = 0;
}
l2_i += 1;
l1_i = 0;
}
assert!(mapped >= size);
Some(phys + PHYS_OFFSET)
}
}
-124
View File
@@ -1,124 +0,0 @@
use core::slice;
use super::*;
use crate::os::Os;
// Sv57 scheme
pub(crate) const PHYS_OFFSET: u64 = 0xFF00_0000_0000_0000;
pub(crate) const SATP_BIT: usize = 10;
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
// Create L4
let l4 = paging_allocate(os)?;
{
// Create L3
let l3 = paging_allocate(os)?;
// Map L3 into beginning of userspace and kernelspace
l4[0] = (l3.as_ptr() as u64 >> 2) | VALID;
l4[PAGE_ENTRIES / 2] = (l3.as_ptr() as u64 >> 2) | VALID;
// Create L2 for identity mapping
let l2 = paging_allocate(os)?;
// Identity map 8 GiB using 1GB pages
for l2_i in 0..8 {
let addr = l2_i as u64 * 0x4000_0000;
l2[l2_i] = addr >> 2 | RWX | VALID;
}
}
{
// Create L3
let l3 = paging_allocate(os)?;
// Link last L4 entry to L3
l4[511] = l3.as_ptr() as u64 >> 2 | VALID;
// Create L2 for kernel mapping
let l2 = paging_allocate(os)?;
// Link last L3 entry to L2
l3[511] = l2.as_ptr() as u64 >> 2 | VALID;
// Create L1 for kernel mapping
let l1 = paging_allocate(os)?;
// Link last L1 entry to L2
l2[510] = l1.as_ptr() as u64 >> 2 | VALID;
// Map kernel_size at kernel offset
let mut kernel_mapped = 0;
let mut l1_i = 0;
while kernel_mapped < kernel_size && l1_i < l1.len() {
let l0 = paging_allocate(os)?;
l1[l1_i] = l0.as_ptr() as u64 >> 2 | VALID;
l1_i += 1;
let mut l0_i = 0;
while kernel_mapped < kernel_size && l0_i < l2.len() {
let addr = kernel_phys + kernel_mapped;
l0[l0_i] = addr >> 2 | RWX | VALID | ACCESSED | DIRTY;
l0_i += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
assert!(kernel_mapped >= kernel_size);
}
Some(l4.as_ptr() as usize)
}
}
pub unsafe fn paging_physmem(os: &impl Os, page_phys: usize, phys: u64, size: u64) -> Option<u64> {
unsafe {
if phys + size <= 0x2_0000_0000 {
return Some(phys + PHYS_OFFSET);
}
let mut l3_i = (phys as usize >> (PAGE_SHIFT + 4 * TABLE_SHIFT)) + PAGE_ENTRIES / 2;
let mut l2_i = (phys as usize >> (PAGE_SHIFT + 3 * TABLE_SHIFT)) & TABLE_MASK;
let mut l1_i = (phys as usize >> (PAGE_SHIFT + 2 * TABLE_SHIFT)) & TABLE_MASK;
let mut l0_i = (phys as usize >> (PAGE_SHIFT + TABLE_SHIFT)) & TABLE_MASK;
assert_eq!(phys & ((1 << (PAGE_SHIFT + TABLE_SHIFT)) - 1), 0);
let l3 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
// Map framebuffer_size at framebuffer offset
let mut mapped = 0;
while mapped < size && l3_i < l3.len() {
let l2 = get_table(os, l3, l3_i)?;
while mapped < size && l2_i < l2.len() {
let l1 = get_table(os, l2, l2_i)?;
while mapped < size && l1_i < l1.len() {
let l0 = get_table(os, l1, l1_i)?;
while mapped < size && l0_i < l0.len() {
let addr = phys + mapped;
assert_eq!(l0[l0_i], 0);
l0[l0_i] = (addr >> 2) | RWX | VALID;
mapped += 1 << (PAGE_SHIFT + TABLE_SHIFT);
l0_i += 1;
}
l1_i += 1;
l0_i = 0;
}
l2_i += 1;
l1_i = 0;
}
l3_i += 1;
l2_i += 0;
}
assert!(mapped >= size);
Some(phys + PHYS_OFFSET)
}
}
-29
View File
@@ -1,29 +0,0 @@
use crate::os::Os;
pub(crate) mod x32;
pub(crate) mod x64;
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
if crate::KERNEL_64BIT {
x64::paging_create(os, kernel_phys, kernel_size)
} else {
x32::paging_create(os, kernel_phys, kernel_size)
}
}
}
pub unsafe fn paging_framebuffer(
os: &impl Os,
page_phys: usize,
framebuffer_phys: u64,
framebuffer_size: u64,
) -> Option<u64> {
unsafe {
if crate::KERNEL_64BIT {
x64::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size)
} else {
x32::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size)
}
}
}
-88
View File
@@ -1,88 +0,0 @@
use crate::area_add;
use crate::os::{Os, OsMemoryEntry, OsMemoryKind};
use core::slice;
const PAGE_ENTRIES: usize = 1024;
const PAGE_SIZE: usize = 4096;
pub(crate) const PHYS_OFFSET: u32 = 0x8000_0000;
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u32]> {
unsafe {
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
if !ptr.is_null() {
area_add(OsMemoryEntry {
base: ptr as u64,
size: PAGE_SIZE as u64,
kind: OsMemoryKind::Reclaim,
});
Some(slice::from_raw_parts_mut(ptr as *mut u32, PAGE_ENTRIES))
} else {
None
}
}
}
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
let pd = paging_allocate(os)?;
//Identity map 1 GiB using 4 MiB pages, also map at PHYS_OFFSET
for pd_i in 0..256 {
let addr = pd_i as u32 * 0x40_0000;
pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1;
pd[pd_i + 512] = addr | 1 << 7 | 1 << 1 | 1;
}
// Map kernel_size at kernel offset
let mut kernel_mapped = 0;
let mut pd_i = 0xC000_0000 / 0x40_0000;
while kernel_mapped < kernel_size && pd_i < pd.len() {
let pt = paging_allocate(os)?;
pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1;
pd_i += 1;
let mut pt_i = 0;
while kernel_mapped < kernel_size && pt_i < pt.len() {
let addr = kernel_phys + kernel_mapped;
pt[pt_i] = addr as u32 | 1 << 1 | 1;
pt_i += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
assert!(kernel_mapped >= kernel_size);
Some(pd.as_ptr() as usize)
}
}
pub unsafe fn paging_framebuffer(
os: &impl Os,
page_phys: usize,
framebuffer_phys: u64,
framebuffer_size: u64,
) -> Option<u64> {
unsafe {
let framebuffer_virt = 0xD000_0000; // 256 MiB after kernel mapping, but before heap mapping
let pd = slice::from_raw_parts_mut(page_phys as *mut u32, PAGE_ENTRIES);
// Map framebuffer_size at framebuffer offset
let mut framebuffer_mapped = 0;
let mut pd_i = framebuffer_virt / 0x40_0000;
while framebuffer_mapped < framebuffer_size && pd_i < pd.len() {
let pt = paging_allocate(os)?;
pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1;
pd_i += 1;
let mut pt_i = 0;
while framebuffer_mapped < framebuffer_size && pt_i < pt.len() {
let addr = framebuffer_phys + framebuffer_mapped;
pt[pt_i] = addr as u32 | 1 << 1 | 1;
pt_i += 1;
framebuffer_mapped += PAGE_SIZE as u64;
}
}
assert!(framebuffer_mapped >= framebuffer_size);
Some(framebuffer_virt as u64)
}
}
-148
View File
@@ -1,148 +0,0 @@
use core::slice;
use crate::area_add;
use crate::os::{Os, OsMemoryEntry, OsMemoryKind};
const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
const PAGE_ENTRIES: usize = 512;
const PAGE_SIZE: usize = 4096;
pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000;
unsafe fn paging_allocate(os: &impl Os) -> Option<&'static mut [u64]> {
unsafe {
let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE);
if !ptr.is_null() {
area_add(OsMemoryEntry {
base: ptr as u64,
size: PAGE_SIZE as u64,
kind: OsMemoryKind::Reclaim,
});
Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES))
} else {
None
}
}
}
const PRESENT: u64 = 1;
const WRITABLE: u64 = 1 << 1;
const LARGE: u64 = 1 << 7;
pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option<usize> {
unsafe {
// Create PML4
let pml4 = paging_allocate(os)?;
{
// Create PDP for identity mapping
let pdp = paging_allocate(os)?;
// Link first user and first kernel PML4 entry to PDP
pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
// Identity map 8 GiB using 2 MiB pages
for pdp_i in 0..8 {
let pd = paging_allocate(os)?;
pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
for pd_i in 0..pd.len() {
let addr = pdp_i as u64 * 0x4000_0000 + pd_i as u64 * 0x20_0000;
pd[pd_i] = addr | LARGE | WRITABLE | PRESENT;
}
}
}
{
// Create PDP (spanning 512 GiB) for kernel mapping
let pdp = paging_allocate(os)?;
// Link last PML4 entry to PDP
pml4[511] = pdp.as_ptr() as u64 | WRITABLE | PRESENT;
// Create PD (spanning 1 GiB) for kernel mapping.
let pd = paging_allocate(os)?;
// The kernel is mapped at -2^31, i.e. 0xFFFF_FFFF_8000_0000. Since a PD is 1 GiB, link
// the second last PDP entry to PD.
pdp[510] = pd.as_ptr() as u64 | WRITABLE | PRESENT;
// Map kernel_size bytes to kernel offset, i.e. to the start of the PD.
let mut kernel_mapped = 0;
let mut pd_idx = 0;
while kernel_mapped < kernel_size && pd_idx < pd.len() {
let pt = paging_allocate(os)?;
pd[pd_idx] = pt.as_ptr() as u64 | WRITABLE | PRESENT;
pd_idx += 1;
let mut pt_idx = 0;
while kernel_mapped < kernel_size && pt_idx < pt.len() {
let addr = kernel_phys + kernel_mapped;
pt[pt_idx] = addr | WRITABLE | PRESENT;
pt_idx += 1;
kernel_mapped += PAGE_SIZE as u64;
}
}
assert!(kernel_mapped >= kernel_size);
}
Some(pml4.as_ptr() as usize)
}
}
pub unsafe fn paging_framebuffer(
os: &impl Os,
page_phys: usize,
framebuffer_phys: u64,
framebuffer_size: u64,
) -> Option<u64> {
unsafe {
//TODO: smarter test for framebuffer already mapped
if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 {
return Some(framebuffer_phys + PHYS_OFFSET);
}
let pml4_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize;
let mut pdp_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize;
let mut pd_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize;
assert_eq!(framebuffer_phys % 0x20_0000, 0);
let pml4 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES);
// Create PDP for framebuffer mapping
let pdp = if pml4[pml4_i] == 0 {
let pdp = paging_allocate(os)?;
pml4[pml4_i] = pdp.as_ptr() as u64 | 1 << 1 | 1;
pdp
} else {
slice::from_raw_parts_mut(
(pml4[pml4_i] & ENTRY_ADDRESS_MASK) as *mut u64,
PAGE_ENTRIES,
)
};
// Map framebuffer_size at framebuffer offset
let mut framebuffer_mapped = 0;
while framebuffer_mapped < framebuffer_size && pdp_i < pdp.len() {
let pd = paging_allocate(os)?;
assert_eq!(pdp[pdp_i], 0);
pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1;
while framebuffer_mapped < framebuffer_size && pd_i < pd.len() {
let addr = framebuffer_phys + framebuffer_mapped;
assert_eq!(pd[pd_i], 0);
pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1;
framebuffer_mapped += 0x20_0000;
pd_i += 1;
}
pdp_i += 1;
pd_i = 0;
}
assert!(framebuffer_mapped >= framebuffer_size);
Some(framebuffer_phys + PHYS_OFFSET)
}
}
+134
View File
@@ -0,0 +1,134 @@
extern crate arg_parser;
extern crate redox_installer;
extern crate serde;
extern crate toml;
use std::path::Path;
use std::{env, fs, process};
use arg_parser::ArgParser;
use redox_installer::{Config, PackageConfig};
const HELP_STR: &str = r#"
redox_installer - Redox Installer.
Refer to link below for filesystem config reference:
https://doc.redox-os.org/book/configuration-settings.html
Using redox_installer as an installer:
redox_installer <diskpath.img> [--config=file.toml] [--write-bootloader=file.img] [--live] [--no-mount] [--skip-partition]
<diskpath.img> Disk file to write
--config Path to filesystem config TOML
--write-bootloader Path to write UEFI bootloader to in addition to the embedded ESP
--skip-partition Skip writing GPT partition tables
Use this only if you plan to use other partition tool
--live Use bootloader configured for live disk
--no-mount Use RedoxFS AR instead of FUSE to write files
--cookbook Use local Redox OS build system rather than downloading packages
Using redox_installer as a configuration parser:
redox_installer --config=file.toml [--list-packages|--filesystem-size|--output-config path]
--list-packages List packages will be installed
--filesystem-size Output filesystem size in MB
--output-config Path to write the parsed config as another TOML
"#;
fn main() {
let mut parser = ArgParser::new(4)
.add_opt("b", "cookbook")
.add_opt("c", "config")
.add_opt("o", "output-config")
.add_opt("", "write-bootloader")
.add_flag(&["skip-partition"])
.add_flag(&["filesystem-size"])
.add_flag(&["r", "repo-binary"]) // TODO: Remove
.add_flag(&["l", "list-packages"])
.add_flag(&["live"])
.add_flag(&["no-mount"]);
parser.parse(env::args());
let skip_partition = parser.found("skip-partition");
let mut config = if let Some(path) = parser.get_opt("config") {
match Config::from_file(Path::new(&path)) {
Ok(config) => config,
Err(err) => {
eprintln!("installer: {err}");
process::exit(1);
}
}
} else {
redox_installer::Config::default()
};
// Get toml of merged config
let merged_toml = toml::to_string_pretty(&config).unwrap();
// Just output merged config and exit
if let Some(path) = parser.get_opt("output-config") {
fs::write(path, merged_toml).unwrap();
return;
}
// Add filesystem.toml to config
config.files.push(redox_installer::FileConfig {
path: "filesystem.toml".to_string(),
data: merged_toml,
..Default::default()
});
if skip_partition {
config.general.skip_partitions = Some(true);
}
if parser.found("filesystem-size") {
println!("{}", config.general.filesystem_size.unwrap_or(0));
} else if parser.found("list-packages") {
// List the packages that should be fetched or built by the cookbook
for (packagename, package) in &config.packages {
match package {
PackageConfig::Build(rule) if rule == "ignore" => {
// skip this package
}
_ => {
println!("{}", packagename);
}
}
}
} else {
let cookbook = if let Some(path) = parser.get_opt("cookbook") {
if !Path::new(&path).is_dir() {
eprintln!("installer: {}: cookbook not found", path);
process::exit(1);
}
Some(path)
} else {
None
};
if cookbook.is_some() {
config.general.cookbook = cookbook;
}
if parser.found("live") {
config.general.live_disk = Some(true);
}
if parser.found("no-mount") {
config.general.no_mount = Some(true);
}
let write_bootloader = parser.get_opt("write-bootloader");
if write_bootloader.is_some() {
config.general.write_bootloader = write_bootloader;
}
if let Some(path) = parser.args.first() {
if let Err(err) = redox_installer::install(config, path) {
eprintln!("installer: failed to install: {:?}", err);
process::exit(1);
}
} else {
eprint!("{}", HELP_STR);
process::exit(1);
}
}
}
+392
View File
@@ -0,0 +1,392 @@
use anyhow::{anyhow, bail, Result};
use pkgar::{ext::EntryExt, PackageHead};
use pkgar_core::PackageSrc;
use pkgar_keys::PublicKeyFile;
use redox_installer::{try_fast_install, with_redoxfs_mount, with_whole_disk, Config, DiskOption};
use std::{
ffi::OsStr,
fs,
io::{self, Read, Write},
os::unix::fs::{symlink, MetadataExt, OpenOptionsExt},
path::{Path, PathBuf},
process,
};
// TODO: This is not the TUI a regular user would expect it does
// 1. Linux: Implement disk listing, use "dd" to write into whole disk
// 2. Allow partitioning to allow dual boot, possibly an integration with systemd-boot/grub
// 3. Prompt everything (disk password, users, preconfigured packages, import from existing img)
#[cfg(not(target_os = "redox"))]
fn disk_paths(_paths: &mut Vec<(PathBuf, u64)>) {}
#[cfg(target_os = "redox")]
fn disk_paths(paths: &mut Vec<(PathBuf, u64)>) {
let mut schemes = Vec::new();
match fs::read_dir("/scheme") {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(file_name) = entry.file_name().into_string() {
if file_name.starts_with("disk") {
schemes.push(entry.path());
}
}
}
}
}
Err(err) => {
eprintln!("redox_installer_tui: failed to list schemes: {}", err);
}
}
for scheme in schemes {
if scheme.is_dir() {
match fs::read_dir(&scheme) {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(file_name) = entry.file_name().into_string() {
if file_name.contains('p') {
// Skip partitions
continue;
}
if let Ok(metadata) = entry.metadata() {
let size = metadata.len();
if size > 0 {
paths.push((entry.path(), size));
}
}
}
}
}
}
Err(err) => {
eprintln!(
"redox_installer_tui: failed to list '{}': {}",
scheme.display(),
err
);
}
}
}
}
}
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const GIB: u64 = 1024 * MIB;
const TIB: u64 = 1024 * GIB;
fn format_size(size: u64) -> String {
if size >= 4 * TIB {
format!("{:.1} TiB", size as f64 / TIB as f64)
} else if size >= GIB {
format!("{:.1} GiB", size as f64 / GIB as f64)
} else if size >= MIB {
format!("{:.1} MiB", size as f64 / MIB as f64)
} else if size >= KIB {
format!("{:.1} KiB", size as f64 / KIB as f64)
} else {
format!("{} B", size)
}
}
fn copy_file(src: &Path, dest: &Path, buf: &mut [u8]) -> Result<()> {
if let Some(parent) = dest.parent() {
// Parent may be a symlink
if !parent.is_symlink() {
match fs::create_dir_all(&parent) {
Ok(()) => (),
Err(err) => {
bail!("failed to create directory {}: {}", parent.display(), err);
}
}
}
}
let metadata = match fs::symlink_metadata(&src) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to read metadata of {}: {}", src.display(), err);
}
};
if metadata.file_type().is_symlink() {
let real_src = match fs::read_link(&src) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to read link {}: {}", src.display(), err);
}
};
match symlink(&real_src, &dest) {
Ok(()) => (),
Err(err) => {
bail!(
"failed to copy link {} ({}) to {}: {}",
src.display(),
real_src.display(),
dest.display(),
err
);
}
}
} else {
let mut src_file = match fs::File::open(&src) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to open file {}: {}", src.display(), err);
}
};
let mut dest_file = match fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(metadata.mode())
.open(&dest)
{
Ok(ok) => ok,
Err(err) => {
bail!("failed to create file {}: {}", dest.display(), err);
}
};
loop {
let count = match src_file.read(buf) {
Ok(ok) => ok,
Err(err) => {
bail!("failed to read file {}: {}", src.display(), err);
}
};
if count == 0 {
break;
}
match dest_file.write_all(&buf[..count]) {
Ok(()) => (),
Err(err) => {
bail!("failed to write file {}: {}", dest.display(), err);
}
}
}
}
Ok(())
}
fn package_files(
root_path: &Path,
config: &mut Config,
files: &mut Vec<String>,
) -> Result<(), anyhow::Error> {
//TODO: Remove packages from config where all files are located (and have valid shasum?)
config.packages.clear();
let pkey_path = "pkg/id_ed25519.pub.toml";
let pkey = PublicKeyFile::open(&root_path.join(pkey_path))?.pkey;
files.push(pkey_path.to_string());
for item_res in fs::read_dir(&root_path.join("pkg"))? {
let item = item_res?;
let pkg_path = item.path();
if pkg_path.extension() == Some(OsStr::new("pkgar_head")) {
let mut pkg = PackageHead::new(&pkg_path, &root_path, &pkey)?;
for entry in pkg.read_entries()? {
files.push(entry.check_path()?.to_str().unwrap().to_string());
}
files.push(
pkg_path
.strip_prefix(root_path)
.unwrap()
.to_str()
.unwrap()
.to_string(),
);
}
}
Ok(())
}
fn choose_disk() -> PathBuf {
let mut paths = Vec::new();
disk_paths(&mut paths);
loop {
for (i, (path, size)) in paths.iter().enumerate() {
eprintln!(
"\x1B[1m{}\x1B[0m: {}: {}",
i + 1,
path.display(),
format_size(*size)
);
}
if paths.is_empty() {
eprintln!("redox_installer_tui: no RedoxFS partition found");
eprintln!("redox_installer_tui: this tool is used to overwrite unmounted RedoxFS disk in Redox OS");
process::exit(1);
} else {
eprint!("Select a drive from 1 to {}: ", paths.len());
let mut line = String::new();
match io::stdin().read_line(&mut line) {
Ok(0) => {
eprintln!("redox_installer_tui: failed to read line: end of input");
process::exit(1);
}
Ok(_) => (),
Err(err) => {
eprintln!("redox_installer_tui: failed to read line: {}", err);
process::exit(1);
}
}
match line.trim().parse::<usize>() {
Ok(i) => {
if i >= 1 && i <= paths.len() {
break paths[i - 1].0.clone();
} else {
eprintln!("{} not from 1 to {}", i, paths.len());
}
}
Err(err) => {
eprintln!("invalid input: {}", err);
}
}
}
}
}
fn main() {
let root_path = Path::new("/");
let disk_path = choose_disk();
let Ok(password_opt) = redox_installer::prompt_password(
"redox_installer_tui: redoxfs password (empty for none)",
"redox_installer_tui: confirm password",
) else {
process::exit(1);
};
let instant = std::time::Instant::now();
let bootloader_bios = {
let path = root_path.join("usr/lib/boot/bootloader.bios");
if path.exists() {
match fs::read(&path) {
Ok(ok) => ok,
Err(err) => {
eprintln!(
"redox_installer_tui: {}: failed to read: {}",
path.display(),
err
);
process::exit(1);
}
}
} else {
Vec::new()
}
};
let bootloader_efi = {
let path = root_path.join("usr/lib/boot/bootloader.efi");
if path.exists() {
match fs::read(&path) {
Ok(ok) => ok,
Err(err) => {
eprintln!(
"redox_installer_tui: {}: failed to read: {}",
path.display(),
err
);
process::exit(1);
}
}
} else {
Vec::new()
}
};
let disk_option = DiskOption {
bootloader_bios: &bootloader_bios,
bootloader_efi: &bootloader_efi,
password_opt: password_opt.as_ref().map(|x| x.as_bytes()),
efi_partition_size: None,
skip_partitions: false, // TODO?
};
let res = with_whole_disk(&disk_path, &disk_option, |mut fs| {
// Fast install method via filesystem clone
let mut last_percent = 0;
if try_fast_install(&mut fs, move |used, used_old| {
let percent = (used * 100) / used_old;
if percent != last_percent {
eprint!(
"\r{}%: {} MB/{} MB",
percent,
used / 1000 / 1000,
used_old / 1000 / 1000
);
last_percent = percent;
}
})? {
eprintln!("\rfinished installing using fast mode");
return Ok(());
}
// Slow install method via file copy
with_redoxfs_mount(fs, None, |mount_path| {
let mut config: Config = Config::from_file(&root_path.join("filesystem.toml"))?;
// Copy filesystem.toml, which is not packaged
let mut files = vec!["filesystem.toml".to_string()];
// Copy files from locally installed packages
package_files(&root_path, &mut config, &mut files)
// TODO: implement Error trait
.map_err(|err| anyhow!("failed to read package files: {err}"))?;
// Perform config install (after packages have been converted to files)
eprintln!("configuring system");
let cookbook: Option<&'static str> = None;
redox_installer::install_dir(config, mount_path, cookbook)
.map_err(|err| io::Error::other(err))?;
// Sort and remove duplicates
files.sort();
files.dedup();
// Install files
let mut buf = vec![0; 4 * MIB as usize];
for (i, name) in files.iter().enumerate() {
eprintln!("copy {} [{}/{}]", name, i, files.len());
let src = root_path.join(name);
let dest = mount_path.join(name);
copy_file(&src, &dest, &mut buf)?;
}
eprintln!("finished installing, unmounting filesystem");
Ok(())
})
});
match res {
Ok(()) => {
eprintln!(
"redox_installer_tui: installed successfully in {:?}",
instant.elapsed()
);
process::exit(0);
}
Err(err) => {
eprintln!("redox_installer_tui: failed to install: {:?}", err);
process::exit(1);
}
}
}
+83
View File
@@ -0,0 +1,83 @@
use std::fmt::Display;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct FileConfig {
pub path: String,
pub data: String,
#[serde(default)]
pub symlink: bool,
#[serde(default)]
pub directory: bool,
pub mode: Option<u32>,
pub uid: Option<u32>,
pub gid: Option<u32>,
#[serde(default)]
pub recursive_chown: bool,
#[serde(default)]
pub postinstall: bool,
}
impl FileConfig {
pub fn new_file(path: String, data: String) -> FileConfig {
FileConfig {
path,
data,
..Default::default()
}
}
pub fn new_directory(path: String) -> FileConfig {
FileConfig {
path,
data: String::new(),
directory: true,
..Default::default()
}
}
pub fn with_mod(&mut self, mode: u32, uid: u32, gid: u32) -> &mut FileConfig {
self.mode = Some(mode);
self.uid = Some(uid);
self.gid = Some(gid);
self
}
pub fn with_recursive_mod(&mut self, mode: u32, uid: u32, gid: u32) -> &mut FileConfig {
self.with_mod(mode, uid, gid);
self.recursive_chown = true;
self
}
}
impl Display for FileConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path)?;
if self.symlink {
write!(f, " -> {}", self.data)?;
} else if self.directory {
write!(f, " type=dir")?;
if self.recursive_chown {
write!(f, " chown=yes")?;
}
} else {
write!(
f,
" size={}B",
arg_parser::to_human_readable_string(self.data.len() as u64)
)?;
if self.postinstall {
write!(f, "!")?;
}
}
if let Some(uid) = self.uid {
write!(f, " uid={}", uid)?;
}
if let Some(uid) = self.uid {
write!(f, " gid={}", uid)?;
}
if let Some(mode) = self.mode {
write!(f, " mode={:3o}", mode)?;
}
Ok(())
}
}
+88
View File
@@ -0,0 +1,88 @@
use anyhow::{Context, Result};
use libc::{gid_t, uid_t};
use std::ffi::{CString, OsStr};
use std::fs::{self, File};
use std::io::{Error, Write};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{symlink, PermissionsExt};
use std::path::Path;
fn chown<P: AsRef<Path>>(path: P, uid: uid_t, gid: gid_t, recursive: bool) -> Result<()> {
let path = path.as_ref();
let c_path = CString::new(path.as_os_str().as_bytes()).unwrap();
if unsafe { libc::chown(c_path.as_ptr(), uid, gid) } != 0 {
return Err(Error::last_os_error().into());
}
if recursive && path.is_dir() {
for entry_res in fs::read_dir(path)? {
let entry = entry_res?;
chown(entry.path(), uid, gid, recursive)?;
}
}
Ok(())
}
// TODO: Rewrite impls
impl crate::FileConfig {
pub(crate) fn create<P: AsRef<Path>>(&self, prefix: P) -> Result<()> {
let path = self.path.trim_start_matches('/');
let target_file = prefix.as_ref().join(path);
if self.directory {
println!("Create directory {}", target_file.display());
fs::create_dir_all(&target_file)
.with_context(|| format!("failed to create directory {}", target_file.display()))?;
self.apply_perms(&target_file)?;
return Ok(());
} else if let Some(parent) = target_file.parent() {
println!("Create file parent {}", parent.display());
fs::create_dir_all(parent)
.with_context(|| format!("failed to create file parent {}", parent.display()))?;
}
if self.symlink {
println!("Create symlink {} to {}", target_file.display(), self.data);
if target_file.is_symlink() {
fs::remove_file(&target_file).with_context(|| {
format!("failed to remove old symlink {}", target_file.display())
})?;
}
symlink(&OsStr::new(&self.data), &target_file).with_context(|| {
format!(
"failed to create symlink {} to {}",
target_file.display(),
self.data
)
})?;
Ok(())
} else {
println!("Create file {}", target_file.display());
let mut file = File::create(&target_file)
.with_context(|| format!("failed to create file {}", target_file.display()))?;
file.write_all(self.data.as_bytes())?;
self.apply_perms(target_file)
}
}
fn apply_perms<P: AsRef<Path>>(&self, target: P) -> Result<()> {
let path = target.as_ref();
let mode = self
.mode
.unwrap_or_else(|| if self.directory { 0o0755 } else { 0o0644 });
let uid = self.uid.unwrap_or(!0);
let gid = self.gid.unwrap_or(!0);
// chmod
fs::set_permissions(path, fs::Permissions::from_mode(mode))
.with_context(|| format!("failed to set permissions on {}", path.display()))?;
// chown
chown(path, uid, gid, self.recursive_chown)
.with_context(|| format!("failed to chown {}", path.display()))
}
}
+42
View File
@@ -0,0 +1,42 @@
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct GeneralConfig {
/// Specify a path where cookbook exists, all packages will be installed locally
pub cookbook: Option<String>,
/// Allow prompts for missing information such as user password
pub prompt: Option<bool>,
/// Total filesystem size in MB
pub filesystem_size: Option<u32>,
/// EFI partition size in MB, default to 2MB
pub efi_partition_size: Option<u32>,
/// Skip disk partitioning, assume whole disk is a partition
pub skip_partitions: Option<bool>,
/// Set a plain text password to encrypt the disk
pub encrypt_disk: Option<String>,
/// Use live disk for bootloader config, default is false
pub live_disk: Option<bool>,
/// If set, write bootloader disk into this path
pub write_bootloader: Option<String>,
/// Use AR to write files instead of FUSE-based mount
/// (bypasses FUSE, but slower and requires namespaced context such as "podman unshare")
pub no_mount: Option<bool>,
}
impl GeneralConfig {
/// Merge two config, "other" is more dominant
pub(super) fn merge(&mut self, other: GeneralConfig) {
if let Some(cookbook) = other.cookbook {
self.cookbook = Some(cookbook);
}
self.filesystem_size = other.filesystem_size.or(self.filesystem_size);
self.efi_partition_size = other.efi_partition_size.or(self.efi_partition_size);
self.skip_partitions = other.skip_partitions.or(self.skip_partitions);
if let Some(encrypt_disk) = other.encrypt_disk {
self.encrypt_disk = Some(encrypt_disk);
}
self.live_disk = other.live_disk.or(self.live_disk);
if let Some(write_bootloader) = other.write_bootloader {
self.write_bootloader = Some(write_bootloader);
}
self.no_mount = other.no_mount.or(self.no_mount);
}
}
+134
View File
@@ -0,0 +1,134 @@
use std::collections::BTreeMap;
use std::fmt::Display;
use std::fs;
use std::mem;
use std::path::{Path, PathBuf};
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use crate::PackageConfig;
pub mod file;
#[cfg(feature = "installer")]
pub mod file_impl;
pub mod general;
pub mod package;
pub mod user;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Config {
#[serde(default)]
pub include: Vec<PathBuf>,
#[serde(default)]
pub general: general::GeneralConfig,
#[serde(default)]
pub packages: BTreeMap<String, package::PackageConfig>,
#[serde(default)]
pub files: Vec<file::FileConfig>,
#[serde(default)]
pub users: BTreeMap<String, user::UserConfig>,
#[serde(default)]
pub groups: BTreeMap<String, user::GroupConfig>,
}
impl Config {
/// Load installer config from a TOML path
pub fn from_file(path: &Path) -> Result<Self> {
let mut config: Config = match fs::read_to_string(&path) {
Ok(config_data) => match toml::from_str(&config_data) {
Ok(config) => config,
Err(err) => {
bail!("failed to decode '{}': {}", path.display(), err);
}
},
Err(err) => {
bail!("failed to read '{}': {}", path.display(), err);
}
};
let config_dir = path.parent().unwrap();
let mut configs = mem::take(&mut config.include)
.into_iter()
.map(|path| {
Config::from_file(&config_dir.join(&path))
.with_context(|| format!("Importing from {}", path.display()))
})
.collect::<Result<Vec<Config>>>()?;
configs.push(config); // Put ourself last to ensure that it overwrites anything else.
config = configs.remove(0);
for other_config in configs {
config.merge(other_config);
}
Ok(config)
}
/// Load hardcoded install config to fetch bootloaders
pub fn bootloader_config() -> Self {
let mut bootloader_config = Config::default();
// TODO: This is unused
bootloader_config.files.push(file::FileConfig {
path: "/etc/pkg.d/50_redox".to_string(),
data: "https://static.redox-os.org/pkg".to_string(),
..Default::default()
});
bootloader_config
.packages
.insert("bootloader".to_string(), PackageConfig::default());
bootloader_config
}
pub fn merge(&mut self, other: Config) {
assert!(self.include.is_empty());
assert!(other.include.is_empty());
let Config {
include: _,
general: other_general,
packages: other_packages,
files: other_files,
users: other_users,
groups: other_groups,
} = other;
self.general.merge(other_general);
for (package, package_config) in other_packages {
self.packages.insert(package, package_config);
}
self.files.extend(other_files);
for (user, user_config) in other_users {
self.users.insert(user, user_config);
}
for (group, group_config) in other_groups {
self.groups.insert(group, group_config);
}
}
}
impl Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "files:")?;
for file in &self.files {
writeln!(f, "- {}", file)?;
}
writeln!(f, "users:")?;
for (name, user) in &self.users {
writeln!(f, "- {}:{}", name, user)?;
}
write!(f, "packages: ")?;
for name in self.packages.keys() {
write!(f, " {}", name)?;
}
writeln!(f, "")?;
Ok(())
}
}
+19
View File
@@ -0,0 +1,19 @@
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum PackageConfig {
Empty,
Build(String),
// TODO: Sum type
Spec {
version: Option<String>,
git: Option<String>,
path: Option<String>,
},
}
impl Default for PackageConfig {
fn default() -> Self {
Self::Empty
}
}
+43
View File
@@ -0,0 +1,43 @@
use std::fmt::Display;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct UserConfig {
pub password: Option<String>,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub name: Option<String>,
pub home: Option<String>,
pub shell: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct GroupConfig {
pub gid: Option<u32>,
// FIXME move this to the UserConfig struct as extra_groups
pub members: Vec<String>,
}
impl Display for UserConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(uid) = &self.uid {
write!(f, " uid={}", uid)?;
}
if let Some(gid) = &self.gid {
write!(f, " gid={}", gid)?;
}
if let Some(name) = &self.name {
write!(f, " name={}", name)?;
}
if let Some(home) = &self.home {
write!(f, " home={}", home)?;
}
if let Some(shell) = &self.shell {
write!(f, " shell={}", shell)?;
}
if self.password.as_ref().is_some_and(|s| !s.is_empty()) {
write!(f, " password=yes")?;
}
Ok(())
}
}
+127
View File
@@ -0,0 +1,127 @@
use std::{
cmp,
convert::TryInto,
fs::{File, OpenOptions},
io::{Read, Result, Seek, SeekFrom, Write},
path::Path,
};
#[derive(Debug)]
pub struct DiskWrapper {
disk: File,
size: u64,
block: Box<[u8]>,
seek: u64,
}
enum Buffer<'a> {
Read(&'a mut [u8]),
Write(&'a [u8]),
}
impl DiskWrapper {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let disk = OpenOptions::new().read(true).write(true).open(path)?;
let metadata = disk.metadata()?;
let size = metadata.len();
// TODO: get real block size: disk_metadata.blksize() works on disks but not image files
let block_size = 512;
let block = vec![0u8; block_size].into_boxed_slice();
Ok(Self {
disk,
size,
block,
seek: 0,
})
}
pub fn block_size(&self) -> usize {
self.block.len()
}
pub fn size(&self) -> u64 {
self.size
}
fn io<'a>(&mut self, buf: &mut Buffer<'a>) -> Result<usize> {
let buf_len = match buf {
Buffer::Read(read) => read.len(),
Buffer::Write(write) => write.len(),
};
let block_len: u64 = self.block.len().try_into().unwrap();
// Do aligned I/O quickly
if self.seek % block_len == 0 && buf_len as u64 % block_len == 0 {
self.disk.seek(SeekFrom::Start(self.seek))?;
match buf {
Buffer::Read(read) => self.disk.read_exact(read)?,
Buffer::Write(write) => self.disk.write_all(write)?,
}
self.seek = self.seek.checked_add(buf_len.try_into().unwrap()).unwrap();
return Ok(buf_len);
}
let mut i = 0;
while i < buf_len {
let block = self.seek / block_len;
let offset: usize = (self.seek % block_len).try_into().unwrap();
let remaining = buf_len.checked_sub(i).unwrap();
let len = cmp::min(remaining, self.block.len().checked_sub(offset).unwrap());
self.disk
.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
self.disk.read_exact(&mut self.block)?;
match buf {
Buffer::Read(read) => {
read[i..i.checked_add(len).unwrap()]
.copy_from_slice(&self.block[offset..offset.checked_add(len).unwrap()]);
}
Buffer::Write(write) => {
self.block[offset..offset.checked_add(len).unwrap()]
.copy_from_slice(&write[i..i.checked_add(len).unwrap()]);
self.disk
.seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?;
self.disk.write_all(&mut self.block)?;
}
}
i = i.checked_add(len).unwrap();
self.seek = self.seek.checked_add(len.try_into().unwrap()).unwrap();
}
Ok(i)
}
}
impl Read for DiskWrapper {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.io(&mut Buffer::Read(buf))
}
}
impl Seek for DiskWrapper {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let current: i64 = self.seek.try_into().unwrap();
let end: i64 = self.size.try_into().unwrap();
self.seek = match pos {
SeekFrom::Start(offset) => cmp::min(self.size, offset),
SeekFrom::End(offset) => cmp::max(0, cmp::min(end, end.wrapping_add(offset))) as u64,
SeekFrom::Current(offset) => {
cmp::max(0, cmp::min(end, current.wrapping_add(offset))) as u64
}
};
Ok(self.seek)
}
}
impl Write for DiskWrapper {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.io(&mut Buffer::Write(buf))
}
fn flush(&mut self) -> Result<()> {
self.disk.flush()
}
}
-121
View File
@@ -1,121 +0,0 @@
use crate::os::{Os, OsKey};
fn edit_banner(os: &impl Os) {
os.clear_text();
println!("--- Redox Bootloader Environment Editor ---");
println!("ENTER twice to boot. UP/DOWN to edit lines.");
println!("-------------------------------------------");
}
pub fn edit_env(os: &impl Os, env_ptr: *mut u8, env_size: &mut usize, max_size: usize) {
edit_banner(os);
let env_slice = unsafe { core::slice::from_raw_parts_mut(env_ptr, max_size) };
// counting at line index
let mut cursor = 0xFFF;
// position at current line, not including LF
let mut cursor_start = 0;
let mut cursor_end = 0;
let original_size = *env_size;
loop {
os.set_text_position(0, 4);
let mut iline = 0;
for i in 0..*env_size {
let c = env_slice[i] as char;
if c == '\n' {
os.set_text_highlight(iline == cursor);
print!(" ");
os.set_text_highlight(false);
print!("\n");
iline += 1;
if iline == cursor {
cursor_start = i + 1;
}
} else {
print!("{}", c);
}
if iline == cursor {
cursor_end = i + 1;
}
}
if cursor > iline {
cursor = iline;
// update cursors, should never hang
continue;
}
os.set_text_highlight(iline == cursor);
print!(" ");
os.set_text_highlight(false);
match os.get_key() {
OsKey::Enter => {
if cursor_start == cursor_end {
// blank line to boot
break;
}
if *env_size < max_size - 1 {
if *env_size == max_size {
continue;
}
for i in (cursor_end..*env_size).rev() {
env_slice[i + 1] = env_slice[i];
}
env_slice[cursor_end] = b'\n';
*env_size += 1;
cursor += 1;
edit_banner(os);
}
}
OsKey::Backspace => {
if cursor_end == 0 || *env_size == 0 {
continue;
}
if cursor_start == cursor_end && iline > 0 {
iline -= 1;
}
for i in cursor_end..*env_size {
env_slice[i - 1] = env_slice[i];
}
*env_size -= 1;
edit_banner(os);
}
OsKey::Up => {
if cursor > 0 {
cursor -= 1;
}
}
OsKey::Down => {
cursor += 1;
}
OsKey::Char(c) => {
if *env_size == max_size {
continue;
}
for i in (cursor_end..*env_size).rev() {
env_slice[i + 1] = env_slice[i];
}
env_slice[cursor_end] = c as u8;
*env_size += 1;
}
_ => (),
}
}
if *env_size == 0 || env_slice[*env_size - 1] != b'\n' {
if *env_size < max_size {
env_slice[*env_size] = b'\n';
*env_size += 1;
}
}
if *env_size < original_size {
for i in (*env_size..original_size).rev() {
env_slice[i] = 0;
}
}
println!("\nBooting...");
}
+855
View File
@@ -0,0 +1,855 @@
use anyhow::Context;
use anyhow::{bail, Result};
use pkg::Library;
use rand::{rngs::OsRng, TryRngCore};
use redoxfs::{unmount_path, Disk, DiskIo, FileSystem, BLOCK_SIZE};
use termion::input::TermRead;
use crate::config::file::FileConfig;
use crate::config::package::PackageConfig;
use crate::config::Config;
use crate::disk_wrapper::DiskWrapper;
use std::{
cell::RefCell,
collections::BTreeMap,
env, fs,
io::{self, Seek, SeekFrom, Write},
path::{Path, PathBuf},
process,
rc::Rc,
sync::mpsc::channel,
thread,
time::{SystemTime, UNIX_EPOCH},
};
pub struct DiskOption<'a> {
pub bootloader_bios: &'a [u8],
pub bootloader_efi: &'a [u8],
pub password_opt: Option<&'a [u8]>,
pub efi_partition_size: Option<u32>, //MiB
pub skip_partitions: bool,
}
fn get_target() -> String {
// TODO: Configurable from filesystem config?
env::var("TARGET").unwrap_or(
option_env!("TARGET").map_or("x86_64-unknown-redox".to_string(), |x| x.to_string()),
)
}
/// Converts a password to a serialized argon2rs hash, understandable
/// by redox_users. If the password is blank, the hash is blank.
fn hash_password(password: &str) -> Result<String> {
if !password.is_empty() {
let salt = format!("{:X}", OsRng.try_next_u64()?);
let config = argon2::Config::default();
let hash = argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &config)?;
Ok(hash)
} else {
Ok("".into())
}
}
fn syscall_error(err: syscall::Error) -> io::Error {
io::Error::from_raw_os_error(err.errno)
}
/// Returns a password collected from the user (plaintext)
pub fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<Option<String>> {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let stdout = io::stdout();
let mut stdout = stdout.lock();
for i in 0..3 {
print!("{}", prompt);
let mut password = stdin.read_passwd(&mut stdout)?;
if let Some(password) = password.as_mut() {
*password = password.trim().to_string();
}
password.take_if(|s| s.is_empty());
if password.is_none() {
return Ok(None);
}
print!("\n{}", confirm_prompt);
let confirm_password = stdin.read_passwd(&mut stdout)?;
// Note: Actually comparing two Option<String> values
if confirm_password == password {
return Ok(password);
} else if i < 2 {
eprintln!("passwords do not match, please try again");
}
}
bail!("passwords do not match, giving up");
}
fn install_packages(config: &Config, dest: &Path, cookbook: Option<&str>) -> anyhow::Result<()> {
let target = &get_target();
let packages: Vec<&String> = config
.packages
.iter()
.filter_map(|(packagename, package)| match package {
PackageConfig::Build(rule) if rule == "ignore" => None,
_ => Some(packagename),
})
.collect();
let mut library = if let Some(cookbook) = cookbook {
let callback = pkg::callback::PlainCallback::new();
let repo = Path::new(cookbook).join("repo");
let pubkey = Path::new(cookbook).join("build");
Library::new_local(
repo,
pubkey,
dest.to_path_buf(),
target,
Rc::new(RefCell::new(callback)),
)
} else {
let callback = pkg::callback::IndicatifCallback::new();
Library::new_remote(
&vec!["https://static.redox-os.org/pkg"],
dest,
target,
Rc::new(RefCell::new(callback)),
)
}?;
let packages = pkg::PackageName::from_list(packages)?;
library.install(packages)?;
library.apply()?;
Ok(())
}
pub fn install_dir(
config: Config,
output_dir: impl AsRef<Path>,
cookbook: Option<&str>,
) -> Result<()> {
let output_dir = output_dir.as_ref();
let output_dir = output_dir.to_owned();
for file in &config.files {
if !file.postinstall {
file.create(&output_dir)?;
}
}
install_packages(&config, &output_dir, cookbook)?;
for file in &config.files {
if file.postinstall {
file.create(&output_dir)?;
}
}
let mut passwd = String::new();
let mut shadow = String::new();
let mut next_uid = 1000;
let mut next_gid = 1000;
let mut groups = vec![];
for (username, user) in config.users {
// plaintext
let password = if let Some(password) = user.password {
password
} else if config.general.prompt.unwrap_or(true) {
prompt_password(
&format!("{}: enter password: ", username),
&format!("{}: confirm password: ", username),
)?
.unwrap_or_default()
} else {
String::new()
};
let uid = user.uid.unwrap_or(next_uid);
if uid >= next_uid {
next_uid = uid + 1;
}
let gid = user.gid.unwrap_or(next_gid);
if gid >= next_gid {
next_gid = gid + 1;
}
let name = user.name.unwrap_or(username.clone());
let home = user.home.unwrap_or(format!("/home/{}", username));
let shell = user.shell.unwrap_or("/bin/ion".into());
println!("Adding user {username}:");
if password.is_empty() {
println!("\tPassword: unset");
} else {
println!("\tPassword: set");
}
println!("\tUID: {uid}");
println!("\tGID: {gid}");
println!("\tName: {name}");
println!("\tHome: {home}");
println!("\tShell: {shell}");
FileConfig::new_directory(home.clone())
.with_recursive_mod(0o700, uid, gid)
.create(&output_dir)?;
if uid >= 1000 {
prepare_user_home(&output_dir, uid, gid, &home)?;
}
let password = hash_password(&password)?;
passwd.push_str(&format!("{username};{uid};{gid};{name};{home};{shell}\n",));
shadow.push_str(&format!("{username};{password}\n"));
groups.push((username.clone(), gid, vec![username]));
}
for (group, group_config) in config.groups {
// FIXME this assumes there is no overlap between auto-created groups for users
// and explicitly specified groups.
let gid = group_config.gid.unwrap_or(next_gid);
if gid >= next_gid {
next_gid = gid + 1;
}
groups.push((group, gid, group_config.members));
}
if !passwd.is_empty() {
FileConfig::new_file("/etc/passwd".to_string(), passwd).create(&output_dir)?;
}
if !shadow.is_empty() {
FileConfig::new_file("/etc/shadow".to_string(), shadow)
.with_mod(0o0600, 0, 0)
.create(&output_dir)?;
}
if !groups.is_empty() {
let mut groups_data = String::new();
for (name, gid, members) in groups {
use std::fmt::Write;
writeln!(groups_data, "{name};x;{gid};{}", members.join(","))?;
println!("Adding group {name}:");
println!("\tGID: {gid}");
println!("\tMembers: {}", members.join(", "));
}
FileConfig::new_file("/etc/group".to_string(), groups_data)
.with_mod(0o0600, 0, 0)
.create(&output_dir)?;
}
Ok(())
}
fn prepare_user_home(
output_dir: &PathBuf,
uid: u32,
gid: u32,
home: &String,
) -> Result<(), anyhow::Error> {
for xdg_folder in &[
"Desktop",
"Documents",
"Downloads",
"Music",
"Pictures",
"Public",
"Templates",
"Videos",
".config",
".local",
".local/share",
".local/share/Trash",
".local/share/Trash/info",
] {
FileConfig::new_directory(format!("{}/{}", home, xdg_folder))
.with_mod(0o0700, uid, gid)
.create(output_dir)?;
}
FileConfig::new_file(
format!("{}/.config/user-dirs.dirs", home),
r#"# Produced by redox installer
XDG_DESKTOP_DIR="$HOME/Desktop"
XDG_DOCUMENTS_DIR="$HOME/Documents"
XDG_DOWNLOAD_DIR="$HOME/Downloads"
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_PUBLICSHARE_DIR="$HOME/Public"
XDG_TEMPLATES_DIR="$HOME/Templates"
XDG_VIDEOS_DIR="$HOME/Videos"
"#
.to_string(),
)
.with_mod(0o0600, uid, gid)
.create(output_dir)?;
let skel_dir = output_dir.join("etc/skel");
if skel_dir.is_dir() {
copy_dir_all(&skel_dir, home.clone(), output_dir, uid, gid)?;
}
Ok(())
}
fn copy_dir_all(
src: impl AsRef<Path>,
dst: String,
output_dir: &Path,
uid: u32,
gid: u32,
) -> anyhow::Result<()> {
if !Path::new(dst.as_str()).is_dir() {
FileConfig::new_directory(dst.clone())
.with_mod(0o0700, uid, gid)
.create(&output_dir)?;
}
for entry in fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let dst_path = format!("{}/{}", dst, entry.file_name().display());
if file_type.is_dir() {
copy_dir_all(entry.path(), dst_path, output_dir, uid, gid)?;
} else if file_type.is_file() {
FileConfig::new_file(
dst_path,
fs::read_to_string(entry.path())
.with_context(|| format!("Reading {}", entry.path().display()))?,
)
.with_mod(0o0600, uid, gid)
.create(&output_dir)?;
} else if file_type.is_symlink() {
// TODO
}
}
Ok(())
}
pub fn with_redoxfs<D, T, F>(disk: D, password_opt: Option<&[u8]>, callback: F) -> Result<T>
where
D: Disk + Send + 'static,
F: FnOnce(FileSystem<D>) -> Result<T>,
{
let ctime = SystemTime::now().duration_since(UNIX_EPOCH)?;
let fs = FileSystem::create(disk, password_opt, ctime.as_secs(), ctime.subsec_nanos())
.map_err(syscall_error)?;
callback(fs)
}
fn decide_mount_path(mount_path: Option<&Path>) -> PathBuf {
let mount_path = mount_path.map(|p| p.to_path_buf()).unwrap_or_else(|| {
PathBuf::from(if cfg!(target_os = "redox") {
format!("file.redox_installer_{}", process::id())
} else {
format!("/tmp/redox_installer_{}", process::id())
})
});
mount_path
}
pub fn with_redoxfs_mount<D, T, F>(
fs: FileSystem<D>,
mount_path: Option<&Path>,
callback: F,
) -> Result<T>
where
D: Disk + Send + 'static,
F: FnOnce(&Path) -> Result<T>,
{
let mount_path = decide_mount_path(mount_path);
if cfg!(not(target_os = "redox")) && !mount_path.exists() {
fs::create_dir(&mount_path)?;
}
let (tx, rx) = channel();
let join_handle = {
let mount_path = mount_path.clone();
thread::spawn(move || {
let res = redoxfs::mount(fs, &mount_path, |real_path| {
tx.send(Ok(real_path.to_owned())).unwrap();
});
match res {
Ok(()) => (),
Err(err) => {
tx.send(Err(err)).unwrap();
}
};
})
};
let res = match rx.recv() {
Ok(ok) => match ok {
Ok(real_path) => callback(&real_path),
Err(err) => return Err(err.into()),
},
Err(_) => {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"redoxfs thread did not send a result",
)
.into())
}
};
unmount_path(&mount_path.as_os_str().to_str().unwrap())?;
join_handle.join().unwrap();
if cfg!(not(target_os = "redox")) {
fs::remove_dir_all(&mount_path)?;
}
res
}
pub fn with_redoxfs_ar<D, T, F>(
mut fs: FileSystem<D>,
mount_path: Option<&Path>,
callback: F,
) -> Result<T>
where
D: Disk + Send + 'static,
F: FnOnce(&Path) -> Result<T>,
{
let mount_path = decide_mount_path(mount_path);
let res = callback(Path::new(&mount_path));
if res.is_ok() {
let _end_block = fs
.tx(|tx| {
// Archive_at root node
redoxfs::archive_at(tx, Path::new(&mount_path), redoxfs::TreePtr::root())
.map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?;
// Squash alloc log
tx.sync(true)?;
let end_block = tx.header.size() / BLOCK_SIZE;
/* TODO: Cut off any free blocks at the end of the filesystem
let mut end_changed = true;
while end_changed {
end_changed = false;
let allocator = fs.allocator();
let levels = allocator.levels();
for level in 0..levels.len() {
let level_size = 1 << level;
for &block in levels[level].iter() {
if block < end_block && block + level_size >= end_block {
end_block = block;
end_changed = true;
}
}
}
}
*/
// Update header
tx.header.size = (end_block * BLOCK_SIZE).into();
tx.header_changed = true;
tx.sync(false)?;
Ok(end_block)
})
.map_err(syscall_error)?;
// let size = (fs.block + end_block) * BLOCK_SIZE;
// fs.disk.file.set_len(size)?;
}
fs::remove_dir_all(&mount_path)?;
res
}
pub fn fetch_bootloaders(
config: &Config,
cookbook: Option<&str>,
live: bool,
) -> Result<(Vec<u8>, Vec<u8>)> {
let bootloader_dir =
PathBuf::from(format!("/tmp/redox_installer_bootloader_{}", process::id()));
if bootloader_dir.exists() {
fs::remove_dir_all(&bootloader_dir)?;
}
fs::create_dir(&bootloader_dir)?;
let mut bootloader_config = Config::bootloader_config();
bootloader_config.general = config.general.clone();
install_packages(&bootloader_config, &bootloader_dir, cookbook)?;
let boot_dir = bootloader_dir.join("usr/lib/boot");
let bios_path = boot_dir.join(if live {
"bootloader-live.bios"
} else {
"bootloader.bios"
});
let efi_path = boot_dir.join(if live {
"bootloader-live.efi"
} else {
"bootloader.efi"
});
let bios_data = if bios_path.exists() {
fs::read(bios_path)?
} else {
Vec::new()
};
let efi_data = if efi_path.exists() {
fs::read(efi_path)?
} else {
Vec::new()
};
fs::remove_dir_all(&bootloader_dir)?;
Ok((bios_data, efi_data))
}
//TODO: make bootloaders use Option, dynamically create BIOS and EFI partitions
pub fn with_whole_disk<P, F, T>(disk_path: P, disk_option: &DiskOption, callback: F) -> Result<T>
where
P: AsRef<Path>,
F: FnOnce(FileSystem<DiskIo<fscommon::StreamSlice<DiskWrapper>>>) -> Result<T>,
{
let target = get_target();
let bootloader_efi_name = match target.as_str() {
"aarch64-unknown-redox" => "BOOTAA64.EFI",
"i586-unknown-redox" | "i686-unknown-redox" => "BOOTIA32.EFI",
"x86_64-unknown-redox" => "BOOTX64.EFI",
"riscv64gc-unknown-redox" => "BOOTRISCV64.EFI",
_ => {
bail!("target '{target}' not supported");
}
};
// Open disk and read metadata
eprintln!("Opening disk {}", disk_path.as_ref().display());
let mut disk_file = DiskWrapper::open(disk_path.as_ref())?;
let disk_size = disk_file.size();
let block_size = disk_file.block_size() as u64;
if disk_option.skip_partitions {
return with_redoxfs(
DiskIo(fscommon::StreamSlice::new(
disk_file,
0,
disk_size.next_multiple_of(block_size),
)?),
disk_option.password_opt,
callback,
);
}
let gpt_block_size = match block_size {
512 => gpt::disk::LogicalBlockSize::Lb512,
_ => {
// TODO: support (and test) other block sizes
bail!("block size {block_size} not supported");
}
};
// Calculate partition offsets
let gpt_reserved = 34 * 512; // GPT always reserves 34 512-byte sectors
let mibi = 1024 * 1024;
// First megabyte of the disk is reserved for BIOS partition, wich includes GPT tables
let bios_start = gpt_reserved / block_size;
let bios_end = (mibi / block_size) - 1;
// Second megabyte of the disk is reserved for EFI partition
let efi_start = bios_end + 1;
let efi_size = if let Some(size) = disk_option.efi_partition_size {
size as u64
} else {
1
};
let efi_end = efi_start + (efi_size * mibi / block_size) - 1;
// The rest of the disk is RedoxFS, reserving the GPT table mirror at the end of disk
let redoxfs_start = efi_end + 1;
let redoxfs_end = ((((disk_size - gpt_reserved) / mibi) * mibi) / block_size) - 1;
// Format and install BIOS partition
{
// Write BIOS bootloader to disk
eprintln!(
"Write bootloader with size {:#x}",
disk_option.bootloader_bios.len()
);
disk_file.seek(SeekFrom::Start(0))?;
disk_file.write_all(&disk_option.bootloader_bios)?;
// Replace MBR tables with protective MBR
// TODO: div_ceil
let mbr_blocks = ((disk_size + block_size - 1) / block_size) - 1;
eprintln!("Writing protective MBR with disk blocks {mbr_blocks:#x}");
gpt::mbr::ProtectiveMBR::with_lb_size(mbr_blocks as u32)
.update_conservative(&mut disk_file)?;
// Open disk, mark it as not initialized
let mut gpt_disk = gpt::GptConfig::new()
.initialized(false)
.writable(true)
.logical_block_size(gpt_block_size)
.create_from_device(Box::new(&mut disk_file), None)?;
// Add BIOS boot partition
let mut partitions = BTreeMap::new();
let mut partition_id = 1;
partitions.insert(
partition_id,
gpt::partition::Partition {
part_type_guid: gpt::partition_types::BIOS,
part_guid: uuid::Uuid::new_v4(),
first_lba: bios_start,
last_lba: bios_end,
flags: 0, // TODO
name: "BIOS".to_string(),
},
);
partition_id += 1;
// Add EFI boot partition
partitions.insert(
partition_id,
gpt::partition::Partition {
part_type_guid: gpt::partition_types::EFI,
part_guid: uuid::Uuid::new_v4(),
first_lba: efi_start,
last_lba: efi_end,
flags: 0, // TODO
name: "EFI".to_string(),
},
);
partition_id += 1;
// Add RedoxFS partition
partitions.insert(
partition_id,
gpt::partition::Partition {
//TODO: Use REDOX_REDOXFS type (needs GPT crate changes)
part_type_guid: gpt::partition_types::LINUX_FS,
part_guid: uuid::Uuid::new_v4(),
first_lba: redoxfs_start,
last_lba: redoxfs_end,
flags: 0,
name: "REDOX".to_string(),
},
);
eprintln!("Writing GPT tables: {partitions:#?}");
// Initialize GPT table
gpt_disk.update_partitions(partitions)?;
// Write partition layout, returning disk file
gpt_disk.write()?;
}
// Format and install EFI partition
{
let disk_efi_start = efi_start * block_size;
let disk_efi_end = (efi_end + 1) * block_size;
let mut disk_efi =
fscommon::StreamSlice::new(&mut disk_file, disk_efi_start, disk_efi_end)?;
eprintln!(
"Formatting EFI partition with size {:#x}",
disk_efi_end - disk_efi_start
);
fatfs::format_volume(&mut disk_efi, fatfs::FormatVolumeOptions::new())?;
eprintln!("Opening EFI partition");
let fs = fatfs::FileSystem::new(&mut disk_efi, fatfs::FsOptions::new())?;
eprintln!("Creating EFI directory");
let root_dir = fs.root_dir();
root_dir.create_dir("EFI")?;
eprintln!("Creating EFI/BOOT directory");
let efi_dir = root_dir.open_dir("EFI")?;
efi_dir.create_dir("BOOT")?;
eprintln!(
"Writing EFI/BOOT/{} file with size {:#x}",
bootloader_efi_name,
disk_option.bootloader_efi.len()
);
let boot_dir = efi_dir.open_dir("BOOT")?;
let mut file = boot_dir.create_file(bootloader_efi_name)?;
file.truncate()?;
file.write_all(&disk_option.bootloader_efi)?;
}
// Format and install RedoxFS partition
eprintln!(
"Installing to RedoxFS partition with size {:#x}",
(redoxfs_end - redoxfs_start) * block_size
);
let disk_redoxfs = DiskIo(fscommon::StreamSlice::new(
disk_file,
redoxfs_start * block_size,
(redoxfs_end + 1) * block_size,
)?);
with_redoxfs(disk_redoxfs, disk_option.password_opt, callback)
}
#[cfg(not(target_os = "redox"))]
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
_fs: &mut redoxfs::FileSystem<D>,
_progress: F,
) -> Result<bool> {
Ok(false)
}
/// Try fast install using live disk memory
#[cfg(target_os = "redox")]
pub fn try_fast_install<D: redoxfs::Disk, F: FnMut(u64, u64)>(
fs: &mut redoxfs::FileSystem<D>,
mut progress: F,
) -> Result<bool> {
use libredox::{call::MmapArgs, flag};
use std::os::fd::AsRawFd;
use syscall::PAGE_SIZE;
let phys = env::var("DISK_LIVE_ADDR")
.ok()
.and_then(|x| usize::from_str_radix(&x, 16).ok())
.unwrap_or(0);
let size = env::var("DISK_LIVE_SIZE")
.ok()
.and_then(|x| usize::from_str_radix(&x, 16).ok())
.unwrap_or(0);
if phys == 0 || size == 0 {
return Ok(false);
}
let start = (phys / PAGE_SIZE) * PAGE_SIZE;
let end = phys
.checked_add(size)
.context("phys + size overflow")?
.next_multiple_of(PAGE_SIZE);
let size = end - start;
let original = unsafe {
//TODO: unmap this memory
let file = fs::File::open("/scheme/memory/physical")?;
let base = libredox::call::mmap(MmapArgs {
fd: file.as_raw_fd() as usize,
addr: core::ptr::null_mut(),
offset: start as u64,
length: size,
prot: flag::PROT_READ,
flags: flag::MAP_SHARED,
})
.map_err(|err| anyhow::anyhow!("failed to mmap livedisk: {}", err))?;
std::slice::from_raw_parts(base as *const u8, size)
};
struct DiskLive {
original: &'static [u8],
}
impl redoxfs::Disk for DiskLive {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
let offset = (block * redoxfs::BLOCK_SIZE) as usize;
if offset + buffer.len() > self.original.len() {
return Err(syscall::Error::new(syscall::EINVAL));
}
buffer.copy_from_slice(&self.original[offset..offset + buffer.len()]);
Ok(buffer.len())
}
unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result<usize> {
Err(syscall::Error::new(syscall::EINVAL))
}
fn size(&mut self) -> syscall::Result<u64> {
Ok(self.original.len() as u64)
}
}
let mut fs_old = redoxfs::FileSystem::open(DiskLive { original }, None, None, false)?;
let size_old = fs_old.header.size();
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
let used_old = size_old - free_old;
redoxfs::clone(&mut fs_old, fs, move |used| {
progress(used, used_old);
})?;
Ok(true)
}
fn install_inner(config: Config, output: &Path) -> Result<()> {
println!("Installing to {}:\n{}", output.display(), config);
let cookbook = config.general.cookbook.clone();
let cookbook = cookbook.as_ref().map(|p| p.as_str());
if output.is_dir() {
install_dir(config, output, cookbook)
} else {
if !output.is_file() {
let fs_size = config.general.filesystem_size.unwrap_or(0) as u64;
// arbitrary size approximately fit just for initfs
if fs_size < 32 {
bail!("Refusing to create image disk less than 32 MB");
}
eprintln!(
"Creating a new file to {} with size {} MB",
output.display(),
fs_size
);
let file = fs::File::create(output)?;
file.set_len(fs_size * 1024 * 1024)?;
}
let live = config.general.live_disk.unwrap_or(false);
let password_opt = config.general.encrypt_disk.clone();
let password_opt = password_opt.as_ref().map(|p| p.as_bytes());
let (bootloader_bios, bootloader_efi) = fetch_bootloaders(&config, cookbook, live)?;
if let Some(write_bootloader) = &config.general.write_bootloader {
std::fs::write(write_bootloader, &bootloader_efi)?;
}
let disk_option = DiskOption {
bootloader_bios: &bootloader_bios,
bootloader_efi: &bootloader_efi,
password_opt: password_opt,
efi_partition_size: config.general.efi_partition_size,
skip_partitions: config.general.skip_partitions.unwrap_or(false),
};
with_whole_disk(output, &disk_option, move |fs| {
if config.general.no_mount.unwrap_or(false) {
with_redoxfs_ar(fs, None, move |mount_path| {
install_dir(config, mount_path, cookbook)
})
} else {
with_redoxfs_mount(fs, None, move |mount_path| {
install_dir(config, mount_path, cookbook)
})
}
})
}
}
/// Install RedoxFS into a new disk file, or a sysroot directory.
/// This function assumes all interactive prompts resolved by the caller.
pub fn install(config: Config, output: impl AsRef<Path>) -> Result<()> {
install_inner(config, output.as_ref())
}
+14
View File
@@ -0,0 +1,14 @@
#[macro_use]
extern crate serde_derive;
mod config;
#[cfg(feature = "installer")]
mod disk_wrapper;
#[cfg(feature = "installer")]
mod installer;
#[cfg(feature = "installer")]
pub use crate::installer::*;
pub use crate::config::file::FileConfig;
pub use crate::config::package::PackageConfig;
pub use crate::config::Config;
-26
View File
@@ -1,26 +0,0 @@
use log::{LevelFilter, Log, Metadata, Record};
pub static LOGGER: Logger = Logger;
pub struct Logger;
impl Logger {
pub fn init(&'static self) {
log::set_logger(self).unwrap();
log::set_max_level(LevelFilter::Info);
}
}
impl Log for Logger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("{} - {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
-761
View File
@@ -1,761 +0,0 @@
#![no_std]
#![cfg_attr(any(target_arch = "riscv64", target_os = "uefi"), no_main)]
extern crate alloc;
#[cfg(any(target_arch = "riscv64", target_os = "uefi"))]
#[macro_use]
extern crate uefi_std as std;
use alloc::{format, string::String, vec::Vec};
use core::{
cmp,
convert::TryFrom,
fmt::{self, Write},
mem, ptr, slice, str,
};
use redoxfs::{Disk, Node, TreeData};
use self::arch::{paging_create, paging_framebuffer};
use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode};
#[macro_use]
mod os;
mod arch;
mod editor;
mod logger;
mod serial_16550;
const KIBI: usize = 1024;
const MIBI: usize = KIBI * KIBI;
//TODO: allocate this in a more reasonable manner
static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry {
base: 0,
size: 0,
kind: OsMemoryKind::Null,
}; 1024];
static mut AREAS_LEN: usize = 0;
pub fn area_add(area: OsMemoryEntry) {
#[allow(static_mut_refs)]
unsafe {
for existing_area in &mut AREAS[0..AREAS_LEN] {
if existing_area.kind == area.kind {
if existing_area.base.unchecked_add(existing_area.size) == area.base {
existing_area.size += area.size;
return;
}
if area.base.unchecked_add(area.size) == existing_area.base {
existing_area.size += area.size;
existing_area.base = area.base;
return;
}
}
}
*AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area;
AREAS_LEN += 1;
}
}
pub static mut KERNEL_64BIT: bool = false;
pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None;
struct SliceWriter<'a> {
slice: &'a mut [u8],
i: usize,
}
impl<'a> Write for SliceWriter<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for b in s.bytes() {
if let Some(slice_b) = self.slice.get_mut(self.i) {
*slice_b = b;
self.i += 1;
} else {
return Err(fmt::Error);
}
}
Ok(())
}
}
#[allow(dead_code)]
#[derive(Debug)]
#[repr(C, packed(8))]
pub struct KernelArgs {
kernel_base: u64,
kernel_size: u64,
stack_base: u64,
stack_size: u64,
env_base: u64,
env_size: u64,
/// The base pointer to the saved RSDP.
///
/// This field can be NULL, and if so, the system has not booted with UEFI or in some other way
/// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS
/// memory instead. On UEFI systems, searching is not guaranteed to actually work though.
acpi_rsdp_base: u64,
/// The size of the RSDP region.
acpi_rsdp_size: u64,
areas_base: u64,
areas_size: u64,
bootstrap_base: u64,
bootstrap_size: u64,
}
fn select_mode(
os: &impl Os,
output_i: usize,
live: &mut bool,
edit_env: &mut bool,
) -> Option<OsVideoMode> {
const DEFAULT_WIDTH: u32 = 1280;
const DEFAULT_HEIGHT: u32 = 720;
const AUTOBOOT_SECONDS: usize = 5;
let mut modes = Vec::new();
for mode in os.video_modes(output_i) {
let mut aspect_w = mode.width;
let mut aspect_h = mode.height;
for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) {
while aspect_w % i == 0 && aspect_h % i == 0 {
aspect_w /= i;
aspect_h /= i;
}
}
modes.push((
mode,
format!(
"{:>4}x{:<4} {:>3}:{:<3}",
mode.width, mode.height, aspect_w, aspect_h
),
));
}
if modes.is_empty() {
return None;
}
// Sort modes by pixel area, reversed
modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height)));
// Set selected based on Red Bear default resolution first, then best resolution fallback
print!("Output {}", output_i);
let mut selected = modes.first().map_or(0, |x| x.0.id);
let mut selected_from_default = false;
for (mode, _text) in modes.iter() {
if mode.width == DEFAULT_WIDTH && mode.height == DEFAULT_HEIGHT {
selected = mode.id;
selected_from_default = true;
print!(", default resolution: {}x{}", DEFAULT_WIDTH, DEFAULT_HEIGHT);
break;
}
}
if !selected_from_default {
if let Some((best_width, best_height)) = os.best_resolution(output_i) {
print!(", best resolution: {}x{}", best_width, best_height);
for (mode, _text) in modes.iter() {
if mode.width == best_width && mode.height == best_height {
selected = mode.id;
break;
}
}
}
}
println!();
println!("Arrow keys and enter select mode");
let live_mode = os.get_text_position();
if *live {
println!("Press l to disable live mode");
} else {
println!("Press l to enable live mode");
}
println!("Press e to edit boot environment");
println!(
"Autobooting default mode in {} seconds (press any key to cancel countdown)",
AUTOBOOT_SECONDS
);
println!();
print!(" ");
let (off_x, off_y) = os.get_text_position();
let rows = 12;
let mut mode_opt = None;
let countdown_y = off_y.saturating_sub(2);
let mut countdown = AUTOBOOT_SECONDS;
let mut countdown_active = true;
while !modes.is_empty() {
let mut row = 0;
let mut col = 0;
for (mode, text) in modes.iter() {
if row >= rows {
col += 1;
row = 0;
}
os.set_text_position(off_x + col * 20, off_y + row);
os.set_text_highlight(mode.id == selected);
print!("{}", text);
row += 1;
}
os.set_text_position(0, countdown_y);
os.set_text_highlight(false);
if countdown_active {
println!(
"Autobooting default mode in {} seconds (press any key to cancel countdown)",
countdown
);
} else {
println!("Manual mode selection active. Press Enter to boot selected mode. ");
}
// Read keypress
match if countdown_active {
os.get_key_timeout(1000)
} else {
os.get_key()
} {
OsKey::Timeout => {
if countdown_active {
if countdown == 0 {
if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
if let Some((mode, _text)) = modes.get(mode_i) {
mode_opt = Some(*mode);
}
}
break;
}
countdown = countdown.saturating_sub(1);
}
}
OsKey::Left => {
countdown_active = false;
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
if mode_i < rows {
while mode_i < modes.len() {
mode_i += rows;
}
}
mode_i -= rows;
if let Some(new) = modes.get(mode_i) {
selected = new.0.id;
}
}
}
OsKey::Right => {
countdown_active = false;
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
mode_i += rows;
if mode_i >= modes.len() {
mode_i %= rows;
}
if let Some(new) = modes.get(mode_i) {
selected = new.0.id;
}
}
}
OsKey::Up => {
countdown_active = false;
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
if mode_i % rows == 0 {
mode_i += rows;
if mode_i > modes.len() {
mode_i = modes.len();
}
}
mode_i -= 1;
if let Some(new) = modes.get(mode_i) {
selected = new.0.id;
}
}
}
OsKey::Down => {
countdown_active = false;
if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == selected) {
mode_i += 1;
if mode_i % rows == 0 {
mode_i -= rows;
}
if mode_i >= modes.len() {
mode_i = mode_i - mode_i % rows;
}
if let Some(new) = modes.get(mode_i) {
selected = new.0.id;
}
}
}
OsKey::Enter => {
countdown_active = false;
if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
if let Some((mode, _text)) = modes.get(mode_i) {
mode_opt = Some(*mode);
}
}
break;
}
OsKey::Char('l') => {
countdown_active = false;
*live = !*live;
os.set_text_position(live_mode.0, live_mode.1);
if *live {
println!("Press l to disable live mode");
} else {
println!("Press l to enable live mode");
}
}
OsKey::Char('e') => {
countdown_active = false;
if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
if let Some((mode, _text)) = modes.get(mode_i) {
*edit_env = true;
mode_opt = Some(*mode);
}
}
break;
}
OsKey::Other | OsKey::Backspace | OsKey::Delete | OsKey::Char(_) => {
countdown_active = false;
}
}
}
os.set_text_position(0, off_y + rows);
os.set_text_highlight(false);
println!();
mode_opt
}
fn redoxfs<O: Os>(os: &O) -> (redoxfs::FileSystem<O::D>, Option<&'static [u8]>) {
let attempts = 10;
for attempt in 0..=attempts {
let mut password_opt = None;
if attempt > 0 {
print!("\rRedoxFS password ({}/{}): ", attempt, attempts);
let mut password = String::new();
loop {
match os.get_key() {
OsKey::Backspace | OsKey::Delete => {
if !password.is_empty() {
print!("\x08 \x08");
password.pop();
}
}
OsKey::Char(c) => {
print!("*");
password.push(c)
}
OsKey::Enter => break,
_ => (),
}
}
// Erase password information
while os.get_text_position().0 > 0 {
print!("\x08 \x08");
}
if !password.is_empty() {
password_opt = Some(password);
}
}
match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) {
Ok(fs) => {
return (
fs,
password_opt.map(|password| {
// Copy password to page aligned memory
let password_size = password.len();
let password_base = os.alloc_zeroed_page_aligned(password_size);
area_add(OsMemoryEntry {
base: password_base as u64,
size: password_size as u64,
kind: OsMemoryKind::Reserved,
});
unsafe {
ptr::copy(password.as_ptr(), password_base, password_size);
slice::from_raw_parts(password_base, password_size)
}
}),
);
}
Err(err) => match err.errno {
// Incorrect password, try again
syscall::ENOKEY => (),
_ => {
panic!("Failed to open RedoxFS: {}", err);
}
},
}
}
panic!("RedoxFS out of unlock attempts");
}
#[derive(PartialEq)]
enum Filetype {
Elf,
Initfs,
}
fn load_to_memory<O: Os>(
os: &O,
fs: &mut redoxfs::FileSystem<O::D>,
path: &str,
filetype: Filetype,
) -> &'static mut [u8] {
fs.tx(|tx| {
let mut node = None;
for component in path.split('/') {
node = Some(
tx.find_node(
node.map_or(redoxfs::TreePtr::root(), |node: TreeData<Node>| node.ptr()),
component,
)
.unwrap_or_else(|err| panic!("Failed to find {component}: {err}")),
);
}
let node = node.unwrap();
let size = node.data().size();
print!("{}: 0/{} MiB", path, size / MIBI as u64);
let ptr = os.alloc_zeroed_page_aligned(size as usize);
if ptr.is_null() {
panic!("Failed to allocate memory for {}", path);
}
let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) };
let mut i = 0;
for chunk in slice.chunks_mut(MIBI) {
print!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64);
i += tx
.read_node_inner(&node, i, chunk)
.unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", path, err))
as u64;
}
println!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64);
if filetype == Filetype::Elf {
let magic = &slice[..4];
if magic != b"\x7FELF" {
panic!("{} has invalid magic number {:#X?}", path, magic);
}
} else if filetype == Filetype::Initfs {
let magic = &slice[..8];
if magic != b"RedoxFtw" {
panic!("{} has invalid magic number {:#X?}", path, magic);
}
}
Ok(slice)
})
.unwrap_or_else(|err| {
panic!(
"RedoxFS transaction failed while loading `{}`: {}",
path, err
)
})
}
fn elf_entry(data: &[u8]) -> (u64, bool) {
match (data[4], data[5]) {
// 32-bit, little endian
(1, 1) => (
u32::from_le_bytes(
<[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"),
) as u64,
false,
),
// 32-bit, big endian
(1, 2) => (
u32::from_be_bytes(
<[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"),
) as u64,
false,
),
// 64-bit, little endian
(2, 1) => (
u64::from_le_bytes(
<[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"),
),
true,
),
// 64-bit, big endian
(2, 2) => (
u64::from_be_bytes(
<[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"),
),
true,
),
(ei_class, ei_data) => {
panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data);
}
}
}
fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
println!(
"Redox OS Bootloader {} on {}",
env!("CARGO_PKG_VERSION"),
os.name()
);
let hwdesc = os.hwdesc();
println!("Hardware descriptor: {:x?}", hwdesc);
let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc {
OsHwDesc::Acpi(base, size) => (base, size),
OsHwDesc::DeviceTree(base, size) => (base, size),
OsHwDesc::NotFound => (0, 0),
};
let (mut fs, password_opt) = redoxfs(os);
print!("RedoxFS ");
for i in 0..fs.header.uuid().len() {
if i == 4 || i == 6 || i == 8 || i == 10 {
print!("-");
}
print!("{:>02x}", fs.header.uuid()[i]);
}
println!(": {} MiB", fs.header.size() / MIBI as u64);
println!();
let mut mode_opts = Vec::new();
let mut live = cfg!(feature = "live");
let mut edit_env = false;
for output_i in 0..os.video_outputs() {
if output_i > 0 {
os.clear_text();
}
mode_opts.push(select_mode(os, output_i, &mut live, &mut edit_env));
}
let stack_size = 128 * KIBI;
let stack_base = os.alloc_zeroed_page_aligned(stack_size);
if stack_base.is_null() {
panic!("Failed to allocate memory for stack");
}
let live_opt = if live {
let size = fs.header.size();
print!("live: 0/{} MiB", size / MIBI as u64);
let live_size = match usize::try_from(size) {
Ok(live_size) => live_size,
Err(_) => {
println!("\rlive: disabled (image too large for bootloader address space)");
live = false;
0
}
};
let ptr = if live {
os.alloc_zeroed_page_aligned(live_size)
} else {
ptr::null_mut()
};
if live && ptr.is_null() {
println!(
"\rlive: disabled (unable to allocate {} MiB upfront)",
size / MIBI as u64
);
live = false;
}
let live = if live {
Some(unsafe { slice::from_raw_parts_mut(ptr, live_size) })
} else {
println!("Continuing without live preload");
None
};
if let Some(live) = live {
let mut i = 0;
for chunk in live.chunks_mut(MIBI) {
print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
i += unsafe {
fs.disk
.read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk)
.expect("Failed to read live disk") as u64
};
}
println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64);
println!("Switching to live disk");
unsafe {
LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, live_size)));
}
area_add(OsMemoryEntry {
base: live.as_ptr() as u64,
size: live.len() as u64,
kind: OsMemoryKind::Reserved,
});
Some(live)
} else {
None
}
} else {
None
};
let (kernel, kernel_entry) = {
let kernel = load_to_memory(os, &mut fs, "usr/lib/boot/kernel", Filetype::Elf);
let (kernel_entry, kernel_64bit) = elf_entry(kernel);
unsafe {
KERNEL_64BIT = kernel_64bit;
}
(kernel, kernel_entry)
};
let (bootstrap_size, bootstrap_base) = {
let initfs_slice = load_to_memory(os, &mut fs, "usr/lib/boot/initfs", Filetype::Initfs);
let memory = unsafe {
let total_size = initfs_slice.len().next_multiple_of(4096);
let ptr = os.alloc_zeroed_page_aligned(total_size);
assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory");
core::slice::from_raw_parts_mut(ptr, total_size)
};
memory[..initfs_slice.len()].copy_from_slice(initfs_slice);
(memory.len() as u64, memory.as_mut_ptr() as u64)
};
let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) }
.expect("Failed to set up paging");
let max_env_size = 64 * KIBI;
let mut env_size = max_env_size;
let env_base = os.alloc_zeroed_page_aligned(env_size);
if env_base.is_null() {
panic!("Failed to allocate memory for stack");
}
{
let mut w = SliceWriter {
slice: unsafe { slice::from_raw_parts_mut(env_base, max_env_size) },
i: 0,
};
match hwdesc {
OsHwDesc::Acpi(addr, size) => {
writeln!(w, "RSDP_ADDR={addr:016x}").unwrap();
writeln!(w, "RSDP_SIZE={size:016x}").unwrap();
}
OsHwDesc::DeviceTree(addr, size) => {
writeln!(w, "DTB_ADDR={addr:016x}").unwrap();
writeln!(w, "DTB_SIZE={size:016x}").unwrap();
}
OsHwDesc::NotFound => {}
}
if let Some(live) = live_opt {
writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap();
writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap();
writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap();
} else {
writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap();
}
write!(w, "REDOXFS_UUID=").unwrap();
for i in 0..fs.header.uuid().len() {
if i == 4 || i == 6 || i == 8 || i == 10 {
write!(w, "-").unwrap();
}
write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap();
}
writeln!(w).unwrap();
if let Some(password) = password_opt {
writeln!(
w,
"REDOXFS_PASSWORD_ADDR={:016x}",
password.as_ptr() as usize
)
.unwrap();
writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap();
}
#[cfg(target_arch = "riscv64")]
{
let boot_hartid = os::efi_get_boot_hartid()
.expect("Could not retrieve boot hart id from EFI implementation!");
writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap();
}
if edit_env {
editor::edit_env(os, env_base, &mut w.i, max_env_size);
}
for output_i in 0..os.video_outputs() {
if let Some(mut mode) = mode_opts[output_i] {
// Set mode to get updated values
os.set_video_mode(output_i, &mut mode);
if output_i == 0 {
let virt = unsafe {
paging_framebuffer(
os,
page_phys,
mode.base,
(mode.stride * mode.height * 4) as u64,
)
}
.expect("Failed to map framebuffer");
writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap();
writeln!(w, "FRAMEBUFFER_VIRT={virt:016x}").unwrap();
writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap();
writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap();
writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap();
} else {
writeln!(
w,
"FRAMEBUFFER{}={:#x},{},{},{}",
output_i, mode.base, mode.width, mode.height, mode.stride,
)
.unwrap();
}
}
}
env_size = w.i;
}
#[allow(static_mut_refs)]
(
page_phys,
kernel_entry,
KernelArgs {
kernel_base: kernel.as_ptr() as u64,
kernel_size: kernel.len() as u64,
stack_base: stack_base as u64,
stack_size: stack_size as u64,
env_base: env_base as u64,
env_size: env_size as u64,
acpi_rsdp_base,
acpi_rsdp_size,
areas_base: unsafe { AREAS.as_ptr() as u64 },
areas_size: unsafe { (AREAS.len() * mem::size_of::<OsMemoryEntry>()) as u64 },
bootstrap_base,
bootstrap_size,
},
)
}
-177
View File
@@ -1,177 +0,0 @@
use core::{mem, ptr};
use redoxfs::{Disk, BLOCK_SIZE};
use syscall::error::{Error, Result, EIO};
use super::{ThunkData, DISK_ADDRESS_PACKET_ADDR, DISK_BIOS_ADDR};
const SECTOR_SIZE: u64 = 512;
const BLOCKS_PER_SECTOR: u64 = BLOCK_SIZE / SECTOR_SIZE;
// 128 sectors is the amount allocated for DISK_BIOS_ADDR
// 127 sectors is the maximum for many BIOSes
const MAX_SECTORS: u64 = 127;
const MAX_BLOCKS: u64 = MAX_SECTORS * SECTOR_SIZE / BLOCK_SIZE;
#[allow(dead_code)]
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct DiskAddressPacket {
size: u8,
reserved: u8,
sectors: u16,
buffer: u16,
segment: u16,
address: u64,
}
impl DiskAddressPacket {
pub fn from_block(block: u64, count: u64) -> DiskAddressPacket {
let address = block * BLOCKS_PER_SECTOR;
let sectors = count * BLOCKS_PER_SECTOR;
assert!(sectors <= MAX_SECTORS);
DiskAddressPacket {
size: mem::size_of::<DiskAddressPacket>() as u8,
reserved: 0,
sectors: sectors as u16,
buffer: (DISK_BIOS_ADDR & 0xF) as u16,
segment: (DISK_BIOS_ADDR >> 4) as u16,
address,
}
}
}
pub struct DiskBios {
boot_disk: u8,
thunk13: extern "C" fn(),
chs_opt: Option<(u32, u32, u32)>,
}
impl DiskBios {
pub fn new(boot_disk: u8, thunk13: extern "C" fn()) -> Self {
let chs_opt = unsafe {
let mut data = ThunkData::new();
data.eax = 0x4100;
data.ebx = 0x55AA;
data.edx = boot_disk as u32;
data.with(thunk13);
if (data.ebx & 0xFFFF) == 0xAA55 {
// Extensions are installed, do not use CHS
None
} else {
// Extensions are not installed, get CHS geometry
data = ThunkData::new();
data.eax = 0x0800;
data.edx = boot_disk as u32;
data.edi = 0;
data.with(thunk13);
//TODO: return result on error
let ah = ({ data.eax } >> 8) & 0xFF;
assert_eq!(ah, 0);
let c = (data.ecx >> 8) & 0xFF | ((data.ecx >> 6) & 0x3) << 8;
let h = ((data.edx >> 8) & 0xFF) + 1;
let s = data.ecx & 0x3F;
Some((c, h, s))
}
};
Self {
boot_disk,
thunk13,
chs_opt,
}
}
}
impl Disk for DiskBios {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
unsafe {
// Optimization for live disks
if let Some(live) = crate::LIVE_OPT {
if block >= live.0 {
let start = ((block - live.0) * BLOCK_SIZE) as usize;
let end = start + buffer.len();
if end <= live.1.len() {
buffer.copy_from_slice(&live.1[start..end]);
return Ok(buffer.len());
}
}
}
for (i, chunk) in buffer
.chunks_mut((MAX_BLOCKS * BLOCK_SIZE) as usize)
.enumerate()
{
let dap = DiskAddressPacket::from_block(
block + i as u64 * MAX_BLOCKS,
chunk.len() as u64 / BLOCK_SIZE,
);
if let Some((_, h_max, s_max)) = self.chs_opt {
let s = (dap.address % s_max as u64) + 1;
assert!(s <= 63, "invalid sector {s}");
let tmp = dap.address / s_max as u64;
let h = tmp % h_max as u64;
assert!(h <= 255, "invalid head {h}");
let c = tmp / h_max as u64;
assert!(c <= 1023, "invalid cylinder {c}");
let mut data = ThunkData::new();
data.eax = 0x0200 | (dap.sectors as u32);
data.ebx = dap.buffer as u32;
data.ecx =
(s as u32) | (((c as u32) & 0xFF) << 8) | ((((c as u32) >> 8) & 0x3) << 6);
data.edx = (self.boot_disk as u32) | ((h as u32) << 8);
data.es = dap.segment;
data.with(self.thunk13);
//TODO: return result on error
let ah = ({ data.eax } >> 8) & 0xFF;
assert_eq!(ah, 0);
} else {
ptr::write(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket, dap);
let mut data = ThunkData::new();
data.eax = 0x4200;
data.edx = self.boot_disk as u32;
data.esi = DISK_ADDRESS_PACKET_ADDR as u32;
data.with(self.thunk13);
//TODO: return result on error
let ah = ({ data.eax } >> 8) & 0xFF;
assert_eq!(ah, 0);
//TODO: check blocks transferred
// dap = ptr::read(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket);
}
ptr::copy(DISK_BIOS_ADDR as *const u8, chunk.as_mut_ptr(), chunk.len());
}
Ok(buffer.len())
}
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
log::error!(
"DiskBios::write_at(0x{:X}, 0x{:X}:0x{:X}) not allowed",
block,
buffer.as_ptr() as usize,
buffer.len()
);
Err(Error::new(EIO))
}
fn size(&mut self) -> Result<u64> {
log::error!("DiskBios::size not implemented");
Err(Error::new(EIO))
}
}
-20
View File
@@ -1,20 +0,0 @@
/// Print to console
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ({
use core::fmt::Write;
#[cfg(feature = "serial_debug")]
{
let _ = write!($crate::os::serial::COM1.lock(), $($arg)*);
}
let _ = write!($crate::os::VGA.lock(), $($arg)*);
});
}
/// Print with new line to console
#[macro_export]
macro_rules! println {
() => (print!("\n"));
($fmt:expr_2021) => (print!(concat!($fmt, "\n")));
($fmt:expr_2021, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
}
-84
View File
@@ -1,84 +0,0 @@
use core::{cmp, mem, ptr};
use crate::area_add;
use crate::os::{OsMemoryEntry, OsMemoryKind};
use super::{thunk::ThunkData, MEMORY_MAP_ADDR};
#[repr(C, packed)]
struct MemoryMapEntry {
pub base: u64,
pub size: u64,
pub kind: u32,
}
pub struct MemoryMapIter {
thunk15: extern "C" fn(),
data: ThunkData,
first: bool,
}
impl MemoryMapIter {
pub fn new(thunk15: extern "C" fn()) -> Self {
Self {
thunk15,
data: ThunkData::new(),
first: true,
}
}
}
impl Iterator for MemoryMapIter {
type Item = OsMemoryEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.first {
self.first = false;
} else if self.data.ebx == 0 {
return None;
}
self.data.eax = 0xE820;
self.data.ecx = mem::size_of::<MemoryMapEntry>() as u32;
self.data.edx = 0x534D4150;
self.data.edi = MEMORY_MAP_ADDR as u32;
unsafe {
self.data.with(self.thunk15);
}
//TODO: return error?
assert_eq!({ self.data.eax }, 0x534D4150);
assert_eq!({ self.data.ecx }, mem::size_of::<MemoryMapEntry>() as u32);
let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) };
Some(Self::Item {
base: entry.base,
size: entry.size,
kind: match entry.kind {
0 => OsMemoryKind::Null,
1 => OsMemoryKind::Free,
3 => OsMemoryKind::Reclaim,
_ => OsMemoryKind::Reserved,
},
})
}
}
pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> {
let mut heap_limits = None;
for entry in MemoryMapIter::new(thunk15) {
let heap_start = 1024 * 1024;
if { entry.kind } == OsMemoryKind::Free
&& entry.base <= heap_start as u64
&& (entry.base + entry.size) >= heap_start as u64
{
let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize;
if heap_end >= heap_start {
heap_limits = Some((heap_start, heap_end - heap_start));
}
}
area_add(entry);
}
heap_limits
}
-323
View File
@@ -1,323 +0,0 @@
use alloc::alloc::{alloc_zeroed, Layout};
use core::{convert::TryFrom, mem, ptr, slice};
use linked_list_allocator::LockedHeap;
use spin::Mutex;
use crate::logger::LOGGER;
use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode};
use crate::KernelArgs;
use self::disk::DiskBios;
use self::memory_map::memory_map;
use self::thunk::ThunkData;
use self::vbe::VideoModeIter;
use self::vga::{Vga, VgaTextColor};
#[macro_use]
mod macros;
mod disk;
mod memory_map;
mod panic;
pub(crate) mod serial;
mod thunk;
mod vbe;
mod vga;
// Real mode memory allocation, for use with thunk
// 0x500 to 0x7BFF is free
const DISK_BIOS_ADDR: usize = 0x70000; // 64 KiB at 448 KiB, ends at 512 KiB
const VBE_CARD_INFO_ADDR: usize = 0x1000; // 512 bytes, ends at 0x11FF
const VBE_MODE_INFO_ADDR: usize = 0x1200; // 256 bytes, ends at 0x12FF
const VBE_EDID_ADDR: usize = 0x1300; // 128 bytes, ends at 0x137F
const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397
const DISK_ADDRESS_PACKET_ADDR: usize = 0x1398; // 16 bytes, ends at 0x13A7
const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards
const VGA_ADDR: usize = 0xB8000;
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
pub(crate) static VGA: Mutex<Vga> = Mutex::new(unsafe { Vga::new(VGA_ADDR, 80, 25) });
pub struct OsBios {
boot_disk: usize,
thunk10: extern "C" fn(),
thunk13: extern "C" fn(),
thunk15: extern "C" fn(),
thunk16: extern "C" fn(),
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Rsdp {
signature: [u8; 8],
checksum: u8,
oemid: [u8; 6],
revision: u8,
rsdt_address: u32,
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Xsdp {
rsdp: Rsdp,
length: u32,
xsdt_address: u64,
extended_checksum: u8,
reserved: [u8; 3],
}
unsafe fn search_rsdp(start: usize, end: usize) -> Option<(u64, u64)> {
unsafe {
// Align start up to 16 bytes
let mut addr = start.div_ceil(16) * 16;
// Search until reading the end of the Rsdp would be past the end of the memory area
while addr + mem::size_of::<Rsdp>() <= end {
let rsdp = ptr::read(addr as *const Rsdp);
if &rsdp.signature == b"RSD PTR " {
//TODO: check checksum?
if rsdp.revision == 0 {
return Some((addr as u64, mem::size_of::<Rsdp>() as u64));
} else if rsdp.revision == 2 {
let xsdp = ptr::read(addr as *const Xsdp);
//TODO: check extended checksum?
return Some((addr as u64, xsdp.length as u64));
}
}
// Rsdp is always aligned to 16 bytes
addr += 16;
}
None
}
}
impl Os for OsBios {
type D = DiskBios;
type V = VideoModeIter;
fn name(&self) -> &str {
"x86/BIOS"
}
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
assert!(size != 0);
let page_size = self.page_size();
let pages = size.div_ceil(page_size);
let Some(total_size) = pages.checked_mul(page_size) else {
return ptr::null_mut();
};
let Ok(layout) = Layout::from_size_align(total_size, page_size) else {
return ptr::null_mut();
};
let ptr = unsafe { alloc_zeroed(layout) };
assert!(!ptr.is_null());
ptr
}
fn page_size(&self) -> usize {
4096
}
fn filesystem(
&self,
password_opt: Option<&[u8]>,
) -> syscall::Result<redoxfs::FileSystem<DiskBios>> {
let disk = DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13);
//TODO: get block from partition table
let block = 2 * crate::MIBI as u64 / redoxfs::BLOCK_SIZE;
redoxfs::FileSystem::open(disk, password_opt, Some(block), false)
}
fn hwdesc(&self) -> OsHwDesc {
// See ACPI specification - Finding the RSDP on IA-PC Systems
unsafe {
let ebda_segment = ptr::read(0x40E as *const u16);
let ebda_addr = (ebda_segment as usize) << 4;
if let Some((addr, size)) =
search_rsdp(ebda_addr, ebda_addr + 1024).or(search_rsdp(0xE0000, 0xFFFFF))
{
// Copy to a page
let page_aligned = self.alloc_zeroed_page_aligned(size as usize);
ptr::copy(addr as *const u8, page_aligned, size as usize);
return OsHwDesc::Acpi(page_aligned as u64, size);
}
}
OsHwDesc::NotFound
}
fn video_outputs(&self) -> usize {
//TODO: return 1 only if vbe supported?
1
}
fn video_modes(&self, _output_i: usize) -> VideoModeIter {
VideoModeIter::new(self.thunk10)
}
fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) {
// Set video mode
let mut data = ThunkData::new();
data.eax = 0x4F02;
data.ebx = mode.id;
unsafe {
data.with(self.thunk10);
}
//TODO: check result
}
fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> {
let mut data = ThunkData::new();
data.eax = 0x4F15;
data.ebx = 0x01;
data.ecx = 0;
data.edx = 0;
data.edi = VBE_EDID_ADDR as u32;
unsafe {
data.with(self.thunk10);
}
if data.eax == 0x4F {
let edid = unsafe { slice::from_raw_parts(VBE_EDID_ADDR as *const u8, 128) };
Some((
(edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
(edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
))
} else {
log::warn!("Failed to get VBE EDID: 0x{:X}", { data.eax });
None
}
}
fn get_key(&self) -> OsKey {
// Read keypress
let mut data = ThunkData::new();
unsafe {
data.with(self.thunk16);
}
match (data.eax >> 8) as u8 {
0x4B => OsKey::Left,
0x4D => OsKey::Right,
0x48 => OsKey::Up,
0x50 => OsKey::Down,
0x0E => OsKey::Backspace,
0x53 => OsKey::Delete,
0x1C => OsKey::Enter,
_ => match data.eax as u8 {
0 => OsKey::Other,
b => OsKey::Char(b as char),
},
}
}
fn clear_text(&self) {
let mut vga = VGA.lock();
vga.clear();
}
fn get_text_position(&self) -> (usize, usize) {
let vga = VGA.lock();
(vga.x, vga.y)
}
fn set_text_position(&self, x: usize, y: usize) {
//TODO: ensure this is inside bounds!
let mut vga = VGA.lock();
vga.x = x;
vga.y = y;
}
fn set_text_highlight(&self, highlight: bool) {
let mut vga = VGA.lock();
if highlight {
vga.bg = VgaTextColor::Gray;
vga.fg = VgaTextColor::Black;
} else {
vga.bg = VgaTextColor::Black;
vga.fg = VgaTextColor::Gray;
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn start(
kernel_entry: extern "C" fn(
page_table: usize,
stack: u64,
func: u64,
args: *const KernelArgs,
long_mode: usize,
) -> !,
boot_disk: usize,
thunk10: extern "C" fn(),
thunk13: extern "C" fn(),
thunk15: extern "C" fn(),
thunk16: extern "C" fn(),
) -> ! {
unsafe {
#[cfg(feature = "serial_debug")]
{
let mut com1 = serial::COM1.lock();
com1.init();
com1.write(b"SERIAL\n");
}
{
// Make sure we are in mode 3 (80x25 text mode)
let mut data = ThunkData::new();
data.eax = 0x03;
data.with(thunk10);
}
{
// Disable cursor
let mut data = ThunkData::new();
data.eax = 0x0100;
data.ecx = 0x3F00;
data.with(thunk10);
}
// Clear screen
VGA.lock().clear();
// Set logger
LOGGER.init();
let mut os = OsBios {
boot_disk,
thunk10,
thunk13,
thunk15,
thunk16,
};
let (heap_start, heap_size) = memory_map(os.thunk15).expect("No memory for heap");
ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);
let (page_phys, func, args) = crate::main(&mut os);
kernel_entry(
page_phys,
args.stack_base
+ args.stack_size
+ if crate::KERNEL_64BIT {
crate::arch::x64::PHYS_OFFSET
} else {
crate::arch::x32::PHYS_OFFSET as u64
},
func,
&args,
if crate::KERNEL_64BIT { 1 } else { 0 },
);
}
}
-16
View File
@@ -1,16 +0,0 @@
//! Intrinsics for panic handling
use core::alloc::Layout;
use core::arch::asm;
use core::panic::PanicInfo;
/// Required to handle panics
#[panic_handler]
pub fn rust_begin_unwind(info: &PanicInfo) -> ! {
unsafe {
println!("BOOTLOADER PANIC:\n{}", info);
loop {
asm!("hlt");
}
}
}
-9
View File
@@ -1,9 +0,0 @@
use spin::Mutex;
use syscall::Pio;
use crate::serial_16550::SerialPort;
pub static COM1: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x3F8));
pub static COM2: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x2F8));
pub static COM3: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x3E8));
pub static COM4: Mutex<SerialPort<Pio<u8>>> = Mutex::new(SerialPort::<Pio<u8>>::new(0x2E8));
-52
View File
@@ -1,52 +0,0 @@
use core::ptr;
use super::THUNK_STACK_ADDR;
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct ThunkData {
pub es: u16,
pub edi: u32,
pub esi: u32,
pub ebp: u32,
pub ebx: u32,
pub edx: u32,
pub ecx: u32,
pub eax: u32,
}
impl ThunkData {
pub fn new() -> Self {
Self {
es: 0,
edi: 0,
esi: 0,
ebp: 0,
ebx: 0,
edx: 0,
ecx: 0,
eax: 0,
}
}
pub unsafe fn save(&self) {
unsafe {
ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self);
}
}
pub unsafe fn load(&mut self) {
unsafe {
*self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData);
}
}
pub unsafe fn with(&mut self, f: extern "C" fn()) {
unsafe {
self.save();
f();
self.load();
}
}
}
-151
View File
@@ -1,151 +0,0 @@
use core::ptr;
use log::error;
use crate::os::OsVideoMode;
use super::{ThunkData, VBE_CARD_INFO_ADDR, VBE_MODE_INFO_ADDR};
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct VbeFarPtr {
pub offset: u16,
pub segment: u16,
}
impl VbeFarPtr {
pub unsafe fn as_ptr<T>(&self) -> *const T {
(((self.segment as usize) << 4) + (self.offset as usize)) as *const T
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct VbeCardInfo {
pub signature: [u8; 4],
pub version: u16,
pub oemstring: VbeFarPtr,
pub capabilities: [u8; 4],
pub videomodeptr: VbeFarPtr,
pub totalmemory: u16,
pub oemsoftwarerev: u16,
pub oemvendornameptr: VbeFarPtr,
pub oemproductnameptr: VbeFarPtr,
pub oemproductrevptr: VbeFarPtr,
pub reserved: [u8; 222],
pub oemdata: [u8; 256],
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct VbeModeInfo {
pub attributes: u16,
pub win_a: u8,
pub win_b: u8,
pub granularity: u16,
pub winsize: u16,
pub segment_a: u16,
pub segment_b: u16,
pub winfuncptr: u32,
pub bytesperscanline: u16,
pub xresolution: u16,
pub yresolution: u16,
pub xcharsize: u8,
pub ycharsize: u8,
pub numberofplanes: u8,
pub bitsperpixel: u8,
pub numberofbanks: u8,
pub memorymodel: u8,
pub banksize: u8,
pub numberofimagepages: u8,
pub unused: u8,
pub redmasksize: u8,
pub redfieldposition: u8,
pub greenmasksize: u8,
pub greenfieldposition: u8,
pub bluemasksize: u8,
pub bluefieldposition: u8,
pub rsvdmasksize: u8,
pub rsvdfieldposition: u8,
pub directcolormodeinfo: u8,
pub physbaseptr: u32,
pub offscreenmemoryoffset: u32,
pub offscreenmemsize: u16,
pub reserved: [u8; 206],
}
pub struct VideoModeIter {
thunk10: extern "C" fn(),
mode_ptr: *const u16,
}
impl VideoModeIter {
pub fn new(thunk10: extern "C" fn()) -> Self {
// Get card info
let mut data = ThunkData::new();
data.eax = 0x4F00;
data.edi = VBE_CARD_INFO_ADDR as u32;
unsafe {
data.with(thunk10);
}
let mode_ptr = if data.eax == 0x004F {
let card_info = unsafe { ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo) };
unsafe { card_info.videomodeptr.as_ptr::<u16>() }
} else {
error!("Failed to read VBE card info: 0x{:04X}", { data.eax });
ptr::null()
};
Self { thunk10, mode_ptr }
}
}
impl Iterator for VideoModeIter {
type Item = OsVideoMode;
fn next(&mut self) -> Option<Self::Item> {
if self.mode_ptr.is_null() {
return None;
}
loop {
// Set bit 14 to get linear frame buffer
let mode = unsafe { *self.mode_ptr } | (1 << 14);
if mode == 0xFFFF {
return None;
}
self.mode_ptr = unsafe { self.mode_ptr.add(1) };
// Get mode info
let mut data = ThunkData::new();
data.eax = 0x4F01;
data.ecx = mode as u32;
data.edi = VBE_MODE_INFO_ADDR as u32;
unsafe {
data.with(self.thunk10);
}
if data.eax == 0x004F {
let mode_info = unsafe { ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo) };
// We only support 32-bits per pixel modes
if mode_info.bitsperpixel != 32 {
continue;
}
let width = mode_info.xresolution as u32;
let height = mode_info.yresolution as u32;
//TODO: support stride that is not a multiple of 4
let stride = mode_info.bytesperscanline as u32 / 4;
return Some(OsVideoMode {
id: mode as u32,
width,
height,
stride,
base: mode_info.physbaseptr as u64,
});
} else {
error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, {
data.eax
});
}
}
}
}
-121
View File
@@ -1,121 +0,0 @@
use core::{fmt, slice};
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct VgaTextBlock {
pub char: u8,
pub color: u8,
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum VgaTextColor {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Purple = 5,
Brown = 6,
Gray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
LightPurple = 13,
Yellow = 14,
White = 15,
}
pub struct Vga {
pub base: usize,
pub width: usize,
pub height: usize,
pub x: usize,
pub y: usize,
pub bg: VgaTextColor,
pub fg: VgaTextColor,
}
impl Vga {
pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self {
Self {
base,
width,
height,
x: 0,
y: 0,
bg: VgaTextColor::Black,
fg: VgaTextColor::Gray,
}
}
pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] {
unsafe {
slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height)
}
}
pub fn clear(&mut self) {
self.x = 0;
self.y = 0;
let blocks = unsafe { self.blocks() };
for i in 0..blocks.len() {
blocks[i] = VgaTextBlock {
char: 0,
color: ((self.bg as u8) << 4) | (self.fg as u8),
};
}
}
}
impl fmt::Write for Vga {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let blocks = unsafe { self.blocks() };
for c in s.chars() {
if self.x >= self.width {
self.x = 0;
self.y += 1;
}
while self.y >= self.height {
for y in 1..self.height {
for x in 0..self.width {
let i = y * self.width + x;
let j = i - self.width;
blocks[j] = blocks[i];
if y + 1 == self.height {
blocks[i].char = 0;
}
}
}
self.y -= 1;
}
match c {
'\x08' => {
if self.x > 0 {
self.x -= 1;
}
}
'\r' => {
self.x = 0;
}
'\n' => {
self.x = 0;
self.y += 1;
}
_ => {
let i = self.y * self.width + self.x;
if let Some(block) = blocks.get_mut(i) {
block.char = c as u8;
block.color = ((self.bg as u8) << 4) | (self.fg as u8);
}
self.x += 1;
}
}
}
Ok(())
}
}
-99
View File
@@ -1,99 +0,0 @@
use redoxfs::Disk;
#[cfg(all(target_arch = "x86", target_os = "none"))]
pub use self::bios::*;
#[cfg(all(target_arch = "x86", target_os = "none"))]
#[macro_use]
mod bios;
#[cfg(any(target_arch = "riscv64", target_os = "uefi"))]
#[allow(unused_imports)]
pub use self::uefi::*;
#[cfg(any(target_arch = "riscv64", target_os = "uefi"))]
#[macro_use]
mod uefi;
#[derive(Clone, Copy, Debug)]
pub enum OsHwDesc {
Acpi(u64, u64),
DeviceTree(u64, u64),
NotFound,
}
#[derive(Clone, Copy, Debug)]
pub enum OsKey {
Left,
Right,
Up,
Down,
Backspace,
Delete,
Enter,
Char(char),
Timeout,
Other,
}
// Keep synced with BootloaderMemoryKind in kernel
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u64)]
pub enum OsMemoryKind {
Null = 0,
Free = 1,
Reclaim = 2,
Reserved = 3,
}
// Keep synced with BootloaderMemoryEntry in kernel
#[derive(Clone, Copy, Debug)]
#[repr(C, packed(8))]
pub struct OsMemoryEntry {
pub base: u64,
pub size: u64,
pub kind: OsMemoryKind,
}
#[derive(Clone, Copy, Debug)]
pub struct OsVideoMode {
pub id: u32,
pub width: u32,
pub height: u32,
pub stride: u32,
pub base: u64,
}
pub trait Os {
type D: Disk;
type V: Iterator<Item = OsVideoMode>;
fn name(&self) -> &str;
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8;
#[allow(dead_code)]
fn page_size(&self) -> usize;
fn filesystem(
&self,
password_opt: Option<&[u8]>,
) -> syscall::Result<redoxfs::FileSystem<Self::D>>;
fn hwdesc(&self) -> OsHwDesc;
fn video_outputs(&self) -> usize;
fn video_modes(&self, output_i: usize) -> Self::V;
fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode);
fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>;
fn get_key(&self) -> OsKey;
fn get_key_timeout(&self, _milliseconds: usize) -> OsKey {
self.get_key()
}
fn clear_text(&self);
fn get_text_position(&self) -> (usize, usize);
fn set_text_position(&self, x: usize, y: usize);
fn set_text_highlight(&self, highlight: bool);
}
-110
View File
@@ -1,110 +0,0 @@
use core::slice;
use uefi::guid::{ACPI_20_TABLE_GUID, ACPI_TABLE_GUID};
use crate::Os;
struct Invalid;
fn validate_rsdp(address: usize, _v2: bool) -> core::result::Result<usize, Invalid> {
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct Rsdp {
signature: [u8; 8], // b"RSD PTR "
chksum: u8,
oem_id: [u8; 6],
revision: u8,
rsdt_addr: u32,
// the following fields are only available for ACPI 2.0, and are reserved otherwise
length: u32,
xsdt_addr: u64,
extended_chksum: u8,
_rsvd: [u8; 3],
}
// paging is not enabled at this stage; we can just read the physical address here.
let rsdp_bytes =
unsafe { core::slice::from_raw_parts(address as *const u8, core::mem::size_of::<Rsdp>()) };
let rsdp = unsafe {
(rsdp_bytes.as_ptr() as *const Rsdp)
.as_ref::<'static>()
.unwrap()
};
log::debug!("RSDP: {:?}", rsdp);
if rsdp.signature != *b"RSD PTR " {
return Err(Invalid);
}
let mut base_sum = 0u8;
for base_byte in &rsdp_bytes[..20] {
base_sum = base_sum.wrapping_add(*base_byte);
}
if base_sum != 0 {
return Err(Invalid);
}
if rsdp.revision == 2 {
let mut extended_sum = 0u8;
for byte in rsdp_bytes {
extended_sum = extended_sum.wrapping_add(*byte);
}
if extended_sum != 0 {
return Err(Invalid);
}
}
let length = if rsdp.revision == 2 {
rsdp.length as usize
} else {
core::mem::size_of::<Rsdp>()
};
Ok(length)
}
pub(crate) fn find_acpi_table_pointers(os: &impl Os) -> Option<(u64, u64)> {
let cfg_tables = std::system_table().config_tables();
let mut acpi = None;
let mut acpi2 = None;
for cfg_table in cfg_tables.iter() {
if cfg_table.VendorGuid == ACPI_TABLE_GUID {
match validate_rsdp(cfg_table.VendorTable, false) {
Ok(length) => {
acpi = Some(unsafe {
core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length)
});
}
Err(_) => log::warn!(
"Found RSDP that was not valid at {:p}",
cfg_table.VendorTable as *const u8
),
}
} else if cfg_table.VendorGuid == ACPI_20_TABLE_GUID {
match validate_rsdp(cfg_table.VendorTable, true) {
Ok(length) => {
acpi2 = Some(unsafe {
core::slice::from_raw_parts(cfg_table.VendorTable as *const u8, length)
});
}
Err(_) => log::warn!(
"Found RSDP that was not valid at {:p}",
cfg_table.VendorTable as *const u8
),
}
}
}
let rsdp_area = acpi2.or(acpi).unwrap_or(&[]);
if !rsdp_area.is_empty() {
unsafe {
// Copy to page aligned area
let size = rsdp_area.len();
let base = os.alloc_zeroed_page_aligned(size);
slice::from_raw_parts_mut(base, size).copy_from_slice(rsdp_area);
Some((base as u64, size as u64))
}
} else {
None
}
}
-236
View File
@@ -1,236 +0,0 @@
use core::{arch::asm, fmt::Write, mem, slice};
use uefi::status::Result;
use crate::{
arch::{ENTRY_ADDRESS_MASK, PAGE_ENTRIES, PF_PRESENT, PF_TABLE, PHYS_OFFSET},
logger::LOGGER,
KernelArgs,
};
use super::super::{memory_map::memory_map, OsEfi};
unsafe fn dump_page_tables(table_phys: u64, table_virt: u64, table_level: u64) {
unsafe {
let entries = slice::from_raw_parts(table_phys as *const u64, PAGE_ENTRIES);
for (i, entry) in entries.iter().enumerate() {
let phys = entry & ENTRY_ADDRESS_MASK;
let flags = entry & !ENTRY_ADDRESS_MASK;
if flags & PF_PRESENT == 0 {
continue;
}
let mut shift = 39u64;
for _ in 0..table_level {
shift -= 9;
print!("\t");
}
let virt = table_virt + (i as u64) << shift;
println!(
"index {} virt {:#x}: phys {:#x} flags {:#x}",
i, virt, phys, flags
);
if table_level < 3 && flags & PF_TABLE == PF_TABLE {
dump_page_tables(phys, virt, table_level + 1);
}
}
}
}
unsafe extern "C" fn kernel_entry(
page_phys: usize,
stack: u64,
func: u64,
args: *const KernelArgs,
) -> ! {
unsafe {
// Read memory map and exit boot services
memory_map().exit_boot_services();
let currentel: u64;
asm!(
"mrs {0}, currentel", // Read current exception level
out(reg) currentel,
);
if currentel == (2 << 2) {
// Need to drop from EL2 to EL1
// Allow access to timers
asm!(
"mrs {0}, cnthctl_el2",
"orr {0}, {0}, #0x3",
"msr cnthctl_el2, {0}",
"msr cntvoff_el2, xzr",
out(reg) _
);
// Initialize ID registers
asm!(
"mrs {0}, midr_el1",
"msr vpidr_el2, {0}",
"mrs {0}, mpidr_el1",
"msr vmpidr_el2, {0}",
out(reg) _
);
// Disable traps
asm!(
"msr cptr_el2, {0}",
"msr hstr_el2, xzr",
in(reg) 0x33FF as u64
);
// Enable floating point
asm!(
"msr cpacr_el1, {0}",
in(reg) (3 << 20) as u64
);
// Set EL1 system control register
asm!(
"msr sctlr_el1, {0}",
in(reg) 0x30d00800 as u64
);
// Set EL1 stack and VBAR
asm!(
"mov {0}, sp",
"msr sp_el1, {0}",
"mrs {0}, vbar_el2",
"msr vbar_el1, {0}",
out(reg) _
);
// Configure execution state of EL1 as aarch64 and disable hypervisor call.
asm!(
"msr hcr_el2, {0}",
in(reg) ((1u64 << 31) | (1u64 << 29)),
);
// Set saved program status register
asm!(
"msr spsr_el2, {0}",
in(reg) 0x3C5 as u64
);
// Switch to EL1
asm!(
"adr {0}, 1f",
"msr elr_el2, {0}",
"eret",
"1:",
out(reg) _
);
} else if currentel == (1 << 2) {
// Already in EL1
} else {
//TODO: what to do if not EL2 or already EL1?
loop {
asm!("wfi");
}
}
// Disable MMU
asm!(
"mrs {0}, sctlr_el1", // Read system control register
"bic {0}, {0}, 1", // Clear MMU enable bit
"msr sctlr_el1, {0}", // Write system control register
"isb", // Instruction sync barrier
out(reg) _,
);
// Set MAIR
// You can think about MAIRs as of an array with 8 elements each of 8 bits long.
// You can store inside MAIRs up to 8 attributes sets and reffer them by the index 0..7 stored in INDX (AttrIndx) field of the table descriptor.
// https://lowenware.com/blog/aarch64-mmu-programming/
// https://developer.arm.com/documentation/102376/0200/Describing-memory-in-AArch64
// https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/MAIR-EL1--Memory-Attribute-Indirection-Register--EL1-
// Attribute 0 (0xFF) - normal memory, caches are enabled
// Attribute 1 (0x44) - normal memory, caches are disabled. Atomics wouldn't work here if memory doesn't support exclusive access (most real hardware don't)
// Attribute 2 (0x00) - nGnRnE device memory, caches are disabled, gathering, re-ordering, and early write acknowledgement aren't allowed.
asm!(
"msr mair_el1, {0}",
in(reg) 0x00000000000044FF as u64, // MAIR: Arrange for Device, Normal Non-Cache, Normal Write-Back access types
);
// Set TCR
asm!(
"mrs {1}, id_aa64mmfr0_el1", // Read memory model feature register
"bfi {0}, {1}, #32, #3",
"msr tcr_el1, {0}", // Write translation control register
"isb", // Instruction sync barrier
in(reg) 0x1085100510u64, // TCR: (TxSZ, ASID_16, TG1_4K, Cache Attrs, SMP Attrs)
out(reg) _,
);
// Set page tables
asm!(
"dsb sy", // Data sync barrier
"msr ttbr1_el1, {0}", // Set higher half page table
"msr ttbr0_el1, {0}", // Set lower half page table
"isb", // Instruction sync barrier
"dsb ishst", // Data sync barrier, only for stores, and only for inner shareable domain
"tlbi vmalle1is", // Invalidate TLB
"dsb ish", // Dta sync bariar, only for inner shareable domain
"isb", // Instruction sync barrier
in(reg) page_phys,
);
// Enable MMU
asm!(
"mrs {2}, sctlr_el1", // Read system control register
"bic {2}, {2}, {0}", // Clear bits
"orr {2}, {2}, {1}", // Set bits
"msr sctlr_el1, {2}", // Write system control register
"isb", // Instruction sync barrier
in(reg) 0x32802c2u64, // Clear SCTLR bits: (EE, EOE, IESB, WXN, UMA, ITD, THEE, A)
in(reg) 0x3485d13du64, // Set SCTLR bits: (LSMAOE, nTLSMD, UCI, SPAN, nTWW, nTWI, UCT, DZE, I, SED, SA0, SA, C, M, CP15BEN)
out(reg) _,
);
// Set stack
asm!("mov sp, {}", in(reg) stack);
// Call kernel entry
let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func);
entry_fn(args);
}
}
pub fn main() -> Result<()> {
LOGGER.init();
let mut os = OsEfi::new();
// Disable cursor
let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
let currentel: u64;
unsafe {
asm!(
"mrs {0}, currentel", // Read current exception level
out(reg) currentel,
);
}
log::info!("Currently in EL{}", (currentel >> 2) & 3);
let (page_phys, func, args) = crate::main(&mut os);
unsafe {
let stack = args.stack_base + args.stack_size + PHYS_OFFSET;
// dump_page_tables(page_phys as _, 0, 0);
println!(
"kernel_entry({:#x}, {:#x}, {:#x}, {:p})",
page_phys, stack, func, &args
);
println!("{:#x?}", args);
kernel_entry(page_phys, stack, func, &args);
}
}
pub fn disable_interrupts() {
unsafe {
asm!("msr daifset, #2");
}
}
-14
View File
@@ -1,14 +0,0 @@
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use self::aarch64::*;
#[cfg(target_arch = "x86_64")]
mod x86_64;
#[cfg(target_arch = "x86_64")]
pub use self::x86_64::*;
#[cfg(target_arch = "riscv64")]
mod riscv64;
#[cfg(target_arch = "riscv64")]
pub use self::riscv64::*;
-45
View File
@@ -1,45 +0,0 @@
use std::proto::Protocol;
use uefi::guid::Guid;
use uefi::status::{Result, Status};
#[derive(Debug)]
#[repr(C)]
struct RiscVEfiBootProtocol {
pub revision: u64,
pub efi_get_boot_hartid:
unsafe extern "efiapi" fn(this: *mut Self, phartid: *mut usize) -> Status,
}
impl RiscVEfiBootProtocol {
pub const GUID: Guid = Guid::parse_str("ccd15fec-6f73-4eec-8395-3e69e4b940bf");
// pub const REVISION: u64 = 0x00010000;
}
struct RiscVEfiBoot(pub &'static mut RiscVEfiBootProtocol);
impl Protocol<RiscVEfiBootProtocol> for RiscVEfiBoot {
fn guid() -> Guid {
RiscVEfiBootProtocol::GUID
}
fn new(inner: &'static mut RiscVEfiBootProtocol) -> Self {
Self(inner)
}
}
impl RiscVEfiBoot {
pub fn efi_get_boot_hartid(&mut self) -> Result<usize> {
let mut boot_hartid: usize = 0;
match unsafe { (self.0.efi_get_boot_hartid)(self.0, &mut boot_hartid) } {
ok if ok.is_success() => Ok(boot_hartid),
err => Err(err),
}
}
}
pub fn efi_get_boot_hartid() -> Result<usize> {
let handles = RiscVEfiBoot::locate_handle()?;
let handle = handles.first().ok_or(Status::NOT_FOUND)?;
let mut proto = RiscVEfiBoot::handle_protocol(*handle)?;
proto.efi_get_boot_hartid()
}
-113
View File
@@ -1,113 +0,0 @@
use core::arch::{global_asm, naked_asm};
/// Unfortunately this can't be written in Rust because it might use some not-yet
/// relocated data such as jump tables
#[unsafe(naked)]
#[unsafe(no_mangle)]
extern "C" fn coff_relocate(dynentry: *const u8, base: usize) -> usize {
unsafe {
naked_asm!(
"
mv t4, zero // RELA
li t5, -1 // RELASZ
li t6, -1 // RELAENT
5:
ld t0, 0(a0)
beqz t0, 6f
addi a0, a0, 16
addi t0, t0, -4
bltz t0, 3f // fail on DT_NEEDED=1, DT_PLTRELSZ=2, DT_PLTGOT=3
addi t0, t0, -3
bltz t0, 5b // skip DT_HASH=4, DT_STRTAB=5, DT_SYMTAB=6
bnez t0, 2f
ld t4, -8(a0) // DT_RELA=7
j 5b
2: addi t0, t0, -1 // DT_RELASZ=8
bnez t0, 2f
ld t5, -8(a0)
j 5b
2: addi t0, t0, -1 // DT_RELAENT=9
bnez t0, 2f
ld t6, -8(a0)
j 5b
2: addi t0, t0, -3
bltz t0, 5b // skip DT_STRSZ=10, DT_SYMENT=11
addi t0, t0, -2
bltz t0, 3f // fail on DT_INIT=12, DT_FINI=13
beqz t0, 5b // skip DT_SONAME=14
2: addi t0, t0, -2
bltz t0, 3f // fail on DT_RPATH
beqz t0, 5b // skip SYMBOLIC=16
li t1, 0x6ffffef5-16
sub t0, t0, t1
beqz t0, 5b // skip DT_GNU_HASH=0x6ffffef5
nop
3: // error
mv a0, zero
ret
6:
bnez t4, 2f
4: // success
li a0, 1
ret
2: bltz t5, 3b
blez t6, 3b
add t4, t4, a1
add t5, t5, t4
7:
bge t4, t5, 4b
ld t0, 0(t4) // r_offset
add t0, t0, a1
lwu t1, 8(t4) // r_type
ld t2, 16(t4) // r_addend
add t4, t4, t6
addi t1, t1, -3 // R_RISCV_RELATIVE=3
bnez t1, 3b
add t2, t2, a1 // RELATIVE: *value = base + addend
sd t2, 0(t0)
j 7b
"
)
}
}
global_asm!(
r#"
.global coff_start
coff_start:
.option norelax
addi sp, sp, -24
sd a0, 0(sp)
sd a1, 8(sp)
sd ra, 16(sp)
lla a0, _DYNAMIC
lla a1, ImageBase // actual loaded image base to relocate to
jal coff_relocate
.option relax
mv t0, a0
ld a0, 0(sp)
ld a1, 8(sp)
ld ra, 16(sp)
addi sp, sp, 24
beqz t0, 2f
j efi_main
2: ret
"#
);
// GNU-EFI .reloc trick to make objcopy say we are relocatable
global_asm!(
r#"
.section .data
DUMMY_RELOCATION: .4byte 0
.section .reloc, "a"
2:
.4byte DUMMY_RELOCATION - ImageBase
.4byte 12
.4byte 0
"#
);
-70
View File
@@ -1,70 +0,0 @@
use crate::KernelArgs;
use crate::arch::PHYS_OFFSET;
use crate::arch::SATP_BITS;
use crate::logger::LOGGER;
use crate::os::OsEfi;
use crate::os::uefi::memory_map::memory_map;
use core::arch::asm;
use core::mem;
use uefi::status::Result;
mod boot_protocol;
mod coff_helper;
pub use boot_protocol::*;
unsafe extern "C" fn kernel_entry(
page_phys: usize,
stack: u64,
func: u64,
args: *const KernelArgs,
) -> ! {
unsafe {
// Set page tables
asm!(
"csrw satp, {0}",
"sfence.vma",
in(reg) (page_phys >> 12 | SATP_BITS << 60)
);
let entry_fn: extern "C" fn(*const KernelArgs) -> ! = mem::transmute(func);
// Set stack and go to kernel
asm!("mv sp, {0}",
"mv a0, {1}",
"jalr {2}",
in(reg) stack,
in(reg) args,
in(reg) entry_fn
);
loop {}
}
}
pub fn main() -> Result<()> {
LOGGER.init();
let mut os = OsEfi::new();
// Disable cursor
let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
let (page_phys, func, args) = crate::main(&mut os);
unsafe {
memory_map().exit_boot_services();
kernel_entry(
page_phys,
args.stack_base + args.stack_size + PHYS_OFFSET,
func,
&args,
);
}
}
pub fn disable_interrupts() {
unsafe {
asm!("csrci sstatus, 2");
}
}
-82
View File
@@ -1,82 +0,0 @@
use core::{arch::asm, mem};
use uefi::status::Result;
use x86::{
controlregs::{self, Cr0, Cr4},
msr,
};
use crate::{logger::LOGGER, KernelArgs};
use super::super::{memory_map::memory_map, OsEfi};
unsafe extern "C" fn kernel_entry(
page_phys: usize,
stack: u64,
func: u64,
args: *const KernelArgs,
) -> ! {
unsafe {
// Read memory map and exit boot services
memory_map().exit_boot_services();
// Enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
let mut cr4 = controlregs::cr4();
cr4 |= Cr4::CR4_ENABLE_SSE
| Cr4::CR4_ENABLE_GLOBAL_PAGES
| Cr4::CR4_ENABLE_PAE
| Cr4::CR4_ENABLE_PSE;
controlregs::cr4_write(cr4);
// Enable Long mode and NX bit
let mut efer = msr::rdmsr(msr::IA32_EFER);
efer |= 1 << 11 | 1 << 8;
msr::wrmsr(msr::IA32_EFER, efer);
// Set new page map
controlregs::cr3_write(page_phys as u64);
// Enable paging, write protect kernel, protected mode
let mut cr0 = controlregs::cr0();
cr0 |= Cr0::CR0_ENABLE_PAGING | Cr0::CR0_WRITE_PROTECT | Cr0::CR0_PROTECTED_MODE;
controlregs::cr0_write(cr0);
// Set stack
asm!("mov rsp, {}", in(reg) stack);
// Call kernel entry
let entry_fn: extern "sysv64" fn(*const KernelArgs) -> ! = mem::transmute(func);
entry_fn(args);
}
}
pub fn main() -> Result<()> {
LOGGER.init();
let mut os = OsEfi::new();
// Disable cursor
let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
let (page_phys, func, args) = crate::main(&mut os);
unsafe {
kernel_entry(
page_phys,
args.stack_base
+ args.stack_size
+ if crate::KERNEL_64BIT {
crate::arch::x64::PHYS_OFFSET
} else {
crate::arch::x32::PHYS_OFFSET as u64
},
func,
&args,
);
}
}
pub fn disable_interrupts() {
unsafe {
asm!("cli");
}
}
-647
View File
@@ -1,647 +0,0 @@
use alloc::{string::String, vec, vec::Vec};
use core::{fmt::Write, mem, ptr, slice};
use uefi::{
device::{
DevicePath, DevicePathAcpiType, DevicePathBbsType, DevicePathEndType,
DevicePathHardwareType, DevicePathMediaType, DevicePathMessagingType, DevicePathType,
},
guid::Guid,
status::Status,
Handle,
};
use uefi_std::{fs::FileSystem, loaded_image::LoadedImage, proto::Protocol};
use super::disk::{DiskEfi, DiskOrFileEfi};
use redoxfs::Disk;
const LINUX_FS_GUID: [u8; 16] = [
0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47,
0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4,
];
fn gpt_find_redoxfs_offset(disk: &mut DiskEfi) -> u64 {
let block_size = disk.0.Media.BlockSize as u64;
if block_size == 0 {
return 2 * crate::MIBI as u64;
}
let mut header_buf = [0u8; 512];
let lba1_block = block_size / redoxfs::BLOCK_SIZE;
let header_read_len = if 512 <= redoxfs::BLOCK_SIZE as usize {
redoxfs::BLOCK_SIZE as usize
} else {
512
};
let mut read_buf = vec![0u8; header_read_len];
if unsafe { disk.read_at(lba1_block, &mut read_buf) }.is_err() {
log::warn!("GPT: failed to read LBA 1");
return 2 * crate::MIBI as u64;
}
let copy_len = 512.min(read_buf.len());
header_buf[..copy_len].copy_from_slice(&read_buf[..copy_len]);
if &header_buf[0..8] != b"EFI PART" {
log::warn!("GPT: no valid signature at LBA 1");
return 2 * crate::MIBI as u64;
}
let read_le_u32 = |off: usize| -> u32 {
u32::from_le_bytes(header_buf[off..off + 4].try_into().unwrap_or([0; 4]))
};
let read_le_u64 = |off: usize| -> u64 {
u64::from_le_bytes(header_buf[off..off + 8].try_into().unwrap_or([0; 8]))
};
let _revision = read_le_u32(8);
let _header_size = read_le_u32(12);
let partition_entry_start_lba = read_le_u64(72);
let num_entries = read_le_u32(80);
let entry_size = read_le_u32(84);
if num_entries == 0 || entry_size == 0 || num_entries > 128 {
log::warn!("GPT: invalid entry count {} or size {}", num_entries, entry_size);
return 2 * crate::MIBI as u64;
}
let entries_byte_offset = partition_entry_start_lba * block_size;
let entries_start_block = entries_byte_offset / redoxfs::BLOCK_SIZE;
let total_entries_bytes = num_entries as usize * entry_size as usize;
let read_size = total_entries_bytes.min(4096);
let mut entries_buf = vec![0u8; read_size + redoxfs::BLOCK_SIZE as usize];
let entries_read_blocks = (read_size as u64).div_ceil(redoxfs::BLOCK_SIZE) as usize;
if unsafe { disk.read_at(entries_start_block, &mut entries_buf[..entries_read_blocks * redoxfs::BLOCK_SIZE as usize]) }.is_err() {
log::warn!("GPT: failed to read partition entries");
return 2 * crate::MIBI as u64;
}
let entries_per_chunk = read_size / entry_size as usize;
for i in 0..entries_per_chunk.min(num_entries as usize) {
let off = i * entry_size as usize;
if off + entry_size as usize > entries_buf.len() {
break;
}
let entry = &entries_buf[off..off + entry_size as usize];
let type_guid = &entry[0..16];
if type_guid == [0u8; 16] {
continue;
}
if type_guid == LINUX_FS_GUID {
let first_lba = u64::from_le_bytes(
entry[32..40].try_into().unwrap_or([0; 8]),
);
let offset_bytes = first_lba * block_size;
log::debug!("GPT: found REDOXFS/Linux partition at LBA {} (offset {} bytes)", first_lba, offset_bytes);
return offset_bytes;
}
}
log::warn!("GPT: no Linux filesystem partition found, falling back to 2 MiB");
2 * crate::MIBI as u64
}
fn gpt_find_redoxfs_offset_from_slice(data: &[u8]) -> u64 {
if data.len() < 520 {
return 2 * crate::MIBI as u64;
}
let header = &data[512..];
if &header[0..8] != b"EFI PART" {
return 2 * crate::MIBI as u64;
}
let read_le_u32 = |off: usize| -> u32 {
u32::from_le_bytes(header[off..off + 4].try_into().unwrap_or([0; 4]))
};
let read_le_u64 = |off: usize| -> u64 {
u64::from_le_bytes(header[off..off + 8].try_into().unwrap_or([0; 8]))
};
let partition_entry_start_lba = read_le_u64(72);
let num_entries = read_le_u32(80);
let entry_size = read_le_u32(84);
if num_entries == 0 || entry_size == 0 || num_entries > 128 {
return 2 * crate::MIBI as u64;
}
let gpt_lba_size: u64 = 512;
let entries_byte_offset = partition_entry_start_lba * gpt_lba_size;
for i in 0..num_entries as usize {
let off = entries_byte_offset as usize + i * entry_size as usize;
if off + entry_size as usize > data.len() {
break;
}
let entry = &data[off..off + entry_size as usize];
let type_guid = &entry[0..16];
if type_guid == [0u8; 16] {
continue;
}
if type_guid == LINUX_FS_GUID {
let first_lba = u64::from_le_bytes(
entry[32..40].try_into().unwrap_or([0; 8]),
);
return first_lba * gpt_lba_size;
}
}
2 * crate::MIBI as u64
}
#[derive(Debug)]
enum DevicePathRelation {
This,
Parent(usize),
Child(usize),
None,
}
fn device_path_relation(a_path: &DevicePath, b_path: &DevicePath) -> DevicePathRelation {
let mut a_iter = DevicePathIter::new(a_path);
let mut b_iter = DevicePathIter::new(b_path);
loop {
match (a_iter.next(), b_iter.next()) {
(None, None) => return DevicePathRelation::This,
(None, Some(_)) => return DevicePathRelation::Parent(b_iter.count()),
(Some(_), None) => return DevicePathRelation::Child(a_iter.count()),
(Some((a_node, a_data)), Some((b_node, b_data))) => {
if a_node.Type != b_node.Type {
return DevicePathRelation::None;
}
if a_node.SubType != b_node.SubType {
return DevicePathRelation::None;
}
if a_data != b_data {
return DevicePathRelation::None;
}
}
}
}
}
fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option<Vec<u8>> {
let mut esp_fs = match FileSystem::handle_protocol(esp_handle) {
Ok(esp_fs) => esp_fs,
Err(err) => {
log::warn!("Failed to find SimpleFileSystem protocol: {:?}", err);
return None;
}
};
let mut root = match esp_fs.root() {
Ok(root) => root,
Err(err) => {
log::warn!("Failed to open ESP filesystem: {:?}", err);
return None;
}
};
const fn as_utf16_str<const N: usize>(s: [u8; N]) -> [u16; N] {
let mut ret = [0; N];
let mut i = 0;
while i < N {
ret[i] = s[i] as u16;
i += 1;
}
ret
}
let filename = const { &as_utf16_str(*b"redox-live.iso\0") };
let mut live_image = match root.open(filename) {
Ok(live_image) => live_image,
Err(Status::NOT_FOUND) => return None,
Err(err) => {
log::warn!(
"Failed to open {}\\redox-live.iso: {:?}",
device_path_to_string(esp_device_path),
err
);
return None;
}
};
let mut buffer = Vec::new();
live_image.read_to_end(&mut buffer).unwrap();
Some(buffer)
}
pub struct DiskDevice {
pub handle: Handle,
pub disk: DiskOrFileEfi,
pub partition_offset: u64,
pub device_path: DevicePathProtocol,
pub file_path: Option<&'static str>,
}
pub fn disk_device_priority() -> Vec<DiskDevice> {
// Get the handle of the partition this program was loaded from, which should be the ESP
let esp_handle = match LoadedImage::handle_protocol(std::handle()) {
Ok(loaded_image) => loaded_image.0.DeviceHandle,
Err(err) => {
log::warn!("Failed to find LoadedImage protocol: {:?}", err);
return Vec::new();
}
};
// Get the device path of the ESP
let esp_device_path = match DevicePathProtocol::handle_protocol(esp_handle) {
Ok(ok) => ok,
Err(err) => {
log::warn!(
"Failed to find device path protocol on {:?}: {:?}",
esp_handle,
err
);
return Vec::new();
}
};
if cfg!(feature = "live") {
if let Some(buffer) = esp_live_image(esp_handle, esp_device_path.0) {
let partition_offset = if buffer.len() > 520 && &buffer[512..520] == b"EFI PART" {
gpt_find_redoxfs_offset_from_slice(&buffer)
} else {
0
};
return vec![DiskDevice {
handle: esp_handle,
partition_offset,
disk: DiskOrFileEfi::File(buffer),
device_path: esp_device_path,
file_path: Some("redox-live.iso"),
}];
}
}
// Get all block I/O handles along with their block I/O implementations and device paths
let handles = match DiskEfi::locate_handle() {
Ok(ok) => ok,
Err(err) => {
log::warn!("Failed to find block I/O handles: {:?}", err);
Vec::new()
}
};
let mut devices = Vec::with_capacity(handles.len());
for handle in handles {
let mut disk = match DiskEfi::handle_protocol(handle) {
Ok(ok) => ok,
Err(err) => {
log::warn!(
"Failed to find block I/O protocol on {:?}: {:?}",
handle,
err
);
continue;
}
};
if !disk.0.Media.MediaPresent {
continue;
}
let device_path = match DevicePathProtocol::handle_protocol(handle) {
Ok(ok) => ok,
Err(err) => {
log::warn!(
"Failed to find device path protocol on {:?}: {:?}",
handle,
err
);
continue;
}
};
let partition_offset = if disk.0.Media.LogicalPartition {
0
} else {
gpt_find_redoxfs_offset(&mut disk)
};
devices.push(DiskDevice {
handle,
partition_offset,
disk: DiskOrFileEfi::Disk(disk),
device_path,
file_path: None,
});
}
// Find possible boot disks
let mut boot_disks = Vec::with_capacity(1);
{
let mut i = 0;
while i < devices.len() {
if let DevicePathRelation::Parent(0) =
device_path_relation(devices[i].device_path.0, esp_device_path.0)
{
boot_disks.push(devices.remove(i));
continue;
}
i += 1;
}
}
// Find all children of possible boot devices
let mut priority = Vec::with_capacity(devices.capacity());
for boot_disk in boot_disks {
let mut i = 0;
while i < devices.len() {
// Only prioritize non-ESP devices
if devices[i].handle != esp_handle {
if let DevicePathRelation::Child(0) =
device_path_relation(devices[i].device_path.0, boot_disk.device_path.0)
{
priority.push(devices.remove(i));
continue;
}
}
i += 1;
}
priority.push(boot_disk);
}
// Add any remaining devices
priority.extend(devices);
priority
}
#[repr(C, packed)]
#[allow(dead_code)]
struct DevicePathHarddrive {
partition_number: u32,
partition_start: u64,
partition_size: u64,
partition_signature: [u8; 16],
partition_format: u8,
signature_type: u8,
}
pub fn device_path_to_string(device_path: &DevicePath) -> String {
let mut s = String::new();
for (node, node_data) in DevicePathIter::new(device_path) {
let read_u16 = |i: usize| -> u16 { (node_data[i] as u16) | (node_data[i + 1] as u16) << 8 };
let read_u32 = |i: usize| -> u32 {
(node_data[i] as u32)
| (node_data[i + 1] as u32) << 8
| (node_data[i + 2] as u32) << 16
| (node_data[i + 3] as u32) << 24
};
if !s.is_empty() {
s.push('/');
}
let _ = match DevicePathType::try_from(node.Type) {
Ok(path_type) => match path_type {
DevicePathType::Hardware => match DevicePathHardwareType::try_from(node.SubType) {
Ok(sub_type) => match sub_type {
DevicePathHardwareType::Pci if node_data.len() == 2 => {
let func = node_data[0];
let dev = node_data[1];
write!(s, "Pci(0x{dev:X},0x{func:X})")
}
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
},
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
},
DevicePathType::Acpi => match DevicePathAcpiType::try_from(node.SubType) {
Ok(sub_type) => match sub_type {
DevicePathAcpiType::Acpi if node_data.len() == 8 => {
let hid = read_u32(0);
let uid = read_u32(4);
if hid & 0xFFFF == 0x41D0 {
write!(s, "Acpi(PNP{:04X},0x{:X})", hid >> 16, uid)
} else {
write!(s, "Acpi(0x{hid:08X},0x{uid:X})")
}
}
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
},
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
},
DevicePathType::Messaging => {
match DevicePathMessagingType::try_from(node.SubType) {
Ok(sub_type) => match sub_type {
DevicePathMessagingType::Sata if node_data.len() == 6 => {
let hba_port = read_u16(0);
let multiplier_port = read_u16(2);
let logical_unit = read_u16(4);
if multiplier_port & (1 << 15) != 0 {
write!(s, "Sata(0x{hba_port:X},0x{logical_unit:X})")
} else {
write!(
s,
"Sata(0x{hba_port:X},0x{multiplier_port:X},0x{logical_unit:X})"
)
}
}
DevicePathMessagingType::Usb if node_data.len() == 2 => {
let port = node_data[0];
let iface = node_data[1];
write!(s, "Usb(0x{port:X},0x{iface:X})")
}
DevicePathMessagingType::Nvme if node_data.len() == 12 => {
let nsid = read_u32(0);
let eui = &node_data[4..];
if eui == [0, 0, 0, 0, 0, 0, 0, 0] {
write!(s, "NVMe(0x{nsid:X})")
} else {
write!(
s,
"NVMe(0x{:X},{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X})",
nsid,
eui[0],
eui[1],
eui[2],
eui[3],
eui[4],
eui[5],
eui[6],
eui[7],
)
}
}
DevicePathMessagingType::Mac
if node_data.len() == 33 && node_data[32] == 0
|| node_data[32] == 1 =>
{
write!(
s,
"Mac({:02x}{:02x}{:02x}{:02x}{:02x}{:02x},{:#02x})",
node_data[0],
node_data[1],
node_data[2],
node_data[3],
node_data[4],
node_data[5],
node_data[32],
)
}
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
},
Err(()) => {
write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data)
}
}
}
DevicePathType::Media => match DevicePathMediaType::try_from(node.SubType) {
Ok(sub_type) => {
match sub_type {
DevicePathMediaType::Harddrive
if node_data.len() == mem::size_of::<DevicePathHarddrive>() =>
{
let harddrive = unsafe {
ptr::read(node_data.as_ptr() as *const DevicePathHarddrive)
};
let partition_number = unsafe {
ptr::read_unaligned(ptr::addr_of!(harddrive.partition_number))
};
match harddrive.signature_type {
1 => {
let id = unsafe {
ptr::read(harddrive.partition_signature.as_ptr()
as *const u32)
};
write!(s, "HD(0x{partition_number:X},MBR,0x{id:X})")
}
2 => {
let guid = unsafe {
ptr::read(harddrive.partition_signature.as_ptr()
as *const Guid)
};
write!(
s,
"HD(0x{:X},GPT,{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X})",
partition_number,
guid.0,
guid.1,
guid.2,
guid.3[0],
guid.3[1],
guid.3[2],
guid.3[3],
guid.3[4],
guid.3[5],
guid.3[6],
guid.3[7],
)
}
_ => {
write!(
s,
"HD(0x{:X},0x{:X},{:X?})",
partition_number,
harddrive.signature_type,
harddrive.partition_signature
)
}
}
}
DevicePathMediaType::Filepath => {
for chunk in node_data.chunks_exact(2) {
let data = (chunk[0] as u16) | (chunk[1] as u16) << 8;
match unsafe { char::from_u32_unchecked(data as u32) } {
'\\' => s.push('/'),
c => s.push(c),
}
}
Ok(())
}
_ => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
}
}
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
},
DevicePathType::Bbs => match DevicePathBbsType::try_from(node.SubType) {
Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
},
DevicePathType::End => match DevicePathEndType::try_from(node.SubType) {
Ok(sub_type) => write!(s, "{path_type:?} {sub_type:?} {node_data:X?}"),
Err(()) => write!(s, "{:?} 0x{:02X} {:X?}", path_type, node.SubType, node_data),
},
},
Err(()) => {
write!(
s,
"0x{:02X} 0x{:02X} {:X?}",
node.Type, node.SubType, node_data
)
}
};
}
s
}
pub struct DevicePathProtocol(pub &'static mut DevicePath);
impl Protocol<DevicePath> for DevicePathProtocol {
fn guid() -> Guid {
uefi::guid::DEVICE_PATH_GUID
}
fn new(inner: &'static mut DevicePath) -> Self {
Self(inner)
}
}
pub struct LoadedImageDevicePathProtocol(pub &'static mut DevicePath);
impl Protocol<DevicePath> for LoadedImageDevicePathProtocol {
fn guid() -> Guid {
uefi::guid::LOADED_IMAGE_DEVICE_PATH_GUID
}
fn new(inner: &'static mut DevicePath) -> Self {
Self(inner)
}
}
pub struct DevicePathIter<'a> {
device_path: &'a DevicePath,
node_ptr: *const DevicePath,
}
impl<'a> DevicePathIter<'a> {
pub fn new(device_path: &'a DevicePath) -> Self {
Self {
device_path,
node_ptr: device_path as *const DevicePath,
}
}
}
impl<'a> Iterator for DevicePathIter<'a> {
type Item = (&'a DevicePath, &'a [u8]);
fn next(&mut self) -> Option<Self::Item> {
let node = unsafe { &*self.node_ptr };
if node.Type == DevicePathType::End as u8 {
return None;
}
let node_data = unsafe {
slice::from_raw_parts(
self.node_ptr.add(1) as *mut u8,
node.Length.saturating_sub(4) as usize,
)
};
self.node_ptr = (self.node_ptr as usize + node.Length as usize) as *const DevicePath;
Some((node, node_data))
}
}
-119
View File
@@ -1,119 +0,0 @@
use alloc::vec::Vec;
use core::slice;
use redoxfs::{Disk, BLOCK_SIZE, RECORD_SIZE};
use std::proto::Protocol;
use syscall::{Error, Result, EINVAL, EIO};
use uefi::block_io::BlockIo as UefiBlockIo;
use uefi::guid::{Guid, BLOCK_IO_GUID};
pub enum DiskOrFileEfi {
Disk(DiskEfi),
File(Vec<u8>),
}
impl redoxfs::Disk for DiskOrFileEfi {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result<usize> {
unsafe {
match self {
DiskOrFileEfi::Disk(disk_efi) => disk_efi.read_at(block, buffer),
DiskOrFileEfi::File(data) => {
buffer.copy_from_slice(
&data[(block * redoxfs::BLOCK_SIZE) as usize
..(block * redoxfs::BLOCK_SIZE) as usize + buffer.len()],
);
Ok(buffer.len())
}
}
}
}
unsafe fn write_at(&mut self, _block: u64, _buffer: &[u8]) -> syscall::Result<usize> {
unreachable!()
}
fn size(&mut self) -> syscall::Result<u64> {
unreachable!()
}
}
pub struct DiskEfi(pub &'static mut UefiBlockIo, &'static mut [u8]);
impl Protocol<UefiBlockIo> for DiskEfi {
fn guid() -> Guid {
BLOCK_IO_GUID
}
fn new(inner: &'static mut UefiBlockIo) -> Self {
// Hack to get aligned buffer
let block = unsafe {
let ptr = super::alloc_zeroed_page_aligned(RECORD_SIZE as usize);
slice::from_raw_parts_mut(ptr, RECORD_SIZE as usize)
};
Self(inner, block)
}
}
impl Disk for DiskEfi {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
unsafe {
// Optimization for live disks
if let Some(live) = crate::LIVE_OPT {
if block >= live.0 {
let start = ((block - live.0) * BLOCK_SIZE) as usize;
let end = start + buffer.len();
if end <= live.1.len() {
buffer.copy_from_slice(&live.1[start..end]);
return Ok(buffer.len());
}
}
}
// Use aligned buffer if necessary
let mut ptr = buffer.as_mut_ptr();
if self.0.Media.IoAlign != 0 {
if (ptr as usize) % (self.0.Media.IoAlign as usize) != 0 {
if buffer.len() <= self.1.len() {
ptr = self.1.as_mut_ptr();
} else {
println!(
"DiskEfi::read_at 0x{:X} requires alignment, ptr = 0x{:p}, len = 0x{:x}",
block,
ptr,
buffer.len()
);
return Err(Error::new(EINVAL));
}
}
}
let block_size = self.0.Media.BlockSize as u64;
let lba = block * BLOCK_SIZE / block_size;
match (self.0.ReadBlocks)(self.0, self.0.Media.MediaId, lba, buffer.len(), ptr) {
status if status.is_success() => {
// Copy to original buffer if using aligned buffer
if ptr != buffer.as_mut_ptr() {
let (left, _) = self.1.split_at(buffer.len());
buffer.copy_from_slice(left);
}
Ok(buffer.len())
}
err => {
println!("DiskEfi::read_at 0x{:X} failed: {:?}", block, err);
Err(Error::new(EIO))
}
}
}
}
unsafe fn write_at(&mut self, block: u64, _buffer: &[u8]) -> Result<usize> {
println!("DiskEfi::write_at 0x{:X} not implemented", block);
Err(Error::new(EIO))
}
fn size(&mut self) -> Result<u64> {
println!("DiskEfi::size not implemented");
Err(Error::new(EIO))
}
}
-41
View File
@@ -1,41 +0,0 @@
use std::proto::Protocol;
use uefi::graphics::GraphicsOutput;
use uefi::guid::{Guid, GRAPHICS_OUTPUT_PROTOCOL_GUID};
pub struct Output(pub &'static mut GraphicsOutput);
impl Protocol<GraphicsOutput> for Output {
fn guid() -> Guid {
GRAPHICS_OUTPUT_PROTOCOL_GUID
}
fn new(inner: &'static mut GraphicsOutput) -> Self {
Output(inner)
}
}
const EDID_ACTIVE_PROTOCOL_GUID: Guid = Guid(
0xbd8c1056,
0x9f36,
0x44ec,
[0x92, 0xa8, 0xa6, 0x33, 0x7f, 0x81, 0x79, 0x86],
);
#[allow(non_snake_case)]
#[repr(C)]
pub struct EdidActiveProtocol {
pub SizeOfEdid: u32,
pub Edid: *const u8,
}
pub struct EdidActive(pub &'static mut EdidActiveProtocol);
impl Protocol<EdidActiveProtocol> for EdidActive {
fn guid() -> Guid {
EDID_ACTIVE_PROTOCOL_GUID
}
fn new(inner: &'static mut EdidActiveProtocol) -> Self {
EdidActive(inner)
}
}
-125
View File
@@ -1,125 +0,0 @@
use crate::Os;
use alloc::vec::Vec;
use byteorder::ByteOrder;
use byteorder::BE;
use core::slice;
use fdt::Fdt;
use uefi::guid::DEVICE_TREE_GUID;
#[cfg(target_arch = "aarch64")]
use uefi::{
guid::SMBIOS3_TABLE_GUID,
status::{Result, Status},
};
pub static mut DEV_MEM_AREA: Vec<(usize, usize)> = Vec::new();
pub unsafe fn is_in_dev_mem_region(addr: usize) -> bool {
#[allow(static_mut_refs)]
unsafe {
if DEV_MEM_AREA.is_empty() {
return false;
}
for item in DEV_MEM_AREA.iter() {
if (addr >= item.0) && (addr < item.0 + item.1) {
return true;
}
}
return false;
}
}
unsafe fn get_dev_mem_region(fdt: &Fdt) {
unsafe {
let Some(soc) = fdt.find_node("/soc") else {
return;
};
let Some(ranges) = soc.ranges() else {
return;
};
let cell_sizes = soc.cell_sizes();
for chunk in ranges {
let child_bus_addr = chunk.child_bus_address;
let parent_bus_addr = chunk.parent_bus_address;
let addr_size = chunk.size;
println!(
"dev mem 0x{:08x} 0x{:08x} 0x{:08x}",
child_bus_addr, parent_bus_addr, addr_size
);
#[allow(static_mut_refs)]
DEV_MEM_AREA.push((parent_bus_addr as usize, addr_size as usize));
}
}
}
fn parse_dtb(os: &impl Os, address: *const u8) -> Option<(u64, u64)> {
unsafe {
if let Ok(fdt) = fdt::Fdt::from_ptr(address) {
let mut rsdps_area = Vec::new();
//println!("DTB model = {}", fdt.root().model());
get_dev_mem_region(&fdt);
let length = fdt.total_size();
let align = 8;
rsdps_area.extend(core::slice::from_raw_parts(address, length));
rsdps_area.resize(((rsdps_area.len() + (align - 1)) / align) * align, 0u8);
let size = rsdps_area.len();
let base = os.alloc_zeroed_page_aligned(size);
slice::from_raw_parts_mut(base, size).copy_from_slice(&rsdps_area);
Some((base as u64, size as u64))
} else {
println!("Failed to parse DTB");
None
}
}
}
#[cfg(target_arch = "aarch64")]
fn find_smbios3_system(address: *const u8) -> Result<dmidecode::System<'static>> {
unsafe {
let smb = core::slice::from_raw_parts(address, 24);
if let Ok(smbios) = dmidecode::EntryPoint::search(smb) {
let smb_structure_data = core::slice::from_raw_parts(
smbios.smbios_address() as *const u8,
smbios.smbios_len() as usize,
);
for structure in smbios.structures(smb_structure_data) {
if let Ok(sval) = structure {
//println!("SMBIOS: {:#?}", sval);
if let dmidecode::Structure::System(buf) = sval {
return Ok(buf);
}
}
}
}
}
Err(Status::NOT_FOUND)
}
pub(crate) fn find_dtb(os: &impl Os) -> Option<(u64, u64)> {
let cfg_tables = std::system_table().config_tables();
for cfg_table in cfg_tables.iter() {
if cfg_table.VendorGuid == DEVICE_TREE_GUID {
let addr = cfg_table.VendorTable;
return parse_dtb(os, addr as *const u8);
}
}
/* This hack is no longer needed, but can be re-enabled for testing
#[cfg(target_arch = "aarch64")]
for cfg_table in cfg_tables.iter() {
if cfg_table.VendorGuid == SMBIOS3_TABLE_GUID {
let addr = cfg_table.VendorTable;
if let Ok(sys) = find_smbios3_system(addr as *const u8) {
let get_dtb_addr = match (sys.manufacturer, sys.version) {
("QEMU", version) if version.starts_with("virt") => Some(0x4000_0000 as usize),
_ => None,
};
if let Some(dtb_addr) = get_dtb_addr {
return parse_dtb(os, dtb_addr as *const u8);
}
}
}
}
*/
None
}
-141
View File
@@ -1,141 +0,0 @@
use alloc::vec;
use alloc::vec::Vec;
use core::{mem, ptr};
use uefi::memory::{MemoryDescriptor, MemoryType};
use crate::area_add;
use crate::os::{OsMemoryEntry, OsMemoryKind};
use super::status_to_result;
pub struct MemoryMapIter {
map: Vec<u8>,
map_key: usize,
descriptor_size: usize,
descriptor_version: u32,
i: usize,
}
impl MemoryMapIter {
pub fn new() -> Self {
let uefi = std::system_table();
let mut map = vec![0; 65536];
let mut map_size = map.len();
let mut map_key = 0;
let mut descriptor_size = 0;
let mut descriptor_version = 0;
status_to_result((uefi.BootServices.GetMemoryMap)(
&mut map_size,
map.as_mut_ptr() as *mut MemoryDescriptor,
&mut map_key,
&mut descriptor_size,
&mut descriptor_version,
))
.expect("Failed to get UEFI memory map");
// Ensure descriptor size is usable
assert!(descriptor_size >= mem::size_of::<MemoryDescriptor>());
// Ensure descriptor version is supported
assert_eq!(descriptor_version, 1);
// Reduce map size to returned value
map.truncate(map_size);
Self {
map,
map_key,
descriptor_size,
descriptor_version,
i: 0,
}
}
pub fn exit_boot_services(mut self) {
let handle = std::handle();
let uefi = std::system_table();
// We are writing to the memory map that will be passed to
// SetVirtualAddressMap before ExitBootServices as on some firmware
// EfiLoaderData memory regions like this one are marked as read-only
// after ExitBootServices
for i in 0..self.map.len() / self.descriptor_size {
let descriptor_ptr = unsafe { self.map.as_mut_ptr().add(i * self.descriptor_size) };
let descriptor = unsafe { &mut *(descriptor_ptr as *mut MemoryDescriptor) };
// Map all memory regions even when not marked as EFI_MEMORY_RUNTIME
// as some firmware uses memory regions not marked as
// EFI_MEMORY_RUNTIME in runtime services. Linux has a list of
// exactly which memory regions need to be mapped, but for simplicity
// we are mapping all regions here.
// Identity map all memory regions as some firmware fails to update
// all pointers in SetVirtualAddressMap.
descriptor.VirtualStart.0 = descriptor.PhysicalStart.0;
}
status_to_result((uefi.BootServices.ExitBootServices)(handle, self.map_key))
.expect("Failed to exit UEFI boot services");
// Runtime services must be called with interrupts disabled
super::arch::disable_interrupts();
status_to_result((uefi.RuntimeServices.SetVirtualAddressMap)(
self.map.len(),
self.descriptor_size,
self.descriptor_version,
self.map.as_ptr() as *const MemoryDescriptor,
))
.expect("Failed to set UEFI runtime services virtual address map");
// After ExitBootServices, GlobalAlloc::dealloc() is not allowed anymore
// as it uses boot services.
mem::forget(self);
}
}
impl Iterator for MemoryMapIter {
type Item = OsMemoryEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.i < self.map.len() / self.descriptor_size {
let descriptor_ptr = unsafe { self.map.as_ptr().add(self.i * self.descriptor_size) };
self.i += 1;
let descriptor = unsafe { ptr::read(descriptor_ptr as *const MemoryDescriptor) };
let descriptor_type: MemoryType = unsafe { mem::transmute(descriptor.Type) };
Some(OsMemoryEntry {
base: descriptor.PhysicalStart.0,
//TODO: do not hard code page size
size: descriptor.NumberOfPages * 4096,
kind: match descriptor_type {
MemoryType::EfiLoaderCode
| MemoryType::EfiLoaderData
| MemoryType::EfiBootServicesCode
| MemoryType::EfiBootServicesData
| MemoryType::EfiConventionalMemory => OsMemoryKind::Free,
//TODO: mark ACPI memory as reclaim
_ => OsMemoryKind::Reserved,
},
})
} else {
None
}
}
}
pub unsafe fn memory_map() -> MemoryMapIter {
let mut iter = MemoryMapIter::new();
// Using next to avoid consuming iterator
while let Some(entry) = iter.next() {
area_add(entry);
}
// Rewind iterator
iter.i = 0;
iter
}
-443
View File
@@ -1,443 +0,0 @@
use alloc::vec::Vec;
use core::{cell::RefCell, mem, ptr, slice};
use std::proto::Protocol;
use uefi::{
boot::LocateSearchType,
memory::MemoryType,
reset::ResetType,
status::{Result, Status},
system::SystemTable,
text::TextInputKey,
Handle,
};
use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode};
use self::{
device::{device_path_to_string, disk_device_priority},
disk::DiskOrFileEfi,
display::{EdidActive, Output},
video_mode::VideoModeIter,
};
mod acpi;
mod arch;
mod device;
mod disk;
mod display;
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
pub mod dtb;
mod memory_map;
mod video_mode;
#[cfg(target_arch = "riscv64")]
pub use arch::efi_get_boot_hartid;
pub(crate) fn page_size() -> usize {
// EDK2 always uses 4096 as the page size
4096
}
pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 {
assert!(size != 0);
let page_size = page_size();
let pages = size.div_ceil(page_size);
let ptr = {
// Max address mapped by src/arch paging code (8 GiB)
let mut ptr = 0x2_0000_0000;
status_to_result((std::system_table().BootServices.AllocatePages)(
1, // AllocateMaxAddress
MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list
pages,
&mut ptr,
))
.unwrap_or_else(|_| {
ptr = 0;
0
});
if ptr == 0 { ptr::null_mut() } else { ptr as *mut u8 }
};
if !ptr.is_null() {
unsafe { ptr::write_bytes(ptr, 0, pages * page_size) };
}
ptr
}
pub struct OsEfi {
st: &'static SystemTable,
outputs: RefCell<Vec<(Output, Option<EdidActive>)>>,
}
impl OsEfi {
pub fn new() -> Self {
let st = std::system_table();
let mut outputs = Vec::<(Output, Option<EdidActive>)>::new();
{
let guid = Output::guid();
let mut handles = Vec::with_capacity(256);
let mut len = handles.capacity() * mem::size_of::<Handle>();
match status_to_result((st.BootServices.LocateHandle)(
LocateSearchType::ByProtocol,
&guid,
ptr::null(),
&mut len,
handles.as_mut_ptr(),
)) {
Ok(_) => {
unsafe {
handles.set_len(len / mem::size_of::<Handle>());
}
'handles: for handle in handles {
//TODO: do we have to query all modes to get good edid?
match Output::handle_protocol(handle) {
Ok(output) => {
log::debug!(
"Output {:?} at {:x}",
handle,
output.0.Mode.FrameBufferBase
);
if output.0.Mode.FrameBufferBase == 0 {
log::debug!("Skipping output with frame buffer base of 0");
continue 'handles;
}
for other_output in outputs.iter() {
if output.0.Mode.FrameBufferBase
== other_output.0 .0.Mode.FrameBufferBase
{
log::debug!(
"Skipping output with frame buffer base matching another output"
);
continue 'handles;
}
}
outputs.push((
output,
match EdidActive::handle_protocol(handle) {
Ok(efi_edid) => Some(efi_edid),
Err(err) => {
log::warn!(
"Failed to get EFI EDID from handle {:?}: {:?}",
handle,
err
);
None
}
},
));
}
Err(err) => {
log::warn!(
"Failed to get Output from handle {:?}: {:?}",
handle,
err
);
}
}
}
}
Err(err) => {
log::warn!("Failed to locate Outputs: {:?}", err);
}
}
}
Self {
st,
outputs: RefCell::new(outputs),
}
}
}
impl Os for OsEfi {
type D = DiskOrFileEfi;
type V = VideoModeIter;
#[cfg(target_arch = "aarch64")]
fn name(&self) -> &str {
"aarch64/UEFI"
}
#[cfg(target_arch = "x86_64")]
fn name(&self) -> &str {
"x86_64/UEFI"
}
#[cfg(target_arch = "riscv64")]
fn name(&self) -> &str {
"riscv64/UEFI"
}
fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
alloc_zeroed_page_aligned(size)
}
fn page_size(&self) -> usize {
page_size()
}
fn filesystem(
&self,
password_opt: Option<&[u8]>,
) -> syscall::Result<redoxfs::FileSystem<DiskOrFileEfi>> {
let mut seen_enokey = false;
// Search for RedoxFS on disks in prioritized order
println!("Looking for RedoxFS:");
for device in disk_device_priority() {
if let Some(file_path) = device.file_path {
log::debug!(
" - {}\\{}",
device_path_to_string(device.device_path.0),
file_path
);
} else {
log::debug!(" - {}", device_path_to_string(device.device_path.0));
}
let block = device.partition_offset / redoxfs::BLOCK_SIZE;
match redoxfs::FileSystem::open(device.disk, password_opt, Some(block), false) {
Ok(ok) => return Ok(ok),
Err(err) => match err.errno {
// Ignore header not found error
syscall::ENOENT => (),
// Bubble ENOKEY up later if we can't find another partition to return
syscall::ENOKEY => seen_enokey = true,
// Print any other errors
_ => {
log::warn!("BlockIo error: {:?}", err);
}
},
}
}
// Let the caller prompt for a password
if seen_enokey {
return Err(syscall::Error::new(syscall::ENOKEY));
}
log::warn!("No RedoxFS partitions found");
Err(syscall::Error::new(syscall::ENOENT))
}
fn hwdesc(&self) -> OsHwDesc {
//TODO: if both DTB and ACPI are found, we should probably let the OS choose what to use?
// For now we will prefer DTB on platforms that have it
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
if let Some((addr, size)) = dtb::find_dtb(self) {
return OsHwDesc::DeviceTree(addr, size);
}
if let Some((addr, size)) = acpi::find_acpi_table_pointers(self) {
return OsHwDesc::Acpi(addr, size);
}
OsHwDesc::NotFound
}
fn video_outputs(&self) -> usize {
self.outputs.borrow().len()
}
fn video_modes(&self, output_i: usize) -> VideoModeIter {
let output_opt = match self.outputs.borrow_mut().get_mut(output_i) {
Some(output) => unsafe {
// Hack to enable clone
let ptr = output.0 .0 as *mut _;
Some(Output::new(&mut *ptr))
},
None => None,
};
VideoModeIter::new(output_opt)
}
fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode) {
//TODO: return error?
let mut outputs = self.outputs.borrow_mut();
let (output, _efi_edid_opt) = &mut outputs[output_i];
status_to_result((output.0.SetMode)(output.0, mode.id)).unwrap();
// Update with actual mode information
mode.width = output.0.Mode.Info.HorizontalResolution;
mode.height = output.0.Mode.Info.VerticalResolution;
mode.base = output.0.Mode.FrameBufferBase as u64;
}
fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)> {
let mut outputs = self.outputs.borrow_mut();
let (output, efi_edid_opt) = outputs.get_mut(output_i)?;
if let Some(efi_edid) = efi_edid_opt {
let edid =
unsafe { slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize) };
if edid.len() > 0x3D {
return Some((
(edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
(edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
));
} else {
log::warn!("EFI EDID too small: {}", edid.len());
}
}
// Fallback to the current output resolution
Some((
output.0.Mode.Info.HorizontalResolution,
output.0.Mode.Info.VerticalResolution,
))
}
fn get_key(&self) -> OsKey {
//TODO: do not unwrap
let mut index = 0;
status_to_result((self.st.BootServices.WaitForEvent)(
1,
&self.st.ConsoleIn.WaitForKey,
&mut index,
))
.unwrap();
let mut key = TextInputKey {
ScanCode: 0,
UnicodeChar: 0,
};
status_to_result((self.st.ConsoleIn.ReadKeyStroke)(
self.st.ConsoleIn,
&mut key,
))
.unwrap();
match key.ScanCode {
0 => match key.UnicodeChar {
8 => OsKey::Backspace,
13 => OsKey::Enter,
w => match char::from_u32(w as u32) {
Some(c) => OsKey::Char(c),
None => OsKey::Other,
},
},
1 => OsKey::Up,
2 => OsKey::Down,
3 => OsKey::Right,
4 => OsKey::Left,
8 => OsKey::Delete,
_ => OsKey::Other,
}
}
fn get_key_timeout(&self, milliseconds: usize) -> OsKey {
let slices = milliseconds.div_ceil(100);
for _ in 0..slices {
let mut key = TextInputKey {
ScanCode: 0,
UnicodeChar: 0,
};
let status = (self.st.ConsoleIn.ReadKeyStroke)(self.st.ConsoleIn, &mut key);
if status.is_success() {
return match key.ScanCode {
0 => match key.UnicodeChar {
8 => OsKey::Backspace,
13 => OsKey::Enter,
w => match char::from_u32(w as u32) {
Some(c) => OsKey::Char(c),
None => OsKey::Other,
},
},
1 => OsKey::Up,
2 => OsKey::Down,
3 => OsKey::Right,
4 => OsKey::Left,
8 => OsKey::Delete,
_ => OsKey::Other,
};
}
let _ = status_to_result((self.st.BootServices.Stall)(100_000));
}
OsKey::Timeout
}
fn clear_text(&self) {
//TODO: why does this sometimes return InvalidParameter, but otherwise appear to work?
let _ = status_to_result((self.st.ConsoleOut.ClearScreen)(self.st.ConsoleOut));
}
fn get_text_position(&self) -> (usize, usize) {
(
self.st.ConsoleOut.Mode.CursorColumn as usize,
self.st.ConsoleOut.Mode.CursorRow as usize,
)
}
fn set_text_position(&self, x: usize, y: usize) {
// Ignore error because Tow-Boot appears to not implement this
let _ = status_to_result((self.st.ConsoleOut.SetCursorPosition)(
self.st.ConsoleOut,
x,
y,
));
}
fn set_text_highlight(&self, highlight: bool) {
let attr = if highlight { 0x70 } else { 0x07 };
status_to_result((self.st.ConsoleOut.SetAttribute)(self.st.ConsoleOut, attr)).unwrap();
}
}
fn status_to_result(status: Status) -> Result<usize> {
match status {
Status(ok) if status.is_success() => Ok(ok),
err => Err(err),
}
}
fn set_max_mode(output: &uefi::text::TextOutput) -> Result<()> {
let mut max_i = None;
let mut max_w = 0;
let mut max_h = 0;
for i in 0..output.Mode.MaxMode as usize {
let mut w = 0;
let mut h = 0;
if (output.QueryMode)(output, i, &mut w, &mut h).is_success() {
if w >= max_w && h >= max_h {
max_i = Some(i);
max_w = w;
max_h = h;
}
}
}
if let Some(i) = max_i {
status_to_result((output.SetMode)(output, i))?;
}
Ok(())
}
#[unsafe(no_mangle)]
pub extern "C" fn main() -> Status {
let uefi = std::system_table();
let _ = (uefi.BootServices.SetWatchdogTimer)(0, 0, 0, ptr::null());
if let Err(err) = set_max_mode(uefi.ConsoleOut) {
println!("Failed to set max mode: {:?}", err);
}
if let Err(err) = arch::main() {
panic!("App error: {:?}", err);
}
(uefi.RuntimeServices.ResetSystem)(ResetType::Cold, Status(0), 0, ptr::null());
}
-57
View File
@@ -1,57 +0,0 @@
use core::ptr;
use log::error;
use uefi::status::Status;
use crate::os::uefi::display::Output;
use crate::os::OsVideoMode;
pub struct VideoModeIter {
output_opt: Option<Output>,
i: u32,
}
impl VideoModeIter {
pub fn new(output_opt: Option<Output>) -> Self {
Self { output_opt, i: 0 }
}
}
impl Iterator for VideoModeIter {
type Item = OsVideoMode;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ref mut output) = self.output_opt {
while self.i < output.0.Mode.MaxMode {
let id = self.i;
self.i += 1;
let mut mode_ptr = ::core::ptr::null_mut();
let mut mode_size = 0;
match (output.0.QueryMode)(output.0, id, &mut mode_size, &mut mode_ptr) {
Status::SUCCESS => (),
err => {
error!("Failed to read mode {}: {:?}", id, err);
continue;
}
}
//TODO: ensure mode_size is set correctly
let mode = unsafe { ptr::read(mode_ptr) };
let width = mode.HorizontalResolution;
let height = mode.VerticalResolution;
let stride = mode.PixelsPerScanLine;
return Some(OsVideoMode {
id,
width,
height,
stride,
// Base is retrieved later by setting the mode
base: 0,
});
}
}
None
}
}
-142
View File
@@ -1,142 +0,0 @@
use bitflags::bitflags;
use core::convert::TryInto;
use core::fmt;
use core::ptr::{addr_of, addr_of_mut};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use syscall::io::Pio;
use syscall::io::{Io, Mmio, ReadOnly};
bitflags! {
/// Interrupt enable flags
struct IntEnFlags: u8 {
const RECEIVED = 1;
const SENT = 1 << 1;
const ERRORED = 1 << 2;
const STATUS_CHANGE = 1 << 3;
// 4 to 7 are unused
}
}
bitflags! {
/// Line status flags
struct LineStsFlags: u8 {
const INPUT_FULL = 1;
// 1 to 4 unknown
const OUTPUT_EMPTY = 1 << 5;
// 6 and 7 unknown
}
}
#[allow(dead_code)]
#[repr(C, packed)]
pub struct SerialPort<T: Io> {
/// Data register, read to receive, write to send
data: T,
/// Interrupt enable
int_en: T,
/// FIFO control
fifo_ctrl: T,
/// Line control
line_ctrl: T,
/// Modem control
modem_ctrl: T,
/// Line status
line_sts: ReadOnly<T>,
/// Modem status
modem_sts: ReadOnly<T>,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl SerialPort<Pio<u8>> {
pub const fn new(base: u16) -> SerialPort<Pio<u8>> {
SerialPort {
data: Pio::new(base),
int_en: Pio::new(base + 1),
fifo_ctrl: Pio::new(base + 2),
line_ctrl: Pio::new(base + 3),
modem_ctrl: Pio::new(base + 4),
line_sts: ReadOnly::new(Pio::new(base + 5)),
modem_sts: ReadOnly::new(Pio::new(base + 6)),
}
}
}
impl SerialPort<Mmio<u32>> {
pub unsafe fn new(base: usize) -> &'static mut SerialPort<Mmio<u32>> {
unsafe { &mut *(base as *mut Self) }
}
}
impl<T: Io> SerialPort<T>
where
T::Value: From<u8> + TryInto<u8>,
{
pub fn init(&mut self) {
unsafe {
//TODO: Cleanup
// FIXME: Fix UB if unaligned
(*addr_of_mut!(self.int_en)).write(0x00.into());
(*addr_of_mut!(self.line_ctrl)).write(0x80.into());
(*addr_of_mut!(self.data)).write(0x01.into());
(*addr_of_mut!(self.int_en)).write(0x00.into());
(*addr_of_mut!(self.line_ctrl)).write(0x03.into());
(*addr_of_mut!(self.fifo_ctrl)).write(0xC7.into());
(*addr_of_mut!(self.modem_ctrl)).write(0x0B.into());
(*addr_of_mut!(self.int_en)).write(0x01.into());
}
}
fn line_sts(&self) -> LineStsFlags {
LineStsFlags::from_bits_truncate(
(unsafe { &*addr_of!(self.line_sts) }.read() & 0xFF.into())
.try_into()
.unwrap_or(0),
)
}
pub fn receive(&mut self) -> Option<u8> {
if self.line_sts().contains(LineStsFlags::INPUT_FULL) {
Some(
(unsafe { &*addr_of!(self.data) }.read() & 0xFF.into())
.try_into()
.unwrap_or(0),
)
} else {
None
}
}
pub fn send(&mut self, data: u8) {
while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {}
unsafe { &mut *addr_of_mut!(self.data) }.write(data.into())
}
pub fn write(&mut self, buf: &[u8]) {
for &b in buf {
match b {
8 | 0x7F => {
self.send(8);
self.send(b' ');
self.send(8);
}
b'\n' => {
self.send(b'\r');
self.send(b'\n');
}
_ => {
self.send(b);
}
}
}
}
}
impl<T: Io> fmt::Write for SerialPort<T>
where
T::Value: From<u8> + TryInto<u8>,
{
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
self.write(s.as_bytes());
Ok(())
}
}
-25
View File
@@ -1,25 +0,0 @@
{
"arch": "aarch64",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"default-hidden-visibility": true,
"emit-debug-gdb-scripts": false,
"exe-suffix": ".efi",
"executables": true,
"is-like-windows": true,
"linker": "rust-lld",
"linker-flavor": "lld-link",
"llvm-target": "aarch64-pc-windows-msvc",
"os": "uefi",
"panic-strategy": "abort",
"pre-link-args": {
"lld-link": [
"/subsystem:EFI_Application",
"/entry:efi_main",
"/machine:arm64"
]
},
"stack_probes": true,
"target-c-int-width": 32,
"target-endian": "little",
"target-pointer-width": 64
}
-25
View File
@@ -1,25 +0,0 @@
{
"arch": "riscv64",
"code-model": "medium",
"cpu": "generic-rv64",
"data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
"emit-debug-gdb-scripts": false,
"exe-suffix": ".elf",
"executables": true,
"linker-flavor": "gnu-cc",
"linker": "riscv64-unknown-redox-gcc",
"llvm-abiname": "lp64d",
"features": "+m,+a,+f,+d,+c",
"llvm-target": "riscv64-unknown-none-elf",
"os": "none",
"metadata": {
"description": null,
"host_tools": null,
"std": null,
"tier": null
},
"panic-strategy": "abort",
"target-c-int-width": 32,
"target-endian": "little",
"target-pointer-width": 64
}
-29
View File
@@ -1,29 +0,0 @@
{
"llvm-target": "i686-unknown-none",
"target-endian": "little",
"target-pointer-width": 32,
"target-c-int-width": 32,
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"arch": "x86",
"os": "none",
"env": "",
"vendor": "unknown",
"linker-flavor": "gcc",
"panic-strategy": "abort",
"pre-link-args": {
"gcc": ["-m32", "-nostdlib", "-static"]
},
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"rustc-abi": "x86-softfloat",
"dynamic-linking": false,
"executables": false,
"relocation-model": "static",
"code-model": "large",
"disable-redzone": true,
"frame-pointer": "always",
"exe-suffix": "",
"has-rpath": false,
"no-default-libraries": true,
"position-independent-executables": false,
"tls-model": "global-dynamic"
}
Executable
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
IMAGE=test.bin
QEMU_ARGS=(
-cpu max
-machine q35
-m 2048
-smp 4
-serial mon:stdio
-netdev user,id=net0
-device e1000,netdev=net0
)
if [ -e /dev/kvm ]
then
QEMU_ARGS+=(-accel kvm)
fi
set -ex
cargo build --release
rm -f "${IMAGE}"
fallocate -l 1GiB "${IMAGE}"
target/release/redox_installer -c res/test.toml "${IMAGE}"
qemu-system-x86_64 "${QEMU_ARGS[@]}" -drive "file=${IMAGE},format=raw"