System administration

Compiling a five-way Universal Binary of Rsync 3 with LLVM-GCC on Leopard

The Xcode 3.1 Developer Tools install both the standard GNU compiler, gcc, and the LLVM compiler, llvm-gcc. I thought I’d try my hand at compiling something with LLVM, so naturally I decided to update my instructions for compiling Rsync 3 as a Universal Binary for Leopard. Let’s try Rsync 3.0.3!

The following set of instructions, of course, requires that you have already installed the Xcode 3.1, available as a download from Apple. To choose LLVM as your C compiler temporarily for this compile, specify the CC=“/Developer/usr/bin/llvm-gcc” variable (as explained in the LLVM FAQ).

$ cd /tmp
$ curl -O curl -O http://rsync.samba.org/ftp/rsync/rsync-3.0.3.tar.gz
$ tar -xzvf rsync-3.0.3.tar.gz
$ rm rsync-3.0.3.tar.gz
$ curl -O http://rsync.samba.org/ftp/rsync/rsync-patches-3.0.3.tar.gz
$ tar -xzvf rsync-patches-3.0.3.tar.gz
$ rm rsync-patches-3.0.3.tar.gz
$ cd rsync-3.0.3
$ patch -p1 <patches/fileflags.diff
$ patch -p1 <patches/crtimes.diff
$ ./prepare-source
$ env CFLAGS="-O -g -isysroot /Developer/SDKs/MacOSX10.5.sdk -arch i386 \
-arch ppc7400 -arch ppc970 -arch ppc64 -arch x86_64" LDFLAGS="-arch i386 \
-arch ppc7400 -arch ppc970 -arch ppc64 -arch x86_64" \
CC="/Developer/usr/bin/llvm-gcc" \
./configure --prefix=/usr/local --disable-dependency-tracking

$ make
$ ./rsync --version
rsync version 3.0.3 protocol version 30
Copyright (C) 1996-2008 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
64-bit files, 32-bit inums, 64-bit timestamps, 64-bit long ints,
socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
append, ACLs, xattrs, no iconv, symtimes, file-flags
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
are welcome to redistribute it under certain conditions. See the GNU
General Public Licence for details.
$ file ./rsync
./rsync: Mach-O universal binary with 5 architectures
./rsync (for architecture i386): Mach-O executable i386
./rsync (for architecture ppc7400): Mach-O executable ppc
./rsync (for architecture ppc970): Mach-O executable ppc
./rsync (for architecture ppc64): Mach-O 64-bit executable ppc64
./rsync (for architecture x86_64): Mach-O 64-bit executable x86_64
$ lipo -info ./rsync
Architectures in the fat file: ./rsync are: i386 ppc7400 ppc970 ppc64 x86_64
$ sudo make install

Warning: I have not tested Rsync 3 compiled with LLVM to ensure that it operates at all or is compatible with Rsync compiled by GCC. I haven’t compared the compilation speed against GCC’s performance, determined whether the files are larger/smaller, or have produced a more optimized binary. You use the software compiled with these instructions at your own risk.

Most of the original instructions were helpfully documented by Mike Bombich. My contributions are simply:

  • Universal Binary support: four- or five-way, depending on whether you want to use “-arch i386 -arch ppc7400 -arch ppc970 -arch ppc64 -arch x86_64” (which gives specific optimized binaries for PowerPC G4 and G5 systems in 32-bit mode) or “-arch i386 -arch ppc -arch ppc64 -arch x86_64” (for more general 32-bit PowerPC support, as I did in my previous article).
  • LLVM compilation: use of the llvm-gcc front end, which hopefully produces a working Rsync 3.

Finding and viewing the Test Print document in Mac OS X Leopard

When you have the print queue window open for a printer in Mac OS X Leopard, the Printer menu > Print Test Page command is active. That command sends the CUPS test page to the selected print queue.

The test page file itself is a PostScript document located at this path:

/usr/share/cups/data/testprint.ps

Since Leopard — like Mac OS X versions since Panther, has a built-in PostScript interpreter — you can open up this PostScript document and have it rendered as a PDF. By default, it will be sent to the Preview application.

It’s helpful to be able to view this document on-screen, so you can see what it is supposed to look like, in case your printer is not working as intended. You can compare the on-screen results with the printed output to verify how well your printer is working.

Is that site running Drupal, curl edition

The Lullabot Web site has a clever way to help answer the question of “Is that site running Drupal?” Angie Byron mentions that the HTTP “Expires” header returned by Drupal corresponds to a specific default date. Look for that date in the HTTP headers, and you can make a reasonable guess that a site is a Drupal site — or at least one that hasn’t modified one of the core files.

Some commenters posted notes about how to do the same thing with wget and then curl. I expanded on the curl instructions to make them a little more robust (especially in the case of redirects or URL rewriting), and here’s the result:

