Skip to content

Posts tagged ‘scripting’

20
Jul

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.

10
Jul

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
1
Jul

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.

15
Jun

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

27
May

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.

30
Mar

Using AppleScript to Automate an iChat Video Chat

I had to write a script to automatically start iChat at login and start a video chat with a specified screenname. I wanted to only start the chat if the user was online and available, and quit iChat on an error or if the chat ended. So here’s the script I have:

using terms from application "iChat"
	tell application "iChat"
		activate
		log in
		delay 5
		set theBuddy to buddy "ScreennameGoesHere" of service "AIM"
		try
			set theStatus to status of theBuddy
		on error errmesg number errno
			set message to display dialog "The user is currently unavailable." buttons {"OK"}
			quit
			return
		end try
		if theStatus is available then
			set theCapabilities to get capabilities of theBuddy
			if (theCapabilities contains multiperson video) then
				send "A user is attempting to contact you." to theBuddy
				delay 2
				tell service "AIM" to make video chat with properties {participants:theBuddy}
				set theChat to result
				delay 30
				try
					set theStatus to the av connection status of theChat
				on error errmesg number errno
					quit
					return
				end try
				repeat while theStatus is not ended
					delay 5
					try
						set theStatus to the av connection status of theChat
					on error errmesg number errno
						quit
						return
					end try
				end repeat
				quit
				return
			else
				set message to display dialog "The user cannot video chat at this time. Please try again later." buttons {"OK"}
				quit
				return
			end if
		else
			set message to display dialog "The user is currently unavailable." buttons {"OK"}
			quit
			return
		end if
	end tell
end using terms from

A couple of gotchas:

  • I tried using video chat instead of multiperson video, but that always returned false. I don’t know why.
  • Once the video chat has ended, you can’t poll its status (hence the try block).
13
Feb

Using Apple’s SimplePing on iPhone OS

If you try out of the box to compile Apple’s “SimplePing” code sample on the iPhone OS, you’ll wind up with a lot of errors as some files don’t exist in those SDKs. Specifically, you need these files (you need more than just these files to compile, obviously, but these are the ones that aren’t included):

  • /usr/include/netinet/ip.h
  • /usr/include/netinet/in_systm.h
  • /usr/include/netinet/ip_icmp.h
  • /usr/include/netinet/ip_var.h

So here’s a quick Bash script that links the relevant files to your iPhone OS and iPhone Simulator SDKs:

