Skip to content

Commit

Permalink
Attempt to support mounting with default arguments under new macOS
Browse files Browse the repository at this point in the history
This was caused by a change in macOS that causes the mount to block
looking for the statd program, unless explicitly mounting without locks.

We handle this by providing a stub statd that always returns fail.
  • Loading branch information
JordanMilne committed Nov 13, 2023
1 parent 5219f22 commit 8e41214
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 2 deletions.
1 change: 1 addition & 0 deletions compile_idl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ compile_idl rfc1813
compile_idl rfc1831
compile_idl rfc1833_portmapper
compile_idl rfc1833_rpcbind
compile_idl statd

echo "Done"
158 changes: 158 additions & 0 deletions shenaniganfs/generated/statd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Auto-generated from IDL file
import abc
import dataclasses
import typing
from dataclasses import dataclass

from shenaniganfs import rpchelp

TRUE = True
FALSE = False
stat_succ = 0
stat_fail = 1
SM_MAXSTRLEN = 1024
SM_PRIV_SIZE = 16


@dataclass
class SmName(rpchelp.Struct): # sm_name
mon_name: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.VAR, SM_MAXSTRLEN))


@dataclass
class MyId(rpchelp.Struct): # my_id
my_name: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.VAR, SM_MAXSTRLEN))
my_prog: int = rpchelp.rpc_field(rpchelp.r_int)
my_vers: int = rpchelp.rpc_field(rpchelp.r_int)
my_proc: int = rpchelp.rpc_field(rpchelp.r_int)


@dataclass
class MonId(rpchelp.Struct): # mon_id
mon_name: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.VAR, SM_MAXSTRLEN))
my_id: MyId = rpchelp.rpc_field(MyId)


@dataclass
class Mon(rpchelp.Struct): # mon
mon_id: MonId = rpchelp.rpc_field(MonId)
priv: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.FIXED, SM_PRIV_SIZE))


@dataclass
class StatChge(rpchelp.Struct): # stat_chge
mon_name: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.VAR, SM_MAXSTRLEN))
state: int = rpchelp.rpc_field(rpchelp.r_int)


@dataclass
class SmStat(rpchelp.Struct): # sm_stat
state: int = rpchelp.rpc_field(rpchelp.r_int)


class Res(rpchelp.Enum): # res
stat_succ = 0
stat_fail = 1


@dataclass
class SmStatRes(rpchelp.Struct): # sm_stat_res
res_stat: typing.Union[Res, int] = rpchelp.rpc_field(Res)
state: int = rpchelp.rpc_field(rpchelp.r_int)


@dataclass
class Status(rpchelp.Struct): # status
mon_name: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.VAR, SM_MAXSTRLEN))
state: int = rpchelp.rpc_field(rpchelp.r_int)
priv: bytes = rpchelp.rpc_field(rpchelp.Opaque(rpchelp.LengthType.FIXED, SM_PRIV_SIZE))


from shenaniganfs import client, transport


class SM_PROG_1_SERVER(transport.Prog):
prog = 100024
vers = 1
procs = {
0: rpchelp.Proc('NULL', rpchelp.r_void, []),
1: rpchelp.Proc('STAT', SmStatRes, [SmName]),
2: rpchelp.Proc('MON', SmStatRes, [Mon]),
3: rpchelp.Proc('UNMON', SmStat, [MonId]),
4: rpchelp.Proc('UNMON_ALL', SmStat, [MyId]),
5: rpchelp.Proc('SIMU_CRASH', rpchelp.r_void, []),
6: rpchelp.Proc('NOTIFY', rpchelp.r_void, [StatChge]),
}

@abc.abstractmethod
async def NULL(self, call_ctx: transport.CallContext) \
-> transport.ProcRet[None]:
raise NotImplementedError()

@abc.abstractmethod
async def STAT(self, call_ctx: transport.CallContext, arg_0: SmName) \
-> transport.ProcRet[SmStatRes]:
raise NotImplementedError()

@abc.abstractmethod
async def MON(self, call_ctx: transport.CallContext, arg_0: Mon) \
-> transport.ProcRet[SmStatRes]:
raise NotImplementedError()

@abc.abstractmethod
async def UNMON(self, call_ctx: transport.CallContext, arg_0: MonId) \
-> transport.ProcRet[SmStat]:
raise NotImplementedError()

