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.