$ curl -fsIL http://jaharmi.com/ 2>&1 | grep -q -m 1 "Expires: Sun, 19 Nov 1978 05:00:00 GMT" && echo "Yes, this appears to be a Drupal site." || echo "No, this does not appear to be a Drupal site."
Yes, this appears to be a Drupal site.

Comparing Radmind command file timestamps between client and server

In trying to determine the age of the command file on a Radmind client — in this case, a Mac OS X 10.5 Leopard system — I learned that its modification date is the same as on the server. This is true on multiple clients I examined, so I believe it to be both consistent and intentional. It also makes sense.

What timestamp, then, can you use to determine how old your command file is, or when your client last updated?

Let’s look into this. For example, here is the command file on a client:

[RadmindClientA]$ ls -ul /private/var/radmind/client | grep command.K
-rw-r--r-- 1 root wheel 667 Jun 3 05:30 command.K
[RadmindClientA]$ ls -cl /private/var/radmind/client | grep command.K
-rw-r--r-- 1 root wheel 667 May 29 10:28 command.K
[RadmindClientA]$ ls -l /private/var/radmind/client | grep command.K
-rw-r--r-- 1 root wheel 667 May 29 10:21 command.K

And the same file examined on the server:

[RadmindServer]$ ls -ul /private/var/radmind/command | grep selected-client-command.K
-rw-r----- 1 root wheel 667 Jun 3 05:27 selected-client-command.K
[RadmindServer]$ ls -cl /private/var/radmind/command | grep selected-client-command.K
-rw-r----- 1 root wheel 667 May 29 10:21 selected-client-command.K
[RadmindServer]$ ls -l /private/var/radmind/command | grep selected-client-command.K
-rw-r----- 1 root wheel 667 May 29 10:21 selected-client-command.K

The status change time (ls -cl) is a little bit harder to understand. I’ve seen it minutes apart from the modification time on a client, but it is the same as the modification time when viewed on the server.

The access time (ls -ul) of the command file on the client, however, simply corresponds to when ktcheck last ran.

For good measure, another client:

[RadmindClientB]$ ls -ul /private/var/radmind/client/ | grep command.K
-rw-r--r-- 1 root wheel 1188 Jun 5 05:08 command.K
[RadmindClientB]$ ls -cl /private/var/radmind/client/ | grep command.K
-rw-r--r-- 1 root wheel 1188 Jun 3 14:13 command.K
[RadmindClientB]$ ls -l /private/var/radmind/client/ | grep command.K
-rw-r--r-- 1 root wheel 1188 Jun 3 14:09 command.K

And its command file on the server:

[RadmindServer]$ ls -ul /private/var/radmind/command | grep another-selected-client-command.K
-rw-r--r-- 1 root wheel 1188 Jun 4 05:13 another-selected-client-command.K
[RadmindServer]$ ls -cl /private/var/radmind/command | grep another-selected-client-command.K
-rw-r--r-- 1 root wheel 1188 Jun 3 14:09 another-selected-client-command.K
[RadmindServer]$ ls -l /private/var/radmind/command | grep another-selected-client-command.K
-rw-r--r-- 1 root wheel 1188 Jun 3 14:09 another-selected-client-command.K

I looked into this because I was wondering why ktcheck -C (to “clean” the /private/var/radmind/client directory) wasn’t having the effect I expected. I thought it cleared out the client directory and started with fresh files, which would all have new timestamps because they were new to the client. When I didn’t get the new timestamps I anticipated, I worked through what I saw until I settled on this explanation.

The result? The main means you have of determining whether the command file has been updated is its access time. The access timestamp corresponds with the date and time of the last ktcheck action. This appears to be the case no matter which flags I’ve used with ktcheck — even with ktcheck -n (where “no files are modified”).

The modification time, for all intents and purposes, only indicates when the file was last changed on the server. Testing this out — with command files that have been overwritten with the exact same contents (using cat), as well as command files deleted on the client before ktcheck — I have reached the conclusion that the modification time changes only when the file contents do.

So, I don’t have solid explanations for all of this. But, I’m now armed with the knowledge that the access timestamp is the most important when you’re looking for the age — or at least the last refresh time — of a Radmind client’s command file.

Limit Launchd LaunchAgents to specific session types in Leopard

In Leopard, launchd has some options that can tailor your now-working LaunchAgents for specific circumstances. For example, if you want your LaunchAgent to run only when a user has logged in at the Aqua console — rather than SSH or other login sessions — you can use the “LimitLoadToSessionType” key:

<key>LimitLoadToSessionType</key>
<string>Aqua</string>

