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.

Reply via email to