Stop folding '..' across a usbfs node

JO
8h ago
+618-188

/dev/bus/usb/001/001/.. answers as the bus directory where Linux answers ENOTDIR, because a .. after a usbfs node is folded away, and /dev/bus/../null answers ENOENT, because a .. that climbs out of /dev/bus reaches a backing that cannot apply it. The first commit gives the relative-name join one home without changing behavior, and the second makes a /dev/bus descriptor and cwd report the directory reached rather than a spelling with .. in it.

Reproduce with the fixture devices and a static aarch64 busybox:

$ ELFUSE_USB_FIXTURE=1 build/elfuse busybox stat -c %F /dev/bus/usb/001/001/..
$ ELFUSE_USB_FIXTURE=1 build/elfuse busybox stat -c %F /dev/bus/../null

On main these print directory and No such file or directory. With this series they print Not a directory and character special file, as Linux does.

Tested on macOS 15.6.1 (24G90) with the 15.5 SDK on an Apple M1 (MacBookAir10,1). make check passes 100 of 101 and make test-matrix-elfuse-aarch64 gives 298 passed, 1 failed and 10 skipped. Main gives the same on this machine: the one failure in both is test-thread-churn timing out, and it passes when run alone.


Summary by cubic

Stops folding .. after a usbfs node and rewrites .. climbs out of /dev/bus, so names like /dev/bus/usb/001/001/.. and /dev/bus/../null answer as Linux does (ENOTDIR and /dev/null, respectively) instead of resolving to the wrong directory or ENOENT.

  • A .. after a usbfs node now stops the fold, keeping the rest as written, so every entry point answers ENOTDIR as Linux does.
  • A .. that climbs out of /dev/bus over the layer's directories is rewritten in path_translate_at to /dev plus what follows, so /dev/bus/../null resolves; a climb that ends the name becomes /dev/., keeping rmdir and rename from acting on /dev.
  • Descriptors and the cwd on a synthetic /dev/bus directory now report the directory reached, not the guest's spelling with .. or a trailing slash.
  • Relative names against a /dev/bus cwd or dirfd fold components up to a usbfs node, which the earlier relative-name join refactor (path_rebuild_under) makes a single code path.
  • statfs asks the /dev/bus layer before the /sys fold so a .. through a node answers ENOTDIR.
  • Matrix coverage adds dev-climb, dev-climb-f, and node-dotdot columns, recorded against Linux 7.0; test-usb-sysfs-sysroot pins that rmdir and rename of a final .. leave the sysroot's /dev in place.