Note that to find events that have been limited in this way, you must use launchctl’s “-S” flag to specify the session type. Otherwise, launchctl won’t find jobs that are specified for a different kind of session than the current one, as per Levi Brown’s post.

I also found the “LimitLoadToHosts” and “LimitLoadFromHosts” keys when looking this topic up in the launchd.plist man page.

Thanks to James Bucanek for mentioning this on the launchd-dev list.

Python to remove commands module and some Mac-specific modules

Drat! I’ve learned that the commands module for Python, which I use, is deprecated and removed in Python according to PEP 3108. That means I can no longer safely call commands.getstatusoutput() anymore. I’ve frequently used this call in the past because it seemed the sanest, easiest way to call for a shell utility and get both its output and return status (for success or error).

I’ll have to find some other way to perform the same function — preferably one that will work on Python 2.3 from Mac OS X Tiger, Python 2.5.1 from Leopard, and future Pythons. The stated replacement for a number of similar modules (including popen2, which frankly kind-of frightened me off with its name) is the subprocess module from PEP 324, but I don’t know if that will work for my purposes.

There are also a bunch of Mac-specific modules being removed. I don’t use any of them right now, but that doesn’t mean they wouldn’t have been useful.

This kind of thing is spirit-crushing to me for some reason. I’m especially annoyed that Python has been around for so long and it is still reorganizing the ways it calls shell commands. Just settle on something! It seems hard to take it seriously as a system administration scripting language when things like this happen.

On the other hand, I love so much of the Python Standard Library, which has afforded me a lot for system administration …

Radmind server logging and the repo command

Under normal circumstances, the latest Radmind tools that communicate with the server report client status updates in the Radmind server’s system log. These standard messages can include ones like:

May 8 03:14:56 RadmindServerHost radmind[7890]: report radmind-client.example.com 192.168.7.42 - - ktcheck No updates needed
May 15 03:15:25 RadmindServerHost radmind[24531]: report radmind-client.example.com 192.168.7.42 - - ktcheck Updates retrieved
May 15 03:21:48 RadmindServerHost radmind[24534]: report radmind-client.example.com 192.168.7.42 - - lapply Changes applied successfully
May 15 03:31:07 RadmindServerHost radmind[24356]: report radmind-client.example.com 192.168.7.42 CertificateCN - lapply Error, changes made

The Radmind repo, or “report,” tool provides the ability to send arbitrary messages to the Radmind server process. But how are these messages formatted and sent?

$ repo -e "Debug" -h radmindserverhost.example.com -w2 "Test message"

… results in the system log message:

May 15 03:31:56 RadmindServerHost radmind[25236]: report radmind-client.example.com 192.168.7.42 CertificateCN - Debug Test message

Here, we can see that an entry created with repo looks like the standard Radmind log messages above. The client hostname and IP address are reported after the “report” text. The CertificateCN for the client — if the highest authorization level is specified (with the -w2 flag) — is also listed; if not, a dash takes its place. I haven’t seen a case where the second dash is substituted, however.

Finally, where the Radmind command/tool used would normally be, the “event” specified by repo will printed. After that, the message text appears.

The value proposition is that if you’re using Radmind, the repo command can help you send arbitrary messages to the server for logging. As bonus, if you’ve taken the time and effort to build the certificate infrastructure for Radmind, you can send these messages securely between the clients and the server cloaked in SSL.

If you’re using multiple servers, you may want to combine their logs in one location so that you can get all of the clients’ reports in one location. You may also want or need to retain these reports for more time. In either case, determine what policies you should apply to the syslog or Apple System Logger (ASL, for Mac OS X) configuration for your server systems.

Whether or not you use repo, it’s good to know that the tools do some logging. The logging can be followed to try to determine the status of your clients, or whether they are failing their updates.

Unfortunately, the most common client failures I have seen tend to involve the lapply tool, and the default level of detail I’ve seen reported back to the server does not provide an indication of what problem has been encountered. You see only that there was an error. Still, even though you may not get enough detail to remotely resolve the problem, it’s something for you to go by find problems in the first place.

Format numbers with the Python locale module

I’m constantly astounded by the breadth of features available in the Python Standard Library. Although the functions I find there are not always easy to grasp, it is almost always worth searching around a bit for a function or method in the standard library before I write my own code to do something someone else has probably had to do before.

Take the “locale” module. It lets you format certain kinds of data based on your locale and its customs. Numbers (including currency) happen to be one of its specialties. Since I had a need to output long numbers whose digits were grouped with commas — which makes them easier to read — and locale.format() does just that. Even better, it’s internationalized and formats them for your system’s own locale.

>>> import locale
>>> a = {'size': 123456789, 'unit': 'bytes'}
>>> print(locale.format("%(size).2f", a, 1))
123456789.00
>>> locale.setlocale(locale.LC_ALL, '') # Set the locale for your system
'en_US.UTF-8'
>>> print(locale.format("%(size).2f", a, 1))
123,456,789.00

