77//!
88//! ## Integration test via FUSE
99//!
10- //! [`cross_device_excludes_mount`] uses `fuse2fs ` to mount an ext2 filesystem image via FUSE
10+ //! [`cross_device_excludes_mount`] uses `squashfuse ` to mount a squashfs image via FUSE
1111//! (no root or user namespaces required) and checks that `-x` correctly excludes entries on
1212//! the mounted filesystem.
1313//!
14- //! The FUSE test panics when `fuse2fs `, `/dev/fuse`, or `fusermount` are unavailable.
15- //! It can be excluded via `RUSTFLAGS='--cfg pdu_test_skip_cross_device'`.
14+ //! The FUSE test panics when `mksquashfs `, `squashfuse`, ` /dev/fuse`, or `fusermount` are
15+ //! unavailable. It can be excluded via `RUSTFLAGS='--cfg pdu_test_skip_cross_device'`.
1616
1717#![ cfg( unix) ]
1818#![ cfg( feature = "cli" ) ]
@@ -71,27 +71,39 @@ struct FuseTools {
7171 fusermount : & ' static str ,
7272}
7373
74- /// Probes for `fuse2fs` and FUSE infrastructure.
74+ /// Probes for `squashfuse`, `mksquashfs`, and FUSE infrastructure.
7575///
7676/// Verifies:
77- /// 1. `fuse2fs` binary exists
78- /// 2. `/dev/fuse` is accessible
79- /// 3. `fusermount` (or `fusermount3`) binary exists
77+ /// 1. `mksquashfs` binary exists
78+ /// 2. `squashfuse` binary exists
79+ /// 3. `/dev/fuse` is accessible
80+ /// 4. `fusermount` (or `fusermount3`) binary exists
8081///
8182/// Returns `Ok(FuseTools)` with the discovered tool paths, or `Err` with a diagnostic message.
8283#[ cfg( target_os = "linux" ) ]
8384#[ cfg( not( pdu_test_skip_cross_device) ) ]
8485fn fuse_probe ( ) -> Result < FuseTools , String > {
8586 use std:: { path:: Path , process:: Command } ;
8687
87- // Check that fuse2fs is installed
88- Command :: new ( "fuse2fs" )
88+ // Check that mksquashfs is installed
89+ Command :: new ( "mksquashfs" )
90+ . arg ( "-version" )
91+ . output ( )
92+ . map_err ( |error| {
93+ format ! (
94+ "`mksquashfs` not found: {error}. \
95+ Install via `apt install squashfs-tools`."
96+ )
97+ } ) ?;
98+
99+ // Check that squashfuse is installed
100+ Command :: new ( "squashfuse" )
89101 . arg ( "--help" )
90102 . output ( )
91103 . map_err ( |error| {
92104 format ! (
93- "`fuse2fs ` not found: {error}. \
94- Install the `fuse2fs` package (or `e2fsprogs` on distros that bundle it) ."
105+ "`squashfuse ` not found: {error}. \
106+ Install via `apt install squashfuse` ."
95107 )
96108 } ) ?;
97109
@@ -111,9 +123,9 @@ fn fuse_probe() -> Result<FuseTools, String> {
111123 ( true , _) => "fusermount" ,
112124 ( _, true ) => "fusermount3" ,
113125 _ => {
114- return Err (
115- "Neither `fusermount` nor `fusermount3` found. Install fuse or fuse3." . to_string ( ) ,
116- ) ;
126+ return Err ( "Neither `fusermount` nor `fusermount3` found. \
127+ Install via `apt install fuse3`."
128+ . to_string ( ) ) ;
117129 }
118130 } ;
119131
@@ -122,8 +134,9 @@ fn fuse_probe() -> Result<FuseTools, String> {
122134
123135/// When a subdirectory is a mount point for a different filesystem, `-x` should exclude it.
124136///
125- /// Uses `fuse2fs` to mount an ext2 filesystem image via FUSE — no root privileges or
126- /// user namespaces required.
137+ /// Uses `squashfuse` to mount a squashfs image via FUSE — no root privileges or
138+ /// user namespaces required. The image is pre-built with `mksquashfs` containing the
139+ /// test file, so the mount is read-only (which is fine since `pdu` only reads).
127140/// Skipped when FUSE infrastructure is unavailable.
128141#[ test]
129142#[ cfg( target_os = "linux" ) ]
@@ -140,9 +153,10 @@ fn cross_device_excludes_mount() {
140153 let fuse_tools = match fuse_probe ( ) {
141154 Ok ( tools) => tools,
142155 Err ( reason) => panic ! (
143- "error: This test requires FUSE (`fuse2fs`, `/dev/fuse`, `fusermount`) but the probe failed.\n \
156+ "error: This test requires FUSE (`mksquashfs`, `squashfuse`, `/dev/fuse`, \
157+ `fusermount`) but the probe failed.\n \
144158 reason: {reason}\n \
145- hint: Install `fuse2fs` and ` fuse3` packages , or set \
159+ hint: Install via `apt install squashfs-tools squashfuse fuse3`, or set \
146160 `RUSTFLAGS='--cfg pdu_test_skip_cross_device'` to skip this test.",
147161 ) ,
148162 } ;
@@ -151,55 +165,54 @@ fn cross_device_excludes_mount() {
151165 let temp = Temp :: new_dir ( ) . expect ( "create temp dir for cross-device test" ) ;
152166 let workspace = temp. join ( "workspace" ) ;
153167 let mount_point = workspace. join ( "mounted" ) ;
154- let image_path = temp. join ( "ext2.img" ) ;
168+ let image_path = temp. join ( "squash.img" ) ;
169+ let staging_dir = temp. join ( "staging" ) ;
155170
156171 fs:: create_dir_all ( & mount_point) . expect ( "create workspace and mount point" ) ;
172+ fs:: create_dir_all ( & staging_dir) . expect ( "create staging directory" ) ;
157173
158174 // Write a file on the root filesystem
159175 let outside_content = "A" . repeat ( 1000 ) ;
160176 fs:: write ( workspace. join ( "outside.txt" ) , & outside_content) . expect ( "write outside.txt" ) ;
161177
162- // Create a small ext2 filesystem image (4 MiB)
163- let mkfs_output = Command :: new ( "mkfs.ext2" )
164- . with_args ( [ "-F" , "-q" ] )
178+ // Create a file in the staging directory to be packed into the squashfs image
179+ let inside_content = "B" . repeat ( 2000 ) ;
180+ fs:: write ( staging_dir. join ( "inside.txt" ) , & inside_content) . expect ( "write staging/inside.txt" ) ;
181+
182+ // Build a squashfs image from the staging directory
183+ let mksquashfs_output = Command :: new ( "mksquashfs" )
184+ . with_arg ( & staging_dir)
165185 . with_arg ( & image_path)
166- . with_arg ( "4096" ) // 4096 × 1K blocks = 4 MiB
186+ . with_args ( [ "-noappend" , "-quiet" ] )
167187 . with_stdout ( Stdio :: piped ( ) )
168188 . with_stderr ( Stdio :: piped ( ) )
169189 . output ( )
170- . expect ( "run mkfs.ext2 " ) ;
190+ . expect ( "run mksquashfs " ) ;
171191 assert ! (
172- mkfs_output . status. success( ) ,
173- "mkfs.ext2 failed: {}" ,
174- String :: from_utf8_lossy( & mkfs_output . stderr) ,
192+ mksquashfs_output . status. success( ) ,
193+ "mksquashfs failed: {}" ,
194+ String :: from_utf8_lossy( & mksquashfs_output . stderr) ,
175195 ) ;
176196
177- // Mount the image via fuse2fs
178- let mount_output = Command :: new ( "fuse2fs " )
197+ // Mount the squashfs image via squashfuse (read-only)
198+ let mount_output = Command :: new ( "squashfuse " )
179199 . with_arg ( & image_path)
180200 . with_arg ( & mount_point)
181- . with_args ( [ "-o" , "rw,fakeroot" ] )
182201 . with_stdout ( Stdio :: piped ( ) )
183202 . with_stderr ( Stdio :: piped ( ) )
184203 . output ( )
185- . expect ( "run fuse2fs " ) ;
204+ . expect ( "run squashfuse " ) ;
186205 assert ! (
187206 mount_output. status. success( ) ,
188- "fuse2fs mount failed: {}" ,
207+ "squashfuse mount failed: {}" ,
189208 String :: from_utf8_lossy( & mount_output. stderr) ,
190209 ) ;
191210
192211 // Small delay to let FUSE settle
193212 thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
194213
195- // Write a file on the mounted (different) filesystem
196- let inside_content = "B" . repeat ( 2000 ) ;
197- let write_result = fs:: write ( mount_point. join ( "inside.txt" ) , & inside_content) ;
198-
199214 // Ensure we unmount even if assertions fail
200215 let test_result = std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || {
201- write_result. expect ( "write inside.txt on mounted filesystem" ) ;
202-
203216 // Run pdu WITHOUT -x — should see both files
204217 let without_x = Command :: new ( pdu)
205218 . with_args ( [ "--bytes-format=plain" ] )
0 commit comments