@abc.abstractmethod
async def UNMON_ALL(self, call_ctx: transport.CallContext, arg_0: MyId) \
-> transport.ProcRet[SmStat]:
raise NotImplementedError()

@abc.abstractmethod
async def SIMU_CRASH(self, call_ctx: transport.CallContext) \
-> transport.ProcRet[None]:
raise NotImplementedError()

@abc.abstractmethod
async def NOTIFY(self, call_ctx: transport.CallContext, arg_0: StatChge) \
-> transport.ProcRet[None]:
raise NotImplementedError()


class SM_PROG_1_CLIENT(client.BaseClient):
prog = 100024
vers = 1
procs = {
0: rpchelp.Proc('NULL', rpchelp.r_void, []),
1: rpchelp.Proc('STAT', SmStatRes, [SmName]),
2: rpchelp.Proc('MON', SmStatRes, [Mon]),
3: rpchelp.Proc('UNMON', SmStat, [MonId]),
4: rpchelp.Proc('UNMON_ALL', SmStat, [MyId]),
5: rpchelp.Proc('SIMU_CRASH', rpchelp.r_void, []),
6: rpchelp.Proc('NOTIFY', rpchelp.r_void, [StatChge]),
}

async def NULL(self) -> client.UnpackedRPCMsg[None]:
return await self.send_call(0, )

async def STAT(self, arg_0: SmName) -> client.UnpackedRPCMsg[SmStatRes]:
return await self.send_call(1, arg_0)

async def MON(self, arg_0: Mon) -> client.UnpackedRPCMsg[SmStatRes]:
return await self.send_call(2, arg_0)

async def UNMON(self, arg_0: MonId) -> client.UnpackedRPCMsg[SmStat]:
return await self.send_call(3, arg_0)

async def UNMON_ALL(self, arg_0: MyId) -> client.UnpackedRPCMsg[SmStat]:
return await self.send_call(4, arg_0)

async def SIMU_CRASH(self) -> client.UnpackedRPCMsg[None]:
return await self.send_call(5, )

async def NOTIFY(self, arg_0: StatChge) -> client.UnpackedRPCMsg[None]:
return await self.send_call(6, arg_0)


__all__ = ['SM_PROG_1_SERVER', 'SM_PROG_1_CLIENT', 'TRUE', 'FALSE', 'SM_MAXSTRLEN', 'SM_PRIV_SIZE', 'stat_succ', 'stat_fail', 'SmName', 'MyId', 'MonId', 'Mon', 'StatChge', 'SmStat', 'Res', 'SmStatRes', 'Status']
86 changes: 86 additions & 0 deletions shenaniganfs/idl/statd.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@

const SM_MAXSTRLEN = 1024;
const SM_PRIV_SIZE = 16;

struct sm_name {
string mon_name<SM_MAXSTRLEN>;
};

struct my_id {
string my_name<SM_MAXSTRLEN>; /* name of the site iniates the monitoring request*/
int my_prog; /* rpc program # of the requesting process */
int my_vers; /* rpc version # of the requesting process */
int my_proc; /* rpc procedure # of the requesting process */
};

struct mon_id {
string mon_name<SM_MAXSTRLEN>; /* name of the site to be monitored */
my_id my_id;
};


struct mon {
mon_id mon_id;
opaque priv[SM_PRIV_SIZE]; /* private information to store at monitor for requesting process */
};

struct stat_chge {
string mon_name<SM_MAXSTRLEN>; /* name of the site that had the state change */
int state;
};

/*
* state # of status monitor monitonically increases each time
* status of the site changes:
* an even number (>= 0) indicates the site is down and
* an odd number (> 0) indicates the site is up;
*/
struct sm_stat {
int state; /* state # of status monitor */
};

enum res {
stat_succ = 0, /* status monitor agrees to monitor */
stat_fail = 1 /* status monitor cannot monitor */
};

struct sm_stat_res {
res res_stat;
int state;
};

/*
* structure of the status message sent back by the status monitor
* when monitor site status changes
*/
struct status {
string mon_name<SM_MAXSTRLEN>;
int state;
opaque priv[SM_PRIV_SIZE]; /* stored private information */
};


