Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions apps/browser-demos/test/path-resolution.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from "@playwright/test";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const programPath = resolve(
__dirname,
"../../../examples/mount_probe_test.wasm",
);

test("browser resolves pathname components and rejects an invalid initial cwd", async ({
page,
baseURL,
browserName,
}) => {
test.skip(
browserName !== "chromium",
"the aggregate browser gate uses Chromium",
);
expect(baseURL).toBeTruthy();

await page.goto(new URL("/pages/test-runner/", baseURL).href);
await page.waitForFunction(() => (window as any).__testRunnerReady === true);

const programUrl = new URL(`/@fs/${programPath}`, baseURL).href;
const result = await page.evaluate(
async ({ programUrl }) => {
const response = await fetch(programUrl);
if (!response.ok) {
throw new Error(`program fetch failed: ${response.status}`);
}
const program = await response.arrayBuffer();
const probe = await (window as any).__runTest(
program.slice(0),
["mount_probe_test", "path-resolution", "/tmp/kandelo-path-resolution"],
15_000,
{
dataFiles: [{ path: "/etc/services", data: [1, 2, 3, 4] }],
},
);
const cwdError = await (window as any)
.__runTest(
program.slice(0),
["mount_probe_test", "rootfs", "/etc/services"],
15_000,
{ cwd: "/no/such/cwd" },
)
.then(
() => null,
(error: unknown) => String(error),
);
return { probe, cwdError };
},
{ programUrl },
);

expect(result.probe.exitCode, result.probe.stderr).toBe(0);
expect(result.probe.stdout).toContain("PATH_RESOLUTION_PASS");
expect(result.probe.stderr).toBe("");
expect(result.cwdError).toMatch(/setCwd failed for pid \d+: errno 2/);
});
33 changes: 33 additions & 0 deletions crates/kernel/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
extern crate alloc;
use alloc::vec::Vec;

/// Make a pathname absolute without interpreting any of its components.
///
/// Pathname resolution is stateful: `missing/..` must look up `missing`, and
/// a `..` after a symlink applies to the symlink target rather than to the
/// spelling of the input. Callers that need POSIX resolution must therefore
/// preserve `.`, `..`, repeated separators, and a final slash until the
/// namespace walker has examined them.
pub fn make_absolute(path: &[u8], cwd: &[u8]) -> Vec<u8> {
if path.is_empty() {
return Vec::new();
}
if path[0] == b'/' {
return path.to_vec();
}

let mut absolute = cwd.to_vec();
if absolute.last() != Some(&b'/') {
absolute.push(b'/');
}
absolute.extend_from_slice(path);
absolute
}

/// Resolve a path against a working directory.
/// If path is absolute (starts with '/'), normalize and return it.
/// If path is relative, prepend cwd + '/' and normalize.
Expand Down Expand Up @@ -56,6 +79,16 @@ mod tests {
assert_eq!(resolved, b"/home/user/file.txt");
}

#[test]
fn test_make_absolute_preserves_resolution_components() {
assert_eq!(
make_absolute(b"missing/../file/.", b"/working/dir"),
b"/working/dir/missing/../file/."
);
assert_eq!(make_absolute(b"/a//b/../", b"/ignored"), b"/a//b/../");
assert!(make_absolute(b"", b"/working/dir").is_empty());
}

#[test]
fn test_relative_path_prepends_cwd() {
let resolved = resolve_path(b"file.txt", b"/working/dir");
Expand Down
Loading