AF:
NF:0
PS:10
SRH:1
SFN:
DSR:
MID:<20070404142733.1f4893e8@ripper.onstor.net>
CFG:
PT:0
S:andy.sharp@onstor.com
RQ:
SSV:onstor-exch02.onstor.net
NSV:
SSH:
R:<maxim.kozlovsky@onstor.com>,<jonathan.goldick@onstor.com>
MAID:1
X-Sylpheed-Privacy-System:
X-Sylpheed-Sign:0
SCF:#mh/Mailbox/sent
X-Sylpheed-End-Special-Headers: 1
Date: Wed, 4 Apr 2007 14:28:12 -0700
From: Andrew Sharp <andy.sharp@onstor.com>
To: Maxim Kozlovsky <maxim.kozlovsky@onstor.com>, Jonathan Goldick
 <jonathan.goldick@onstor.com>
Subject: porting cookbook pre-alpha version
Message-ID: <20070404142812.44085100@ripper.onstor.net>
Organization: Onstor
X-Mailer: Sylpheed-Claws 2.6.0 (GTK+ 2.8.20; x86_64-pc-linux-gnu)
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="MP_PK4jZCHuvY+=FXv0_Obi4yR"

--MP_PK4jZCHuvY+=FXv0_Obi4yR
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Just in .txt file right now.  Tell me what you think.  Is it clear, are
big things assumed or left out ....

Thanks,

a

--MP_PK4jZCHuvY+=FXv0_Obi4yR
Content-Type: text/plain; name=porting-cookbook.txt
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename=porting-cookbook.txt



Porting cookbook - Onstor Software Engineering

Part 1: Creating a portable code base v. something that compiles in
multiple environments.

The initial temptation when porting some code is just to get it to
compile by adding ifdef's here and there.  This works, but if you ever
have to port the code again, you have not gained any ground for your
effort.  The idea then is to make the code portable instead.  This
means creating a framework whereby the code can easily and quickly be
ported again to another platform or environment (simulator?).

The trick to making code portable is to not use ifdefs, but rather use
wrapper functions and macros, and have the makefile system fix
everything up for you automagically.  Here is an example:

Suppose you have a code snippet that calls a system call that has
different arguments on OpenBSD and Linux.  Furthermore, it has
different meaning for the return value as well.  So you have to handle
both of these.  Let's say our system call, sys_foo() has the following
symantics on the two operating systems:

OpenBSD:

		int sys_foo(struct fubar *bar);

where the return type is a value that you want, or -1 if there was
and error.

Linux:

		int sys_foo(struct fubar *bar, int mask, int *val);

where the return value means 0 for success, non-zero for failure,
and the value that you want is returned in the variable pointed to
by val.

You have the following bit of code you now need to make portable:

		int val;
		struct fubar bar;
		int rc;

		val = sys_foo(&bar);

		if (val == -1) {
			rc = errno;
			val = 0;
		} else {
			rc = function(val);
		}

		return rc;


You could use ifdefs:


		int val;
		struct fubar bar;
		int rc;

	#ifdef OPENBSD
		val = sys_foo(&bar);

		if (val == -1) {
			rc = errno;
		} else {
			rc = function(val);
		}
	#elif defined(LINUX)
		rc = sys_foo(&bar, 0xdeadbeef, &val);

		if (rc != 0) {
			rc = errno;
		} else {
			rc = function(val);
		}
	#endif

		return rc;


But then the next time you need to port this code, you would have to
do the work all over again, changing all your main source files.

To make it portable, you would use a wrapper function instead.  Here
I've used a function of the same name except with "os_" (for OnStor)
prepended to indicate what the function is and that this is a wrapper
around it.  Adopting a consistent naming scheme for wrappers will help
maintainers to identify wrapper functions vs. regular functions and
macros.


		int val;
		struct fubar bar;
		int rc;

		rc = os_sys_foo(&bar, 0xdeadbeef, &val);

		if (rc == 0) {
			rc = function(val);
		}

		return rc;

