Commit b1aae16b authored by Aurelio Colosimo's avatar Aurelio Colosimo Committed by Alessandro Rubini

arch-wrs: copy mini-rpc module from its git repository

mini-rpc is a copy of commit 4c87062d from
repository git://github.com/a-rubini/mini-rpc.gitSigned-off-by: Aurelio Colosimo's avatarAurelio Colosimo <aurelio@aureliocolosimo.it>
parent c232328f
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
CFLAGS = -Wall -ggdb -O2 -fno-strict-aliasing
LDFLAGS = -L. -lminipc -lm
# We need to support freestading environments: an embedded CPU that
# sits as a server on its own memory are and awaits commands
# The user can pass IPC_FREESTANDING=y
IPC_FREESTANDING ?= $(shell ./check-freestanding $(CC))
IPC_HOSTED ?= $(shell ./check-freestanding -n $(CC))
# Hosted is the opposite of freestanding, and cflags change too
ifeq ($(IPC_FREESTANDING),y)
IPC_HOSTED = n
CFLAGS += -ffreestanding -Iarch-$(ARCH)
else
IPC_HOSTED = y
endif
OBJ-$(IPC_HOSTED) = minipc-core.o minipc-server.o minipc-client.o
OBJ-$(IPC_FREESTANDING) = minipc-mem-server.o
LIB = libminipc.a
# export these to the examples (but if you make there IPC_HOSTED is default
export IPC_FREESTANDING IPC_HOSTED
all: $(LIB)
$(MAKE) -C examples
$(LIB): $(OBJ-y)
$(AR) r $@ $^
# the default puts LDFLAGS too early. Bah...
%: %.c
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
# This is stupid, it won't apply the first time, but, well... it works
$(wildcard *.o): $(wildcard *.h)
clean:
rm -f *.o *~ $(LIB)
$(MAKE) -C examples clean
install:
@echo "We have no install rule by now"
All documentation is now under the doc/ directory.
The input file is called doc/mini-ipc.in (it is readable by itself).
To have all usual output formats you can run "make" withing ./doc
but you need TexInfo installed.
#ifndef __ARCH_LM32_STDINT_H__
#define __ARCH_LM32_STDINT_H__
/*
* We miss a stdint.h in our compiler, so provide some types here,
* knowing the CPU is 32-bits and uses LP32 model
*/
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed long int32_t;
#endif /* __ARCH_LM32_STDINT_H__ */
#!/bin/sh
# I used to just check __STDC_HOSTED__ without special CFLAGS, but now I
# want to force freestanding compilation on otherwise hosted processors.
# Thus, the user can set IPC_FREESTANDING=y . If unset, this gets called.
# Accept "-n" to invert the result (select second field, not first)
if [ "x$1" = "x-n" ]; then select=2; shift; else select=1; fi
CC=$1
if [ "x$CC" = "x" ]; then
echo "$0: pass the compiler path (\$CC) as argument" >& 2
exit 1
fi
# Check the compiler: if it has no matches is prints the unadorned string
if [ "$($CC -print-file-name=libc.so)" = "libc.so" ]; then
res=y,n
else
res=n,y
fi
# If passed from the user, override the autodetected value
if [ "$IPC_FREESTANDING" = "y" ]; then
res=y,n
fi
echo $res | cut -d, -f $select
*~
*.aux
*.cp
*.fn
*.html
*.info
*.ky
*.log
*.pdf
*.pg
*.texi
*.toc
*.tp
*.txt
*.vr
#
# Makefile for the documentation directory
#
# Copyright 1994,2000,2010,2011 Alessandro Rubini <rubini@linux.it>
#
#################
# There is not basenames here, all *.in are considered input
INPUT = $(wildcard *.in)
TEXI = $(INPUT:.in=.texi)
INFO = $(INPUT:.in=.info)
HTML = $(INPUT:.in=.html)
TXT = $(INPUT:.in=.txt)
PDF = $(INPUT:.in=.pdf)
ALL = $(INFO) $(HTML) $(TXT) $(PDF)
MAKEINFO ?= makeinfo
%.texi: %.in
@rm -f $@
sed -f ./infofilter $< > $@
emacs -batch --no-site-file -l fixinfo $@
chmod -w $@
%.pdf: %.texi
texi2pdf --batch $<
%.info: %.texi
$(MAKEINFO) $< -o $@
%.html: %.texi
$(MAKEINFO) --html --no-split -o $@ $<
%.txt: %.texi
$(MAKEINFO) --no-headers $< > $@
##############################################
.PHONY: all images check terse clean install
.INTERMEDIATE: $(TEXI)
all: images $(ALL)
$(MAKE) terse
images::
if [ -d images ]; then $(MAKE) -C images || exit 1; fi
check: _err.ps
gs -sDEVICE=linux -r320x200x16 $<
terse:
for n in cp fn ky pg toc tp vr aux log; do rm -f *.$$n; done
rm -f *~
clean: terse
rm -f $(ALL) $(TEXI)
install:
;; use:
;; emacs -batch -l ./fixinfo.el <file>
;; or, better:
;; emacs -batch --no-site-file -l ./fixinfo.el <file>
(defun fixinfo (file)
(find-file-other-window file)
(message (concat "Maxing texinfo tree in " file))
(texinfo-all-menus-update)
(texinfo-every-node-update)
(save-buffer)
(kill-buffer (current-buffer))
)
;; loop over command line arguments
(mapcar 'fixinfo command-line-args-left)
(kill-emacs)
#! /usr/bin/sed -f
# allow "%" as a comment char, but only at the beginning of the line
s/^%/@c /
#s/[^\\]%.*$//
s/^\\%/%/
#preserve blanks and braces in @example blocks
/@example/,/@end example/ s/{/@{/g
/@example/,/@end example/ s/}/@}/g
/@example/,/@end example/ p
/@example/,/@end example/ d
/@smallexample/,/@end smallexample/ s/{/@{/g
/@smallexample/,/@end smallexample/ s/}/@}/g
/@smallexample/,/@end smallexample/ p
/@smallexample/,/@end smallexample/ d
# remove leading blanks
s/^[ ]*//
\input texinfo @c -*-texinfo-*-
%
% mini-ipc.in - main file for the documentation
%
%%%%
%------------------------------------------------------------------------------
%
% NOTE FOR THE UNAWARE USER
% =========================
%
% This file is a texinfo source. It isn't the binary file of some strange
% editor of mine. If you want ASCII, you should "make mini-ipc.txt".
%
%------------------------------------------------------------------------------
%
% This is not a conventional info file...
% I use three extra features:
% - The '%' as a comment marker, if at beginning of line ("\%" -> "%")
% - leading blanks are allowed (this is something I can't live without)
% - braces are automatically escaped when they appear in example blocks
%
@comment %**start of header
@documentlanguage en
@documentencoding ISO-8859-1
@setfilename mini-ipc.info
@settitle mini-ipc
@iftex
@afourpaper
@end iftex
@paragraphindent none
@comment %**end of header
@setchapternewpage off
@set update-month March 2012
@finalout
@titlepage
@title mini-ipc
@subtitle @value{update-month}
@subtitle A misnamed minimal RPC library
@author Alessandro Rubini (@code{rubini@@gnudd.com})
@end titlepage
@headings single
@c ##########################################################################
@iftex
@contents
@end iftex
@c ##########################################################################
@node Top
@top Introduction
This is a mini remote procedure call, but I called it IPC in error.
The short name throughout the project is @code{minipc}, used both as
a prefix for exported symbols and header/library name.
It is currently being suggested as a replacement for what we have in the
@i{White Rabbit} system, because the way it is there is not portable
(assembly code and knowledge of the ABI is required for each
platform). Nonetheless, I borrowed quite some ideas from the
implementation by Tomasz Wlostowski, who must definitely be
considered co-author and my inspiring muse.
This version also supports a @i{freestanding} mode, whereas a
memory-mapped embedded CPU can act as a server for a process
on the main CPU that acts as a client. The library offers the same
API as the normal hosted version, but only for server operation.
@menu
* Basic Ideas::
* The Main Data Structure::
* The Client::
* The Server::
* Diagnostics::
* The Communication Protocol::
* Transport Mechanisms::
* Freestanding Operation::
* Examples::
* Bugs::
@end menu
@c ##########################################################################
@node Basic Ideas
@chapter Basic Ideas
The package is meant to be as simple as possible, while still having
some abstraction to be useable on different environments than the
@i{White Rabbit Switch}. There are other RPC environments, but they are
usually oversized for our needs. Besides, I personally love small
things that I can use as completely-understandable examples when
teaching.
With this library, a process can be a server, or a client, or both.
A server program opens a Unix domain socket (or other communication channels)
and then accepts client programs. Thus,
performing a server action means either accepting a new client or
performing a client request. To avoid requiring the library
to be the only I/O of the process, I export the @i{fd_set} structure
that lists the file descriptors that are active in the server (I don't directly
support @code{poll(2)}, only @code{select(2)}).
A client program only works on a single file descriptor, so the library
provides a function to retrieve the fd (thus you can use poll yourself).
A function being exported is defined by a data structure, which lists both
the argument list and the return value. Data types supported are
integers, double-precision floating point numbers,
strings and generic structures. We might (or might
not) export the thing over TCP and add endian conversion, but at this
point the data type is really just informative.
An argument list is described by a zero-terminated array of 32-bit
integers, which specifies both the type and the size of the argument
being passed. The return value is described by a single 32-bit
integer specifying type and size. The @code{minipc.h} header provides
macros to build the actual values.
@c ##########################################################################
@node The Main Data Structure
@chapter The Main Data Structure
In order to communicate, client and server must agree on the functions
being called remotely. In the library, a function is identified by a
string name, 18 bytes at most.
Following is the "procedure definition" structure, from @code{minipc.h}.
Its length (on 32-bit machines) is 36 bytes if the function takes
no arguments, each argument is described by 4 more bytes.
@example
struct minipc_pd {
minipc_f *f; /* pointer to the function */
char name[20]; /* name of the function */
uint32_t flags;
uint32_t retval; /* type of return value */
uint32_t args[]; /* null-terminated list of arguments */
};
@end example
Client and server are expected to share such data structure, or at least
the macros used to build the data structure. The fields
@i{name}, @i{retval} and @i{args}, must match. The function pointer
is only used in the server. No
user-accessible flags are currently there.
The "args" array must be terminated by a "MINIPC_ARG_END" value, which
is guaranteed to be bitwise zero. The other arguments encode type and
size, even if some types (like integers, floats) always have the same
size. The header offers the following macros to encode and decode
the arguments (and retval). Please look at the examples to better
get the feeling of the code.
@table @code
@item MINIPC_ARG_ENCODE(atype, type)
The macro returns a 32-bit integer that includes the argument
type (see below for a list of @i{atypes}) and the size of @i{type}
(using @code{sizeof} internally). The size must be 64kB at most.
@item MINIPC_GET_ATYPE(word)
@itemx MINIPC_GET_ASIZE(word)
The functions return the type and size of an argument.
@item MINIPC_ARG_END
This is zero, the terminator for the argument list.
@end table
The supported types are the values of this enumeration (the error
special type is used internally by the library):
@example
enum minipc_at {
MINIPC_ATYPE_ERROR = 0xffff,
MINIPC_ATYPE_NONE = 0, /* used as terminator */
MINIPC_ATYPE_INT = 1,
MINIPC_ATYPE_INT64,
MINIPC_ATYPE_DOUBLE, /* float is promoted to double */
MINIPC_ATYPE_STRING, /* size is strlen() each time */
MINIPC_ATYPE_STRUCT
};
@end example
@c ##########################################################################
@node The Client
@chapter The Client
A process acting as a @i{minipc} client creates a client channel. The
@code{minipc_ch} structure includes a file descriptor that you can
retrieve. The following functions are thus available:
@example
struct minipc_ch *minipc_client_create(const char *name, int flags);
int minipc_fileno(struct minipc_ch *ch);
@end example
The name used to create a channel is a filename including no
path separators and no colon (@code{:}) character.
The library currently only supports Unix domain sockets, and the
filename is used to connect to a socket in the @code{MINIPC_BASE_PATH}
directory (default value: @code{/tmp/.minipc}). Other channels will
use colon-separated names, for example @code{TCP:@i{host}:@i{port}}
for TCP channels.
With the fileno a client may poll for reading, although this is not
usually needed because the server will only send reply packets:
exactly one reply packet for each request packet.
A client can make a request by calling this function:
@example
int minipc_call(struct minipc_ch *ch, int millisec_timeout,
const struct minipc_pd *pd, void *ret, ...);
@end example
The arguments are passed as a list of values (structures and strings
are passed by pointer); the return pointer is filled with the returned
data item (please look at the examples for actual use patterns).
The @code{minipc_call} function picks data from the variable arguments
according to the argument types set forth in the "pd" procedure
definition. Similarly, the return value is stored where pointed by
ret, according to pd->retval. Note that the return value must be at least
4 bytes long.
The @code{minipc_call} returns 0 on success and -1 on failure, setting
@code{errno} accordingly.. If @i{send}, @i{poll} or @i{recv} return
an error, the original @code{errno} is preserved. If @i{poll} times
out (using the provided timeout) @code{errno} is set to
@code{ETIMEDOUT}. If the server returns an error (using
@code{MINIPC_ATYPE_ERROR}), the local errno is set to @code{EREMOTEIO}
and the remote one is saved using the retval pointer (which is
guaranteer to point to an int-sized or bigger area).
To close the connection, a client can call
@example
int minipc_close(struct minipc_ch *ch);
@end example
@c ##########################################################################
@node The Server
@chapter The Server
The server must open a server channel and process requests as needed.
The library exports the following functions for server use:
@example
struct minipc_ch *minipc_server_create(const char *name, int flags);
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd);
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd);
int minipc_server_action(struct minipc_ch *ch, int timeoutms);
int minipc_server_get_fdset(struct minipc_ch *ch, fd_set *setptr);
@end example
The @i{export} and @i{unexport} functions should be self-explicative. Note
that the exported function name is part of the @code{pd} structure.
The @code{pd} passed to @i{minipc_unexport} must be the
same as the one passed to @i{minipc_export}, not just a pointer to a
data structure with the same contents.
The function @i{minipc_server_action} either accepts a new client or
handles all pending client requests. For every packet received from a
client, the function send back a reply packet. So, even if communication is
based on @code{SOCK_STREAM}, packet boundaries are preserved by using only
synchronous communication.
The @i{minipc_get_fdset} function returns an @i{fdset} structure, so the caller
may use select() in the main loop by augmenting the minipc @i{fdset}
with its own.
Internally, the server action calls @i{poll} by itself (with the
specified timeout). Thus, if you already used @i{select} you can pass
0 as @code{timeoutms} in minipc_server_action.
The header uses a @code{typedef} for exported functions, to ease their
definition:
@example
typedef int (minipc_f)(const struct minipc_pd *,
void *retval, void *args);
@end example
An exported function receives a pointer to its own description, a
pointer to the data being returned and a pointer to
the unmarshalled arguments (again, please look at the examples)
The function itself should pick up data from the pointers. Such
pointers are usually directed to the packet buffer, so you should not
change the arguments themselves before calling the real function with
the ABI of the current CPU.
For example, the code exporting @code{sqrt} looks like the following:
@example
extern double sqrt(double x);
int export_sqrt(const struct minipc_pd *pd, uint32_t *args, void *ret)
{
double in, out;
in = *(double *)args;
out = sqrt(in);
*(double *)retval = out;
return 0;
}
struct minipc_pd sqrt_pd = {
.f = export_sqrt,
.id.s = "sqrt",
.retval = MINIPC_ARG_ENCODE(MINIPC_AT_DOUBLE, double),
.args = {
MINIPC_ARG_ENCODE(MINIPC_AT_DOUBLE, double),
MINIPC_ARG_END,
},
}
@end example
Please note that while using doubles or integers is easy, unmarshalling
a string from the args array is not trivial. The library
offers a generic unmarshall function:
@example
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype);
@end example
The function receives the current pointer to an argument and the
current type. The function offers a portable solution to the
problem of strings or structure whose size is not a multiple of
4 bytes.
This is an example use of the function:
@example
int export_strcat(const struct minipc_pd *pd, uint32_t *args, void *ret)
{
char *s1, char *s2, char sout[256];
s1 = (char *)args;
args = minipc_get_next_arg(args, pd->args[0]);
s2 = (char *)args;
strncpy(sout, s1, sizeof(sout));
strncat(sout, s2, sizeof(sout) - strlen(sout));
strncpy(ret, sout, MINIPC_MAX_REPLY));
return 0;
}
@end example
The client that calls a server exporting @i{sqrt} (as shown above)
will do it in the following way. The code assumes
@code{struct minipc_ch *client} and @code{struct minipc_pd pd_sqrt}
are valid variables:
@example
double operand;
double result;
int error;
error = minipc_call(client, &pd_sqrt, &result, operand);
if (error < 0) { ... }
@end example
@c ##########################################################################
@node Diagnostics
@chapter Diagnostics
A @code{minipc_ch} channel may have an associated log file (you can
use stderr or whatever). This following function sets the log file:
@example
int minipc_set_logfile(struct minipc_ch *ch, FILE *logf);
@end example
You can set the logfile to NULL (default at channel creation) if you
want to stop sending diagnostics. The function does not open or close
the file, as otherwise it wouldn't be possible to use stderr, so if
you want to open a log file on disk, it's your duty to do so; in this
case you possibly want to make it unbuffered, since the library only
calls @i{fprintf} on it, with no explicit @i{fflush}.
@c ##########################################################################
@node The Communication Protocol
@chapter The Communication Protocol
The protocol itself is an internal detail, but worth knowing in case a
bug is discovered. Relevant structures are defined in the internal header
@code{minipc-int.h}.
Request packets are sent as @code{struct mpc_req_packet}, which
includes the following items:
@itemize @bullet
@item 20 bytes for the function name, copied from "pd" structure
@item an array of 32-bit integers for the arguments.
@end itemize
Each argument passed to the remote function is stored into one or
more such 32-bit integers, in host byte order. Note that the
actual size of the argument is written in the "pd" args list.
Marshalling is performed by minipc_call() with @i{varargs} evaluation.
Reply packets are sent as @code{struct mpc_rep_packet}", which has two fields:
@itemize @bullet
@item @code{uint32_t type}
@item @code{uint8_t val[]}
@end itemize
The type is checked to be the same as what is defined as "ret" in the
@code{pd} structure. The @code{val} array is a plain byte array that
is copied to the @code{ret} pointer passed to minipc_call (like
@i{result} in the sqrt example above). When @code{type} is an error
value (i.e. @code{MINIPC_ATYPE_ERROR}) the payload is a 4-byte errno
value. Note that @code{type} is not passed back to the caller of
@i{minipc_call}, because errors are identified by a negative return value.
@c ##########################################################################
@node Transport Mechanisms
@chapter Transport Mechanisms
The @i{name} argument used in either @i{minipc_server_create} or
@i{minipc_client_create} is used to select both the back-end transport
and instance. At this point in time, the library supports the
following back-ends for communication:
@table @i
@item Unix sockets
The default transport for communication is Unix-domain sockets
(@code{PF_UNIX} a.k.a. @code{PF_FILE} or @code{PF_LOCAL}). The
@i{name} argument is used as socket name within the default
directory for @i{mini-ipc} sockets. A server can
handle many clients at a time in a single thread, based on @i{select}.
@item System-V Shared memory
If the @i{name} argument is of the form @code{shm:<id>}, then
the transport being used is a shared memory area. The @i{id}
is either a decimal oh hex number (with leading @code{0x}).
In this case a server can only
accept one client, because there is no check for concurrency. Please
note that the library doesn't enforce this, so more clients may
be used if you make your own locking. @i{mini-ipc} places its
data structures at the beginning of the shared memory area.
@item I/O memory
If the @i{name} argument is of the form @code{mem:<address>},
the library will open @i{/dev/mem} and use the specific address
as communication transport. The address must be a multiple
of page size and written in hex form. This is meant to communicate with
a coprocessor living on and FPGA (this is one of the use cases
in the @i{White Rabbit Switch}). Again, both server and client
operation is supported, but a server should accept only one client
at a time.
@end table
Since the library is based on file descriptors, the two memory-based
transports fork a process that polls the memory area to signal
events on the @code{minipc_ch} file descriptor. The default polling
interval is 10ms, but it can be changed by calling @i{minipc_set_poll}
before creating the channel:
@example
int minipc_set_poll(int usec);
@end example
The function returns the previous polling interval, in microseconds,
or -1 with @code{errno} set to @code{-EINVAL} if the argument is
negative or zero. Please note that the polling interval must be set
before creating the channel, because the parameter will be used by the
child process.
@c ##########################################################################
@node Freestanding Operation
@chapter Freestanding Operation
This version of the library can be compiled for a @i{freestanding}
system that acts as a server for a hosted system. Our use case is an
LM32 soft-core that runs some real-time activities on request of the
main ARM processor.
The @i{Makefiles} of the package identify a @i{freestanding}
compilation environment by the @code{CROSS_COMPILE} prefix: if the
compiler has a dynamic C library, it is assumed to be hosted,
otherwise it's assumed to be freestanding. The user can force
freestanding compilation by passing @code{IPC_FREESTANDING}
in the environment or on the command line of @i{make}.
When compiling in a freestanding environment, only
@code{minipc-mem-server.c} is compiled. The source file includes
the usual @code{minipc.h} and @code{minipc-int.h}, but the code
itself is simpler (no sockets, no @code{/dev/mem} nor @i{shmget}).
The program has been tested (and is being used) on LM32, within an FPGA.
In a freestanding server, the following functions are implemented,
with some limitations:
@table @code
@item minipc_server_create
The function receives the usual arguments (name and flags),
but the name is mandated to be @code{mem:@i{XXXX}} where the
trailing part is an hex address. The data structure for
communication is stored at that address in CPU memory.
@item minipc_close
Works as expected, releasing resources. Note, however,
that only one server may be active at any time (so, you'll
most likely run a server at boot and won't ever close it).
@item minipc_export
The function exports one procedure that can be called
by the server. To void using @i{malloc}, the library uses
a static array of structures to host export information.
The length of the array is @code{MINIPC_MAX_EXPORT}
(12 by default).
@item minipc_unexport
The function frees one slot in the static array.
@item minipc_get_next_arg
The function works exactly like it does in hosted
environments
@item minipc_server_action
The function receives the same arguments as the equivalent
one in hosted environments, but doesn't honor the @i{timeout}
value. Timing is very device-specific, so the library can't
provide any. If a new request is pending, the function serves
it, otherwise it returns immediately.
@end table
In our use case, the compiler has @code{<strings.h>} and a few more
headers needed to compile the library, but lacks @code{<stdint.h>}.
As an ugly workaround, the file @code{arch-lm32/stdint.h}: the
@i{Makefile} adds @code{-Iarch-$(ARCH)} to the compiler's command
line, so we can build the library using @code{ARCH=lm32}. You may use
a similar trick to add missing headers for your freestanding
architecture.
@c ##########################################################################
@node Examples
@chapter Examples
The subdirectory @code{examples/} hosts a number of examples, that
are logically divided in groups.
@menu
* Trivial Example::
* Pty-based Example::
* A Bridge to Shared Memory::
* Native Shared Memory::
* Freestanding Server::
@end menu
@c ==========================================================================
@node Trivial Example
@section Trivial Example
The two programs in this group are called @code{trivial-server} and
@code{trivial-client}.
The server exports three functions:
@table @i
@item sum
The function receives two integers and returns another integer value.
@item gettimeofday
The function receives nothing and returns @code{struct timeval}.
@item sqrt
The functions receives one @code{double} number and returns another.
@end table
The server program shows how the @code{minipc_pd} structures are built
and how the @o{minipc_f} function is implemented. For each exported
function, the source file includes first the non-rpc-aware function
and then the mini-ipc wrapper.
In this case the main loop of the server just calls the server action,
without doing anything else. It is verbose in its own, and
declares @i{stderr} as @code{logfile} for the library.
The client declares the same @code{minipc_pd} structures as the server.
In more serious uses you should define the structure in a separate
source file, in order to share them in the server and client.
The client just calls the server functions a few times, delaying
half a second in between.
The following commands, run in two different terminal sessions,
can be used to test the pair of programs:
@example
./trivial-server
for n in $(seq 1 20); do
./trivial-client &
sleep 0.2
done
@end example
@c ==========================================================================
@node Pty-based Example
@section Pty-based Example
This set of programs sets up a more complete example. It shows how to
multiplex RPC operations and other activities by using th RPC @i{fd_set}
together with your own channels. The programs are called @code{pty-server}
and @code{pty-client}.
The server creates a pseudo-tty device and spawn a shell running in it.
If then feeds its own @i{stdin} to the shell and the shell's @i{stdout} to
its own @i{stdout}. You should call it with no arguments. Its log file
is called @code{/tmp/pty-server.XXXXXX}, with @code{XXXXXX} replaced
by some unique string by the @i{mkstemp} library function.
The RPC client can make queries to this server, without disturbing the
operation of the underlying shell. The client can ask for the
statistics of bytes read/written to/from the @i{pty} device; it can
read or write environment variables in the @code{pty-server}
application; it can feed strings to the shell driven by the pty
server; it can run strlen and strcat remotely, as well as remotely run
the stat system call (to check string and structure exchange). Each
@code{pty-client} run takes a command line, like the following ones
(names in angle-brackets are place holders):
@example
./pty-client count
./pty-client getenv <name>
./pty-client setenv <name> <value>
./pty-client feed <shell-command>
./pty-client strlen <string>
./pty-client strcat <string> <string>
./pty-client stat <filename>
@end example
The commands @code{count}, @code{feed}, @code{strlen}, @code{strcat}
and @code{stat} can be verified by just inspecting their own output. The
commands @code{setenv} and @code{getenv} may cross-verified.
Note that only one pty-server can be running at a time (as the rpc
socket is a static name within the socket directory), but you can run
several clients at the same time.
The sharing of @code{minipc_pd} (procedure description) in this set of
example is accomplished by linking the same object file in both
programs, to avoid code duplication.
@c ==========================================================================
@node A Bridge to Shared Memory
@section A Bridge to Shared Memory
This set of examples shows how to write a @i{bridge} from @i{mini-ipc}
to other communications media. Moreover, it shows how a single
process can act both as a client and a server.
The example example shows how to route @i{mini-ipc} requests to
another process through shared memory. Sure there is no real interest
in shared memory for IPC calls, but the same approach can be used with
mailbox-kind communication mechanisms to talk with hardware; for
example a CPU without OS running on an FPGA. That's why the examples
in this set are called @code{mbox-@i{something}}.
Please note that @i{mini-ipc} now has shared memory support: this set
of example doesn't use the library internals but implements a simpler
protocol.
This example-set is made up of three programs:
@table @code
@item mbox-bridge
The process acts as both a server and a client of the @i{mini-ipc}
protocol, routing requests to and from a shared-memory mailbox.
As @i{mini-ipc} client, it requests the current time to
@code{trivial-server} (such queries are originated from the shared
memory area). As @i{mini-ipc} server, it replies to
@i{stat} calls for local filenames (and such service is asked
to another process through the shared memory).
@item mbox-process
The "remote" process. It only communicating though
the mailbox device. It asks for the current time once a
second and can serve @i{stat} requests when they happen.
@item mbox-client
A program that reads @i{stdin} forever, asking a @i{stat}
system call for every line it reads (such @i{stat} is asked
to @code{mbox-bridge} using @i{mini-ipc}.
@end table
To demonstrate the mechanism, you'll need to run @code{trivial-server}
(which answers @i{timeofday} queries), @code{mbox-bridge} (to bridge
requests), @code{mbox-process} and one or more @code{mbox-client}
processes.
The following is an example out output generated by @code{mbox-bridge}
(which passes @i{gettimeofday} requests silently as a @i{mini-ipc}
client, but uses @i{mini-ipc} verbose logging to @i{stderr} as a server).
The server in the fragment below is being contacted by several clients,
some of which close the connection:
@example
minipc_export: exported 0x8a3d228 (stat) with pd 0x804a1a0
-- retval 00050058, args 00040004...
mpc_handle_connection: accept returned fd 5 (error 0)
mpc_handle_client: request for stat
mpc_handle_client: request for stat
mpc_handle_client: request for stat
mpc_handle_client: error 0 in fd 5, closing
mpc_handle_connection: accept returned fd 5 (error 0)
mpc_handle_client: request for stat
mpc_handle_client: request for stat
mpc_handle_client: error 0 in fd 5, closing
mpc_handle_connection: accept returned fd 5 (error 0)
mpc_handle_connection: accept returned fd 6 (error 0)
mpc_handle_client: request for stat
mpc_handle_client: error 0 in fd 6, closing
@end example
The following fragment shows the output of an @code{mbox-process},
which reports all actions (it asks the time and serves @i{stat}
queries):
@example
time: 1326231447.212400
time: 1326231448.218001
time: 1326231449.233714
Serving stat(/home/rubini)
time: 1326231450.240125
@end example
Finally, the following fragment shows the input line (first line)
and output lines reported by one invocation of @code{mbox-client}:
@example
/home/rubini
minipc_call: calling "stat"
/home/rubini:
mode 40755, size 32768, owner 410, atime 1326230999
@end example
@c ==========================================================================
@node Native Shared Memory
@section Native Shared Memory
This set of examples uses the native shared memory support in the
library. This means that server code exports functions using the
library data structures, and the client calls them in the same way.
There are two examples in this set, that share the source file that
instantiates function definitions. The code, as you may expect, is
mainly copied from the @i{pty} set of examples described earlier. I
see no point in factorizing stuff here, as the example must be
understandable by themselves.
@table @code
@item shmem-server
The program creates a server using a name @code{shm:@i{XXXX}} where
the whole identifier must be passed on the command line.
For example ``@code{./shmem-server shm:45}''. It uses verbose reporting
on @i{stderr} and just waits for requests. Passing @code{shm:}
on the command line is mandatory for consistency with the
corresponding client.
@item shmem-client
The program receives several command line arguments: the first
is the RPC channel identifier (e.g. @code{shm:45}); it should match
the identifier used by the corresponding server. If your server
lives in system memory (e.g., it is a freestanding server in
a soft-core), you can use this client, passing @code{mem:10003000}
or equivalent.
Further arguments are commands. The client
supports the following commands, that access all functions
exported by the server. Each of them receives 1 or 2 arguments:
@i{getenv}, @i{setenv}, @i{add}, @i{strlen}, @i{strcat}, @i{stat}.
@end table
This is a sample client session, with diagnostic stripped:
@example
$ ./shmem-client shm:45 getenv USER
getenv(USER) = rubini
$ ./shmem-client shm:45 setenv USER pippo
setenv USER = pippo : success
$ ./shmem-client shm:45 getenv USER
getenv(USER) = pippo
$ ./shmem-client shm:45 strlen "this is a test"
strlen(this is a test) = 14
$ ./shmem-client shm:45 add 666 999
add 666 + 999 = 1665 : success
@end example
At the same time, the server reported the following:
@example
mpc_handle_client: request for getenv
mpc_handle_client: request for setenv
mpc_handle_client: request for getenv
mpc_handle_client: request for strlen
mpc_handle_client: request for add
@end example
Remote errors are handled as usual: @code{EREMOTEIO} locally and the
proper @code{errno} value in the packet:
@example
$ ./shmem-client shm:45 stat /tmp/nosuchfile
minipc_call: remote error "No such file or directory"
.//shmem-client: remote "stat": Remote I/O error
@end example
@c ==========================================================================
@node Freestanding Server
@section Freestanding Server
When compiling for a freestanding CPU environment, the @i{Makefile}
only builds the example @code{freestanding-server} . This exports
two functions, @code{add} and @code{mul}.
You may use the @code{add} functionality from a @code{shmem-client}
example file, by passing the proper address in @code{/dev/mem}.
In order to run this example, however, you'll need to complete it
with all the missing stuff (drivers, linker scripts, an so on),
as well as fixing the address used for the communication data
structure.
Finally, please note that the library doesn't currently manage endian
conversion, and I'd love not to do that until I add IP channels (TCP
and UDP). However, your freestanding server may have a different
endianness from the host, so you may need the conversion.
But there are worse situations: your soft core may be big endian and
the host may be little endian, but using a straight 32-bit memory link
you won't notice endian issues when exchanging integers. That's great,
until you exchange strings: first of all, the bus just discards
byte-wide and halfword-wide accesses (the library does @i{memcpy} for
procedure names now, so this is mostly fixed); then, every word in the
string is byte-swapped. So you want @code{"add"} and you get
@code{"\0dda"} instead. You can work around the issue by using names
like @code{ssss}, @code{1111} and the like; ugly but simple -- fixing
this @i{cleanly} in the library is hard, I've no idea how to do it.
@c ##########################################################################
@node Bugs
@chapter Bugs
Please note that there are a number of untested corner cases as I write this.
There are no known bugs, but the following points at least may be
problematic as I didn't thoroughly test the code for them:
@itemize @bullet
@item There are some hard limits, like less than 64 file descriptors
open. The thing is meant to remain small and avoid some complexity.
@item Again, for simplicity, not everything is undone properly; but for
small systems with a well defined process-set this is not a problem.
For example, shared memory regions are not destroyed, and some
unlike errors don't undo everything that succeed before the error.
@item The shared memory thing has no locking at all, and the sequencing
engine is pretty simple-minded. The only expected user is a
real-time process running on a coprocessor with a single client
feeding it one request at a time.
@item Please don't try going near 1k argument/retval size
(@code{MINIPC_MAX_REPLY}: off-by-1 errors are most likely present (and
I have no tests yet to feel safe).
@item Strings passing may have some hidden bugs.
@item Struct passing may be faulty in some corner cases.
@end itemize
@c ##########################################################################
@bye
@c LocalWords: gnudd titlepage iftex texinfo CERN documentlanguage settitle
@c LocalWords: documentencoding setfilename afourpaper paragraphindent Tomasz
@c LocalWords: setchapternewpage finalout Wlostowski minipc struct uint ATYPE
@c LocalWords: retval enum strlen atype ASIZE args atypes sizeof const fileno
@c LocalWords: millisec errno recv ETIMEDOUT EREMOTEIO unexport timeoutms
@c LocalWords: fdset setptr endian itemx logf stderr logfile fprintf fflush
@c LocalWords: varargs unmarshalled unmarshalling unmarshall gettimeofday
@c LocalWords: timeval FPGA stat timeofday strncat usec EINVAL mkstemp atime
@c LocalWords: getenv setenv XXXXXX stdin stdout
trivial-server
trivial-client
pty-server
pty-client
mbox-bridge
mbox-client
mbox-process
shmem-server
shmem-client
freestanding-server
# examples for mini-rpc; depends on ..
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
CFLAGS = -Wall -ggdb -I.. -O2
LDFLAGS = -L.. -lminipc -lm
# we may be hosted or freestanding. For freestanding there is one
# example only. The following variables are exported by the upper
# Makefile, but if you "make" in this directory, it checks by itself
IPC_FREESTANDING ?= $(shell ../check-freestanding $(CC))
IPC_HOSTED ?= $(shell ../check-freestanding -n $(CC))
# Hosted is the opposite of freestanding, and cflags change too
ifeq ($(IPC_FREESTANDING),y)
IPC_HOSTED = n
CFLAGS += -ffreestanding -I../arch-$(ARCH)
else
IPC_HOSTED = y
endif
PROGS-$(IPC_HOSTED) = trivial-server trivial-client
PROGS-$(IPC_HOSTED) += pty-server pty-client
PROGS-$(IPC_HOSTED) += mbox-process mbox-bridge mbox-client
PROGS-$(IPC_HOSTED) += shmem-server shmem-client
IPC_FREESTANDING ?= n
PROGS-$(IPC_FREESTANDING) += freestanding-server
all: $(PROGS-y)
# the default puts LDFLAGS too early. Bah...
%: %.c
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
pty-server: pty-server.o pty-rpc_server.o pty-rpc_structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -lutil -o $@
pty-client: pty-client.o pty-rpc_structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
shmem-server: shmem-server.o shmem-structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
shmem-client: shmem-client.o shmem-structs.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
# This is stupid, it won't apply the first time, but, well... it works
$(PROGS-y) $(wildcard *.o): $(wildcard ../*.h ../*.a)
clean:
rm -f *.o *~ $(PROGS)
All documentation is now under the ../doc/ directory.
The input file is called doc/mini-ipc.in (it is readable by itself).
To have all usual output formats you can run "make" withing ./doc
but you need TexInfo installed.
/*
* Mini-ipc: an example freestanding server, based in memory
*
* Copyright (C) 2011,2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This code is copied from trivial-server, and made even more trivial
*/
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include "minipc.h"
/* A function that ignores the RPC and is written normally */
static int ss_do_sum(int a, int b)
{
return a + b;
}
/* The following ones are RPC-aware */
static int ss_sum_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int i;
i = ss_do_sum(args[0], args[1]);
*(int *)ret = i;
return 0;
}
static int ss_mul_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int a, b;
a = *(int *)args;
b = *(int *)(args + 1);
*(int *)ret = a * b;
return 0;
}
/* Describe the two functions above */
const struct minipc_pd ss_sum_struct = {
.f = ss_sum_function,
.name = "sum",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_mul_struct = {
.f = ss_mul_function,
.name = "mul",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *server;
server = minipc_server_create("mem:f000", 0);
if (!server)
return 1;
minipc_export(server, &ss_sum_struct);
minipc_export(server, &ss_mul_struct);
while (1) {
/* do something else... */
minipc_server_action(server, 1000);
}
}
/*
* Example bridge beteen mini-ipc and a mailbox memory area
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This example uses a shared memory area to talk with mbox-process.
* The same technique can be used to communicate with an os-less
* soft-core within an FPGA, and actually this is the reason why I'm
* writing this demo.
*
* The bridge can export functions in both ways: as a mini-ipc server
* it exports "stat", and as a client it calls gettimeofday in the
* trivial-server.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/select.h>
#include "minipc.h"
#include "minipc-shmem.h" /* The shared data structures */
struct minipc_mbox_info *info; /* unfortunately global... */
/* This function implements the "stat" mini-ipc server, by asking mbox */
static int mb_stat_server(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *fname = (void *)args;
/* pass this to the shmem */
memset(info->fname, 0, sizeof(info->fname));
strncpy(info->fname, fname, sizeof(info->fname) -1 );
/* ask and wait */
info->bridge_req = 1;
while (info->bridge_req)
usleep(1000);
memcpy(ret, &info->stbuf, sizeof(info->stbuf));
return 0;
}
/* The description here is the same as in the server */
const struct minipc_pd mb_tod_struct = {
.name = "gettimeofday",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct timeval),
.args = {
MINIPC_ARG_END,
},
};
/* And here it's the same as in the client */
struct minipc_pd mb_stat_struct = {
.f = mb_stat_server,
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* No arguments */
int main(int argc, char **argv)
{
struct minipc_ch *server;
struct minipc_ch *client;
void *shmem;
int ret;
if (argc > 1) {
fprintf(stderr, "%s: no arguments please\n", argv[0]);
exit(1);
}
/* Create your shared memory and/or attach to it */
ret = shmget(MINIPC_SHM, sizeof(struct minipc_mbox_info),
IPC_CREAT | 0666);
if (ret < 0) {
fprintf(stderr, "%s: shmget(): %s\n", argv[0],
strerror(errno));
exit(1);
}
shmem = shmat(ret, NULL, SHM_RND);
if (shmem == (void *)-1) {
fprintf(stderr, "%s: shmat(): %s\n", argv[0],
strerror(errno));
exit(1);
}
info = shmem;
/* Create a server socket for mini-ipc */
server = minipc_server_create("mbox", 0);
if (!server) {
fprintf(stderr, "%s: server_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(server, stderr);
minipc_export(server, &mb_stat_struct);
/* Connect as a client to the trivial-server */
client = minipc_client_create("trivial", 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Loop serving both mini-ipc and the mailbox */
while (1) {
fd_set set;
struct timeval to = {
MBOX_POLL_US / (1000*1000),
MBOX_POLL_US % (1000*1000)
};
minipc_server_get_fdset(server, &set);
/* Wait for any server, with the defined timeout */
ret = select(16 /* hack */, &set, NULL, NULL, &to);
if (ret > 0) {
if (minipc_server_action(server, 0) < 0) {
fprintf(stderr, "%s: server_action(): %s\n",
argv[0], strerror(errno));
exit(1);
}
}
/* No IPC request: if needed act as IPC client */
if (info->proc_req) {
memset(&info->tv, 0, sizeof(info->tv));
minipc_call(client, 100 /* ms */,
&mb_tod_struct, &info->tv, NULL);
info->proc_req = 0;
}
}
}
/*
* Example mbox client
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*
* This process requests "stat" calls to the mbox-bridge, which in turn
* asks it to the "remote" process that lives behing an mbox (shmem).
* The program reads filenames from stdin, in order to allow several clients
* connected to the same bridge.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "minipc.h"
#define CLIENT_TIMEOUT 100 /* ms */
/* The description here is the same as in the server */
struct minipc_pd rpc_stat = {
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *client;
struct stat stbuf;
int ret;
char s[80];
client = minipc_client_create("mbox", 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(client, stderr);
while (fgets(s, sizeof(s), stdin)) {
/* strip trailing newline */
s[strlen(s)-1] = '\0';
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_stat,
&stbuf, s);
if (ret < 0) {
fprintf(stderr, "stat(%s): %s\n", s, strerror(errno));
continue;
}
printf("%s:\n", s);
printf("mode %o, size %li, owner %i, atime %li\n",
stbuf.st_mode, (long)stbuf.st_size, stbuf.st_uid,
stbuf.st_atime);
}
return 0;
}
/*
* Example process connected to the world through a mailbox
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This example uses a shared memory area to talk with the mbox-bridge.
* In my real-world application this will be an os-less
* soft-core within an FPGA.
*
* The process is both a server and a client. As a server iIt replies
* to "stat" calls coming from the bridge process; as a client it asks
* the time of day once a second or so.
*
* It doesn't need minipc.h because it is not directly connected to the ipc
* mechanism
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "minipc-shmem.h" /* The shared data structures */
struct minipc_mbox_info *info; /* unfortunately global... */
/* No arguments */
int main(int argc, char **argv)
{
void *shmem;
int ret, i;
if (argc > 1) {
fprintf(stderr, "%s: no arguments please\n", argv[0]);
exit(1);
}
/* Create your shared memory and/or attach to it */
ret = shmget(MINIPC_SHM, sizeof(struct minipc_mbox_info),
IPC_CREAT | 0666);
if (ret < 0) {
fprintf(stderr, "%s: shmget(): %s\n", argv[0],
strerror(errno));
exit(1);
}
shmem = shmat(ret, NULL, SHM_RND);
if (shmem == (void *)-1) {
fprintf(stderr, "%s: shmat(): %s\n", argv[0],
strerror(errno));
exit(1);
}
info = shmem;
/* Loop forever */
for (i = 0; 1; i++) {
if (info->bridge_req) {
/* We got a request: serve it */
printf("Serving stat(%s)\n", info->fname);
memset(&info->stbuf, 0, sizeof(info->stbuf));
stat(info->fname, &info->stbuf);
info->bridge_req = 0;
}
if (i * MBOX_POLL_US >= 1000 * 1000) {
/* 1s passed, ask for the date */
info->proc_req = 1;
while (info->proc_req)
;
printf("time: %li.%06li\n", (long)info->tv.tv_sec,
(long)info->tv.tv_usec);
i = 0;
}
usleep(MBOX_POLL_US);
}
}
#ifndef __MINIPC_SHMEM_H__
#define __MINIPC_SHMEM_H__
#include <sys/stat.h>
#include <sys/time.h>
#define MINIPC_SHM 0x3465 /* Random */
struct minipc_mbox_info {
volatile int bridge_req; /* A request is asked by the bridge */
volatile int proc_req; /* A request is asked by the process */
/* The bridge can ask "stat" to the bare process */
char fname[64];
struct stat stbuf;
/* The bare process can ask timeofday to the bridge */
struct timeval tv;
};
#define MBOX_POLL_US (10*1000) /* 10ms */
#endif /* __MINIPC_SHMEM_H__ */
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "minipc.h"
#include "pty-server.h"
#define CLIENT_TIMEOUT 200 /* ms */
/* Retrieve the counters of the pty file descriptors */
static int do_count(struct minipc_ch *client, char **argv)
{
int ret;
struct pty_counts c;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_count, &c);
if (ret < 0)
return ret;
printf("counts: %i %i\n", c.in, c.out);
return 0;
}
/* getenv and setenv in the remote process */
static int do_getenv(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_getenv, buf, argv[2]);
if (ret < 0)
return ret;
printf("getenv(%s) = %s\n", argv[2], buf);
return 0;
}
static int do_setenv(struct minipc_ch *client, char **argv)
{
int ret, ret2;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_setenv, &ret2, argv[2],
argv[3]);
if (ret < 0)
return ret;
printf("setenv %s = %s : success\n", argv[2], argv[3]);
return 0;
}
/* send a string to the subshell of the server */
static int do_feed(struct minipc_ch *client, char **argv)
{
int ret, ret2;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_feed, &ret2, argv[2]);
if (ret < 0)
return ret;
printf("feeded %s : success\n", argv[2]);
return 0;
}
/* calculate a remote strlen */
static int do_strlen(struct minipc_ch *client, char **argv)
{
int ret, len;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strlen, &len, argv[2]);
if (ret < 0)
return ret;
printf("strlen(%s) = %i\n", argv[2], len);
return 0;
}
/* calculate a remote strcat */
static int do_strcat(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strcat, buf,
argv[2], argv[3]);
if (ret < 0)
return ret;
printf("strcat(%s,%s) = %s\n", argv[2], argv[3], buf);
return 0;
}
/* ask for a remote stat on a passed filename */
static int do_stat(struct minipc_ch *client, char **argv)
{
int ret;
struct stat stbuf;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_stat, &stbuf,
argv[2]);
if (ret < 0)
return ret;
printf("stat(\"%s\"):\n", argv[2]);
printf("dev %04x, inode %i, mode %o (rdev %04x), size %i\n",
(int)stbuf.st_dev, (int)stbuf.st_ino, stbuf.st_mode,
(int)stbuf.st_rdev, (int)stbuf.st_size);
return 0;
}
/*
* This is a parsing table for argv[1]
*/
struct {
char *name;
int (*f)(struct minipc_ch *client, char **argv);
int argc;
} *cp, calls[] = {
{ "count", do_count, 2},
{ "getenv", do_getenv, 3},
{ "setenv", do_setenv, 4},
{ "feed", do_feed, 3},
{ "strlen", do_strlen, 3},
{ "strcat", do_strcat, 4},
{ "stat", do_stat, 3},
{NULL, },
};
/*
* And this is the trivial main function -- more error checking than code
*/
int main(int argc, char **argv)
{
struct minipc_ch *client;
if (argc < 2) {
fprintf(stderr, "%s: use \"%s <cmd> [<arg> ...]\"\n",
argv[0], argv[0]);
exit(1);
}
client = minipc_client_create(PTY_RPC_NAME, 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* scan the table */
for (cp = calls; cp->name; cp++)
if (!strcmp(argv[1], cp->name))
break;
if (!cp->name) {
fprintf(stderr, "%s: no such command \"%s\"\n", argv[0],
argv[1]);
exit(1);
}
if (argc != cp->argc) {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
argv[0], argv[1]);
exit(1);
}
/* run the RPC we found */
if (cp->f(client, argv) < 0) {
fprintf(stderr, "%s: remote \"%s\": %s\n",
argv[0], argv[1], strerror(errno));
exit(1);
}
exit(0);
}
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file includes the RPC functions exported by the server.
* RPC-related stuff has been split to a different file for clarity.
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "minipc.h"
#include "pty-server.h"
static int saved_fdm; /* file descriptor of pty master, used by "feed" */
static struct pty_counts *saved_pc; /* counters, used by "count" */
/* Return the count accessing the pointer to the structure within main() */
static int pty_server_do_count(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
*(struct pty_counts *)ret = *saved_pc;
return 0;
}
/* Run getenv and setenv on behalf of the client */
static int pty_server_do_getenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *res = getenv(envname);
strcpy(ret, res); /* FIXME: max size */
return 0;
}
static int pty_server_do_setenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *envval;
args = minipc_get_next_arg(args, pd->args[0]);
envval = (void *)args;
setenv(envname, envval, 1);
return 0;
}
/* feed a string to the sub-shell (adding a newline) */
static int pty_server_do_feed(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *command = (void *)args;
int wrote;
wrote = write(saved_fdm, command, strlen(command));
wrote += write(saved_fdm, "\n", 1);
return 0;
}
/* calculate strlen for a string of the client */
static int pty_server_do_strlen(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s = (void *)args;
*(int *)ret = strlen(s);
return 0;
}
/* strcat two remote strings and pass the result back */
static int pty_server_do_strcat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s, *t;
char scat[256];
s = (void *)args;
args = minipc_get_next_arg(args, pd->args[0]);
t = (void *)args;
strncpy(scat, s, sizeof(scat));
strncat(scat, t, sizeof(scat) - strlen(s));
strcpy(ret, scat); /* FIXME: max size */
return 0;
}
/* stat a pathname received from the remote party */
static int pty_server_do_stat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
struct stat stbuf;
char *fname = (void *)args;
if (stat(fname, &stbuf) < 0)
return -1;
memcpy(ret, &stbuf, sizeof(stbuf));
return 0;
}
/*
* The following is called by the main function, and exports stuff.
* All the rest happens through callbacks from the library
*/
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
int pty_export_functions(struct minipc_ch *ch, int fdm, struct pty_counts *pc)
{
int i;
/* save data for the callbacks */
saved_fdm = fdm;
saved_pc = pc;
/* Use a handy table for the registration loop */
static struct {
struct minipc_pd *desc;
minipc_f *f;
} export_list [] = {
{&rpc_count, pty_server_do_count},
{&rpc_getenv, pty_server_do_getenv},
{&rpc_setenv, pty_server_do_setenv},
{&rpc_feed, pty_server_do_feed},
{&rpc_strlen, pty_server_do_strlen},
{&rpc_strcat, pty_server_do_strcat},
{&rpc_stat, pty_server_do_stat},
};
/*
* Complete the data structures by filling the function pointers
* and then register each of the exported procedures
*/
for (i = 0; i < ARRAY_SIZE(export_list); i++) {
export_list[i].desc->f = export_list[i].f;
if (minipc_export(ch, export_list[i].desc) < 0)
return -1;
}
return 0;
}
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file defines the data structures used to describe the RPC calls
* between server and client. Note that the function pointers are only
* instantiated in the server
*/
#include <sys/stat.h> /* for the structure */
#include "minipc.h"
#include "pty-server.h"
/* retrieve in and out counts from the pty-server */
struct minipc_pd rpc_count = {
.name = "count",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct pty_counts),
.args = {
MINIPC_ARG_END,
},
};
/* run getenv in the server */
struct minipc_pd rpc_getenv = {
.name = "getenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run setenv in the server */
struct minipc_pd rpc_setenv = {
.name = "setenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* feed a string to the underlying shell */
struct minipc_pd rpc_feed = {
.name = "feed",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* horribly run strlen and strcat */
struct minipc_pd rpc_strlen = {
.name = "strlen",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
struct minipc_pd rpc_strcat = {
.name = "strcat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run "stat" on a file name */
struct minipc_pd rpc_stat = {
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This is the pty-server. It only does the pty-master work, all RPC stuff
* has been confined to prt-rpc_server.c, which is called by our main loop
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pty.h>
#include <fcntl.h>
#include <utmp.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include "minipc.h"
#include "pty-server.h"
int main(int argc, char **argv)
{
int fdm, fds, pid, exitval = 0;
struct minipc_ch *ch;
struct pty_counts counters = {0,};
/* First, open the pty */
if (openpty(&fdm, &fds, NULL, NULL, NULL) < 0) {
fprintf(stderr, "%s: openpty(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Run a shell and let it go by itself before we open the rpc */
fprintf(stderr, "%s: Running a sub-shell in a new pty\n", argv[0]);
if ((pid = fork()) < 0) {
fprintf(stderr, "%s: fork(): %s\n", argv[0],
strerror(errno));
exit(1);
}
if (!pid) {
/* Child: become a shell and disappear... */
close(fdm);
login_tty(fds);
execl("/bin/sh", "sh", NULL);
fprintf(stderr, "%s: exec(/bin/sh): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Open the RPC server channel */
ch = minipc_server_create(PTY_RPC_NAME, 0);
if (!ch) {
fprintf(stderr, "%s: rpc_open(): %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Log file for diagnostics */
{
char name[] = "/tmp/pty-server.XXXXXX";
int logfd;
FILE *logf;
logfd = mkstemp(name);
if (logfd >= 0) {
logf = fdopen(logfd, "w");
if (logf)
minipc_set_logfile(ch, logf);
}
}
/* Register your functions: all our RPC is split to another source */
if (pty_export_functions(ch, fdm, &counters)) {
fprintf(stderr, "%s: exporting RPC functions: %s\n", argv[0],
strerror(errno));
exit(1);
}
/*
* Now, we must mirror stdin/stdout to the pty master, with RPC too.
* The first step is horribly changing the termios of our tty
*/
close(fds);
if (system("stty raw -echo") < 0) {
fprintf(stderr, "%s: can't run \"stty\"\n", argv[0]);
exit(1);
}
while (waitpid(pid, NULL, WNOHANG) != pid) {
fd_set set;
int nfd, i, j;
char buf[256];
/* ask the RPC engine its current fdset and augment it */
minipc_server_get_fdset(ch, &set);
FD_SET(STDIN_FILENO, &set);
FD_SET(fdm, &set);
/* wait for any of the FD to be active */
nfd = select(64 /* Hmmm... */, &set, NULL, NULL, NULL);
if (nfd < 0 && errno == EINTR)
continue;
if (nfd < 0) {
fprintf(stderr, "%s: select(): %s\n", argv[0],
strerror(errno));
exitval = 1;
break;
}
/* Handle fdm and fds by just mirroring stuff and counting */
if (FD_ISSET(STDIN_FILENO, &set)) {
i = read(0, buf, sizeof(buf));
if (i > 0) {
counters.in += i;
do {
j = write(fdm, buf, i);
i -= j;
} while (i && j >= 0);
}
nfd--;
}
if (FD_ISSET(fdm, &set)) {
i = read(fdm, buf, sizeof(buf));
if (i > 0) {
counters.out += i;
do {
j = write(1, buf, i);
i -= j;
} while (i && j >= 0);
}
nfd--;
}
/* If there are no more active fd, loop over */
if (!nfd)
continue;
/*
* If we are there, there has been an RPC call.
* We tell the library to use a 0 timeout, since we know
* for sure that at least one of its descriptors is pending.
*/
minipc_server_action(ch, 0);
}
/* The child shell exited, reset the tty and exit. Let RPC die out */
if (system("stty sane") < 0)
fprintf(stderr, "%s: can't restore tty settings\n", argv[0]);
exit(exitval);
}
#include "minipc.h"
struct pty_counts {
int in, out;
};
#define PTY_RPC_NAME "pty-server"
/* structures are in pty-rpc_structs.c */
extern struct minipc_pd
rpc_count, rpc_getenv, rpc_setenv, rpc_feed, rpc_strlen, rpc_strcat, rpc_stat;
/* The server needs to call out to a different file */
extern int pty_export_functions(struct minipc_ch *ch,
int fdm, struct pty_counts *pc);
/*
* Example mini-ipc client for shmem
* Mostly copied and adapted from pty-client.c
*
* Copyright (C) 2011,2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "minipc.h"
#include "shmem-structs.h"
#define CLIENT_TIMEOUT 200 /* ms */
/* getenv and setenv in the remote process */
static int do_getenv(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_getenv, buf, argv[2]);
if (ret < 0)
return ret;
printf("getenv(%s) = %s\n", argv[2], buf);
return 0;
}
static int do_setenv(struct minipc_ch *client, char **argv)
{
int ret, ret2;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_setenv, &ret2, argv[2],
argv[3]);
if (ret < 0)
return ret;
printf("setenv %s = %s : success\n", argv[2], argv[3]);
return 0;
}
static int do_add(struct minipc_ch *client, char **argv)
{
int ret;
int a = atoi(argv[2]);
int b = atoi(argv[3]);
int sum;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_add, &sum, a, b);
if (ret < 0)
return ret;
printf("add %i + %i = %i : success\n", a, b, sum);
return 0;
}
/* calculate a remote strlen */
static int do_strlen(struct minipc_ch *client, char **argv)
{
int ret, len;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strlen, &len, argv[2]);
if (ret < 0)
return ret;
printf("strlen(%s) = %i\n", argv[2], len);
return 0;
}
/* calculate a remote strcat */
static int do_strcat(struct minipc_ch *client, char **argv)
{
int ret;
char buf[256]; /* FIXME: size limits? */
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_strcat, buf,
argv[2], argv[3]);
if (ret < 0)
return ret;
printf("strcat(%s,%s) = %s\n", argv[2], argv[3], buf);
return 0;
}
/* ask for a remote stat on a passed filename */
static int do_stat(struct minipc_ch *client, char **argv)
{
int ret;
struct stat stbuf;
ret = minipc_call(client, CLIENT_TIMEOUT, &rpc_stat, &stbuf,
argv[2]);
if (ret < 0)
return ret;
printf("stat(\"%s\"):\n", argv[2]);
printf("dev %04x, inode %i, mode %o (rdev %04x), size %i\n",
(int)stbuf.st_dev, (int)stbuf.st_ino, stbuf.st_mode,
(int)stbuf.st_rdev, (int)stbuf.st_size);
return 0;
}
/*
* This is a parsing table for argv[1]
*/
struct {
char *name;
int (*f)(struct minipc_ch *client, char **argv);
int argc;
} *cp, calls[] = {
{ "getenv", do_getenv, 3},
{ "setenv", do_setenv, 4},
{ "add", do_add, 4},
{ "strlen", do_strlen, 3},
{ "strcat", do_strcat, 4},
{ "stat", do_stat, 3},
{NULL, },
};
/*
* And this is the trivial main function -- more error checking than code
*/
int main(int argc, char **argv)
{
struct minipc_ch *client;
if (argc <= 2) {
fprintf(stderr, "%s: use \"%s <shmid> <cmd> [<arg> ...]\n",
argv[0], argv[0]);
exit(1);
}
client = minipc_client_create(argv[1], 0);
if (!client) {
fprintf(stderr, "%s: rpc_open(%s): %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
minipc_set_logfile(client, stderr);
argv[1] = argv[0]; argv++; argc--;
/* scan the table */
for (cp = calls; cp->name; cp++)
if (!strcmp(argv[1], cp->name))
break;
if (!cp->name) {
fprintf(stderr, "%s: no such command \"%s\"\n", argv[0],
argv[1]);
exit(1);
}
if (argc != cp->argc) {
fprintf(stderr, "%s: %s: wrong number of arguments\n",
argv[0], argv[1]);
exit(1);
}
/* run the RPC we found */
if (cp->f(client, argv) < 0) {
fprintf(stderr, "%s: remote \"%s\": %s\n",
argv[0], argv[1], strerror(errno));
exit(1);
}
exit(0);
}
/*
* Example mini-ipc server for shmem
* (mostly copied from the pty server code)
*
* Copyright (C) 2011-2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file includes the RPC functions exported by the server.
* RPC-related stuff has been split to a different file for clarity.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "minipc.h"
#include "shmem-structs.h"
/* Run getenv and setenv on behalf of the client */
static int shm_server_do_getenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *res = getenv(envname);
strcpy(ret, res); /* FIXME: max size */
return 0;
}
static int shm_server_do_setenv(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *envname = (void *)args;
char *envval;
args = minipc_get_next_arg(args, pd->args[0]);
envval = (void *)args;
setenv(envname, envval, 1);
return 0;
}
/* add two integer numbers */
static int shm_server_do_add(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int a, b, sum;
a = args[0];
args = minipc_get_next_arg(args, pd->args[0]);
b = args[0];
sum = a + b;
*(int *)ret = sum;
return 0;
}
/* calculate strlen for a string of the client */
static int shm_server_do_strlen(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s = (void *)args;
*(int *)ret = strlen(s);
return 0;
}
/* strcat two remote strings and pass the result back */
static int shm_server_do_strcat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
char *s, *t;
char scat[256];
s = (void *)args;
args = minipc_get_next_arg(args, pd->args[0]);
t = (void *)args;
strncpy(scat, s, sizeof(scat));
strncat(scat, t, sizeof(scat) - strlen(s));
strcpy(ret, scat); /* FIXME: max size */
return 0;
}
/* stat a pathname received from the remote party */
static int shm_server_do_stat(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
struct stat stbuf;
char *fname = (void *)args;
if (stat(fname, &stbuf) < 0)
return -1;
memcpy(ret, &stbuf, sizeof(stbuf));
return 0;
}
/*
* The following is called by the main function, and exports stuff.
* All the rest happens through callbacks from the library
*/
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
int shm_export_functions(struct minipc_ch *ch)
{
int i;
/* Use a handy table for the registration loop */
static struct {
struct minipc_pd *desc;
minipc_f *f;
} export_list [] = {
{&rpc_getenv, shm_server_do_getenv},
{&rpc_setenv, shm_server_do_setenv},
{&rpc_add, shm_server_do_add},
{&rpc_strlen, shm_server_do_strlen},
{&rpc_strcat, shm_server_do_strcat},
{&rpc_stat, shm_server_do_stat},
};
/*
* Complete the data structures by filling the function pointers
* and then register each of the exported procedures
*/
for (i = 0; i < ARRAY_SIZE(export_list); i++) {
export_list[i].desc->f = export_list[i].f;
if (minipc_export(ch, export_list[i].desc) < 0)
return -1;
}
return 0;
}
/* the main function only runs the server */
int main(int argc, char **argv)
{
struct minipc_ch *ch;
if (argc != 2) {
fprintf(stderr, "%s: use \"%s <shmid>\n", argv[0], argv[0]);
exit(1);
}
ch = minipc_server_create(argv[1], 0);
if (!ch) {
fprintf(stderr, "%s: rpc_open(%s): %s\n", argv[0], argv[1],
strerror(errno));
exit(1);
}
minipc_set_logfile(ch, stderr);
/* Register your functions */
if (shm_export_functions(ch)) {
fprintf(stderr, "%s: exporting RPC functions: %s\n", argv[0],
strerror(errno));
exit(1);
}
/* Then just obey requests */
while (1) {
minipc_server_action(ch, 1000);
}
}
/*
* Structures for example shmem mini-ipc backends
*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
/*
* This file defines the data structures used to describe the RPC calls
* between server and client. Note that the function pointers are only
* instantiated in the server
*/
#include "minipc.h"
#include "shmem-structs.h"
#include <sys/stat.h>
/* run getenv in the server */
struct minipc_pd rpc_getenv = {
.name = "getenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run setenv in the server */
struct minipc_pd rpc_setenv = {
.name = "setenv",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* addition of two integers */
struct minipc_pd rpc_add = {
.name = "add",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
/* horribly run strlen and strcat */
struct minipc_pd rpc_strlen = {
.name = "strlen",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
struct minipc_pd rpc_strcat = {
.name = "strcat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
/* run "stat" on a file name */
struct minipc_pd rpc_stat = {
.name = "stat",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct stat),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, char *),
MINIPC_ARG_END,
},
};
extern struct minipc_pd rpc_getenv;
extern struct minipc_pd rpc_setenv;
extern struct minipc_pd rpc_add;
extern struct minipc_pd rpc_strlen;
extern struct minipc_pd rpc_strcat;
extern struct minipc_pd rpc_stat;
/*
* Example mini-ipc client
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include "minipc.h"
#define TRIVIAL_TIMEOUT 100 /* ms */
/* The description here is the same as in the server */
const struct minipc_pd ss_sum_struct = {
.name = "sum",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_tod_struct = {
.name = "gettimeofday",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct timeval),
.args = {
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_sqrt_struct = {
.name = "sqrt",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *client;
int a, b, c, ret;
struct timeval tv;
double rt_in, rt_out;
client = minipc_client_create("trivial", 0);
if (!client) {
fprintf(stderr, "%s: client_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(client, stderr);
/*
* gettod, sum, sum, gettod
* pause a while in-between, so several clients can be run
* concurrently as a load test on the server
*/
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_tod_struct, &tv, NULL);
if (ret < 0) {
goto error;
}
printf("tv: %li.%06li\n", tv.tv_sec, tv.tv_usec);
usleep(500*1000);
a = 345; b = 628;
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_sum_struct, &c, a, b);
if (ret < 0) {
goto error;
}
printf("%i + %i = %i\n", a, b, c);
usleep(500*1000);
a = 10; b = 20;
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_sum_struct, &c, a, b);
if (ret < 0) {
goto error;
}
printf("%i + %i = %i\n", a, b, c);
usleep(500*1000);
rt_in = 2.0;
ret = minipc_call(client, TRIVIAL_TIMEOUT, &ss_sqrt_struct, &rt_out, rt_in);
if (ret < 0) {
goto error;
}
printf("sqrt(%lf) = %lf\n", rt_in, rt_out);
usleep(500*1000);
return 0;
error:
fprintf(stderr, "Error in rpc: %s\n", strerror(errno));
exit(1);
}
/*
* Example mini-ipc server
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* Released in the public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <sys/time.h>
#include "minipc.h"
/* A function that ignores the RPC and is written normally */
static int ss_do_sum(int a, int b)
{
return a + b;
}
/* The following ones are RPC-aware */
static int ss_sum_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
int i;
i = ss_do_sum(args[0], args[1]);
*(int *)ret = i;
return 0;
}
static int ss_tod_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
struct timeval tv;
gettimeofday(&tv, NULL);
*(struct timeval *)ret = tv;
return 0;
}
static int ss_sqrt_function(const struct minipc_pd *pd,
uint32_t *args, void *ret)
{
double input, output;
input = *(double *)args;
output = sqrt(input);
*(double *)ret = output;
return 0;
}
/* Describe the three functions above */
const struct minipc_pd ss_sum_struct = {
.f = ss_sum_function,
.name = "sum",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_ENCODE(MINIPC_ATYPE_INT, int),
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_tod_struct = {
.f = ss_tod_function,
.name = "gettimeofday",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRUCT, struct timeval),
.args = {
MINIPC_ARG_END,
},
};
const struct minipc_pd ss_sqrt_struct = {
.f = ss_sqrt_function,
.name = "sqrt",
.retval = MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
.args = {
MINIPC_ARG_ENCODE(MINIPC_ATYPE_DOUBLE, double),
MINIPC_ARG_END,
},
};
int main(int argc, char **argv)
{
struct minipc_ch *server;
server = minipc_server_create("trivial", 0);
if (!server) {
fprintf(stderr, "%s: server_create(): %s\n", argv[0],
strerror(errno));
exit(1);
}
minipc_set_logfile(server, stderr);
minipc_export(server, &ss_sum_struct);
minipc_export(server, &ss_tod_struct);
minipc_export(server, &ss_sqrt_struct);
while (1) {
if (minipc_server_action(server, 1000) < 0) {
fprintf(stderr, "%s: server_action(): %s\n", argv[0],
strerror(errno));
exit(1);
}
fprintf(stdout, "%s: looping...\n", __func__);
}
}
/*
* Mini-ipc: Exported functions for client operation
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <poll.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "minipc-int.h"
struct minipc_ch *minipc_client_create(const char *name, int f)
{
return __minipc_link_create(name, MPC_USER_FLAGS(f) | MPC_FLAG_CLIENT);
}
int minipc_call(struct minipc_ch *ch, int millisec_timeout,
const struct minipc_pd *pd, void *ret, ...)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_shmem *shm = link->memaddr;
struct pollfd pfd;
int i, narg, size, retsize, pollnr;
int atype, asize;
va_list ap;
struct mpc_req_packet *p_out, _pkt_out = {"",};
struct mpc_rep_packet *p_in, _pkt_in;
CHECK_LINK(link);
if (shm) {
p_out = &shm->request;
p_in = &shm->reply;
} else {
p_out = & _pkt_out;
p_in = & _pkt_in;
}
/* Build the packet to send out -- marshall args */
if (link->logf) {
fprintf(link->logf, "%s: calling \"%s\"\n",
__func__, pd->name);
}
memcpy(p_out->name, pd->name, MINIPC_MAX_NAME);
va_start(ap, ret);
for (i = narg = 0; ; i++) {
int next_narg = narg;
atype = MINIPC_GET_ATYPE(pd->args[i]);
asize = MINIPC_GET_ASIZE(pd->args[i]);
next_narg += MINIPC_GET_ANUM(asize);
if (next_narg >= MINIPC_MAX_ARGUMENTS) /* unlikely */
goto doesnt_fit;
switch (atype) {
case MINIPC_ATYPE_NONE:
goto out; /* end of list */
case MINIPC_ATYPE_INT:
p_out->args[narg++] = va_arg(ap, int);
break;
case MINIPC_ATYPE_INT64:
*(uint64_t *)(p_out->args + narg)
= va_arg(ap, uint64_t);
narg = next_narg;
break;
case MINIPC_ATYPE_DOUBLE:
*(double *)(p_out->args + narg) = va_arg(ap, double);
narg = next_narg;
break;
case MINIPC_ATYPE_STRING:
{
char *sin = va_arg(ap, void *);
char *sout = (void *)(p_out->args + narg);
int slen = strlen(sin);
int alen;
/*
* argument len is arbitrary, terminate and 4-align
* we can't use next_narg here, as len changes
*/
alen = MINIPC_GET_ANUM(slen + 1);
if (narg + alen >= MINIPC_MAX_ARGUMENTS)
goto doesnt_fit;
strcpy(sout, sin);
narg += alen;
break;
}
case MINIPC_ATYPE_STRUCT:
memcpy(p_out->args + narg, va_arg(ap, void *), asize);
narg = next_narg;
break;
default:
if (link->logf) {
fprintf(link->logf, "%s: unkown type 0x%x\n",
__func__, atype);
}
errno = EPROTO;
return -1;
}
}
out:
va_end(ap);
if (shm) {
shm->nrequest++;
} else {
size = sizeof(p_out->name) + sizeof(p_out->args[0]) * narg;
if (send(ch->fd, p_out, size, 0) < 0) {
/* errno already set */
return -1;
}
}
/* Wait for the reply packet */
pfd.fd = ch->fd;
pfd.events = POLLIN | POLLHUP;
pfd.revents = 0;
pollnr = poll(&pfd, 1, millisec_timeout);
if (pollnr < 0) {
/* errno already set */
return -1;
}
if (pollnr == 0) {
errno = ETIMEDOUT;
return -1;
}
if (shm) {
read(ch->fd, &i, 1);
size = retsize = sizeof(shm->reply);
} else {
/* this "size" is wrong for strings, so recv the max size */
size = MINIPC_GET_ASIZE(pd->retval) + sizeof(uint32_t);
retsize = recv(ch->fd, p_in, sizeof(*p_in), 0);
}
/* if very short, we have a problem */
if (retsize < (sizeof(p_in->type)) + sizeof(int))
goto too_short;
/* remote error reported */
if (MINIPC_GET_ATYPE(p_in->type) == MINIPC_ATYPE_ERROR) {
int remoteerr = *(int *)&p_in->val;
if (link->logf) {
fprintf(link->logf, "%s: remote error \"%s\"\n",
__func__, strerror(remoteerr));
}
*(int *)ret = remoteerr;
errno = EREMOTEIO;
return -1;
}
/* another check: the return type must match */
if (MINIPC_GET_ATYPE(p_in->type) != MINIPC_GET_ATYPE(pd->retval)) {
if (link->logf) {
fprintf(link->logf, "%s: wrong code %08x (not %08x)\n",
__func__, p_in->type, pd->retval);
}
errno = EPROTO;
return -1;
}
/* check size */
if (retsize < size)
goto too_short;
/* all good */
memcpy(ret, &p_in->val, MINIPC_GET_ASIZE(p_in->type));
return 0;
too_short:
if (link->logf) {
fprintf(link->logf, "%s: short reply (%i bytes)\n",
__func__, retsize);
}
errno = EPROTO;
return -1;
doesnt_fit:
if (link->logf) {
fprintf(link->logf, "%s: rpc call \"%s\" won't fit %i slots\n",
__func__, pd->name, MINIPC_MAX_ARGUMENTS);
}
errno = EPROTO;
return -1;
}
/*
* Mini-ipc: Core library functions and data
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include "minipc-int.h"
struct mpc_link *__mpc_base;
static int __mpc_poll_usec = MINIPC_DEFAULT_POLL;
void mpc_free_flist(struct mpc_link *link, struct mpc_flist *flist)
{
struct mpc_flist **nextp;
/* Look for flist and release it*/
for (nextp = &link->flist; (*nextp); nextp = &(*nextp)->next)
if (*nextp == flist)
break;
if (!*nextp) {
if (link->logf)
fprintf(link->logf, "%s: function not found %p (%s)\n",
__func__, flist, flist->pd->name);
return;
}
*nextp = flist->next;
if (link->logf)
fprintf(link->logf, "%s: unexported function %p (%s)\n",
__func__, flist->pd->f, flist->pd->name);
free(flist);
}
int minipc_close(struct minipc_ch *ch)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_link **nextp;
CHECK_LINK(link);
/* Look for link in our list */
for (nextp = &__mpc_base; (*nextp); nextp = &(*nextp)->nextl)
if (*nextp == link)
break;
if (!*nextp) {
errno = ENOENT;
return -1;
}
(*nextp)->nextl = link->nextl;
if (link->logf) {
fprintf(link->logf, "%s: found link %p (fd %i)\n",
__func__, link, link->ch.fd);
}
close(ch->fd);
if (link->pid)
kill(link->pid, SIGINT);
if (link->flags & MPC_FLAG_SHMEM)
shmdt(link->memaddr);
if (link->flags & MPC_FLAG_DEVMEM)
munmap(link->memaddr, link->memsize);
/* Release allocated functions */
while (link->flist)
mpc_free_flist(link, link->flist);
free(link);
return 0;
}
int minipc_set_poll(int usec)
{
int ret = __mpc_poll_usec;
if (usec <= 0) {
errno = EINVAL;
return -1;
}
__mpc_poll_usec = usec;
return ret;
}
int minipc_set_logfile(struct minipc_ch *ch, FILE *logf)
{
struct mpc_link *link = mpc_get_link(ch);
CHECK_LINK(link);
link->logf = logf;
return 0;
}
/* the child for memory-based channels just polls */
void __minipc_child(void *addr, int fd, int flags)
{
int i;
uint32_t prev, *vptr;
struct mpc_shmem *shm = addr;
for (i = 0; i < 256; i++)
if (i != fd) close(i);
/* check the parent: if it changes, then we exit */
i = getppid();
/* the process must only send one byte when the value changes */
if (flags & MPC_FLAG_SERVER)
vptr = &shm->nrequest;
else
vptr = &shm->nreply;
prev = *vptr;
/* Ok, unlock the parent: we are ready */
write(fd, "-", 1);
while (1) {
if (*vptr != prev) {
write(fd, "", 1);
prev++;
}
if (getppid() != i)
exit(0);
usleep(__mpc_poll_usec);
}
}
/* helper function for memory-based channels */
static struct mpc_link *__minipc_memlink_create(struct mpc_link *link)
{
void *addr;
long offset;
int memsize, pid, ret;
int pagesize = getpagesize();
int pfd[2];
char msg;
memsize = (sizeof(struct mpc_shmem) + pagesize - 1) & ~pagesize;
/* Warning: no check for trailing garbage in name */
if (sscanf(link->name, "shm:%li", &offset)) {
ret = shmget(offset, memsize, IPC_CREAT | 0666);
if (ret < 0)
return NULL;
addr = shmat(ret, NULL, SHM_RND);
if (addr == (void *)-1)
return NULL;
link->flags |= MPC_FLAG_SHMEM;
}
/* Warning: no check for trailing garbage in name -- hex mandatory */
if (sscanf(link->name, "mem:%lx", &offset)) {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0)
return NULL;
addr = mmap(0, memsize, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, offset);
close(fd);
if (addr == (MAP_FAILED))
return NULL;
link->flags |= MPC_FLAG_DEVMEM;
}
link->memaddr = addr;
link->memsize = memsize;
if (link->flags & MPC_FLAG_SERVER)
memset(addr, 0, sizeof(struct mpc_shmem));
/* fork a polling process */
if (pipe(pfd) < 0)
goto err_unmap;
switch ( (pid = fork()) ) {
case 0: /* child */
close(pfd[0]);
__minipc_child(addr, pfd[1], link->flags);
exit(1);
default: /* father */
close(pfd[1]);
link->ch.fd = pfd[0];
link->pid = pid;
/* Before operating, wait for the child to ping us */
read (pfd[0], &msg, 1); /* must be '-' ... check? */
return link;
case -1:
break; /* error... */
}
close(pfd[0]);
close(pfd[1]);
err_unmap:
if (link->flags & MPC_FLAG_SHMEM)
shmdt(link->memaddr);
if (link->flags & MPC_FLAG_DEVMEM)
munmap(link->memaddr, link->memsize);
return NULL;
}
/* create a link, either server or client */
struct minipc_ch *__minipc_link_create(const char *name, int flags)
{
struct mpc_link *link, *next;
struct sockaddr_un sun;
int fd, i;
link = calloc(1, sizeof(*link));
if (!link) return NULL;
link->magic = MPC_MAGIC;
link->flags = flags;
strncpy(link->name, name, sizeof(link->name) -1);
/* special-case the memory-based channels */
if (!strncmp(name, "shm:", 4) || !strncmp(name, "mem:", 4)) {
if (!__minipc_memlink_create(link))
goto out_free;
goto out_success;
}
/* now create the socket and prepare the service */
fd = socket(SOCK_STREAM, AF_UNIX, 0);
if(fd < 0)
goto out_free;
link->ch.fd = fd;
sun.sun_family = AF_UNIX;
strcpy(sun.sun_path, MINIPC_BASE_PATH);
strcat(sun.sun_path, "/");
strcat(sun.sun_path, link->name);
mkdir(MINIPC_BASE_PATH, 0777); /* may exist, ignore errors */
if (flags & MPC_FLAG_SERVER) {
unlink(sun.sun_path);
if (bind (fd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
goto out_close;
if (listen(fd, 5) < 0)
goto out_close;
} else { /* client */
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
goto out_close;
}
/* success: fix your fd values, link to the list and return */
out_success:
if (flags & MPC_FLAG_SERVER) {
for (i = 0; i < MINIPC_MAX_CLIENTS; i++)
link->fd[i] = -1;
FD_ZERO(&link->fdset);
FD_SET(link->ch.fd, &link->fdset);
}
link->addr = sun;
next = __mpc_base;
link->nextl = __mpc_base;
__mpc_base = link;
return &link->ch;
out_close:
close(fd);
out_free:
free(link);
return NULL;
}
/*
* Private definition for mini-ipc
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __MINIPC_INT_H__
#define __MINIPC_INT_H__
#include <sys/types.h>
#if __STDC_HOSTED__ /* freestanding servers have less material */
#include <sys/un.h>
#include <sys/select.h>
#endif
#include "minipc.h"
/* be safe, in case some other header had them slightly differntly */
#undef container_of
#undef offsetof
#undef ARRAY_SIZE
/* We are strongly based on container_of internally */
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/*
* While public symbols are minipc_* internal ones are mpc_* to be shorter.
* The connection includes an fd, which is the only thing returned back
*/
/* The list of functions attached to a service */
struct mpc_flist {
const struct minipc_pd *pd;
struct mpc_flist *next;
};
/*
* The main server or client structure. Server links have client sockets
* hooking on it.
*/
struct mpc_link {
struct minipc_ch ch;
int magic;
int pid;
int flags;
struct mpc_link *nextl;
struct mpc_flist *flist;
void *memaddr;
int memsize;
#if __STDC_HOSTED__ /* these fields are not used in freestanding uC */
FILE *logf;
struct sockaddr_un addr;
int fd[MINIPC_MAX_CLIENTS];
fd_set fdset;
#endif
char name[MINIPC_MAX_NAME];
};
#define MPC_MAGIC 0xc0ffee99
#define MPC_FLAG_SERVER 0x00010000
#define MPC_FLAG_CLIENT 0x00020000
#define MPC_FLAG_SHMEM 0x00040000
#define MPC_FLAG_DEVMEM 0x00080000
#define MPC_USER_FLAGS(x) ((x) & 0xffff)
/* The request packet being transferred */
struct mpc_req_packet {
char name[MINIPC_MAX_NAME];
uint32_t args[MINIPC_MAX_ARGUMENTS];
};
/* The reply packet being transferred */
struct mpc_rep_packet {
uint32_t type;
uint8_t val[MINIPC_MAX_REPLY];
};
/* A structure for shared memory (takes more than 2kB) */
struct mpc_shmem {
uint32_t nrequest; /* incremented at each request */
uint32_t nreply; /* incremented at each reply */
struct mpc_req_packet request;
struct mpc_rep_packet reply;
};
#define MPC_TIMEOUT 1000 /* msec, hardwired */
static inline struct mpc_link *mpc_get_link(struct minipc_ch *ch)
{
return container_of(ch, struct mpc_link, ch);
}
#define CHECK_LINK(link) /* Horrible shortcut, don't tell around... */ \
if ((link)->magic != MPC_MAGIC) { \
errno = EINVAL; \
return -1; \
}
extern struct mpc_link *__mpc_base;
extern void mpc_free_flist(struct mpc_link *link, struct mpc_flist *flist);
extern struct minipc_ch *__minipc_link_create(const char *name, int flags);
/* Used for lists and structures -- sizeof(uint32_t) is 4, is it? */
#define MINIPC_GET_ANUM(len) (((len) + 3) >> 2)
#endif /* __MINIPC_INT_H__ */
/*
* Mini-ipc: Exported functions for freestanding server
*
* Copyright (C) 2012 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
*
* This replicates some code of minipc-core and minipc-server.
* It implements the functions needed to make a freestanding server
* (for example, an lm32 running on an FPGA -- the case I actually need).
*/
#include "minipc-int.h"
#include <string.h>
#include <sys/errno.h>
/* HACK: use a static link, one only */
static struct mpc_link __static_link;
/* The create function just picks an hex address from the name "mem:AABBCC" */
struct minipc_ch *minipc_server_create(const char *name, int flags)
{
struct mpc_link *link = &__static_link;
int i, c, addr = 0;
int memsize = sizeof(struct mpc_shmem);
if (link->magic) {
errno = EBUSY;
return NULL;
}
/* Most code from __minipc_link_create and __minipc_memlink_create */
flags |= MPC_FLAG_SERVER;
if (strncmp(name, "mem:", 4)) {
errno = EINVAL;
return NULL;
}
/* Ok, valid name. Hopefully */
link->magic = MPC_MAGIC;
link->flags = flags;
strncpy(link->name, name, sizeof(link->name) -1);
/* Parse the hex address */
for (i = 4; (c = name[i]); i++) {
addr *= 0x10;
if (c >= '0' && c <= '9') addr += c - '0';
if (c >= 'a' && c <= 'f') addr += c - 'a' + 10;
if (c >= 'A' && c <= 'F') addr += c - 'A' + 10;
}
link->flags |= MPC_FLAG_SHMEM; /* needed? */
link->memaddr = (void *)addr;
link->memsize = memsize;
link->pid = 0; /* hack: nrequest */
if (link->flags & MPC_FLAG_SERVER)
memset(link->memaddr, 0, memsize);
return &link->ch;
}
/* Close only marks the link as available */
int minipc_close(struct minipc_ch *ch)
{
struct mpc_link *link = mpc_get_link(ch);
CHECK_LINK(link);
link->magic = 0; /* available */
return 0;
}
/* HACK: use a static array of flist, to avoid malloc and free */
static struct mpc_flist __static_flist[MINIPC_MAX_EXPORT];
static void *calloc(size_t unused, size_t unused2)
{
int i;
struct mpc_flist *p;
for (p = __static_flist, i = 0; i < MINIPC_MAX_EXPORT; p++, i++)
if (!p->pd)
break;
if (i == MINIPC_MAX_EXPORT) {
errno = ENOMEM;
return NULL;
}
return p;
}
static void free(void *ptr)
{
struct mpc_flist *p = ptr;
p->pd = NULL;
}
/* From: minipc-core.c, but relying on fake free above */
void mpc_free_flist(struct mpc_link *link, struct mpc_flist *flist)
{
struct mpc_flist **nextp;
/* Look for flist and release it*/
for (nextp = &link->flist; (*nextp); nextp = &(*nextp)->next)
if (*nextp == flist)
break;
if (!*nextp) {
return;
}
*nextp = flist->next;
free(flist);
}
/* From: minipc-server.c -- but no log and relies on fake calloc above */
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
flist = calloc(1, sizeof(*flist));
if (!flist)
return -1;
flist->pd = pd;
flist->next = link->flist;
link->flist = flist;
return 0;
}
/* From: minipc-server.c -- but no log file */
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
/* We must find the flist that points to pd */
for (flist = link->flist; flist; flist = flist->next)
if (flist->pd == pd)
break;
if (!flist) {
errno = EINVAL;
return -1;
}
flist = container_of(&pd, struct mpc_flist, pd);
mpc_free_flist(link, flist);
return 0;
}
/* From: minipc-server.c */
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype)
{
int asize;
char *s = (void *)arg;
if (MINIPC_GET_ATYPE(atype) != MINIPC_ATYPE_STRING)
asize = MINIPC_GET_ANUM(MINIPC_GET_ASIZE(atype));
else
asize = MINIPC_GET_ANUM(strlen(s) + 1);
return arg + asize;
}
/* From: minipc-server.c (mostly: mpc_handle_client) */
int minipc_server_action(struct minipc_ch *ch, int timeoutms)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_req_packet *p_in;
struct mpc_rep_packet *p_out;
struct mpc_shmem *shm = link->memaddr;
const struct minipc_pd *pd;
struct mpc_flist *flist;
int i;
CHECK_LINK(link);
/* keep track of the request in an otherwise unused field */
if (shm->nrequest == link->pid)
return 0;
link->pid = shm->nrequest;
p_in = &shm->request;
p_out = &shm->reply;
/* use p_in->name to look for the function */
for (flist = link->flist; flist; flist = flist->next)
if (!(strcmp(p_in->name, flist->pd->name)))
break;
if (!flist) {
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = EOPNOTSUPP;
goto send_reply;
}
pd = flist->pd;
/* call the function and send back stuff */
i = pd->f(pd, p_in->args, p_out->val);
if (i < 0) {
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = errno;
} else {
/* Use retval, but fix the length for strings */
if (MINIPC_GET_ATYPE(pd->retval) == MINIPC_ATYPE_STRING) {
int size = strlen((char *)p_out->val) + 1;
size = (size + 3) & ~3; /* align */
p_out->type =
__MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, size);
} else {
p_out->type = pd->retval;
}
}
send_reply:
shm->nreply++; /* message already in place */
return 0;
}
/*
* Mini-ipc: Exported functions for server operation
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas and code by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include "minipc-int.h"
/*
* This function creates a server structure and links it to the
* process-wide list of links
*/
struct minipc_ch *minipc_server_create(const char *name, int f)
{
return __minipc_link_create(name, MPC_USER_FLAGS(f) | MPC_FLAG_SERVER);
}
/*
* The following ones add to the export list and remove from it
*/
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
flist = calloc(1, sizeof(*flist));
if (!flist)
return -1;
flist->pd = pd;
flist->next = link->flist;
link->flist = flist;
if (link->logf)
fprintf(link->logf, "%s: exported %p (%s) with pd %p --"
" retval %08x, args %08x...\n", __func__,
flist, pd->name, pd, pd->retval, pd->args[0]);
return 0;
}
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd)
{
struct mpc_link *link = mpc_get_link(ch);
struct mpc_flist *flist;
CHECK_LINK(link);
/* We must find the flist that points to pd */
for (flist = link->flist; flist; flist = flist->next)
if (flist->pd == pd)
break;
if (!flist) {
if (link->logf)
fprintf(link->logf, "%s: not found pd %p\n",
__func__, pd);
errno = EINVAL;
return -1;
}
flist = container_of(&pd, struct mpc_flist, pd);
mpc_free_flist(link, flist);
return 0;
}
/* Return the current fdset associated to the service */
int minipc_server_get_fdset(struct minipc_ch *ch, fd_set *setptr)
{
struct mpc_link *link = mpc_get_link(ch);
CHECK_LINK(link);
*setptr = link->fdset;
return 0;
}
/* Get a pointer to the next argument in the array */
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype)
{
int asize;
char *s = (void *)arg;
if (MINIPC_GET_ATYPE(atype) != MINIPC_ATYPE_STRING)
asize = MINIPC_GET_ANUM(MINIPC_GET_ASIZE(atype));
else
asize = MINIPC_GET_ANUM(strlen(s) + 1);
return arg + asize;
}
/*
* Internal functions used by server action below: handle a request
* or the arrival of a new client
*/
static void mpc_handle_client(struct mpc_link *link, int pos, int fd)
{
struct mpc_req_packet *p_in, _pkt_in;
struct mpc_rep_packet *p_out, _pkt_out;
struct mpc_shmem *shm = link->memaddr;
const struct minipc_pd *pd;
struct mpc_flist *flist;
int i;
if (shm) {
p_in = &shm->request;
p_out = &shm->reply;
/* read one byte, it's just a signal */
read(fd, &i, 1);
} else {
p_in = & _pkt_in;
p_out = & _pkt_out;
/* receive the packet and manage errors */
i = recv(fd, p_in, sizeof(*p_in), 0);
if (i < 0 && errno == EINTR)
return;
if (i <= 0)
goto close_client;
}
/* use p_in->name to look for the function */
for (flist = link->flist; flist; flist = flist->next)
if (!(strcmp(p_in->name, flist->pd->name)))
break;
if (!flist) {
if (link->logf)
fprintf(link->logf, "%s: function %s not found\n",
__func__, p_in->name);
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = EOPNOTSUPP;
goto send_reply;
}
pd = flist->pd;
if (link->logf)
fprintf(link->logf, "%s: request for %s\n",
__func__, pd->name);
/* call the function and send back stuff */
i = pd->f(pd, p_in->args, p_out->val);
if (i < 0) {
p_out->type = MINIPC_ARG_ENCODE(MINIPC_ATYPE_ERROR, int);
*(int *)(&p_out->val) = errno;
} else {
/* Use retval, but fix the length for strings */
if (MINIPC_GET_ATYPE(pd->retval) == MINIPC_ATYPE_STRING) {
int size = strlen((char *)p_out->val) + 1;
size = (size + 3) & ~3; /* align */
p_out->type =
__MINIPC_ARG_ENCODE(MINIPC_ATYPE_STRING, size);
} else {
p_out->type = pd->retval;
}
}
send_reply:
if (shm) {
shm->nreply++; /* message already in place */
return;
}
/* send a 32-bit value plus the declared return length */
if (send(fd, p_out, sizeof(p_out->type)
+ MINIPC_GET_ASIZE(p_out->type), MSG_NOSIGNAL) < 0)
goto close_client;
return;
close_client:
if (link->logf)
fprintf(link->logf, "%s: error %i in fd %i, closing\n",
__func__, i < 0 ? errno : 0, fd);
close(fd);
FD_CLR(fd, &link->fdset);
link->fd[pos] = -1;
return;
}
static void mpc_handle_connection(struct mpc_link *link, int fd)
{
int i, newfd;
struct sockaddr_un sun;
socklen_t slen = sizeof(sun);
newfd = accept(fd, (struct sockaddr *)&sun, &slen);
if (link->logf)
fprintf(link->logf, "%s: accept returned fd %i (error %i)\n",
__func__, newfd, newfd < 0 ? errno : 0);
if (newfd < 0)
return;
/* Lookf for a place for this */
for (i = 0; i < MINIPC_MAX_CLIENTS; i++)
if (link->fd[i] < 0)
break;
if (i == MINIPC_MAX_CLIENTS) {
if (link->logf)
fprintf(link->logf, "%s: refused: too many clients\n",
__func__);
close(newfd);
return;
}
link->fd[i] = newfd;
FD_SET(newfd, &link->fdset);
}
/*
* The server action returns an error or zero. If the user wants
* the list of active descriptors, it must ask for the filemask
* (at this point there is no support for poll, only select)
*/
int minipc_server_action(struct minipc_ch *ch, int timeoutms)
{
struct mpc_link *link = mpc_get_link(ch);
struct timeval to;
fd_set set;
int i;
CHECK_LINK(link);
to.tv_sec = timeoutms/1000;
to.tv_usec = (timeoutms % 1000) * 1000;
set = link->fdset;
i = select(64 /* FIXME: hard constant */, &set, NULL, NULL, &to);
if (!i)
return 0;
if (i < 0 && errno == EINTR)
return 0;
if (i < 0)
return -1;
if (i == 0) {
errno = EAGAIN;
return -1;
}
/* A shmem server has only one descriptor, for one client */
if (link->memaddr) {
mpc_handle_client(link, -1, ch->fd);
return 0;
}
/* First, look for all clients */
for (i = 0; i < MINIPC_MAX_CLIENTS; i++) {
if (link->fd[i] < 0)
continue;
if (!FD_ISSET(link->fd[i], &set))
continue;
mpc_handle_client(link, i, link->fd[i]);
}
/* Finally, look for a new client */
if (FD_ISSET(ch->fd, &set))
mpc_handle_connection(link, ch->fd);
return 0;
}
/*
* Public definition for mini-ipc
*
* Copyright (C) 2011 CERN (www.cern.ch)
* Author: Alessandro Rubini <rubini@gnudd.com>
* Based on ideas by Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __MINIPC_H__
#define __MINIPC_H__
#include <stdint.h>
#if __STDC_HOSTED__ /* freestanding servers have less material */
#include <stdio.h>
#include <sys/select.h>
#endif
/* Hard limits */
#define MINIPC_MAX_NAME 20 /* includes trailing 0 */
#define MINIPC_MAX_CLIENTS 64
#define MINIPC_MAX_ARGUMENTS 256 /* Also, max size of packet words -- 1k */
#define MINIPC_MAX_REPLY 1024 /* bytes */
#if !__STDC_HOSTED__
#define MINIPC_MAX_EXPORT 12 /* freestanding: static allocation */
#endif
/* The base pathname, mkdir is performed as needed */
#define MINIPC_BASE_PATH "/tmp/.minipc"
/* Default polling interval for memory-based channels */
#define MINIPC_DEFAULT_POLL (10*1000)
/* Argument type (and retval type). The size is encoded in the same word */
enum minipc_at {
MINIPC_ATYPE_ERROR = 0xffff,
MINIPC_ATYPE_NONE = 0, /* used as terminator */
MINIPC_ATYPE_INT = 1,
MINIPC_ATYPE_INT64,
MINIPC_ATYPE_DOUBLE, /* float is promoted to double */
MINIPC_ATYPE_STRING, /* size of strings is strlen() each time */
MINIPC_ATYPE_STRUCT
};
/* Encoding of argument type and size in one word */
#define __MINIPC_ARG_ENCODE(atype, asize) (((atype) << 16) | (asize))
#define MINIPC_ARG_ENCODE(atype, type) __MINIPC_ARG_ENCODE(atype, sizeof(type))
#define MINIPC_GET_ATYPE(word) ((word) >> 16)
#define MINIPC_GET_ASIZE(word) ((word) & 0xffff)
#define MINIPC_ARG_END __MINIPC_ARG_ENCODE(MINIPC_ATYPE_NONE, 0) /* zero */
/* The exported procedure looks like this */
struct minipc_pd;
typedef int (minipc_f)(const struct minipc_pd *, uint32_t *args, void *retval);
/* This is the "procedure definition" */
struct minipc_pd {
minipc_f *f; /* pointer to the function */
char name[MINIPC_MAX_NAME]; /* name of the function */
uint32_t flags;
uint32_t retval; /* type of return value */
uint32_t args[]; /* zero-terminated */
};
/* Flags: verbosity is about argument and retval marshall/unmarshall */
#define MINIPC_FLAG_VERBOSE 1
/* This is the channel definition */
struct minipc_ch {
int fd;
};
static inline int minipc_fileno(struct minipc_ch *ch) {return ch->fd;}
/* These return NULL with errno on error, name is the socket pathname */
struct minipc_ch *minipc_server_create(const char *name, int flags);
struct minipc_ch *minipc_client_create(const char *name, int flags);
int minipc_close(struct minipc_ch *ch);
/* Generic: set the default polling interval for mem-based channels */
int minipc_set_poll(int usec);
/* Server: register exported functions */
int minipc_export(struct minipc_ch *ch, const struct minipc_pd *pd);
int minipc_unexport(struct minipc_ch *ch, const struct minipc_pd *pd);
/* Server: helpers to unmarshall a string or struct from a request */
uint32_t *minipc_get_next_arg(uint32_t arg[], uint32_t atype);
/* Handle a request if pending, otherwise -1 and EAGAIN */
int minipc_server_action(struct minipc_ch *ch, int timeoutms);
#if __STDC_HOSTED__
/* Generic: attach diagnostics to a log file */
int minipc_set_logfile(struct minipc_ch *ch, FILE *logf);
/* Return an fdset for the user to select() on the service */
int minipc_server_get_fdset(struct minipc_ch *ch, fd_set *setptr);
/* Client: make requests */
int minipc_call(struct minipc_ch *ch, int millisec_timeout,
const struct minipc_pd *pd, void *ret, ...);
#endif /* __STDC_HOSTED__ */
#endif /* __MINIPC_H__ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment