Skip to main content

ax_fs/
lib.rs

1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! AXFS - Axvisor Filesystem Module
16#![cfg_attr(all(not(test), not(doc)), no_std)]
17#[macro_use]
18extern crate log;
19extern crate alloc;
20
21pub mod dev;
22pub mod fs;
23mod mounts;
24mod partition;
25mod root;
26
27pub mod api;
28pub mod fops;
29
30use alloc::{
31    string::{String, ToString},
32    sync::Arc,
33    vec::Vec,
34};
35
36use ax_driver::{AxBlockDevice, AxDeviceContainer, prelude::*};
37
38use crate::partition::PartitionInfo;
39
40/// Initializes filesystems by block devices.
41pub fn init_filesystems(mut blk_devs: AxDeviceContainer<AxBlockDevice>, bootargs: Option<&str>) {
42    info!("Initialize filesystems...");
43
44    let dev = blk_devs.take_one().expect("No block device found!");
45    info!("  use block device 0: {:?}", dev.device_name());
46    let mut disk = self::dev::Disk::new(dev);
47
48    // Parse root parameter from bootargs
49    let root_spec = parse_root_spec(bootargs);
50
51    // Try to scan GPT partitions first
52    match self::partition::scan_gpt_partitions(&mut disk) {
53        Ok(partitions) if !partitions.is_empty() => {
54            initialize_with_partitions(disk, partitions, &root_spec)
55        }
56        Ok(_) => {
57            warn!("No partitions found, mount ramfs as rootfs");
58            self::root::init_rootfs_with_ramfs();
59        }
60        Err(e) => {
61            warn!("Failed to scan GPT partitions: {:?}", e);
62        }
63    }
64}
65
66/// Initialize filesystems with detected partitions
67fn initialize_with_partitions(
68    disk: self::dev::Disk,
69    partitions: Vec<PartitionInfo>,
70    root_spec: &RootSpec,
71) {
72    info!(
73        "Found {} partitions, initializing with dynamic filesystem detection",
74        partitions.len()
75    );
76
77    // Find root partition based on specification
78    let root_partition_index = find_root_partition(&partitions, root_spec);
79
80    // Check if any partition has a supported filesystem
81    let has_supported_fs = partitions.iter().any(|p| p.filesystem_type.is_some());
82
83    if has_supported_fs {
84        // Try to initialize with partitions
85        let disk_arc = Arc::new(disk);
86        if !self::root::init_rootfs_with_partitions(disk_arc, partitions, root_partition_index) {
87            warn!("Failed to initialize with partitions.");
88        }
89    } else {
90        warn!("No supported filesystem found in partitions.");
91    }
92}
93
94/// Format a GUID as a string in format used by Linux PARTUUID
95fn format_guid_as_partuuid(guid: &[u8; 16]) -> alloc::string::String {
96    // Linux PARTUUID format is little-endian for first 3 fields,
97    // and big-endian for the rest
98    alloc::format!(
99        "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:\
100         02X}{:02X}{:02X}",
101        guid[3],
102        guid[2],
103        guid[1],
104        guid[0], // First 4 bytes (little-endian)
105        guid[5],
106        guid[4], // Next 2 bytes (little-endian)
107        guid[7],
108        guid[6], // Next 2 bytes (little-endian)
109        guid[8],
110        guid[9], // Next 2 bytes (big-endian)
111        guid[10],
112        guid[11],
113        guid[12],
114        guid[13],
115        guid[14],
116        guid[15] // Last 6 bytes (big-endian)
117    )
118}
119
120/// Root filesystem specification
121#[derive(Debug, Default)]
122struct RootSpec {
123    partition_index: Option<usize>,
124    partuuid: Option<String>,
125    uuid: Option<String>,
126    partlabel: Option<String>,
127}
128
129/// Parse root parameter from bootargs
130fn parse_root_spec(bootargs: Option<&str>) -> RootSpec {
131    let mut spec = RootSpec::default();
132
133    if let Some(bootargs) = bootargs
134        && let Some(root_arg) = bootargs
135            .split_whitespace()
136            .find(|arg| arg.starts_with("root="))
137    {
138        let root_value = root_arg.strip_prefix("root=").unwrap_or("");
139
140        spec = match root_value {
141            v if v.starts_with("/dev/sda") => parse_device_path(v, "/dev/sda"),
142            v if v.starts_with("/dev/mmcblk") => parse_mmcblk_path(v),
143            v if v.starts_with("PARTUUID=") => {
144                let partuuid = v.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase();
145                info!("Looking for partition with PARTUUID: {}", partuuid);
146                RootSpec {
147                    partuuid: Some(partuuid),
148                    ..Default::default()
149                }
150            }
151            v if v.starts_with("UUID=") => {
152                let uuid = v.strip_prefix("UUID=").unwrap_or("").to_uppercase();
153                info!("Looking for filesystem with UUID: {}", uuid);
154                RootSpec {
155                    uuid: Some(uuid),
156                    ..Default::default()
157                }
158            }
159            v if v.starts_with("PARTLABEL=") => {
160                let partlabel = v.strip_prefix("PARTLABEL=").unwrap_or("").to_string();
161                info!("Looking for partition with PARTLABEL: {}", partlabel);
162                RootSpec {
163                    partlabel: Some(partlabel),
164                    ..Default::default()
165                }
166            }
167            _ => spec,
168        };
169    }
170
171    spec
172}
173
174/// Parse device path like /dev/sdaX
175fn parse_device_path(path: &str, prefix: &str) -> RootSpec {
176    if let Some(part_num) = path.strip_prefix(prefix)
177        && let Ok(num) = part_num.parse::<usize>()
178        && num > 0
179    {
180        return RootSpec {
181            partition_index: Some(num - 1),
182            ..Default::default()
183        };
184    }
185    RootSpec::default()
186}
187
188/// Parse mmcblk path like /dev/mmcblkXpY
189fn parse_mmcblk_path(path: &str) -> RootSpec {
190    if let Some(remaining) = path.strip_prefix("/dev/mmcblk")
191        && let Some(p_pos) = remaining.find('p')
192    {
193        let part_str = &remaining[p_pos + 1..];
194        if let Ok(num) = part_str.parse::<usize>()
195            && num > 0
196        {
197            return RootSpec {
198                partition_index: Some(num - 1),
199                ..Default::default()
200            };
201        }
202    }
203    RootSpec::default()
204}
205
206/// Find partition index based on root specification
207fn find_root_partition(partitions: &[PartitionInfo], root_spec: &RootSpec) -> Option<usize> {
208    // If we have a specific partition index, use it
209    if let Some(index) = root_spec.partition_index {
210        return if index < partitions.len() {
211            Some(index)
212        } else {
213            None
214        };
215    }
216
217    // Try to match by PARTUUID or UUID
218    for (i, partition) in partitions.iter().enumerate() {
219        if partition.filesystem_type.is_none() {
220            continue;
221        }
222
223        // Check PARTUUID match
224        if let Some(ref partuuid) = root_spec.partuuid {
225            let partition_guid = format_guid_as_partuuid(&partition.unique_partition_guid);
226            debug!("Partition {} PARTUUID: {}", i, partition_guid);
227            if partition_guid.contains(partuuid) {
228                info!("Found matching partition by PARTUUID: {}", i);
229                return Some(i);
230            }
231        }
232
233        // Check UUID match
234        if let Some(ref uuid) = root_spec.uuid
235            && let Some(ref partition_uuid) = partition.filesystem_uuid
236            && partition_uuid.to_uppercase() == *uuid
237        {
238            info!("UUID matches partition {} ({})", i, partition.name);
239            return Some(i);
240        }
241
242        // Check PARTLABEL match
243        if let Some(ref partlabel) = root_spec.partlabel
244            && partition.name == *partlabel
245        {
246            info!("PARTLABEL matches partition {} ({})", i, partition.name);
247            return Some(i);
248        }
249    }
250
251    None
252}