fix(services/ipmfs): stop stripping the root twice from listed entries - #8070
Conversation
The listing loop concatenates the path being listed with a files/ls
name, then runs the result through build_rel_path a second time:
let path = match object.mode() {
EntryMode::FILE => format!("{}{}", self.path, object.name),
...
};
let path = build_rel_path(&self.root, &path);
self.path is what the operator handed the service, so it is already
relative to the root -- IpmfsCore::ipmfs_ls is what turns it into a
rooted absolute path for the request. The concatenation is therefore
root-relative before that call, and the call removes a prefix that is
not there.
Under the default root "/" the second strip happens to cancel out, which
is why this has gone unnoticed. Under any other root, in release:
root "/abc/", listing "dir/", name "a"
build_rel_path("/abc/", "dir/a") -> "a"
the directory is dropped from every entry path
root "/abc/", listing the root itself (self.path is "/"), name "a"
build_rel_path("/abc/", "/a")
panicked: start byte index 5 is out of bounds for string of
length 2
In a debug build both cases trip the debug_assert! inside build_rel_path
instead.
The construction moves into build_entry_path, which takes no root at
all -- an entry path does not depend on one. The only thing it has to
handle is that self.path is "/" when the root itself is listed, and an
entry path carries no leading slash.
The self-entry twenty lines above is left alone: it does
build_abs_path then build_rel_path, a deliberate round trip, and is
correct.
Three unit tests. Putting the old expression back passes all of them
with a root of "/" and fails three of them with a root of "/abc/" --
which is the shape of the bug.
Note for rebasing: apache#7801 renames this very call to
build_relative_path. If that lands first this needs a one-word rebase;
the call itself goes away here.
Matching the shape asked for on apache#8068, apache#8069 and apache#8071 rather than waiting to be asked again: the extracted helper goes, the construction is inline, and the test module goes with it. The fix is unchanged -- the concatenation is already relative to the root, so the second build_rel_path is what truncated it, and "/" as the listed path must not become a leading slash on the entry.
|
Slimmed this to match the shape you asked for on #8068, #8069 and #8071, rather than making you say it again. The extracted helper is gone and the construction is inline; the test module went with it. The diff is now +10/-4. The fix itself is unchanged: The removed tests were the thing pinning that the entry path does not depend on the service root. Your call, not asking to keep them. |
| ctx.done = true; | ||
|
|
||
| for object in entries_body.entries.unwrap_or_default() { | ||
| // `self.path` is "/" when the root itself is listed, and an entry path carries |
|
Removed. The diff is now +7/-4 — the The reasoning stays in the commit message and the PR description, so it is still findable without sitting in the loop body. |
Which issue does this PR close?
None — found while reading the service.
Rationale for this change
The listing loop concatenates the path being listed with a
files/lsname, then runs the result throughbuild_rel_patha second time:self.pathis the path the operator handed the service, so it is already relative to the root —IpmfsCore::ipmfs_lsis what turns it into a rooted absolute path for the request:So the concatenation is root-relative before that call, and the call strips a prefix that is not there.
Under the default root
/the second strip cancels out, which is why this has gone unnoticed. Under any other root, measured in release (cargo test --release):/dir/adir/a✅/abc/dir/aa— the directory is dropped from every entry path/abc/self.pathis/astart byte index 5 is out of bounds for string of length 2In a debug build both of the last two trip the
debug_assert!insidebuild_rel_pathinstead.What changes are included in this PR?
The construction moves into
build_entry_path, which takes no root at all — an entry path does not depend on one. The only thing it has to handle is thatself.pathis"/"when the root itself is listed, and an entry path carries no leading slash.The self-entry twenty lines above is deliberately left alone: it does
build_abs_pathand thenbuild_rel_path, a genuine round trip, and is correct.Tests
Three unit tests. Restoring the old expression shows the defect and why it survived:
//abc/cargo test -p opendal-service-ipmfs --lib→ 4 passed.cargo fmt --all -- --checkandcargo clippy -p opendal-service-ipmfs --all-targetsboth clean.Note for rebasing
#7801 renames this very call to
build_relative_path. If that lands first this needs a one-word rebase — though the call disappears here, which is the point.Are there any user-facing changes?
Yes, for
services-ipmfswith a root other than/: listed entry paths keep the directory they are in, and listing the root no longer panics. Behaviour under the default root/is unchanged.