On Sun, 19 Jun 2005 16:23:16 +1000, Darren Tucker wrote:
>Chris Zakelj wrote:
>> I'm curious as to how programs actually get ported from one OS to
>> another,
>
>Yes, some techniques make the job easier, but it depends on what the
>program does and whether you're doing a one-way port or an ongoing port.
> The following aren't necessarily the only ways to do porting, but it's
>how the Open* Portable projects are done.
>
>If it's a one-way port (ie the port will be done once and thereafter
>maintained separately), the usual method is to just change what needs
>changing to suit the target platform. This is effectively what happened
>when the original ssh-1.2.12 code was 'ported' to OpenBSD.
>
>In the case of OpenSSH and OpenNTPD Portable, they're ongoing ports (ie
>changes are regularly sync'ed from OpenBSD's to Portable's). There are
>2 main ways to accomplish this: sprinkle the code with #ifdefs or
>implement the missing functionality in a compatibilty layer.
>
>In the Portable projects, the first preference is leave the common code
>alone and implement the required functionality in a portability layer.
>This has the advantage of keeping the common code clean and if done
>properly the components are reusable. (A number of functions used by
>OpenNTPD Portable came unmodified from OpenSSH Portable). Sometimes
>that's not possible or more effort than it's worth, so in those cases an
>#ifdef is used which imposes an ongoing maintenance cost (ie next time a
>change is made in that area in the main code, you'll have to manually
>resolve conflict when syncing changes).
>
>For example: OpenNTPD Portable was ported to run on QNX4 (a POSIXish
>embedded system) by Anthony O.Zabelin. The 2 main missing pieces were
>the adjtime() and poll() calls. Simplifying somewhat, the code that
>used adjtime looked like this:
>
> d_to_tv(d, &tv);
> if (adjtime(&tv, NULL) == -1)
> log_warn("adjtime failed");
>
>If we had used the #ifdef technique, that would have changed to
>something like this:
>
>#ifndef __QNX__
> d_to_tv(d, &tv);
> if (adjtime(&tv, NULL) == -1)
> log_warn("adjtime failed");
>#else
> usec = (int)(d * 1000000);
> if (qnx_adj_time(usec, ADJUST_RATE, NULL, NULL) == -1)
> log_warn("qnx_adj_time failed");
>#endif
>
>Now one or two of those aren't too bad, but it rapidly becomes difficult
>to follow once you add a few more to the same piece of code.
>
>Instead, Anthony wrote a stand-alone replacement adjtime() function
>which is in the portability layer (openbsd-compat/port-qnx.c). This had
>a higher initial cost (it's 23 lines of code in a single function plus a
>Makefile change instead of 6 lines listed above) but it leaves the main
>code unchanged. It can also be tested separately and is reusable.
>
>I took the same approach with poll() and built a replacement on top of
>select(), which QNX4 did have. Hey presto, it now worked on QNX4, and
>the codebase is no harder to maintain.
>
>> and if certain directions are easier than others.
>
>In general, the difficulty is directly proportional to how different the
>target platform is compared to the platforms already supported in the
>area in which the program operates. In most current OSes there's a slow
>convergance toward common APIs for standard languages so if you stick to
>those standard APIs you life will be easier.
>
>Newer/more featureful -> older/less featureful is usually harder than
>the other way around unless the program was originally written to stick
>to a common subset.
>
>Beyond that it depends on the program. Porting a simple text filter
>from a bleeding-edge Linux to 10-year old BSD is likely to be simple,
>but other programs may be difficult to impossible.
>
>> That is, how does one figure out what needs to be changed in order to
>> make OpenNTPD work on Linux?
>
>I had the advantage of having worked on Portable OpenSSH for a couple of
>years so had a reasonable idea what to expect, so for OpenNTPD I just
>copied the code onto a Linux box, hacked the BSDisms out of the Makefile
>and tried to compile it. This highlighted some obvious problems (eg
>missing strl* functions, the lack of sa_len in struct sockaddr). I
>fixed these (stole the strl* functions from OpenSSH and changed
>sa->sa_len into SA_LEN(sa)) and tried again. After a few iterations of
>this process it compiled and after a couple more, amazingly enough, it
>worked. At that point I added basic autoconf support, put a tarball on
>my web page and mentioned it on [EMAIL PROTECTED]
>
>After that, other folks and myself repeated the process on other
>platforms, slowly expanding the list that it would run on. (The
>platform list on openntpd.org is in chronological order, earliest first).
>
>> Is it generally easier to move a program from $some_bsd
>> to $some_other_os, or from $some_other_os to $some_bsd?
>
>Depends on the type of program, and in particular what OS-level services
>it uses.
>
>OpenSSH, for example, has to deal with user authentication for which
>there is a large amount of variance between platforms, so the diff
>between OpenBSD's and Portable's is large.
>
>OpenNTPD has to deal with far less variance between platforms so the
>diff is much smaller. (adjtime() is common and the interface is simple,
>but if/when it starts compensating for systematic clock skew then that
>will introduce a significant amount of platform-specific code, however
>most of that can be hidden in the compatibility layer.)
>
>> How would you even begin to port something like OpenSSH to a non-Unix
>> system like Windows?
>
>I would say you would need a POSIX-like compatibility layer (eg external
>like Cygwin or implement your own), otherwise you would probably have to
>do a one-way port. In that case you could keep the protocol code as is,
>but you would probably need to replace large chunks of the rest.
>
>> Does the chosen language (C, C++, Java, etc) make a difference
>> in difficulty?
>
>I don't use C++ or Java so I won't comment on them, but IMO the language
>can a make significant difference, but only for some things.
>
>It is possible to write portable C. In the case of OpenSSH, most of the
>platform-dependant code is there because the operating system requires
>it, not the language, and unless the language provides an abstraction
>for exactly what you're trying to do then the choice of language makes
>little difference.
>
>For example (and I'm oversimplifying here), validating a user's
>password: on a traditional Unix system you encrypt the password and
>compare against what getpwnam() returns. If your system has shadow
>passwords, though, you need to use getspnam() since the encrypted
>password isn't returned by getpwnam() on those. Oh, and some really old
>systems based on SecureWare have another function (getprpwnam). (This
>is ignoring platform-specific functions and the attempts to standardize
>this in libraries such as BSD auth or PAM, which have varying degrees of
>success.)
>
>If a language provided a "authenticate_user(user, password)" then that
>would help in this case, but most don't seem to (and there are many
>other examples of platform-dependant things you would need to do besides
>this one).
>
>Anyway, some guidelines to avoid common traps for portable C:
>
>1) write for correctness and clarity.
>
>2) try to stick to standard functions (eg POSIX).
>
>3) be prepared to implement your own replacements for non-standard
>functions you use.
>
>3b) or standard functions that aren't available on you target platform.
>
>3c) or standard functions that are broken on your target platform.
>
>4) if possible put all the system #includes in a single header and
>include that from all of your source files. Headers vary quite a bit
>and it's better if you only have to deal with all that variance once.
>
>5) use the datatypes you're supposed to. eg if you need to store a 32
>bit value, use "u_int32_t" not "unsigned int", if you're using it in a
>signal handler use "sig_atomic_t" not "int". Check and typedef it
>yourself if your platform doesn't.
>
>6) Turn on all the compiler warnings and fix what it warns about. Many
>are potential portability problems, even if they haven't bitten you yet.
>
>[This list isn't exhaustive, I'm sure other folks will be able to add to
>it.]
>
>> When I've built from ports, I can see make files doing
>> OS detection, but from there (not being a very good coder), I can't
>> really make out how it changes the code based on that. Any
>> recommendations for "casual programmer" books would be cool...
>
>A place to start would be this diff:
>http://www.zip.com.au/~dtucker/openntpd/patches/ntpd-vs-openbsd.diff
>It shows the (small) changes to the OpenBSD ntpd code (3.6.1) to make it
>"Portable". The remainder of the changes are in files that are only in
>Portable (take a look in openbsd-compat/ in the OpenNTPD portable
>distribution).
>
>After that, I suggest downloading the OpenBSD-specific ntpd and the
>equivalent Portable one and comparing them (diff -ru is your friend).
>It's small enough that it ought to be understandable but it's a real
>application that runs on 9 platforms (so far :-).
>
>--
>Darren Tucker (dtucker at zip.com.au)
>GPG key 8FF4FA69 / D9A3 86E9 7EEE AF4B B2D4 37C9 C982 80C7 8FF4 FA69
> Good judgement comes with experience. Unfortunately, the experience
>usually comes from bad judgement.
>
>
I wouldn't have had a reason to ask the question you answered - I think
I'm getting to be a bit too rusty to do stuff like that these days.
That said, I have to congratulate you on a wonderfully clear
description of the process as seen by a craftsman. Although it wouldn't
get me back to doing the same type of task as the one described, the
old synapses recognised that you were handing out a valuable lesson to
the up-and-coming coders we (or rather, non-OBSD users) depend on.
On behalf of the community -Thanks mate! Bloody well written!
In the beginning was The Word
and The Word was Content-type: text/plain
The Word of Rod.
Do NOT CC me - I am subscribed to the list.
Replies to the sender address will fail except from the list-server.