On Sun, Oct 27, 2024 at 3:02 PM Zhaoming Luo <zhaoming1...@qq.com> wrote:
> I didn't develop it in-tree because I haven't figure out how to write a
> Makefile for an in-tree server. I haven't found a reference or document
> for me to learn writing it. Any recommendations? Maybe I should write
> one based on the Makefile of lwip and add something in the top-level
> Makefile?

There is some brief documentation in the top-level Makeconf (starting
with "Directory makefiles should set"...), and yes, you probably need
to add your directory to prog-subdirs in the top-level Makefile. I'm
not super familiar with the build system myself, but it is somewhat
approachable if you know your way around Make.

> >> /* 9 RTC_RD_TIME */
> >> routine pioctl_rtc_rd_time (
> >>       reqport: io_t;
> >>       inout time: tm_t);
> > Shouldn't this be 'out' rather than 'inout'?
> I think my idea was that time should be a pointer. I think there may be
> two method to correct it now: 1) let time be a pointer (tm_p_t?) 2)
> using 'out' rather than 'inout'.
> Maybe 1) is better? Because the message length will be shorter.

You mention message length, which tells me that you understand that
this is about messaging; good. Let me spell it out anyway: every MIG
routine defines two Mach messages: a request message (from a client to
a server) and a reply message (sent by the server back to the client).
The request message contains 'in' parameter values ('in' being the
implicit default), the reply message contains 'out' parameter values,
and 'inout' parameter values are contained in both.

As for tm_p_t: if you pass an actual pointer (i.e. a memory address)
through MIG, the server will receive just that, a memory address in
the client's address space. Which will be completely useless to it, of
course. You need to actually place the data you want to pass into the
message, by value. In this case the data (rtc_time_t) flows from the
server to the client, so it should be an out parameter in the routine,
so it gets placed in the reply message. Whether the parameter of the
generated C function will be a pointer or not is secondary, getting
the actual thing passed over IPC correctly should be the first
concern.

Next, the type you define for a parameter in MIG does not correspond
100% exactly to the type the generated C function will have; for
example MIG will add a level of indirection (a pointer-to) for an out
or inout parameter; so if you have

routine get_foo(port: mach_port_t; out foo: int);

the C signature will be

kern_return_t get_foo(mach_port_t port, int *foo);

And so for

routine pioctl_rtc_rd_time (
        reqport: io_t;
        out time: rtc_time_t);

you'd get

kern_return_t pioctl_rtc_rd_time(io_t reqport, rtc_time_t *time);

on the C side, which is what you want. The request message will
contain nothing, and the reply message will contain an rtc_time_t, by
value.

But then: the generated C pioctl_rtc_rd_time () function is not how
glibc will actually invoke your RPC when someone calls ioctl(...,
RTC_RD_TIME, ...). If it was done that way, glibc would have to know,
in advance, all the possible ioctls, and there'd be a giant switch
statement inside ioctl () implementation that would pick the right one
and send the appropriate RPC. That'd be one way to do it, for sure,
but that would not be extensible with out-of-tree ioctls (which is
incidentally what you're trying to do).

So the way this is actually done is clever, if obscure. The message id
of the RPC, along with its signature, is encoded right into the ioctl
number (using the _IOC () macro or one of its wrappers). The ioctl ()
implementation in glibc then picks apart the ioctl number, constructs
the request message according to the signature, sends it off, waits
for a reply from the server, and parses the reply according to the
expected reply signature (also encoded in the ioctl number).

The details are complicated and I won't pretend to fully understand
them myself, luckily you don't have to either; the ioctl ()
implementation exists and works. But the important point is that on
the client side, glibc doesn't use the generated C function to invoke
the routine, and instead does things based on the ioctl number, so
it's crucial that the definition of RTC_RD_TIME constant matches the
signature of the MIG routine.

On Linux, RTC_RD_TIME is defined as _IOR('p', 0x09, struct rtc_time),
and IIUC you plan to keep the same definition. _IOR (..., ..., type)
means an RPC that has a single out-parameter of the given type, so
that's what your MIG routine should be defined as. You'll also have to
define _IOT_rtc_time in a C header (rtc.h) next to the struct rtc_time
definition, using the _IOT macro.

Please tell me if this all makes sense, and if I need to rephrase /
expand on something.

> >> /* 10 RTC_SET_TIME */
> >> routine pioctl_rtc_set_time (
> >>       reqport: io_t;
> >>       time: tm_t);
> > Do I understand it right that this would be more privileged than
> > reading the time?
> Indeed[1]. How to make it more privileged. I didn't find information
> about it in [2][3] by searching 'privilged'.

Well for one thing you misspelled it :)

I see that the man page for Linux says that the caller must have the
CAP_SYS_TIME capability. We don't have those kinds of capabilities on
the Hurd, so there are two things we could do, either:

1. require the caller to be root, i.e. for the device node to have
been opened by UID 0;

2. just require the caller to have the device node opened for writing,
and then rely on the Unix file access permissions mechanism to control
who can do that (e.g. if it's owned by root and the mode is 0644, only
root can open it for writing, but anyone can read).

The second option sounds better to me, but perhaps Linux implements a
different semantic for a good reason? What do other kernels do?

Sergey

Reply via email to