In OpenBSD the wrapper function would be:

	/*
	 * I'm reusing the 'mask' argument because we don't use it in OpenBSD
	 */
	int os_sys_foo(struct fubar *b, int mask, int *v)
	{
		mask = sys_foo(b);
		if (mask == -1) {
			mask = errno;
			*v = 0; /* just in case -- we don't know what sys_foo does */
		} else {
			*v = mask;
			mask = 0;
		}

		return mask;
	}

In Linux, the wrapper function would be:

	int os_sys_foo(struct fubar *b, int m, int *v)
	{
		int rc;

		rc = sys_foo(b, m, v);

		if (rc != 0) {
			rc = errno;
			*v = 0; /* just in case -- we don't know what sys_foo does */
		}

		return rc;
	}


Now, in this rather simple case, you could also have used a macro,
since all the variables necessary for both versions already exist in
the function.

So you probably get the idea, and you've probably done something like
it before.  The question remains, however, how do I get the right
wrapper function selected and compiled and linked in without an ifdef
somewhere?

The basic principal is that the makefiles know all, and do the heavy
lifting for you.  The way this works is that in any directory where
you have a logical building module, say nfx-tree/code/ssc-nfxsh, for
example, you might have any of the following files as necessary:

Linux.h
OpenBSD.h
Linux.c
OpenBSD.c

Your wrapper functions (unless they are inlines) would go in the
respective .c files, and macros and other selective includes would go
in the .h files.

The .h files are heirachical, so that macro and inline function
wrappers that are universal or widely utilized, would be defined in a
higher level .h file of the same name, and the lower level file will
always include the higher level file.  So, in nfx-tree/code/ssc-nfxsh,
the Linux.h file, if there is one, would have the following include:

.
.
#include <Linux.h>
.
.

Assuming, that is, that the path where that file is found is part of
the include search list specified by the -I compiler option.  In our
case, we probably only have one higher level, and that's the Includes
directory, but I could be wrong.  If there is more than one level,
each include file only includes the next higher level file.

The first part of the name of the files you'll notice I've
capitalized, but isn't necessarily how we'll do it.  However, that is
handy if you are using the uname command in the makefiles to determine
which system you are on, without using ifdefs in the Makefile:

	PLAT=`uname -s`
	include $(PLAT).mk
or, if there is no local $(PLAT).mk, then this would be
	include $(TOP)/$(PLAT).mk
otherwise the local $(PLAT).mk would include $(TOP)/$(PLAT).mk

The $(PLAT).mk file would have some of the following in it:

	CFLAGS += -DPLAT_INC="$(PLAT).h"
and
	SRCS += $(PLAT).c

In a .c file that is a main source file, you would have the following
include:

	#include PLAT_INC

That way, the main source file would include whichever .h file was
appropriate for the operating system platform it was being built for.

Depending on how our makefiles currently operate, they may have to be
modified to output binaries to other directories (subdirectories or a
different tree) so that you could build for both platforms with fear
of mixing things up.  The makefile might look something like this:

	SRCS = foo.c bar.c
	LIBS = $(LIBS)

	include $(PLAT).mk

	BINS = $(SRCS: $(PLAT)/$(PROD)/%.o=%.c)
	$(BINS): depends

	.c.o:
		$(CC) $(CFLAGS) $< -o $(PLAT)/$(PROD)/$@

	$(PLAT)/$(PROD)/foo: $(BINS)
		$(CC) $(LFLAGS) $(BINS) $(LIBS) -o $(PLAT)/$(PROD)/foo

	install: $(PLAT)/$(PROD)/foo
		install $(PLAT)/$(PROD)/foo $(INSTALL_DIR)


This forms the basics of a portable code base/framework.  Then, if you
have to port your code again in the future, you simply have to add the
.h and .c files for your new platform, and it all just works(tm).  You
don't have to modify your makefiles or anything else.  Sweet.

--MP_PK4jZCHuvY+=FXv0_Obi4yR--
