Nothing to see here, move along meow
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 17 kB View raw
1#![no_std] 2#![no_main] 3 4use lancer_user::fs::{self, FsClient, FsRights, FsStatus, READDIR_ENTRY_SIZE}; 5use lancer_user::net; 6use lancer_user::show; 7use lancer_user::syscall; 8use lancer_vfs_proto::ReadDirEntry; 9 10struct TestCtx { 11 client: FsClient, 12 pass_count: u32, 13 fail_count: u32, 14} 15 16impl TestCtx { 17 fn check(&mut self, name: &str, ok: bool) { 18 match ok { 19 true => { 20 self.pass_count += 1; 21 } 22 false => { 23 self.fail_count += 1; 24 show!(fstest, error, "{name}"); 25 } 26 } 27 } 28 29 fn check_ok<T>(&mut self, name: &str, result: &Result<T, FsStatus>) { 30 match result { 31 Ok(_) => self.check(name, true), 32 Err(e) => { 33 self.fail_count += 1; 34 let ename = e.name(); 35 show!(fstest, error, "{name} ({ename})"); 36 } 37 } 38 } 39 40 fn check_err(&mut self, name: &str, result: &Result<(), FsStatus>, expected: FsStatus) { 41 match result { 42 Err(e) if *e == expected => self.check(name, true), 43 Ok(()) => { 44 self.fail_count += 1; 45 show!(fstest, error, "{name} (expected error, got ok)"); 46 } 47 Err(e) => { 48 self.fail_count += 1; 49 let exp = expected.name(); 50 let got = e.name(); 51 show!(fstest, error, "{name} (expected {exp}, got {got})"); 52 } 53 } 54 } 55} 56 57fn bytes_match(a: &[u8], b: &[u8]) -> bool { 58 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x == y) 59} 60 61fn make_name(prefix: &[u8], idx: u8, buf: &mut [u8; 8]) -> usize { 62 let p_len = prefix.len().min(5); 63 buf[..p_len].copy_from_slice(&prefix[..p_len]); 64 buf[p_len] = b'0' + (idx / 10); 65 buf[p_len + 1] = b'0' + (idx % 10); 66 p_len + 2 67} 68 69fn handle_leak_check(ctx: &mut TestCtx, count: u8) { 70 let mut handles = [0u8; 62]; 71 let all_ok = (0..count).all(|i| { 72 let mut name = [0u8; 8]; 73 let nlen = make_name(b"leak", i, &mut name); 74 match ctx.client.open(0, &name[..nlen], FsRights::ALL) { 75 Ok(h) => { 76 handles[i as usize] = h; 77 true 78 } 79 Err(_) => false, 80 } 81 }); 82 ctx.check("handle leak check (open)", all_ok); 83 84 let close_ok = (0..count).all(|i| ctx.client.close(handles[i as usize]).is_ok()); 85 ctx.check("handle leak check (close)", close_ok); 86 87 let cleanup_ok = (0..count).all(|i| { 88 let mut name = [0u8; 8]; 89 let nlen = make_name(b"leak", i, &mut name); 90 ctx.client.unlink(0, &name[..nlen]).is_ok() 91 }); 92 ctx.check("handle leak check (cleanup)", cleanup_ok); 93} 94 95fn test_single_fs_smoke(ctx: &mut TestCtx) { 96 show!(fstest, "single fs smoke"); 97 98 let _ = ctx.client.unlink(0, b"test"); 99 let mkdir_r = ctx.client.mkdir(0, b"test"); 100 ctx.check_ok("mkdir /test", &mkdir_r); 101 102 let test_h = match ctx.client.open(0, b"test", FsRights::ALL) { 103 Ok(h) => h, 104 Err(_) => { 105 ctx.check("open /test dir", false); 106 return; 107 } 108 }; 109 ctx.check("open /test dir", true); 110 111 let hello_h = match ctx.client.open(test_h, b"hello", FsRights::ALL) { 112 Ok(h) => h, 113 Err(_) => { 114 ctx.check("create /test/hello", false); 115 let _ = ctx.client.close(test_h); 116 return; 117 } 118 }; 119 ctx.check("create /test/hello", true); 120 121 let wr = ctx.client.write(hello_h, 0, b"world"); 122 ctx.check_ok("write 'world'", &wr); 123 ctx.check( 124 "write returned 5 bytes", 125 wr.map(|n| n == 5).unwrap_or(false), 126 ); 127 128 let mut rbuf = [0u8; 64]; 129 let rr = ctx.client.read(hello_h, 0, &mut rbuf); 130 ctx.check_ok("read back", &rr); 131 match rr { 132 Ok(n) => ctx.check( 133 "read data matches 'world'", 134 bytes_match(&rbuf[..n], b"world"), 135 ), 136 Err(_) => ctx.check("read data matches 'world'", false), 137 } 138 139 let _ = ctx.client.close(hello_h); 140 141 let mut dir_buf = [0u8; READDIR_ENTRY_SIZE * 4]; 142 let readdir_r = ctx.client.readdir(test_h, 0, &mut dir_buf); 143 ctx.check_ok("readdir /test", &readdir_r); 144 match readdir_r { 145 Ok((count, _)) => { 146 ctx.check("readdir returns 1 entry", count == 1); 147 if count >= 1 { 148 let entry = ReadDirEntry::from_bytes(&dir_buf); 149 match entry { 150 Some(e) => { 151 ctx.check( 152 "entry name is 'hello'", 153 bytes_match(e.name_slice(), b"hello"), 154 ); 155 ctx.check("entry type is file", e.inode_type == 0); 156 ctx.check("entry size is 5", e.size == 5); 157 } 158 None => ctx.check("parse readdir entry", false), 159 } 160 } 161 } 162 Err(_) => ctx.check("readdir returns 1 entry", false), 163 } 164 165 let unlink_r = ctx.client.unlink(test_h, b"hello"); 166 ctx.check_ok("unlink hello", &unlink_r); 167 168 let readdir_r2 = ctx.client.readdir(test_h, 0, &mut dir_buf); 169 ctx.check_ok("readdir after unlink", &readdir_r2); 170 match readdir_r2 { 171 Ok((count, _)) => ctx.check("readdir returns 0 entries", count == 0), 172 Err(_) => ctx.check("readdir returns 0 entries", false), 173 } 174 175 let _ = ctx.client.close(test_h); 176 177 let unlink_dir_r = ctx.client.unlink(0, b"test"); 178 ctx.check_ok("unlink /test dir", &unlink_dir_r); 179 180 handle_leak_check(ctx, 62); 181} 182 183fn test_multi_mount_smoke(ctx: &mut TestCtx) { 184 show!(fstest, "multi-mount smoke"); 185 186 let tmp_h = match fs::open_path(&mut ctx.client, 0, b"/tmp") { 187 Ok(h) => h, 188 Err(_) => { 189 ctx.check("open /tmp", false); 190 return; 191 } 192 }; 193 ctx.check("open /tmp", true); 194 195 let mkdir_r = ctx.client.mkdir(tmp_h, b"scratch"); 196 ctx.check_ok("mkdir /tmp/scratch", &mkdir_r); 197 198 let scratch_h = match ctx.client.open(tmp_h, b"scratch", FsRights::ALL) { 199 Ok(h) => h, 200 Err(_) => { 201 ctx.check("open /tmp/scratch", false); 202 let _ = ctx.client.close(tmp_h); 203 return; 204 } 205 }; 206 ctx.check("open /tmp/scratch", true); 207 208 let data_h = match ctx.client.open(scratch_h, b"data", FsRights::ALL) { 209 Ok(h) => h, 210 Err(_) => { 211 ctx.check("create /tmp/scratch/data", false); 212 let _ = ctx.client.close(scratch_h); 213 let _ = ctx.client.close(tmp_h); 214 return; 215 } 216 }; 217 ctx.check("create /tmp/scratch/data", true); 218 219 let wr = ctx.client.write(data_h, 0, b"test"); 220 ctx.check_ok("write to /tmp/scratch/data", &wr); 221 let _ = ctx.client.close(data_h); 222 223 let data_h2 = match ctx.client.open(scratch_h, b"data", FsRights::READ) { 224 Ok(h) => h, 225 Err(_) => { 226 ctx.check("reopen /tmp/scratch/data", false); 227 let _ = ctx.client.close(scratch_h); 228 let _ = ctx.client.close(tmp_h); 229 return; 230 } 231 }; 232 ctx.check("reopen /tmp/scratch/data", true); 233 234 let mut rbuf = [0u8; 64]; 235 let rr = ctx.client.read(data_h2, 0, &mut rbuf); 236 ctx.check_ok("read /tmp/scratch/data", &rr); 237 match rr { 238 Ok(n) => ctx.check("data matches 'test'", bytes_match(&rbuf[..n], b"test")), 239 Err(_) => ctx.check("data matches 'test'", false), 240 } 241 let _ = ctx.client.close(data_h2); 242 243 let persist_h = match ctx.client.open(0, b"persist_check", FsRights::ALL) { 244 Ok(h) => h, 245 Err(_) => { 246 ctx.check("create /persist_check on root", false); 247 let _ = ctx.client.close(scratch_h); 248 let _ = ctx.client.close(tmp_h); 249 return; 250 } 251 }; 252 ctx.check("create /persist_check on root", true); 253 let _ = ctx.client.write(persist_h, 0, b"root_data"); 254 let _ = ctx.client.close(persist_h); 255 256 let vol_h = match ctx.client.open(tmp_h, b"vol_check", FsRights::ALL) { 257 Ok(h) => h, 258 Err(_) => { 259 ctx.check("create /tmp/vol_check", false); 260 let _ = ctx.client.unlink(0, b"persist_check"); 261 let _ = ctx.client.close(scratch_h); 262 let _ = ctx.client.close(tmp_h); 263 return; 264 } 265 }; 266 ctx.check("create /tmp/vol_check", true); 267 let _ = ctx.client.write(vol_h, 0, b"tmp_data"); 268 let _ = ctx.client.close(vol_h); 269 270 let cross = ctx.client.open(tmp_h, b"persist_check", FsRights::READ); 271 ctx.check("persist_check not visible under /tmp", cross.is_err()); 272 if let Ok(h) = cross { 273 let _ = ctx.client.close(h); 274 } 275 276 let cross2 = ctx.client.open(0, b"vol_check", FsRights::READ); 277 ctx.check("vol_check not visible at root", cross2.is_err()); 278 if let Ok(h) = cross2 { 279 let _ = ctx.client.close(h); 280 } 281 282 let rename_r = ctx.client.rename(scratch_h, b"data", 0, b"moved"); 283 ctx.check_err( 284 "cross-mount rename returns CrossDevice", 285 &rename_r, 286 FsStatus::CrossDevice, 287 ); 288 289 let _ = ctx.client.unlink(scratch_h, b"data"); 290 let _ = ctx.client.close(scratch_h); 291 let _ = ctx.client.unlink(tmp_h, b"scratch"); 292 let _ = ctx.client.unlink(tmp_h, b"vol_check"); 293 let _ = ctx.client.close(tmp_h); 294 let _ = ctx.client.unlink(0, b"persist_check"); 295} 296 297fn test_handle_exhaustion(ctx: &mut TestCtx) { 298 show!(fstest, "handle exhaustion"); 299 300 let mut handles = [0u8; 63]; 301 let all_opened = (0..63u8).all(|i| { 302 let mut name = [0u8; 8]; 303 let nlen = make_name(b"h", i, &mut name); 304 match ctx.client.open(0, &name[..nlen], FsRights::ALL) { 305 Ok(h) => { 306 handles[i as usize] = h; 307 true 308 } 309 Err(_) => false, 310 } 311 }); 312 ctx.check("open 63 files all succeed", all_opened); 313 314 let unique = (0..63usize).all(|i| (i + 1..63).all(|j| handles[i] != handles[j])); 315 ctx.check("all 63 handles are unique", unique); 316 317 let overflow_r = ctx.client.open(0, b"overflow", FsRights::ALL); 318 ctx.check( 319 "64th open returns HandleTableFull", 320 matches!(overflow_r, Err(FsStatus::HandleTableFull)), 321 ); 322 323 let _ = ctx.client.unlink(0, b"overflow"); 324 let _ = ctx.client.close(handles[0]); 325 326 let retry_r = ctx.client.open(0, b"h00", FsRights::READ); 327 ctx.check("retry after close succeeds", retry_r.is_ok()); 328 if let Ok(h) = retry_r { 329 handles[0] = h; 330 } 331 332 let close_ok = (0..63u8).all(|i| ctx.client.close(handles[i as usize]).is_ok()); 333 ctx.check("close all 63 handles", close_ok); 334 335 let unlink_ok = (0..63u8).all(|i| { 336 let mut name = [0u8; 8]; 337 let nlen = make_name(b"h", i, &mut name); 338 ctx.client.unlink(0, &name[..nlen]).is_ok() 339 }); 340 ctx.check("unlink all 63 files", unlink_ok); 341 342 let fresh_h = ctx.client.open(0, b"fresh_test", FsRights::ALL); 343 ctx.check_ok("post-exhaustion fresh open", &fresh_h); 344 if let Ok(h) = fresh_h { 345 let _ = ctx.client.close(h); 346 let _ = ctx.client.unlink(0, b"fresh_test"); 347 } 348} 349 350fn test_path_resolution_stress(ctx: &mut TestCtx) { 351 show!(fstest, "path resolution"); 352 353 const DEPTH: u8 = 30; 354 355 let create_ok = create_deep_dirs(ctx, 0, 0, DEPTH); 356 ctx.check("create 30-level directory tree", create_ok); 357 358 let mut path_buf = [0u8; 256]; 359 let path_len = build_deep_path(&mut path_buf, DEPTH, b"deep_file"); 360 361 let (parent_h, _leaf) = match fs::open_parent(&mut ctx.client, 0, &path_buf[..path_len]) { 362 Ok(result) => result, 363 Err(_) => { 364 ctx.check("open parent of deep file", false); 365 delete_deep_dirs(ctx, DEPTH); 366 return; 367 } 368 }; 369 ctx.check("open parent of deep file", true); 370 371 let deep_h = match ctx.client.open(parent_h, b"deep_file", FsRights::ALL) { 372 Ok(h) => h, 373 Err(_) => { 374 ctx.check("create deep file", false); 375 let _ = ctx.client.close(parent_h); 376 delete_deep_dirs(ctx, DEPTH); 377 return; 378 } 379 }; 380 ctx.check("create deep file", true); 381 382 let wr = ctx.client.write(deep_h, 0, b"deep content"); 383 ctx.check_ok("write to deep file", &wr); 384 385 let mut rbuf = [0u8; 64]; 386 let rr = ctx.client.read(deep_h, 0, &mut rbuf); 387 ctx.check_ok("read deep file", &rr); 388 match rr { 389 Ok(n) => ctx.check( 390 "deep file content matches", 391 bytes_match(&rbuf[..n], b"deep content"), 392 ), 393 Err(_) => ctx.check("deep file content matches", false), 394 } 395 396 let _ = ctx.client.close(deep_h); 397 398 let unlink_r = ctx.client.unlink(parent_h, b"deep_file"); 399 ctx.check_ok("unlink deep file", &unlink_r); 400 let _ = ctx.client.close(parent_h); 401 402 delete_deep_dirs(ctx, DEPTH); 403 404 handle_leak_check(ctx, 62); 405} 406 407fn create_deep_dirs(ctx: &mut TestCtx, parent_handle: u8, level: u8, max: u8) -> bool { 408 match level >= max { 409 true => true, 410 false => { 411 let mut name = [0u8; 8]; 412 let nlen = make_name(b"d", level, &mut name); 413 match ctx.client.mkdir(parent_handle, &name[..nlen]) { 414 Ok(()) => match ctx.client.open(parent_handle, &name[..nlen], FsRights::ALL) { 415 Ok(dir_h) => { 416 let result = create_deep_dirs(ctx, dir_h, level + 1, max); 417 let _ = ctx.client.close(dir_h); 418 result 419 } 420 Err(_) => false, 421 }, 422 Err(_) => false, 423 } 424 } 425 } 426} 427 428fn build_deep_path(buf: &mut [u8; 256], depth: u8, leaf: &[u8]) -> usize { 429 buf[0] = b'/'; 430 let mut pos = 1; 431 (0..depth).for_each(|i| { 432 let mut name = [0u8; 8]; 433 let nlen = make_name(b"d", i, &mut name); 434 buf[pos..pos + nlen].copy_from_slice(&name[..nlen]); 435 pos += nlen; 436 buf[pos] = b'/'; 437 pos += 1; 438 }); 439 if !leaf.is_empty() { 440 let llen = leaf.len().min(buf.len() - pos); 441 buf[pos..pos + llen].copy_from_slice(&leaf[..llen]); 442 pos += llen; 443 } 444 pos 445} 446 447fn delete_deep_dirs(ctx: &mut TestCtx, depth: u8) { 448 delete_deep_dirs_inner(ctx, depth); 449} 450 451fn delete_deep_dirs_inner(ctx: &mut TestCtx, remaining: u8) { 452 match remaining { 453 0 => {} 454 _ => { 455 let level = remaining - 1; 456 let mut parent_path = [0u8; 256]; 457 let parent_len = build_deep_path(&mut parent_path, level, b""); 458 let trimmed = match parent_len > 1 && parent_path[parent_len - 1] == b'/' { 459 true => parent_len - 1, 460 false => parent_len, 461 }; 462 463 if let Ok(parent_h) = fs::open_path(&mut ctx.client, 0, &parent_path[..trimmed]) { 464 let mut name = [0u8; 8]; 465 let nlen = make_name(b"d", level, &mut name); 466 let _ = ctx.client.unlink(parent_h, &name[..nlen]); 467 let _ = ctx.client.close(parent_h); 468 } 469 470 delete_deep_dirs_inner(ctx, remaining - 1); 471 } 472 } 473} 474 475fn test_concurrent_a(ctx: &mut TestCtx) { 476 let h = match ctx.client.open(0, b"conc_a", FsRights::ALL) { 477 Ok(h) => h, 478 Err(_) => { 479 show!(fstest, error, "concurrent-a open failed"); 480 return; 481 } 482 }; 483 let _ = ctx.client.write(h, 0, b"alpha_data"); 484 let _ = ctx.client.close(h); 485 show!(fstest, "concurrent-a done"); 486} 487 488fn test_concurrent_b(ctx: &mut TestCtx) { 489 let h = match ctx.client.open(0, b"conc_b", FsRights::ALL) { 490 Ok(h) => h, 491 Err(_) => { 492 show!(fstest, error, "concurrent-b open failed"); 493 return; 494 } 495 }; 496 let _ = ctx.client.write(h, 0, b"bravo_data"); 497 let _ = ctx.client.close(h); 498 show!(fstest, "concurrent-b done"); 499} 500 501fn print_results(ctx: &TestCtx) { 502 let p = ctx.pass_count; 503 let f = ctx.fail_count; 504 match f { 505 0 => show!(fstest, "results: {p} pass, {f} fail - ok"), 506 _ => show!(fstest, error, "results: {p} pass, {f} fail - failed"), 507 } 508} 509 510#[unsafe(no_mangle)] 511pub extern "C" fn lancer_main() -> ! { 512 let (rx, _tx) = match net::init() { 513 Some(pair) => pair, 514 None => { 515 show!(fstest, error, "netsock init failed"); 516 syscall::exit(); 517 } 518 }; 519 520 let mut args_buf = [0u8; 256]; 521 let args_len = net::recv_args(&rx, &mut args_buf); 522 let args = &args_buf[..args_len]; 523 524 let (_cmd, rest) = net::next_token(args); 525 let (subcmd, _) = net::next_token(rest); 526 527 let client = unsafe { fs::init() }; 528 529 let mut ctx = TestCtx { 530 client, 531 pass_count: 0, 532 fail_count: 0, 533 }; 534 535 match subcmd { 536 b"single" => test_single_fs_smoke(&mut ctx), 537 b"multi" => test_multi_mount_smoke(&mut ctx), 538 b"handles" => test_handle_exhaustion(&mut ctx), 539 b"paths" => test_path_resolution_stress(&mut ctx), 540 b"concurrent-a" => { 541 test_concurrent_a(&mut ctx); 542 syscall::exit(); 543 } 544 b"concurrent-b" => { 545 test_concurrent_b(&mut ctx); 546 syscall::exit(); 547 } 548 _ => { 549 test_single_fs_smoke(&mut ctx); 550 test_multi_mount_smoke(&mut ctx); 551 test_handle_exhaustion(&mut ctx); 552 test_path_resolution_stress(&mut ctx); 553 } 554 } 555 556 print_results(&ctx); 557 syscall::exit() 558}