Fugu
view release on metacpan or search on metacpan
lib/Fugu/Control.pod view on Meta::CPAN
The server registers with a L<Fugu::EventLoop> and answers commands
from process state.
The client connects, sends one request, and reads the reply.
The payload is JSON, and the transport is the imsg(3) framing of
L<Fugu::Imsg>.
Everything that arrives on the socket is untrusted.
An unknown command, a payload that is not JSON, and a frame over the
limit all give an error reply.
None of them ends the daemon.
=head2 The server
=over 4
=item C<new(%args)>
Make a server.
The method opens nothing.
C<path> is the socket, and it is necessary.
C<log> is the logger, and it defaults to the process default of
L<Fugu::Log>.
=item C<register($command, $code)>
Add a command.
The code gets the decoded arguments hash reference and returns the
reply, which must encode as JSON.
A handler that dies gives the caller an error reply.
=item C<listen(%args)>
Bind the socket and start accepting.
The listener and every accepted connection register with the event
loop that C<loop> names.
That argument is necessary, and the method dies without it.
C<mode> is the socket mode, as an integer, with the default C<0600>.
C<group> is the socket group, as a name or as a numeric group id,
with no default.
A group name resolves with C<getgrnam>.
An unresolvable name, a failed chown(2) and a failed chmod(2) are
each a recoverable failure: the method takes the socket down, sets
C<error()>, and returns C<undef>, so a half-built socket never
accepts a connection.
The socket never accepts a wider set of users than the final set.
With no C<group> the mode holds from birth, through a umask(2) guard.
With a C<group> the socket binds under the owner bits of the final
mode, takes the group with chown(2), and widens to C<mode> with
chmod(2) last.
A daemon drops privileges first, and it calls C<listen> after.
A process can chgrp its own file to a group that it belongs to, so
the group form needs no root.
=item C<accept_one($loop)>
Take one connection and register it as a read handler on the loop.
Where C<peer_supported()> is true, the method reads the peer
credentials of the connection once, with getsockopt(2) and
C<SO_PEERCRED>.
A credential read that fails closes the connection at once, and the
server logs the reason at the error level: a control socket that
cannot name its peer must not answer.
=item C<peer()>
The credentials of the connection that the server is answering now,
as a hash reference with C<uid>, C<gid> and C<pid>: the effective
user id, the effective group id, and the process id of the peer.
A handler that needs the operator identity calls it, and a handler
that does not need it ignores it.
The method returns C<undef> outside a handler call, and C<undef>
where C<peer_supported()> is false.
It holds no policy and reports three numbers.
The group of the socket is the coarse gate, and the handler is the
fine gate.
On OpenBSD the read returns a C<struct sockpeercred>, which holds the
user id, then the group id, then the process id.
The field order differs from the Linux C<struct ucred>, which holds
the process id first, and one module must not carry two field orders.
The method therefore reads the credentials on OpenBSD only, and it
reports "not supported" everywhere else, through C<peer_supported()>.
=item C<peer_supported()>
A class method.
It returns true only where the platform reports peer credentials in
the C<struct sockpeercred> order.
A caller or a test uses it to tell "not supported" from "the read
failed".
=item C<shutdown(%args)>
Close every connection, close the listener, and remove the socket.
A socket left behind names a daemon that is not there.
=item C<path()>
The socket path.
=item C<error()>
The most recent failure.
=back
=head2 The client
=over 4
=item C<new(%args)>
Make a client.
C<path> is necessary; C<timeout> is the per-frame deadline and
defaults to 5 seconds.
=item C<request($command, $args)>
Send one command and return the decoded reply, or C<undef> with the
reason in C<error()>.
The method connects if it is not connected.
=item C<connect()>, C<disconnect()>
Open and close the connection by hand.
Both are idempotent.
=item C<socket_absent()>
Report if the most recent failure was an absent socket, and not a
refusal.
A tool says "the daemon is not running" for the first and "the daemon
said no" for the second.
A tool that cannot tell them apart reports the wrong thing to an
operator half the time.
A socket inside a directory that the caller may not search is not an
absent socket.
C<connect()> tells the two apart and reports the permission, because
an operator who reads "not running" about a daemon that is running
looks in the wrong place.
=item C<error()>
The most recent failure.
=back
( run in 0.872 second using v1.01-cache-2.11-cpan-007c89162af )