Compare commits
326 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d90f84883e | |||
| e604868baf | |||
| 386b19566d | |||
| 4591fbabcc | |||
| ed6013c295 | |||
| e79c1be43e | |||
| b5b9bfd825 | |||
| 8c2b0f9de7 | |||
| 9800eac87c | |||
| e61506c50f | |||
| b29021c352 | |||
| 474ff08431 | |||
| f691afabab | |||
| f74e3fad19 | |||
| 3ef2cac56d | |||
| aa9c978f90 | |||
| a6fd457652 | |||
| 7806f87c56 | |||
| 70b8cb0e37 | |||
| 397974cb68 | |||
| 8765c51696 | |||
| 2d3b2ed38a | |||
| 1840ceac85 | |||
| 59d0872197 | |||
| 7f918e544e | |||
| 730eb9a606 | |||
| a790f1c228 | |||
| b33f3c1a80 | |||
| b0f90da7b8 | |||
| c4ed26e3d4 | |||
| 753256cfaf | |||
| cfc7938ecc | |||
| 15de2b4272 | |||
| fed265252d | |||
| d40619d66c | |||
| 64f46052db | |||
| eff771e640 | |||
| 397749f1f5 | |||
| 753c8af0e5 | |||
| 846e8446e0 | |||
| 385696d84a | |||
| ca9748f1ba | |||
| 7e697cfb3a | |||
| 86d2af6ca0 | |||
| 8ba8d6e15b | |||
| 3dba5827f2 | |||
| 433aa99660 | |||
| 990d9d343e | |||
| 8bc7c9d88d | |||
| 4fcd97e8da | |||
| 7a85862733 | |||
| 7308cfe259 | |||
| 58f161e019 | |||
| 693d9470f9 | |||
| 2a910e7058 | |||
| 5fad110e5e | |||
| 09531dfdc8 | |||
| e5233171ae | |||
| 8281316190 | |||
| 9594e87937 | |||
| bb9fa4ec7c | |||
| f30179a889 | |||
| 97dd77918e | |||
| 8e6a1c3aa4 | |||
| c35c9a88a9 | |||
| 43c1cd987f | |||
| 727c703bad | |||
| 0fa085c0f2 | |||
| 69de3d62a1 | |||
| c540ed8f9d | |||
| 7023dc0eb4 | |||
| 039ded92a4 | |||
| e74c9279cf | |||
| ee5a94dbe4 | |||
| b425b12cf5 | |||
| 432abd9249 | |||
| 01a3adb662 | |||
| 81e8bc09f8 | |||
| 5758ba854a | |||
| 64b047195d | |||
| 2730f1a70c | |||
| d82dd8998d | |||
| 721d49adfb | |||
| a86aec193d | |||
| 55a0bd1643 | |||
| 2c3afb4956 | |||
| 2c8c6bf30f | |||
| 0db88e3cb4 | |||
| 7ce72b7299 | |||
| 9e888c06fa | |||
| a4032c27b7 | |||
| fcc2dfb1c8 | |||
| d85eceac71 | |||
| a78377da19 | |||
| 2bf4865794 | |||
| d53435420b | |||
| 3a084e1c27 | |||
| e17550b6bd | |||
| 183e18be31 | |||
| 729c5631bc | |||
| 47282ba9dd | |||
| 7c359f1622 | |||
| 37cc477d5c | |||
| dc7d923e5a | |||
| 71ac2646bb | |||
| 17db563677 | |||
| cf83106610 | |||
| 0ee8e95f62 | |||
| b3e90be70b | |||
| 20fa660797 | |||
| eebccba932 | |||
| 4b9e9e17ab | |||
| 17263316d9 | |||
| 5c8cdd8950 | |||
| 5c52a2e80e | |||
| 7ccaccee4e | |||
| afc225c8d6 | |||
| 098fd8bbc9 | |||
| 66d66a537c | |||
| bfe70c409f | |||
| cb707c3ce4 | |||
| 9c3c5228df | |||
| caebc0d6f7 | |||
| 087810a252 | |||
| 34a832b43e | |||
| d352203794 | |||
| ad54d088d5 | |||
| a5534b8699 | |||
| e409c11e67 | |||
| 6895d9a9aa | |||
| 31d4816fcb | |||
| 2058101f61 | |||
| 69d4db511a | |||
| 52281ea9b5 | |||
| 7b9f9f663d | |||
| 7bcc07f357 | |||
| 5b7c64d587 | |||
| 248990b5ef | |||
| 2acf506d16 | |||
| f725079176 | |||
| 1c30f4a3c1 | |||
| 17219dab9f | |||
| 6dfcee04ad | |||
| dbfdd417f1 | |||
| 51f6efbaad | |||
| 3d417f7742 | |||
| a258044dcc | |||
| 9a1463dd11 | |||
| 7a7790e9bd | |||
| 09da116ccd | |||
| 6a4802d556 | |||
| 352407371c | |||
| 6f40e32d49 | |||
| b84146edd5 | |||
| adfee95484 | |||
| 1f4bbece4a | |||
| 5cdec46800 | |||
| c9e091be67 | |||
| aa1a982479 | |||
| b4f2f01d73 | |||
| 38f6aa70bd | |||
| 39f4b1c8b6 | |||
| ba2fc38a3f | |||
| f241649bba | |||
| 4821c55242 | |||
| cf9e2c9719 | |||
| 59e0f9e7ae | |||
| e0c909536c | |||
| cbdea61a61 | |||
| c5335d4aae | |||
| 2b34b31956 | |||
| d61b30de3f | |||
| 970cbfaef6 | |||
| 7a85c43974 | |||
| 7b35a76e49 | |||
| 363166ad1e | |||
| 3f306765ad | |||
| 20c0bdf60f | |||
| 46e14ea06f | |||
| 7e0f635d0e | |||
| 46e39bc109 | |||
| b04fcab423 | |||
| b23b2f6e5d | |||
| c4a7020e93 | |||
| 1411257784 | |||
| cfd556f3b3 | |||
| 257020852d | |||
| c91bea9452 | |||
| 41a656af92 | |||
| fadb0a1c1d | |||
| f710fa79db | |||
| b724e714d3 | |||
| c6d7cf16e1 | |||
| e24f5ef8e3 | |||
| fa836008c0 | |||
| 5e8e596d40 | |||
| 7b24f868b8 | |||
| 0b4f836f2a | |||
| 8d2d4cd62d | |||
| 9666c62036 | |||
| f103b5c4b9 | |||
| 4b1df136a6 | |||
| ddf59d8c7d | |||
| c3e679d5f1 | |||
| ad11b2adb2 | |||
| e8e4687c05 | |||
| 1ecb9b49fc | |||
| f1a806b288 | |||
| 4e7f7d8413 | |||
| be5eefd44f | |||
| 2b1107c856 | |||
| 2418286a29 | |||
| 0ed65d7d3d | |||
| f76771491c | |||
| f8ec4d28bc | |||
| e666636ab7 | |||
| 900f77e7f2 | |||
| adebf727dc | |||
| 9ea9a6ecac | |||
| 3c03576518 | |||
| 0c731043ac | |||
| 2c54a9b868 | |||
| b35e132153 | |||
| 41eba65942 | |||
| 1634877666 | |||
| ff2fdeca84 | |||
| 621a4dee2c | |||
| 84856f459d | |||
| 7f0a57108d | |||
| 70a2dc7532 | |||
| 224bb047dc | |||
| 150d65c31b | |||
| af5f64b807 | |||
| 03320a2025 | |||
| 03f996c70f | |||
| c22b1cf71b | |||
| 6a49914f0f | |||
| 3a1a257f0f | |||
| 1fccae679d | |||
| 7c5236fe35 | |||
| 3b0f59cbb0 | |||
| cd9e026705 | |||
| 9ff577792b | |||
| fbfc4edf6f | |||
| 505dec6069 | |||
| 89dcc2c060 | |||
| 7ef2567da6 | |||
| 5762840f79 | |||
| 39d68618ca | |||
| b75b1c2f86 | |||
| acb7bced58 | |||
| f30987b1e5 | |||
| 713359252a | |||
| f794ab2a66 | |||
| 40addc7020 | |||
| 8e88231ede | |||
| a262db6f25 | |||
| 78e9f77950 | |||
| 03fc74e21f | |||
| ed25899b0e | |||
| 49b7a5f427 | |||
| 7f38ffac1f | |||
| 33fa0af521 | |||
| 91c303df7d | |||
| 123350ae60 | |||
| be2e09472a | |||
| 03cae0ca37 | |||
| 8e39b2ea32 | |||
| 66b24d1116 | |||
| e58815f89c | |||
| ce6e96bd20 | |||
| 80add18dc9 | |||
| 1359501d3f | |||
| a1afbb144f | |||
| 999db45b0e | |||
| f6c5fd94a7 | |||
| 681fd3d0ef | |||
| 62412ef548 | |||
| 43e506c447 | |||
| ac759a17bd | |||
| a01dc9f37a | |||
| a82a577180 | |||
| a7b16dcb97 | |||
| 093ae7de55 | |||
| 25d6a0fec4 | |||
| 7ad7827497 | |||
| a4adb124d0 | |||
| 5cfe357821 | |||
| a76c8df867 | |||
| a9a1f0e76d | |||
| ed468af4c3 | |||
| 3efca19473 | |||
| c0bc46b954 | |||
| 2c0e14cadf | |||
| d73dd18f07 | |||
| 15d37cea36 | |||
| e47108a70e | |||
| d6f2936a53 | |||
| f99fac30ea | |||
| af1dd3b9fd | |||
| 31a60eb8a3 | |||
| 318e7bb89f | |||
| 8d6a4d7071 | |||
| 7e9f15e54f | |||
| c2bae24835 | |||
| 160a793017 | |||
| 7c7b30c16e | |||
| bb94edcb15 | |||
| e2aa3c3032 | |||
| bb56cee678 | |||
| 4e9d22ada4 | |||
| cfdb2a4968 | |||
| be092f3754 | |||
| eb2c0aafd4 | |||
| 5303301d3d | |||
| 9125d089fb | |||
| aeb9f89ec1 | |||
| 7d5baf798e | |||
| a56ec333ae | |||
| dd0a541bfe | |||
| a5773c3504 | |||
| e4db84e1d7 | |||
| 542ca6cb3d | |||
| 961983e788 | |||
| f666d06485 | |||
| 1abfd39b8d |
@@ -1 +1,4 @@
|
|||||||
|
pkg
|
||||||
|
sysroot
|
||||||
/target/
|
/target/
|
||||||
|
/test.bin
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
image: "rust:latest"
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- lint
|
||||||
|
- test
|
||||||
|
|
||||||
|
workflow:
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "master"'
|
||||||
|
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
stage: lint
|
||||||
|
script:
|
||||||
|
- rustup component add rustfmt
|
||||||
|
- 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
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
language: rust
|
|
||||||
rust:
|
|
||||||
- nightly
|
|
||||||
sudo: false
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
Generated
+2685
File diff suppressed because it is too large
Load Diff
+58
-67
@@ -1,82 +1,73 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "userutils"
|
name = "redox_installer"
|
||||||
version = "0.1.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"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "id"
|
name = "redox_installer"
|
||||||
path = "src/bin/id.rs"
|
path = "src/bin/installer.rs"
|
||||||
|
required-features = ["installer"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "getty"
|
name = "redox_installer_tui"
|
||||||
path = "src/bin/getty.rs"
|
path = "src/bin/installer_tui.rs"
|
||||||
|
required-features = ["installer"]
|
||||||
|
|
||||||
[[bin]]
|
[lib]
|
||||||
name = "groupadd"
|
name = "redox_installer"
|
||||||
path = "src/bin/groupadd.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "groupdel"
|
|
||||||
path = "src/bin/groupdel.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "groupmod"
|
|
||||||
path = "src/bin/groupmod.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "login"
|
|
||||||
path = "src/bin/login.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "passwd"
|
|
||||||
path = "src/bin/passwd.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "su"
|
|
||||||
path = "src/bin/su.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "sudo"
|
|
||||||
path = "src/bin/sudo.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "useradd"
|
|
||||||
path = "src/bin/useradd.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "userdel"
|
|
||||||
path = "src/bin/userdel.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "usermod"
|
|
||||||
path = "src/bin/usermod.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.0"
|
anyhow = "1"
|
||||||
extra = { git = "https://gitlab.redox-os.org/redox-os/libextra.git" }
|
arg_parser = "0.1.0"
|
||||||
orbclient = "0.3.47"
|
fatfs = { version = "0.3.0", optional = true }
|
||||||
plain = "0.2.3"
|
fscommon = { version = "0.1.1", optional = true }
|
||||||
redox_liner = "0.5.2"
|
gpt = { version = "3.0.0", optional = true }
|
||||||
libredox = { path = "../libredox", features = ["mkns"] }
|
libc = "0.2.70"
|
||||||
redox_termios = "0.1.3"
|
pkgar = { version = "0.2", optional = true }
|
||||||
redox_event = "0.4.3"
|
pkgar-core = { version = "0.2", optional = true }
|
||||||
redox-scheme = { path = "../redox-scheme" }
|
pkgar-keys = { version = "0.2", optional = true }
|
||||||
redox_syscall = { path = "../syscall" }
|
rand = { version = "0.9", optional = true }
|
||||||
redox_users = "0.4.6"
|
redox-pkg = { version = "0.3", features = ["indicatif"], optional = true }
|
||||||
termion = "4"
|
redox_syscall = { path = "../syscall", optional = true }
|
||||||
libc = "0.2"
|
redoxfs = { path = "../redoxfs", optional = true, default-features = false, features = ["std", "log"] }
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
rust-argon2 = { version = "3", optional = true }
|
||||||
toml = "0.8.11"
|
serde = "1"
|
||||||
ioslice = "0.6"
|
serde_derive = "1.0"
|
||||||
|
termion = { version = "4", optional = true }
|
||||||
|
toml = "0.8"
|
||||||
|
uuid = { version = "1.4", features = ["v4"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "redox")'.dependencies]
|
[target.'cfg(target_os = "redox")'.dependencies]
|
||||||
redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc", default-features = false }
|
libredox = { path = "../libredox" }
|
||||||
|
ring = "=0.17.8"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["installer", "fuse"]
|
||||||
|
installer = [
|
||||||
|
"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" }
|
||||||
libredox = { path = "../libredox" }
|
libredox = { path = "../libredox" }
|
||||||
redox-scheme = { path = "../redox-scheme" }
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016 The Redox developers
|
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
|
||||||
|
|||||||
@@ -1,24 +1,50 @@
|
|||||||
# Redox OS user and group utilities.
|
# Redox OS installer
|
||||||
|
|
||||||
The `userutils` crate contains the utilities for dealing with users and groups in Redox OS.
|
The Redox installer will allow you to produce a Redox OS image. You will
|
||||||
They are heavily influenced by UNIX and are, when needed, tailored to specific Redox use cases.
|
be able to specify:
|
||||||
|
- Output device (raw image, ISO, QEMU, VirtualBox, drive)
|
||||||
|
- Filesystem
|
||||||
|
- Included packages
|
||||||
|
- Method of installation (from source, from binary)
|
||||||
|
- User accounts
|
||||||
|
|
||||||
These implementations strive to be as simple as possible drawing particular
|
You will be prompted to install dependencies, based on your OS and method of
|
||||||
inspiration by BSD systems. They are indeed small, by choice.
|
installation. The easiest method is to install from binaries.
|
||||||
|
|
||||||
[](https://travis-ci.org/redox-os/userutils)
|
## Usage
|
||||||
|
|
||||||
**Currently included:**
|
It is recommended to compile with `cargo`, in release mode:
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
- `getty`: Used by `init(8)` to open and initialize the TTY line, read a login name and invoke `login(1)`.
|
By default, you will be prompted to supply configuration options. You can
|
||||||
- `id`: Displays user identity.
|
use the scripted mode by supplying a configuration file:
|
||||||
- `login`: Allows users to login into the system
|
```bash
|
||||||
- `passwd`: Allows users to modify their passwords.
|
cargo run --release -- config/example.toml
|
||||||
- `su`: Allows users to substitute identity.
|
```
|
||||||
- `sudo`: Enables users to execute a command as another user.
|
An example configuration can be found in [config/example.toml](./config/example.toml).
|
||||||
- `useradd`: Add a user
|
Unsuplied configuration will use the default. You can use the `general.prompt`
|
||||||
- `usermod`: Modify user information
|
setting to prompt when configuration is not set. Multiple configurations can
|
||||||
- `userdel`: Delete a user
|
be specified, they will be built in order.
|
||||||
- `groupadd`: Add a user group
|
|
||||||
- `groupmod`: Modify group information
|
## Embedding
|
||||||
- `groupdel`: Remove a user group
|
|
||||||
|
The installer can also be used inside of other crates, as a library:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Cargo.toml
|
||||||
|
[dependencies]
|
||||||
|
redox_installer = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/main.rs
|
||||||
|
extern crate redox_installer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut config = redox_installer::Config::default();
|
||||||
|
...
|
||||||
|
redox_installer::install(config);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
########## Red Bear OS #########
|
|
||||||
# Login with the following: #
|
|
||||||
# `user` #
|
|
||||||
# `root`:`password` #
|
|
||||||
################################
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
########## Redox OS ##########
|
|
||||||
# Login with the following: #
|
|
||||||
# `user` #
|
|
||||||
# `root`:`password` #
|
|
||||||
##############################
|
|
||||||
|
|
||||||
+411
@@ -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
@@ -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"
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, ErrorKind, Read, Stderr, Write};
|
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
|
||||||
use std::process::{Child, Command, Stdio};
|
|
||||||
use std::str;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use event::{EventFlags, RawEventQueue};
|
|
||||||
use extra::io::fail;
|
|
||||||
use libredox::call as redox;
|
|
||||||
use libredox::errno::EAGAIN;
|
|
||||||
use libredox::flag;
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{getty} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
getty - set terminal mode
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
getty [-J | --noclear | -C | --contain ] tty
|
|
||||||
getty [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The getty utility is called by init(8) to open and initialize the tty line,
|
|
||||||
read a login name, and invoke login(1).
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Display this help and exit.
|
|
||||||
|
|
||||||
-J, --noclear
|
|
||||||
Do not clear the screen before forking login(1).
|
|
||||||
|
|
||||||
-C, --contain
|
|
||||||
Run contain_login instead of login
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Jeremy Soller.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
const DEFAULT_COLS: u16 = 80;
|
|
||||||
const DEFAULT_LINES: u16 = 30;
|
|
||||||
|
|
||||||
pub fn handle(
|
|
||||||
event_queue: &mut RawEventQueue,
|
|
||||||
tty_fd: RawFd,
|
|
||||||
master_fd: RawFd,
|
|
||||||
process: &mut Child,
|
|
||||||
) {
|
|
||||||
// tty_fd => Display
|
|
||||||
// master_fd => PTY
|
|
||||||
|
|
||||||
let handle_event = |event_id: usize| {
|
|
||||||
if event_id as RawFd == tty_fd {
|
|
||||||
let mut packet = [0; 4096];
|
|
||||||
loop {
|
|
||||||
let count = match redox::read(tty_fd as usize, &mut packet) {
|
|
||||||
Ok(0) => return,
|
|
||||||
Ok(count) => count,
|
|
||||||
Err(ref err) if err.errno() == EAGAIN => break,
|
|
||||||
Err(_) => panic!("getty: failed to read from TTY"),
|
|
||||||
};
|
|
||||||
redox::write(master_fd as usize, &packet[..count])
|
|
||||||
.expect("getty: failed to write master PTY");
|
|
||||||
}
|
|
||||||
} else if event_id as RawFd == master_fd {
|
|
||||||
let mut packet = [0; 4096];
|
|
||||||
loop {
|
|
||||||
let count = match redox::read(master_fd as usize, &mut packet) {
|
|
||||||
Ok(0) => return,
|
|
||||||
Ok(count) => count,
|
|
||||||
Err(ref err) if err.errno() == EAGAIN => break,
|
|
||||||
Err(_) => panic!("getty: failed to read from master TTY"),
|
|
||||||
};
|
|
||||||
redox::write(tty_fd as usize, &packet[1..count])
|
|
||||||
.expect("getty: failed to write to TTY");
|
|
||||||
if packet[0] & 1 == 1 {
|
|
||||||
let _ = redox::fsync(tty_fd as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handle_event(tty_fd as usize);
|
|
||||||
handle_event(master_fd as usize);
|
|
||||||
|
|
||||||
'events: loop {
|
|
||||||
let sys_event = event_queue
|
|
||||||
.next()
|
|
||||||
.expect("getty: event queue stopped")
|
|
||||||
.expect("getty: failed to read event file");
|
|
||||||
handle_event(sys_event.fd);
|
|
||||||
|
|
||||||
match process.try_wait() {
|
|
||||||
Ok(status) => match status {
|
|
||||||
Some(_code) => break 'events,
|
|
||||||
None => (),
|
|
||||||
},
|
|
||||||
Err(err) => match err.kind() {
|
|
||||||
ErrorKind::WouldBlock => (),
|
|
||||||
_ => panic!("getty: failed to wait on child: {:?}", err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = process.kill();
|
|
||||||
process.wait().expect("getty: failed to wait on login");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getpty(columns: u16, lines: u16) -> (RawFd, String) {
|
|
||||||
let master = redox::open(
|
|
||||||
"/scheme/pty",
|
|
||||||
flag::O_CLOEXEC | flag::O_RDWR | flag::O_CREAT | flag::O_NONBLOCK,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.expect("getty: failed to create PTY");
|
|
||||||
|
|
||||||
if let Ok(winsize_fd) = redox::dup(master, b"winsize") {
|
|
||||||
let _ = redox::write(
|
|
||||||
winsize_fd,
|
|
||||||
&redox_termios::Winsize {
|
|
||||||
ws_row: lines,
|
|
||||||
ws_col: columns,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let _ = redox::close(winsize_fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buf: [u8; 4096] = [0; 4096];
|
|
||||||
let count = redox::fpath(master, &mut buf).unwrap();
|
|
||||||
(master as RawFd, unsafe {
|
|
||||||
String::from_utf8_unchecked(Vec::from(&buf[..count]))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// termion cursor_pos prone to error and does not work on nonblocking files
|
|
||||||
fn tty_cursor_pos(tty: &mut File) -> Result<(u16, u16), Box<dyn Error>> {
|
|
||||||
write!(tty, "\x1B[6n")?;
|
|
||||||
tty.flush()?;
|
|
||||||
|
|
||||||
let timeout = Duration::from_millis(500);
|
|
||||||
let instant = Instant::now();
|
|
||||||
let mut data = String::new();
|
|
||||||
while instant.elapsed() < timeout {
|
|
||||||
let mut bytes = [0];
|
|
||||||
match tty.read(&mut bytes) {
|
|
||||||
Ok(count) => if count == 1 {
|
|
||||||
let c = bytes[0] as char;
|
|
||||||
if c == 'R' {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
data.push(c);
|
|
||||||
},
|
|
||||||
Err(err) => if err.kind() != ErrorKind::WouldBlock {
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.is_empty() {
|
|
||||||
return Err("cursor position timed out".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let beg = data.rfind('[').ok_or("failed to find [")?;
|
|
||||||
let coords: String = data.chars().skip(beg + 1).collect();
|
|
||||||
let mut nums = coords.split(';');
|
|
||||||
|
|
||||||
let row = nums.next().ok_or("failed to find row")?.parse::<u16>()?;
|
|
||||||
let col = nums.next().ok_or("failed to find col")?.parse::<u16>()?;
|
|
||||||
|
|
||||||
Ok((col, row))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tty_columns_lines(tty: &mut File) -> Result<(u16, u16), Box<dyn Error>> {
|
|
||||||
write!(tty, "{}", termion::cursor::Save)?;
|
|
||||||
tty.flush()?;
|
|
||||||
|
|
||||||
write!(tty, "{}", termion::cursor::Goto(999, 999))?;
|
|
||||||
tty.flush()?;
|
|
||||||
|
|
||||||
let res = tty_cursor_pos(tty);
|
|
||||||
|
|
||||||
write!(tty, "{}", termion::cursor::Restore)?;
|
|
||||||
tty.flush()?;
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn daemon(tty: &mut File, clear: bool, contain: bool, stderr: &mut Stderr) {
|
|
||||||
let (columns, lines) = tty_columns_lines(tty).unwrap_or((DEFAULT_COLS, DEFAULT_LINES));
|
|
||||||
let tty_fd = tty.as_raw_fd();
|
|
||||||
|
|
||||||
let (master_fd, pty) = getpty(columns, lines);
|
|
||||||
|
|
||||||
let mut event_queue = event::RawEventQueue::new().expect("getty: failed to open event queue");
|
|
||||||
|
|
||||||
event_queue
|
|
||||||
.subscribe(tty_fd as usize, 0, EventFlags::READ)
|
|
||||||
.expect("getty: failed to fevent TTY");
|
|
||||||
|
|
||||||
event_queue
|
|
||||||
.subscribe(master_fd as usize, 0, EventFlags::READ)
|
|
||||||
.expect("getty: failed to fevent master PTY");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if clear {
|
|
||||||
let _ = redox::write(tty_fd as usize, b"\x1Bc");
|
|
||||||
}
|
|
||||||
let _ = redox::fsync(tty_fd as usize);
|
|
||||||
|
|
||||||
let slave_stdin = redox::open(&pty, flag::O_CLOEXEC | flag::O_RDONLY, 0)
|
|
||||||
.expect("getty: failed to open slave stdin");
|
|
||||||
let slave_stdout = redox::open(&pty, flag::O_CLOEXEC | flag::O_WRONLY, 0)
|
|
||||||
.expect("getty: failed to open slave stdout");
|
|
||||||
let slave_stderr = redox::open(&pty, flag::O_CLOEXEC | flag::O_WRONLY, 0)
|
|
||||||
.expect("getty: failed to open slave stderr");
|
|
||||||
|
|
||||||
let mut command = if contain {
|
|
||||||
Command::new("contain_login")
|
|
||||||
} else {
|
|
||||||
Command::new("login")
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
command
|
|
||||||
.stdin(Stdio::from_raw_fd(slave_stdin as RawFd))
|
|
||||||
.stdout(Stdio::from_raw_fd(slave_stdout as RawFd))
|
|
||||||
.stderr(Stdio::from_raw_fd(slave_stderr as RawFd))
|
|
||||||
.env("TERM", "xterm-256color")
|
|
||||||
.env("TTY", &pty);
|
|
||||||
}
|
|
||||||
|
|
||||||
match command.spawn() {
|
|
||||||
Ok(mut process) => {
|
|
||||||
handle(&mut event_queue, tty_fd, master_fd, &mut process);
|
|
||||||
}
|
|
||||||
Err(err) => fail(&format!("getty: failed to execute login: {}", err), stderr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
|
|
||||||
let args = clap_app!(getty =>
|
|
||||||
(author: "Jeremy Soller")
|
|
||||||
(about: "Set terminal mode")
|
|
||||||
(@arg TTY: +required "")
|
|
||||||
(@arg NO_CLEAR: -J --("no-clear") "Do not clear the screen before forking")
|
|
||||||
(@arg CONTAIN: -C --("contain") "Run contain_login instead of login")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let clear = !args.is_present("NO_CLEAR");
|
|
||||||
|
|
||||||
let contain = args.is_present("CONTAIN");
|
|
||||||
|
|
||||||
let vt = args.value_of("TTY").unwrap();
|
|
||||||
|
|
||||||
let buf: String;
|
|
||||||
let vt_path = if vt.parse::<usize>().is_ok() {
|
|
||||||
buf = format!("/scheme/fbcon/{vt}");
|
|
||||||
&*buf
|
|
||||||
} else {
|
|
||||||
vt
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tty = match redox::open(
|
|
||||||
&vt_path,
|
|
||||||
flag::O_CLOEXEC | flag::O_RDWR | flag::O_NONBLOCK,
|
|
||||||
0,
|
|
||||||
) {
|
|
||||||
Ok(fd) => unsafe { File::from_raw_fd(fd as RawFd) },
|
|
||||||
Err(err) => fail(
|
|
||||||
&format!("getty: failed to open TTY {}: {}", vt_path, err),
|
|
||||||
&mut stderr,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
daemon(&mut tty, clear, contain, &mut stderr);
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use redox_users::{All, AllGroups, Config, Error, GroupBuilder};
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{groupadd} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
groupadd - add a user group
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
groupadd [ -f | --force ] GROUP
|
|
||||||
groupadd [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The groupadd utility adds a new user group using values
|
|
||||||
passed on the command line and system defaults.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-f, --force
|
|
||||||
Simply forces the exit status of the program to 0
|
|
||||||
even if the group already exists. A message is still
|
|
||||||
printed to stdout.
|
|
||||||
|
|
||||||
-g, --gid GID
|
|
||||||
The group id to use. This value must not be used and must
|
|
||||||
be non-negative. The default is to pick the smallest available
|
|
||||||
group id (between values defined in redox_users).
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Display this help and exit.
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Wesley Hershberger.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = clap_app!(groupadd =>
|
|
||||||
(author: "Wesley Hershberger")
|
|
||||||
(about: "Add groups based on the system's redox_users backend")
|
|
||||||
(@arg GROUP: +required "Add group GROUP")
|
|
||||||
(@arg FORCE: -f --force "Force the status of the program to be 0 even if the group exists")
|
|
||||||
(@arg GID: -g --gid +takes_value "Group id. Positive integer and must not be in use")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let groupname = args.value_of("GROUP").unwrap();
|
|
||||||
|
|
||||||
let gid = match args.value_of("GID") {
|
|
||||||
Some(gid) => {
|
|
||||||
let id = gid.parse::<usize>().unwrap_or_exit(1);
|
|
||||||
if let Some(_group) = sys_groups.get_by_id(id) {
|
|
||||||
eprintln!("groupadd: group already exists");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
None => sys_groups.get_unique_id().unwrap_or_else(|| {
|
|
||||||
eprintln!("groupadd: no available gid");
|
|
||||||
exit(1);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let group = GroupBuilder::new(groupname).gid(gid);
|
|
||||||
match sys_groups.add_group(group) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(Error::GroupAlreadyExists) if args.is_present("FORCE") => {
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("groupadd: {}", err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllGroups, Config};
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{groupdel} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
groupdel - modify system files to delete groups
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
groupdel [ options ] GROUP
|
|
||||||
groupdel [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
groupdel removes groups from whatever backend is employed by
|
|
||||||
the system's redox_users.
|
|
||||||
|
|
||||||
Note that you should not remove a primary user group before
|
|
||||||
removing the user. It is also generally wise not to remove
|
|
||||||
groups that still own files on the system.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-h, --help
|
|
||||||
Print this help page and exit.
|
|
||||||
|
|
||||||
AUTHORS
|
|
||||||
Wesley Hershberger.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let matches = clap_app!(groupdel =>
|
|
||||||
(author: "Wesley Hershberger")
|
|
||||||
(about: "Removes a group from the system using redox_users")
|
|
||||||
(@arg GROUP: +required "Removes group GROUP")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let group = matches.value_of("GROUP").unwrap();
|
|
||||||
|
|
||||||
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
sys_groups.remove_by_name(group.to_string());
|
|
||||||
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllGroups, AllUsers, Config};
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{groupmod} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
groupmod - modify group information
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
groupmod [ options ] GROUP
|
|
||||||
groupmod [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
groupmod modifies a user group GROUP in the system's
|
|
||||||
redox_users backend.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-h, --help
|
|
||||||
Print this help page and exit.
|
|
||||||
|
|
||||||
-g, --gid GID
|
|
||||||
Change GROUP's group id. GID must be a non-negative
|
|
||||||
decimal integer.
|
|
||||||
|
|
||||||
Files with GROUP's old gid will not be updated.
|
|
||||||
|
|
||||||
User's who use the old gid as their primary gid will
|
|
||||||
be updated.
|
|
||||||
|
|
||||||
-n, --name NAME
|
|
||||||
The name of the group will be set to NAME
|
|
||||||
|
|
||||||
AUTHORS
|
|
||||||
Wesley Hershberger.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = clap_app!(groupmod =>
|
|
||||||
(author: "Wesley Hershberger")
|
|
||||||
(about: "Modify users according to the system's redox_users backend")
|
|
||||||
(@arg GROUP: +required "Modify GROUP")
|
|
||||||
(@arg GID: -g --gid +takes_value "Change GROUP's group id. See man page for details")
|
|
||||||
(@arg NAME: -n --name +takes_value "Change GROUP's name")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let groupname = args.value_of("GROUP").unwrap();
|
|
||||||
|
|
||||||
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
{
|
|
||||||
let group = sys_groups.get_mut_by_name(groupname).unwrap_or_else(|| {
|
|
||||||
eprintln!("groupmod: group not found: {}", groupname);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(gid) = args.value_of("GID") {
|
|
||||||
let gid = gid.parse::<usize>().unwrap_or_exit(1);
|
|
||||||
// Update users
|
|
||||||
let mut sys_users =
|
|
||||||
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
for user in sys_users.iter_mut() {
|
|
||||||
if user.gid == group.gid {
|
|
||||||
user.gid = gid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sys_users.save().unwrap_or_exit(1);
|
|
||||||
group.gid = gid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(name) = args.value_of("NAME") {
|
|
||||||
group.group = name.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
-163
@@ -1,163 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::env::args;
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllGroups, AllUsers, Config, get_egid, get_euid, get_gid, get_uid};
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{id} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
id - display user identity
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
id
|
|
||||||
id -g [-nr]
|
|
||||||
id -u [-nr]
|
|
||||||
id [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The id utility displays the user and group names and numeric IDs, of
|
|
||||||
the calling process, to the standard output.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-G, --groups
|
|
||||||
Display the different group IDs (effective and real) as white-space
|
|
||||||
separated numbers, in no particular order.
|
|
||||||
|
|
||||||
-g, --group
|
|
||||||
Display the effective group ID as a number.
|
|
||||||
|
|
||||||
-n, --name
|
|
||||||
Display the name of the user or group ID for the -g and -u options
|
|
||||||
instead of the number.
|
|
||||||
|
|
||||||
-u, --user
|
|
||||||
Display the effective user ID as a number.
|
|
||||||
|
|
||||||
-a
|
|
||||||
Ignored for compatibility with other id implementations.
|
|
||||||
|
|
||||||
-r, --real
|
|
||||||
Display the real ID for the -g and -u options instead of the effective ID.
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Display help and exit.
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Jose Narvaez.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let app = clap_app!(id =>
|
|
||||||
(author: "Jose Narvaez")
|
|
||||||
(about: "Get user and group information about the current user")
|
|
||||||
(@arg IGNORE: -a "Ignored for compatibility with other impls of id")
|
|
||||||
(@arg GROUPS: -G --groups conflicts_with[selector modifier] "Display current user's real and effective group id's")
|
|
||||||
(@group selector =>
|
|
||||||
(@arg GROUP: -g --group "Display current user's effective group id")
|
|
||||||
(@arg USER: -u --user "Display the effective userid")
|
|
||||||
)
|
|
||||||
(@group modifier =>
|
|
||||||
(@arg NAME: -n --name requires[selector] "Display names of groups/users instead of ids (use with -g or -u)")
|
|
||||||
(@arg REAL: -r --real requires[selector] "Display real id's instead of effective ids (use with -g and -u)")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let args = match &*args().nth(0).unwrap_or(String::new()) {
|
|
||||||
"whoami" => app.get_matches_from(["id", "-un"].iter()),
|
|
||||||
_ => app.get_matches(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Display the different group IDs (effective and real)
|
|
||||||
// as white-space separated numbers, in no particular order.
|
|
||||||
if args.is_present("GROUPS") {
|
|
||||||
let egid = get_egid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let gid = get_gid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{} {}", egid, gid);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display effective/real process user ID UNIX user name
|
|
||||||
if args.is_present("USER") && args.is_present("NAME") {
|
|
||||||
// Did they pass -r? If so, we show the real
|
|
||||||
let uid = if args.is_present("REAL") {
|
|
||||||
get_uid()
|
|
||||||
} else {
|
|
||||||
get_euid()
|
|
||||||
}
|
|
||||||
.unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
|
|
||||||
let user = users.get_by_id(uid).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{}", user.user);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display real user ID
|
|
||||||
if args.is_present("USER") && args.is_present("REAL") {
|
|
||||||
let uid = get_uid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{}", uid);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display effective user ID
|
|
||||||
if args.is_present("USER") {
|
|
||||||
let euid = get_euid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{}", euid);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display effective/real process group ID UNIX group name
|
|
||||||
if args.is_present("GROUP") && args.is_present("NAME") {
|
|
||||||
// Did they pass -r? If so we show the real one
|
|
||||||
let gid = if args.is_present("REAL") {
|
|
||||||
get_gid()
|
|
||||||
} else {
|
|
||||||
get_egid()
|
|
||||||
}
|
|
||||||
.unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
|
|
||||||
let group = groups.get_by_id(gid).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{}", group.group);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display the real group ID
|
|
||||||
if args.is_present("GROUP") && args.is_present("REAL") {
|
|
||||||
let gid = get_gid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{}", gid);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display effective group ID
|
|
||||||
if args.is_present("GROUP") {
|
|
||||||
let egid = get_egid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("{}", egid);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We get everything we can and show
|
|
||||||
let euid = get_euid().unwrap_or_exit(1);
|
|
||||||
let egid = get_egid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
|
|
||||||
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let user = users.get_by_id(euid).unwrap_or_exit(1);
|
|
||||||
let group = groups.get_by_id(egid).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
println!("uid={}({}) gid={}({})", euid, user.user, egid, group.group);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use libredox::error::Result;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllUsers, Config, User};
|
|
||||||
use termion::input::TermRead;
|
|
||||||
use userutils::spawn_shell;
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{login} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
login - log into the computer
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
login
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The login utility logs users (and pseudo-users) into the computer system.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h --help
|
|
||||||
Display help info and exit.
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Jeremy Soller, Jose Narvaez.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
const ISSUE_FILE: &'static str = "/etc/issue";
|
|
||||||
const MOTD_FILE: &'static str = "/etc/motd";
|
|
||||||
|
|
||||||
// TODO: Move to redox_users once the definition solidifies.
|
|
||||||
const DEFAULT_SCHEMES: [&'static str; 26] = [
|
|
||||||
// Kernel schemes
|
|
||||||
"debug",
|
|
||||||
"event",
|
|
||||||
"memory",
|
|
||||||
"pipe",
|
|
||||||
"serio",
|
|
||||||
"irq",
|
|
||||||
"time",
|
|
||||||
"sys",
|
|
||||||
// Base schemes
|
|
||||||
"rand",
|
|
||||||
"null",
|
|
||||||
"zero",
|
|
||||||
"log",
|
|
||||||
// Network schemes
|
|
||||||
"ip",
|
|
||||||
"icmp",
|
|
||||||
"tcp",
|
|
||||||
"udp",
|
|
||||||
// IPC schemes
|
|
||||||
"shm",
|
|
||||||
"chan",
|
|
||||||
"uds_stream",
|
|
||||||
"uds_dgram",
|
|
||||||
// File schemes
|
|
||||||
"file",
|
|
||||||
// Display schemes
|
|
||||||
"display.vesa",
|
|
||||||
"display*",
|
|
||||||
// Other schemes
|
|
||||||
"pty",
|
|
||||||
"sudo",
|
|
||||||
"audio",
|
|
||||||
];
|
|
||||||
pub fn apply_login_schemes(
|
|
||||||
user: &User<redox_users::auth::Full>,
|
|
||||||
default_schemes: &[&str],
|
|
||||||
) -> Result<libredox::Fd> {
|
|
||||||
let schemes = match load_config_schemes(user) {
|
|
||||||
Some(s) => s,
|
|
||||||
_ => default_schemes.iter().map(|s| s.to_string()).collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut names: Vec<ioslice::IoSlice> = Vec::with_capacity(schemes.len());
|
|
||||||
for scheme in schemes.iter() {
|
|
||||||
names.push(ioslice::IoSlice::new(scheme.as_bytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ns_fd = libredox::call::mkns(&names)?;
|
|
||||||
let before_ns_fd = libredox::Fd::new(libredox::call::setns(ns_fd)?);
|
|
||||||
|
|
||||||
Ok(before_ns_fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_config_schemes(user: &User<redox_users::auth::Full>) -> Option<Vec<String>> {
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
const LOGIN_SCHEMES_FILE: &'static str = "/etc/login_schemes.toml";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct UserSchemeConfig {
|
|
||||||
pub schemes: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct LoginConfig {
|
|
||||||
#[serde(rename = "user_schemes")]
|
|
||||||
pub user_schemes: BTreeMap<String, UserSchemeConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_str = fs::read_to_string(LOGIN_SCHEMES_FILE).ok()?;
|
|
||||||
let config: LoginConfig = toml::from_str(&config_str).ok()?;
|
|
||||||
|
|
||||||
config
|
|
||||||
.user_schemes
|
|
||||||
.get(&user.user)
|
|
||||||
.map(|cfg| cfg.schemes.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
|
|
||||||
let _args = clap_app!(login =>
|
|
||||||
(author: "Jeremy Soller, Jose Narvaez")
|
|
||||||
(about: "Login as a user")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
if let Ok(mut issue) = File::open(ISSUE_FILE) {
|
|
||||||
io::copy(&mut issue, &mut stdout).r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let user = liner::Context::new()
|
|
||||||
.read_line(
|
|
||||||
liner::Prompt::from("\x1B[1mRed Bear login:\x1B[0m "),
|
|
||||||
None,
|
|
||||||
&mut liner::BasicCompleter::new(Vec::<String>::new()),
|
|
||||||
)
|
|
||||||
.r#try(&mut stderr);
|
|
||||||
|
|
||||||
if !user.is_empty() {
|
|
||||||
let stdin = io::stdin();
|
|
||||||
let mut stdin = stdin.lock();
|
|
||||||
let sys_users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
match sys_users.get_by_name(user) {
|
|
||||||
None => {
|
|
||||||
stdout.write(b"\nLogin incorrect\n").r#try(&mut stderr);
|
|
||||||
stdout.write(b"\n").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(user) => {
|
|
||||||
if user.is_passwd_blank() {
|
|
||||||
if let Ok(mut motd) = File::open(MOTD_FILE) {
|
|
||||||
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let before_ns_fd =
|
|
||||||
apply_login_schemes(user, &DEFAULT_SCHEMES).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let _ = syscall::fcntl(
|
|
||||||
before_ns_fd.raw(),
|
|
||||||
syscall::F_SETFD,
|
|
||||||
syscall::O_CLOEXEC,
|
|
||||||
);
|
|
||||||
spawn_shell(user).unwrap_or_exit(1);
|
|
||||||
let _ = syscall::fcntl(before_ns_fd.raw(), syscall::F_SETFD, 0);
|
|
||||||
let _ = libredox::call::close(
|
|
||||||
libredox::call::setns(before_ns_fd.into_raw()).unwrap_or_exit(1),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout
|
|
||||||
.write_all(b"\x1B[1mpassword:\x1B[0m ")
|
|
||||||
.r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
|
|
||||||
stdout.write(b"\n").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
|
|
||||||
if user.verify_passwd(&password) {
|
|
||||||
if let Ok(mut motd) = File::open(MOTD_FILE) {
|
|
||||||
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn_shell(user).unwrap_or_exit(1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stdout.write(b"\n").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use libredox::flag::O_CLOEXEC;
|
|
||||||
use libredox::errno::EPERM;
|
|
||||||
use redox_users::{All, AllUsers, Config, get_uid};
|
|
||||||
use termion::input::TermRead;
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{passwd} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
passwd - modify a user's password
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
passwd [ LOGIN ]
|
|
||||||
passwd [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The passwd utility changes the user's local password. If the user is not
|
|
||||||
the super-user, passwd first prompts for the current password and will
|
|
||||||
not continue unless the correct password is entered.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Display this help and exit.
|
|
||||||
|
|
||||||
-l, --lock
|
|
||||||
Lock the password of the named account. This changes the stored password
|
|
||||||
hash so that it matches no encrypted value ("!")
|
|
||||||
|
|
||||||
Users with locked passwords are not allowed to change their password.
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Jeremy Soller, Jose Narvaez.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut stdin = io::stdin().lock();
|
|
||||||
let mut stdout = io::stdout().lock();
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
|
|
||||||
let args = clap_app!(passwd =>
|
|
||||||
(author: "Jeremy Soller, Jose Narvaez")
|
|
||||||
(about: "Set user passwords")
|
|
||||||
(@arg LOGIN: "Apply to login. Sets password for current user if not supplied")
|
|
||||||
(@arg LOCK: -l --lock "Lock the password for an account (no login)")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
if args.is_present("LOCK") {
|
|
||||||
if get_uid().unwrap_or_exit(1) != 0 {
|
|
||||||
eprintln!("passwd: only root is allowed to lock accounts");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut users =
|
|
||||||
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let Some(login) = args.value_of("LOGIN") else {
|
|
||||||
eprintln!("passwd: no account specified to lock");
|
|
||||||
exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = users.get_mut_by_name(login).unwrap_or_else(|| {
|
|
||||||
eprintln!("passwd: user does not exist: {}", login);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
user.unset_passwd();
|
|
||||||
users.save().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let uid = get_uid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
if uid == 0 {
|
|
||||||
let mut users =
|
|
||||||
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let user = find_user(&args, &mut users);
|
|
||||||
|
|
||||||
let msg = format!("changing password for '{}' \n", user.user);
|
|
||||||
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
|
|
||||||
let new_password = ask_new_password(stdin, stdout, stderr);
|
|
||||||
|
|
||||||
user.set_passwd(&new_password).unwrap_or_exit(1);
|
|
||||||
users.save().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let user = find_user(&args, &mut users);
|
|
||||||
|
|
||||||
if user.uid != uid {
|
|
||||||
eprintln!(
|
|
||||||
"passwd: you do not have permission to set the password of '{}'",
|
|
||||||
user.user
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = format!("changing password for '{}' \n", user.user);
|
|
||||||
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
|
|
||||||
drop(users); // Unlock /etc/passwd
|
|
||||||
|
|
||||||
stdout.write_all(b"current password: ").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
|
|
||||||
let file = libredox::call::open("/scheme/sudo/passwd", O_CLOEXEC, 0).unwrap();
|
|
||||||
|
|
||||||
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
|
|
||||||
stdout.write(b"\n").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
|
|
||||||
match libredox::call::write(file, password.as_bytes()) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) if err.errno() == EPERM => {
|
|
||||||
eprintln!("passwd: incorrect current password");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Err(err) => panic!("{err}"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("passwd: incorrect current password");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_password = ask_new_password(stdin, stdout, stderr);
|
|
||||||
|
|
||||||
match libredox::call::write(file, new_password.as_bytes()) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) if err.errno() == EPERM => {
|
|
||||||
eprintln!("passwd: invalid new password");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Err(err) => panic!("{err}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_user<'a, T: Default>(
|
|
||||||
args: &clap::ArgMatches<'_>,
|
|
||||||
users: &'a mut AllUsers<T>,
|
|
||||||
) -> &'a mut redox_users::User<T> {
|
|
||||||
let uid = get_uid().unwrap_or_exit(1);
|
|
||||||
match args.value_of("LOGIN") {
|
|
||||||
Some(login) => users.get_mut_by_name(login).unwrap_or_else(|| {
|
|
||||||
eprintln!("passwd: user does not exist: {}", login);
|
|
||||||
exit(1);
|
|
||||||
}),
|
|
||||||
None => users.get_mut_by_id(uid).unwrap_or_else(|| {
|
|
||||||
eprintln!("passwd: you do not exist");
|
|
||||||
exit(1);
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ask_new_password(
|
|
||||||
mut stdin: io::StdinLock<'_>,
|
|
||||||
mut stdout: io::StdoutLock<'_>,
|
|
||||||
mut stderr: io::Stderr,
|
|
||||||
) -> String {
|
|
||||||
stdout.write_all(b"new password: ").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
let Some(new_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
|
|
||||||
eprintln!("passwd: no new password provided");
|
|
||||||
exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
stdout.write(b"\nconfirm password: ").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
let Some(confirm_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
|
|
||||||
eprintln!("\npasswd: no confirm password provided");
|
|
||||||
exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
stdout.write(b"\n").r#try(&mut stderr);
|
|
||||||
stdout.flush().r#try(&mut stderr);
|
|
||||||
|
|
||||||
if new_password != confirm_password {
|
|
||||||
eprintln!("passwd: new password does not match confirm password");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
new_password
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::process::exit;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use libredox::flag::O_CLOEXEC;
|
|
||||||
use redox_users::{All, AllUsers, Config, get_uid};
|
|
||||||
use syscall::EPERM;
|
|
||||||
use termion::input::TermRead;
|
|
||||||
use userutils::spawn_shell;
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{su} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
su - substitute user identity
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
su [ user ]
|
|
||||||
su [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The su utility requests appropriate user credentials via PAM and switches to
|
|
||||||
that user ID (the default user is the superuser). A shell is then executed.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h, --help
|
|
||||||
Display this help and exit.
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Jeremy Soller, Jose Narvaez.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let stdin = io::stdin();
|
|
||||||
let mut stdin = stdin.lock();
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
|
|
||||||
let args = clap_app!(su =>
|
|
||||||
(author: "Jeremy Soller, Jose Narvaez")
|
|
||||||
(about: "substitue user identity")
|
|
||||||
(@arg LOGIN: "Login as LOGIN. Default is \'root\'")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let target_user = args.value_of("LOGIN").unwrap_or("root");
|
|
||||||
|
|
||||||
let uid = get_uid().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
|
|
||||||
let user = users.get_by_name(&target_user).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
// If the user executing su is root, then they can do anything without a password.
|
|
||||||
// Same if the user we're being asked to login as doesn't have a password.
|
|
||||||
if uid == 0 {
|
|
||||||
writeln!(stdout).unwrap_or_exit(1);
|
|
||||||
exit(spawn_shell(user).unwrap_or_exit(1));
|
|
||||||
} else {
|
|
||||||
let file = libredox::call::open("/scheme/sudo/su", O_CLOEXEC, 0).unwrap();
|
|
||||||
|
|
||||||
write!(stdout, "password: ").unwrap_or_exit(1);
|
|
||||||
stdout.flush().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
// Read the password, reading an empty string if CTRL-d is specified
|
|
||||||
let password = stdin
|
|
||||||
.read_passwd(&mut stdout)
|
|
||||||
.r#try(&mut stderr)
|
|
||||||
.unwrap_or(String::new());
|
|
||||||
|
|
||||||
match libredox::call::write(file, password.as_bytes()) {
|
|
||||||
Ok(_) => exit(spawn_shell(user).unwrap_or_exit(1)),
|
|
||||||
Err(err) if err.errno() == EPERM => {
|
|
||||||
writeln!(stderr, "su: authentication failed").unwrap_or_exit(1);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Err(err) => panic!("{err}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-402
@@ -1,402 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
use std::process::{Command, exit};
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use libredox::flag::O_CLOEXEC;
|
|
||||||
use libredox::protocol::ProcCall;
|
|
||||||
use redox_rt::sys::proc_call;
|
|
||||||
use redox_scheme::scheme::{SchemeSync, register_sync_scheme};
|
|
||||||
use redox_scheme::{
|
|
||||||
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
|
|
||||||
};
|
|
||||||
use redox_users::{All, AllGroups, AllUsers, Config, get_uid};
|
|
||||||
use syscall::error::*;
|
|
||||||
use syscall::flag::*;
|
|
||||||
use syscall::schemev2::NewFdFlags;
|
|
||||||
use termion::input::TermRead;
|
|
||||||
|
|
||||||
const MAX_ATTEMPTS: u16 = 3;
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{sudo} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
sudo - execute a command as another user
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
sudo command
|
|
||||||
sudo [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The sudo utility allows a permitted user to execute a command as the
|
|
||||||
superuser or another user, as specified by the security policy.
|
|
||||||
|
|
||||||
EXIT STATUS
|
|
||||||
Upon successful execution of a command, the exit status from sudo will
|
|
||||||
be the exit status of the program that was executed. In case of error
|
|
||||||
the exit status will be >0.
|
|
||||||
|
|
||||||
AUTHOR
|
|
||||||
Written by Jeremy Soller, Jose Narvaez, bjorn3.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if env::args().nth(1).as_deref() == Some("--daemon") {
|
|
||||||
daemon_main();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut args = env::args().skip(1);
|
|
||||||
let cmd = args.next().unwrap_or_else(|| {
|
|
||||||
eprintln!("sudo: no command provided");
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
|
|
||||||
let uid = get_uid().unwrap_or_exit(1);
|
|
||||||
let user = users.get_by_id(uid).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
if uid == 0 {
|
|
||||||
// We are root already. No need to elevate privileges
|
|
||||||
run_command_as_root(&cmd, &args.collect());
|
|
||||||
}
|
|
||||||
|
|
||||||
let file = libredox::call::open("/scheme/sudo", O_CLOEXEC, 0).unwrap();
|
|
||||||
|
|
||||||
let mut attempts = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
print!("[sudo] password for {}: ", user.user);
|
|
||||||
let _ = io::stdout().flush();
|
|
||||||
|
|
||||||
match io::stdin().read_passwd(&mut io::stdout()).unwrap() {
|
|
||||||
Some(password) => {
|
|
||||||
println!();
|
|
||||||
|
|
||||||
match libredox::call::write(file, password.as_bytes()) {
|
|
||||||
Ok(_) => break,
|
|
||||||
Err(err) if err.errno() == EPERM => {
|
|
||||||
attempts += 1;
|
|
||||||
eprintln!(
|
|
||||||
"sudo: incorrect password or not in sudo group ({}/{})",
|
|
||||||
attempts, MAX_ATTEMPTS,
|
|
||||||
);
|
|
||||||
if attempts >= MAX_ATTEMPTS {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => panic!("{err}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME move to libredox
|
|
||||||
unsafe extern "C" {
|
|
||||||
safe fn redox_cur_procfd_v0() -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Elevate privileges of our own process with help from the sudo daemon
|
|
||||||
syscall::sendfd(
|
|
||||||
file,
|
|
||||||
syscall::dup(redox_cur_procfd_v0(), &[]).unwrap(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// FIXME perhaps keep the original namespace available in a subdirectory of the namespace we switch to?
|
|
||||||
let ns = syscall::openat(file, "ns", 0, syscall::O_CLOEXEC).unwrap();
|
|
||||||
libredox::call::setns(ns).unwrap();
|
|
||||||
|
|
||||||
run_command_as_root(&cmd, &args.collect());
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Policy {
|
|
||||||
Deny,
|
|
||||||
Authenticate,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn policy_for_user(uid: u32) -> Policy {
|
|
||||||
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
|
|
||||||
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let user = users.get_by_id(uid as usize).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let sudo_group = groups.get_by_name("sudo").unwrap_or_exit(1);
|
|
||||||
if !sudo_group.users.iter().any(|name| name == &user.user) {
|
|
||||||
return Policy::Deny;
|
|
||||||
}
|
|
||||||
|
|
||||||
Policy::Authenticate
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_command_as_root(cmd: &str, args: &Vec<String>) -> ! {
|
|
||||||
let mut command = Command::new(&cmd);
|
|
||||||
for arg in args {
|
|
||||||
command.arg(&arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
command.uid(0);
|
|
||||||
command.gid(0);
|
|
||||||
command.env("USER", "root");
|
|
||||||
command.env("UID", "0");
|
|
||||||
command.env("GROUPS", "0");
|
|
||||||
|
|
||||||
let err = command.exec();
|
|
||||||
|
|
||||||
eprintln!("sudo: failed to execute {}: {}", cmd, err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Scheme {
|
|
||||||
next_fd: usize,
|
|
||||||
handles: HashMap<usize, Handle>,
|
|
||||||
}
|
|
||||||
enum Handle {
|
|
||||||
AwaitingPassword { uid: u32 },
|
|
||||||
AwaitingRootPassword,
|
|
||||||
AwaitingContextFd,
|
|
||||||
AwaitingNamespaceFetch { ns: libredox::Fd },
|
|
||||||
|
|
||||||
AwaitingPasswordForPasswd { uid: u32 },
|
|
||||||
AwaitingNewPassword { uid: u32 },
|
|
||||||
|
|
||||||
Placeholder,
|
|
||||||
|
|
||||||
SchemeRoot,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchemeSync for Scheme {
|
|
||||||
fn scheme_root(&mut self) -> Result<usize> {
|
|
||||||
let fd = self.next_fd;
|
|
||||||
self.next_fd = self.next_fd.checked_add(1).ok_or(Error::new(EMFILE))?;
|
|
||||||
self.handles.insert(fd, Handle::SchemeRoot);
|
|
||||||
Ok(fd)
|
|
||||||
}
|
|
||||||
fn openat(
|
|
||||||
&mut self,
|
|
||||||
dirfd: usize,
|
|
||||||
path: &str,
|
|
||||||
_flags: usize,
|
|
||||||
_fcntl_flags: u32,
|
|
||||||
ctx: &CallerCtx,
|
|
||||||
) -> Result<OpenResult> {
|
|
||||||
let handle = match self.handles.get_mut(&dirfd).ok_or(Error::new(EBADF))? {
|
|
||||||
Handle::SchemeRoot => match path {
|
|
||||||
"" => Handle::AwaitingPassword { uid: ctx.uid },
|
|
||||||
"su" => Handle::AwaitingRootPassword,
|
|
||||||
"passwd" => Handle::AwaitingPasswordForPasswd { uid: ctx.uid },
|
|
||||||
_ => return Err(Error::new(ENOENT)),
|
|
||||||
},
|
|
||||||
Handle::AwaitingNamespaceFetch { .. } => {
|
|
||||||
if path != "ns" {
|
|
||||||
return Err(Error::new(ENOENT));
|
|
||||||
}
|
|
||||||
let ns = match self.handles.insert(dirfd, Handle::Placeholder).unwrap() {
|
|
||||||
Handle::AwaitingNamespaceFetch { ns } => ns,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
return Ok(OpenResult::OtherScheme { fd: ns.into_raw() });
|
|
||||||
}
|
|
||||||
_ => return Err(Error::new(EINVAL)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let fd = self.next_fd;
|
|
||||||
self.next_fd = self.next_fd.checked_add(1).ok_or(Error::new(EMFILE))?;
|
|
||||||
self.handles.insert(fd, handle);
|
|
||||||
|
|
||||||
Ok(OpenResult::ThisScheme {
|
|
||||||
number: fd,
|
|
||||||
flags: NewFdFlags::empty(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
buf: &[u8],
|
|
||||||
_off: u64,
|
|
||||||
_flags: u32,
|
|
||||||
_ctx: &CallerCtx,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
|
||||||
|
|
||||||
let validate_utf8 = |buf| std::str::from_utf8(buf).map_err(|_| Error::new(EINVAL));
|
|
||||||
|
|
||||||
match std::mem::replace(handle, Handle::Placeholder) {
|
|
||||||
Handle::AwaitingPassword { uid } => {
|
|
||||||
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
|
|
||||||
let user = users.get_by_id(uid as usize).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
match policy_for_user(uid) {
|
|
||||||
Policy::Deny => {
|
|
||||||
*handle = Handle::AwaitingPassword { uid };
|
|
||||||
return Err(Error::new(EPERM));
|
|
||||||
}
|
|
||||||
Policy::Authenticate => {
|
|
||||||
let password = validate_utf8(buf)?;
|
|
||||||
if user.verify_passwd(&password) {
|
|
||||||
*handle = Handle::AwaitingContextFd
|
|
||||||
} else {
|
|
||||||
*handle = Handle::AwaitingPassword { uid };
|
|
||||||
return Err(Error::new(EPERM));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Handle::AwaitingRootPassword => {
|
|
||||||
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
|
|
||||||
let user = users.get_by_id(0).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let password = validate_utf8(buf)?;
|
|
||||||
if user.verify_passwd(&password) {
|
|
||||||
*handle = Handle::AwaitingContextFd
|
|
||||||
} else {
|
|
||||||
*handle = Handle::AwaitingRootPassword;
|
|
||||||
return Err(Error::new(EPERM));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Handle::AwaitingContextFd => {
|
|
||||||
*handle = Handle::AwaitingContextFd;
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle::AwaitingPasswordForPasswd { uid } => {
|
|
||||||
let users =
|
|
||||||
AllUsers::authenticator(Config::default()).map_err(|_| Error::new(ENOLCK))?;
|
|
||||||
let user = users.get_by_id(uid as usize).ok_or(Error::new(EEXIST))?;
|
|
||||||
|
|
||||||
let password = validate_utf8(buf)?;
|
|
||||||
if user.verify_passwd(&password) {
|
|
||||||
*handle = Handle::AwaitingNewPassword { uid }
|
|
||||||
} else {
|
|
||||||
*handle = Handle::AwaitingPasswordForPasswd { uid };
|
|
||||||
return Err(Error::new(EPERM));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Handle::AwaitingNewPassword { uid } => {
|
|
||||||
let mut users = AllUsers::authenticator(Config::default().writeable(true))
|
|
||||||
.map_err(|_| Error::new(ENOLCK))?;
|
|
||||||
let user = users
|
|
||||||
.get_mut_by_id(uid as usize)
|
|
||||||
.ok_or(Error::new(EEXIST))?;
|
|
||||||
|
|
||||||
let new_password = validate_utf8(buf)?;
|
|
||||||
if user.set_passwd(&new_password).is_ok() {
|
|
||||||
users.save().map_err(|_| Error::new(ENOLCK))?;
|
|
||||||
*handle = Handle::Placeholder
|
|
||||||
} else {
|
|
||||||
*handle = Handle::AwaitingNewPassword { uid };
|
|
||||||
return Err(Error::new(EPERM));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle::AwaitingNamespaceFetch { .. } => {
|
|
||||||
eprintln!("sudo: found namespace fetch handle with ID {id}");
|
|
||||||
return Err(Error::new(EBADFD));
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle::Placeholder => {
|
|
||||||
eprintln!("sudo: found placeholder handle with ID {id}");
|
|
||||||
return Err(Error::new(EBADFD));
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle::SchemeRoot => {
|
|
||||||
eprintln!("sudo: found Scheme root handle with ID {id}");
|
|
||||||
return Err(Error::new(EBADFD));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Scheme {
|
|
||||||
fn on_close(&mut self, id: usize) {
|
|
||||||
self.handles.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_sendfd(&mut self, socket: &Socket, req: &SendFdRequest) -> Result<usize> {
|
|
||||||
let handle = self.handles.get_mut(&req.id()).ok_or(Error::new(EBADF))?;
|
|
||||||
match std::mem::replace(handle, Handle::Placeholder) {
|
|
||||||
Handle::AwaitingContextFd => {
|
|
||||||
let mut proc_fd = usize::MAX;
|
|
||||||
req.obtain_fd(
|
|
||||||
socket,
|
|
||||||
FobtainFdFlags::empty(),
|
|
||||||
std::slice::from_mut(&mut proc_fd),
|
|
||||||
)?;
|
|
||||||
let proc_fd = unsafe { OwnedFd::from_raw_fd(proc_fd as RawFd) };
|
|
||||||
|
|
||||||
let [ruid, euid, suid] = [0, 0, 0];
|
|
||||||
let [rgid, egid, sgid] = [0, 0, 0];
|
|
||||||
let mut payload = [0; size_of::<u32>() * 6];
|
|
||||||
plain::slice_from_mut_bytes(&mut payload)
|
|
||||||
.unwrap()
|
|
||||||
.copy_from_slice(&[ruid, euid, suid, rgid, egid, sgid]);
|
|
||||||
|
|
||||||
if let Err(err) = proc_call(
|
|
||||||
proc_fd.as_raw_fd() as usize,
|
|
||||||
&mut payload,
|
|
||||||
CallFlags::empty(),
|
|
||||||
&[ProcCall::SetResugid as u64],
|
|
||||||
) {
|
|
||||||
eprintln!("failed to setresugid: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
*handle = Handle::AwaitingNamespaceFetch {
|
|
||||||
ns: libredox::Fd::new(
|
|
||||||
syscall::dup(libredox::call::getns().unwrap(), b"").unwrap(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
old => {
|
|
||||||
*handle = old;
|
|
||||||
return Err(Error::new(EBADF));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn daemon_main() -> ! {
|
|
||||||
// TODO: Linux kernel audit-like logging?
|
|
||||||
let socket = Socket::create().expect("failed to open scheme socket");
|
|
||||||
|
|
||||||
let mut scheme = Scheme {
|
|
||||||
next_fd: 1,
|
|
||||||
handles: HashMap::new(),
|
|
||||||
};
|
|
||||||
let mut scheme_state = redox_scheme::scheme::SchemeState::new();
|
|
||||||
|
|
||||||
register_sync_scheme(&socket, "sudo", &mut scheme)
|
|
||||||
.expect("failed to register sudo scheme to namespace");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let Some(req) = socket
|
|
||||||
.next_request(SignalBehavior::Restart)
|
|
||||||
.expect("failed to get request")
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = match req.kind() {
|
|
||||||
RequestKind::Call(call) => call.handle_sync(&mut scheme, &mut scheme_state),
|
|
||||||
RequestKind::SendFd(req) => Response::new(scheme.on_sendfd(&socket, &req), req),
|
|
||||||
RequestKind::OnClose { id } => {
|
|
||||||
scheme.on_close(id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
socket
|
|
||||||
.write_response(response, SignalBehavior::Restart)
|
|
||||||
.expect("sudo: scheme write failed");
|
|
||||||
}
|
|
||||||
std::process::exit(0)
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllGroups, AllUsers, Config, GroupBuilder, UserBuilder};
|
|
||||||
use userutils::create_user_dir;
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{useradd} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
useradd - add a new user
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
useradd [ options ] LOGIN
|
|
||||||
useradd [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The useradd utility creates a new user based on
|
|
||||||
system defaults and values passed on the command line.
|
|
||||||
|
|
||||||
Useradd creates a new group for the user by default and
|
|
||||||
can also be instructed to create the user's home directory.
|
|
||||||
|
|
||||||
Note that useradd creates a new user with the password
|
|
||||||
unset (no login). This is better documented with the
|
|
||||||
redox_users crate.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-h, --help
|
|
||||||
Display this help and exit.
|
|
||||||
|
|
||||||
-c, --comment
|
|
||||||
Any text string, usually used as the user's full name.
|
|
||||||
Historically known as the GECOS field
|
|
||||||
|
|
||||||
-d, --home-dir HOME_DIR
|
|
||||||
The new user will be created with HOME_DIR as their home
|
|
||||||
directory. The default value is LOGIN prepended with "/home/".
|
|
||||||
This flag DOES NOT create the home directory. See --create-home
|
|
||||||
|
|
||||||
-g, --gid GID
|
|
||||||
The group id to use when creating the default login group. This value
|
|
||||||
must not be in use and must be non-negative. The default is to pick the
|
|
||||||
smallest available group id between values defined in redox_users.
|
|
||||||
|
|
||||||
-m, --create-home
|
|
||||||
Creates the user's home directory if it does not already exist.
|
|
||||||
|
|
||||||
This option is not enabled by default. This option must be specified
|
|
||||||
for a home directory to be created. If not set, the user's home dir is
|
|
||||||
set to "/"
|
|
||||||
|
|
||||||
-N, --no-user-group
|
|
||||||
Do not attempt to create the user's user group. Instead, the groupid
|
|
||||||
is set to 99 ("nobody"). -N and -g are mutually exclusive.
|
|
||||||
|
|
||||||
-s, --shell SHELL
|
|
||||||
The path to the user's default login shell. If not specified, the
|
|
||||||
default shell is set as "/bin/ion"
|
|
||||||
|
|
||||||
-u, --uid UID
|
|
||||||
The user id to use. This value must not be in use and must be
|
|
||||||
non-negative. The default is to pick the smallest available
|
|
||||||
user id between the defaults defined in redox_users
|
|
||||||
|
|
||||||
AUTHORS
|
|
||||||
Written by Wesley Hershberger.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
const DEFAULT_SHELL: &'static str = "/bin/ion";
|
|
||||||
const DEFAULT_HOME: &'static str = "/home";
|
|
||||||
const DEFAULT_NO_GROUP: &'static str = "nobody";
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = clap_app!(useradd =>
|
|
||||||
(author: "Wesley Hershberger")
|
|
||||||
(about: "Add users based on the system's redox_users backend")
|
|
||||||
(@arg LOGIN:
|
|
||||||
+required
|
|
||||||
"Add user LOGIN")
|
|
||||||
(@arg COMMENT:
|
|
||||||
-c --comment
|
|
||||||
+takes_value
|
|
||||||
"Set user description (GECOS field)")
|
|
||||||
(@arg HOME_DIR:
|
|
||||||
-d --("home-dir")
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's home dir to HOME_DIR (does not create directory)")
|
|
||||||
(@arg CREATE_HOME:
|
|
||||||
-m --("create-home")
|
|
||||||
"Create the user's home directory")
|
|
||||||
(@arg SHELL:
|
|
||||||
-s --shell
|
|
||||||
+takes_value
|
|
||||||
"Set user's default login shell")
|
|
||||||
(@arg GID:
|
|
||||||
-g --gid
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's primary group id. Positive integer and must not be in use.")
|
|
||||||
(@arg NO_USER_GROUP:
|
|
||||||
-N --("no-user-group")
|
|
||||||
conflicts_with[GID]
|
|
||||||
"Do not create primary user group (set gid to 99, \"nobody\")")
|
|
||||||
(@arg UID:
|
|
||||||
-u --uid
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's user id. Positive ineger and must not be in use.")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
// unwrap is safe because of "+required". clap-rs is cool...
|
|
||||||
let login = args.value_of("LOGIN").unwrap();
|
|
||||||
|
|
||||||
let mut sys_users =
|
|
||||||
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
|
|
||||||
let uid = match args.value_of("UID") {
|
|
||||||
Some(uid) => {
|
|
||||||
let id = uid.parse::<usize>().unwrap_or_exit(1);
|
|
||||||
if let Some(_user) = sys_users.get_by_id(id) {
|
|
||||||
eprintln!("useradd: userid already in use: {}", id);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
None => sys_users.get_unique_id().unwrap_or_else(|| {
|
|
||||||
eprintln!("useradd: no available uid");
|
|
||||||
exit(1);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let gid = if args.is_present("NO_USER_GROUP") {
|
|
||||||
let nobody = sys_groups
|
|
||||||
.get_mut_by_name(DEFAULT_NO_GROUP)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
eprintln!("useradd: group not found: {}", DEFAULT_NO_GROUP);
|
|
||||||
exit(1)
|
|
||||||
});
|
|
||||||
nobody.users.push(login.to_string());
|
|
||||||
99
|
|
||||||
} else {
|
|
||||||
let id = match args.value_of("GID") {
|
|
||||||
Some(id) => {
|
|
||||||
let id = id.parse::<usize>().unwrap_or_exit(1);
|
|
||||||
if let Some(_group) = sys_groups.get_by_id(id) {
|
|
||||||
eprintln!("useradd: group already exists with gid: {}", id);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
None => sys_groups.get_unique_id().unwrap_or_else(|| {
|
|
||||||
eprintln!("useradd: no available gid");
|
|
||||||
exit(1);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
sys_groups
|
|
||||||
.add_group(GroupBuilder::new(login).gid(id).user(login))
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
eprintln!("useradd: {}: {}", err, login);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
id
|
|
||||||
};
|
|
||||||
|
|
||||||
let gecos = args.value_of("COMMENT").unwrap_or(login);
|
|
||||||
|
|
||||||
//Ugly way to satisfy the borrow checker...
|
|
||||||
let mut sys_homes = String::from(DEFAULT_HOME);
|
|
||||||
let userhome = args.value_of("HOME_DIR").unwrap_or_else(|| {
|
|
||||||
if args.is_present("CREATE_HOME") {
|
|
||||||
sys_homes.push_str("/");
|
|
||||||
sys_homes.push_str(&login);
|
|
||||||
sys_homes.as_str()
|
|
||||||
} else {
|
|
||||||
"/"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let shell = args.value_of("SHELL").unwrap_or(DEFAULT_SHELL);
|
|
||||||
|
|
||||||
let user = UserBuilder::new(login)
|
|
||||||
.uid(uid)
|
|
||||||
.gid(gid)
|
|
||||||
.name(gecos)
|
|
||||||
.home(userhome)
|
|
||||||
.shell(shell);
|
|
||||||
sys_users.add_user(user).unwrap_or_else(|err| {
|
|
||||||
eprintln!("useradd: {}: {}", err, login);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure to try and create the user/groups before we create
|
|
||||||
// their home, that way we get a permissions error that makes
|
|
||||||
// more sense
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
sys_users.save().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
if args.is_present("CREATE_HOME") {
|
|
||||||
//Shouldn't ever error...
|
|
||||||
let user = sys_users.get_by_id(uid).unwrap_or_exit(1);
|
|
||||||
create_user_dir(user, userhome).unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::fs::remove_dir;
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllGroups, AllUsers, Config};
|
|
||||||
use userutils::AllGroupsExt;
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{userdel} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
userdel - modify system files to delete users
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
userdel [ options ] LOGIN
|
|
||||||
userdel [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
userdel removes users from whatever backend is employed by
|
|
||||||
the system's redox_users. The utility removes the user from
|
|
||||||
all groups of which they are a member.
|
|
||||||
|
|
||||||
It can also be used to manage removal of home directories.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-h, --help
|
|
||||||
Print this help page and exit.
|
|
||||||
|
|
||||||
-r, --remove
|
|
||||||
The user's home directory and all files inside will be
|
|
||||||
removed.
|
|
||||||
|
|
||||||
AUTHORS
|
|
||||||
Wesley Hershberger.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = clap_app!(userdel =>
|
|
||||||
(author: "Wesley Hershberger")
|
|
||||||
(about: "Removes system users using redox_users")
|
|
||||||
(@arg LOGIN: +required "Remove user LOGIN")
|
|
||||||
(@arg REMOVE: -r --remove "Remove the user's home and all files and directories inside")
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let login = args.value_of("LOGIN").unwrap();
|
|
||||||
|
|
||||||
let mut sys_users =
|
|
||||||
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
{
|
|
||||||
sys_groups.remove_user_from_all_groups(login);
|
|
||||||
|
|
||||||
if args.is_present("REMOVE") {
|
|
||||||
let user = sys_users.get_by_name(login).unwrap_or_else(|| {
|
|
||||||
eprintln!("userdel: user does not exist: {}", login);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
remove_dir(&user.home).unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_users.remove_by_name(login.to_string());
|
|
||||||
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
sys_users.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::fs::{remove_dir, rename};
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use extra::option::OptionalExt;
|
|
||||||
use redox_users::{All, AllGroups, AllUsers, Config};
|
|
||||||
use userutils::{AllGroupsExt, create_user_dir};
|
|
||||||
|
|
||||||
const _MAN_PAGE: &'static str = /* @MANSTART{usermod} */
|
|
||||||
r#"
|
|
||||||
NAME
|
|
||||||
usermod - modify user information
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
usermod [ options ] LOGIN
|
|
||||||
usermod [ -h | --help ]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
The usermod utility can be used to modify user information.
|
|
||||||
|
|
||||||
This utility uses the redox_users API, so the backend is whatever
|
|
||||||
backend in use on the system for that API at the time.
|
|
||||||
|
|
||||||
See passwd for setting user passwords.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
-h, --help
|
|
||||||
Display this help and exit.
|
|
||||||
|
|
||||||
-c, --comment COMMENT
|
|
||||||
The comment field (or GECOS, historically) for the user. This
|
|
||||||
is typically the full name of the user, although sometimes it
|
|
||||||
includes an e-mail.
|
|
||||||
|
|
||||||
-d, --home-dir HOME_DIR
|
|
||||||
Sets the home directory to HOME_DIR and creates the directory.
|
|
||||||
See -m for move
|
|
||||||
|
|
||||||
-m, --move-home
|
|
||||||
Moves the the user's old home directory into the home directory
|
|
||||||
specified by --home-dir. Has no effect if passed without --home-dir
|
|
||||||
|
|
||||||
-G, --append-groups GROUP[,GROUP, ...]
|
|
||||||
Add this user to GROUP groups. This does not remove the user from
|
|
||||||
any group of which they are already a member.
|
|
||||||
|
|
||||||
-S, --set-groups GROUP[,GROUP, ...]
|
|
||||||
Remove the user from all groups of which they are a part and add
|
|
||||||
them to GROUP groups.
|
|
||||||
|
|
||||||
-g, --gid GID
|
|
||||||
Set the user's primary group id. If the group does not exist,
|
|
||||||
a warning is issued and no changes are applied.
|
|
||||||
|
|
||||||
-l, --login NEW_LOGIN
|
|
||||||
Set the new login name for the user. Must not be in use.
|
|
||||||
|
|
||||||
-s, --shell SHELL
|
|
||||||
Set the user's login shell as SHELL. This must be a full path.
|
|
||||||
|
|
||||||
-u, --uid UID
|
|
||||||
Set the user's user id. If another user's userid is the same as
|
|
||||||
UID, a warning is issued and no changes are applied. Note that
|
|
||||||
changing the value of the user's userid may have unexpected consequences.
|
|
||||||
|
|
||||||
AUTHORS
|
|
||||||
Written by Wesley Hershberger.
|
|
||||||
"#; /* @MANEND */
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = clap_app!(usermod =>
|
|
||||||
(author: "Wesley Hershberger")
|
|
||||||
(about: "Modify users according to the system's redox_users backend")
|
|
||||||
(@arg LOGIN:
|
|
||||||
+required
|
|
||||||
"Apply modifications to LOGIN")
|
|
||||||
(@arg COMMENT:
|
|
||||||
-c --comment
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's description (GECOS field)")
|
|
||||||
(@arg HOME_DIR:
|
|
||||||
-d --("home-dir")
|
|
||||||
+takes_value
|
|
||||||
"Create and set LOGIN's home directory")
|
|
||||||
(@arg MOVE_HOME:
|
|
||||||
-m --("move-home")
|
|
||||||
requires[HOME_DIR]
|
|
||||||
"Move LOGIN's old home to HOME_DIR (see --home-dir) instead of creating it. Requires -d")
|
|
||||||
(@arg APPEND_GROUPS:
|
|
||||||
-G --("append-groups")
|
|
||||||
+takes_value conflicts_with[SET_GROUPS]
|
|
||||||
"Add user to groups specified (comma separated list, see man page)")
|
|
||||||
(@arg SET_GROUPS:
|
|
||||||
-S --("set-groups")
|
|
||||||
+takes_value conflicts_with[APPEND_GROUPS]
|
|
||||||
"Set LOGIN's groups as specified (truncates existing, see man page)")
|
|
||||||
(@arg GID:
|
|
||||||
-g --gid
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's primary group id. Group must exist")
|
|
||||||
(@arg NEW_LOGIN:
|
|
||||||
-l --login
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's name to NEW_LOGIN")
|
|
||||||
(@arg SHELL:
|
|
||||||
-s --shell
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's default login shell")
|
|
||||||
(@arg UID:
|
|
||||||
-u --uid
|
|
||||||
+takes_value
|
|
||||||
"Set LOGIN's user id. See man page for details")
|
|
||||||
).get_matches();
|
|
||||||
|
|
||||||
let login = args.value_of("LOGIN").unwrap();
|
|
||||||
|
|
||||||
//TODO: Does not always need shadowfile access
|
|
||||||
let mut sys_users =
|
|
||||||
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
let mut sys_groups;
|
|
||||||
|
|
||||||
if let Some(new_groups) = args.value_of("SET_GROUPS") {
|
|
||||||
sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
sys_groups.remove_user_from_all_groups(login);
|
|
||||||
sys_groups
|
|
||||||
.add_user_to_groups(login, new_groups.split(',').collect())
|
|
||||||
.unwrap_or_exit(1);
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(new_groups) = args.value_of("APPEND_GROUPS") {
|
|
||||||
sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
|
|
||||||
sys_groups
|
|
||||||
.add_user_to_groups(login, new_groups.split(',').collect())
|
|
||||||
.unwrap_or_exit(1);
|
|
||||||
sys_groups.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let uid = args.value_of("UID").map(|uid| {
|
|
||||||
let uid = uid.parse::<usize>().unwrap_or_exit(1);
|
|
||||||
if let Some(_user) = sys_users.get_by_id(uid) {
|
|
||||||
eprintln!("usermod: userid already in use: {}", uid);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
uid
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
let user = sys_users.get_mut_by_name(&login).unwrap_or_else(|| {
|
|
||||||
eprintln!("usermod: user \"{}\" not found", login);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(gecos) = args.value_of("COMMENT") {
|
|
||||||
user.name = gecos.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(new_login) = args.value_of("NEW_LOGIN") {
|
|
||||||
user.user = new_login.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shell) = args.value_of("SHELL") {
|
|
||||||
user.shell = shell.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(home) = args.value_of("HOME_DIR") {
|
|
||||||
if args.is_present("MOVE_HOME") {
|
|
||||||
rename(&user.home, &home).unwrap_or_exit(1);
|
|
||||||
} else {
|
|
||||||
create_user_dir(user, &home).unwrap_or_exit(1);
|
|
||||||
remove_dir(&user.home).unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
user.home = home.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(uid) = uid {
|
|
||||||
user.uid = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(gid) = args.value_of("GID") {
|
|
||||||
sys_groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
|
|
||||||
let gid = gid.parse::<usize>().unwrap_or_exit(1);
|
|
||||||
|
|
||||||
if let Some(_group) = sys_groups.get_by_id(gid) {
|
|
||||||
user.gid = gid;
|
|
||||||
} else {
|
|
||||||
eprintln!("usermod: no group found for id: {}", gid);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_users.save().unwrap_or_exit(1);
|
|
||||||
}
|
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
+12
-90
@@ -1,92 +1,14 @@
|
|||||||
//! Redox OS user and group utilities.
|
#[macro_use]
|
||||||
//!
|
extern crate serde_derive;
|
||||||
//! The `userutils` crate contains the utilities for dealing with users and groups in Redox OS.
|
|
||||||
//! They are heavily influenced by UNIX and are, when needed, tailored to specific Redox use cases.
|
|
||||||
//!
|
|
||||||
//! These implementations strive to be as simple as possible drawing particular
|
|
||||||
//! inspiration by BSD systems. They are indeed small, by choice.
|
|
||||||
//!
|
|
||||||
//! The included utilities are:
|
|
||||||
//!
|
|
||||||
//! - `getty`: Used by `init(8)` to open and initialize the TTY line, read a login name and invoke `login(1)`.
|
|
||||||
//! - `id`: Displays user identity.
|
|
||||||
//! - `login`: Allows users to into the system.
|
|
||||||
//! - `passwd`: Allows users to modify their passwords.
|
|
||||||
//! - `su`: Allows users to substitute identity.
|
|
||||||
//! - `sudo`: Enables users to execute a command as another user.
|
|
||||||
//! - `whoami`: Display effective user ID.
|
|
||||||
|
|
||||||
use std::io::Result as IoResult;
|
mod config;
|
||||||
|
#[cfg(feature = "installer")]
|
||||||
|
mod disk_wrapper;
|
||||||
|
#[cfg(feature = "installer")]
|
||||||
|
mod installer;
|
||||||
|
#[cfg(feature = "installer")]
|
||||||
|
pub use crate::installer::*;
|
||||||
|
|
||||||
use libredox::call::{fchown, open};
|
pub use crate::config::file::FileConfig;
|
||||||
use libredox::error::Result as SysResult;
|
pub use crate::config::package::PackageConfig;
|
||||||
use libredox::flag::{O_CLOEXEC, O_CREAT, O_DIRECTORY};
|
pub use crate::config::Config;
|
||||||
use redox_users::{All, AllGroups, Error, Result, User, auth};
|
|
||||||
|
|
||||||
const DEFAULT_MODE: u16 = 0o700;
|
|
||||||
|
|
||||||
// Not the prettiest thing in the world, but some functionality here makes
|
|
||||||
// some of the utils much less gross
|
|
||||||
pub trait AllGroupsExt {
|
|
||||||
fn add_user_to_groups(&mut self, login: &str, groups: Vec<&str>) -> Result<()>;
|
|
||||||
fn remove_user_from_all_groups(&mut self, login: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AllGroupsExt for AllGroups {
|
|
||||||
// new_groups is a comma separated list of groupnames
|
|
||||||
fn add_user_to_groups(&mut self, login: &str, new_groups: Vec<&str>) -> Result<()> {
|
|
||||||
for groupname in new_groups {
|
|
||||||
let group = match self.get_mut_by_name(groupname) {
|
|
||||||
Some(group) => group,
|
|
||||||
None => return Err(Error::UserNotFound),
|
|
||||||
};
|
|
||||||
group.users.push(login.to_string());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a user from all groups of which they are a member
|
|
||||||
fn remove_user_from_all_groups(&mut self, login: &str) {
|
|
||||||
for group in self.iter_mut() {
|
|
||||||
let op_pos = group.users.iter().position(|username| username == login);
|
|
||||||
if let Some(indx) = op_pos {
|
|
||||||
group.users.remove(indx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawns a shell for the given `User`.
|
|
||||||
///
|
|
||||||
/// This function wraps the shell_cmd function of the User struct
|
|
||||||
/// from redox_users and manages the child process. It is a blocking
|
|
||||||
/// operation.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use redox_users::AllUsers;
|
|
||||||
///
|
|
||||||
/// let sys_users = AllUsers::new().unwrap();
|
|
||||||
/// let user = sys_users.get_by_name("goyox86");
|
|
||||||
/// spawn_shell(user).unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn spawn_shell<T: Default>(user: &User<T>) -> IoResult<i32> {
|
|
||||||
let mut command = user.shell_cmd();
|
|
||||||
|
|
||||||
let mut child = command.spawn()?;
|
|
||||||
match child.wait()?.code() {
|
|
||||||
Some(code) => Ok(code),
|
|
||||||
None => Ok(1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a directory with 700 user:user permissions
|
|
||||||
pub fn create_user_dir<T>(user: &User<auth::Full>, dir: T) -> SysResult<()>
|
|
||||||
where
|
|
||||||
T: AsRef<str> + std::convert::AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
let fd = open(dir, O_CREAT | O_DIRECTORY | O_CLOEXEC, DEFAULT_MODE)?;
|
|
||||||
fchown(fd, user.uid as u32, user.gid as u32)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user