program SM_PROG {
version SM_VERS {
/* res_stat = stat_succ if status monitor agrees to monitor */
/* res_stat = stat_fail if status monitor cannot monitor */
/* if res_stat == stat_succ, state = state number of site sm_name */
sm_stat_res SM_STAT(sm_name) = 1;

/* res_stat = stat_succ if status monitor agrees to monitor */
/* res_stat = stat_fail if status monitor cannot monitor */
/* stat consists of state number of local site */
sm_stat_res SM_MON(mon) = 2;

/* stat consists of state number of local site */
sm_stat SM_UNMON(mon_id) = 3;

/* stat consists of state number of local site */
sm_stat SM_UNMON_ALL(my_id) = 4;

void SM_SIMU_CRASH(void) = 5;

void SM_NOTIFY(stat_chge) = 6;

} = 1;
} = 100024;
2 changes: 2 additions & 0 deletions shenaniganfs/nfs_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from shenaniganfs.nfs3 import MountV3Service, NFSV3Service
from shenaniganfs.portmanager import PortManager, SimplePortMapper, SimpleRPCBind
from shenaniganfs.server import TCPTransportServer
from shenaniganfs.statd import StatDV1Server


async def serve_nfs(fs_manager: FileSystemManager, use_internal_rpcbind=True):
Expand All @@ -20,6 +21,7 @@ async def serve_nfs(fs_manager: FileSystemManager, use_internal_rpcbind=True):
transport_server.register_prog(NFSV2Service(fs_manager))
transport_server.register_prog(MountV3Service(fs_manager))
transport_server.register_prog(NFSV3Service(fs_manager))
transport_server.register_prog(StatDV1Server())
if use_internal_rpcbind:
transport_server.notify_port_manager(port_manager)
else:
Expand Down
36 changes: 36 additions & 0 deletions shenaniganfs/statd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
A statd stub so that new versions of macOS can connect without extra options,
see https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/nfs/nfs_vfsops.c#L4622-L4655
Previously mount on macOS was hanging unless an option was passed to mount without locks.
"""

from shenaniganfs import transport
from shenaniganfs.generated.statd import *


class StatDV1Server(SM_PROG_1_SERVER):
async def NULL(self, call_ctx: transport.CallContext) -> transport.ProcRet[None]:
pass

async def STAT(self, call_ctx: transport.CallContext, arg_0: SmName) -> transport.ProcRet[SmStatRes]:
# Just say fail no matter what
return SmStatRes(
res_stat=Res.stat_fail,
state=0,
)

async def MON(self, call_ctx: transport.CallContext, arg_0: Mon) -> transport.ProcRet[SmStatRes]:
raise NotImplementedError()

async def UNMON(self, call_ctx: transport.CallContext, arg_0: MonId) -> transport.ProcRet[SmStat]:
raise NotImplementedError()

async def UNMON_ALL(self, call_ctx: transport.CallContext, arg_0: MyId) -> transport.ProcRet[SmStat]:
raise NotImplementedError()

async def SIMU_CRASH(self, call_ctx: transport.CallContext) -> transport.ProcRet[None]:
raise NotImplementedError()

async def NOTIFY(self, call_ctx: transport.CallContext, arg_0: StatChge) -> transport.ProcRet[None]:
raise NotImplementedError()
10 changes: 8 additions & 2 deletions shenaniganfs/tools/rpcgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,15 @@ def _common_prefix(vals):
prefix = prefix[:last_match]
return prefix

def remove_common_prefix(self):
def remove_common_prefix(self, vers_ident: typing.Optional[str] = None):
if len(self.children) == 1:
# Only one child, can't check if there's a common prefix across children,
# just check if there's a version ident prefix instead
child = self.children[0]
if vers_ident and child.ident.startswith(vers_ident) and vers_ident != child.ident:
child.ident = child.ident[len(vers_ident):].lstrip("_")
return

prefix = self._common_prefix([p.ident for p in self.children])
if not prefix:
return
Expand Down Expand Up @@ -677,7 +683,7 @@ class Version(Node):
def __init__(self, ident, proc_defs, version_id):
Node.__init__(self)
self.ident = ident
proc_defs.remove_common_prefix()
proc_defs.remove_common_prefix(ident)
# All programs have an implicit NULL procedure
if not any(str(proc.proc_id) == "0" for proc in proc_defs.children):
void_spec = TypeSpec("void", False, True, False)
Expand Down

0 comments on commit 8e41214

Please sign in to comment.