@@ -1246,6 +1246,70 @@ mod tests {
12461246 assert ! ( install_payload( & src, & dest) . is_err( ) ) ;
12471247 }
12481248
1249+ #[ test]
1250+ #[ serial]
1251+ #[ cfg( unix) ]
1252+ fn install_payload_retries_with_sudo_on_permission_denied ( ) {
1253+ let ( _sudo_dir, _path_guard) = setup_fake_sudo ( ) ;
1254+ let temp = tempfile:: tempdir ( ) . expect ( "temp dir" ) ;
1255+ let src = temp. path ( ) . join ( "src" ) ;
1256+ let dest = temp. path ( ) . join ( "dest" ) ;
1257+ fs:: write ( & src, b"hello" ) . expect ( "write" ) ;
1258+ fs:: write ( & dest, b"old" ) . expect ( "write dest" ) ;
1259+
1260+ {
1261+ use std:: os:: unix:: fs:: PermissionsExt ;
1262+ let mut perms = fs:: metadata ( & dest) . expect ( "stat" ) . permissions ( ) ;
1263+ perms. set_mode ( 0o444 ) ;
1264+ fs:: set_permissions ( & dest, perms) . expect ( "chmod" ) ;
1265+ }
1266+
1267+ install_payload ( & src, & dest) . expect ( "install payload" ) ;
1268+ assert_eq ! ( fs:: read( & dest) . expect( "read" ) , b"hello" ) ;
1269+ }
1270+
1271+ #[ test]
1272+ #[ serial]
1273+ #[ cfg( unix) ]
1274+ fn install_with_sudo_uses_fake_sudo ( ) {
1275+ let ( _sudo_dir, _path_guard) = setup_fake_sudo ( ) ;
1276+ let temp = tempfile:: tempdir ( ) . expect ( "temp dir" ) ;
1277+ let src = temp. path ( ) . join ( "src" ) ;
1278+ let dest = temp. path ( ) . join ( "dest" ) ;
1279+ fs:: write ( & src, b"hello" ) . expect ( "write" ) ;
1280+
1281+ install_with_sudo ( & src, & dest) . expect ( "install with sudo" ) ;
1282+ assert_eq ! ( fs:: read( & dest) . expect( "read" ) , b"hello" ) ;
1283+ }
1284+
1285+ #[ test]
1286+ #[ serial]
1287+ #[ cfg( unix) ]
1288+ fn ensure_install_dir_uses_sudo_on_permission_denied ( ) {
1289+ let ( _sudo_dir, _path_guard) = setup_fake_sudo ( ) ;
1290+ let temp = tempfile:: tempdir ( ) . expect ( "temp dir" ) ;
1291+ let protected = temp. path ( ) . join ( "protected" ) ;
1292+ fs:: create_dir_all ( & protected) . expect ( "mkdir" ) ;
1293+
1294+ {
1295+ use std:: os:: unix:: fs:: PermissionsExt ;
1296+ let mut perms = fs:: metadata ( & protected) . expect ( "stat" ) . permissions ( ) ;
1297+ perms. set_mode ( 0o500 ) ;
1298+ fs:: set_permissions ( & protected, perms) . expect ( "chmod" ) ;
1299+ }
1300+
1301+ let install_dir = protected. join ( "bin" ) ;
1302+ ensure_install_dir ( & install_dir) . expect ( "ensure install dir" ) ;
1303+ assert ! ( install_dir. exists( ) ) ;
1304+
1305+ {
1306+ use std:: os:: unix:: fs:: PermissionsExt ;
1307+ let mut perms = fs:: metadata ( & protected) . expect ( "stat" ) . permissions ( ) ;
1308+ perms. set_mode ( 0o700 ) ;
1309+ fs:: set_permissions ( & protected, perms) . expect ( "chmod" ) ;
1310+ }
1311+ }
1312+
12491313 #[ test]
12501314 fn is_permission_denied_detects_nested_error ( ) {
12511315 let err = anyhow:: Error :: new ( io:: Error :: new ( io:: ErrorKind :: PermissionDenied , "nope" ) ) ;
@@ -1767,6 +1831,50 @@ mod tests {
17671831 }
17681832 }
17691833
1834+ #[ cfg( unix) ]
1835+ fn setup_fake_sudo ( ) -> ( tempfile:: TempDir , EnvGuard ) {
1836+ use std:: os:: unix:: fs:: PermissionsExt ;
1837+
1838+ let dir = tempfile:: tempdir ( ) . expect ( "temp dir" ) ;
1839+ let sudo_path = dir. path ( ) . join ( "sudo" ) ;
1840+ let script = r#"#!/bin/sh
1841+ set -e
1842+ cmd="$1"
1843+ shift
1844+
1845+ case "$cmd" in
1846+ mv)
1847+ exec /bin/mv "$@"
1848+ ;;
1849+ mkdir)
1850+ last=""
1851+ for arg in "$@"; do
1852+ last="$arg"
1853+ done
1854+ if [ -n "$last" ]; then
1855+ parent=$(dirname "$last")
1856+ chmod u+w "$parent" 2>/dev/null || true
1857+ fi
1858+ exec /bin/mkdir "$@"
1859+ ;;
1860+ *)
1861+ echo "unexpected sudo command: $cmd" >&2
1862+ exit 1
1863+ ;;
1864+ esac
1865+ "# ;
1866+
1867+ fs:: write ( & sudo_path, script) . expect ( "write fake sudo" ) ;
1868+ let mut perms = fs:: metadata ( & sudo_path) . expect ( "stat" ) . permissions ( ) ;
1869+ perms. set_mode ( 0o755 ) ;
1870+ fs:: set_permissions ( & sudo_path, perms) . expect ( "chmod" ) ;
1871+
1872+ let existing = env:: var ( "PATH" ) . unwrap_or_default ( ) ;
1873+ let path = format ! ( "{}:{}" , dir. path( ) . display( ) , existing) ;
1874+ let guard = EnvGuard :: set ( "PATH" , path) ;
1875+ ( dir, guard)
1876+ }
1877+
17701878 struct TestServer {
17711879 base : String ,
17721880 handle : Option < thread:: JoinHandle < ( ) > > ,
0 commit comments