for path in /Developer/Platforms/iPhone*/Developer/SDKs/*; do
    for file in /usr/include/netinet/ip.h \
                /usr/include/netinet/in_systm.h \
                /usr/include/netinet/ip_icmp.h \
                /usr/include/netinet/ip_var.h; do
        if [ ! -f "${path}${file}" ]; then
            sudo ln "${file}" "${path}${file}"
        fi;
    done;
done


I’ve spoken to an Apple engineer and confirmed that this is the best way to do it, as well as filed a bug, which I encourage you to do as well if this annoys you.

19
May

Automatically get the latest Chromium snapshot with launchd

I’ve been checking out the snapshots of Chromium recently, and they’re coming quicker than you can say “multithreaded web browser.” To facilitate always having the latest version, I wrote a quick LaunchAgent that takes care of it on Mac OS X. First, I have a script named ~/bin/chromiupdate:

#!/bin/bash

# Downloads the latest version of Chromium.

remove_working_dir()
{
    rm -rf "${WORKING_DIR}"
    exit 0
}

USER_DIR=$(dscl . -read /Users/$(whoami) NFSHomeDirectory | awk '{ print $2 }')
USER_APP_DIR="${USER_DIR}/Applications"
CHROMIUM_DIR="${USER_APP_DIR}/Chromium.app"
LATEST_URL="http://build.chromium.org/buildbot/snapshots/sub-rel-mac/LATEST"
TMP_DIR="/private/tmp"
WORKING_DIR="${TMP_DIR}/.chromium_launchd"
URL_BEGIN="http://build.chromium.org/buildbot/snapshots/sub-rel-mac"

if [ ! -d "${CHROMIUM_DIR}" ]; then
    mkdir -p "${CHROMIUM_DIR}"
fi

INSTALLED_VERSION="$(defaults read "${CHROMIUM_DIR}/Contents/Info" SVNRevision)"
VERSION=$(curl "${LATEST_URL}")

if [ "${VERSION}" != "${INSTALLED_VERSION}" ]; then
    logger Installed Chromium version \(${INSTALLED_VERSION}\) does not equal \
            latest version \(${VERSION}\), updating now...
    mkdir "${WORKING_DIR}" || exit 1
    trap remove_working_dir 1 2 3 6 15
    cd "${WORKING_DIR}" || exit 1
    curl -O "${URL_BEGIN}/${VERSION}/chrome-mac.zip"
    unzip chrome-mac.zip
    rsync -HavP --exclude="Contents/MacOS/chrome_debug.log" \
          "${WORKING_DIR}/chrome-mac/Chromium.app/" "${CHROMIUM_DIR}/"

    if [ "$(ps -aef | grep -i chromium | grep -v grep)" != "" ]; then
        open "${USER_DIR}/Library/Scripts/Chromium Update Dialog.app"
    fi

    logger "Chromium update complete. Version ${VERSION} installed."

    remove_working_dir
else
    logger Installed Chromium version \(${INSTALLED_VERSION}\) is up-to-date. \
           No action needed.
fi

exit 0


Next, I have a property list named ~/Library/LaunchAgents/com.slaunchaman.chromium.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.slaunchaman.chromium</string>
        <key>Program</key>
        <string>/Users/slauncha/bin/chromiupdate</string>
        <key>KeepAlive</key>
        <false/>
        <key>StartInterval</key>
        <integer>3600</integer>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardOutPath</key>
        <string>/dev/null</string>
        <key>StandardErrorPath</key>
        <string>/dev/null</string>
    </dict>
</plist>

Finally, I have an AppleScript at ~/Library/Scripts/Chromium Update Dialog.app:

display dialog "Chromium was just updated. You should restart it."

The LaunchAgent runs once an hour, checking to see if the installed version of Chromium is older than the latest snapshot. If so, it downloads it and uses rsync to copy the changes. The script places Chromium in ~/Applications, but it shouldn’t be hard to modify to put it into /Applications.

18
Jul

Computers are Disgusting

I recently wrote a script to automatically install firmware updates for our Macs. The problem with them is that for some, you have to hold down the power button to finish the installation after the machine has turned itself off. Now I don’t know about you, but I sure as hell don’t want to go around to 900 or so Macs and hold down their power button after installing an update. This script, while it can’t hold down the button for me, does allow student staff to automate the proces up to that point. I don’t have to give them a password they could use to break the system, they can do the update for me, everybody wins. It’s a beautiful system.

Well, today, I was testing the script out. The problem with testing it is that you can only test a given computer once; once the firmware update is installed, it won’t show up as needing to be installed anymore. So, I packed up my stuff and went to our biggest computer lab to do some testing. My test subjects were some “CyberStations,” computers we have with basic web-browsing and e-mail loadsets. They’re relatively unused during the summer, so if I broke any it wouldn’t be a big deal. I went up to one, ran the update, and waited. That’s when smoke came out of the back of the computer as the update ran. Oh shit, I thought, this thing is on fire!

Let’s go back a minute to my description of CyberStations. Their loadset consists of a vanilla Mac OS X Leopard install with some applications taken out, Firefox, Thunderbird, and Adium. Basically, it has the Apple-provided web browser, e-mail client, and IM program, as well as the leading open-source alternative for each. Nothing on these computers is too taxing. As a consequence, the fans never need to run too heavily, as the computer never really gets too hot.

Back to the smoking machine. I was sure it was on fire—where there’s smoke, there’s fire, right? Well, the student employee to whom I was demonstrating the script wasn’t too sure. He placed his hand on the back of the computer (an iMac G5, not that it matters) and didn’t feel any heat. So, if it wasn’t smoke, what was it? That’s when I realized what had happened. The fans, never having been run too hard, hadn’t been blowing a whole lot of dust around. When they ran full speed during the firmware update, dust had come flying out of the computer’s vents. A white, puffy cloud composed largely of the dead skin cells that had fallen off of our users’ skin for years. It was coming out of the bottom, out of the back, and even where the screen met the case. I quickly held my breath and stepped back.

I’m really glad that we have student staff to do the rest of this.

28
May

Updated: Resizer AppleScript

After looking at my AppleScript to resize windows, I’ve decided to make a few updates.  Instead of manually doing the math for each resolution, I’ve created a new variable, desired_width, which is exactly what it sounds like: how wide you want your windows to be.  I’ve also made the other variable names more human-friendly: _nl and _nr are now left_bound and right_bound, respectively, for example.  After adding iTunes to my script, I noticed that it was being resized behind the menubar, so instead of setting the upper bound for all windows at 0, I’ve defined the variable top_bound, which defaults to 22 to account for the height of the menu bar.  If you find that this setting is incorrect (e.g. if you’ve enabled some accessibility settings that change font sizes and therefore the size of the menu bar) you may need to change it; I haven’t found a way to get the height of the menu bar in AppleScript yet—so far I’ve only found it in Java—so if anyone knows feel free to leave a comment.  Finally, after seeing this post by Jamie Matthews, I added some functionality to automatically set bottom_bound to the height of the Dock.

After all of these updates, the script now takes a desired width and moves applications that support AppleScript such that they range horizontally to your desired width, centered on the screen, and stretching from the bottom of the menu bar to the top of the Dock.  In the future, I’d like to make a separate application, perhaps AppleScript-based, that will allow for user customization of how the windows are arranged, allow for custom application settings, and perhaps Spaces integration.

Jeff Kelley’s Blog is Digg proof thanks to caching by WP Super Cache