In trying to use it in the bundled Python 2.5.1 on Mac OS X Leopard, I noticed that the default for scripts and the interpreter doesn’t format numbers as I expected for my locale. I found that I needed to set a locale to get the expected formatting. To do this, I run locale.setlocale(), as above. I’m not sure if this is required for other Python installations, but it’s worth mentioning.

One difficulty I created for myself was when I tried mixing more into my format string. For example, locale.format() would fail to reformat a number when I added in the string for the unit from my original dictionary:

>>> print(locale.format("%(size).2f %(unit)s", a, 1))
123456789.00 bytes

In retrospect, this makes total sense, but it took Mark looking over my code to discover my error.

Get Python installation information from Distutils

The Distutils Python module includes functions to obtain information about the Python installation. This may be useful for system administrators, and it certainly caught my eye when I read about it.

The results below are from Apple’s bundled build of Python 2.5.1 in Mac OS X Leopard. Credit for the comments describing each function comes from the distutils.sysconfig documentation.

>>> import distutils.sysconfig
>>> distutils.sysconfig.get_python_version() # Get the major Python version without patchlevel
'2.5'
>>> distutils.sysconfig.get_python_lib(standard_lib=True) # Return the directory containing the Python library; if 'standard_lib' is true, return the directory containing standard Python library modules
'/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5'
>>> distutils.sysconfig.get_python_lib() # Otherwise, return the directory for site-specific modules
'/Library/Python/2.5/site-packages'
>>> distutils.sysconfig.get_python_lib(plat_specific=True) # Return any platform-specific modules from a non-pure-Python module distribution
'/Library/Python/2.5/site-packages'

You’d install your own modules for system-wide use in the directory returned by distutils.sysconfig.get_python_lib().

Computational groups for Leopard directory services

There are occasionally times in system administration where I feel as if I’m on the bleeding edge. They are not frequent … which probably puts me somewhere much closer to the lunatic fringe. These are also the times when no one else probably cares about the same things — but no matter, I generally have my own pragmatic reasons for them. This is one of those times, and I have waited for this particular feature for a while. I’m happy to have discovered it.

Let’s set up a scenario: you have a Mac OS X Tiger system bound to an Active Directory. All of the thousands of Active Directory users are available on that Tiger system; any one of them could log in to that computer. This isn’t bad if you only allow logins at the login window. However, if you enable any sharing services, the game changes and your exposure widens.

What if you don’t want everyone in your enterprise directory to log in? This gets a little scary to think about. What if you want to limit those who can log in, particularly for services like SSH? How do you let some users in but keep others out — even going to the point of denying access to whole classes of users?

There are a number of things you can do, some at the client and some in the directory. Unfortunately, some kinds of configuration must be done at the client. There isn’t a great way I’m aware of to distinguish between local and network accounts, complicates the situation for the administrator. With Open Directory in Tiger, any user from any directory service was as valid as any other. There will probably be some manual effort — ongoing effort at that — required to restrict access.

Enter Mac OS X Leopard. It now has “computational groups” in its Open Directory client. They are like smart groups in your local node. The memberships of these groups are calculated, rather than static. They are more like query-based groups.

For the scenario above, the two computational groups that provide the most utility are probably “localaccounts” and “netaccounts.” They allow you to distinguish users who are in the DSLocal database from those in Active Directory. This magic gives the glue we need to maintain finer-grained access controls automatically over time; you could use these groups for the sshd server configuration or in the authorization database at /private/etc/authorization, as examples.

Let’s try the groups out with dsmemberutil, with which we can determine whether or not a user is a member of a specific group. What about the first local user, typically created by the Mac OS X Setup Assistant?

$ dsmemberutil checkmembership -u 501 -G localaccounts
user is a member of the group
$ dsmemberutil checkmembership -u 501 -G netaccounts
user is not a member of the group

Digging further, all of the computational groups I’ve found had the same comment. We can search for that to find the rest, to find all of the computational groups that ship with a Leopard client.

$ dscl . -search /Groups Comment "Group membership calculated by system" | awk '/user|account/ { print $1 }'
authedusers
consoleusers
interactusers
localaccounts
netaccounts
netusers

From that list, we can determine more information about the groups:

Short name Real name GID Notes
authedusers Authenticated Users 50
consoleusers Terminal Server User 53
interactusers Interactive 51
netaccounts Network Accounts 61 Accounts from a non-local database
localaccounts Local Accounts 62 Accounts residing only in one of the local databases
netusers Network 52

Unfortunately, I couldn’t find any other documentation about the computational groups. If you do find some, I’d love to hear about it.

Syndicate content