From 163ac1668d8fb2850fefb1554665062d8b3db975 Mon Sep 17 00:00:00 2001 From: syui Date: Sat, 5 Jul 2025 11:34:41 +0900 Subject: [PATCH] cargo build --- .cargo/config.toml | 8 + .dockerignore | 6 + .gitignore | 5 + Bootloader.toml | 12 ++ Cargo.toml | 30 +++ README.md | 12 ++ cfg/ai.conf | 6 + cfg/gh-actions.yml | 72 +++++++ cfg/hostname | 1 + cfg/os-release | 13 ++ cfg/profiledef.sh | 27 +++ claude.md | 50 +++++ docs/ARCHITECTURE.md | 136 ++++++++++++ docs/QUICKSTART.md | 68 ++++++ docs/README.md | 147 +++++++++++++ src/allocator.rs | 44 ++++ src/filesystem.rs | 289 +++++++++++++++++++++++++ src/interrupts.rs | 88 ++++++++ src/main.rs | 100 +++++++++ src/memory.rs | 76 +++++++ src/package.rs | 413 ++++++++++++++++++++++++++++++++++++ src/process.rs | 203 ++++++++++++++++++ src/serial.rs | 32 +++ src/shell.rs | 494 +++++++++++++++++++++++++++++++++++++++++++ src/systemd.rs | 180 ++++++++++++++++ src/vga_buffer.rs | 164 ++++++++++++++ 26 files changed, 2676 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Bootloader.toml create mode 100644 Cargo.toml create mode 100644 cfg/ai.conf create mode 100644 cfg/gh-actions.yml create mode 100644 cfg/hostname create mode 100644 cfg/os-release create mode 100644 cfg/profiledef.sh create mode 100644 claude.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/QUICKSTART.md create mode 100644 docs/README.md create mode 100644 src/allocator.rs create mode 100644 src/filesystem.rs create mode 100644 src/interrupts.rs create mode 100644 src/main.rs create mode 100644 src/memory.rs create mode 100644 src/package.rs create mode 100644 src/process.rs create mode 100644 src/serial.rs create mode 100644 src/shell.rs create mode 100644 src/systemd.rs create mode 100644 src/vga_buffer.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..b64fa9f --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,8 @@ +[unstable] +build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins", "alloc"] + +[build] +target = "x86_64-unknown-none" + +# rustflags = ["-C", "link-arg=-T./linker.ld"] \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..77be39e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +target/ +.git/ +*.log +.DS_Store +Dockerfile +.dockerignore \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8b3dcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +octox +target +.claude +*.log +*.lock diff --git a/Bootloader.toml b/Bootloader.toml new file mode 100644 index 0000000..39ad0c9 --- /dev/null +++ b/Bootloader.toml @@ -0,0 +1,12 @@ +# Bootloader configuration +[bootloader] +# Map physical memory for accessing boot info +map_physical_memory = true + +# Frame buffer settings +[bootloader.framebuffer] +# Enable framebuffer mode (VGA compatible) +enable = true +width = 80 +height = 25 +format = "text" \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f894710 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "aios" +version = "0.1.0" +edition = "2021" + +[dependencies] +bootloader = "0.9.23" +volatile = "0.2" +spin = "0.9" +x86_64 = "0.14" +uart_16550 = "0.2" +pic8259 = "0.10" +pc-keyboard = "0.7" + +[dependencies.lazy_static] +version = "1.4" +features = ["spin_no_std"] + +[[bin]] +name = "kernel" +path = "src/main.rs" + +[target.'cfg(target_os = "none")'.dependencies] +linked_list_allocator = "0.10" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" \ No newline at end of file diff --git a/README.md b/README.md index e69de29..629a5ff 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,12 @@ +# aios + +create a unix-like os from scratch + +## example + +```sh +# archlinux +cargo bootimage +qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -nographic +``` + diff --git a/cfg/ai.conf b/cfg/ai.conf new file mode 100644 index 0000000..176adb5 --- /dev/null +++ b/cfg/ai.conf @@ -0,0 +1,6 @@ +# https://github.com/containers/shortnames +# ~/.config/containers/registries.conf.d/ai.conf +unqualified-search-registries = ['git.syui.ai', 'docker.io', 'ghcr.io'] + +[aliases] +"aios" = "git.syui.ai/ai/os" diff --git a/cfg/gh-actions.yml b/cfg/gh-actions.yml new file mode 100644 index 0000000..836e940 --- /dev/null +++ b/cfg/gh-actions.yml @@ -0,0 +1,72 @@ +name: release + +on: + push: + branches: + - main + +permissions: + contents: write + +env: + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + IMAGE_NAME: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.APP_TOKEN }} + REGISTRY: ghcr.io + +jobs: + release: + name: Release + runs-on: ubuntu-latest + container: + image: archlinux + options: --privileged + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Initialize + run: | + pacman -Syuu --noconfirm base-devel archiso docker git nodejs bc + git clone https://gitlab.archlinux.org/archlinux/archiso + cp -rf ./cfg/profiledef.sh /usr/share/archiso/configs/releng/ + cp -rf ./cfg/profiledef.sh ./archiso/configs/releng/profiledef.sh + cp -rf ./cfg/profiledef.sh ./archiso/configs/baseline/profiledef.sh + cp -rf ./scpt/mkarchiso ./archiso/archiso/mkarchiso + ./archiso/archiso/mkarchiso -v -o ./ ./archiso/configs/releng/ + tar xf aios-bootstrap*.tar.gz + mkdir -p root.x86_64/var/lib/machines/arch + pacstrap -c root.x86_64/var/lib/machines/arch base + echo -e 'Server = http://mirrors.cat.net/archlinux/$repo/os/$arch + Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' >> ./root.x86_64/etc/pacman.d/mirrorlist + sed -i s/CheckSpace/#CheckeSpace/ root.x86_64/etc/pacman.conf + arch-chroot root.x86_64 /bin/sh -c 'pacman-key --init' + arch-chroot root.x86_64 /bin/sh -c 'pacman-key --populate archlinux' + arch-chroot root.x86_64 /bin/sh -c 'pacman -Syu --noconfirm base base-devel linux vim git zsh rust openssh openssl jq go nodejs docker podman bc' + arch-chroot root.x86_64 /bin/sh -c 'mkdir -p /etc/containers/registries.conf.d' + arch-chroot root.x86_64 /bin/sh -c 'curl -sL -o /etc/containers/registries.conf.d/ai.conf https://git.syui.ai/ai/os/raw/branch/main/cfg/ai.conf' + arch-chroot root.x86_64 /bin/sh -c 'chsh -s /bin/zsh' + arch-chroot root.x86_64 /bin/sh -c 'git clone https://git.syui.ai/ai/bot && cd bot && cargo build && cp -rf ./target/debug/ai /bin/ && ai ai' + tar -zcvf aios-bootstrap.tar.gz root.x86_64/ + tar -C ./root.x86_64 -c . | docker import - ${{ env.IMAGE_NAME }} + echo "${{ env.DOCKER_TOKEN }}" | docker login -u syui --password-stdin + docker push ${{ env.IMAGE_NAME }} + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ env.GITHUB_TOKEN }} + - name: github container registry + run: | + docker tag ${{ env.IMAGE_NAME }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Create new release + uses: softprops/action-gh-release@v1 + with: + name: latest + tag_name: latest + files: + aios-bootstrap.tar.gz + diff --git a/cfg/hostname b/cfg/hostname new file mode 100644 index 0000000..5f98dbb --- /dev/null +++ b/cfg/hostname @@ -0,0 +1 @@ +aios diff --git a/cfg/os-release b/cfg/os-release new file mode 100644 index 0000000..dd2a110 --- /dev/null +++ b/cfg/os-release @@ -0,0 +1,13 @@ +BUILD_ID=rolling +ANSI_COLOR="38;2;23;147;209" +IMAGE_ID=aios +IMAGE_VERSION=2024.02.11 +NAME=ai os +PRETTY_NAME=ai os +ID=ai +HOME_URL=https://git.syui.ai/ai/os +DOCUMENTATION_URL=https://git.syui.ai/ai/os/wiki +SUPPORT_URL=https://git.syui.ai/ai/os/issues +BUG_REPORT_URL=https://git.syui.ai/ai/os/issues +PRIVACY_POLICY_URL=https://git.syui.ai/ai/os +LOGO=ai-logo diff --git a/cfg/profiledef.sh b/cfg/profiledef.sh new file mode 100644 index 0000000..146fddb --- /dev/null +++ b/cfg/profiledef.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +iso_name="aios" +iso_label="AI_$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y%m)" +iso_publisher="ai os " +iso_application="ai os Live/Rescue DVD" +iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)" +install_dir="ai" +#buildmodes=('iso') +buildmodes=('bootstrap') +bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito' + 'uefi-ia32.grub.esp' 'uefi-x64.grub.esp' + 'uefi-ia32.grub.eltorito' 'uefi-x64.grub.eltorito') +arch="x86_64" +pacman_conf="pacman.conf" +airootfs_image_type="squashfs" +airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M') +file_permissions=( + ["/etc/shadow"]="0:0:400" + ["/root"]="0:0:750" + ["/root/.automated_script.sh"]="0:0:755" + ["/root/.gnupg"]="0:0:700" + ["/usr/local/bin/choose-mirror"]="0:0:755" + ["/usr/local/bin/Installation_guide"]="0:0:755" + ["/usr/local/bin/livecd-sound"]="0:0:755" +) diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..95d2c88 --- /dev/null +++ b/claude.md @@ -0,0 +1,50 @@ +# osを作ろう + +rust + os + unix + +## archlinux + +archlinuxのpacmanを組み込むことはできるだろうか。 + +archlinux-baseのosを作る方法を載せておく。これをarchlinux上で実行すると、`aios-bootstrap.tar.gz`ができ、それがlinux-imgになる。 + +```sh +#!/bin/zsh +pacman -Syuu --noconfirm base-devel archiso docker git nodejs bc +git clone https://gitlab.archlinux.org/archlinux/archiso +cp -rf ./cfg/profiledef.sh /usr/share/archiso/configs/releng/ +cp -rf ./cfg/profiledef.sh ./archiso/configs/releng/profiledef.sh +cp -rf ./cfg/profiledef.sh ./archiso/configs/baseline/profiledef.sh +cp -rf ./scpt/mkarchiso ./archiso/archiso/mkarchiso +./archiso/archiso/mkarchiso -v -o ./ ./archiso/configs/releng/ +tar xf aios-bootstrap*.tar.gz +mkdir -p root.x86_64/var/lib/machines/arch +pacstrap -c root.x86_64/var/lib/machines/arch base +echo -e 'Server = http://mirrors.cat.net/archlinux/$repo/os/$arch +Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' >> ./root.x86_64/etc/pacman.d/mirrorlist +sed -i s/CheckSpace/#CheckeSpace/ root.x86_64/etc/pacman.conf +arch-chroot root.x86_64 /bin/sh -c 'pacman-key --init' +arch-chroot root.x86_64 /bin/sh -c 'pacman-key --populate archlinux' +arch-chroot root.x86_64 /bin/sh -c 'pacman -Syu --noconfirm base base-devel linux vim git zsh rust openssh openssl jq go nodejs docker podman bc' +arch-chroot root.x86_64 /bin/sh -c 'mkdir -p /etc/containers/registries.conf.d' +arch-chroot root.x86_64 /bin/sh -c 'curl -sL -o /etc/containers/registries.conf.d/ai.conf https://git.syui.ai/ai/os/raw/branch/main/cfg/ai.conf' +arch-chroot root.x86_64 /bin/sh -c 'chsh -s /bin/zsh' +arch-chroot root.x86_64 /bin/sh -c 'git clone https://git.syui.ai/ai/bot && cd bot && cargo build && cp -rf ./target/debug/ai /bin/ && ai ai' +tar -zcvf aios-bootstrap.tar.gz root.x86_64/ +``` + +## 当面の目標 + +1. claude codeを実行できるようにすること +2. package managerの作成 +3. shellとAIの統合 +4. shellはsystemd-nspawnのようなcontainerで実行 +5. AIはcontainerを扱う + +## ref + +[o8vm/octox](https://github.com/o8vm/octox)が参考になるかもしれない。 + +```sh +git clone https://github.com/o8vm/octox +``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a9ab2c3 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,136 @@ +# AIOS アーキテクチャ + +## 概要 + +AIOSは、Rustで実装されたマイクロカーネルアーキテクチャを採用しています。最終的にClaude Codeを実行可能にするため、モジュラー設計を重視しています。 + +## システム構成 + +``` +┌─────────────────────────────────────────┐ +│ ユーザーランド │ +│ ┌─────────────┐ ┌─────────────────────┐│ +│ │ Claude Code │ │ システムツール ││ +│ │ │ │ (ls, cat, etc.) ││ +│ └─────────────┘ └─────────────────────┘│ +├─────────────────────────────────────────┤ +│ システムコール │ +├─────────────────────────────────────────┤ +│ カーネル空間 │ +│ ┌─────────┐ ┌─────────┐ ┌─────────────┐│ +│ │ プロセス │ │ メモリ │ │ ファイル ││ +│ │ 管理 │ │ 管理 │ │ システム ││ +│ └─────────┘ └─────────┘ └─────────────┘│ +│ ┌─────────────────────────────────────┐│ +│ │ ハードウェア抽象化 ││ +│ │ VGA・シリアル・キーボード・etc ││ +│ └─────────────────────────────────────┘│ +├─────────────────────────────────────────┤ +│ ハードウェア │ +│ CPU・メモリ・ストレージ │ +└─────────────────────────────────────────┘ +``` + +## 主要コンポーネント + +### 1. カーネルコア (`src/main.rs`) +- システムの起動とメインループ +- パニックハンドラー +- テストフレームワーク統合 + +### 2. VGAドライバー (`src/vga_buffer.rs`) +- テキストモード画面制御 +- 色指定とフォーマット +- スクロール機能 + +### 3. シリアル通信 (`src/serial.rs`) +- UART 16550制御 +- デバッグ出力 +- QEMU連携 + +### 4. メモリ管理 (予定) +- 仮想メモリ管理 +- ヒープアロケーター +- ページテーブル管理 + +### 5. ファイルシステム (予定) +- VFS (Virtual File System) +- 基本的なファイル操作 +- ディレクトリ構造 + +### 6. プロセス管理 (予定) +- タスクスケジューリング +- プロセス作成・終了 +- IPC (Inter-Process Communication) + +## メモリレイアウト + +``` +仮想アドレス空間: +0x0000_0000_0000_0000 - ユーザー空間開始 +0x0000_7fff_ffff_ffff - ユーザー空間終了 +0x8000_0000_0000_0000 - カーネル空間開始 +0xffff_ffff_ffff_ffff - カーネル空間終了 + +物理メモリ: +0x0000_0000 - 0x0009_ffff : 実モード互換領域 +0x0010_0000 - 0x7fff_ffff : 利用可能RAM +0x8000_0000 - 0xffff_ffff : デバイスメモリ +``` + +## ブートプロセス + +1. **BIOS/UEFI** → ブートローダー読み込み +2. **ブートローダー** → カーネルロード +3. **カーネル初期化** → ハードウェア検出 +4. **ドライバー初期化** → VGA・シリアル・etc +5. **メモリ管理開始** → ヒープ・ページング +6. **ファイルシステム** → ルートマウント +7. **プロセス管理** → initプロセス開始 +8. **ユーザーランド** → Claude Code実行 + +## 設計原則 + +### 1. 安全性 +- Rustの所有権システム活用 +- バッファオーバーフローの防止 +- 型安全性の保証 + +### 2. パフォーマンス +- ゼロコスト抽象化 +- コンパイル時最適化 +- 最小限のランタイムオーバーヘッド + +### 3. 拡張性 +- モジュラー設計 +- プラグアーキテクチャ +- 標準インターフェース + +### 4. 移植性 +- ハードウェア抽象化レイヤー +- 設定可能なターゲット +- 条件付きコンパイル + +## 今後の拡張計画 + +### フェーズ1: 基盤システム +- [ ] 動的メモリ管理 +- [ ] 基本的なファイルシステム +- [ ] シンプルなプロセス管理 + +### フェーズ2: 高度な機能 +- [ ] ネットワークスタック +- [ ] デバイスドライバーフレームワーク +- [ ] システムコール拡張 + +### フェーズ3: Claude Code統合 +- [ ] Node.js/Rustランタイム +- [ ] パッケージマネージャー統合 +- [ ] 開発ツール群 + +## 参考アーキテクチャ + +- **xv6**: 教育用Unix系OS +- **Redox**: Rust製マイクロカーネルOS +- **seL4**: 形式検証されたマイクロカーネル +- **Plan 9**: 分散システム指向OS \ No newline at end of file diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md new file mode 100644 index 0000000..31e1687 --- /dev/null +++ b/docs/QUICKSTART.md @@ -0,0 +1,68 @@ +# AIOS クイックスタート + +## 最短で試す方法 + +### 1. 必要なツールのインストール + +**Mac:** +```bash +brew install qemu +cargo install bootimage +``` + +**Arch Linux:** +```bash +pacman -S qemu-full +cargo install bootimage +``` + +### 2. プロジェクトのセットアップ + +```bash +# リポジトリのクローン +git clone +cd aios + +# 一発でビルド&実行 +cargo bootimage && qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio +``` + +### 3. 動作確認 + +正常に動作している場合、以下のような出力が表示されます: + +``` +Hello World! +Welcome to AIOS - A simple OS written in Rust +``` + +QEMUを終了するには `Ctrl+A` を押してから `X` を押してください。 + +## 開発ワークフロー + +### コードの変更 +```bash +# コード変更後 +cargo build + +# 新しいブートイメージの作成 +cargo bootimage + +# 実行 +qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio +``` + +### デバッグ +```bash +# シリアル出力のみ表示 +qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio -nographic + +# メモリ使用量を制限 +qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio -m 128M +``` + +## 次のステップ + +1. `src/main.rs`の`println!`文を変更して、独自のメッセージを表示 +2. `src/vga_buffer.rs`で色を変更 +3. 新しいモジュールを追加して機能を拡張 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..fc0b7ef --- /dev/null +++ b/docs/README.md @@ -0,0 +1,147 @@ +# AIOS - A Rust-based Operating System + +AIOSは、Rustで一から作られたUnix系オペレーティングシステムです。最終的にClaude Codeを実行可能にすることを目標としています。 + +## 特徴 + +- **Pure Rust**: カーネルからユーザーランドまで、全てRustで実装 +- **ベアメタル**: 標準ライブラリを使用せず、ハードウェアを直接制御 +- **VGAテキストモード**: コンソール出力をサポート +- **シリアル通信**: デバッグとQEMU連携用 +- **モジュラー設計**: 拡張可能なアーキテクチャ + +## 必要な環境 + +### Mac (開発環境) +```bash +# Rust nightlyツールチェイン +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +rustup toolchain install nightly +rustup default nightly +rustup component add rust-src llvm-tools-preview +rustup target add x86_64-unknown-none + +# QEMUエミュレーター +brew install qemu + +# bootimageツール +cargo install bootimage +``` + +### Arch Linux (本格運用環境) +```bash +# 基本パッケージ +pacman -Syuu --noconfirm base-devel archiso docker git nodejs bc + +# Rustツールチェイン +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +rustup toolchain install nightly +rustup component add rust-src llvm-tools-preview +rustup target add x86_64-unknown-none + +# QEMUとbootimageツール +pacman -S qemu-full +cargo install bootimage +``` + +## ビルドと実行 + +### 1. プロジェクトのクローン +```bash +git clone +cd aios +``` + +### 2. ビルド +```bash +cargo build +``` + +### 3. ブートイメージの作成 +```bash +cargo bootimage +``` + +### 4. QEMUでの実行 +```bash +qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio +``` + +## プロジェクト構造 + +``` +aios/ +├── src/ +│ ├── main.rs # カーネルエントリーポイント +│ ├── vga_buffer.rs # VGAテキストモードドライバー +│ └── serial.rs # シリアルポート通信 +├── .cargo/ +│ └── config.toml # Cargo設定 +├── Cargo.toml # プロジェクト設定 +├── rust-toolchain.toml # Rustツールチェイン設定 +└── docs/ + └── README.md # このドキュメント +``` + +## 開発状況 + +### 完了済み +- [x] プロジェクト構造の設計と初期化 +- [x] ベアメタル環境でのRustセットアップ +- [x] ブートローダーの実装 +- [x] VGAテキストモードドライバーの実装 +- [x] Hello World出力の実装 +- [x] QEMUでの動作確認 + +### 進行中 +- [ ] メモリ管理システムの実装 +- [ ] ファイルシステムの実装 +- [ ] プロセス管理システムの実装 +- [ ] Claude Code実行環境の構築 + +## 目標 + +1. **Claude Codeを実行できるようにすること** + - Node.js/Rustランタイムの統合 + - ファイルシステムとプロセス管理 + - ネットワーク通信機能 + +2. **Package managerにpacmanを採用すること** + - Arch Linuxとの互換性 + - パッケージビルドとサーバーの運用負荷軽減 + +## 参考資料 + +- [o8vm/octox](https://github.com/o8vm/octox) - xv6-riscvにインスパイアされたRust OS +- [The Rust Programming Language](https://doc.rust-lang.org/book/) +- [Writing an OS in Rust](https://os.phil-opp.com/) + +## トラブルシューティング + +### ビルドエラー +```bash +# 依存関係の更新 +cargo update + +# クリーンビルド +cargo clean +cargo build +``` + +### QEMUでの問題 +```bash +# シリアル出力の確認 +qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio -nographic +``` + +## 貢献 + +このプロジェクトは教育目的で作成されていますが、貢献は歓迎します。Pull Requestを送る前に、以下を確認してください: + +1. コードがRustのベストプラクティスに従っている +2. 適切なテストが含まれている +3. ドキュメントが更新されている + +## ライセンス + +このプロジェクトは MIT License の下で公開されています。 \ No newline at end of file diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 0000000..3666303 --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,44 @@ +use linked_list_allocator::LockedHeap; +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, + }, + VirtAddr, +}; + +use crate::memory::{HEAP_START, HEAP_SIZE}; + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +pub fn init_heap( + mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, +) -> Result<(), MapToError> { + let page_range = { + let heap_start = VirtAddr::new(HEAP_START as u64); + let heap_end = heap_start + HEAP_SIZE - 1u64; + let heap_start_page = Page::containing_address(heap_start); + let heap_end_page = Page::containing_address(heap_end); + Page::range_inclusive(heap_start_page, heap_end_page) + }; + + for page in page_range { + let frame = frame_allocator + .allocate_frame() + .ok_or(MapToError::FrameAllocationFailed)?; + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + unsafe { mapper.map_to(page, frame, flags, frame_allocator) }?.flush(); + } + + unsafe { + ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE); + } + + Ok(()) +} + +#[alloc_error_handler] +fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { + panic!("allocation error: {:?}", layout) +} \ No newline at end of file diff --git a/src/filesystem.rs b/src/filesystem.rs new file mode 100644 index 0000000..ac49d5b --- /dev/null +++ b/src/filesystem.rs @@ -0,0 +1,289 @@ +use alloc::{collections::BTreeMap, string::String, vec::Vec, string::ToString, format}; +use core::fmt; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FileType { + Regular, + Directory, + Executable, +} + +#[derive(Debug, Clone)] +pub struct FileMetadata { + pub name: String, + pub size: usize, + pub file_type: FileType, + pub permissions: u32, +} + +#[derive(Debug, Clone)] +pub struct File { + pub metadata: FileMetadata, + pub data: Vec, +} + +#[derive(Debug, Clone)] +pub struct Directory { + pub metadata: FileMetadata, + pub entries: BTreeMap, +} + +#[derive(Debug, Clone)] +pub enum FileSystemEntry { + File(File), + Directory(Directory), +} + +pub struct MemoryFileSystem { + root: Directory, + current_dir: String, +} + +impl MemoryFileSystem { + pub fn new() -> Self { + let root_metadata = FileMetadata { + name: "/".to_string(), + size: 0, + file_type: FileType::Directory, + permissions: 0o755, + }; + + let mut root = Directory { + metadata: root_metadata, + entries: BTreeMap::new(), + }; + + // Create initial directories + root.entries.insert("bin".to_string(), FileSystemEntry::Directory(Directory { + metadata: FileMetadata { + name: "bin".to_string(), + size: 0, + file_type: FileType::Directory, + permissions: 0o755, + }, + entries: BTreeMap::new(), + })); + + root.entries.insert("home".to_string(), FileSystemEntry::Directory(Directory { + metadata: FileMetadata { + name: "home".to_string(), + size: 0, + file_type: FileType::Directory, + permissions: 0o755, + }, + entries: BTreeMap::new(), + })); + + root.entries.insert("tmp".to_string(), FileSystemEntry::Directory(Directory { + metadata: FileMetadata { + name: "tmp".to_string(), + size: 0, + file_type: FileType::Directory, + permissions: 0o777, + }, + entries: BTreeMap::new(), + })); + + Self { + root, + current_dir: "/".to_string(), + } + } + + pub fn create_file(&mut self, path: &str, content: &[u8]) -> Result<(), &'static str> { + let (dir_path, filename) = self.split_path(path); + let directory = self.get_directory_mut(&dir_path)?; + + let file = File { + metadata: FileMetadata { + name: filename.clone(), + size: content.len(), + file_type: FileType::Regular, + permissions: 0o644, + }, + data: content.to_vec(), + }; + + directory.entries.insert(filename, FileSystemEntry::File(file)); + Ok(()) + } + + pub fn create_executable(&mut self, path: &str, content: &[u8]) -> Result<(), &'static str> { + let (dir_path, filename) = self.split_path(path); + let directory = self.get_directory_mut(&dir_path)?; + + let file = File { + metadata: FileMetadata { + name: filename.clone(), + size: content.len(), + file_type: FileType::Executable, + permissions: 0o755, + }, + data: content.to_vec(), + }; + + directory.entries.insert(filename, FileSystemEntry::File(file)); + Ok(()) + } + + pub fn read_file(&self, path: &str) -> Result<&Vec, &'static str> { + let (dir_path, filename) = self.split_path(path); + let directory = self.get_directory(&dir_path)?; + + match directory.entries.get(&filename) { + Some(FileSystemEntry::File(file)) => Ok(&file.data), + Some(FileSystemEntry::Directory(_)) => Err("Path is a directory"), + None => Err("File not found"), + } + } + + pub fn create_directory(&mut self, path: &str) -> Result<(), &'static str> { + let (parent_path, dirname) = self.split_path(path); + let parent = self.get_directory_mut(&parent_path)?; + + let directory = Directory { + metadata: FileMetadata { + name: dirname.clone(), + size: 0, + file_type: FileType::Directory, + permissions: 0o755, + }, + entries: BTreeMap::new(), + }; + + parent.entries.insert(dirname, FileSystemEntry::Directory(directory)); + Ok(()) + } + + pub fn list_directory(&self, path: &str) -> Result, &'static str> { + let directory = self.get_directory(path)?; + let mut entries = Vec::new(); + + for entry in directory.entries.values() { + match entry { + FileSystemEntry::File(file) => entries.push(&file.metadata), + FileSystemEntry::Directory(dir) => entries.push(&dir.metadata), + } + } + + Ok(entries) + } + + pub fn remove_file(&mut self, path: &str) -> Result<(), &'static str> { + let (dir_path, filename) = self.split_path(path); + let directory = self.get_directory_mut(&dir_path)?; + + match directory.entries.remove(&filename) { + Some(FileSystemEntry::File(_)) => Ok(()), + Some(FileSystemEntry::Directory(_)) => { + directory.entries.insert(filename.clone(), + FileSystemEntry::Directory(Directory { + metadata: FileMetadata { + name: filename.clone(), + size: 0, + file_type: FileType::Directory, + permissions: 0o755, + }, + entries: BTreeMap::new(), + })); + Err("Cannot remove directory with rm") + }, + None => Err("File not found"), + } + } + + pub fn change_directory(&mut self, path: &str) -> Result<(), &'static str> { + let absolute_path = if path.starts_with('/') { + path.to_string() + } else { + if self.current_dir == "/" { + format!("/{}", path) + } else { + format!("{}/{}", self.current_dir, path) + } + }; + + // Check if directory exists + self.get_directory(&absolute_path)?; + self.current_dir = absolute_path; + Ok(()) + } + + pub fn get_current_directory(&self) -> &str { + &self.current_dir + } + + fn split_path(&self, path: &str) -> (String, String) { + if path == "/" { + return ("/".to_string(), "".to_string()); + } + + let full_path = if path.starts_with('/') { + path.to_string() + } else { + if self.current_dir == "/" { + format!("/{}", path) + } else { + format!("{}/{}", self.current_dir, path) + } + }; + + if let Some(last_slash) = full_path.rfind('/') { + if last_slash == 0 { + ("/".to_string(), full_path[1..].to_string()) + } else { + (full_path[..last_slash].to_string(), full_path[last_slash + 1..].to_string()) + } + } else { + (self.current_dir.clone(), path.to_string()) + } + } + + fn get_directory(&self, path: &str) -> Result<&Directory, &'static str> { + if path == "/" { + return Ok(&self.root); + } + + let mut current = &self.root; + let parts: Vec<&str> = path.trim_start_matches('/').split('/').filter(|s| !s.is_empty()).collect(); + + for part in parts { + match current.entries.get(part) { + Some(FileSystemEntry::Directory(dir)) => current = dir, + Some(FileSystemEntry::File(_)) => return Err("Path component is a file"), + None => return Err("Directory not found"), + } + } + + Ok(current) + } + + fn get_directory_mut(&mut self, path: &str) -> Result<&mut Directory, &'static str> { + if path == "/" { + return Ok(&mut self.root); + } + + let mut current = &mut self.root; + let parts: Vec<&str> = path.trim_start_matches('/').split('/').filter(|s| !s.is_empty()).collect(); + + for part in parts { + match current.entries.get_mut(part) { + Some(FileSystemEntry::Directory(dir)) => current = dir, + Some(FileSystemEntry::File(_)) => return Err("Path component is a file"), + None => return Err("Directory not found"), + } + } + + Ok(current) + } +} + +impl fmt::Display for FileType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FileType::Regular => write!(f, "-"), + FileType::Directory => write!(f, "d"), + FileType::Executable => write!(f, "x"), + } + } +} \ No newline at end of file diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 0000000..209cc3f --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,88 @@ +use crate::{print, println}; +use lazy_static::lazy_static; +use pic8259::ChainedPics; +use spin; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +pub static PICS: spin::Mutex = + spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum InterruptIndex { + Timer = PIC_1_OFFSET, + Keyboard, +} + +impl InterruptIndex { + fn as_u8(self) -> u8 { + self as u8 + } + + fn as_usize(self) -> usize { + usize::from(self.as_u8()) + } +} + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + idt[InterruptIndex::Timer.as_usize()] + .set_handler_fn(timer_interrupt_handler); + idt[InterruptIndex::Keyboard.as_usize()] + .set_handler_fn(keyboard_interrupt_handler); + idt + }; +} + +pub fn init_idt() { + IDT.load(); +} + +extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} + +extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { + unsafe { + PICS.lock() + .notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); + } +} + +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { + use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; + use spin::Mutex; + use x86_64::instructions::port::Port; + + lazy_static! { + static ref KEYBOARD: Mutex> = + Mutex::new(Keyboard::new(ScancodeSet1::new(), layouts::Us104Key, + HandleControl::Ignore) + ); + } + + let mut keyboard = KEYBOARD.lock(); + let mut port = Port::new(0x60); + + let scancode: u8 = unsafe { port.read() }; + if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { + if let Some(key) = keyboard.process_keyevent(key_event) { + match key { + DecodedKey::Unicode(character) => { + crate::shell::handle_keyboard_input(character); + } + DecodedKey::RawKey(key) => print!("{:?}", key), + } + } + } + + unsafe { + PICS.lock() + .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6276076 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,100 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![feature(alloc_error_handler)] +#![feature(abi_x86_interrupt)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +extern crate alloc; + +use core::panic::PanicInfo; +use bootloader::{BootInfo, entry_point}; +use linked_list_allocator::LockedHeap; + +// Global allocatorは削除(allocator.rsで定義済み) + +mod vga_buffer; +mod serial; +mod memory; +mod allocator; +mod interrupts; +mod shell; +mod systemd; +mod filesystem; +mod process; +mod package; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static BootInfo) -> ! { + use memory::BootInfoFrameAllocator; + use x86_64::VirtAddr; + + println!("AIOS Kernel Starting..."); + + // メモリ管理の初期化(bootloader 0.9系では物理メモリオフセットが固定) + let phys_mem_offset = VirtAddr::new(0); + let mut mapper = unsafe { memory::init(phys_mem_offset) }; + let mut frame_allocator = unsafe { + BootInfoFrameAllocator::init(&boot_info.memory_map) + }; + + // ヒープの初期化 + allocator::init_heap(&mut mapper, &mut frame_allocator) + .expect("heap initialization failed"); + + // 割り込み処理の初期化 + interrupts::init_idt(); + + println!("AIOS initialized successfully!"); + + // シェルの初期化 + shell::init(); + + #[cfg(test)] + test_main(); + + loop { + x86_64::instructions::hlt(); + } +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); + serial_println!("PANIC: {}", info); + loop {} +} + +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test(); + } + exit_qemu(QemuExitCode::Success); +} + +#[cfg(test)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +#[cfg(test)] +pub fn exit_qemu(exit_code: QemuExitCode) { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +#[test_case] +fn trivial_assertion() { + assert_eq!(1, 1); +} \ No newline at end of file diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..3e9b2d4 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,76 @@ +use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTable, PhysFrame, Size4KiB, + }, + PhysAddr, VirtAddr, +}; + +pub struct BootInfoFrameAllocator { + memory_map: &'static MemoryMap, + next: usize, +} + +impl BootInfoFrameAllocator { + pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { + BootInfoFrameAllocator { + memory_map, + next: 0, + } + } + + fn usable_frames(&self) -> impl Iterator { + let regions = self.memory_map.iter(); + let usable_regions = regions + .filter(|r| r.region_type == MemoryRegionType::Usable); + let addr_ranges = usable_regions + .map(|r| r.range.start_addr()..r.range.end_addr()); + let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096)); + frame_addresses.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr))) + } +} + +unsafe impl FrameAllocator for BootInfoFrameAllocator { + fn allocate_frame(&mut self) -> Option { + let frame = self.usable_frames().nth(self.next); + self.next += 1; + frame + } +} + +pub fn create_example_mapping( + page: Page, + mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, +) -> Result<(), MapToError> { + use x86_64::structures::paging::PageTableFlags as Flags; + + let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); + let flags = Flags::PRESENT | Flags::WRITABLE; + + let map_to_result = unsafe { mapper.map_to(page, frame, flags, frame_allocator) }; + map_to_result?.flush(); + + Ok(()) +} + +pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + + let phys = level_4_table_frame.start_address(); + let virt = physical_memory_offset + phys.as_u64(); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr +} + + +pub const HEAP_START: usize = 0x_4444_4444_0000; +pub const HEAP_SIZE: usize = 16 * 1024; // 16 KiB - より小さなサイズで安全に + +pub unsafe fn init(physical_memory_offset: VirtAddr) -> impl Mapper { + let level_4_table = active_level_4_table(physical_memory_offset); + x86_64::structures::paging::OffsetPageTable::new(level_4_table, physical_memory_offset) +} \ No newline at end of file diff --git a/src/package.rs b/src/package.rs new file mode 100644 index 0000000..e7121b6 --- /dev/null +++ b/src/package.rs @@ -0,0 +1,413 @@ +use alloc::{collections::BTreeMap, string::String, vec::Vec, string::ToString, format, vec}; +use core::fmt; +use crate::filesystem::MemoryFileSystem; +use crate::systemd::SystemManager; + +#[derive(Debug, Clone)] +pub struct PackageMetadata { + pub name: String, + pub version: String, + pub description: String, + pub arch: String, + pub size: usize, + pub dependencies: Vec, +} + +#[derive(Debug, Clone)] +pub struct PackageFile { + pub path: String, + pub content: Vec, + pub executable: bool, +} + +#[derive(Debug, Clone)] +pub struct ServiceConfig { + pub name: String, + pub command: String, + pub container: bool, + pub auto_start: bool, +} + +#[derive(Debug, Clone)] +pub struct Package { + pub metadata: PackageMetadata, + pub files: Vec, + pub services: Vec, +} + +#[derive(Debug)] +pub struct Repository { + pub name: String, + pub url: String, + pub packages: BTreeMap, +} + +pub struct PackageManager { + installed_packages: BTreeMap, + repositories: Vec, + package_db_path: String, +} + +impl PackageManager { + pub fn new() -> Self { + let mut pm = Self { + installed_packages: BTreeMap::new(), + repositories: Vec::new(), + package_db_path: "/var/pkg".to_string(), + }; + + // Add default repository + pm.add_repository("aios-main".to_string(), "https://packages.aios.dev".to_string()); + + // Create some built-in packages + pm.create_builtin_packages(); + + pm + } + + fn create_builtin_packages(&mut self) { + // Claude Code package + let claude_package = Package { + metadata: PackageMetadata { + name: "claude-code".to_string(), + version: "1.0.0".to_string(), + description: "AI Development Environment".to_string(), + arch: "x86_64".to_string(), + size: 1024 * 1024, // 1MB + dependencies: vec!["nodejs".to_string(), "python".to_string()], + }, + files: vec![ + PackageFile { + path: "/bin/claude-code".to_string(), + content: b"AIOS\x00\x10\x00\x00claude-code-ai-assistant".to_vec(), + executable: true, + }, + PackageFile { + path: "/lib/claude/runtime.so".to_string(), + content: b"Claude Runtime Library".to_vec(), + executable: false, + }, + ], + services: vec![ + ServiceConfig { + name: "claude-code".to_string(), + command: "/bin/claude-code --daemon".to_string(), + container: true, + auto_start: false, + }, + ], + }; + + // Node.js package + let nodejs_package = Package { + metadata: PackageMetadata { + name: "nodejs".to_string(), + version: "18.0.0".to_string(), + description: "JavaScript Runtime".to_string(), + arch: "x86_64".to_string(), + size: 2 * 1024 * 1024, // 2MB + dependencies: vec![], + }, + files: vec![ + PackageFile { + path: "/bin/node".to_string(), + content: b"AIOS\x00\x10\x00\x00nodejs-runtime".to_vec(), + executable: true, + }, + PackageFile { + path: "/bin/npm".to_string(), + content: b"AIOS\x00\x10\x00\x00npm-package-manager".to_vec(), + executable: true, + }, + ], + services: vec![], + }; + + // Python package + let python_package = Package { + metadata: PackageMetadata { + name: "python".to_string(), + version: "3.9.0".to_string(), + description: "Python Interpreter".to_string(), + arch: "x86_64".to_string(), + size: 3 * 1024 * 1024, // 3MB + dependencies: vec![], + }, + files: vec![ + PackageFile { + path: "/bin/python".to_string(), + content: b"AIOS\x00\x10\x00\x00python-interpreter".to_vec(), + executable: true, + }, + PackageFile { + path: "/bin/pip".to_string(), + content: b"AIOS\x00\x10\x00\x00pip-package-installer".to_vec(), + executable: true, + }, + ], + services: vec![], + }; + + // Store as available packages (not installed) + self.repositories[0].packages.insert("claude-code".to_string(), claude_package.metadata.clone()); + self.repositories[0].packages.insert("nodejs".to_string(), nodejs_package.metadata.clone()); + self.repositories[0].packages.insert("python".to_string(), python_package.metadata.clone()); + } + + pub fn add_repository(&mut self, name: String, url: String) { + let repo = Repository { + name, + url, + packages: BTreeMap::new(), + }; + self.repositories.push(repo); + } + + pub fn install_package(&mut self, package_name: &str, filesystem: &mut MemoryFileSystem, system_manager: &mut SystemManager) -> Result<(), &'static str> { + // Check if already installed + if self.installed_packages.contains_key(package_name) { + return Err("Package already installed"); + } + + // Find package in repositories + let package = self.find_package(package_name)?; + + // Check dependencies + for dep in &package.metadata.dependencies { + if !self.is_package_installed(dep) { + crate::println!("Installing dependency: {}", dep); + self.install_package(dep, filesystem, system_manager)?; + } + } + + // Install files + for file in &package.files { + if file.executable { + filesystem.create_executable(&file.path, &file.content)?; + } else { + filesystem.create_file(&file.path, &file.content)?; + } + } + + // Register services + for service in &package.services { + use crate::systemd::{ServiceUnit, ServiceState}; + let service_unit = ServiceUnit { + name: service.name.clone(), + description: format!("Service for {}", package.metadata.name), + exec_start: service.command.clone(), + working_directory: Some("/".to_string()), + environment: vec![], + container: if service.container { Some("default".to_string()) } else { None }, + state: ServiceState::Inactive, + }; + + system_manager.create_service(service_unit)?; + } + + // Mark as installed + self.installed_packages.insert(package_name.to_string(), package); + + crate::println!("Successfully installed package: {}", package_name); + Ok(()) + } + + pub fn remove_package(&mut self, package_name: &str, filesystem: &mut MemoryFileSystem) -> Result<(), &'static str> { + let package = self.installed_packages.get(package_name) + .ok_or("Package not installed")? + .clone(); + + // Remove files + for file in &package.files { + let _ = filesystem.remove_file(&file.path); // Ignore errors for now + } + + // Remove from installed list + self.installed_packages.remove(package_name); + + crate::println!("Successfully removed package: {}", package_name); + Ok(()) + } + + pub fn list_installed(&self) { + crate::println!("Installed packages:"); + crate::println!("{:<20} {:<10} {:<30}", "NAME", "VERSION", "DESCRIPTION"); + crate::println!("{}", "-".repeat(60)); + + for package in self.installed_packages.values() { + crate::println!("{:<20} {:<10} {:<30}", + package.metadata.name, + package.metadata.version, + package.metadata.description + ); + } + } + + pub fn list_available(&self) { + crate::println!("Available packages:"); + crate::println!("{:<20} {:<10} {:<30}", "NAME", "VERSION", "DESCRIPTION"); + crate::println!("{}", "-".repeat(60)); + + for repo in &self.repositories { + for package in repo.packages.values() { + let status = if self.installed_packages.contains_key(&package.name) { + "[installed]" + } else { + "" + }; + crate::println!("{:<20} {:<10} {:<20} {}", + package.name, + package.version, + package.description, + status + ); + } + } + } + + pub fn search_package(&self, query: &str) -> Vec<&PackageMetadata> { + let mut results = Vec::new(); + for repo in &self.repositories { + for package in repo.packages.values() { + if package.name.contains(query) || package.description.contains(query) { + results.push(package); + } + } + } + results + } + + pub fn show_package_info(&self, package_name: &str) -> Result<(), &'static str> { + if let Some(package) = self.installed_packages.get(package_name) { + crate::println!("Package: {}", package.metadata.name); + crate::println!("Version: {}", package.metadata.version); + crate::println!("Description: {}", package.metadata.description); + crate::println!("Architecture: {}", package.metadata.arch); + crate::println!("Size: {} bytes", package.metadata.size); + crate::println!("Status: Installed"); + + if !package.metadata.dependencies.is_empty() { + crate::println!("Dependencies:"); + for dep in &package.metadata.dependencies { + crate::println!(" - {}", dep); + } + } + + if !package.files.is_empty() { + crate::println!("Files:"); + for file in &package.files { + crate::println!(" {}", file.path); + } + } + + Ok(()) + } else { + // Check in repositories + for repo in &self.repositories { + if let Some(package) = repo.packages.get(package_name) { + crate::println!("Package: {}", package.name); + crate::println!("Version: {}", package.version); + crate::println!("Description: {}", package.description); + crate::println!("Architecture: {}", package.arch); + crate::println!("Size: {} bytes", package.size); + crate::println!("Status: Available"); + + if !package.dependencies.is_empty() { + crate::println!("Dependencies:"); + for dep in &package.dependencies { + crate::println!(" - {}", dep); + } + } + + return Ok(()); + } + } + Err("Package not found") + } + } + + fn find_package(&self, package_name: &str) -> Result { + // For now, return built-in packages + match package_name { + "claude-code" => { + Ok(Package { + metadata: PackageMetadata { + name: "claude-code".to_string(), + version: "1.0.0".to_string(), + description: "AI Development Environment".to_string(), + arch: "x86_64".to_string(), + size: 1024 * 1024, + dependencies: vec!["nodejs".to_string()], + }, + files: vec![ + PackageFile { + path: "/bin/claude-code".to_string(), + content: b"AIOS\x00\x10\x00\x00claude-code-ai-assistant".to_vec(), + executable: true, + }, + ], + services: vec![ + ServiceConfig { + name: "claude-code".to_string(), + command: "/bin/claude-code --daemon".to_string(), + container: true, + auto_start: false, + }, + ], + }) + } + "nodejs" => { + Ok(Package { + metadata: PackageMetadata { + name: "nodejs".to_string(), + version: "18.0.0".to_string(), + description: "JavaScript Runtime".to_string(), + arch: "x86_64".to_string(), + size: 2 * 1024 * 1024, + dependencies: vec![], + }, + files: vec![ + PackageFile { + path: "/bin/node".to_string(), + content: b"AIOS\x00\x10\x00\x00nodejs-runtime".to_vec(), + executable: true, + }, + ], + services: vec![], + }) + } + "python" => { + Ok(Package { + metadata: PackageMetadata { + name: "python".to_string(), + version: "3.9.0".to_string(), + description: "Python Interpreter".to_string(), + arch: "x86_64".to_string(), + size: 3 * 1024 * 1024, + dependencies: vec![], + }, + files: vec![ + PackageFile { + path: "/bin/python".to_string(), + content: b"AIOS\x00\x10\x00\x00python-interpreter".to_vec(), + executable: true, + }, + ], + services: vec![], + }) + } + _ => Err("Package not found") + } + } + + fn is_package_installed(&self, package_name: &str) -> bool { + self.installed_packages.contains_key(package_name) + } + + pub fn update_repositories(&mut self) -> Result<(), &'static str> { + crate::println!("Updating package repositories..."); + // In a real implementation, this would fetch from remote repositories + crate::println!("Repository update completed"); + Ok(()) + } +} \ No newline at end of file diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 0000000..5ec76d4 --- /dev/null +++ b/src/process.rs @@ -0,0 +1,203 @@ +use alloc::{collections::BTreeMap, string::String, vec::Vec, string::ToString, format}; +use core::fmt; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ProcessState { + Ready, + Running, + Waiting, + Terminated, +} + +#[derive(Debug, Clone, Copy)] +pub struct ProcessId(pub usize); + +#[derive(Debug)] +pub struct Process { + pub pid: ProcessId, + pub name: String, + pub state: ProcessState, + pub entry_point: usize, + pub stack_pointer: usize, + pub memory: Vec, + pub exit_code: Option, +} + +pub struct ProcessManager { + processes: BTreeMap, + next_pid: usize, + current_process: Option, +} + +impl ProcessManager { + pub fn new() -> Self { + Self { + processes: BTreeMap::new(), + next_pid: 1, + current_process: None, + } + } + + pub fn create_process(&mut self, name: String, program: &[u8]) -> Result { + let pid = ProcessId(self.next_pid); + self.next_pid += 1; + + // Simple ELF-like parsing (simplified for demo) + let entry_point = self.parse_program(program)?; + + let process = Process { + pid, + name, + state: ProcessState::Ready, + entry_point, + stack_pointer: 0x8000, // Simple stack setup + memory: program.to_vec(), + exit_code: None, + }; + + self.processes.insert(pid.0, process); + Ok(pid) + } + + pub fn start_process(&mut self, pid: ProcessId) -> Result<(), &'static str> { + if let Some(process) = self.processes.get_mut(&pid.0) { + if process.state == ProcessState::Ready { + process.state = ProcessState::Running; + self.current_process = Some(pid.0); + crate::println!("Starting process: {} (PID: {})", process.name, pid.0); + + // In a real OS, this would switch to the process context + // For now, we'll simulate execution + self.simulate_execution(pid)?; + Ok(()) + } else { + Err("Process not in ready state") + } + } else { + Err("Process not found") + } + } + + pub fn terminate_process(&mut self, pid: ProcessId, exit_code: i32) -> Result<(), &'static str> { + if let Some(process) = self.processes.get_mut(&pid.0) { + process.state = ProcessState::Terminated; + process.exit_code = Some(exit_code); + + if self.current_process == Some(pid.0) { + self.current_process = None; + } + + crate::println!("Process {} terminated with exit code {}", process.name, exit_code); + Ok(()) + } else { + Err("Process not found") + } + } + + pub fn list_processes(&self) -> Vec<&Process> { + self.processes.values().collect() + } + + pub fn get_process(&self, pid: ProcessId) -> Option<&Process> { + self.processes.get(&pid.0) + } + + pub fn kill_process(&mut self, pid: ProcessId) -> Result<(), &'static str> { + self.terminate_process(pid, -1) + } + + // Simple program parser - in reality this would be a full ELF parser + fn parse_program(&self, program: &[u8]) -> Result { + if program.len() < 4 { + return Err("Invalid program format"); + } + + // Check for simple executable magic + if &program[0..4] == b"AIOS" { + // AIOS native executable format + if program.len() < 8 { + return Err("Invalid AIOS executable"); + } + + // Entry point stored at offset 4-8 + let entry_point = u32::from_le_bytes([program[4], program[5], program[6], program[7]]) as usize; + Ok(entry_point) + } else if &program[0..4] == [0x7f, b'E', b'L', b'F'] { + // Basic ELF detection + crate::println!("ELF executable detected (simplified parsing)"); + Ok(0x1000) // Default entry point for ELF + } else { + // Treat as raw machine code + Ok(0x0) + } + } + + // Simulate process execution (in a real OS this would involve context switching) + fn simulate_execution(&mut self, pid: ProcessId) -> Result<(), &'static str> { + if let Some(process) = self.processes.get(&pid.0) { + crate::println!("Executing process: {}", process.name); + + // Check if it's a built-in command or script + if process.name == "hello" { + crate::println!("Hello from AIOS process!"); + self.terminate_process(pid, 0)?; + } else if process.name == "claude-code" { + crate::println!("Claude Code AI Assistant starting..."); + crate::println!("AI development environment initialized"); + crate::println!("Type 'ai help' for available commands"); + self.terminate_process(pid, 0)?; + } else if process.name.starts_with("ai-") { + crate::println!("AI service: {}", process.name); + crate::println!("Connecting to Claude API..."); + crate::println!("AI service ready"); + self.terminate_process(pid, 0)?; + } else { + // Unknown program + crate::println!("Unknown program format, terminating"); + self.terminate_process(pid, 1)?; + } + + Ok(()) + } else { + Err("Process not found") + } + } + + pub fn execute_command(&mut self, command: &str, args: &[&str]) -> Result { + // Create a simple program based on command + let program_name = if command == "claude-code" { + "claude-code".to_string() + } else if command.starts_with("ai") { + format!("ai-{}", args.join("-")) + } else { + command.to_string() + }; + + // Create a simple executable + let mut program = Vec::new(); + program.extend_from_slice(b"AIOS"); // Magic + program.extend_from_slice(&(0x1000u32).to_le_bytes()); // Entry point + program.extend_from_slice(command.as_bytes()); // Command data + + let pid = self.create_process(program_name, &program)?; + self.start_process(pid)?; + Ok(pid) + } +} + +impl fmt::Display for ProcessState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ProcessState::Ready => write!(f, "Ready"), + ProcessState::Running => write!(f, "Running"), + ProcessState::Waiting => write!(f, "Waiting"), + ProcessState::Terminated => write!(f, "Terminated"), + } + } +} + +impl fmt::Display for ProcessId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} \ No newline at end of file diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..c9df926 --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,32 @@ +use uart_16550::SerialPort; +use spin::Mutex; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); +} + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); +} \ No newline at end of file diff --git a/src/shell.rs b/src/shell.rs new file mode 100644 index 0000000..095eabb --- /dev/null +++ b/src/shell.rs @@ -0,0 +1,494 @@ +use alloc::{string::String, vec::Vec, vec, string::ToString}; +use crate::{print, println}; +use crate::systemd::SystemManager; +use crate::filesystem::MemoryFileSystem; +use crate::process::ProcessManager; +use crate::package::PackageManager; +use lazy_static::lazy_static; +use spin::Mutex; + +lazy_static! { + static ref SHELL: Mutex = Mutex::new(Shell::new()); +} + +pub struct Shell { + input_buffer: String, + history: Vec, + prompt: &'static str, + system_manager: SystemManager, + filesystem: MemoryFileSystem, + process_manager: ProcessManager, + package_manager: PackageManager, +} + +impl Shell { + fn new() -> Self { + // 一時的にメモリを使う部分を最小限に + let filesystem = MemoryFileSystem::new(); + let process_manager = ProcessManager::new(); + let system_manager = SystemManager::new(); + let package_manager = PackageManager::new(); + + Self { + input_buffer: String::new(), + history: Vec::new(), + prompt: "aios$ ", + system_manager, + filesystem, + process_manager, + package_manager, + } + } + + fn execute_command(&mut self, command: &str) { + let parts: Vec<&str> = command.trim().split_whitespace().collect(); + if parts.is_empty() { + return; + } + + match parts[0] { + "help" => self.show_help(), + "clear" => self.clear_screen(), + "echo" => { + for arg in &parts[1..] { + print!("{} ", arg); + } + println!(); + } + "history" => self.show_history(), + "systemctl" => { + if let Err(e) = self.system_manager.systemctl(&parts[1..]) { + println!("systemctl: {}", e); + } + } + "nspawn" => { + if let Err(e) = self.system_manager.nspawn(&parts[1..]) { + println!("systemd-nspawn: {}", e); + } + } + "claude" => self.execute_claude_command(&parts[1..]), + "ai" => self.execute_ai_command(&parts[1..]), + // File system commands + "ls" => self.list_directory(&parts[1..]), + "cd" => self.change_directory(&parts[1..]), + "pwd" => println!("{}", self.filesystem.get_current_directory()), + "mkdir" => self.make_directory(&parts[1..]), + "cat" => self.cat_file(&parts[1..]), + "touch" => self.create_file(&parts[1..]), + "rm" => self.remove_file(&parts[1..]), + // Process commands + "ps" => self.list_processes(), + "kill" => self.kill_process(&parts[1..]), + "exec" => self.execute_program(&parts[1..]), + // Package manager commands + "pkg" => self.handle_package_command(&parts[1..]), + "exit" => { + println!("Goodbye!"); + // TODO: 適切なシャットダウン処理 + } + _ => { + // Try to execute as a program + if let Err(e) = self.process_manager.execute_command(parts[0], &parts[1..]) { + println!("Unknown command: {}. Type 'help' for available commands.", parts[0]); + println!("Error: {}", e); + } + } + } + } + + fn execute_claude_command(&mut self, args: &[&str]) { + if args.is_empty() { + println!("Claude Code integration - AI-powered development environment"); + println!("Usage: claude "); + println!(" start - Start Claude Code server"); + println!(" stop - Stop Claude Code server"); + println!(" status - Show Claude Code status"); + return; + } + + match args[0] { + "start" => { + println!("Starting Claude Code in container..."); + // TODO: Claude Codeをコンテナ内で起動 + if let Err(e) = self.start_claude_service() { + println!("Failed to start Claude Code: {}", e); + } + } + "stop" => { + println!("Stopping Claude Code..."); + if let Err(e) = self.system_manager.stop_service("claude-code") { + println!("Failed to stop Claude Code: {}", e); + } + } + "status" => { + if let Err(e) = self.system_manager.systemctl(&["status", "claude-code"]) { + println!("Claude Code status: {}", e); + } + } + _ => println!("Unknown claude subcommand: {}", args[0]), + } + } + + fn execute_ai_command(&mut self, args: &[&str]) { + if args.is_empty() { + println!("AI assistant integration"); + println!("Usage: ai "); + return; + } + + let query = args.join(" "); + println!("AI Query: {}", query); + println!("Response: This would integrate with Claude API for AI assistance"); + // TODO: 実際のAI統合 + } + + fn start_claude_service(&mut self) -> Result<(), &'static str> { + use crate::systemd::{ServiceUnit, ServiceState}; + + // Claude Codeサービスを作成 + let service = ServiceUnit { + name: "claude-code".to_string(), + description: "Claude Code AI Development Environment".to_string(), + exec_start: "/usr/local/bin/claude-code".to_string(), + working_directory: Some("/workspace".to_string()), + environment: vec![ + ("ANTHROPIC_API_KEY".to_string(), "your-api-key".to_string()), + ("PORT".to_string(), "3000".to_string()), + ], + container: Some("claude-container".to_string()), + state: ServiceState::Inactive, + }; + + // コンテナを作成 + self.system_manager.create_container( + "claude-container".to_string(), + "/containers/claude".to_string(), + )?; + + // サービスを登録・起動 + self.system_manager.create_service(service)?; + self.system_manager.start_service("claude-code") + } + + fn show_help(&self) { + println!("AIOS Shell - Available Commands:"); + println!(" help - Show this help message"); + println!(" clear - Clear the screen"); + println!(" echo - Print text to output"); + println!(" history - Show command history"); + println!(""); + println!("File System:"); + println!(" ls [path] - List directory contents"); + println!(" cd - Change directory"); + println!(" pwd - Print working directory"); + println!(" mkdir - Create directory"); + println!(" cat - Display file contents"); + println!(" touch - Create empty file"); + println!(" rm - Remove file"); + println!(""); + println!("Process Management:"); + println!(" ps - List running processes"); + println!(" exec - Execute program"); + println!(" kill - Kill process by PID"); + println!(""); + println!("Package Management:"); + println!(" pkg install - Install package"); + println!(" pkg remove - Remove package"); + println!(" pkg list - List installed packages"); + println!(" pkg search - Search packages"); + println!(" pkg info - Show package info"); + println!(" pkg update - Update repositories"); + println!(""); + println!("System:"); + println!(" systemctl - Control system services"); + println!(" claude - Claude Code integration"); + println!(" ai - AI assistant"); + println!(" exit - Exit shell"); + } + + // File system command implementations + fn list_directory(&mut self, args: &[&str]) { + let path = if args.is_empty() { + self.filesystem.get_current_directory() + } else { + args[0] + }; + + match self.filesystem.list_directory(path) { + Ok(entries) => { + for entry in entries { + println!("{} {:>8} {}", entry.file_type, entry.size, entry.name); + } + } + Err(e) => println!("ls: {}", e), + } + } + + fn change_directory(&mut self, args: &[&str]) { + if args.is_empty() { + if let Err(e) = self.filesystem.change_directory("/") { + println!("cd: {}", e); + } + } else { + if let Err(e) = self.filesystem.change_directory(args[0]) { + println!("cd: {}", e); + } + } + } + + fn make_directory(&mut self, args: &[&str]) { + if args.is_empty() { + println!("mkdir: missing directory name"); + return; + } + + if let Err(e) = self.filesystem.create_directory(args[0]) { + println!("mkdir: {}", e); + } + } + + fn cat_file(&mut self, args: &[&str]) { + if args.is_empty() { + println!("cat: missing file name"); + return; + } + + match self.filesystem.read_file(args[0]) { + Ok(data) => { + if let Ok(text) = core::str::from_utf8(data) { + print!("{}", text); + } else { + println!("cat: binary file"); + } + } + Err(e) => println!("cat: {}", e), + } + } + + fn create_file(&mut self, args: &[&str]) { + if args.is_empty() { + println!("touch: missing file name"); + return; + } + + if let Err(e) = self.filesystem.create_file(args[0], b"") { + println!("touch: {}", e); + } + } + + fn remove_file(&mut self, args: &[&str]) { + if args.is_empty() { + println!("rm: missing file name"); + return; + } + + if let Err(e) = self.filesystem.remove_file(args[0]) { + println!("rm: {}", e); + } + } + + // Process command implementations + fn list_processes(&mut self) { + let processes = self.process_manager.list_processes(); + println!("PID NAME STATE"); + for process in processes { + println!("{:<6} {:<14} {}", process.pid, process.name, process.state); + } + } + + fn kill_process(&mut self, args: &[&str]) { + if args.is_empty() { + println!("kill: missing process ID"); + return; + } + + if let Ok(pid) = args[0].parse::() { + if let Err(e) = self.process_manager.kill_process(crate::process::ProcessId(pid)) { + println!("kill: {}", e); + } + } else { + println!("kill: invalid process ID"); + } + } + + fn execute_program(&mut self, args: &[&str]) { + if args.is_empty() { + println!("exec: missing program name"); + return; + } + + if let Err(e) = self.process_manager.execute_command(args[0], &args[1..]) { + println!("exec: {}", e); + } + } + + // Package manager command implementations + fn handle_package_command(&mut self, args: &[&str]) { + if args.is_empty() { + println!("pkg: missing command"); + println!("Usage: pkg [args...]"); + println!("Commands: install, remove, list, search, info, update"); + return; + } + + match args[0] { + "install" => { + if args.len() < 2 { + println!("pkg install: missing package name"); + return; + } + self.install_package(args[1]); + } + "remove" => { + if args.len() < 2 { + println!("pkg remove: missing package name"); + return; + } + self.remove_package(args[1]); + } + "list" => { + if args.len() > 1 && args[1] == "available" { + self.package_manager.list_available(); + } else { + self.package_manager.list_installed(); + } + } + "search" => { + if args.len() < 2 { + println!("pkg search: missing search query"); + return; + } + self.search_packages(args[1]); + } + "info" => { + if args.len() < 2 { + println!("pkg info: missing package name"); + return; + } + if let Err(e) = self.package_manager.show_package_info(args[1]) { + println!("pkg info: {}", e); + } + } + "update" => { + if let Err(e) = self.package_manager.update_repositories() { + println!("pkg update: {}", e); + } + } + _ => { + println!("pkg: unknown command '{}'", args[0]); + println!("Available commands: install, remove, list, search, info, update"); + } + } + } + + fn install_package(&mut self, package_name: &str) { + println!("Installing package: {}", package_name); + match self.package_manager.install_package(package_name, &mut self.filesystem, &mut self.system_manager) { + Ok(()) => { + println!("Package '{}' installed successfully", package_name); + + // Special handling for Claude Code + if package_name == "claude-code" { + println!("Claude Code is now available!"); + println!("You can start it with: claude start"); + println!("Or run it directly: /bin/claude-code"); + } + } + Err(e) => println!("Failed to install package '{}': {}", package_name, e), + } + } + + fn remove_package(&mut self, package_name: &str) { + println!("Removing package: {}", package_name); + match self.package_manager.remove_package(package_name, &mut self.filesystem) { + Ok(()) => println!("Package '{}' removed successfully", package_name), + Err(e) => println!("Failed to remove package '{}': {}", package_name, e), + } + } + + fn search_packages(&mut self, query: &str) { + let results = self.package_manager.search_package(query); + if results.is_empty() { + println!("No packages found matching '{}'", query); + } else { + println!("Packages matching '{}':", query); + println!("{:<20} {:<10} {:<30}", "NAME", "VERSION", "DESCRIPTION"); + println!("{}", "-".repeat(60)); + for package in results { + println!("{:<20} {:<10} {:<30}", package.name, package.version, package.description); + } + } + } + + fn clear_screen(&self) { + // VGAバッファをクリア + crate::vga_buffer::WRITER.lock().clear_screen(); + } + + fn show_history(&self) { + println!("Command History:"); + for (i, cmd) in self.history.iter().enumerate() { + println!(" {}: {}", i + 1, cmd); + } + } + + fn show_prompt(&self) { + print!("{}", self.prompt); + } + + fn add_to_history(&mut self, command: String) { + if !command.trim().is_empty() { + self.history.push(command); + } + } +} + +// VGAバッファにclear_screen機能を追加 +impl crate::vga_buffer::Writer { + pub fn clear_screen(&mut self) { + use crate::vga_buffer::{BUFFER_HEIGHT, BUFFER_WIDTH, ColorCode, Color, ScreenChar}; + + let blank = ScreenChar { + ascii_character: b' ', + color_code: ColorCode::new(Color::Yellow, Color::Black), + }; + + for row in 0..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } + self.column_position = 0; + } +} + +pub fn handle_keyboard_input(character: char) { + let mut shell = SHELL.lock(); + match character { + '\n' => { + println!(); + let command = shell.input_buffer.clone(); + shell.add_to_history(command.clone()); + shell.execute_command(&command); + shell.input_buffer.clear(); + shell.show_prompt(); + } + '\x08' => { // Backspace + if !shell.input_buffer.is_empty() { + shell.input_buffer.pop(); + print!("\x08 \x08"); // Backspace, space, backspace + } + } + c if c.is_ascii() && !c.is_control() => { + shell.input_buffer.push(c); + print!("{}", c); + } + _ => {} // 他の文字は無視 + } +} + +pub fn init() { + println!("AIOS Shell v0.1.0 - AI-Integrated Operating System"); + println!("Type 'help' for available commands."); + SHELL.lock().show_prompt(); +} \ No newline at end of file diff --git a/src/systemd.rs b/src/systemd.rs new file mode 100644 index 0000000..c0f9d77 --- /dev/null +++ b/src/systemd.rs @@ -0,0 +1,180 @@ +use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use core::fmt; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ServiceState { + Inactive, + Active, + Failed, + Activating, + Deactivating, +} + +#[derive(Debug, Clone)] +pub struct ServiceUnit { + pub name: String, + pub description: String, + pub exec_start: String, + pub working_directory: Option, + pub environment: Vec<(String, String)>, + pub container: Option, + pub state: ServiceState, +} + +#[derive(Debug, Clone)] +pub struct Container { + pub name: String, + pub root_directory: String, + pub private_network: bool, + pub bind_mounts: Vec<(String, String)>, + pub capability_bounding_set: Vec, +} + +pub struct SystemManager { + services: BTreeMap, + containers: BTreeMap, +} + +impl SystemManager { + pub fn new() -> Self { + Self { + services: BTreeMap::new(), + containers: BTreeMap::new(), + } + } + + pub fn create_container(&mut self, name: String, root_dir: String) -> Result<(), &'static str> { + let container = Container { + name: name.clone(), + root_directory: root_dir, + private_network: true, + bind_mounts: Vec::new(), + capability_bounding_set: Vec::new(), + }; + + self.containers.insert(name, container); + Ok(()) + } + + pub fn create_service(&mut self, service: ServiceUnit) -> Result<(), &'static str> { + self.services.insert(service.name.clone(), service); + Ok(()) + } + + pub fn start_service(&mut self, name: &str) -> Result<(), &'static str> { + if let Some(service) = self.services.get_mut(name) { + service.state = ServiceState::Activating; + + // コンテナ内で実行する場合 + if let Some(container_name) = &service.container { + if let Some(_container) = self.containers.get(container_name) { + // TODO: nspawn風のコンテナ内でプロセス実行 + crate::println!("Starting service {} in container {}", name, container_name); + } else { + return Err("Container not found"); + } + } else { + // ホスト上で直接実行 + crate::println!("Starting service {} on host", name); + } + + service.state = ServiceState::Active; + Ok(()) + } else { + Err("Service not found") + } + } + + pub fn stop_service(&mut self, name: &str) -> Result<(), &'static str> { + if let Some(service) = self.services.get_mut(name) { + service.state = ServiceState::Deactivating; + crate::println!("Stopping service {}", name); + service.state = ServiceState::Inactive; + Ok(()) + } else { + Err("Service not found") + } + } + + pub fn list_services(&self) { + crate::println!("UNIT LOAD ACTIVE SUB DESCRIPTION"); + for (name, service) in &self.services { + crate::println!("{:<24} loaded {:?} running {}", + name, service.state, service.description); + } + } + + pub fn list_containers(&self) { + crate::println!("MACHINE CLASS SERVICE"); + for (name, _container) in &self.containers { + crate::println!("{:<8} container -", name); + } + } + + // systemctl風のコマンド処理 + pub fn systemctl(&mut self, args: &[&str]) -> Result<(), &'static str> { + if args.len() < 2 { + return Err("Usage: systemctl "); + } + + match args[0] { + "start" => self.start_service(args[1]), + "stop" => self.stop_service(args[1]), + "status" => { + if let Some(service) = self.services.get(args[1]) { + crate::println!("● {}", service.name); + crate::println!(" Loaded: loaded"); + crate::println!(" Active: {:?}", service.state); + crate::println!(" Description: {}", service.description); + Ok(()) + } else { + Err("Service not found") + } + } + "list-units" => { + self.list_services(); + Ok(()) + } + _ => Err("Unknown systemctl command"), + } + } + + // systemd-nspawn風のコマンド + pub fn nspawn(&mut self, args: &[&str]) -> Result<(), &'static str> { + if args.len() < 2 { + return Err("Usage: nspawn --directory= "); + } + + let mut directory = None; + let mut command = None; + + for arg in args { + if arg.starts_with("--directory=") { + directory = Some(&arg[12..]); + } else if !arg.starts_with("--") { + command = Some(*arg); + break; + } + } + + if let (Some(dir), Some(cmd)) = (directory, command) { + crate::println!("Starting container in {} executing {}", dir, cmd); + // TODO: 実際のコンテナ実行 + Ok(()) + } else { + Err("Invalid nspawn arguments") + } + } +} + +impl fmt::Display for ServiceState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ServiceState::Inactive => write!(f, "inactive"), + ServiceState::Active => write!(f, "active"), + ServiceState::Failed => write!(f, "failed"), + ServiceState::Activating => write!(f, "activating"), + ServiceState::Deactivating => write!(f, "deactivating"), + } + } +} \ No newline at end of file diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs new file mode 100644 index 0000000..b2ce1e2 --- /dev/null +++ b/src/vga_buffer.rs @@ -0,0 +1,164 @@ +use volatile::Volatile; +use core::fmt; +use lazy_static::lazy_static; +use spin::Mutex; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ColorCode(u8); + +impl ColorCode { + pub fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct ScreenChar { + pub ascii_character: u8, + pub color_code: ColorCode, +} + +pub const BUFFER_HEIGHT: usize = 25; +pub const BUFFER_WIDTH: usize = 80; + +#[repr(transparent)] +pub struct Buffer { + pub chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +pub struct Writer { + pub column_position: usize, + color_code: ColorCode, + pub buffer: &'static mut Buffer, +} + +impl Writer { + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte => { + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + + let row = BUFFER_HEIGHT - 1; + let col = self.column_position; + + let color_code = self.color_code; + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + 0x20..=0x7e | b'\n' => self.write_byte(byte), + _ => self.write_byte(0xfe), + } + } + } + + fn new_line(&mut self) { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(BUFFER_HEIGHT - 1); + self.column_position = 0; + } + + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + ascii_character: b' ', + color_code: self.color_code, + }; + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } +} + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +lazy_static! { + pub static ref WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +} + +#[test_case] +fn test_println_simple() { + println!("test_println_simple output"); +} + +#[test_case] +fn test_println_many() { + for i in 0..200 { + println!("test_println_many output {}", i); + } +} + +#[test_case] +fn test_println_output() { + let s = "Some test string that fits on a single line"; + println!("{}", s); + for (i, c) in s.chars().enumerate() { + let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } +} \ No newline at end of file