Xcode 3.2: Using GDB as a Non-Admin User

New in Xcode 3.2 is an authorization setting that looks like this:

<dict>
	<key>allow-root</key>
	<false/>
	<key>class</key>
	<string>rule</string>
	<key>comment</key>
	<string>For use by Apple.  WARNING: administrators are advised not to
	        modify this right.</string>
	<key>k-of-n</key>
	<integer>1</integer>
	<key>rule</key>
	<array>
		<string>is-admin</string>
		<string>is-developer</string>
		<string>authenticate-developer</string>
	</array>
	<key>shared</key>
	<true/>
</dict>

The upshot of this is that if you aren’t in the _developer group in the local directory, you’ll have to authenticate as an administrator to use gdb or some of the performance tools. For the vast majority of developers on Mac OS X, who run as an administrator, this is fine, but if you’re running as a regular user, either for security reasons or because you’re in something like a lab setting, this can be a problem. To add a user to the _developer group, use the dscl command:

dscl . -append /Groups/_developer GroupMembership UserName

Replace UserName with the short name of your user account (or $(whoami)) and you should be all set.

If you’re administering Mac OS X in a lab setting, you can either create a LaunchAgent that handles this or a login hook. See the Apple tech note “Running At Login” for more information on login hooks. As an added touch, my login and logout scripts to handle this also remove all users from the group, like so:

dscl . -delete /Groups/_developer GroupMembership

If the GroupMembership key doesn’t exist, dscl will create it—and it doesn’t exist by default—so deleting it outright shouldn’t cause any problems.

Dealing with Special Characters in iPhone 4 Graphics Filenames with Subversion

With the iPhone 4’s high-resolution screen, designers need to create two sets of art; the guidelines are to name the files like so: SomeCoolImage.png and SomeCoolImage@2x.png. Unfortunately, if you try to add these files to an SVN repository, the @ symbol throws them off:

$ svn add Icon\@2x~iphone.png 
svn: warning: 'Icon' not found

The fix, thanks to the subversion_users Google Group, is to add another @ to the end of the filename, like so:

$ svn add ./Icon\@2x~iphone.png@ 
A  (bin)  Icon@2x~iphone.png

If you’d like to do this for all of your high-resolution art in a folder, here’s a tiny Bash command for the task:

for x in `ls *\@*`; do svn add $x\@; done

How-To: Run a LaunchDaemon That Requires Networking

I’m a big fan of using launchd to automate things in Mac OS X. That serves me well, as that’s how Apple wants things done moving forward. That said, one of launchd’s biggest shortcomings is a lack of a dependency system. There is currently no way, for instance, to specify in a LaunchDaemon’s property list that the daemon requires the network to be active in order to run. This is problematic for some things, such as a script I wrote to automatically set the computer’s hostname based on the DNS server (more on that later). Luckily, Apple has already defined a function, CheckForNetwork, in /private/etc/rc.common. Here it is in all its glory:

##
# Determine if the network is up by looking for any non-loopback
# internet network interfaces.
##
CheckForNetwork()
{
	local test

	if [ -z "${NETWORKUP:=}" ]; then
		test=$(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l)
		if [ "${test}" -gt 0 ]; then
			NETWORKUP="-YES-"
		else
			NETWORKUP="-NO-"
		fi
	fi
}

In your code, simply include rc.common, then call CheckForNetwork as needed. An example:

#!/bin/bash

# Example Daemon Starter
. /etc/rc.common

CheckForNetwork

while [ "${NETWORKUP}" != "-YES-" ]
do
        sleep 5
        NETWORKUP=
        CheckForNetwork
done

# Now do what you need to do.

Note that this will keep the script running indefinitely until CheckForNetwork sets NETWORKUP to “-YES-,” so if there’s a networking problem your code may never execute.

Updating dyld Shared Caches with Radmind: Best Practices

Similar to my last post about updating kernel extensions, you can run into problems with Radmind due to the dyld shared cache. You may see messages like this in your system log:

current cache invalid because /System/Library/Frameworks/WebKit.framework/Versions/A/WebKit has changed

Running the update_dyld_shared_cache command will fix this, but there’s a better way. Sure, there’s almost no overhead to that command, but where’s the fun in that? Here’s a pre-apply script that will delete any shared caches that have changed, which will then be re-built at reboot.

#!/bin/sh

# update_dyld_caches: Inspects the applicable transcript for something that
#                     might cause a dyld cache to become outdated. If it exists,
#                     delete the cache so it's re-created at startup.

DYLD_CACHE_FOLDER="/private/var/db/dyld"
DYLD_PREFIX="dyld_shared_cache_"
ARCHITECTURES="i386 x86_64 rosetta ppc ppc64"

for arch in ${ARCHITECTURES}; do
	cache="${DYLD_CACHE_FOLDER}/${DYLD_PREFIX}${arch}"
	map="${DYLD_CACHE_FOLDER}/${DYLD_PREFIX}${arch}.map"

	if /bin/test -f "${cache}"; then
		if /bin/test -f "${map}"; then
			/bin/cat "${map}" | grep ^/ | sort --unique --ignore-case | while read line; do
				if /bin/test -n "$(grep ${line} ${1})"; then # found a match
					/bin/rm -f "${cache}"
					/bin/rm -f "${map}"
					break;
				fi
			done
		else
			# Cache exists, but there's no map.
			/bin/rm -f "${cache}"
		fi
	fi
done

Updating Kernel Extensions with Radmind: Best Practices

One of the problems that I’ve run into so far using Radmind to manage Mac OS X—specifically, the Leopard to Snow Leopard transition—is that kextd helpfully starts recreating your kernel extension cache as soon as you modify anything in /System/Library/Extensions. This can be problematic when you’re updating core system files; as you update the 10.5 kernel extensions to their 10.6 counterparts, you don’t want the 10.5 version of kextd creating a cache of 10.6 kernel extensions, especially as the kernel extension cache has moved (from /System/Library/Extensions.mkext to /System/Library/Caches/com.apple.kext.caches/). So, should you handle this? My solution is to stop kextd if I know that I’m updating kernel extensions; that way, they won’t be re-created until reboot. Here’s the script:

/private/var/radmind/preapply/update_kernel_extensions:

#!/bin/sh

# update_kernel_extensions: Manage the replacement of old kernel extensions.
#                           If there are updates, kill kextd and destroy the
#                           caches.

KEXT_CACHE="/System/Library/Caches/com.apple.kext.caches"
KEXT_FOLDER="/System/Library/Extensions"
KEXTD_LAUNCHD="com.apple.kextd"
SYSTEM_LAUNCHD_FOLDER="/System/Library/LaunchDaemons"

transcript="${1}"
result="${transcript}.$$"

/usr/bin/grep "${KEXT_FOLDER}" "${transcript}" > "${result}"

if test -n "${result}"; then #result is non-empty
    # Disable kextd to prevent it from recreating kernel extension caches, which
    # will be re-created at startup.
    if test -n "$(/bin/launchctl list | /usr/bin/grep ${KEXTD_LAUNCHD})"; then
        #kextd is running
        /bin/launchctl unload "${SYSTEM_LAUNCHD_FOLDER}/${KEXTD_LAUNCHD}.plist"
    fi

    # Remove kernel extension cache.
    /bin/rm -rf "${KEXT_CACHE}"
fi

rm -f "${result}"

In its present form, it only works on Snow Leopard, but I’ll be updating it to work on Leopard as well.